Add smart tick labels and move direction arrows inside screen
Some checks failed
Validate / validate (push) Failing after 8s

- Add intermediate tick labels at nice intervals (5, 10, 25, 50, 100, 250, 500)
  with max 5 labels per edge and pixel-based overlap prevention
- Move direction chevrons from outside edge bars to inside the screen area
- Make chevrons filled with green fill and white outline for better contrast

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 05:16:17 +03:00
parent 36ace0c563
commit 57e6754461

View File

@@ -1219,12 +1219,13 @@ function renderCalibrationCanvas() {
right: ox + cW + 3, // labels right of right edge
};
// Arrow positions (further out than tick labels)
// Arrow positions (inside the screen area, near each edge bar)
const arrowInset = 12;
const arrowPos = {
top: oy - 10,
bottom: oy + cH + 10,
left: ox - 10,
right: ox + cW + 10,
top: oy + ch + arrowInset,
bottom: oy + cH - ch - arrowInset,
left: ox + cw + arrowInset,
right: ox + cW - cw - arrowInset,
};
// Draw ticks and direction arrows for each segment
@@ -1235,17 +1236,55 @@ function renderCalibrationCanvas() {
const count = seg.led_count;
if (count === 0) return;
// Show only first and last LED index per edge, plus LED 0 if offset > 0
// 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);
// Add LED 0 tick on the edge where it wraps
if (offset > 0 && totalLeds > 0) {
const zeroPos = (totalLeds - seg.led_start % totalLeds) % totalLeds;
if (zeroPos < count) labelsToShow.add(zeroPos);
}
// Add intermediate ticks at "nice" intervals (max 5 labels per edge)
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 niceSteps = [5, 10, 25, 50, 100, 250, 500];
let step = niceSteps[niceSteps.length - 1];
for (const s of niceSteps) {
if (Math.floor(count / s) <= maxIntermediate) {
step = s;
break;
}
}
// 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
const placed = [];
labelsToShow.forEach(i => placed.push(tickPx(i)));
// Add ticks at LED indices divisible by step
for (let i = 1; i < count - 1; i++) {
const idx = totalLeds > 0 ? (seg.led_start + i) % totalLeds : seg.led_start + i;
if (idx % step === 0) {
const px = tickPx(i);
if (!placed.some(p => Math.abs(px - p) < minSpacing)) {
labelsToShow.add(i);
placed.push(px);
}
}
}
}
// Tick styling
const tickLen = 5;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
@@ -1291,8 +1330,8 @@ function renderCalibrationCanvas() {
}
});
// Draw direction chevron at midpoint, outside the edge bar
const s = 5;
// Draw direction chevron at midpoint, inside the screen area
const s = 7;
let mx, my, angle;
if (geo.horizontal) {
mx = (geo.x1 + geo.x2) / 2;
@@ -1307,14 +1346,17 @@ function renderCalibrationCanvas() {
ctx.save();
ctx.translate(mx, my);
ctx.rotate(angle);
ctx.strokeStyle = '#4CAF50';
ctx.lineWidth = 1.5;
ctx.fillStyle = 'rgba(76, 175, 80, 0.85)';
ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
ctx.lineWidth = 1;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.moveTo(-s * 0.4, -s * 0.5);
ctx.lineTo(s * 0.4, 0);
ctx.lineTo(-s * 0.4, s * 0.5);
ctx.moveTo(-s * 0.5, -s * 0.6);
ctx.lineTo(s * 0.5, 0);
ctx.lineTo(-s * 0.5, s * 0.6);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
});