glutamatt's picture
glutamatt HF Staff
init
35527e2 verified
raw
history blame
7.04 kB
import { Chart, ChartConfiguration, registerables } from 'chart.js';
import type { MetricACWRData } from '@/types';
// Register all Chart.js components
Chart.register(...registerables);
let distanceChart: Chart | null = null;
let durationChart: Chart | null = null;
let tssChart: Chart | null = null;
// Common chart styling inspired by Garmin
const commonOptions = {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: true,
position: 'top' as const,
labels: {
usePointStyle: true,
padding: 15,
font: {
size: 12,
},
},
},
tooltip: {
mode: 'index' as const,
intersect: false,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
titleColor: '#1e293b',
bodyColor: '#475569',
borderColor: '#e2e8f0',
borderWidth: 1,
padding: 12,
displayColors: true,
},
},
scales: {
x: {
grid: {
display: false,
drawBorder: false,
},
ticks: {
maxRotation: 45,
minRotation: 45,
padding: 8,
color: '#64748b',
font: {
size: 10,
},
autoSkip: true,
maxTicksLimit: 20,
},
},
},
};
function createDualAxisChart(
canvasId: string,
data: MetricACWRData,
metricLabel: string,
metricUnit: string,
barColor: string
): Chart {
const canvas = document.getElementById(canvasId) as HTMLCanvasElement;
if (!canvas) throw new Error(`Canvas ${canvasId} not found`);
const config: ChartConfiguration = {
type: 'bar',
data: {
labels: data.dates,
datasets: [
{
type: 'bar',
label: `Daily ${metricLabel}`,
data: data.values,
backgroundColor: barColor,
borderWidth: 0,
borderRadius: 2,
barPercentage: 0.8,
yAxisID: 'y',
},
{
type: 'line',
label: '7-Day Average',
data: data.average7d,
borderColor: 'rgba(239, 68, 68, 0.9)',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 4,
tension: 0.4,
fill: false,
yAxisID: 'y',
},
{
type: 'line',
label: '28-Day Average',
data: data.average28d,
borderColor: 'rgba(249, 115, 22, 0.9)',
backgroundColor: 'rgba(249, 115, 22, 0.1)',
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 4,
tension: 0.4,
fill: false,
yAxisID: 'y',
},
{
type: 'line',
label: 'ACWR',
data: data.acwr,
borderColor: 'rgba(139, 92, 246, 1)',
backgroundColor: 'rgba(139, 92, 246, 0.1)',
borderWidth: 3,
pointRadius: 0,
pointHoverRadius: 5,
tension: 0.4,
fill: false,
yAxisID: 'y1',
},
],
},
options: {
...commonOptions,
scales: {
...commonOptions.scales,
y: {
type: 'linear',
position: 'left',
beginAtZero: true,
border: {
display: false,
},
grid: {
color: 'rgba(148, 163, 184, 0.1)',
},
ticks: {
padding: 8,
color: '#64748b',
font: {
size: 11,
},
},
title: {
display: true,
text: `${metricLabel} ${metricUnit}`,
color: '#64748b',
font: {
size: 12,
weight: 500,
},
},
},
y1: {
type: 'linear',
position: 'right',
beginAtZero: true,
suggestedMin: 0,
suggestedMax: 2,
grid: {
drawOnChartArea: false,
},
ticks: {
padding: 8,
color: '#8b5cf6',
font: {
size: 11,
},
},
title: {
display: true,
text: 'ACWR',
color: '#8b5cf6',
font: {
size: 12,
weight: 500,
},
},
},
},
interaction: {
mode: 'index' as const,
intersect: false,
},
},
};
return new Chart(canvas, config);
}
export function createDistanceChart(data: MetricACWRData): void {
if (distanceChart) {
distanceChart.destroy();
}
distanceChart = createDualAxisChart(
'distance-chart',
data,
'Distance',
'(km)',
'rgba(59, 130, 246, 0.6)'
);
}
export function createDurationChart(data: MetricACWRData): void {
if (durationChart) {
durationChart.destroy();
}
durationChart = createDualAxisChart(
'duration-chart',
data,
'Duration',
'(min)',
'rgba(16, 185, 129, 0.6)'
);
}
export function createTSSChart(data: MetricACWRData): void {
if (tssChart) {
tssChart.destroy();
}
tssChart = createDualAxisChart(
'tss-chart',
data,
'TSS',
'',
'rgba(245, 158, 11, 0.6)'
);
}
export function destroyAllCharts(): void {
if (distanceChart) {
distanceChart.destroy();
distanceChart = null;
}
if (durationChart) {
durationChart.destroy();
durationChart = null;
}
if (tssChart) {
tssChart.destroy();
tssChart = null;
}
}