Render baselines as horizontal reference lines, not timeline points
Browse files- static/index.html +81 -8
static/index.html
CHANGED
|
@@ -1613,18 +1613,31 @@ const GRID_COLOR = 'rgba(0,0,0,0.05)';
|
|
| 1613 |
function renderChart(entries) {
|
| 1614 |
if (!window.Chart) return;
|
| 1615 |
if (chart) { chart.destroy(); chart = null; }
|
| 1616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1617 |
let runningBest = Infinity;
|
| 1618 |
sorted.forEach(e => { e.isRecord = e.score < runningBest; if (e.isRecord) runningBest = e.score; });
|
| 1619 |
const bestEntries = sorted.filter(e => e.isRecord);
|
| 1620 |
const nonBestEntries = sorted.filter(e => !e.isRecord);
|
| 1621 |
|
| 1622 |
-
|
| 1623 |
-
|
| 1624 |
-
const
|
|
|
|
|
|
|
|
|
|
| 1625 |
const timeRange = latestDate - minDate || 3600000;
|
| 1626 |
const datePadding = timeRange * 0.05;
|
| 1627 |
const extendedEnd = latestDate + timeRange * 0.08;
|
|
|
|
| 1628 |
|
| 1629 |
const bestLineData = bestEntries.map(e => ({ x: new Date(e.date).getTime(), y: e.score, agent: e.agent }));
|
| 1630 |
if (bestLineData.length) {
|
|
@@ -1634,9 +1647,10 @@ function renderChart(entries) {
|
|
| 1634 |
const bestScatter = bestEntries.map(e => ({ x: new Date(e.date).getTime(), y: e.score, agent: e.agent }));
|
| 1635 |
const nonBestData = nonBestEntries.map(e => ({ x: new Date(e.date).getTime(), y: e.score, agent: e.agent }));
|
| 1636 |
|
| 1637 |
-
|
| 1638 |
-
const
|
| 1639 |
-
const
|
|
|
|
| 1640 |
const scorePad = (maxScore - minScore) * 0.2 || 100;
|
| 1641 |
|
| 1642 |
const bestLabels = {
|
|
@@ -1698,6 +1712,62 @@ function renderChart(entries) {
|
|
| 1698 |
}
|
| 1699 |
};
|
| 1700 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1701 |
const ctx = document.getElementById('evolutionChart').getContext('2d');
|
| 1702 |
chart = new Chart(ctx, {
|
| 1703 |
type: 'line',
|
|
@@ -1706,6 +1776,7 @@ function renderChart(entries) {
|
|
| 1706 |
{ label: 'Running Best', data: bestLineData, borderColor: HF_ORANGE, backgroundColor: HF_ORANGE_DIM, borderWidth: 2.5, stepped: 'after', fill: true, pointRadius: 0, pointHoverRadius: 0, tension: 0, order: 2 },
|
| 1707 |
{ label: 'Records', data: bestScatter, type: 'scatter', backgroundColor: HF_ORANGE, borderColor: '#fff', borderWidth: 2, pointRadius: 7, pointHoverRadius: 9, pointStyle: 'circle', order: 1 },
|
| 1708 |
{ label: 'Non-Records', data: nonBestData, type: 'scatter', backgroundColor: NON_BEST_COLOR, borderColor: '#fff', borderWidth: 1.5, pointRadius: 5, pointHoverRadius: 7, pointStyle: 'circle', order: 0 },
|
|
|
|
| 1709 |
],
|
| 1710 |
},
|
| 1711 |
options: {
|
|
@@ -1720,6 +1791,8 @@ function renderChart(entries) {
|
|
| 1720 |
titleFont: { family: "'Source Sans 3', sans-serif", size: 12, weight: '700' },
|
| 1721 |
bodyFont: { family: "'JetBrains Mono', monospace", size: 11 },
|
| 1722 |
titleColor: '#fff', bodyColor: '#d1d5db',
|
|
|
|
|
|
|
| 1723 |
callbacks: {
|
| 1724 |
title: items => items[0]?.raw?.agent || '',
|
| 1725 |
label: it => { const d = new Date(it.raw.x); return [`Bytes: ${it.raw.y.toLocaleString()}`, `Date: ${d.toLocaleString()}`]; }
|
|
@@ -1752,7 +1825,7 @@ function renderChart(entries) {
|
|
| 1752 |
},
|
| 1753 |
interaction: { mode: 'nearest', intersect: true },
|
| 1754 |
},
|
| 1755 |
-
plugins: [bestLabels, nonBestLabels],
|
| 1756 |
});
|
| 1757 |
}
|
| 1758 |
|
|
|
|
| 1613 |
function renderChart(entries) {
|
| 1614 |
if (!window.Chart) return;
|
| 1615 |
if (chart) { chart.destroy(); chart = null; }
|
| 1616 |
+
|
| 1617 |
+
// Baselines are fixed historical references, not events on this collab's
|
| 1618 |
+
// timeline. Render them as horizontal dashed lines, not as points that
|
| 1619 |
+
// contribute to the running-best curve.
|
| 1620 |
+
const runEntries = entries.filter(e => e.agent !== 'baseline');
|
| 1621 |
+
const baselineEntries = [...entries]
|
| 1622 |
+
.filter(e => e.agent === 'baseline')
|
| 1623 |
+
.sort((a, b) => a.score - b.score);
|
| 1624 |
+
|
| 1625 |
+
const sorted = [...runEntries].sort((a, b) => new Date(a.date) - new Date(b.date));
|
| 1626 |
let runningBest = Infinity;
|
| 1627 |
sorted.forEach(e => { e.isRecord = e.score < runningBest; if (e.isRecord) runningBest = e.score; });
|
| 1628 |
const bestEntries = sorted.filter(e => e.isRecord);
|
| 1629 |
const nonBestEntries = sorted.filter(e => !e.isRecord);
|
| 1630 |
|
| 1631 |
+
// X axis: based on agent runs only. Fallback to a window around "now" when
|
| 1632 |
+
// no runs exist yet, so baselines still have a sensible x-range to span.
|
| 1633 |
+
const now = Date.now();
|
| 1634 |
+
const runDates = sorted.map(e => new Date(e.date).getTime());
|
| 1635 |
+
const minDate = runDates.length ? Math.min(...runDates) : now - 30 * 60 * 1000;
|
| 1636 |
+
const latestDate = runDates.length ? Math.max(...runDates) : now;
|
| 1637 |
const timeRange = latestDate - minDate || 3600000;
|
| 1638 |
const datePadding = timeRange * 0.05;
|
| 1639 |
const extendedEnd = latestDate + timeRange * 0.08;
|
| 1640 |
+
const xMin = minDate - datePadding;
|
| 1641 |
|
| 1642 |
const bestLineData = bestEntries.map(e => ({ x: new Date(e.date).getTime(), y: e.score, agent: e.agent }));
|
| 1643 |
if (bestLineData.length) {
|
|
|
|
| 1647 |
const bestScatter = bestEntries.map(e => ({ x: new Date(e.date).getTime(), y: e.score, agent: e.agent }));
|
| 1648 |
const nonBestData = nonBestEntries.map(e => ({ x: new Date(e.date).getTime(), y: e.score, agent: e.agent }));
|
| 1649 |
|
| 1650 |
+
// Y axis covers runs *and* baselines so baseline lines aren't clipped.
|
| 1651 |
+
const allScores = [...sorted.map(e => e.score), ...baselineEntries.map(e => e.score)];
|
| 1652 |
+
const minScore = allScores.length ? Math.min(...allScores) : 14_000_000;
|
| 1653 |
+
const maxScore = allScores.length ? Math.max(...allScores) : 25_000_000;
|
| 1654 |
const scorePad = (maxScore - minScore) * 0.2 || 100;
|
| 1655 |
|
| 1656 |
const bestLabels = {
|
|
|
|
| 1712 |
}
|
| 1713 |
};
|
| 1714 |
|
| 1715 |
+
// Horizontal dashed line per baseline, spanning the full visible x-range.
|
| 1716 |
+
// Added *after* the three core datasets so the bestLabels (idx 1) and
|
| 1717 |
+
// nonBestLabels (idx 2) plugins continue to find their dataset metas.
|
| 1718 |
+
const BASELINE_COLOR = 'rgba(107,114,128,0.55)';
|
| 1719 |
+
const baselineDatasets = baselineEntries.map(e => ({
|
| 1720 |
+
label: `baseline 路 ${e.method || ''}`,
|
| 1721 |
+
data: [{ x: xMin, y: e.score }, { x: extendedEnd, y: e.score }],
|
| 1722 |
+
type: 'line',
|
| 1723 |
+
borderColor: BASELINE_COLOR,
|
| 1724 |
+
backgroundColor: 'transparent',
|
| 1725 |
+
borderWidth: 1,
|
| 1726 |
+
borderDash: [5, 4],
|
| 1727 |
+
pointRadius: 0,
|
| 1728 |
+
pointHoverRadius: 0,
|
| 1729 |
+
fill: false,
|
| 1730 |
+
tension: 0,
|
| 1731 |
+
order: 100,
|
| 1732 |
+
}));
|
| 1733 |
+
|
| 1734 |
+
// Right-edge label per baseline. Stack vertically when y-values cluster.
|
| 1735 |
+
const baselineLabels = {
|
| 1736 |
+
id: 'baselineLabels',
|
| 1737 |
+
afterDatasetsDraw(c) {
|
| 1738 |
+
if (!baselineEntries.length) return;
|
| 1739 |
+
const ctx2 = c.ctx;
|
| 1740 |
+
const a = c.chartArea;
|
| 1741 |
+
const yScale = c.scales.y;
|
| 1742 |
+
ctx2.save();
|
| 1743 |
+
ctx2.font = '500 10px "JetBrains Mono", monospace';
|
| 1744 |
+
const placed = [];
|
| 1745 |
+
// Draw smallest-score (top of chart) first so stacking pushes downward.
|
| 1746 |
+
baselineEntries.forEach(e => {
|
| 1747 |
+
const text = `${e.method || 'baseline'} ${e.score.toLocaleString()}`;
|
| 1748 |
+
const tw = ctx2.measureText(text).width;
|
| 1749 |
+
const px = 6, boxH = 18, boxW = tw + px * 2;
|
| 1750 |
+
let yc = yScale.getPixelForValue(e.score);
|
| 1751 |
+
for (const p of placed) {
|
| 1752 |
+
if (Math.abs(yc - p) < boxH + 2) yc = p + boxH + 2;
|
| 1753 |
+
}
|
| 1754 |
+
placed.push(yc);
|
| 1755 |
+
let lx = a.right - boxW - 4;
|
| 1756 |
+
let ly = yc - boxH / 2;
|
| 1757 |
+
if (ly < a.top) ly = a.top + 1;
|
| 1758 |
+
if (ly + boxH > a.bottom) ly = a.bottom - boxH - 1;
|
| 1759 |
+
ctx2.fillStyle = 'rgba(243,244,246,0.95)';
|
| 1760 |
+
ctx2.strokeStyle = 'rgba(107,114,128,0.45)';
|
| 1761 |
+
ctx2.lineWidth = 1;
|
| 1762 |
+
ctx2.beginPath(); ctx2.roundRect(lx, ly, boxW, boxH, 5); ctx2.fill(); ctx2.stroke();
|
| 1763 |
+
ctx2.fillStyle = '#6b7280';
|
| 1764 |
+
ctx2.textBaseline = 'middle';
|
| 1765 |
+
ctx2.fillText(text, lx + px, ly + boxH / 2);
|
| 1766 |
+
});
|
| 1767 |
+
ctx2.restore();
|
| 1768 |
+
},
|
| 1769 |
+
};
|
| 1770 |
+
|
| 1771 |
const ctx = document.getElementById('evolutionChart').getContext('2d');
|
| 1772 |
chart = new Chart(ctx, {
|
| 1773 |
type: 'line',
|
|
|
|
| 1776 |
{ label: 'Running Best', data: bestLineData, borderColor: HF_ORANGE, backgroundColor: HF_ORANGE_DIM, borderWidth: 2.5, stepped: 'after', fill: true, pointRadius: 0, pointHoverRadius: 0, tension: 0, order: 2 },
|
| 1777 |
{ label: 'Records', data: bestScatter, type: 'scatter', backgroundColor: HF_ORANGE, borderColor: '#fff', borderWidth: 2, pointRadius: 7, pointHoverRadius: 9, pointStyle: 'circle', order: 1 },
|
| 1778 |
{ label: 'Non-Records', data: nonBestData, type: 'scatter', backgroundColor: NON_BEST_COLOR, borderColor: '#fff', borderWidth: 1.5, pointRadius: 5, pointHoverRadius: 7, pointStyle: 'circle', order: 0 },
|
| 1779 |
+
...baselineDatasets,
|
| 1780 |
],
|
| 1781 |
},
|
| 1782 |
options: {
|
|
|
|
| 1791 |
titleFont: { family: "'Source Sans 3', sans-serif", size: 12, weight: '700' },
|
| 1792 |
bodyFont: { family: "'JetBrains Mono', monospace", size: 11 },
|
| 1793 |
titleColor: '#fff', bodyColor: '#d1d5db',
|
| 1794 |
+
// Skip tooltip points that aren't real run events (baselines, line extensions).
|
| 1795 |
+
filter: it => it.datasetIndex < 3 && it.raw && !it.raw._ext && it.raw.agent,
|
| 1796 |
callbacks: {
|
| 1797 |
title: items => items[0]?.raw?.agent || '',
|
| 1798 |
label: it => { const d = new Date(it.raw.x); return [`Bytes: ${it.raw.y.toLocaleString()}`, `Date: ${d.toLocaleString()}`]; }
|
|
|
|
| 1825 |
},
|
| 1826 |
interaction: { mode: 'nearest', intersect: true },
|
| 1827 |
},
|
| 1828 |
+
plugins: [bestLabels, nonBestLabels, baselineLabels],
|
| 1829 |
});
|
| 1830 |
}
|
| 1831 |
|