Add infinity max option for reciprocal scale
This commit is contained in:
+30
-8
@@ -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))))
|
||||
|
||||
Reference in New Issue
Block a user