Add infinity max option for reciprocal scale

This commit is contained in:
2026-07-14 23:28:40 +02:00
parent cf844d007b
commit f19c462c1b
3 changed files with 68 additions and 11 deletions
+30 -8
View File
@@ -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))))