Skip to content

Commit fcfb7ee

Browse files
committed
Label more ticks in relative scatter plots.
1 parent aa81d6d commit fcfb7ee

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

docs/news.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ next (unreleased)
77
Downward Lab
88
^^^
99
* Add buttons for revealing and hiding all tables in an AbsoluteReport section (Jendrik Seipp).
10+
* Label more ticks in relative scatter plots (Jendrik Seipp).
1011

1112

1213
v8.8 (2026-01-17)

downward/reports/scatter_matplotlib.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import logging
2+
import math
23
import sys
34

45
import matplotlib
56
from matplotlib import figure
67
from matplotlib import lines as mlines
78
from matplotlib.backends import backend_agg
8-
from matplotlib.ticker import MaxNLocator
9+
from matplotlib.ticker import FuncFormatter, MaxNLocator, ScalarFormatter
910

1011

1112
class 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

Comments
 (0)