From 4971ab92d6e50ec5fad8dbf2891b2f88f88858d8 Mon Sep 17 00:00:00 2001 From: Julian Pawlowski <75446+jpawlowski@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:39:35 +0000 Subject: [PATCH] fix(chartdata): use proportional padding for yaxis bounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../tibber_prices/services/get_chartdata.py | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/custom_components/tibber_prices/services/get_chartdata.py b/custom_components/tibber_prices/services/get_chartdata.py index 9caccfd..c7e7ccf 100644 --- a/custom_components/tibber_prices/services/get_chartdata.py +++ b/custom_components/tibber_prices/services/get_chartdata.py @@ -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 - 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 + # 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: + # 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 (€, kr): round to 2 decimals + yaxis_min = round(combined_stats["min"] - padding_below, 2) + yaxis_max = round(combined_stats["max"] + padding_above, 2) 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 + # Fallback for empty data + yaxis_min = 0 + yaxis_max = 100 if subunit_currency else 1.0 return { "currency": currency_obj,