Fix final-review findings: unit-label baseline, sqrt minor-tick math, dead code, edge cases
- Unit label was vertical-centered on a box derived from a baseline coordinate, shifting it 3.34mm too low and making the generated deliverables inconsistent with E440-Scale_template.svg. UNIT_Y is now used directly as the baseline; regenerated E440-Scale_paths.svg and E440-Scale_text.svg accordingly. - sqrt scale's minor-tick angle formula double-applied the square-root mapping, badly misplacing minor ticks. Removed the special case so sqrt falls through to the already-correct generic value-interpolation branch. Corrected README's sqrt row (crowds toward the high end, not low). - Removed dead code: unused _has_markup(), identity-transform wrapper and _UNIT_SCALE_X in _markup_unit_svg, unreachable max_val<=0 check, redundant math.isinf() in the minor-tick condition. - Linear scale with min==max now raises ValueError like the other three mappings, instead of silently producing a degenerate dial. - Form numeric parsing moved inside the try/except so bad input returns 400 instead of crashing with 500. - left_label/right_label now split on splitlines() to handle CRLF from browser textareas correctly. - Formatting consistency: PIVOT_CIRCLE_R/R0 use :.5f like other constants; font_face indentation matches its sibling elements. - LICENSE and CLAUDE.md: last M1730-Scale references and a doc inaccuracy about which labels are live text vs. outlined paths. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+22
-34
@@ -99,7 +99,6 @@ _FRAC_SIDE = 0.12
|
||||
_UNIT_BOX_X0, _UNIT_BOX_X1 = UNIT_X - 15.0, UNIT_X + 15.0
|
||||
_UNIT_BOX_Y0, _UNIT_BOX_Y1 = UNIT_Y - 6.0, UNIT_Y + 6.0
|
||||
_UNIT_MAX_FS = 9.46938021
|
||||
_UNIT_SCALE_X = 1.0
|
||||
|
||||
|
||||
def _point_at(radius: float, angle_deg: float):
|
||||
@@ -271,30 +270,23 @@ def _draw_nodes(nodes, x: float, y: float, fs: float, fill: str, fd: _FontData):
|
||||
return parts, cx - x
|
||||
|
||||
|
||||
def _has_markup(s: str) -> bool:
|
||||
return any(c in s for c in '^_') or ('{' in s and '}' in s)
|
||||
|
||||
|
||||
def _markup_unit_svg(unit: str, fd: _FontData, fill='#1a1a1a') -> str:
|
||||
nodes = _parse_markup(unit)
|
||||
x0, x1 = _UNIT_BOX_X0, _UNIT_BOX_X1
|
||||
y0, y1 = _UNIT_BOX_Y0, _UNIT_BOX_Y1
|
||||
xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
|
||||
xc = (x0 + x1) / 2
|
||||
|
||||
w1 = _measure(nodes, 1.0, fd)
|
||||
top1, bot1 = _vbounds(nodes, 1.0, fd)
|
||||
fs = _UNIT_MAX_FS
|
||||
if w1 > 0:
|
||||
fs = min(fs, (x1 - x0) / (w1 * _UNIT_SCALE_X))
|
||||
fs = min(fs, (x1 - x0) / w1)
|
||||
if bot1 - top1 > 0:
|
||||
fs = min(fs, (y1 - y0) / (bot1 - top1))
|
||||
|
||||
w = _measure(nodes, fs, fd)
|
||||
top, bot = _vbounds(nodes, fs, fd)
|
||||
y_base = yc - (top + bot) / 2
|
||||
parts, _ = _draw_nodes(nodes, xc - w / 2, y_base, fs, fill, fd)
|
||||
cond = f'translate({xc:.5f},0) scale({_UNIT_SCALE_X},1) translate({-xc:.5f},0)'
|
||||
return f'<g transform="{cond}">\n{chr(10).join(parts)}\n</g>'
|
||||
parts, _ = _draw_nodes(nodes, xc - w / 2, UNIT_Y, fs, fill, fd)
|
||||
return '\n'.join(parts)
|
||||
|
||||
|
||||
def _text_to_paths(text, x, y, font_size, fd: _FontData, anchor='start', fill='#1a1a1a', wrap_transform=None):
|
||||
@@ -387,8 +379,6 @@ def generate_svg(unit, min_val, max_val, left_label, right_label, big_ticks, sma
|
||||
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):
|
||||
@@ -399,10 +389,10 @@ def generate_svg(unit, min_val, max_val, left_label, right_label, big_ticks, sma
|
||||
|
||||
else:
|
||||
span = max_val - min_val
|
||||
if span == 0:
|
||||
raise ValueError("Linear scale requires min and max values to differ.")
|
||||
|
||||
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):
|
||||
@@ -434,7 +424,7 @@ def generate_svg(unit, min_val, max_val, left_label, right_label, big_ticks, sma
|
||||
)
|
||||
|
||||
font_face = (
|
||||
'<defs><style>'
|
||||
' <defs><style>'
|
||||
f'@font-face {{font-family:"{fd.label}";'
|
||||
f'src:url("data:font/truetype;base64,{fd.b64}") format("truetype");}}'
|
||||
'</style></defs>'
|
||||
@@ -463,14 +453,14 @@ def generate_svg(unit, min_val, max_val, left_label, right_label, big_ticks, sma
|
||||
parts.append(
|
||||
f' <path style="fill:none;stroke:#000000;stroke-width:0.484;'
|
||||
f'stroke-linecap:round;stroke-miterlimit:10"'
|
||||
f' d="M {p0[0]:.5f},{p0[1]:.5f} A {R0},{R0} 0 0 1 {pmid[0]:.5f},{pmid[1]:.5f}'
|
||||
f' A {R0},{R0} 0 0 1 {p1[0]:.5f},{p1[1]:.5f}" />'
|
||||
f' d="M {p0[0]:.5f},{p0[1]:.5f} A {R0:.5f},{R0:.5f} 0 0 1 {pmid[0]:.5f},{pmid[1]:.5f}'
|
||||
f' A {R0:.5f},{R0:.5f} 0 0 1 {p1[0]:.5f},{p1[1]:.5f}" />'
|
||||
)
|
||||
|
||||
parts.append(
|
||||
f' <circle style="fill:none;stroke:#b3b3b3;stroke-width:0.184;'
|
||||
f'stroke-linecap:round;stroke-miterlimit:10"'
|
||||
f' cx="{PIVOT_X:.5f}" cy="{PIVOT_Y:.5f}" r="{PIVOT_CIRCLE_R}" />'
|
||||
f' cx="{PIVOT_X:.5f}" cy="{PIVOT_Y:.5f}" r="{PIVOT_CIRCLE_R:.5f}" />'
|
||||
)
|
||||
|
||||
for angle in big_angles:
|
||||
@@ -481,9 +471,7 @@ def generate_svg(unit, min_val, max_val, left_label, right_label, big_ticks, sma
|
||||
medium_j = sub // 2 if small_ticks % 2 == 1 else None
|
||||
for i in range(big_ticks):
|
||||
for j in range(1, small_ticks + 1):
|
||||
if scale_type == 'sqrt':
|
||||
angle = big_angles[i] + (j / sub) ** 2 * (big_angles[i + 1] - big_angles[i])
|
||||
elif scale_type == 'reciprocal' or math.isinf(big_vals[i]):
|
||||
if scale_type == 'reciprocal':
|
||||
angle = big_angles[i] + (j / sub) * (big_angles[i + 1] - big_angles[i])
|
||||
else:
|
||||
v = big_vals[i] + j * (big_vals[i + 1] - big_vals[i]) / sub
|
||||
@@ -507,8 +495,8 @@ def generate_svg(unit, min_val, max_val, left_label, right_label, big_ticks, sma
|
||||
|
||||
parts.append(' ' + _markup_unit_svg(unit, fd))
|
||||
|
||||
left_lines = left_label.split('\n')[:2]
|
||||
right_lines = right_label.split('\n')[:2]
|
||||
left_lines = left_label.splitlines()[:2]
|
||||
right_lines = right_label.splitlines()[:2]
|
||||
|
||||
for i, line in enumerate(left_lines):
|
||||
if not line:
|
||||
@@ -548,23 +536,23 @@ def index():
|
||||
@app.route('/generate', methods=['POST'])
|
||||
def generate():
|
||||
unit = request.form.get('unit', '%')
|
||||
min_val = float(request.form.get('min', 0))
|
||||
max_val = float(request.form.get('max', 100))
|
||||
left_label = request.form.get('left_label', '')
|
||||
right_label = request.form.get('right_label', '')
|
||||
big_ticks = max(1, min(50, int(request.form.get('big_ticks', 10))))
|
||||
small_ticks = max(0, min(20, int(request.form.get('small_ticks', 4))))
|
||||
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')
|
||||
font_key = request.form.get('font', _DEFAULT_FONT)
|
||||
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', 5.1126))))
|
||||
|
||||
try:
|
||||
min_val = float(request.form.get('min', 0))
|
||||
max_val = float(request.form.get('max', 100))
|
||||
big_ticks = max(1, min(50, int(request.form.get('big_ticks', 10))))
|
||||
small_ticks = max(0, min(20, int(request.form.get('small_ticks', 4))))
|
||||
label_count = max(2, int(request.form.get('label_count', 6)))
|
||||
label_size = max(0.5, min(25.0, float(request.form.get('label_size', 5.1126))))
|
||||
if request.form.get('max_is_inf') == '1':
|
||||
max_val = math.inf
|
||||
|
||||
svg = generate_svg(unit, min_val, max_val, left_label, right_label, big_ticks,
|
||||
small_ticks, label_count, use_paths, scale_type, custom_labels,
|
||||
label_size, font_key)
|
||||
|
||||
Reference in New Issue
Block a user