diff --git a/server/src/wled_controller/static/app.js b/server/src/wled_controller/static/app.js index ab54e4a..13722fb 100644 --- a/server/src/wled_controller/static/app.js +++ b/server/src/wled_controller/static/app.js @@ -1297,23 +1297,28 @@ function renderCalibrationCanvas() { const count = seg.led_count; if (count === 0) return; - // Mandatory ticks: first and last LED index per edge, plus LED 0 if offset > 0 - const labelsToShow = new Set(); - labelsToShow.add(0); - if (count > 1) labelsToShow.add(count - 1); + // Edge boundary ticks (first/last LED on edge) and special ticks (LED 0 position) + const edgeBounds = new Set(); + edgeBounds.add(0); + if (count > 1) edgeBounds.add(count - 1); + const specialTicks = new Set(); if (offset > 0 && totalLeds > 0) { const zeroPos = (totalLeds - seg.led_start % totalLeds) % totalLeds; - if (zeroPos < count) labelsToShow.add(zeroPos); + if (zeroPos < count) specialTicks.add(zeroPos); } - // Add intermediate ticks at "nice" intervals (max 5 labels per edge) + // Round-number ticks get priority; edge boundary labels suppressed if overlapping + const labelsToShow = new Set([...specialTicks]); + const tickLinesOnly = new Set(); + if (count > 2) { const edgeLen = geo.horizontal ? (geo.x2 - geo.x1) : (geo.y2 - geo.y1); const maxDigits = String(totalLeds > 0 ? totalLeds - 1 : count - 1).length; const minSpacing = geo.horizontal ? maxDigits * 7 + 8 : 22; - const maxIntermediate = Math.max(0, 5 - labelsToShow.size); + const allMandatory = new Set([...edgeBounds, ...specialTicks]); + const maxIntermediate = Math.max(0, 5 - allMandatory.size); const niceSteps = [5, 10, 25, 50, 100, 250, 500]; let step = niceSteps[niceSteps.length - 1]; for (const s of niceSteps) { @@ -1323,18 +1328,17 @@ function renderCalibrationCanvas() { } } - // Pixel position helper (0..edgeLen along the edge) const tickPx = i => { const f = i / (count - 1); return (seg.reverse ? (1 - f) : f) * edgeLen; }; - // Collect pixel positions of mandatory ticks + // Phase 1: place round-number ticks (checked against specials + each other) const placed = []; - labelsToShow.forEach(i => placed.push(tickPx(i))); + specialTicks.forEach(i => placed.push(tickPx(i))); - // Add ticks at LED indices divisible by step for (let i = 1; i < count - 1; i++) { + if (specialTicks.has(i)) continue; const idx = totalLeds > 0 ? (seg.led_start + i) % totalLeds : seg.led_start + i; if (idx % step === 0) { const px = tickPx(i); @@ -1344,9 +1348,23 @@ function renderCalibrationCanvas() { } } } + + // Phase 2: edge boundaries — show label unless overlapping a round-number tick + edgeBounds.forEach(bi => { + if (labelsToShow.has(bi) || specialTicks.has(bi)) return; + const px = tickPx(bi); + if (placed.some(p => Math.abs(px - p) < minSpacing)) { + tickLinesOnly.add(bi); + } else { + labelsToShow.add(bi); + placed.push(px); + } + }); + } else { + edgeBounds.forEach(i => labelsToShow.add(i)); } - // Tick styling — min/max ticks extend to container border, others short + // Tick styling const tickLenLong = toggleSize + 3; const tickLenShort = 4; ctx.strokeStyle = tickStroke; @@ -1354,45 +1372,66 @@ function renderCalibrationCanvas() { ctx.fillStyle = tickFill; ctx.font = '12px -apple-system, BlinkMacSystemFont, sans-serif'; + // Draw labeled ticks labelsToShow.forEach(i => { const fraction = count > 1 ? i / (count - 1) : 0.5; const displayFraction = seg.reverse ? (1 - fraction) : fraction; const ledIndex = totalLeds > 0 ? (seg.led_start + i) % totalLeds : seg.led_start + i; - const tickLen = (i === 0 || i === count - 1) ? tickLenLong : tickLenShort; + const tickLen = edgeBounds.has(i) ? tickLenLong : tickLenShort; if (geo.horizontal) { const tx = geo.x1 + displayFraction * (geo.x2 - geo.x1); const axisY = axisPos[seg.edge]; - const tickDir = seg.edge === 'top' ? 1 : -1; // tick toward container + const tickDir = seg.edge === 'top' ? 1 : -1; - // Tick line ctx.beginPath(); ctx.moveTo(tx, axisY); ctx.lineTo(tx, axisY + tickDir * tickLen); ctx.stroke(); - // Label outside ctx.textAlign = 'center'; ctx.textBaseline = seg.edge === 'top' ? 'bottom' : 'top'; ctx.fillText(String(ledIndex), tx, axisY - tickDir * 1); } else { const ty = geo.y1 + displayFraction * (geo.y2 - geo.y1); const axisX = axisPos[seg.edge]; - const tickDir = seg.edge === 'left' ? 1 : -1; // tick toward container + const tickDir = seg.edge === 'left' ? 1 : -1; - // Tick line ctx.beginPath(); ctx.moveTo(axisX, ty); ctx.lineTo(axisX + tickDir * tickLen, ty); ctx.stroke(); - // Label outside ctx.textBaseline = 'middle'; ctx.textAlign = seg.edge === 'left' ? 'right' : 'left'; ctx.fillText(String(ledIndex), axisX - tickDir * 1, ty); } }); + // Draw tick lines only (no labels) for suppressed edge boundaries + tickLinesOnly.forEach(i => { + const fraction = count > 1 ? i / (count - 1) : 0.5; + const displayFraction = seg.reverse ? (1 - fraction) : fraction; + + if (geo.horizontal) { + const tx = geo.x1 + displayFraction * (geo.x2 - geo.x1); + const axisY = axisPos[seg.edge]; + const tickDir = seg.edge === 'top' ? 1 : -1; + ctx.beginPath(); + ctx.moveTo(tx, axisY); + ctx.lineTo(tx, axisY + tickDir * tickLenLong); + ctx.stroke(); + } else { + const ty = geo.y1 + displayFraction * (geo.y2 - geo.y1); + const axisX = axisPos[seg.edge]; + const tickDir = seg.edge === 'left' ? 1 : -1; + ctx.beginPath(); + ctx.moveTo(axisX, ty); + ctx.lineTo(axisX + tickDir * tickLenLong, ty); + ctx.stroke(); + } + }); + // Draw direction chevron at full-edge midpoint (not affected by span) const s = 7; let mx, my, angle; diff --git a/server/src/wled_controller/static/index.html b/server/src/wled_controller/static/index.html index 7b1a8c7..8b2a919 100644 --- a/server/src/wled_controller/static/index.html +++ b/server/src/wled_controller/static/index.html @@ -84,7 +84,13 @@