fix: theme-aware chart colors — readable axes and tooltips in light mode
This commit is contained in:
@@ -121,9 +121,27 @@
|
||||
// Reset when switching away from a zone-capable metric or leaving histogram
|
||||
$: if (!canAlignZones) alignZones = false;
|
||||
|
||||
// ── Theme-aware colours ──────────────────────────────────────────────────
|
||||
function getThemeColors() {
|
||||
const isDark = document.documentElement.getAttribute('data-theme') !== 'light';
|
||||
return {
|
||||
axis: isDark ? '#71717a' : '#52525b', // zinc-500 / zinc-600
|
||||
rule: isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.2)',
|
||||
tooltipFg: isDark ? '#ffffff' : '#18181b',
|
||||
tooltipBg: isDark ? '#09090b' : '#ffffff', // text outline backing
|
||||
ruleY: isDark ? '#3f3f46' : '#d4d4d8', // baseline rule
|
||||
};
|
||||
}
|
||||
|
||||
// ── Rendering ────────────────────────────────────────────────────────────
|
||||
onMount(() => { renderChart(); });
|
||||
onDestroy(() => { chart?.remove(); chart = null; });
|
||||
let themeObserver: MutationObserver | null = null;
|
||||
|
||||
onMount(() => {
|
||||
renderChart();
|
||||
themeObserver = new MutationObserver(() => renderChart());
|
||||
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
});
|
||||
onDestroy(() => { chart?.remove(); chart = null; themeObserver?.disconnect(); });
|
||||
|
||||
$: if (chartEl) {
|
||||
activeTab; xMode; chartType; histData; histThresholds; alignZones;
|
||||
@@ -162,6 +180,7 @@
|
||||
|
||||
function renderLine(w: number, h: number, yKey: string, yLabel: string, color: string) {
|
||||
const x = xMode === 'distance' ? 'dist_km' : 't';
|
||||
const tc = getThemeColors();
|
||||
const marks: any[] = [];
|
||||
|
||||
if (activeTab === 'cadence') {
|
||||
@@ -174,12 +193,14 @@
|
||||
}
|
||||
|
||||
marks.push(
|
||||
Plot.ruleX(data, Plot.pointerX({ x, stroke: 'rgba(255,255,255,0.3)', strokeWidth: 1, strokeDasharray: '4,4' })),
|
||||
Plot.dot(data, Plot.pointerX({ x, y: yKey, r: 4, fill: color, stroke: 'white', strokeWidth: 1.5 })),
|
||||
Plot.ruleX(data, Plot.pointerX({ x, stroke: tc.rule, strokeWidth: 1, strokeDasharray: '4,4' })),
|
||||
Plot.dot(data, Plot.pointerX({ x, y: yKey, r: 4, fill: color, stroke: tc.tooltipBg, strokeWidth: 1.5 })),
|
||||
Plot.text(data, Plot.pointerX({
|
||||
x, y: yKey,
|
||||
text: (d: any) => d[yKey] != null ? `${Math.round(d[yKey])}` : '',
|
||||
dy: -12, fill: 'white', fontSize: 11, fontWeight: '600',
|
||||
dy: -12,
|
||||
fill: tc.tooltipFg, stroke: tc.tooltipBg, strokeWidth: 3,
|
||||
fontSize: 11, fontWeight: '600',
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -193,7 +214,7 @@
|
||||
|
||||
return Plot.plot({
|
||||
width: w, height: h, marginLeft: 48, marginBottom: 32,
|
||||
style: { background: 'transparent', color: 'var(--text-4, #a1a1aa)', fontSize: '11px' },
|
||||
style: { background: 'transparent', color: tc.axis, fontSize: '11px' },
|
||||
x: { label: null, tickFormat: xTickFormat, grid: false, ticks: 6 },
|
||||
y: { label: yLabel, grid: true, tickCount: 4 },
|
||||
marks,
|
||||
@@ -204,6 +225,7 @@
|
||||
const yTickFormat = (v: number) => v >= 60 ? `${Math.round(v / 60)}m` : `${v}s`;
|
||||
const rawZones = activeTab === 'hr' ? athlete?.hr_zones : activeTab === 'power' ? athlete?.power_zones : null;
|
||||
const zoneColors = activeTab === 'hr' ? HR_ZONE_COLORS : PWR_ZONE_COLORS;
|
||||
const tc = getThemeColors();
|
||||
|
||||
// ── Zone-aligned: one colored bar per zone ──────────────────────────────
|
||||
if (alignZones && rawZones?.length) {
|
||||
@@ -224,7 +246,7 @@
|
||||
|
||||
return Plot.plot({
|
||||
width: w, height: h, marginLeft: 48, marginBottom: 32,
|
||||
style: { background: 'transparent', color: 'var(--text-4, #a1a1aa)', fontSize: '11px' },
|
||||
style: { background: 'transparent', color: tc.axis, fontSize: '11px' },
|
||||
x: { label: yLabel, grid: false, domain: [clampedZones[0][0], clampedZones[clampedZones.length - 1][1]] },
|
||||
y: { label: 'Time', grid: true, tickCount: 4, tickFormat: yTickFormat },
|
||||
marks: [
|
||||
@@ -240,7 +262,7 @@
|
||||
fontSize: 10, fontWeight: '600',
|
||||
dy: -8,
|
||||
}),
|
||||
Plot.ruleY([0], { stroke: '#52525b' }),
|
||||
Plot.ruleY([0], { stroke: tc.ruleY }),
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -251,7 +273,7 @@
|
||||
{ y: 'count' },
|
||||
{ x: yKey, fill: color, fillOpacity: 0.7, thresholds: histThresholds },
|
||||
)),
|
||||
Plot.ruleY([0], { stroke: '#52525b' }),
|
||||
Plot.ruleY([0], { stroke: tc.ruleY }),
|
||||
];
|
||||
|
||||
if (rawZones?.length) {
|
||||
@@ -282,7 +304,7 @@
|
||||
|
||||
return Plot.plot({
|
||||
width: w, height: h, marginLeft: 48, marginBottom: 32,
|
||||
style: { background: 'transparent', color: 'var(--text-4, #a1a1aa)', fontSize: '11px' },
|
||||
style: { background: 'transparent', color: tc.axis, fontSize: '11px' },
|
||||
x: { label: yLabel, grid: false, ticks: 6, domain: [trimMin, trimMax] },
|
||||
y: { label: 'Time', grid: true, tickCount: 4, tickFormat: yTickFormat },
|
||||
marks,
|
||||
|
||||
@@ -82,6 +82,10 @@
|
||||
|
||||
$: colorMap = Object.fromEntries(selectedKeys.map((k, i) => [k, curveColor(k, i)]));
|
||||
|
||||
function getAxisColor() {
|
||||
return document.documentElement.getAttribute('data-theme') === 'light' ? '#52525b' : '#a1a1aa';
|
||||
}
|
||||
|
||||
function renderChart(data: typeof plotData, cmap: typeof colorMap) {
|
||||
if (!chartEl) return;
|
||||
chartEl.innerHTML = '';
|
||||
@@ -95,7 +99,7 @@
|
||||
height: 320,
|
||||
marginLeft: 52,
|
||||
marginBottom: 40,
|
||||
style: { background: 'transparent', color: '#e4e4e7' },
|
||||
style: { background: 'transparent', color: getAxisColor() },
|
||||
x: {
|
||||
type: 'log',
|
||||
label: 'Duration',
|
||||
@@ -160,7 +164,9 @@
|
||||
onMount(() => {
|
||||
const ro = new ResizeObserver(() => renderChart(currentPlotData, currentColorMap));
|
||||
ro.observe(chartEl);
|
||||
return () => ro.disconnect();
|
||||
const mo = new MutationObserver(() => renderChart(currentPlotData, currentColorMap));
|
||||
mo.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
return () => { ro.disconnect(); mo.disconnect(); };
|
||||
});
|
||||
|
||||
// ── Toggle helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user