fix: refresh timeline axes when switching songs
Publish GitHub Pages output / publish (push) Successful in 43s

This commit is contained in:
2026-09-07 17:13:01 +09:00
parent 0af64052c6
commit 7d79e0ab97
3 changed files with 42 additions and 30 deletions
+22 -14
View File
@@ -33,6 +33,14 @@
$: yTicks = Array.from({ length: chartMaximumScore / 500_000 + 1 }, (_, index) => index * 500_000);
$: hoveredPoint = hoverTime === null ? null : pointAtTime(points, hoverTime);
// A different chart invalidates the previous pointer/keyboard selection.
$: resetHover(notes, durationSeconds);
function resetHover(_notes: readonly NoteScorePoint[], _duration: number): void {
hoverTime = null;
tooltipPercent = 0;
}
function buildPoints(source: readonly NoteScorePoint[]): ScorePoint[] {
let averageScore = 0;
let maximumScore = 0;
@@ -45,27 +53,27 @@
return result;
}
function x(timeSeconds: number, width = viewWidth): number {
return LEFT + Math.max(0, Math.min(1, timeSeconds / Math.max(1, durationSeconds))) * (width - LEFT - RIGHT);
function x(timeSeconds: number, width: number, duration: number): number {
return LEFT + Math.max(0, Math.min(1, timeSeconds / Math.max(1, duration))) * (width - LEFT - RIGHT);
}
function y(score: number, scaleMaximum = chartMaximumScore): number {
function y(score: number, scaleMaximum: number): number {
return TOP + (1 - score / scaleMaximum) * PLOT_HEIGHT;
}
function linePath(source: readonly ScorePoint[], field: 'averageScore' | 'maximumScore', duration: number, scaleMaximum: number, width: number): string {
if (source.length === 0) return '';
const commands = [`M ${x(source[0].timeSeconds, width).toFixed(2)} ${y(source[0][field], scaleMaximum).toFixed(2)}`];
const commands = [`M ${x(source[0].timeSeconds, width, duration).toFixed(2)} ${y(source[0][field], scaleMaximum).toFixed(2)}`];
for (let index = 1; index < source.length; index += 1) {
const previous = source[index - 1];
const point = source[index];
commands.push(
`L ${x(point.timeSeconds, width).toFixed(2)} ${y(previous[field], scaleMaximum).toFixed(2)}`,
`L ${x(point.timeSeconds, width).toFixed(2)} ${y(point[field], scaleMaximum).toFixed(2)}`
`L ${x(point.timeSeconds, width, duration).toFixed(2)} ${y(previous[field], scaleMaximum).toFixed(2)}`,
`L ${x(point.timeSeconds, width, duration).toFixed(2)} ${y(point[field], scaleMaximum).toFixed(2)}`
);
}
const last = source.at(-1)!;
commands.push(`L ${x(duration, width).toFixed(2)} ${y(last[field], scaleMaximum).toFixed(2)}`);
commands.push(`L ${x(duration, width, duration).toFixed(2)} ${y(last[field], scaleMaximum).toFixed(2)}`);
return commands.join(' ');
}
@@ -127,7 +135,7 @@
</small>
</span>
{#each yTicks as tick}
<span class="axis-value" style={`top: ${y(tick)}px;`}>{formatAxisScore(tick)}</span>
<span class="axis-value" style={`top: ${y(tick, chartMaximumScore)}px;`}>{formatAxisScore(tick)}</span>
{/each}
</strong>
<div
@@ -153,17 +161,17 @@
aria-label="시간에 따른 누적 평균 점수와 최고 점수 그래프"
>
{#each timeTicks as tick}
<line class="time-grid-line" x1={x(tick, viewWidth)} x2={x(tick, viewWidth)} y1={TOP} y2={HEIGHT - BOTTOM} />
<line class="time-grid-line" x1={x(tick, viewWidth, durationSeconds)} x2={x(tick, viewWidth, durationSeconds)} y1={TOP} y2={HEIGHT - BOTTOM} />
{/each}
{#each yTicks as tick}
<line class="grid-line" x1={LEFT} x2={viewWidth - RIGHT} y1={y(tick)} y2={y(tick)} />
<line class="grid-line" x1={LEFT} x2={viewWidth - RIGHT} y1={y(tick, chartMaximumScore)} y2={y(tick, chartMaximumScore)} />
{/each}
<path class="score-line maximum-line" d={maximumPath} />
<path class="score-line average-line" d={averagePath} />
{#if hoveredPoint && hoverTime !== null}
<line class="cursor-line" x1={x(hoverTime, viewWidth)} x2={x(hoverTime, viewWidth)} y1={TOP} y2={HEIGHT - BOTTOM} />
<circle class="point maximum-point" cx={x(hoverTime, viewWidth)} cy={y(hoveredPoint.maximumScore)} r="5" />
<circle class="point average-point" cx={x(hoverTime, viewWidth)} cy={y(hoveredPoint.averageScore)} r="5" />
<line class="cursor-line" x1={x(hoverTime, viewWidth, durationSeconds)} x2={x(hoverTime, viewWidth, durationSeconds)} y1={TOP} y2={HEIGHT - BOTTOM} />
<circle class="point maximum-point" cx={x(hoverTime, viewWidth, durationSeconds)} cy={y(hoveredPoint.maximumScore, chartMaximumScore)} r="5" />
<circle class="point average-point" cx={x(hoverTime, viewWidth, durationSeconds)} cy={y(hoveredPoint.averageScore, chartMaximumScore)} r="5" />
{/if}
</svg>
@@ -184,7 +192,7 @@
.legend { display: flex; gap: 9px; color: #aab2bf; font-size: 10px; font-weight: 700; }
.legend span, .tooltip span { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.axis-value { position: absolute; right: 7px; color: #747f90; font-size: 10px; font-weight: 650; transform: translateY(-50%); white-space: nowrap; }
.score-track { position: relative; flex: 1 0 auto; height: 220px; overflow: hidden; border-bottom: 1px solid #465062; background: #10141a; }
.score-track { position: relative; flex: 1 0 0; height: 220px; overflow: hidden; border-bottom: 1px solid #465062; background: #10141a; }
i { display: inline-block; flex: none; width: 8px; height: 8px; border-radius: 50%; }
i.average { background: #f08db8; }
i.maximum { background: #64cbe4; }