11from  datetime  import  date 
22import  enum 
33import  re 
4+ from  decimal  import  Decimal , localcontext 
45from  typing  import  Optional , List , Tuple , Literal , Any , Union , Sequence 
56
67import  matplotlib 
@@ -35,6 +36,18 @@ def _is_grid_line(line: Line2D) -> bool:
3536    return  False 
3637
3738
39+ def  _dynamic_round (number ):
40+     # Convert to Decimal for precise control 
41+     decimal_number  =  Decimal (str (number ))
42+ 
43+     # Dynamically determine precision based on magnitude 
44+     precision  =  max (1 , 16  -  decimal_number .adjusted ())  # 16 digits of precision 
45+ 
46+     with  localcontext () as  ctx :
47+         ctx .prec  =  precision   # Set the dynamic precision 
48+         return  + decimal_number   # The + operator applies rounding 
49+ 
50+ 
3851class  ChartType (str , enum .Enum ):
3952    LINE  =  "line" 
4053    SCATTER  =  "scatter" 
@@ -291,6 +304,7 @@ class BoxAndWhiskerData(BaseModel):
291304    median : float 
292305    third_quartile : float 
293306    max : float 
307+     outliers : List [float ] =  Field (default_factory = list )
294308
295309
296310class  BoxAndWhiskerChart (Chart2D ):
@@ -301,20 +315,23 @@ class BoxAndWhiskerChart(Chart2D):
301315    def  _extract_info (self , ax : Axes ) ->  None :
302316        super ()._extract_info (ax )
303317
318+         labels  =  [item .get_text () for  item  in  ax .get_xticklabels ()]
319+ 
304320        boxes  =  []
305-         for  box  in  ax .patches :
321+         for  label ,  box  in  zip ( labels ,  ax .patches ) :
306322            vertices  =  box .get_path ().vertices 
307-             x_vertices  =  vertices [:, 0 ]
308-             y_vertices  =  vertices [:, 1 ]
323+             x_vertices  =  [ _dynamic_round ( x )  for   x   in   vertices [:, 0 ] ]
324+             y_vertices  =  [ _dynamic_round ( y )  for   y   in   vertices [:, 1 ] ]
309325            x  =  min (x_vertices )
310326            y  =  min (y_vertices )
311327            boxes .append (
312328                {
313329                    "x" : x ,
314330                    "y" : y ,
315-                     "label" : box .get_label (),
316-                     "width" : round (max (x_vertices ) -  x , 4 ),
317-                     "height" : round (max (y_vertices ) -  y , 4 ),
331+                     "label" : label ,
332+                     "width" : max (x_vertices ) -  x ,
333+                     "height" : max (y_vertices ) -  y ,
334+                     "outliers" : [],
318335                }
319336            )
320337
@@ -328,13 +345,21 @@ def _extract_info(self, ax: Axes) -> None:
328345                box ["x" ], box ["y" ] =  box ["y" ], box ["x" ]
329346                box ["width" ], box ["height" ] =  box ["height" ], box ["width" ]
330347
331-         for  line  in  ax .lines :
332-             xdata  =  line .get_xdata ()
333-             ydata  =  line .get_ydata ()
348+         for  i ,  line  in  enumerate ( ax .lines ) :
349+             xdata  =  [ _dynamic_round ( x )  for   x   in   line .get_xdata ()] 
350+             ydata  =  [ _dynamic_round ( y )  for   y   in   line .get_ydata ()] 
334351
335352            if  orientation  ==  "vertical" :
336353                xdata , ydata  =  ydata , xdata 
337354
355+             if  len (xdata ) ==  1 :
356+                 for  box  in  boxes :
357+                     if  box ["x" ] <=  xdata [0 ] <=  box ["x" ] +  box ["width" ]:
358+                         break 
359+                 else :
360+                     continue 
361+ 
362+                 box ["outliers" ].append (ydata [0 ])
338363            if  len (ydata ) !=  2 :
339364                continue 
340365            for  box  in  boxes :
@@ -344,6 +369,7 @@ def _extract_info(self, ax: Axes) -> None:
344369                continue 
345370
346371            if  (
372+                 # Check if the line is inside the box, prevent floating point errors 
347373                ydata [0 ] ==  ydata [1 ]
348374                and  box ["y" ] <=  ydata [0 ] <=  box ["y" ] +  box ["height" ]
349375            ):
@@ -365,6 +391,7 @@ def _extract_info(self, ax: Axes) -> None:
365391                median = box ["median" ],
366392                third_quartile = box ["y" ] +  box ["height" ],
367393                max = box ["whisker_upper" ],
394+                 outliers = box ["outliers" ],
368395            )
369396            for  box  in  boxes 
370397        ]
0 commit comments