Spaces:
Running
Running
| /** | |
| * Chart.js configuration utilities for usage statistics | |
| * Extracted from UsagePage.tsx for reusability | |
| */ | |
| import type { ChartOptions } from 'chart.js'; | |
| /** | |
| * Static sparkline chart options (no dependencies on theme/mobile) | |
| */ | |
| export const sparklineOptions: ChartOptions<'line'> = { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { legend: { display: false }, tooltip: { enabled: false } }, | |
| scales: { x: { display: false }, y: { display: false } }, | |
| elements: { line: { tension: 0.45 }, point: { radius: 0 } } | |
| }; | |
| export interface ChartConfigOptions { | |
| period: 'hour' | 'day'; | |
| labels: string[]; | |
| isDark: boolean; | |
| isMobile: boolean; | |
| } | |
| /** | |
| * Build chart options with theme and responsive awareness | |
| */ | |
| export function buildChartOptions({ | |
| period, | |
| labels, | |
| isDark, | |
| isMobile | |
| }: ChartConfigOptions): ChartOptions<'line'> { | |
| const pointRadius = isMobile && period === 'hour' ? 0 : isMobile ? 2 : 4; | |
| const tickFontSize = isMobile ? 10 : 12; | |
| const maxTickLabelCount = isMobile ? (period === 'hour' ? 8 : 6) : period === 'hour' ? 12 : 10; | |
| const gridColor = isDark ? 'rgba(255, 255, 255, 0.06)' : 'rgba(17, 24, 39, 0.06)'; | |
| const axisBorderColor = isDark ? 'rgba(255, 255, 255, 0.10)' : 'rgba(17, 24, 39, 0.10)'; | |
| const tickColor = isDark ? 'rgba(255, 255, 255, 0.72)' : 'rgba(17, 24, 39, 0.72)'; | |
| const tooltipBg = isDark ? 'rgba(17, 24, 39, 0.92)' : 'rgba(255, 255, 255, 0.98)'; | |
| const tooltipTitle = isDark ? '#ffffff' : '#111827'; | |
| const tooltipBody = isDark ? 'rgba(255, 255, 255, 0.86)' : '#374151'; | |
| const tooltipBorder = isDark ? 'rgba(255, 255, 255, 0.10)' : 'rgba(17, 24, 39, 0.10)'; | |
| return { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| interaction: { | |
| mode: 'index', | |
| intersect: false | |
| }, | |
| plugins: { | |
| legend: { display: false }, | |
| tooltip: { | |
| backgroundColor: tooltipBg, | |
| titleColor: tooltipTitle, | |
| bodyColor: tooltipBody, | |
| borderColor: tooltipBorder, | |
| borderWidth: 1, | |
| padding: 10, | |
| displayColors: true, | |
| usePointStyle: true | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| grid: { | |
| color: gridColor, | |
| drawTicks: false | |
| }, | |
| border: { | |
| color: axisBorderColor | |
| }, | |
| ticks: { | |
| color: tickColor, | |
| font: { size: tickFontSize }, | |
| maxRotation: isMobile ? 0 : 45, | |
| minRotation: 0, | |
| autoSkip: true, | |
| maxTicksLimit: maxTickLabelCount, | |
| callback: (value) => { | |
| const index = typeof value === 'number' ? value : Number(value); | |
| const raw = | |
| Number.isFinite(index) && labels[index] ? labels[index] : typeof value === 'string' ? value : ''; | |
| if (period === 'hour') { | |
| const [md, time] = raw.split(' '); | |
| if (!time) return raw; | |
| if (time.startsWith('00:')) { | |
| return md ? [md, time] : time; | |
| } | |
| return time; | |
| } | |
| if (isMobile) { | |
| const parts = raw.split('-'); | |
| if (parts.length === 3) { | |
| return `${parts[1]}-${parts[2]}`; | |
| } | |
| } | |
| return raw; | |
| } | |
| } | |
| }, | |
| y: { | |
| beginAtZero: true, | |
| grid: { | |
| color: gridColor | |
| }, | |
| border: { | |
| color: axisBorderColor | |
| }, | |
| ticks: { | |
| color: tickColor, | |
| font: { size: tickFontSize } | |
| } | |
| } | |
| }, | |
| elements: { | |
| line: { | |
| tension: 0.35, | |
| borderWidth: isMobile ? 1.5 : 2 | |
| }, | |
| point: { | |
| borderWidth: 2, | |
| radius: pointRadius, | |
| hoverRadius: 4 | |
| } | |
| } | |
| }; | |
| } | |
| /** | |
| * Calculate minimum chart width for hourly data on mobile devices | |
| */ | |
| export function getHourChartMinWidth(labelCount: number, isMobile: boolean): string | undefined { | |
| if (!isMobile || labelCount <= 0) return undefined; | |
| const perPoint = 56; | |
| const minWidth = Math.min(labelCount * perPoint, 3000); | |
| return `${minWidth}px`; | |
| } | |