From 61a592b84a3eb17909d0f21a4095ddcfcfbae4f5 Mon Sep 17 00:00:00 2001 From: matei jordache Date: Thu, 2 Apr 2026 00:07:19 -0700 Subject: [PATCH] add SI prefixes to line graph plots --- backend/nodes/save.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/backend/nodes/save.py b/backend/nodes/save.py index a6f87f4..51bcdbf 100644 --- a/backend/nodes/save.py +++ b/backend/nodes/save.py @@ -10,7 +10,10 @@ import tempfile from backend.node_registry import register_node from backend.execution_context import emit_warning, emit_file_download -from backend.data_types import DataField, LineData, MeshModel, datafield_to_uint8, image_to_uint8 +from backend.data_types import ( + DataField, LineData, MeshModel, datafield_to_uint8, image_to_uint8, + _SI_PREFIXES, _PREFIXABLE_UNITS, +) DOWNLOAD_DIR = Path(tempfile.gettempdir()) / "tono-downloads" @@ -223,8 +226,35 @@ class Save: pw = w - margin["left"] - margin["right"] ph = h - margin["top"] - margin["bottom"] + def _si_scale(unit: str, vmin: float, vmax: float) -> tuple[float, str]: + """Pick the best SI prefix for an axis range. Returns (divisor, prefixed_unit).""" + unit = (unit or "").strip() + if not unit or unit not in _PREFIXABLE_UNITS: + return 1.0, unit if unit else "" + peak = max(abs(vmin), abs(vmax)) + if peak == 0: + return 1.0, unit + for scale, prefix in _SI_PREFIXES: + if peak / scale >= 1.0: + return scale, f"{prefix}{unit}" + return _SI_PREFIXES[-1][0], f"{_SI_PREFIXES[-1][1]}{unit}" + xmin, xmax = float(np.nanmin(x)), float(np.nanmax(x)) ymin, ymax = float(np.nanmin(y)), float(np.nanmax(y)) + + x_scale, x_label = _si_scale(x_unit, xmin, xmax) + y_scale, y_label = _si_scale(y_unit, ymin, ymax) + if not x_label: + x_label = "x" + if not y_label: + y_label = "y" + + # Scale data into prefixed units + x = x / x_scale + y = y / y_scale + xmin, xmax = xmin / x_scale, xmax / x_scale + ymin, ymax = ymin / y_scale, ymax / y_scale + if ymax == ymin: ymin, ymax = ymin - 1, ymax + 1 if xmax == xmin: @@ -266,10 +296,6 @@ class Save: [margin["left"], margin["top"], margin["left"] + pw, margin["top"] + ph], outline=(100, 100, 100), width=1, ) - - # Axis labels - x_label = x_unit if x_unit else "x" - y_label = y_unit if y_unit else "y" draw.text((margin["left"] + pw // 2, h - 10), x_label, fill=text_color, font=font, anchor="mb") # Vertical y label — draw rotated y_label_img = Image.new("RGBA", (200, 20), (0, 0, 0, 0))