11import logging
2+ import math
23import sys
34
45import matplotlib
56from matplotlib import figure
67from matplotlib import lines as mlines
78from matplotlib .backends import backend_agg
8- from matplotlib .ticker import MaxNLocator
9+ from matplotlib .ticker import FuncFormatter , MaxNLocator , ScalarFormatter
910
1011
1112class MatplotlibPlot :
@@ -82,6 +83,69 @@ def _plot(cls, report, axes):
8283 axes .set_xbound (upper = report .x_upper )
8384 axes .set_ybound (upper = report .y_upper )
8485
86+ @staticmethod
87+ def _format_log_value (x , show_power_of_10 = True ):
88+ """Format a value for log scale axis labels.
89+
90+ Args:
91+ x: The value to format
92+ show_power_of_10: If True, show clean powers of 10 as 10^n notation
93+
94+ Returns:
95+ Formatted string for matplotlib
96+ """
97+ if x <= 0 :
98+ return ""
99+
100+ if show_power_of_10 :
101+ exponent = math .log10 (x )
102+ if abs (exponent - round (exponent )) < 1e-10 :
103+ # It's a power of 10, show as 10^n
104+ return f"$10^{{{ int (round (exponent ))} }}$"
105+
106+ # Not a clean power of 10, show with 2 decimal places
107+ if x >= 1000 or x <= 0.001 :
108+ return f"${ x :.2e} $"
109+ else :
110+ return f"${ x :.2f} $"
111+
112+ @classmethod
113+ def _ensure_all_major_ticks_labeled (cls , axes ):
114+ """Ensure all major ticks have labels on both axes.
115+
116+ This is especially important for log scale axes where matplotlib
117+ may omit labels on some major ticks by default.
118+ """
119+ for axis in [axes .xaxis , axes .yaxis ]:
120+ scale = axis .get_scale ()
121+ if scale == "log" :
122+ formatter = FuncFormatter (
123+ lambda x , pos : cls ._format_log_value (x , show_power_of_10 = True )
124+ )
125+ axis .set_major_formatter (formatter )
126+ elif scale == "linear" :
127+ # For linear scale, use ScalarFormatter
128+ formatter = ScalarFormatter ()
129+ formatter .set_useOffset (False )
130+ axis .set_major_formatter (formatter )
131+
132+ @classmethod
133+ def _add_minor_tick_labels (cls , axes ):
134+ """Add labels to minor ticks on y-axis in a smaller font.
135+
136+ This is used for relative plots to provide more detailed scale information.
137+ """
138+ axis = axes .yaxis
139+ scale = axis .get_scale ()
140+ if scale == "log" :
141+ formatter = FuncFormatter (
142+ lambda x , pos : cls ._format_log_value (x , show_power_of_10 = False )
143+ )
144+ axis .set_minor_formatter (formatter )
145+ # Make minor tick labels smaller
146+ for label in axis .get_minorticklabels ():
147+ label .set_fontsize (label .get_fontsize () * 0.7 )
148+
85149 @classmethod
86150 def write (cls , report , filename ):
87151 MatplotlibPlot .set_rc_params (report .matplotlib_options )
@@ -101,8 +165,14 @@ def write(cls, report, filename):
101165 plot .plot_horizontal_line ()
102166 # Ask for more ticks on y axis in relative plots.
103167 plot .axes .yaxis .set_major_locator (MaxNLocator (nbins = "auto" ))
168+ # Ensure all major grid lines have labels in relative plots.
169+ cls ._ensure_all_major_ticks_labeled (plot .axes )
170+ # Add labels to minor ticks in smaller font for relative plots.
171+ cls ._add_minor_tick_labels (plot .axes )
104172 if report .plot_diagonal_line :
105173 plot .plot_diagonal_line ()
174+ # Ensure all major grid lines have labels in diagonal plots.
175+ cls ._ensure_all_major_ticks_labeled (plot .axes )
106176
107177 if report .has_multiple_categories ():
108178 plot .create_legend ()
0 commit comments