Prioritize round-number ticks, add calibration tip list with i18n
Tick labels: round-number ticks (300, 900, etc.) now take priority over edge boundary labels (288, 933). When they overlap, the boundary label is suppressed but its tick line is preserved. Calibration tips: convert single paragraph to bulleted list with individual i18n keys, add tip about toggling edge inputs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -84,7 +84,13 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="calibration-device-id">
|
||||
<p class="section-tip" data-i18n="calibration.preview.click_hint">Click an edge to toggle test LEDs on/off</p>
|
||||
<ul class="section-tip">
|
||||
<li data-i18n="calibration.tip.led_count">Enter LED count per edge</li>
|
||||
<li data-i18n="calibration.tip.start_corner">Click a corner to set the start position</li>
|
||||
<li data-i18n="calibration.tip.span">Drag green bars to adjust coverage span</li>
|
||||
<li data-i18n="calibration.tip.test">Click an edge to toggle test LEDs</li>
|
||||
<li data-i18n="calibration.tip.toggle_inputs">Click total LED count to toggle edge inputs</li>
|
||||
</ul>
|
||||
<!-- Interactive Preview with integrated LED inputs and test toggles -->
|
||||
<div style="margin-bottom: 12px; padding: 0 24px;">
|
||||
<div class="calibration-preview">
|
||||
|
||||
@@ -90,7 +90,11 @@
|
||||
"settings.saved": "Settings saved successfully",
|
||||
"settings.failed": "Failed to save settings",
|
||||
"calibration.title": "LED Calibration",
|
||||
"calibration.preview.click_hint": "Click an edge to toggle test LEDs on/off",
|
||||
"calibration.tip.led_count": "Enter LED count per edge",
|
||||
"calibration.tip.start_corner": "Click a corner to set the start position",
|
||||
"calibration.tip.span": "Drag green bars to adjust coverage span",
|
||||
"calibration.tip.test": "Click an edge to toggle test LEDs",
|
||||
"calibration.tip.toggle_inputs": "Click total LED count to toggle edge inputs",
|
||||
"calibration.start_position": "Starting Position:",
|
||||
"calibration.position.bottom_left": "Bottom Left",
|
||||
"calibration.position.bottom_right": "Bottom Right",
|
||||
|
||||
@@ -90,7 +90,11 @@
|
||||
"settings.saved": "Настройки успешно сохранены",
|
||||
"settings.failed": "Не удалось сохранить настройки",
|
||||
"calibration.title": "Калибровка Светодиодов",
|
||||
"calibration.preview.click_hint": "Нажмите на край чтобы включить/выключить тест светодиодов",
|
||||
"calibration.tip.led_count": "Укажите количество LED на каждой стороне",
|
||||
"calibration.tip.start_corner": "Нажмите на угол для выбора стартовой позиции",
|
||||
"calibration.tip.span": "Перетащите зелёные полосы для настройки зоны покрытия",
|
||||
"calibration.tip.test": "Нажмите на край для теста LED",
|
||||
"calibration.tip.toggle_inputs": "Нажмите на общее количество LED для скрытия боковых полей",
|
||||
"calibration.start_position": "Начальная Позиция:",
|
||||
"calibration.position.bottom_left": "Нижний Левый",
|
||||
"calibration.position.bottom_right": "Нижний Правый",
|
||||
|
||||
@@ -629,6 +629,15 @@ section {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
ul.section-tip {
|
||||
list-style: disc;
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
ul.section-tip li {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user