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 = (
'