From 962d2fdf115784028ec008be65f2b428b4d06d70 Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Tue, 28 Jul 2026 23:49:27 +0200 Subject: [PATCH] Implement arc tick generation with 3 tiers across all scale mappings --- web/app.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/web/app.py b/web/app.py index d6d5419..d9ae546 100644 --- a/web/app.py +++ b/web/app.py @@ -344,6 +344,95 @@ def generate_svg(unit, min_val, max_val, left_label, right_label, big_ticks, sma label_size=5.1126, font_key=None): fd = _FONTS.get(font_key) or _FONTS.get(_DEFAULT_FONT) or next(iter(_FONTS.values())) + if scale_type == 'log': + if min_val <= 0 or max_val <= 0: + raise ValueError("Logarithmic scale requires min and max values greater than 0.") + lmin, lmax = math.log(min_val), math.log(max_val) + lspan = lmax - lmin + if lspan == 0: + raise ValueError("Logarithmic scale requires min and max values to differ.") + + def value_to_angle(v): + return ANGLE_MIN + (ANGLE_MAX - ANGLE_MIN) * (math.log(v) - lmin) / lspan + + def major_value(i): + return min_val * (max_val / min_val) ** (i / big_ticks) + + elif scale_type == 'sqrt': + if min_val < 0: + raise ValueError("Square-root scale requires min value ≥ 0.") + span = max_val - min_val + if span <= 0: + raise ValueError("Square-root scale requires max value greater than min.") + + def value_to_angle(v): + return ANGLE_MIN + (ANGLE_MAX - ANGLE_MIN) * math.sqrt((v - min_val) / span) + + def major_value(i): + return min_val + span * (i / big_ticks) ** 2 + + elif scale_type == 'reciprocal': + if min_val <= 0: + raise ValueError("Reciprocal scale requires min and max values greater than 0.") + if not math.isinf(max_val) and max_val <= min_val: + raise ValueError("Reciprocal scale requires max value greater than min.") + if math.isinf(max_val): + def value_to_angle(v): + if math.isinf(v): + return ANGLE_MIN + return ANGLE_MIN + (ANGLE_MAX - ANGLE_MIN) * min_val / v + + 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 + + def value_to_angle(v): + return ANGLE_MIN + (ANGLE_MAX - ANGLE_MIN) * (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 + + def value_to_angle(v): + if span == 0: + return ANGLE_MIN + return ANGLE_MIN + (ANGLE_MAX - ANGLE_MIN) * (v - min_val) / span + + def major_value(i): + return min_val + span * i / big_ticks + + big_interval_angle = (ANGLE_MAX - ANGLE_MIN) / big_ticks + big_angles = [ANGLE_MIN + i * big_interval_angle for i in range(big_ticks + 1)] + big_vals = [major_value(i) for i in range(big_ticks + 1)] + + if custom_labels: + label_count = max(1, min(len(custom_labels), big_ticks + 1)) + custom_labels = custom_labels[:label_count] + else: + label_count = max(2, min(label_count, big_ticks + 1)) + + if label_count == 1: + label_indices = [0] + else: + label_indices = [round(k * big_ticks / (label_count - 1)) for k in range(label_count)] + + def _tick_line(angle_deg, length, width): + x = PIVOT_X + y1 = PIVOT_Y - R0 + y2 = PIVOT_Y - R0 - length + return ( + f' ' + ) + font_face = ( '