From f19c462c1b1abe493e8e07d20a3232abc5c42517 Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Tue, 14 Jul 2026 23:28:40 +0200 Subject: [PATCH] Add infinity max option for reciprocal scale --- k8s/deployment.yaml | 2 +- web/app.py | 38 ++++++++++++++++++++++++++++++-------- web/templates/index.html | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index c4f422a..841cef7 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -15,7 +15,7 @@ spec: spec: containers: - name: m1730-generator - image: git.baumann.gr/adebaumann/m1730-generator:0.82 + image: git.baumann.gr/adebaumann/m1730-generator:0.83 imagePullPolicy: Always ports: - containerPort: 5000 diff --git a/web/app.py b/web/app.py index d36decc..22cc8e6 100644 --- a/web/app.py +++ b/web/app.py @@ -8,7 +8,7 @@ from fontTools.pens.svgPathPen import SVGPathPen app = Flask(__name__) -VERSION = "0.82" +VERSION = "0.83" _LOGO_PATH = pathlib.Path(__file__).parent / 'static' / 'logo.png' _LOGO_B64 = base64.b64encode(_LOGO_PATH.read_bytes()).decode() @@ -291,6 +291,8 @@ def _unit_font_size(unit: str) -> float: def _fmt(v: float) -> str: + if math.isinf(v): + return '∞' # Snap near-integers — log-scale geometric values accumulate FP error # (e.g. 9999.9999), which would otherwise miss the integer case. r = round(v) @@ -363,17 +365,31 @@ def generate_svg(unit, min_val, max_val, range_label, big_ticks, small_ticks, la return min_val + span * (i / big_ticks) ** 2 elif scale_type == 'reciprocal': - if min_val <= 0 or max_val <= 0: + if min_val <= 0: raise ValueError("Reciprocal scale requires min and max values greater than 0.") - if max_val <= min_val: + if not math.isinf(max_val) and max_val <= min_val: raise ValueError("Reciprocal scale requires max value greater than min.") - k = 1.0 / min_val - 1.0 / max_val # always positive + if math.isinf(max_val): + # Simplified form when max = ∞: x = ZERO_X + SCALE_LEN * min_val / v + def value_to_x(v): + if math.isinf(v): + return ZERO_X + return ZERO_X + SCALE_LEN * min_val / v - def value_to_x(v): - return ZERO_X + SCALE_LEN * (1.0 / v - 1.0 / max_val) / k + def major_value(i): + if i == 0: + return math.inf + return min_val * big_ticks / i + else: + if max_val <= 0: + raise ValueError("Reciprocal scale requires min and max values greater than 0.") + k = 1.0 / min_val - 1.0 / max_val # always positive - def major_value(i): - return 1.0 / (1.0 / max_val + i / big_ticks * k) + def value_to_x(v): + return ZERO_X + SCALE_LEN * (1.0 / v - 1.0 / max_val) / k + + def major_value(i): + return 1.0 / (1.0 / max_val + i / big_ticks * k) else: span = max_val - min_val @@ -471,6 +487,10 @@ def generate_svg(unit, min_val, max_val, range_label, big_ticks, small_ticks, la for j in range(1, small_ticks + 1): if scale_type == 'sqrt': x = big_xs[i] + (j / sub) ** 2 * (big_xs[i + 1] - big_xs[i]) + elif math.isinf(big_vals[i]): + # First interval with ∞ at the left: interpolate in position space + # (linear value interpolation is undefined from ∞). + x = big_xs[i] + (j / sub) * (big_xs[i + 1] - big_xs[i]) else: v = big_vals[i] + j * (big_vals[i + 1] - big_vals[i]) / sub x = value_to_x(v) @@ -511,6 +531,8 @@ def generate(): label_count = max(2, int(request.form.get('label_count', 6))) use_paths = request.form.get('output_mode', 'paths') == 'paths' scale_type = request.form.get('scale_type', 'linear') + if request.form.get('max_is_inf') == '1': + max_val = math.inf custom_labels = [s.strip() for s in request.form.get('labels', '').split(',') if s.strip()] or None label_size = max(0.5, min(25.0, float(request.form.get('label_size', 9)))) diff --git a/web/templates/index.html b/web/templates/index.html index 5199478..323b000 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -330,9 +330,11 @@ -
+
- + + +
@@ -397,6 +399,31 @@