@@ -381,28 +381,55 @@ def DisplayLineGraph(self, x: int, y: int, width: int, height: int,
381381 axis_font_size : int = 10 ,
382382 background_color : Color = (255 , 255 , 255 ),
383383 background_image : Optional [str ] = None ,
384- axis_minmax_format : str = "{:0.0f}" ):
384+ axis_minmax_format : str = "{:0.0f}" ,
385+ fill : bool = False ,
386+ fill_color : Optional [Color ] = None ,
387+ antialias : bool = False ):
385388 # Generate a plot graph and display it
386389 # Provide the background image path to display plot graph with transparent background
390+ # fill: if True, fills the area under the line graph
391+ # fill_color: color for the fill area (with optional alpha for transparency)
392+ # antialias: if True, uses 2x supersampling for smoother lines
387393
388394 line_color = parse_color (line_color )
389395 axis_color = parse_color (axis_color )
390396 background_color = parse_color (background_color )
397+ if fill_color is not None :
398+ fill_color = parse_color (fill_color , allow_alpha = True )
399+ else :
400+ # Default fill color: line color with 50% opacity
401+ fill_color = line_color
391402
392403 assert x <= self .get_width (), 'Progress bar X coordinate must be <= display width'
393404 assert y <= self .get_height (), 'Progress bar Y coordinate must be <= display height'
394405 assert x + width <= self .get_width (), 'Progress bar width exceeds display width'
395406 assert y + height <= self .get_height (), 'Progress bar height exceeds display height'
396407
408+ # For antialiasing, work at 2x resolution
409+ scale = 2 if antialias else 1
410+ work_width = width * scale
411+ work_height = height * scale
412+ work_line_width = line_width * scale
413+
414+ original_background = None
397415 if background_image is None :
398416 # A bitmap is created with solid background
399- graph_image = Image .new ('RGB' , (width , height ), background_color )
417+ graph_image = Image .new ('RGB' , (work_width , work_height ), background_color )
400418 else :
401419 # A bitmap is created from provided background image
402420 graph_image = self .open_image (background_image )
403-
404421 # Crop bitmap to keep only the plot graph background
405422 graph_image = graph_image .crop (box = (x , y , x + width , y + height ))
423+ if antialias :
424+ graph_image = graph_image .resize (
425+ (work_width , work_height ), Image .Resampling .LANCZOS
426+ )
427+ graph_image = graph_image .convert ('RGB' )
428+
429+ # Keep a copy of the background for fill compositing
430+ if fill :
431+ original_background = graph_image .copy ()
432+ graph_image = graph_image .convert ('RGBA' )
406433
407434 # if autoscale is enabled, define new min/max value to "zoom" the graph
408435 if autoscale :
@@ -419,16 +446,16 @@ def DisplayLineGraph(self, x: int, y: int, width: int, height: int,
419446 min_value = max (trueMin - 5 , min_value )
420447 max_value = min (trueMax + 5 , max_value )
421448
422- step = width / len (values )
449+ step = work_width / len (values )
423450 # pre compute yScale multiplier value
424- yScale = (height / (max_value - min_value )) if (max_value - min_value ) != 0 else 0
451+ yScale = (( work_height - 1 ) / (max_value - min_value )) if (max_value - min_value ) != 0 else 1
425452
426453 plotsX = []
427454 plotsY = []
428455 count = 0
429456 for value in values :
430457 if not math .isnan (value ):
431- # Don't let the set value exceed our min or max value, this is bad :)
458+ # Don't let the set value exceed our min or max value, this is bad :)
432459 if value < min_value :
433460 value = min_value
434461 elif max_value < value :
@@ -437,32 +464,71 @@ def DisplayLineGraph(self, x: int, y: int, width: int, height: int,
437464 assert min_value <= value <= max_value , 'Plot point value shall be between min and max'
438465
439466 plotsX .append (count * step )
440- plotsY .append (height - (value - min_value ) * yScale )
467+ # Calculate Y position: 0 at top (max_value), work_height-1 at bottom (min_value)
468+ plotsY .append ((work_height - 1 ) - (value - min_value ) * yScale )
441469
442470 count += 1
443471
444472 # Draw plot graph
445- draw = ImageDraw .Draw (graph_image )
446- draw .line (list (zip (plotsX , plotsY )), fill = line_color , width = line_width )
473+ draw = ImageDraw .Draw (graph_image , 'RGBA' if fill else None )
474+
475+ # Fill area under the line if enabled
476+ if fill and len (plotsX ) > 1 :
477+ # Create polygon points: line points + bottom corners
478+ fill_points = list (zip (plotsX , plotsY ))
479+ # Add bottom-right and bottom-left corners to close the polygon
480+ # Use work_height (not work_height-1) to ensure fill reaches the very bottom
481+ fill_points .append ((plotsX [- 1 ], work_height ))
482+ fill_points .append ((plotsX [0 ], work_height ))
483+ # Draw filled polygon with semi-transparent color
484+ if len (fill_color ) == 3 :
485+ # Add alpha channel for transparency (default 80 opacity)
486+ fill_rgba = (fill_color [0 ], fill_color [1 ], fill_color [2 ], 80 )
487+ elif len (fill_color ) == 4 :
488+ fill_rgba = fill_color
489+ else :
490+ fill_rgba = (* fill_color [:3 ], 80 )
491+ draw .polygon (fill_points , fill = fill_rgba )
492+
493+ # Draw the line on top
494+ if len (plotsX ) > 1 :
495+ draw .line (list (zip (plotsX , plotsY )), fill = line_color , width = work_line_width )
447496
448497 if graph_axis :
449498 # Draw axis
450- draw .line ([0 , height - 1 , width - 1 , height - 1 ], fill = axis_color )
451- draw .line ([0 , 0 , 0 , height - 1 ], fill = axis_color )
499+ draw .line ([0 , work_height - 1 , work_width - 1 , work_height - 1 ], fill = axis_color )
500+ draw .line ([0 , 0 , 0 , work_height - 1 ], fill = axis_color )
452501
453502 # Draw Legend
454- draw .line ([0 , 0 , 1 , 0 ], fill = axis_color )
503+ draw .line ([0 , 0 , 1 * scale , 0 ], fill = axis_color )
455504 text = axis_minmax_format .format (max_value )
456- ttfont = self .open_font (axis_font , axis_font_size )
505+ ttfont = self .open_font (axis_font , axis_font_size * scale )
457506 _ , top , right , bottom = ttfont .getbbox (text )
458- draw .text ((2 , 0 - top ), text ,
507+ draw .text ((2 * scale , 0 - top ), text ,
459508 font = ttfont , fill = axis_color )
460509
461510 text = axis_minmax_format .format (min_value )
462511 _ , top , right , bottom = ttfont .getbbox (text )
463- draw .text ((width - 1 - right , height - 2 - bottom ), text ,
512+ draw .text ((work_width - 1 - right , work_height - 2 * scale - bottom ), text ,
464513 font = ttfont , fill = axis_color )
465514
515+ # Scale down for antialiasing
516+ if antialias :
517+ graph_image = graph_image .resize ((width , height ), Image .Resampling .LANCZOS )
518+ if original_background is not None :
519+ original_background = original_background .resize ((width , height ), Image .Resampling .LANCZOS )
520+
521+ # Convert back to RGB if needed
522+ if fill and graph_image .mode == 'RGBA' :
523+ # Composite with original background (not solid color)
524+ if original_background is not None :
525+ original_background .paste (graph_image , mask = graph_image .split ()[3 ])
526+ graph_image = original_background
527+ else :
528+ bg = Image .new ('RGB' , graph_image .size , background_color )
529+ bg .paste (graph_image , mask = graph_image .split ()[3 ])
530+ graph_image = bg
531+
466532 self .DisplayPILImage (graph_image , x , y )
467533
468534 def DrawRadialDecoration (self , draw : ImageDraw .ImageDraw , angle : float , radius : float , width : float , color : Tuple [int , int , int ] = (0 , 0 , 0 )):
0 commit comments