fix(chartdata): use proportional padding for yaxis bounds

Changed from fixed padding (0.5ct below min, 1ct above max) to
proportional padding based on data range (8% below, 15% above).

This ensures consistent visual "airiness" across all price ranges,
whether prices are at 30ct or 150ct. Both subunit (ct/øre) and
base currency (€/kr) now use the same proportional logic.

Previous fixed padding looked too tight on charts with large price
ranges (e.g., 0.6€-1.5€) compared to charts with small ranges
(e.g., 28-35ct).

Impact: Chart metadata sensor provides better-scaled yaxis_min/yaxis_max
values for all chart cards, making price visualizations more readable
with appropriate whitespace around data regardless of price range.
This commit is contained in:
Julian Pawlowski 2025-12-22 23:39:35 +00:00
parent 49b8a018e7
commit 4971ab92d6

View file

@ -223,16 +223,29 @@ def _calculate_metadata( # noqa: PLR0912, PLR0913, PLR0915
# Determine interval duration in minutes based on resolution
interval_duration_minutes = 15 if resolution == "interval" else 60
# Calculate suggested yaxis bounds
# For subunit currency (ct, øre): integer values (floor/ceil)
# For base currency (€, kr): 2 decimal places precision
# Calculate suggested yaxis bounds with proportional padding
# Goal: Same visual "airiness" regardless of price range
# Strategy: Add padding proportional to data range (min/max spread)
if combined_stats:
data_range = combined_stats["max"] - combined_stats["min"]
# Calculate padding: ~8% of data range below min, ~15% above max
# These percentages match the visual spacing seen in well-scaled charts
padding_below = data_range * 0.08
padding_above = data_range * 0.15
if subunit_currency:
yaxis_min = math.floor(combined_stats["min"]) - 1 if combined_stats else 0
yaxis_max = math.ceil(combined_stats["max"]) + 1 if combined_stats else 100
# Subunit (ct, øre): round to 1 decimal for cleaner axis labels
yaxis_min = round(combined_stats["min"] - padding_below, 1)
yaxis_max = round(combined_stats["max"] + padding_above, 1)
else:
# Base currency: round to 2 decimal places with padding
yaxis_min = round(math.floor(combined_stats["min"] * 100) / 100 - 0.01, 2) if combined_stats else 0
yaxis_max = round(math.ceil(combined_stats["max"] * 100) / 100 + 0.01, 2) if combined_stats else 1.0
# Base currency (€, kr): round to 2 decimals
yaxis_min = round(combined_stats["min"] - padding_below, 2)
yaxis_max = round(combined_stats["max"] + padding_above, 2)
else:
# Fallback for empty data
yaxis_min = 0
yaxis_max = 100 if subunit_currency else 1.0
return {
"currency": currency_obj,