From 7d9643bd1b84cacc5023391c7970d8f8ff4c4ad5 Mon Sep 17 00:00:00 2001
From: hsiegeln <37154749+hsiegeln@users.noreply.github.com>
Date: Sun, 12 Apr 2026 19:36:19 +0200
Subject: [PATCH] feat!: replace custom chart components with ThemedChart +
Recharts
BREAKING: LineChart, AreaChart, BarChart, ChartSeries, DataPoint removed.
Use ThemedChart with Recharts children (Line, Area, Bar, etc.) instead.
---
.../composites/AreaChart/AreaChart.module.css | 167 -----------
.../composites/AreaChart/AreaChart.tsx | 268 ------------------
.../composites/BarChart/BarChart.module.css | 144 ----------
.../composites/BarChart/BarChart.tsx | 242 ----------------
.../composites/LineChart/LineChart.module.css | 156 ----------
.../composites/LineChart/LineChart.tsx | 247 ----------------
src/design-system/composites/_chart-utils.ts | 101 -------
src/design-system/composites/index.ts | 14 +-
src/design-system/index.ts | 2 +-
9 files changed, 9 insertions(+), 1332 deletions(-)
delete mode 100644 src/design-system/composites/AreaChart/AreaChart.module.css
delete mode 100644 src/design-system/composites/AreaChart/AreaChart.tsx
delete mode 100644 src/design-system/composites/BarChart/BarChart.module.css
delete mode 100644 src/design-system/composites/BarChart/BarChart.tsx
delete mode 100644 src/design-system/composites/LineChart/LineChart.module.css
delete mode 100644 src/design-system/composites/LineChart/LineChart.tsx
delete mode 100644 src/design-system/composites/_chart-utils.ts
diff --git a/src/design-system/composites/AreaChart/AreaChart.module.css b/src/design-system/composites/AreaChart/AreaChart.module.css
deleted file mode 100644
index b36f8ca..0000000
--- a/src/design-system/composites/AreaChart/AreaChart.module.css
+++ /dev/null
@@ -1,167 +0,0 @@
-.wrapper {
- position: relative;
- display: flex;
- flex-direction: column;
- gap: 4px;
- width: 100%;
-}
-
-.svg {
- display: block;
- overflow: visible;
-}
-
-.empty {
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--text-muted);
- font-size: 12px;
- height: 120px;
-}
-
-/* Grid */
-.gridLine {
- stroke: var(--border-subtle);
- stroke-width: 1;
- stroke-dasharray: 3 3;
-}
-
-.axisLabel {
- font-family: var(--font-mono);
- font-size: 9px;
- fill: var(--text-faint);
-}
-
-/* Threshold line */
-.thresholdLine {
- stroke: var(--error);
- stroke-width: 1.5;
- stroke-dasharray: 5 3;
-}
-
-.thresholdLabel {
- font-family: var(--font-mono);
- font-size: 9px;
- fill: var(--error);
-}
-
-/* Area + line */
-.area {
- opacity: 0.1;
-}
-
-.line {
- stroke-width: 2;
- stroke-linecap: round;
- stroke-linejoin: round;
-}
-
-/* Crosshair */
-.crosshair {
- stroke: var(--text-faint);
- stroke-width: 1;
- stroke-dasharray: 3 3;
- pointer-events: none;
-}
-
-/* Tooltip */
-.tooltip {
- position: absolute;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius-sm);
- box-shadow: var(--shadow-md);
- padding: 6px 10px;
- font-size: 12px;
- pointer-events: none;
- z-index: 10;
- white-space: nowrap;
-}
-
-.tooltipTime {
- font-family: var(--font-mono);
- font-size: 10px;
- color: var(--text-muted);
- margin-bottom: 4px;
- padding-bottom: 3px;
- border-bottom: 1px solid var(--border-subtle);
-}
-
-.tooltipRow {
- display: flex;
- align-items: center;
- gap: 5px;
- margin-bottom: 2px;
-}
-
-.tooltipRow:last-child {
- margin-bottom: 0;
-}
-
-.tooltipDot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.tooltipLabel {
- color: var(--text-muted);
-}
-
-.tooltipValue {
- font-family: var(--font-mono);
- font-weight: 600;
- color: var(--text-primary);
-}
-
-/* Legend */
-.legend {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- padding-left: 48px;
-}
-
-.legendItem {
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 12px;
- color: var(--text-secondary);
-}
-
-.legendDot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.legendLabel {
- font-size: 12px;
-}
-
-/* Axis labels */
-.yLabel {
- font-size: 12px;
- color: var(--text-muted);
- writing-mode: vertical-lr;
- transform: rotate(180deg);
- text-align: center;
- position: absolute;
- left: 4px;
- top: 0;
- bottom: 28px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.xLabel {
- text-align: center;
- font-size: 12px;
- color: var(--text-muted);
- padding-left: 48px;
-}
diff --git a/src/design-system/composites/AreaChart/AreaChart.tsx b/src/design-system/composites/AreaChart/AreaChart.tsx
deleted file mode 100644
index 6f0f54f..0000000
--- a/src/design-system/composites/AreaChart/AreaChart.tsx
+++ /dev/null
@@ -1,268 +0,0 @@
-import { useState } from 'react'
-import styles from './AreaChart.module.css'
-import {
- computeYScale,
- computeXScale,
- seriesPoints,
- seriesPath,
- formatAxisLabel,
- CHART_COLORS,
- type ChartSeries,
-} from '../_chart-utils'
-
-interface Threshold {
- value: number
- label: string
-}
-
-interface AreaChartProps {
- series: ChartSeries[]
- xLabel?: string
- yLabel?: string
- threshold?: Threshold
- height?: number
- width?: number
- className?: string
-}
-
-const Y_TICK_COUNT = 4
-const DIMS = {
- paddingTop: 12,
- paddingRight: 16,
- paddingBottom: 28,
- paddingLeft: 48,
-}
-
-export function AreaChart({
- series,
- xLabel,
- yLabel,
- threshold,
- height = 200,
- width = 400,
- className,
-}: AreaChartProps) {
- const [tooltip, setTooltip] = useState<{
- x: number
- y: number
- xLabel: string
- values: { label: string; value: number; color: string }[]
- } | null>(null)
-
- const dims = { ...DIMS, width, height }
- const allData = series.flatMap((s) => s.data)
-
- if (allData.length === 0) {
- return
No data
- }
-
- const { max, toY } = computeYScale(series, dims, threshold?.value)
- const { toX } = computeXScale(series, dims)
- const plotH = height - dims.paddingTop - dims.paddingBottom
- const plotW = width - dims.paddingLeft - dims.paddingRight
- const bottomY = dims.paddingTop + plotH
-
- // Y-axis ticks
- const yTicks = Array.from({ length: Y_TICK_COUNT + 1 }, (_, i) =>
- Math.round((max / Y_TICK_COUNT) * i),
- )
-
- // X-axis ticks (first, middle, last)
- const firstSeries = series[0]
- const xSamples =
- firstSeries && firstSeries.data.length > 0
- ? [
- firstSeries.data[0].x,
- firstSeries.data[Math.floor(firstSeries.data.length / 2)]?.x,
- firstSeries.data[firstSeries.data.length - 1].x,
- ].filter(Boolean)
- : []
-
- function handleMouseMove(e: React.MouseEvent) {
- const rect = e.currentTarget.getBoundingClientRect()
- const mx = e.clientX - rect.left
- const my = e.clientY - rect.top
-
- // Find closest x value
- const pctX = (mx - dims.paddingLeft) / plotW
- const firstS = series[0]
- const idx0 = Math.max(0, Math.min(firstS.data.length - 1, Math.round(pctX * (firstS.data.length - 1))))
- const xVal = firstS.data[idx0]?.x
- const xLabel = xVal instanceof Date
- ? xVal.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' })
- : typeof xVal === 'number' && xVal > 1e10
- ? new Date(xVal).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' })
- : String(xVal ?? '')
-
- const values = series.map((s, i) => {
- const idx = Math.round(pctX * (s.data.length - 1))
- const clamped = Math.max(0, Math.min(s.data.length - 1, idx))
- const pt = s.data[clamped]
- return {
- label: s.label,
- value: pt?.y ?? 0,
- color: s.color ?? CHART_COLORS[i % CHART_COLORS.length],
- }
- })
-
- setTooltip({ x: mx, y: my, xLabel, values })
- }
-
- return (
-
- {yLabel &&
{yLabel}
}
-
-
- {/* Tooltip */}
- {tooltip && (
-
- {tooltip.xLabel && (
-
{tooltip.xLabel}
- )}
- {tooltip.values.map((v) => (
-
-
- {v.label}:
- {formatAxisLabel(v.value)}
-
- ))}
-
- )}
-
- {/* Legend */}
- {series.length > 1 && (
-
- {series.map((s, i) => (
-
-
- {s.label}
-
- ))}
-
- )}
-
- {xLabel &&
{xLabel}
}
-
- )
-}
diff --git a/src/design-system/composites/BarChart/BarChart.module.css b/src/design-system/composites/BarChart/BarChart.module.css
deleted file mode 100644
index 61b1683..0000000
--- a/src/design-system/composites/BarChart/BarChart.module.css
+++ /dev/null
@@ -1,144 +0,0 @@
-.wrapper {
- position: relative;
- display: flex;
- flex-direction: column;
- gap: 4px;
- width: 100%;
-}
-
-.svg {
- display: block;
- overflow: visible;
-}
-
-.empty {
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--text-muted);
- font-size: 12px;
- height: 120px;
-}
-
-.gridLine {
- stroke: var(--border-subtle);
- stroke-width: 1;
- stroke-dasharray: 3 3;
-}
-
-.axisLabel {
- font-family: var(--font-mono);
- font-size: 9px;
- fill: var(--text-faint);
-}
-
-.catLabel {
- font-family: var(--font-mono);
- font-size: 9px;
- fill: var(--text-faint);
-}
-
-.bar {
- cursor: pointer;
- transition: opacity 0.1s;
-}
-
-.bar:hover {
- opacity: 0.8;
-}
-
-.tooltip {
- position: absolute;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius-sm);
- box-shadow: var(--shadow-md);
- padding: 6px 10px;
- font-size: 12px;
- pointer-events: none;
- z-index: 10;
- white-space: nowrap;
-}
-
-.tooltipTitle {
- font-weight: 600;
- color: var(--text-primary);
- margin-bottom: 4px;
- font-size: 12px;
-}
-
-.tooltipRow {
- display: flex;
- align-items: center;
- gap: 5px;
- margin-bottom: 2px;
-}
-
-.tooltipRow:last-child {
- margin-bottom: 0;
-}
-
-.tooltipDot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.tooltipLabel {
- color: var(--text-muted);
-}
-
-.tooltipValue {
- font-family: var(--font-mono);
- font-weight: 600;
- color: var(--text-primary);
-}
-
-.legend {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- padding-left: 48px;
-}
-
-.legendItem {
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 12px;
- color: var(--text-secondary);
-}
-
-.legendDot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.legendLabel {
- font-size: 12px;
-}
-
-.yLabel {
- font-size: 12px;
- color: var(--text-muted);
- writing-mode: vertical-lr;
- transform: rotate(180deg);
- text-align: center;
- position: absolute;
- left: 4px;
- top: 0;
- bottom: 40px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.xLabel {
- text-align: center;
- font-size: 12px;
- color: var(--text-muted);
- padding-left: 48px;
-}
diff --git a/src/design-system/composites/BarChart/BarChart.tsx b/src/design-system/composites/BarChart/BarChart.tsx
deleted file mode 100644
index a1e114a..0000000
--- a/src/design-system/composites/BarChart/BarChart.tsx
+++ /dev/null
@@ -1,242 +0,0 @@
-import { useState } from 'react'
-import styles from './BarChart.module.css'
-import { formatAxisLabel, CHART_COLORS } from '../_chart-utils'
-
-interface BarSeries {
- label: string
- data: { x: string; y: number }[]
- color?: string
-}
-
-interface BarChartProps {
- series: BarSeries[]
- stacked?: boolean
- height?: number
- width?: number
- xLabel?: string
- yLabel?: string
- className?: string
-}
-
-const PADDING = { top: 12, right: 16, bottom: 40, left: 48 }
-const Y_TICK_COUNT = 4
-const BAR_GAP = 0.2 // fraction of bar group width reserved for gaps
-
-export function BarChart({
- series,
- stacked = false,
- height = 200,
- width = 400,
- xLabel,
- yLabel,
- className,
-}: BarChartProps) {
- const [tooltip, setTooltip] = useState<{
- x: number
- y: number
- label: string
- values: { series: string; value: number; color: string }[]
- } | null>(null)
-
- if (series.length === 0 || series[0].data.length === 0) {
- return No data
- }
-
- // Collect all x categories (union across all series)
- const categories = Array.from(new Set(series.flatMap((s) => s.data.map((d) => d.x))))
- const numCats = categories.length
-
- const plotW = width - PADDING.left - PADDING.right
- const plotH = height - PADDING.top - PADDING.bottom
-
- // Compute max Y
- let maxY = 0
- if (stacked) {
- for (const cat of categories) {
- const sum = series.reduce((acc, s) => {
- const pt = s.data.find((d) => d.x === cat)
- return acc + (pt?.y ?? 0)
- }, 0)
- maxY = Math.max(maxY, sum)
- }
- } else {
- maxY = Math.max(...series.flatMap((s) => s.data.map((d) => d.y)))
- }
- maxY = maxY || 1
-
- const yTicks = Array.from({ length: Y_TICK_COUNT + 1 }, (_, i) =>
- Math.round((maxY / Y_TICK_COUNT) * i),
- )
- const toY = (val: number) => PADDING.top + plotH - (val / maxY) * plotH
- const bottomY = PADDING.top + plotH
-
- const catWidth = plotW / numCats
- const groupGap = catWidth * BAR_GAP
- const groupW = catWidth - groupGap
-
- function handleMouseEnter(
- catLabel: string,
- mx: number,
- my: number,
- values: { series: string; value: number; color: string }[],
- ) {
- setTooltip({ x: mx, y: my, label: catLabel, values })
- }
-
- function showBarTooltip(e: React.MouseEvent, cat: string) {
- const rect = e.currentTarget.closest('svg')!.getBoundingClientRect()
- handleMouseEnter(
- cat,
- e.clientX - rect.left,
- e.clientY - rect.top,
- series.map((ss, ssi) => ({
- series: ss.label,
- value: ss.data.find((d) => d.x === cat)?.y ?? 0,
- color: ss.color ?? CHART_COLORS[ssi % CHART_COLORS.length],
- })),
- )
- }
-
- return (
-
- {yLabel &&
{yLabel}
}
-
-
- {/* Tooltip */}
- {tooltip && (
-
-
{tooltip.label}
- {tooltip.values.map((v) => (
-
-
- {v.series}:
- {formatAxisLabel(v.value)}
-
- ))}
-
- )}
-
- {/* Legend */}
- {series.length > 1 && (
-
- {series.map((s, i) => (
-
-
- {s.label}
-
- ))}
-
- )}
-
- {xLabel &&
{xLabel}
}
-
- )
-}
diff --git a/src/design-system/composites/LineChart/LineChart.module.css b/src/design-system/composites/LineChart/LineChart.module.css
deleted file mode 100644
index 89f76af..0000000
--- a/src/design-system/composites/LineChart/LineChart.module.css
+++ /dev/null
@@ -1,156 +0,0 @@
-.wrapper {
- position: relative;
- display: flex;
- flex-direction: column;
- gap: 4px;
- width: 100%;
-}
-
-.svg {
- display: block;
- overflow: visible;
-}
-
-.empty {
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--text-muted);
- font-size: 12px;
- height: 120px;
-}
-
-.gridLine {
- stroke: var(--border-subtle);
- stroke-width: 1;
- stroke-dasharray: 3 3;
-}
-
-.axisLabel {
- font-family: var(--font-mono);
- font-size: 9px;
- fill: var(--text-faint);
-}
-
-.thresholdLine {
- stroke: var(--error);
- stroke-width: 1.5;
- stroke-dasharray: 5 3;
-}
-
-.thresholdLabel {
- font-family: var(--font-mono);
- font-size: 9px;
- fill: var(--error);
-}
-
-.line {
- stroke-width: 2;
- stroke-linecap: round;
- stroke-linejoin: round;
-}
-
-.crosshair {
- stroke: var(--text-faint);
- stroke-width: 1;
- stroke-dasharray: 3 3;
- pointer-events: none;
-}
-
-.tooltip {
- position: absolute;
- background: var(--bg-surface);
- border: 1px solid var(--border);
- border-radius: var(--radius-sm);
- box-shadow: var(--shadow-md);
- padding: 6px 10px;
- font-size: 12px;
- pointer-events: none;
- z-index: 10;
- white-space: nowrap;
-}
-
-.tooltipTime {
- font-family: var(--font-mono);
- font-size: 10px;
- color: var(--text-muted);
- margin-bottom: 4px;
- padding-bottom: 3px;
- border-bottom: 1px solid var(--border-subtle);
-}
-
-.tooltipRow {
- display: flex;
- align-items: center;
- gap: 5px;
- margin-bottom: 2px;
-}
-
-.tooltipRow:last-child {
- margin-bottom: 0;
-}
-
-.tooltipDot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.tooltipLabel {
- color: var(--text-muted);
-}
-
-.tooltipValue {
- font-family: var(--font-mono);
- font-weight: 600;
- color: var(--text-primary);
-}
-
-.legend {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- padding-left: 48px;
-}
-
-.legendItem {
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 12px;
- color: var(--text-secondary);
-}
-
-.legendDot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-
-.legendLabel {
- font-size: 12px;
-}
-
-.yLabel {
- font-size: 12px;
- color: var(--text-muted);
- writing-mode: vertical-lr;
- transform: rotate(180deg);
- text-align: center;
- position: absolute;
- left: 4px;
- top: 0;
- bottom: 28px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.xLabel {
- text-align: center;
- font-size: 12px;
- color: var(--text-muted);
- padding-left: 48px;
-}
diff --git a/src/design-system/composites/LineChart/LineChart.tsx b/src/design-system/composites/LineChart/LineChart.tsx
deleted file mode 100644
index 96f5c60..0000000
--- a/src/design-system/composites/LineChart/LineChart.tsx
+++ /dev/null
@@ -1,247 +0,0 @@
-import { useState } from 'react'
-import styles from './LineChart.module.css'
-import {
- computeYScale,
- computeXScale,
- seriesPoints,
- formatAxisLabel,
- CHART_COLORS,
- type ChartSeries,
-} from '../_chart-utils'
-
-interface Threshold {
- value: number
- label: string
-}
-
-interface LineChartProps {
- series: ChartSeries[]
- xLabel?: string
- yLabel?: string
- threshold?: Threshold
- height?: number
- width?: number
- className?: string
-}
-
-const Y_TICK_COUNT = 4
-const DIMS = {
- paddingTop: 12,
- paddingRight: 16,
- paddingBottom: 28,
- paddingLeft: 48,
-}
-
-export function LineChart({
- series,
- xLabel,
- yLabel,
- threshold,
- height = 200,
- width = 400,
- className,
-}: LineChartProps) {
- const [tooltip, setTooltip] = useState<{
- x: number
- y: number
- xLabel: string
- values: { label: string; value: number; color: string }[]
- } | null>(null)
-
- const dims = { ...DIMS, width, height }
- const allData = series.flatMap((s) => s.data)
-
- if (allData.length === 0) {
- return No data
- }
-
- const { max, toY } = computeYScale(series, dims, threshold?.value)
- const { toX } = computeXScale(series, dims)
- const plotH = height - dims.paddingTop - dims.paddingBottom
- const plotW = width - dims.paddingLeft - dims.paddingRight
- const bottomY = dims.paddingTop + plotH
-
- const yTicks = Array.from({ length: Y_TICK_COUNT + 1 }, (_, i) =>
- Math.round((max / Y_TICK_COUNT) * i),
- )
-
- const firstSeries = series[0]
- const xSamples =
- firstSeries && firstSeries.data.length > 0
- ? [
- firstSeries.data[0].x,
- firstSeries.data[Math.floor(firstSeries.data.length / 2)]?.x,
- firstSeries.data[firstSeries.data.length - 1].x,
- ].filter(Boolean)
- : []
-
- function handleMouseMove(e: React.MouseEvent) {
- const rect = e.currentTarget.getBoundingClientRect()
- const mx = e.clientX - rect.left
- const my = e.clientY - rect.top
- const pctX = (mx - dims.paddingLeft) / plotW
-
- const firstS = series[0]
- const idx0 = Math.max(0, Math.min(firstS.data.length - 1, Math.round(pctX * (firstS.data.length - 1))))
- const xVal = firstS.data[idx0]?.x
- const xLabel = xVal instanceof Date
- ? xVal.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' })
- : typeof xVal === 'number' && xVal > 1e10
- ? new Date(xVal).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' })
- : String(xVal ?? '')
-
- const values = series.map((s, i) => {
- const idx = Math.round(pctX * (s.data.length - 1))
- const clamped = Math.max(0, Math.min(s.data.length - 1, idx))
- const pt = s.data[clamped]
- return {
- label: s.label,
- value: pt?.y ?? 0,
- color: s.color ?? CHART_COLORS[i % CHART_COLORS.length],
- }
- })
-
- setTooltip({ x: mx, y: my, xLabel, values })
- }
-
- return (
-
- {yLabel &&
{yLabel}
}
-
-
- {/* Tooltip */}
- {tooltip && (
-
- {tooltip.xLabel && (
-
{tooltip.xLabel}
- )}
- {tooltip.values.map((v) => (
-
-
- {v.label}:
- {formatAxisLabel(v.value)}
-
- ))}
-
- )}
-
- {/* Legend */}
- {series.length > 1 && (
-
- {series.map((s, i) => (
-
-
- {s.label}
-
- ))}
-
- )}
-
- {xLabel &&
{xLabel}
}
-
- )
-}
diff --git a/src/design-system/composites/_chart-utils.ts b/src/design-system/composites/_chart-utils.ts
deleted file mode 100644
index da8cbe9..0000000
--- a/src/design-system/composites/_chart-utils.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-export interface DataPoint {
- x: number | Date
- y: number
-}
-
-export interface ChartSeries {
- label: string
- data: DataPoint[]
- color?: string
-}
-
-export interface ChartDimensions {
- width: number
- height: number
- paddingTop: number
- paddingRight: number
- paddingBottom: number
- paddingLeft: number
-}
-
-export function computeYScale(
- series: ChartSeries[],
- dims: ChartDimensions,
- threshold?: number,
-) {
- const allY = series.flatMap((s) => s.data.map((d) => d.y))
- if (threshold != null) allY.push(threshold)
- const min = 0
- const max = Math.max(...allY, 1)
- const range = max - min
-
- const plotH = dims.height - dims.paddingTop - dims.paddingBottom
- const toY = (val: number) => dims.paddingTop + plotH - ((val - min) / range) * plotH
-
- return { min, max, range, toY }
-}
-
-export function computeXScale(series: ChartSeries[], dims: ChartDimensions) {
- const allX = series.flatMap((s) =>
- s.data.map((d) => (d.x instanceof Date ? d.x.getTime() : d.x)),
- )
- const minX = Math.min(...allX)
- const maxX = Math.max(...allX)
- const rangeX = maxX - minX || 1
-
- const plotW = dims.width - dims.paddingLeft - dims.paddingRight
- const toX = (val: number | Date) => {
- const v = val instanceof Date ? val.getTime() : val
- return dims.paddingLeft + ((v - minX) / rangeX) * plotW
- }
-
- return { minX, maxX, rangeX, toX }
-}
-
-export function seriesPoints(
- series: ChartSeries,
- toX: (v: number | Date) => number,
- toY: (v: number) => number,
-): string {
- return series.data.map((d) => `${toX(d.x).toFixed(1)},${toY(d.y).toFixed(1)}`).join(' ')
-}
-
-export function seriesPath(
- series: ChartSeries,
- toX: (v: number | Date) => number,
- toY: (v: number) => number,
- bottomY: number,
-): string {
- if (series.data.length === 0) return ''
- const pts = series.data.map((d) => `${toX(d.x).toFixed(1)},${toY(d.y).toFixed(1)}`)
- const firstX = toX(series.data[0].x).toFixed(1)
- const lastX = toX(series.data[series.data.length - 1].x).toFixed(1)
- return `M${pts.join(' L')} L${lastX},${bottomY} L${firstX},${bottomY} Z`
-}
-
-export function formatAxisLabel(val: number): string {
- if (val >= 1_000_000) return `${(val / 1_000_000).toFixed(1)}M`
- if (val >= 1000) return `${(val / 1000).toFixed(1)}k`
- if (Number.isInteger(val)) return String(val)
- return val.toFixed(1)
-}
-
-export function formatXLabel(val: number | Date, _totalPoints: number): string {
- if (val instanceof Date || (typeof val === 'number' && val > 1e10)) {
- const d = val instanceof Date ? val : new Date(val)
- return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
- }
- return formatAxisLabel(val as number)
-}
-
-// Default color tokens for series (fallback when no color given)
-export const CHART_COLORS = [
- 'var(--chart-1)',
- 'var(--chart-2)',
- 'var(--chart-3)',
- 'var(--chart-4)',
- 'var(--chart-5)',
- 'var(--chart-6)',
- 'var(--chart-7)',
- 'var(--chart-8)',
-]
diff --git a/src/design-system/composites/index.ts b/src/design-system/composites/index.ts
index d423caa..bef25e2 100644
--- a/src/design-system/composites/index.ts
+++ b/src/design-system/composites/index.ts
@@ -1,8 +1,6 @@
export { Accordion } from './Accordion/Accordion'
export { AlertDialog } from './AlertDialog/AlertDialog'
-export { AreaChart } from './AreaChart/AreaChart'
export { AvatarGroup } from './AvatarGroup/AvatarGroup'
-export { BarChart } from './BarChart/BarChart'
export { Breadcrumb } from './Breadcrumb/Breadcrumb'
export { CommandPalette } from './CommandPalette/CommandPalette'
export type { SearchResult, SearchCategory, ScopeFilter } from './CommandPalette/types'
@@ -20,7 +18,6 @@ export { KpiStrip } from './KpiStrip/KpiStrip'
export type { KpiItem, KpiStripProps } from './KpiStrip/KpiStrip'
export type { FeedEvent } from './EventFeed/EventFeed'
export { FilterBar } from './FilterBar/FilterBar'
-export { LineChart } from './LineChart/LineChart'
export { LogViewer } from './LogViewer/LogViewer'
export type { LogEntry, LogViewerProps } from './LogViewer/LogViewer'
export { LoginDialog } from './LoginForm/LoginDialog'
@@ -43,6 +40,11 @@ export { Tabs } from './Tabs/Tabs'
export { ToastProvider, useToast } from './Toast/Toast'
export { TreeView } from './TreeView/TreeView'
-// Chart utilities for consumers using Recharts or custom charts
-export { CHART_COLORS } from './_chart-utils'
-export type { ChartSeries, DataPoint } from './_chart-utils'
+// Charts — ThemedChart wrapper + Recharts re-exports
+export { ThemedChart } from './ThemedChart/ThemedChart'
+export { CHART_COLORS, rechartsTheme } from '../utils/rechartsTheme'
+export {
+ Line, Area, Bar,
+ ReferenceLine, ReferenceArea,
+ Legend, Brush,
+} from 'recharts'
diff --git a/src/design-system/index.ts b/src/design-system/index.ts
index dd485c1..254049a 100644
--- a/src/design-system/index.ts
+++ b/src/design-system/index.ts
@@ -11,4 +11,4 @@ export { BreadcrumbProvider, useBreadcrumb } from './providers/BreadcrumbProvide
export type { BreadcrumbItem } from './providers/BreadcrumbProvider'
export * from './utils/hashColor'
export * from './utils/timePresets'
-export { rechartsTheme } from './utils/rechartsTheme'
+