chartifypdf 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ContextMenu.d.ts +15 -0
- package/dist/components/ExportButton.d.ts +16 -0
- package/dist/components/LinePath.d.ts +3 -1
- package/dist/components/PeakMarkers.d.ts +11 -0
- package/dist/hooks/useDataDomain.d.ts +6 -0
- package/dist/hooks/useScales.d.ts +1 -1
- package/dist/hooks/useTooltip.d.ts +1 -0
- package/dist/hooks/useZoom.d.ts +5 -3
- package/dist/index.d.ts +48 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/chart.types.d.ts +27 -0
- package/dist/utils/exportPdf.d.ts +5 -0
- package/dist/utils/math.d.ts +1 -0
- package/dist/utils/path.d.ts +5 -0
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/utils/color.ts","../src/hooks/useChartDimensions.ts","../src/utils/math.ts","../src/hooks/useScales.ts","../src/hooks/useZoom.ts","../src/hooks/useTooltip.ts","../src/components/Axes.tsx","../src/components/GridLines.tsx","../src/components/LinePath.tsx","../src/utils/path.ts","../src/components/Tooltip.tsx","../src/components/ZoomControls.tsx","../src/components/LineChart.tsx"],"sourcesContent":["export const DEFAULT_PALETTE = [\n \"#4e79a7\",\n \"#f28e2b\",\n \"#e15759\",\n \"#76b7b2\",\n \"#59a14f\",\n \"#edc948\",\n \"#b07aa1\",\n \"#ff9da7\",\n \"#9c755f\",\n \"#bab0ac\",\n];\n\nexport function getSeriesColor(index: number, palette?: string[]): string {\n const colors = palette ?? DEFAULT_PALETTE;\n return colors[index % colors.length];\n}\n","import { useLayoutEffect, useState, type RefObject } from \"react\";\n\nexport function useChartDimensions(\n containerRef: RefObject<HTMLDivElement | null>,\n providedWidth?: number,\n providedHeight?: number\n): { width: number; height: number } {\n const [dimensions, setDimensions] = useState<{\n width: number;\n height: number;\n }>({\n width: providedWidth ?? 0,\n height: providedHeight ?? 0,\n });\n\n const isFixed =\n providedWidth !== undefined && providedHeight !== undefined;\n\n useLayoutEffect(() => {\n if (isFixed) {\n setDimensions({ width: providedWidth!, height: providedHeight! });\n return;\n }\n\n const node = containerRef.current;\n if (!node) return;\n\n const measure = () => {\n const rect = node.getBoundingClientRect();\n setDimensions({ width: rect.width, height: rect.height });\n };\n\n measure();\n\n const observer = new ResizeObserver(() => {\n measure();\n });\n observer.observe(node);\n\n return () => observer.disconnect();\n }, [containerRef, isFixed, providedWidth, providedHeight]);\n\n return dimensions;\n}\n","export function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nexport function lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nexport function inverseLerp(a: number, b: number, value: number): number {\n if (a === b) return 0;\n return (value - a) / (b - a);\n}\n\nexport function niceNumber(range: number, round: boolean): number {\n const exponent = Math.floor(Math.log10(range));\n const fraction = range / Math.pow(10, exponent);\n\n let niceFraction: number;\n if (round) {\n if (fraction < 1.5) niceFraction = 1;\n else if (fraction < 3) niceFraction = 2;\n else if (fraction < 7) niceFraction = 5;\n else niceFraction = 10;\n } else {\n if (fraction <= 1) niceFraction = 1;\n else if (fraction <= 2) niceFraction = 2;\n else if (fraction <= 5) niceFraction = 5;\n else niceFraction = 10;\n }\n\n return niceFraction * Math.pow(10, exponent);\n}\n\nexport function niceRange(\n min: number,\n max: number,\n tickCount: number\n): { niceMin: number; niceMax: number; tickStep: number } {\n if (min === max) {\n const offset = min === 0 ? 1 : Math.abs(min) * 0.1;\n min = min - offset;\n max = max + offset;\n }\n\n const range = niceNumber(max - min, false);\n const tickStep = niceNumber(range / (tickCount - 1), true);\n const niceMin = Math.floor(min / tickStep) * tickStep;\n const niceMax = Math.ceil(max / tickStep) * tickStep;\n\n return { niceMin, niceMax, tickStep };\n}\n\nexport function linearScale(\n domainMin: number,\n domainMax: number,\n rangeMin: number,\n rangeMax: number\n): (value: number) => number {\n return (value: number) => {\n if (domainMax === domainMin) return (rangeMin + rangeMax) / 2;\n const t = inverseLerp(domainMin, domainMax, value);\n return lerp(rangeMin, rangeMax, t);\n };\n}\n\nexport function generateTicks(\n niceMin: number,\n niceMax: number,\n tickStep: number\n): number[] {\n const ticks: number[] = [];\n for (let v = niceMin; v <= niceMax + tickStep * 0.5; v += tickStep) {\n ticks.push(parseFloat(v.toPrecision(12)));\n }\n return ticks;\n}\n","import { useMemo } from \"react\";\nimport type { DataSeries, AxisConfig } from \"../types/chart.types\";\nimport { niceRange, linearScale, generateTicks } from \"../utils/math\";\n\nexport interface ScalesResult {\n xScale: (value: number) => number;\n yScale: (value: number) => number;\n xTicks: number[];\n yTicks: number[];\n xDomain: [number, number];\n yDomain: [number, number];\n}\n\nexport function useScales(\n data: DataSeries[],\n plotWidth: number,\n plotHeight: number,\n xAxisConfig?: AxisConfig,\n yAxisConfig?: AxisConfig\n): ScalesResult {\n return useMemo(() => {\n let xMin = Infinity;\n let xMax = -Infinity;\n let yMin = Infinity;\n let yMax = -Infinity;\n\n for (const series of data) {\n for (const point of series.data) {\n if (point.x < xMin) xMin = point.x;\n if (point.x > xMax) xMax = point.x;\n if (point.y < yMin) yMin = point.y;\n if (point.y > yMax) yMax = point.y;\n }\n }\n\n if (!isFinite(xMin)) {\n xMin = 0;\n xMax = 1;\n yMin = 0;\n yMax = 1;\n }\n\n if (xAxisConfig?.min !== undefined) xMin = xAxisConfig.min;\n if (xAxisConfig?.max !== undefined) xMax = xAxisConfig.max;\n if (yAxisConfig?.min !== undefined) yMin = yAxisConfig.min;\n if (yAxisConfig?.max !== undefined) yMax = yAxisConfig.max;\n\n const xTickCount = xAxisConfig?.tickCount ?? 6;\n const yTickCount = yAxisConfig?.tickCount ?? 6;\n\n const xNice = niceRange(xMin, xMax, xTickCount);\n const yNice = niceRange(yMin, yMax, yTickCount);\n\n const xScale = linearScale(xNice.niceMin, xNice.niceMax, 0, plotWidth);\n // Y inverted: SVG Y increases downward\n const yScale = linearScale(yNice.niceMin, yNice.niceMax, plotHeight, 0);\n\n const xTicks = generateTicks(xNice.niceMin, xNice.niceMax, xNice.tickStep);\n const yTicks = generateTicks(yNice.niceMin, yNice.niceMax, yNice.tickStep);\n\n return {\n xScale,\n yScale,\n xTicks,\n yTicks,\n xDomain: [xNice.niceMin, xNice.niceMax] as [number, number],\n yDomain: [yNice.niceMin, yNice.niceMax] as [number, number],\n };\n }, [data, plotWidth, plotHeight, xAxisConfig, yAxisConfig]);\n}\n","import { useState, useCallback, useRef } from \"react\";\nimport type { ZoomConfig } from \"../types/chart.types\";\nimport { clamp } from \"../utils/math\";\n\nexport interface ZoomState {\n scale: number;\n offsetX: number;\n offsetY: number;\n isPanning: boolean;\n zoomIn: () => void;\n zoomOut: () => void;\n resetZoom: () => void;\n handleWheel: (e: React.WheelEvent) => void;\n handlePanStart: (e: React.MouseEvent) => void;\n handlePanMove: (e: React.MouseEvent) => void;\n handlePanEnd: () => void;\n}\n\nexport function useZoom(\n config: ZoomConfig | undefined,\n plotWidth: number,\n plotHeight: number\n): ZoomState {\n const enabled = config?.enabled ?? false;\n const minScale = config?.minScale ?? 1;\n const maxScale = config?.maxScale ?? 10;\n const step = config?.step ?? 0.5;\n const enableWheel = config?.enableWheel ?? true;\n const enablePan = config?.enablePan ?? true;\n\n const [scale, setScale] = useState(1);\n const [offsetX, setOffsetX] = useState(0);\n const [offsetY, setOffsetY] = useState(0);\n const [isPanning, setIsPanning] = useState(false);\n const panStart = useRef({ x: 0, y: 0, offsetX: 0, offsetY: 0 });\n\n const clampOffset = useCallback(\n (ox: number, oy: number, s: number) => {\n const maxOffsetX = plotWidth * (s - 1);\n const maxOffsetY = plotHeight * (s - 1);\n return {\n x: clamp(ox, -maxOffsetX, 0),\n y: clamp(oy, -maxOffsetY, 0),\n };\n },\n [plotWidth, plotHeight]\n );\n\n const zoomIn = useCallback(() => {\n if (!enabled) return;\n setScale((prev) => {\n const next = clamp(prev + step, minScale, maxScale);\n // Center zoom\n setOffsetX((ox) => {\n const newOx = ox - (plotWidth * step) / 2;\n return clampOffset(newOx, 0, next).x;\n });\n setOffsetY((oy) => {\n const newOy = oy - (plotHeight * step) / 2;\n return clampOffset(0, newOy, next).y;\n });\n return next;\n });\n }, [enabled, step, minScale, maxScale, plotWidth, plotHeight, clampOffset]);\n\n const zoomOut = useCallback(() => {\n if (!enabled) return;\n setScale((prev) => {\n const next = clamp(prev - step, minScale, maxScale);\n setOffsetX((ox) => {\n const newOx = ox + (plotWidth * step) / 2;\n return clampOffset(newOx, 0, next).x;\n });\n setOffsetY((oy) => {\n const newOy = oy + (plotHeight * step) / 2;\n return clampOffset(0, newOy, next).y;\n });\n return next;\n });\n }, [enabled, step, minScale, maxScale, plotWidth, plotHeight, clampOffset]);\n\n const resetZoom = useCallback(() => {\n setScale(1);\n setOffsetX(0);\n setOffsetY(0);\n }, []);\n\n const handleWheel = useCallback(\n (e: React.WheelEvent) => {\n if (!enabled || !enableWheel) return;\n e.preventDefault();\n\n const rect = (e.currentTarget as SVGElement).getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n\n const delta = e.deltaY < 0 ? step : -step;\n\n setScale((prev) => {\n const next = clamp(prev + delta, minScale, maxScale);\n const ratio = next / prev;\n\n setOffsetX((ox) => {\n const newOx = mouseX - ratio * (mouseX - ox);\n return clampOffset(newOx, 0, next).x;\n });\n setOffsetY((oy) => {\n const newOy = mouseY - ratio * (mouseY - oy);\n return clampOffset(0, newOy, next).y;\n });\n\n return next;\n });\n },\n [enabled, enableWheel, step, minScale, maxScale, clampOffset]\n );\n\n const handlePanStart = useCallback(\n (e: React.MouseEvent) => {\n if (!enabled || !enablePan || scale <= 1) return;\n if (e.button !== 0) return; // left-click only\n setIsPanning(true);\n panStart.current = {\n x: e.clientX,\n y: e.clientY,\n offsetX,\n offsetY,\n };\n },\n [enabled, enablePan, scale, offsetX, offsetY]\n );\n\n const handlePanMove = useCallback(\n (e: React.MouseEvent) => {\n if (!isPanning) return;\n const dx = e.clientX - panStart.current.x;\n const dy = e.clientY - panStart.current.y;\n const clamped = clampOffset(\n panStart.current.offsetX + dx,\n panStart.current.offsetY + dy,\n scale\n );\n setOffsetX(clamped.x);\n setOffsetY(clamped.y);\n },\n [isPanning, scale, clampOffset]\n );\n\n const handlePanEnd = useCallback(() => {\n setIsPanning(false);\n }, []);\n\n return {\n scale,\n offsetX,\n offsetY,\n isPanning,\n zoomIn,\n zoomOut,\n resetZoom,\n handleWheel,\n handlePanStart,\n handlePanMove,\n handlePanEnd,\n };\n}\n","import { useState, useCallback, type RefObject } from \"react\";\nimport type { DataSeries, DataPoint, TooltipConfig } from \"../types/chart.types\";\n\nexport interface TooltipState {\n tooltipVisible: boolean;\n tooltipX: number;\n tooltipY: number;\n activePoint: DataPoint | null;\n activeSeries: DataSeries | null;\n handleMouseMove: (e: React.MouseEvent) => void;\n handleMouseLeave: () => void;\n}\n\nfunction findNearestByPixelX(\n sorted: DataPoint[],\n mouseX: number,\n xScale: (v: number) => number\n): DataPoint | null {\n if (sorted.length === 0) return null;\n\n let low = 0;\n let high = sorted.length - 1;\n\n // Binary search for nearest pixel X\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n if (xScale(sorted[mid].x) < mouseX) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n let nearest = sorted[low];\n let minDist = Math.abs(xScale(nearest.x) - mouseX);\n\n if (low > 0) {\n const dist = Math.abs(xScale(sorted[low - 1].x) - mouseX);\n if (dist < minDist) {\n minDist = dist;\n nearest = sorted[low - 1];\n }\n }\n\n return nearest;\n}\n\nexport function useTooltip(\n svgRef: RefObject<SVGSVGElement | null>,\n data: DataSeries[],\n xScale: (v: number) => number,\n yScale: (v: number) => number,\n marginLeft: number,\n marginTop: number,\n plotWidth: number,\n _config?: TooltipConfig\n): TooltipState {\n const [tooltipVisible, setTooltipVisible] = useState(false);\n const [tooltipX, setTooltipX] = useState(0);\n const [tooltipY, setTooltipY] = useState(0);\n const [activePoint, setActivePoint] = useState<DataPoint | null>(null);\n const [activeSeries, setActiveSeries] = useState<DataSeries | null>(null);\n\n const handleMouseMove = useCallback(\n (e: React.MouseEvent) => {\n const svg = svgRef.current;\n if (!svg) return;\n\n const rect = svg.getBoundingClientRect();\n const mouseX = e.clientX - rect.left - marginLeft;\n\n if (mouseX < 0 || mouseX > plotWidth) {\n setTooltipVisible(false);\n return;\n }\n\n let closestPoint: DataPoint | null = null;\n let closestSeries: DataSeries | null = null;\n let closestDist = Infinity;\n\n for (const series of data) {\n const nearest = findNearestByPixelX(series.data, mouseX, xScale);\n if (!nearest) continue;\n\n const dist = Math.abs(xScale(nearest.x) - mouseX);\n if (dist < closestDist) {\n closestDist = dist;\n closestPoint = nearest;\n closestSeries = series;\n }\n }\n\n if (closestPoint && closestSeries) {\n setTooltipX(xScale(closestPoint.x));\n setTooltipY(yScale(closestPoint.y));\n setActivePoint(closestPoint);\n setActiveSeries(closestSeries);\n setTooltipVisible(true);\n }\n },\n [svgRef, data, xScale, yScale, marginLeft, plotWidth]\n );\n\n const handleMouseLeave = useCallback(() => {\n setTooltipVisible(false);\n setActivePoint(null);\n setActiveSeries(null);\n }, []);\n\n return {\n tooltipVisible,\n tooltipX,\n tooltipY,\n activePoint,\n activeSeries,\n handleMouseMove,\n handleMouseLeave,\n };\n}\n","import React from \"react\";\nimport type { AxisConfig, ChartStyle } from \"../types/chart.types\";\n\ninterface AxesProps {\n xTicks: number[];\n yTicks: number[];\n xScale: (v: number) => number;\n yScale: (v: number) => number;\n plotWidth: number;\n plotHeight: number;\n xAxisConfig?: AxisConfig;\n yAxisConfig?: AxisConfig;\n style?: ChartStyle;\n}\n\nconst defaultTickFormat = (v: number): string => {\n if (Math.abs(v) >= 1e6) return `${(v / 1e6).toFixed(1)}M`;\n if (Math.abs(v) >= 1e3) return `${(v / 1e3).toFixed(1)}K`;\n return Number.isInteger(v) ? v.toString() : v.toFixed(1);\n};\n\nexport const Axes: React.FC<AxesProps> = React.memo(\n ({\n xTicks,\n yTicks,\n xScale,\n yScale,\n plotWidth,\n plotHeight,\n xAxisConfig,\n yAxisConfig,\n style,\n }) => {\n const axisColor = style?.axisColor ?? \"#333\";\n const tickColor = style?.tickColor ?? \"#666\";\n const fontFamily = style?.fontFamily ?? \"sans-serif\";\n const fontSize = style?.fontSize ?? 11;\n const xTickFormat = xAxisConfig?.tickFormat ?? defaultTickFormat;\n const yTickFormat = yAxisConfig?.tickFormat ?? defaultTickFormat;\n\n return (\n <g className=\"chartifypdf-axes\">\n {/* X Axis */}\n <line\n x1={0}\n y1={plotHeight}\n x2={plotWidth}\n y2={plotHeight}\n stroke={axisColor}\n strokeWidth={1}\n />\n {xTicks.map((tick) => {\n const x = xScale(tick);\n return (\n <g key={`x-tick-${tick}`}>\n <line\n x1={x}\n y1={plotHeight}\n x2={x}\n y2={plotHeight + 6}\n stroke={axisColor}\n strokeWidth={1}\n />\n <text\n x={x}\n y={plotHeight + 18}\n textAnchor=\"middle\"\n fill={tickColor}\n fontFamily={fontFamily}\n fontSize={fontSize}\n >\n {xTickFormat(tick)}\n </text>\n </g>\n );\n })}\n {xAxisConfig?.label && (\n <text\n x={plotWidth / 2}\n y={plotHeight + 38}\n textAnchor=\"middle\"\n fill={axisColor}\n fontFamily={fontFamily}\n fontSize={fontSize + 1}\n fontWeight=\"bold\"\n >\n {xAxisConfig.label}\n </text>\n )}\n\n {/* Y Axis */}\n <line\n x1={0}\n y1={0}\n x2={0}\n y2={plotHeight}\n stroke={axisColor}\n strokeWidth={1}\n />\n {yTicks.map((tick) => {\n const y = yScale(tick);\n return (\n <g key={`y-tick-${tick}`}>\n <line\n x1={-6}\n y1={y}\n x2={0}\n y2={y}\n stroke={axisColor}\n strokeWidth={1}\n />\n <text\n x={-10}\n y={y}\n textAnchor=\"end\"\n dominantBaseline=\"middle\"\n fill={tickColor}\n fontFamily={fontFamily}\n fontSize={fontSize}\n >\n {yTickFormat(tick)}\n </text>\n </g>\n );\n })}\n {yAxisConfig?.label && (\n <text\n x={0}\n y={0}\n textAnchor=\"middle\"\n fill={axisColor}\n fontFamily={fontFamily}\n fontSize={fontSize + 1}\n fontWeight=\"bold\"\n transform={`translate(${-40}, ${plotHeight / 2}) rotate(-90)`}\n >\n {yAxisConfig.label}\n </text>\n )}\n </g>\n );\n }\n);\n\nAxes.displayName = \"Axes\";\n","import React from \"react\";\n\ninterface GridLinesProps {\n xTicks: number[];\n yTicks: number[];\n xScale: (v: number) => number;\n yScale: (v: number) => number;\n plotWidth: number;\n plotHeight: number;\n showXGrid: boolean;\n showYGrid: boolean;\n xGridColor?: string;\n yGridColor?: string;\n}\n\nexport const GridLines: React.FC<GridLinesProps> = React.memo(\n ({\n xTicks,\n yTicks,\n xScale,\n yScale,\n plotWidth,\n plotHeight,\n showXGrid,\n showYGrid,\n xGridColor = \"#e0e0e0\",\n yGridColor = \"#e0e0e0\",\n }) => {\n return (\n <g className=\"chartifypdf-grid\">\n {showYGrid &&\n yTicks.map((tick) => {\n const y = yScale(tick);\n return (\n <line\n key={`y-grid-${tick}`}\n x1={0}\n y1={y}\n x2={plotWidth}\n y2={y}\n stroke={yGridColor}\n strokeDasharray=\"4 4\"\n strokeOpacity={0.5}\n />\n );\n })}\n {showXGrid &&\n xTicks.map((tick) => {\n const x = xScale(tick);\n return (\n <line\n key={`x-grid-${tick}`}\n x1={x}\n y1={0}\n x2={x}\n y2={plotHeight}\n stroke={xGridColor}\n strokeDasharray=\"4 4\"\n strokeOpacity={0.5}\n />\n );\n })}\n </g>\n );\n }\n);\n\nGridLines.displayName = \"GridLines\";\n","import React, { useRef, useEffect, useState } from \"react\";\nimport type { DataSeries, DataPoint, AnimationConfig } from \"../types/chart.types\";\nimport { buildLinePath } from \"../utils/path\";\n\ninterface LinePathProps {\n series: DataSeries;\n xScale: (v: number) => number;\n yScale: (v: number) => number;\n color: string;\n animation?: AnimationConfig;\n onPointClick?: (point: DataPoint, series: DataSeries) => void;\n}\n\nexport const LinePath: React.FC<LinePathProps> = React.memo(\n ({ series, xScale, yScale, color, animation, onPointClick }) => {\n const pathRef = useRef<SVGPathElement>(null);\n const [pathLength, setPathLength] = useState(0);\n const [animationDone, setAnimationDone] = useState(!animation?.enabled);\n\n const strokeWidth = series.strokeWidth ?? 2;\n const showDots = series.showDots ?? false;\n const dotRadius = series.dotRadius ?? 3.5;\n\n const points = series.data.map((pt) => ({\n x: xScale(pt.x),\n y: yScale(pt.y),\n }));\n\n const d = buildLinePath(points);\n\n useEffect(() => {\n if (animation?.enabled && pathRef.current) {\n const length = pathRef.current.getTotalLength();\n setPathLength(length);\n setAnimationDone(false);\n\n const timer = setTimeout(() => {\n setAnimationDone(true);\n }, animation.duration ?? 800);\n\n return () => clearTimeout(timer);\n }\n }, [d, animation?.enabled, animation?.duration]);\n\n const animStyle: React.CSSProperties =\n animation?.enabled && pathLength > 0\n ? {\n strokeDasharray: pathLength,\n strokeDashoffset: animationDone ? 0 : pathLength,\n transition: `stroke-dashoffset ${animation.duration ?? 800}ms ${animation.easing ?? \"ease-in-out\"}`,\n }\n : {};\n\n return (\n <g className=\"chartifypdf-line-path\">\n <path\n ref={pathRef}\n d={d}\n fill=\"none\"\n stroke={color}\n strokeWidth={strokeWidth}\n strokeDasharray={series.strokeDasharray}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={animStyle}\n />\n {showDots &&\n series.data.map((pt, i) => (\n <circle\n key={`dot-${series.id}-${i}`}\n cx={xScale(pt.x)}\n cy={yScale(pt.y)}\n r={dotRadius}\n fill={color}\n stroke=\"#fff\"\n strokeWidth={1.5}\n style={{ cursor: onPointClick ? \"pointer\" : \"default\" }}\n onClick={\n onPointClick\n ? () => onPointClick(pt, series)\n : undefined\n }\n />\n ))}\n </g>\n );\n }\n);\n\nLinePath.displayName = \"LinePath\";\n","export function buildLinePath(\n points: { x: number; y: number }[]\n): string {\n if (points.length === 0) return \"\";\n const [first, ...rest] = points;\n return `M${first.x},${first.y}` + rest.map((p) => `L${p.x},${p.y}`).join(\"\");\n}\n","import React from \"react\";\nimport type { DataPoint, DataSeries, TooltipConfig } from \"../types/chart.types\";\n\ninterface TooltipProps {\n visible: boolean;\n x: number;\n y: number;\n point: DataPoint | null;\n series: DataSeries | null;\n plotHeight: number;\n plotWidth: number;\n config?: TooltipConfig;\n seriesColor: string;\n}\n\nexport const Tooltip: React.FC<TooltipProps> = React.memo(\n ({ visible, x, y, point, series, plotHeight, plotWidth, config, seriesColor }) => {\n if (!visible || !point || !series) return null;\n\n const bgColor = config?.backgroundColor ?? \"rgba(0, 0, 0, 0.8)\";\n const textColor = config?.textColor ?? \"#fff\";\n\n // Determine tooltip text\n let content: React.ReactNode;\n if (config?.renderCustom) {\n content = config.renderCustom(point, series);\n } else {\n const text = config?.formatter\n ? config.formatter(point, series)\n : `${series.name}: (${point.x}, ${point.y})`;\n content = text;\n }\n\n // Smart positioning: flip when near edges\n const tooltipWidth = 140;\n const tooltipHeight = 40;\n let tx = x + 12;\n let ty = y - tooltipHeight - 8;\n\n if (tx + tooltipWidth > plotWidth) {\n tx = x - tooltipWidth - 12;\n }\n if (ty < 0) {\n ty = y + 12;\n }\n\n return (\n <g className=\"chartifypdf-tooltip\" pointerEvents=\"none\">\n {/* Vertical indicator line */}\n <line\n x1={x}\n y1={0}\n x2={x}\n y2={plotHeight}\n stroke=\"#999\"\n strokeWidth={1}\n strokeDasharray=\"4 4\"\n opacity={0.6}\n />\n {/* Highlight circle */}\n <circle\n cx={x}\n cy={y}\n r={5}\n fill={seriesColor}\n stroke=\"#fff\"\n strokeWidth={2}\n />\n {/* Tooltip box using foreignObject */}\n <foreignObject\n x={tx}\n y={ty}\n width={tooltipWidth}\n height={tooltipHeight + 20}\n overflow=\"visible\"\n >\n <div\n style={{\n backgroundColor: bgColor,\n color: textColor,\n padding: \"6px 10px\",\n borderRadius: \"4px\",\n fontSize: \"12px\",\n fontFamily: \"sans-serif\",\n whiteSpace: \"nowrap\",\n lineHeight: 1.4,\n boxShadow: \"0 2px 6px rgba(0,0,0,0.2)\",\n }}\n >\n {content}\n </div>\n </foreignObject>\n </g>\n );\n }\n);\n\nTooltip.displayName = \"Tooltip\";\n","import React from \"react\";\nimport type { ZoomConfig } from \"../types/chart.types\";\n\ninterface ZoomControlsProps {\n config?: ZoomConfig;\n svgWidth: number;\n svgHeight: number;\n margin: { top: number; right: number; bottom: number; left: number };\n scale: number;\n onZoomIn: () => void;\n onZoomOut: () => void;\n onReset: () => void;\n}\n\nexport const ZoomControls: React.FC<ZoomControlsProps> = React.memo(\n ({ config, svgWidth, svgHeight, margin, scale, onZoomIn, onZoomOut, onReset }) => {\n if (!config?.enabled || config.showControls === false) return null;\n\n const position = config.controlsPosition ?? \"top-right\";\n const btnSize = 24;\n const gap = 4;\n const groupWidth = btnSize * 3 + gap * 2;\n const groupHeight = btnSize;\n const padding = 8;\n\n let gx: number;\n let gy: number;\n\n switch (position) {\n case \"top-left\":\n gx = margin.left + padding;\n gy = margin.top + padding;\n break;\n case \"bottom-left\":\n gx = margin.left + padding;\n gy = svgHeight - margin.bottom - groupHeight - padding;\n break;\n case \"bottom-right\":\n gx = svgWidth - margin.right - groupWidth - padding;\n gy = svgHeight - margin.bottom - groupHeight - padding;\n break;\n case \"top-right\":\n default:\n gx = svgWidth - margin.right - groupWidth - padding;\n gy = margin.top + padding;\n break;\n }\n\n const buttons = [\n { label: \"+\", onClick: onZoomIn },\n { label: \"\\u2013\", onClick: onReset }, // en-dash for reset\n { label: \"\\u2212\", onClick: onZoomOut }, // minus sign\n ];\n\n // Replace middle button with reset when zoomed, or show as \"-\"\n const displayButtons =\n scale > 1\n ? [\n { label: \"+\", onClick: onZoomIn },\n { label: \"\\u2212\", onClick: onZoomOut },\n { label: \"\\u21BA\", onClick: onReset },\n ]\n : [\n { label: \"+\", onClick: onZoomIn },\n { label: \"\\u2212\", onClick: onZoomOut },\n ];\n\n return (\n <g\n className=\"chartifypdf-zoom-controls\"\n transform={`translate(${gx}, ${gy})`}\n >\n {displayButtons.map((btn, i) => (\n <g\n key={btn.label}\n transform={`translate(${i * (btnSize + gap)}, 0)`}\n onClick={btn.onClick}\n style={{ cursor: \"pointer\" }}\n >\n <rect\n width={btnSize}\n height={btnSize}\n rx={4}\n fill=\"rgba(255,255,255,0.9)\"\n stroke=\"#ccc\"\n strokeWidth={1}\n />\n <text\n x={btnSize / 2}\n y={btnSize / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={14}\n fontFamily=\"sans-serif\"\n fill=\"#333\"\n fontWeight=\"bold\"\n >\n {btn.label}\n </text>\n </g>\n ))}\n </g>\n );\n }\n);\n\nZoomControls.displayName = \"ZoomControls\";\n","import React, { useRef, useId } from \"react\";\nimport type {\n LineChartProps,\n ChartMargin,\n} from \"../types/chart.types\";\nimport { getSeriesColor } from \"../utils/color\";\nimport { useChartDimensions } from \"../hooks/useChartDimensions\";\nimport { useScales } from \"../hooks/useScales\";\nimport { useZoom } from \"../hooks/useZoom\";\nimport { useTooltip } from \"../hooks/useTooltip\";\nimport { Axes } from \"./Axes\";\nimport { GridLines } from \"./GridLines\";\nimport { LinePath } from \"./LinePath\";\nimport { Tooltip } from \"./Tooltip\";\nimport { ZoomControls } from \"./ZoomControls\";\n\nconst DEFAULT_MARGIN: ChartMargin = {\n top: 20,\n right: 20,\n bottom: 50,\n left: 60,\n};\n\nconst DEFAULT_HEIGHT = 400;\n\nexport const LineChart: React.FC<LineChartProps> = ({\n data,\n width: providedWidth,\n height: providedHeight,\n margin: marginOverride,\n xAxis: xAxisConfig,\n yAxis: yAxisConfig,\n tooltip: tooltipConfig,\n zoom: zoomConfig,\n style: chartStyle,\n animation,\n colorPalette,\n className,\n onPointClick,\n ariaLabel,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const svgRef = useRef<SVGSVGElement>(null);\n const clipId = useId();\n\n const margin: ChartMargin = {\n ...DEFAULT_MARGIN,\n ...marginOverride,\n };\n\n const dimensions = useChartDimensions(\n containerRef,\n providedWidth,\n providedHeight ?? DEFAULT_HEIGHT\n );\n const { width: svgWidth, height: svgHeight } = dimensions;\n\n const plotWidth = Math.max(0, svgWidth - margin.left - margin.right);\n const plotHeight = Math.max(0, svgHeight - margin.top - margin.bottom);\n\n const { xScale, yScale, xTicks, yTicks } = useScales(\n data,\n plotWidth,\n plotHeight,\n xAxisConfig,\n yAxisConfig\n );\n\n const zoomState = useZoom(zoomConfig, plotWidth, plotHeight);\n\n const tooltipEnabled = tooltipConfig?.enabled !== false;\n const tooltipState = useTooltip(\n svgRef,\n data,\n xScale,\n yScale,\n margin.left,\n margin.top,\n plotWidth,\n tooltipConfig\n );\n\n const showXGrid = xAxisConfig?.gridLines ?? false;\n const showYGrid = yAxisConfig?.gridLines ?? true;\n\n // Don't render until we have dimensions\n if (svgWidth === 0 || svgHeight === 0) {\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n width: providedWidth ?? \"100%\",\n height: providedHeight ?? DEFAULT_HEIGHT,\n backgroundColor: chartStyle?.backgroundColor,\n }}\n />\n );\n }\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n width: providedWidth ?? \"100%\",\n height: providedHeight ?? DEFAULT_HEIGHT,\n backgroundColor: chartStyle?.backgroundColor,\n }}\n >\n <svg\n ref={svgRef}\n width={svgWidth}\n height={svgHeight}\n role=\"img\"\n aria-label={ariaLabel ?? \"Line chart\"}\n onMouseMove={\n tooltipEnabled && !zoomState.isPanning\n ? tooltipState.handleMouseMove\n : undefined\n }\n onMouseLeave={tooltipEnabled ? tooltipState.handleMouseLeave : undefined}\n onWheel={zoomConfig?.enabled ? zoomState.handleWheel : undefined}\n onMouseDown={zoomConfig?.enabled ? zoomState.handlePanStart : undefined}\n onMouseUp={zoomConfig?.enabled ? zoomState.handlePanEnd : undefined}\n style={{\n userSelect: zoomState.isPanning ? \"none\" : undefined,\n cursor: zoomState.isPanning\n ? \"grabbing\"\n : zoomConfig?.enabled && zoomState.scale > 1\n ? \"grab\"\n : undefined,\n }}\n >\n <defs>\n <clipPath id={clipId}>\n <rect width={plotWidth} height={plotHeight} />\n </clipPath>\n </defs>\n\n <g transform={`translate(${margin.left}, ${margin.top})`}>\n {/* Axes render outside zoom transform — always crisp */}\n <Axes\n xTicks={xTicks}\n yTicks={yTicks}\n xScale={xScale}\n yScale={yScale}\n plotWidth={plotWidth}\n plotHeight={plotHeight}\n xAxisConfig={xAxisConfig}\n yAxisConfig={yAxisConfig}\n style={chartStyle}\n />\n\n {/* Grid lines */}\n <GridLines\n xTicks={xTicks}\n yTicks={yTicks}\n xScale={xScale}\n yScale={yScale}\n plotWidth={plotWidth}\n plotHeight={plotHeight}\n showXGrid={showXGrid}\n showYGrid={showYGrid}\n xGridColor={xAxisConfig?.gridLineColor}\n yGridColor={yAxisConfig?.gridLineColor}\n />\n\n {/* Clipped + zoomed data area */}\n <g clipPath={`url(#${clipId})`}>\n <g\n transform={`translate(${zoomState.offsetX}, ${zoomState.offsetY}) scale(${zoomState.scale})`}\n onMouseMove={\n zoomConfig?.enabled ? zoomState.handlePanMove : undefined\n }\n >\n {data.map((series, i) => (\n <LinePath\n key={series.id}\n series={series}\n xScale={xScale}\n yScale={yScale}\n color={\n series.color ?? getSeriesColor(i, colorPalette)\n }\n animation={animation}\n onPointClick={onPointClick}\n />\n ))}\n </g>\n </g>\n\n {/* Tooltip renders above everything in plot area */}\n {tooltipEnabled && (\n <Tooltip\n visible={tooltipState.tooltipVisible}\n x={tooltipState.tooltipX}\n y={tooltipState.tooltipY}\n point={tooltipState.activePoint}\n series={tooltipState.activeSeries}\n plotHeight={plotHeight}\n plotWidth={plotWidth}\n config={tooltipConfig}\n seriesColor={\n tooltipState.activeSeries?.color ??\n getSeriesColor(\n data.findIndex(\n (s) => s.id === tooltipState.activeSeries?.id\n ),\n colorPalette\n )\n }\n />\n )}\n </g>\n\n {/* Zoom controls render at SVG root level */}\n <ZoomControls\n config={zoomConfig}\n svgWidth={svgWidth}\n svgHeight={svgHeight}\n margin={margin}\n scale={zoomState.scale}\n onZoomIn={zoomState.zoomIn}\n onZoomOut={zoomState.zoomOut}\n onReset={zoomState.resetZoom}\n />\n </svg>\n </div>\n );\n};\n\nLineChart.displayName = \"LineChart\";\n"],"names":["DEFAULT_PALETTE","getSeriesColor","index","palette","colors","length","useChartDimensions","containerRef","providedWidth","providedHeight","dimensions","setDimensions","useState","width","height","isFixed","undefined","useLayoutEffect","node","current","measure","rect","getBoundingClientRect","observer","ResizeObserver","observe","disconnect","clamp","value","min","max","Math","niceNumber","range","round","exponent","floor","log10","fraction","pow","niceFraction","niceRange","tickCount","offset","abs","tickStep","niceMin","niceMax","ceil","linearScale","domainMin","domainMax","rangeMin","rangeMax","t","a","b","inverseLerp","lerp","generateTicks","ticks","v","push","parseFloat","toPrecision","useScales","data","plotWidth","plotHeight","xAxisConfig","yAxisConfig","useMemo","xMin","Infinity","xMax","yMin","yMax","series","point","x","y","isFinite","xTickCount","yTickCount","xNice","yNice","xScale","yScale","xTicks","yTicks","xDomain","yDomain","useZoom","config","enabled","minScale","maxScale","step","enableWheel","enablePan","scale","setScale","offsetX","setOffsetX","offsetY","setOffsetY","isPanning","setIsPanning","panStart","useRef","clampOffset","useCallback","ox","oy","s","maxOffsetY","zoomIn","prev","next","zoomOut","resetZoom","handleWheel","e","preventDefault","currentTarget","mouseX","clientX","left","mouseY","clientY","top","delta","deltaY","ratio","handlePanStart","button","handlePanMove","dx","dy","clamped","handlePanEnd","findNearestByPixelX","sorted","low","high","mid","nearest","minDist","dist","useTooltip","svgRef","marginLeft","marginTop","_config","tooltipVisible","setTooltipVisible","tooltipX","setTooltipX","tooltipY","setTooltipY","activePoint","setActivePoint","activeSeries","setActiveSeries","handleMouseMove","svg","closestPoint","closestSeries","closestDist","handleMouseLeave","defaultTickFormat","toFixed","Number","isInteger","toString","Axes","React","memo","style","axisColor","tickColor","fontFamily","fontSize","xTickFormat","tickFormat","yTickFormat","_jsxs","className","_jsx","x1","y1","x2","y2","stroke","strokeWidth","map","tick","children","textAnchor","fill","label","fontWeight","dominantBaseline","transform","displayName","GridLines","showXGrid","showYGrid","xGridColor","yGridColor","strokeDasharray","strokeOpacity","LinePath","color","animation","onPointClick","pathRef","pathLength","setPathLength","animationDone","setAnimationDone","showDots","dotRadius","d","points","first","rest","p","join","buildLinePath","pt","useEffect","getTotalLength","timer","setTimeout","duration","clearTimeout","animStyle","strokeDashoffset","transition","easing","ref","strokeLinecap","strokeLinejoin","i","cx","cy","r","cursor","onClick","id","Tooltip","visible","seriesColor","bgColor","backgroundColor","textColor","content","renderCustom","formatter","name","tx","ty","pointerEvents","opacity","tooltipHeight","overflow","padding","borderRadius","whiteSpace","lineHeight","boxShadow","ZoomControls","svgWidth","svgHeight","margin","onZoomIn","onZoomOut","onReset","showControls","position","controlsPosition","btnSize","gx","gy","bottom","right","displayButtons","btn","rx","DEFAULT_MARGIN","LineChart","marginOverride","xAxis","yAxis","tooltip","tooltipConfig","zoom","zoomConfig","chartStyle","colorPalette","ariaLabel","clipId","useId","zoomState","tooltipEnabled","tooltipState","gridLines","role","onMouseMove","onMouseLeave","onWheel","onMouseDown","onMouseUp","userSelect","gridLineColor","clipPath","findIndex"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils/color.ts","../src/hooks/useChartDimensions.ts","../src/utils/math.ts","../src/hooks/useDataDomain.ts","../src/hooks/useScales.ts","../src/hooks/useZoom.ts","../src/hooks/useTooltip.ts","../src/components/Axes.tsx","../src/components/GridLines.tsx","../src/utils/path.ts","../src/components/LinePath.tsx","../src/components/Tooltip.tsx","../src/components/ZoomControls.tsx","../src/components/PeakMarkers.tsx","../src/components/ContextMenu.tsx","../src/utils/exportPdf.ts","../src/components/ExportButton.tsx","../src/components/LineChart.tsx"],"sourcesContent":["export const DEFAULT_PALETTE = [\n \"#4e79a7\",\n \"#f28e2b\",\n \"#e15759\",\n \"#76b7b2\",\n \"#59a14f\",\n \"#edc948\",\n \"#b07aa1\",\n \"#ff9da7\",\n \"#9c755f\",\n \"#bab0ac\",\n];\n\nexport function getSeriesColor(index: number, palette?: string[]): string {\n const colors = palette ?? DEFAULT_PALETTE;\n return colors[index % colors.length];\n}\n","import { useLayoutEffect, useState, type RefObject } from \"react\";\n\nexport function useChartDimensions(\n containerRef: RefObject<HTMLDivElement | null>,\n providedWidth?: number,\n providedHeight?: number\n): { width: number; height: number } {\n const [dimensions, setDimensions] = useState<{\n width: number;\n height: number;\n }>({\n width: providedWidth ?? 0,\n height: providedHeight ?? 0,\n });\n\n const isFixed =\n providedWidth !== undefined && providedHeight !== undefined;\n\n useLayoutEffect(() => {\n if (isFixed) {\n setDimensions({ width: providedWidth!, height: providedHeight! });\n return;\n }\n\n const node = containerRef.current;\n if (!node) return;\n\n const measure = () => {\n const rect = node.getBoundingClientRect();\n setDimensions({ width: rect.width, height: rect.height });\n };\n\n measure();\n\n const observer = new ResizeObserver(() => {\n measure();\n });\n observer.observe(node);\n\n return () => observer.disconnect();\n }, [containerRef, isFixed, providedWidth, providedHeight]);\n\n return dimensions;\n}\n","export function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nexport function lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nexport function inverseLerp(a: number, b: number, value: number): number {\n if (a === b) return 0;\n return (value - a) / (b - a);\n}\n\nexport function niceNumber(range: number, round: boolean): number {\n const exponent = Math.floor(Math.log10(range));\n const fraction = range / Math.pow(10, exponent);\n\n let niceFraction: number;\n if (round) {\n if (fraction < 1.5) niceFraction = 1;\n else if (fraction < 3) niceFraction = 2;\n else if (fraction < 7) niceFraction = 5;\n else niceFraction = 10;\n } else {\n if (fraction <= 1) niceFraction = 1;\n else if (fraction <= 2) niceFraction = 2;\n else if (fraction <= 5) niceFraction = 5;\n else niceFraction = 10;\n }\n\n return niceFraction * Math.pow(10, exponent);\n}\n\nexport function niceRange(\n min: number,\n max: number,\n tickCount: number\n): { niceMin: number; niceMax: number; tickStep: number } {\n if (min === max) {\n const offset = min === 0 ? 1 : Math.abs(min) * 0.1;\n min = min - offset;\n max = max + offset;\n }\n\n const range = niceNumber(max - min, false);\n const tickStep = niceNumber(range / (tickCount - 1), true);\n const niceMin = Math.floor(min / tickStep) * tickStep;\n const niceMax = Math.ceil(max / tickStep) * tickStep;\n\n return { niceMin, niceMax, tickStep };\n}\n\nexport function linearScale(\n domainMin: number,\n domainMax: number,\n rangeMin: number,\n rangeMax: number\n): (value: number) => number {\n return (value: number) => {\n if (domainMax === domainMin) return (rangeMin + rangeMax) / 2;\n const t = inverseLerp(domainMin, domainMax, value);\n return lerp(rangeMin, rangeMax, t);\n };\n}\n\nexport function generateTicks(\n niceMin: number,\n niceMax: number,\n tickStep: number\n): number[] {\n const ticks: number[] = [];\n for (let v = niceMin; v <= niceMax + tickStep * 0.5; v += tickStep) {\n ticks.push(parseFloat(v.toPrecision(12)));\n }\n return ticks;\n}\n\nexport function inverseLinearScale(\n domainMin: number,\n domainMax: number,\n rangeMin: number,\n rangeMax: number\n): (pixel: number) => number {\n return (pixel: number) => {\n if (rangeMax === rangeMin) return (domainMin + domainMax) / 2;\n const t = inverseLerp(rangeMin, rangeMax, pixel);\n return lerp(domainMin, domainMax, t);\n };\n}\n","import { useMemo } from \"react\";\nimport type { DataSeries, AxisConfig } from \"../types/chart.types\";\nimport { niceRange } from \"../utils/math\";\n\nexport interface DataDomain {\n fullXDomain: [number, number];\n fullYDomain: [number, number];\n}\n\nexport function useDataDomain(\n data: DataSeries[],\n xAxisConfig?: AxisConfig,\n yAxisConfig?: AxisConfig\n): DataDomain {\n return useMemo(() => {\n let xMin = Infinity;\n let xMax = -Infinity;\n let yMin = Infinity;\n let yMax = -Infinity;\n\n for (const series of data) {\n for (const point of series.data) {\n if (point.x < xMin) xMin = point.x;\n if (point.x > xMax) xMax = point.x;\n if (point.y < yMin) yMin = point.y;\n if (point.y > yMax) yMax = point.y;\n }\n }\n\n if (!isFinite(xMin)) {\n xMin = 0;\n xMax = 1;\n yMin = 0;\n yMax = 1;\n }\n\n if (xAxisConfig?.min !== undefined) xMin = xAxisConfig.min;\n if (xAxisConfig?.max !== undefined) xMax = xAxisConfig.max;\n if (yAxisConfig?.min !== undefined) yMin = yAxisConfig.min;\n if (yAxisConfig?.max !== undefined) yMax = yAxisConfig.max;\n\n const xTickCount = xAxisConfig?.tickCount ?? 6;\n const yTickCount = yAxisConfig?.tickCount ?? 6;\n\n const xNice = niceRange(xMin, xMax, xTickCount);\n const yNice = niceRange(yMin, yMax, yTickCount);\n\n return {\n fullXDomain: [xNice.niceMin, xNice.niceMax] as [number, number],\n fullYDomain: [yNice.niceMin, yNice.niceMax] as [number, number],\n };\n }, [data, xAxisConfig, yAxisConfig]);\n}\n","import { useMemo } from \"react\";\nimport type { DataSeries, AxisConfig } from \"../types/chart.types\";\nimport { niceRange, linearScale, generateTicks } from \"../utils/math\";\n\nexport interface ScalesResult {\n xScale: (value: number) => number;\n yScale: (value: number) => number;\n xTicks: number[];\n yTicks: number[];\n xDomain: [number, number];\n yDomain: [number, number];\n}\n\nexport function useScales(\n data: DataSeries[],\n plotWidth: number,\n plotHeight: number,\n xAxisConfig?: AxisConfig,\n yAxisConfig?: AxisConfig,\n viewXDomain?: [number, number],\n viewYDomain?: [number, number]\n): ScalesResult {\n return useMemo(() => {\n let xMin = Infinity;\n let xMax = -Infinity;\n let yMin = Infinity;\n let yMax = -Infinity;\n\n for (const series of data) {\n for (const point of series.data) {\n if (point.x < xMin) xMin = point.x;\n if (point.x > xMax) xMax = point.x;\n if (point.y < yMin) yMin = point.y;\n if (point.y > yMax) yMax = point.y;\n }\n }\n\n if (!isFinite(xMin)) {\n xMin = 0;\n xMax = 1;\n yMin = 0;\n yMax = 1;\n }\n\n if (xAxisConfig?.min !== undefined) xMin = xAxisConfig.min;\n if (xAxisConfig?.max !== undefined) xMax = xAxisConfig.max;\n if (yAxisConfig?.min !== undefined) yMin = yAxisConfig.min;\n if (yAxisConfig?.max !== undefined) yMax = yAxisConfig.max;\n\n const xTickCount = xAxisConfig?.tickCount ?? 6;\n const yTickCount = yAxisConfig?.tickCount ?? 6;\n\n // Use viewDomain if provided (semantic zoom), otherwise compute from data\n let xDomainMin: number, xDomainMax: number;\n let yDomainMin: number, yDomainMax: number;\n\n if (viewXDomain) {\n xDomainMin = viewXDomain[0];\n xDomainMax = viewXDomain[1];\n } else {\n const xNice = niceRange(xMin, xMax, xTickCount);\n xDomainMin = xNice.niceMin;\n xDomainMax = xNice.niceMax;\n }\n\n if (viewYDomain) {\n yDomainMin = viewYDomain[0];\n yDomainMax = viewYDomain[1];\n } else {\n const yNice = niceRange(yMin, yMax, yTickCount);\n yDomainMin = yNice.niceMin;\n yDomainMax = yNice.niceMax;\n }\n\n // Compute nice ticks for the current view range\n const xViewNice = niceRange(xDomainMin, xDomainMax, xTickCount);\n const yViewNice = niceRange(yDomainMin, yDomainMax, yTickCount);\n\n const xScale = linearScale(xDomainMin, xDomainMax, 0, plotWidth);\n // Y inverted: SVG Y increases downward\n const yScale = linearScale(yDomainMin, yDomainMax, plotHeight, 0);\n\n // Generate ticks, respecting integerTicks option\n let xTickStep = xViewNice.tickStep;\n let xTickMin = xViewNice.niceMin;\n let xTickMax = xViewNice.niceMax;\n if (xAxisConfig?.integerTicks) {\n xTickStep = Math.max(1, Math.ceil(xTickStep));\n xTickMin = Math.ceil(xDomainMin);\n xTickMax = Math.floor(xDomainMax);\n }\n\n let yTickStep = yViewNice.tickStep;\n let yTickMin = yViewNice.niceMin;\n let yTickMax = yViewNice.niceMax;\n if (yAxisConfig?.integerTicks) {\n yTickStep = Math.max(1, Math.ceil(yTickStep));\n yTickMin = Math.ceil(yDomainMin);\n yTickMax = Math.floor(yDomainMax);\n }\n\n const xTicks = generateTicks(xTickMin, xTickMax, xTickStep)\n .filter((t) => t >= xDomainMin && t <= xDomainMax)\n .filter((t) => !xAxisConfig?.integerTicks || Number.isInteger(t));\n const yTicks = generateTicks(yTickMin, yTickMax, yTickStep)\n .filter((t) => t >= yDomainMin && t <= yDomainMax)\n .filter((t) => !yAxisConfig?.integerTicks || Number.isInteger(t));\n\n return {\n xScale,\n yScale,\n xTicks,\n yTicks,\n xDomain: [xDomainMin, xDomainMax] as [number, number],\n yDomain: [yDomainMin, yDomainMax] as [number, number],\n };\n }, [data, plotWidth, plotHeight, xAxisConfig, yAxisConfig, viewXDomain, viewYDomain]);\n}\n","import { useState, useCallback, useRef } from \"react\";\nimport type { ZoomConfig } from \"../types/chart.types\";\nimport { clamp, lerp } from \"../utils/math\";\n\nexport interface ZoomState {\n viewXDomain: [number, number];\n viewYDomain: [number, number];\n scale: number;\n isPanning: boolean;\n showZoomHint: boolean;\n zoomIn: () => void;\n zoomOut: () => void;\n resetZoom: () => void;\n zoomToPoint: (dataX: number, dataY: number) => void;\n handleWheel: (e: React.WheelEvent) => void;\n handlePanStart: (e: React.MouseEvent) => void;\n handlePanMove: (e: React.MouseEvent) => void;\n handlePanEnd: () => void;\n}\n\nexport function useZoom(\n config: ZoomConfig | undefined,\n fullXDomain: [number, number],\n fullYDomain: [number, number],\n plotWidth: number,\n plotHeight: number\n): ZoomState {\n const enabled = config?.enabled ?? false;\n const minScale = config?.minScale ?? 1;\n const maxScale = config?.maxScale ?? 10;\n const step = config?.step ?? 0.5;\n const enableWheel = config?.enableWheel ?? true;\n const enablePan = config?.enablePan ?? true;\n const requireCtrlKey = config?.requireCtrlKey ?? true;\n\n const [viewXDomain, setViewXDomain] = useState<[number, number]>(fullXDomain);\n const [viewYDomain, setViewYDomain] = useState<[number, number]>(fullYDomain);\n const [isPanning, setIsPanning] = useState(false);\n const [showZoomHint, setShowZoomHint] = useState(false);\n\n const panStart = useRef({ x: 0, y: 0, xDomain: fullXDomain, yDomain: fullYDomain });\n const hintTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastFullDomain = useRef({ x: fullXDomain, y: fullYDomain });\n\n // Sync view domain when full domain changes (new data)\n if (\n fullXDomain[0] !== lastFullDomain.current.x[0] ||\n fullXDomain[1] !== lastFullDomain.current.x[1] ||\n fullYDomain[0] !== lastFullDomain.current.y[0] ||\n fullYDomain[1] !== lastFullDomain.current.y[1]\n ) {\n lastFullDomain.current = { x: fullXDomain, y: fullYDomain };\n setViewXDomain(fullXDomain);\n setViewYDomain(fullYDomain);\n }\n\n // Derived scale for UI (ratio of full range to view range)\n const fullXRange = fullXDomain[1] - fullXDomain[0];\n const viewXRange = viewXDomain[1] - viewXDomain[0];\n const scale = fullXRange > 0 && viewXRange > 0 ? fullXRange / viewXRange : 1;\n\n const clampDomain = useCallback(\n (\n domain: [number, number],\n fullDomain: [number, number]\n ): [number, number] => {\n const range = domain[1] - domain[0];\n const fullRange = fullDomain[1] - fullDomain[0];\n // Don't allow zooming out beyond full domain\n if (range >= fullRange) return fullDomain;\n // Clamp to bounds\n let min = domain[0];\n let max = domain[1];\n if (min < fullDomain[0]) {\n min = fullDomain[0];\n max = min + range;\n }\n if (max > fullDomain[1]) {\n max = fullDomain[1];\n min = max - range;\n }\n return [min, max];\n },\n []\n );\n\n const showHint = useCallback(() => {\n if (hintTimer.current) clearTimeout(hintTimer.current);\n setShowZoomHint(true);\n hintTimer.current = setTimeout(() => setShowZoomHint(false), 2000);\n }, []);\n\n const zoomByFactor = useCallback(\n (factor: number, centerXFrac?: number, centerYFrac?: number) => {\n if (!enabled) return;\n\n const cxFrac = centerXFrac ?? 0.5;\n const cyFrac = centerYFrac ?? 0.5;\n\n setViewXDomain((prev) => {\n const range = prev[1] - prev[0];\n const newRange = range / factor;\n const minRange = fullXRange / maxScale;\n const clampedRange = Math.max(newRange, minRange);\n const finalRange = Math.min(clampedRange, fullXRange);\n\n const center = lerp(prev[0], prev[1], cxFrac);\n const newMin = center - finalRange * cxFrac;\n const newMax = newMin + finalRange;\n return clampDomain([newMin, newMax], fullXDomain);\n });\n\n setViewYDomain((prev) => {\n const range = prev[1] - prev[0];\n const fullYRange = fullYDomain[1] - fullYDomain[0];\n const newRange = range / factor;\n const minRange = fullYRange / maxScale;\n const clampedRange = Math.max(newRange, minRange);\n const finalRange = Math.min(clampedRange, fullYRange);\n\n const center = lerp(prev[0], prev[1], cyFrac);\n const newMin = center - finalRange * cyFrac;\n const newMax = newMin + finalRange;\n return clampDomain([newMin, newMax], fullYDomain);\n });\n },\n [enabled, fullXDomain, fullYDomain, fullXRange, maxScale, clampDomain]\n );\n\n const zoomIn = useCallback(() => {\n const factor = 1 + step;\n zoomByFactor(factor);\n }, [step, zoomByFactor]);\n\n const zoomOut = useCallback(() => {\n const factor = 1 / (1 + step);\n zoomByFactor(factor);\n }, [step, zoomByFactor]);\n\n const resetZoom = useCallback(() => {\n setViewXDomain(fullXDomain);\n setViewYDomain(fullYDomain);\n }, [fullXDomain, fullYDomain]);\n\n const zoomToPoint = useCallback(\n (dataX: number, dataY: number) => {\n if (!enabled) return;\n // Zoom 2x centered at the given data point\n const factor = 2;\n\n setViewXDomain((prev) => {\n const range = prev[1] - prev[0];\n const newRange = range / factor;\n const minRange = fullXRange / maxScale;\n const clampedRange = Math.max(newRange, minRange);\n const finalRange = Math.min(clampedRange, fullXRange);\n\n const newMin = dataX - finalRange / 2;\n const newMax = newMin + finalRange;\n return clampDomain([newMin, newMax], fullXDomain);\n });\n\n setViewYDomain((prev) => {\n const range = prev[1] - prev[0];\n const fullYRange = fullYDomain[1] - fullYDomain[0];\n const newRange = range / factor;\n const minRange = fullYRange / maxScale;\n const clampedRange = Math.max(newRange, minRange);\n const finalRange = Math.min(clampedRange, fullYRange);\n\n const newMin = dataY - finalRange / 2;\n const newMax = newMin + finalRange;\n return clampDomain([newMin, newMax], fullYDomain);\n });\n },\n [enabled, fullXDomain, fullYDomain, fullXRange, maxScale, clampDomain]\n );\n\n const handleWheel = useCallback(\n (e: React.WheelEvent) => {\n if (!enabled || !enableWheel) return;\n\n // Ctrl+wheel guard\n if (requireCtrlKey && !e.ctrlKey && !e.metaKey) {\n showHint();\n return; // Let page scroll normally\n }\n\n e.preventDefault();\n\n const rect = (e.currentTarget as SVGElement).getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n\n // Compute mouse position as fraction of plot area\n // Account for margins by treating the full SVG rect\n const cxFrac = clamp(mouseX / (rect.width || 1), 0, 1);\n const cyFrac = clamp(mouseY / (rect.height || 1), 0, 1);\n\n const delta = e.deltaY < 0 ? 1 + step : 1 / (1 + step);\n zoomByFactor(delta, cxFrac, cyFrac);\n },\n [enabled, enableWheel, requireCtrlKey, step, showHint, zoomByFactor]\n );\n\n const handlePanStart = useCallback(\n (e: React.MouseEvent) => {\n if (!enabled || !enablePan || scale <= 1) return;\n if (e.button !== 0) return; // left-click only\n setIsPanning(true);\n panStart.current = {\n x: e.clientX,\n y: e.clientY,\n xDomain: viewXDomain,\n yDomain: viewYDomain,\n };\n },\n [enabled, enablePan, scale, viewXDomain, viewYDomain]\n );\n\n const handlePanMove = useCallback(\n (e: React.MouseEvent) => {\n if (!isPanning) return;\n\n const dx = e.clientX - panStart.current.x;\n const dy = e.clientY - panStart.current.y;\n\n // Convert pixel delta to domain delta\n const prevXDomain = panStart.current.xDomain;\n const prevYDomain = panStart.current.yDomain;\n const xRange = prevXDomain[1] - prevXDomain[0];\n const yRange = prevYDomain[1] - prevYDomain[0];\n\n const domainDx = -(dx / plotWidth) * xRange;\n const domainDy = (dy / plotHeight) * yRange; // Y inverted\n\n const newXDomain: [number, number] = [\n prevXDomain[0] + domainDx,\n prevXDomain[1] + domainDx,\n ];\n const newYDomain: [number, number] = [\n prevYDomain[0] + domainDy,\n prevYDomain[1] + domainDy,\n ];\n\n setViewXDomain(clampDomain(newXDomain, fullXDomain));\n setViewYDomain(clampDomain(newYDomain, fullYDomain));\n },\n [isPanning, plotWidth, plotHeight, fullXDomain, fullYDomain, clampDomain]\n );\n\n const handlePanEnd = useCallback(() => {\n setIsPanning(false);\n }, []);\n\n return {\n viewXDomain,\n viewYDomain,\n scale,\n isPanning,\n showZoomHint,\n zoomIn,\n zoomOut,\n resetZoom,\n zoomToPoint,\n handleWheel,\n handlePanStart,\n handlePanMove,\n handlePanEnd,\n };\n}\n","import { useState, useCallback, type RefObject } from \"react\";\nimport type { DataSeries, DataPoint, TooltipConfig } from \"../types/chart.types\";\n\nexport interface TooltipState {\n tooltipVisible: boolean;\n tooltipX: number;\n tooltipY: number;\n activePoint: DataPoint | null;\n activeSeries: DataSeries | null;\n activeSeriesId: string | null;\n handleMouseMove: (e: React.MouseEvent) => void;\n handleMouseLeave: () => void;\n}\n\nfunction findNearestByPixelX(\n sorted: DataPoint[],\n mouseX: number,\n xScale: (v: number) => number\n): DataPoint | null {\n if (sorted.length === 0) return null;\n\n let low = 0;\n let high = sorted.length - 1;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n if (xScale(sorted[mid].x) < mouseX) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n let nearest = sorted[low];\n let minDist = Math.abs(xScale(nearest.x) - mouseX);\n\n if (low > 0) {\n const dist = Math.abs(xScale(sorted[low - 1].x) - mouseX);\n if (dist < minDist) {\n minDist = dist;\n nearest = sorted[low - 1];\n }\n }\n\n return nearest;\n}\n\nexport function useTooltip(\n svgRef: RefObject<SVGSVGElement | null>,\n data: DataSeries[],\n xScale: (v: number) => number,\n yScale: (v: number) => number,\n marginLeft: number,\n marginTop: number,\n plotWidth: number,\n _config?: TooltipConfig\n): TooltipState {\n const [tooltipVisible, setTooltipVisible] = useState(false);\n const [tooltipX, setTooltipX] = useState(0);\n const [tooltipY, setTooltipY] = useState(0);\n const [activePoint, setActivePoint] = useState<DataPoint | null>(null);\n const [activeSeries, setActiveSeries] = useState<DataSeries | null>(null);\n const [activeSeriesId, setActiveSeriesId] = useState<string | null>(null);\n\n const handleMouseMove = useCallback(\n (e: React.MouseEvent) => {\n const svg = svgRef.current;\n if (!svg) return;\n\n const rect = svg.getBoundingClientRect();\n const mouseX = e.clientX - rect.left - marginLeft;\n const mouseY = e.clientY - rect.top - marginTop;\n\n if (mouseX < 0 || mouseX > plotWidth) {\n setTooltipVisible(false);\n setActiveSeriesId(null);\n return;\n }\n\n // Step 1: Find nearest X across all series\n let nearestX = 0;\n let nearestXDist = Infinity;\n for (const series of data) {\n const nearest = findNearestByPixelX(series.data, mouseX, xScale);\n if (!nearest) continue;\n const dist = Math.abs(xScale(nearest.x) - mouseX);\n if (dist < nearestXDist) {\n nearestXDist = dist;\n nearestX = nearest.x;\n }\n }\n\n // Step 2: At that X, find which series has Y closest to mouse Y (pixel)\n let closestPoint: DataPoint | null = null;\n let closestSeries: DataSeries | null = null;\n let closestYDist = Infinity;\n\n for (const series of data) {\n // Find point at or nearest to nearestX in this series\n let bestPt: DataPoint | null = null;\n let bestXDist = Infinity;\n for (const pt of series.data) {\n const xDist = Math.abs(pt.x - nearestX);\n if (xDist < bestXDist) {\n bestXDist = xDist;\n bestPt = pt;\n }\n }\n if (!bestPt) continue;\n\n const pyY = yScale(bestPt.y);\n const yDist = Math.abs(pyY - mouseY);\n\n if (yDist < closestYDist) {\n closestYDist = yDist;\n closestPoint = bestPt;\n closestSeries = series;\n }\n }\n\n if (closestPoint && closestSeries) {\n setTooltipX(xScale(closestPoint.x));\n setTooltipY(yScale(closestPoint.y));\n setActivePoint(closestPoint);\n setActiveSeries(closestSeries);\n setActiveSeriesId(closestSeries.id);\n setTooltipVisible(true);\n }\n },\n [svgRef, data, xScale, yScale, marginLeft, marginTop, plotWidth]\n );\n\n const handleMouseLeave = useCallback(() => {\n setTooltipVisible(false);\n setActivePoint(null);\n setActiveSeries(null);\n setActiveSeriesId(null);\n }, []);\n\n return {\n tooltipVisible,\n tooltipX,\n tooltipY,\n activePoint,\n activeSeries,\n activeSeriesId,\n handleMouseMove,\n handleMouseLeave,\n };\n}\n","import React from \"react\";\nimport type { AxisConfig, ChartStyle } from \"../types/chart.types\";\n\ninterface AxesProps {\n xTicks: number[];\n yTicks: number[];\n xScale: (v: number) => number;\n yScale: (v: number) => number;\n plotWidth: number;\n plotHeight: number;\n xAxisConfig?: AxisConfig;\n yAxisConfig?: AxisConfig;\n style?: ChartStyle;\n}\n\nconst defaultTickFormat = (v: number): string => {\n if (Math.abs(v) >= 1e6) return `${(v / 1e6).toFixed(1)}M`;\n if (Math.abs(v) >= 1e3) return `${(v / 1e3).toFixed(1)}K`;\n return Number.isInteger(v) ? v.toString() : v.toFixed(1);\n};\n\nexport const Axes: React.FC<AxesProps> = React.memo(\n ({\n xTicks,\n yTicks,\n xScale,\n yScale,\n plotWidth,\n plotHeight,\n xAxisConfig,\n yAxisConfig,\n style,\n }) => {\n const axisColor = style?.axisColor ?? \"#333\";\n const tickColor = style?.tickColor ?? \"#666\";\n const fontFamily = style?.fontFamily ?? \"sans-serif\";\n const fontSize = style?.fontSize ?? 11;\n const xTickFormat = xAxisConfig?.tickFormat ?? defaultTickFormat;\n const yTickFormat = yAxisConfig?.tickFormat ?? defaultTickFormat;\n\n return (\n <g className=\"chartifypdf-axes\">\n {/* X Axis */}\n <line\n x1={0}\n y1={plotHeight}\n x2={plotWidth}\n y2={plotHeight}\n stroke={axisColor}\n strokeWidth={1}\n />\n {xTicks.map((tick) => {\n const x = xScale(tick);\n return (\n <g key={`x-tick-${tick}`}>\n <line\n x1={x}\n y1={plotHeight}\n x2={x}\n y2={plotHeight + 6}\n stroke={axisColor}\n strokeWidth={1}\n />\n <text\n x={x}\n y={plotHeight + 18}\n textAnchor=\"middle\"\n fill={tickColor}\n fontFamily={fontFamily}\n fontSize={fontSize}\n >\n {xTickFormat(tick)}\n </text>\n </g>\n );\n })}\n {xAxisConfig?.label && (\n <text\n x={plotWidth / 2}\n y={plotHeight + 38}\n textAnchor=\"middle\"\n fill={axisColor}\n fontFamily={fontFamily}\n fontSize={fontSize + 1}\n fontWeight=\"bold\"\n >\n {xAxisConfig.label}\n </text>\n )}\n\n {/* Y Axis */}\n <line\n x1={0}\n y1={0}\n x2={0}\n y2={plotHeight}\n stroke={axisColor}\n strokeWidth={1}\n />\n {yTicks.map((tick) => {\n const y = yScale(tick);\n return (\n <g key={`y-tick-${tick}`}>\n <line\n x1={-6}\n y1={y}\n x2={0}\n y2={y}\n stroke={axisColor}\n strokeWidth={1}\n />\n <text\n x={-10}\n y={y}\n textAnchor=\"end\"\n dominantBaseline=\"middle\"\n fill={tickColor}\n fontFamily={fontFamily}\n fontSize={fontSize}\n >\n {yTickFormat(tick)}\n </text>\n </g>\n );\n })}\n {yAxisConfig?.label && (\n <text\n x={0}\n y={0}\n textAnchor=\"middle\"\n fill={axisColor}\n fontFamily={fontFamily}\n fontSize={fontSize + 1}\n fontWeight=\"bold\"\n transform={`translate(${-40}, ${plotHeight / 2}) rotate(-90)`}\n >\n {yAxisConfig.label}\n </text>\n )}\n </g>\n );\n }\n);\n\nAxes.displayName = \"Axes\";\n","import React from \"react\";\n\ninterface GridLinesProps {\n xTicks: number[];\n yTicks: number[];\n xScale: (v: number) => number;\n yScale: (v: number) => number;\n plotWidth: number;\n plotHeight: number;\n showXGrid: boolean;\n showYGrid: boolean;\n xGridColor?: string;\n yGridColor?: string;\n}\n\nexport const GridLines: React.FC<GridLinesProps> = React.memo(\n ({\n xTicks,\n yTicks,\n xScale,\n yScale,\n plotWidth,\n plotHeight,\n showXGrid,\n showYGrid,\n xGridColor = \"#e0e0e0\",\n yGridColor = \"#e0e0e0\",\n }) => {\n return (\n <g className=\"chartifypdf-grid\">\n {showYGrid &&\n yTicks.map((tick) => {\n const y = yScale(tick);\n return (\n <line\n key={`y-grid-${tick}`}\n x1={0}\n y1={y}\n x2={plotWidth}\n y2={y}\n stroke={yGridColor}\n strokeDasharray=\"4 4\"\n strokeOpacity={0.5}\n />\n );\n })}\n {showXGrid &&\n xTicks.map((tick) => {\n const x = xScale(tick);\n return (\n <line\n key={`x-grid-${tick}`}\n x1={x}\n y1={0}\n x2={x}\n y2={plotHeight}\n stroke={xGridColor}\n strokeDasharray=\"4 4\"\n strokeOpacity={0.5}\n />\n );\n })}\n </g>\n );\n }\n);\n\nGridLines.displayName = \"GridLines\";\n","import type { CurveType } from \"../types/chart.types\";\n\nexport function buildLinePath(\n points: { x: number; y: number }[]\n): string {\n if (points.length === 0) return \"\";\n const [first, ...rest] = points;\n return `M${first.x},${first.y}` + rest.map((p) => `L${p.x},${p.y}`).join(\"\");\n}\n\n/**\n * Fritsch-Carlson monotone cubic interpolation.\n * Guarantees no overshoot — ideal for data visualization.\n */\nfunction buildMonotonePath(points: { x: number; y: number }[]): string {\n const n = points.length;\n if (n < 2) return buildLinePath(points);\n if (n === 2) return buildLinePath(points);\n\n // Compute slopes of secant lines\n const dx: number[] = [];\n const dy: number[] = [];\n const m: number[] = []; // slopes of secants\n for (let i = 0; i < n - 1; i++) {\n dx.push(points[i + 1].x - points[i].x);\n dy.push(points[i + 1].y - points[i].y);\n m.push(dx[i] === 0 ? 0 : dy[i] / dx[i]);\n }\n\n // Compute tangent slopes using Fritsch-Carlson method\n const tangents: number[] = new Array(n);\n tangents[0] = m[0];\n tangents[n - 1] = m[n - 2];\n\n for (let i = 1; i < n - 1; i++) {\n if (m[i - 1] * m[i] <= 0) {\n // Local extremum or flat — zero tangent\n tangents[i] = 0;\n } else {\n // Harmonic mean of adjacent slopes\n tangents[i] = 2 / (1 / m[i - 1] + 1 / m[i]);\n }\n }\n\n // Clamp tangents to prevent overshoot (Fritsch-Carlson)\n for (let i = 0; i < n - 1; i++) {\n if (m[i] === 0) {\n tangents[i] = 0;\n tangents[i + 1] = 0;\n } else {\n const alpha = tangents[i] / m[i];\n const beta = tangents[i + 1] / m[i];\n // Ensure within monotone bounds\n const s = alpha * alpha + beta * beta;\n if (s > 9) {\n const tau = 3 / Math.sqrt(s);\n tangents[i] = tau * alpha * m[i];\n tangents[i + 1] = tau * beta * m[i];\n }\n }\n }\n\n // Build SVG path with cubic bezier segments\n let d = `M${points[0].x},${points[0].y}`;\n for (let i = 0; i < n - 1; i++) {\n const segLen = dx[i] / 3;\n const cp1x = points[i].x + segLen;\n const cp1y = points[i].y + tangents[i] * segLen;\n const cp2x = points[i + 1].x - segLen;\n const cp2y = points[i + 1].y - tangents[i + 1] * segLen;\n d += `C${cp1x},${cp1y},${cp2x},${cp2y},${points[i + 1].x},${points[i + 1].y}`;\n }\n\n return d;\n}\n\n/**\n * Natural cubic spline interpolation.\n * Smoother than monotone but may overshoot.\n */\nfunction buildNaturalPath(points: { x: number; y: number }[]): string {\n const n = points.length;\n if (n < 2) return buildLinePath(points);\n if (n === 2) return buildLinePath(points);\n\n // Solve tridiagonal system for natural cubic spline\n // For each coordinate axis independently\n function solveSpline(vals: number[]): { cp1: number; cp2: number }[] {\n const len = vals.length - 1;\n\n // Thomas algorithm for tridiagonal system\n const a: number[] = new Array(len);\n const b: number[] = new Array(len);\n const r: number[] = new Array(len);\n\n // Natural spline boundary: second derivative = 0 at endpoints\n a[0] = 0;\n b[0] = 2;\n r[0] = vals[0] + 2 * vals[1];\n\n for (let i = 1; i < len - 1; i++) {\n a[i] = 1;\n b[i] = 4;\n r[i] = 4 * vals[i] + 2 * vals[i + 1];\n }\n\n a[len - 1] = 2;\n b[len - 1] = 7;\n r[len - 1] = 8 * vals[len - 1] + vals[len];\n\n // Forward sweep\n for (let i = 1; i < len; i++) {\n const m = a[i] / b[i - 1];\n b[i] -= m;\n r[i] -= m * r[i - 1];\n }\n\n // Back substitution — first control points\n const cp1: number[] = new Array(len);\n cp1[len - 1] = r[len - 1] / b[len - 1];\n for (let i = len - 2; i >= 0; i--) {\n cp1[i] = (r[i] - cp1[i + 1]) / b[i];\n }\n\n // Second control points\n const cp2: number[] = new Array(len);\n for (let i = 0; i < len - 1; i++) {\n cp2[i] = 2 * vals[i + 1] - cp1[i + 1];\n }\n cp2[len - 1] = (vals[len] + cp1[len - 1]) / 2;\n\n const result: { cp1: number; cp2: number }[] = [];\n for (let i = 0; i < len; i++) {\n result.push({ cp1: cp1[i], cp2: cp2[i] });\n }\n return result;\n }\n\n const xs = points.map((p) => p.x);\n const ys = points.map((p) => p.y);\n\n const xCPs = solveSpline(xs);\n const yCPs = solveSpline(ys);\n\n let d = `M${points[0].x},${points[0].y}`;\n for (let i = 0; i < n - 1; i++) {\n d += `C${xCPs[i].cp1},${yCPs[i].cp1},${xCPs[i].cp2},${yCPs[i].cp2},${points[i + 1].x},${points[i + 1].y}`;\n }\n\n return d;\n}\n\nexport function buildSmoothPath(\n points: { x: number; y: number }[],\n curveType: CurveType\n): string {\n switch (curveType) {\n case \"monotone\":\n return buildMonotonePath(points);\n case \"natural\":\n return buildNaturalPath(points);\n case \"linear\":\n default:\n return buildLinePath(points);\n }\n}\n","import React, { useRef, useEffect, useState } from \"react\";\nimport type { DataSeries, DataPoint, AnimationConfig, CurveType } from \"../types/chart.types\";\nimport { buildLinePath, buildSmoothPath } from \"../utils/path\";\n\ninterface LinePathProps {\n series: DataSeries;\n xScale: (v: number) => number;\n yScale: (v: number) => number;\n color: string;\n animation?: AnimationConfig;\n onPointClick?: (point: DataPoint, series: DataSeries) => void;\n curveType?: CurveType;\n highlightOpacity?: number;\n}\n\nexport const LinePath: React.FC<LinePathProps> = React.memo(\n ({ series, xScale, yScale, color, animation, onPointClick, curveType, highlightOpacity }) => {\n const pathRef = useRef<SVGPathElement>(null);\n const [pathLength, setPathLength] = useState(0);\n const [animationDone, setAnimationDone] = useState(!animation?.enabled);\n\n const strokeWidth = series.strokeWidth ?? 2;\n const showDots = series.showDots ?? false;\n const dotRadius = series.dotRadius ?? 3.5;\n const effectiveCurve = series.curveType ?? curveType ?? \"linear\";\n const opacity = highlightOpacity ?? 1;\n\n const points = series.data.map((pt) => ({\n x: xScale(pt.x),\n y: yScale(pt.y),\n }));\n\n const d =\n effectiveCurve === \"linear\"\n ? buildLinePath(points)\n : buildSmoothPath(points, effectiveCurve);\n\n useEffect(() => {\n if (animation?.enabled && pathRef.current) {\n const length = pathRef.current.getTotalLength();\n setPathLength(length);\n setAnimationDone(false);\n\n const timer = setTimeout(() => {\n setAnimationDone(true);\n }, animation.duration ?? 800);\n\n return () => clearTimeout(timer);\n }\n }, [d, animation?.enabled, animation?.duration]);\n\n const animStyle: React.CSSProperties =\n animation?.enabled && pathLength > 0\n ? {\n strokeDasharray: pathLength,\n strokeDashoffset: animationDone ? 0 : pathLength,\n transition: `stroke-dashoffset ${animation.duration ?? 800}ms ${animation.easing ?? \"ease-in-out\"}`,\n }\n : {};\n\n return (\n <g\n className=\"chartifypdf-line-path\"\n style={{\n opacity,\n transition: \"opacity 150ms ease\",\n }}\n >\n <path\n ref={pathRef}\n d={d}\n fill=\"none\"\n stroke={color}\n strokeWidth={strokeWidth}\n strokeDasharray={series.strokeDasharray}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={animStyle}\n />\n {showDots &&\n series.data.map((pt, i) => (\n <circle\n key={`dot-${series.id}-${i}`}\n cx={xScale(pt.x)}\n cy={yScale(pt.y)}\n r={dotRadius}\n fill={color}\n stroke=\"#fff\"\n strokeWidth={1.5}\n style={{ cursor: onPointClick ? \"pointer\" : \"default\" }}\n onClick={\n onPointClick\n ? () => onPointClick(pt, series)\n : undefined\n }\n />\n ))}\n </g>\n );\n }\n);\n\nLinePath.displayName = \"LinePath\";\n","import React from \"react\";\nimport type { DataPoint, DataSeries, TooltipConfig } from \"../types/chart.types\";\n\ninterface TooltipProps {\n visible: boolean;\n x: number;\n y: number;\n point: DataPoint | null;\n series: DataSeries | null;\n plotHeight: number;\n plotWidth: number;\n config?: TooltipConfig;\n seriesColor: string;\n}\n\nexport const Tooltip: React.FC<TooltipProps> = React.memo(\n ({ visible, x, y, point, series, plotHeight, plotWidth, config, seriesColor }) => {\n if (!visible || !point || !series) return null;\n\n const bgColor = config?.backgroundColor ?? \"rgba(0, 0, 0, 0.8)\";\n const textColor = config?.textColor ?? \"#fff\";\n\n // Determine tooltip text\n let content: React.ReactNode;\n if (config?.renderCustom) {\n content = config.renderCustom(point, series);\n } else {\n const text = config?.formatter\n ? config.formatter(point, series)\n : `${series.name}: (${point.x}, ${point.y})`;\n content = text;\n }\n\n // Smart positioning: flip when near edges\n const tooltipWidth = 140;\n const tooltipHeight = 40;\n let tx = x + 12;\n let ty = y - tooltipHeight - 8;\n\n if (tx + tooltipWidth > plotWidth) {\n tx = x - tooltipWidth - 12;\n }\n if (ty < 0) {\n ty = y + 12;\n }\n\n return (\n <g className=\"chartifypdf-tooltip\" pointerEvents=\"none\">\n {/* Vertical indicator line */}\n <line\n x1={x}\n y1={0}\n x2={x}\n y2={plotHeight}\n stroke=\"#999\"\n strokeWidth={1}\n strokeDasharray=\"4 4\"\n opacity={0.6}\n />\n {/* Highlight circle */}\n <circle\n cx={x}\n cy={y}\n r={5}\n fill={seriesColor}\n stroke=\"#fff\"\n strokeWidth={2}\n />\n {/* Tooltip box using foreignObject */}\n <foreignObject\n x={tx}\n y={ty}\n width={tooltipWidth}\n height={tooltipHeight + 20}\n overflow=\"visible\"\n >\n <div\n style={{\n backgroundColor: bgColor,\n color: textColor,\n padding: \"6px 10px\",\n borderRadius: \"4px\",\n fontSize: \"12px\",\n fontFamily: \"sans-serif\",\n whiteSpace: \"nowrap\",\n lineHeight: 1.4,\n boxShadow: \"0 2px 6px rgba(0,0,0,0.2)\",\n }}\n >\n {content}\n </div>\n </foreignObject>\n </g>\n );\n }\n);\n\nTooltip.displayName = \"Tooltip\";\n","import React from \"react\";\nimport type { ZoomConfig } from \"../types/chart.types\";\n\ninterface ZoomControlsProps {\n config?: ZoomConfig;\n svgWidth: number;\n svgHeight: number;\n margin: { top: number; right: number; bottom: number; left: number };\n scale: number;\n onZoomIn: () => void;\n onZoomOut: () => void;\n onReset: () => void;\n}\n\nexport const ZoomControls: React.FC<ZoomControlsProps> = React.memo(\n ({ config, svgWidth, svgHeight, margin, scale, onZoomIn, onZoomOut, onReset }) => {\n if (!config?.enabled || config.showControls === false) return null;\n\n const position = config.controlsPosition ?? \"top-right\";\n const btnSize = 24;\n const gap = 4;\n const groupWidth = btnSize * 3 + gap * 2;\n const groupHeight = btnSize;\n const padding = 8;\n\n let gx: number;\n let gy: number;\n\n switch (position) {\n case \"top-left\":\n gx = margin.left + padding;\n gy = margin.top + padding;\n break;\n case \"bottom-left\":\n gx = margin.left + padding;\n gy = svgHeight - margin.bottom - groupHeight - padding;\n break;\n case \"bottom-right\":\n gx = svgWidth - margin.right - groupWidth - padding;\n gy = svgHeight - margin.bottom - groupHeight - padding;\n break;\n case \"top-right\":\n default:\n gx = svgWidth - margin.right - groupWidth - padding;\n gy = margin.top + padding;\n break;\n }\n\n const buttons = [\n { label: \"+\", onClick: onZoomIn },\n { label: \"\\u2013\", onClick: onReset }, // en-dash for reset\n { label: \"\\u2212\", onClick: onZoomOut }, // minus sign\n ];\n\n // Replace middle button with reset when zoomed, or show as \"-\"\n const displayButtons =\n scale > 1\n ? [\n { label: \"+\", onClick: onZoomIn },\n { label: \"\\u2212\", onClick: onZoomOut },\n { label: \"\\u21BA\", onClick: onReset },\n ]\n : [\n { label: \"+\", onClick: onZoomIn },\n { label: \"\\u2212\", onClick: onZoomOut },\n ];\n\n return (\n <g\n className=\"chartifypdf-zoom-controls\"\n transform={`translate(${gx}, ${gy})`}\n >\n {displayButtons.map((btn, i) => (\n <g\n key={btn.label}\n transform={`translate(${i * (btnSize + gap)}, 0)`}\n onClick={btn.onClick}\n style={{ cursor: \"pointer\" }}\n >\n <rect\n width={btnSize}\n height={btnSize}\n rx={4}\n fill=\"rgba(255,255,255,0.9)\"\n stroke=\"#ccc\"\n strokeWidth={1}\n />\n <text\n x={btnSize / 2}\n y={btnSize / 2}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={14}\n fontFamily=\"sans-serif\"\n fill=\"#333\"\n fontWeight=\"bold\"\n >\n {btn.label}\n </text>\n </g>\n ))}\n </g>\n );\n }\n);\n\nZoomControls.displayName = \"ZoomControls\";\n","import React from \"react\";\nimport type { PeakConfig, DataSeries } from \"../types/chart.types\";\n\ninterface PeakMarkersProps {\n peaks: PeakConfig[];\n data: DataSeries[];\n xScale: (v: number) => number;\n yScale: (v: number) => number;\n colorPalette?: string[];\n}\n\nfunction findYAtX(data: DataSeries[], targetX: number): { y: number; seriesIndex: number } | null {\n let maxY = -Infinity;\n let bestSeriesIdx = -1;\n\n for (let si = 0; si < data.length; si++) {\n const series = data[si];\n // Find closest data point to target X\n let closest = series.data[0];\n let closestDist = Infinity;\n for (const pt of series.data) {\n const dist = Math.abs(pt.x - targetX);\n if (dist < closestDist) {\n closestDist = dist;\n closest = pt;\n }\n }\n if (closest && closest.y > maxY) {\n maxY = closest.y;\n bestSeriesIdx = si;\n }\n }\n\n if (bestSeriesIdx === -1) return null;\n return { y: maxY, seriesIndex: bestSeriesIdx };\n}\n\nfunction ArrowIcon({ x, y, color }: { x: number; y: number; color: string }) {\n return (\n <polygon\n points={`${x},${y} ${x - 5},${y - 10} ${x + 5},${y - 10}`}\n fill={color}\n />\n );\n}\n\nfunction DiamondIcon({ x, y, color }: { x: number; y: number; color: string }) {\n return (\n <polygon\n points={`${x},${y - 6} ${x + 5},${y} ${x},${y + 6} ${x - 5},${y}`}\n fill={color}\n />\n );\n}\n\nfunction StarIcon({ x, y, color }: { x: number; y: number; color: string }) {\n // 5-point star\n const r1 = 6;\n const r2 = 3;\n const pts: string[] = [];\n for (let i = 0; i < 5; i++) {\n const outerAngle = (i * 72 - 90) * (Math.PI / 180);\n const innerAngle = ((i * 72 + 36) - 90) * (Math.PI / 180);\n pts.push(`${x + r1 * Math.cos(outerAngle)},${y + r1 * Math.sin(outerAngle)}`);\n pts.push(`${x + r2 * Math.cos(innerAngle)},${y + r2 * Math.sin(innerAngle)}`);\n }\n return <polygon points={pts.join(\" \")} fill={color} />;\n}\n\nexport const PeakMarkers: React.FC<PeakMarkersProps> = React.memo(\n ({ peaks, data, xScale, yScale }) => {\n return (\n <g className=\"chartifypdf-peak-markers\">\n {peaks.map((peak, i) => {\n const result = findYAtX(data, peak.x);\n if (!result) return null;\n\n const px = xScale(peak.x);\n const py = yScale(result.y);\n const color = peak.color ?? \"#ef4444\";\n const icon = peak.icon ?? \"arrow\";\n const label = peak.label;\n\n // Position icon above the data point\n const iconY = py - 14;\n\n return (\n <g key={`peak-${i}`}>\n {/* Vertical dashed line from data point up */}\n <line\n x1={px}\n y1={py}\n x2={px}\n y2={iconY + 6}\n stroke={color}\n strokeWidth={1}\n strokeDasharray=\"3 2\"\n opacity={0.6}\n />\n {/* Icon */}\n {icon === \"arrow\" && <ArrowIcon x={px} y={iconY} color={color} />}\n {icon === \"diamond\" && <DiamondIcon x={px} y={iconY} color={color} />}\n {icon === \"star\" && <StarIcon x={px} y={iconY} color={color} />}\n {/* Label */}\n {label && (\n <text\n x={px}\n y={iconY - 8}\n textAnchor=\"middle\"\n fill={color}\n fontSize={11}\n fontWeight={600}\n >\n {label}\n </text>\n )}\n </g>\n );\n })}\n </g>\n );\n }\n);\n\nPeakMarkers.displayName = \"PeakMarkers\";\n","import React, { useEffect, useRef } from \"react\";\nimport type { DataSeries, ContextMenuConfig } from \"../types/chart.types\";\n\ninterface ContextMenuProps {\n visible: boolean;\n x: number;\n y: number;\n nearestX: number;\n data: DataSeries[];\n config?: ContextMenuConfig;\n colorPalette?: string[];\n getSeriesColor: (index: number) => string;\n onClose: () => void;\n}\n\nfunction findNearestY(series: DataSeries, targetX: number): number | null {\n if (series.data.length === 0) return null;\n let closest = series.data[0];\n let closestDist = Math.abs(closest.x - targetX);\n for (const pt of series.data) {\n const dist = Math.abs(pt.x - targetX);\n if (dist < closestDist) {\n closestDist = dist;\n closest = pt;\n }\n }\n return closest.y;\n}\n\nexport const ContextMenu: React.FC<ContextMenuProps> = ({\n visible,\n x,\n y,\n nearestX,\n data,\n config,\n getSeriesColor,\n onClose,\n}) => {\n const menuRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!visible) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n onClose();\n }\n };\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n\n // Delay adding listeners to avoid immediate close from the contextmenu event\n const timer = setTimeout(() => {\n document.addEventListener(\"mousedown\", handleClickOutside);\n document.addEventListener(\"keydown\", handleKeyDown);\n }, 0);\n\n return () => {\n clearTimeout(timer);\n document.removeEventListener(\"mousedown\", handleClickOutside);\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [visible, onClose]);\n\n if (!visible) return null;\n\n const bgColor = config?.backgroundColor ?? \"#ffffff\";\n const textColor = config?.textColor ?? \"#1f2937\";\n const borderColor = config?.borderColor ?? \"#e5e7eb\";\n\n const rows = data.map((series, i) => {\n const yVal = findNearestY(series, nearestX);\n return {\n name: series.name,\n color: series.color ?? getSeriesColor(i),\n x: nearestX,\n y: yVal,\n };\n });\n\n return (\n <div\n ref={menuRef}\n style={{\n position: \"fixed\",\n left: x,\n top: y,\n zIndex: 9999,\n backgroundColor: bgColor,\n color: textColor,\n border: `1px solid ${borderColor}`,\n borderRadius: 6,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.15)\",\n padding: \"8px 0\",\n minWidth: 200,\n fontSize: 12,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n }}\n >\n <div\n style={{\n padding: \"4px 12px 8px\",\n fontWeight: 600,\n fontSize: 11,\n color: textColor,\n opacity: 0.6,\n borderBottom: `1px solid ${borderColor}`,\n marginBottom: 4,\n }}\n >\n Data at X = {typeof nearestX === \"number\" ? nearestX.toLocaleString() : nearestX}\n </div>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\" }}>\n <thead>\n <tr>\n <th style={{ textAlign: \"left\", padding: \"4px 12px\", fontWeight: 600, opacity: 0.7 }}>\n Series\n </th>\n <th style={{ textAlign: \"right\", padding: \"4px 12px\", fontWeight: 600, opacity: 0.7 }}>\n Y\n </th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row, i) => (\n <tr\n key={i}\n style={{\n borderTop: i > 0 ? `1px solid ${borderColor}` : undefined,\n }}\n >\n <td style={{ padding: \"4px 12px\", display: \"flex\", alignItems: \"center\", gap: 6 }}>\n <span\n style={{\n display: \"inline-block\",\n width: 8,\n height: 8,\n borderRadius: \"50%\",\n backgroundColor: row.color,\n flexShrink: 0,\n }}\n />\n {row.name}\n </td>\n <td style={{ padding: \"4px 12px\", textAlign: \"right\", fontVariantNumeric: \"tabular-nums\" }}>\n {row.y !== null ? row.y.toLocaleString() : \"—\"}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n};\n\nContextMenu.displayName = \"ContextMenu\";\n","export type ExportFormat = \"pdf\" | \"png\" | \"svg\";\n\nfunction cloneSvgWithStyles(svgElement: SVGSVGElement): SVGSVGElement {\n const clone = svgElement.cloneNode(true) as SVGSVGElement;\n\n // Ensure the clone has explicit dimensions\n const rect = svgElement.getBoundingClientRect();\n clone.setAttribute(\"width\", String(rect.width));\n clone.setAttribute(\"height\", String(rect.height));\n clone.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n clone.setAttribute(\"xmlns:xlink\", \"http://www.w3.org/1999/xlink\");\n\n // Inline computed styles for reliable rendering\n const allOriginal = svgElement.querySelectorAll(\"*\");\n const allClone = clone.querySelectorAll(\"*\");\n for (let i = 0; i < allOriginal.length; i++) {\n const orig = allOriginal[i] as Element;\n const cloned = allClone[i] as SVGElement | HTMLElement;\n if (!cloned.style) continue;\n\n const computed = window.getComputedStyle(orig);\n const importantProps = [\n \"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\",\n \"opacity\", \"font-family\", \"font-size\", \"font-weight\",\n \"text-anchor\", \"dominant-baseline\", \"visibility\", \"display\",\n ];\n for (const prop of importantProps) {\n const val = computed.getPropertyValue(prop);\n if (val) {\n cloned.style.setProperty(prop, val);\n }\n }\n }\n\n return clone;\n}\n\nfunction svgToDataUrl(svgElement: SVGSVGElement): string {\n const clone = cloneSvgWithStyles(svgElement);\n const serializer = new XMLSerializer();\n const svgString = serializer.serializeToString(clone);\n const blob = new Blob([svgString], { type: \"image/svg+xml;charset=utf-8\" });\n return URL.createObjectURL(blob);\n}\n\nfunction downloadBlob(blob: Blob, fileName: string) {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = fileName;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\nasync function svgToCanvas(\n svgElement: SVGSVGElement,\n scale: number = 2\n): Promise<HTMLCanvasElement> {\n const rect = svgElement.getBoundingClientRect();\n const width = rect.width * scale;\n const height = rect.height * scale;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Cannot get canvas 2d context\");\n\n ctx.scale(scale, scale);\n\n const url = svgToDataUrl(svgElement);\n\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => {\n // Draw white background\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(0, 0, rect.width, rect.height);\n ctx.drawImage(img, 0, 0, rect.width, rect.height);\n URL.revokeObjectURL(url);\n resolve(canvas);\n };\n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error(\"Failed to load SVG as image\"));\n };\n img.src = url;\n });\n}\n\nexport async function exportChartAsSvg(\n svgElement: SVGSVGElement,\n fileName: string = \"chart\"\n): Promise<void> {\n const clone = cloneSvgWithStyles(svgElement);\n const serializer = new XMLSerializer();\n const svgString = serializer.serializeToString(clone);\n const blob = new Blob([svgString], { type: \"image/svg+xml;charset=utf-8\" });\n downloadBlob(blob, `${fileName}.svg`);\n}\n\nexport async function exportChartAsPng(\n svgElement: SVGSVGElement,\n fileName: string = \"chart\"\n): Promise<void> {\n const canvas = await svgToCanvas(svgElement);\n const blob = await new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n (b) => (b ? resolve(b) : reject(new Error(\"Canvas toBlob failed\"))),\n \"image/png\"\n );\n });\n downloadBlob(blob, `${fileName}.png`);\n}\n\nexport async function exportChartAsPdf(\n svgElement: SVGSVGElement,\n fileName: string = \"chart\"\n): Promise<void> {\n const canvas = await svgToCanvas(svgElement);\n const dataUrl = canvas.toDataURL(\"image/png\");\n\n const rect = svgElement.getBoundingClientRect();\n\n // Open a new window with the chart image and trigger print (Save as PDF)\n const printWindow = window.open(\"\", \"_blank\");\n if (!printWindow) {\n throw new Error(\"Popup blocked. Please allow popups to export PDF.\");\n }\n\n printWindow.document.write(`\n <!DOCTYPE html>\n <html>\n <head>\n <title>${fileName}</title>\n <style>\n @page { size: landscape; margin: 10mm; }\n body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; }\n img { max-width: 100%; height: auto; }\n </style>\n </head>\n <body>\n <img src=\"${dataUrl}\" width=\"${rect.width}\" height=\"${rect.height}\" />\n <script>\n window.onload = function() {\n setTimeout(function() { window.print(); window.close(); }, 300);\n };\n </script>\n </body>\n </html>\n `);\n printWindow.document.close();\n}\n\nexport async function exportChart(\n svgElement: SVGSVGElement,\n format: ExportFormat = \"pdf\",\n fileName: string = \"chart\"\n): Promise<void> {\n switch (format) {\n case \"svg\":\n return exportChartAsSvg(svgElement, fileName);\n case \"png\":\n return exportChartAsPng(svgElement, fileName);\n case \"pdf\":\n return exportChartAsPdf(svgElement, fileName);\n }\n}\n","import React, { useCallback } from \"react\";\nimport type { ExportConfig } from \"../types/chart.types\";\nimport { exportChart } from \"../utils/exportPdf\";\n\ninterface ExportButtonProps {\n config?: ExportConfig;\n svgRef: React.RefObject<SVGSVGElement | null>;\n svgWidth: number;\n svgHeight: number;\n margin: { top: number; right: number; bottom: number; left: number };\n}\n\nexport const ExportButton: React.FC<ExportButtonProps> = React.memo(\n ({ config, svgRef, svgWidth, svgHeight, margin }) => {\n if (!config?.enabled) return null;\n\n const position = config.buttonPosition ?? \"top-right\";\n const btnWidth = 28;\n const btnHeight = 24;\n const padding = 8;\n\n // Offset from zoom controls — place export button next to them\n // Use a fixed offset from the edge\n let gx: number;\n let gy: number;\n\n switch (position) {\n case \"top-left\":\n gx = margin.left + padding;\n gy = margin.top + padding + btnHeight + 8;\n break;\n case \"bottom-left\":\n gx = margin.left + padding;\n gy = svgHeight - margin.bottom - btnHeight - padding;\n break;\n case \"bottom-right\":\n gx = svgWidth - margin.right - btnWidth - padding;\n gy = svgHeight - margin.bottom - btnHeight - padding;\n break;\n case \"top-right\":\n default:\n gx = svgWidth - margin.right - btnWidth - padding;\n gy = margin.top + padding + btnHeight + 8;\n break;\n }\n\n const handleExport = useCallback(() => {\n const svg = svgRef.current;\n if (!svg) return;\n\n const format = config?.format ?? \"pdf\";\n const fileName = config?.fileName ?? \"chart\";\n exportChart(svg, format, fileName);\n }, [svgRef, config?.format, config?.fileName]);\n\n // Download icon (simple arrow-down into tray)\n const iconPath = \"M7,2 L7,10 L4,7 M7,10 L10,7 M3,13 L11,13\";\n\n return (\n <g\n className=\"chartifypdf-export-button\"\n transform={`translate(${gx}, ${gy})`}\n onClick={handleExport}\n style={{ cursor: \"pointer\" }}\n >\n <rect\n width={btnWidth}\n height={btnHeight}\n rx={4}\n fill=\"rgba(255,255,255,0.9)\"\n stroke=\"#ccc\"\n strokeWidth={1}\n />\n <g transform=\"translate(7, 4)\">\n <path\n d={iconPath}\n fill=\"none\"\n stroke=\"#333\"\n strokeWidth={1.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </g>\n </g>\n );\n }\n);\n\nExportButton.displayName = \"ExportButton\";\n","import React, { useRef, useId, useState, useCallback, useEffect } from \"react\";\nimport type {\n LineChartProps,\n ChartMargin,\n} from \"../types/chart.types\";\nimport { getSeriesColor } from \"../utils/color\";\nimport { useChartDimensions } from \"../hooks/useChartDimensions\";\nimport { useDataDomain } from \"../hooks/useDataDomain\";\nimport { useScales } from \"../hooks/useScales\";\nimport { useZoom } from \"../hooks/useZoom\";\nimport { useTooltip } from \"../hooks/useTooltip\";\nimport { Axes } from \"./Axes\";\nimport { GridLines } from \"./GridLines\";\nimport { LinePath } from \"./LinePath\";\nimport { Tooltip } from \"./Tooltip\";\nimport { ZoomControls } from \"./ZoomControls\";\nimport { PeakMarkers } from \"./PeakMarkers\";\nimport { ContextMenu } from \"./ContextMenu\";\nimport { ExportButton } from \"./ExportButton\";\n\nconst DEFAULT_MARGIN: ChartMargin = {\n top: 20,\n right: 20,\n bottom: 50,\n left: 60,\n};\n\nconst DEFAULT_HEIGHT = 400;\n\nexport const LineChart: React.FC<LineChartProps> = ({\n data,\n width: providedWidth,\n height: providedHeight,\n margin: marginOverride,\n xAxis: xAxisConfig,\n yAxis: yAxisConfig,\n tooltip: tooltipConfig,\n zoom: zoomConfig,\n style: chartStyle,\n animation,\n colorPalette,\n className,\n onPointClick,\n ariaLabel,\n curveType: globalCurveType,\n peaks,\n contextMenu: contextMenuConfig,\n export: exportConfig,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const svgRef = useRef<SVGSVGElement>(null);\n const clipId = useId();\n\n const margin: ChartMargin = {\n ...DEFAULT_MARGIN,\n ...marginOverride,\n };\n\n const dimensions = useChartDimensions(\n containerRef,\n providedWidth,\n providedHeight ?? DEFAULT_HEIGHT\n );\n const { width: svgWidth, height: svgHeight } = dimensions;\n\n const plotWidth = Math.max(0, svgWidth - margin.left - margin.right);\n const plotHeight = Math.max(0, svgHeight - margin.top - margin.bottom);\n\n // 1. Extract full domain from data\n const { fullXDomain, fullYDomain } = useDataDomain(data, xAxisConfig, yAxisConfig);\n\n // 2. Domain-based zoom\n const zoomState = useZoom(zoomConfig, fullXDomain, fullYDomain, plotWidth, plotHeight);\n const zoomEnabled = zoomConfig?.enabled ?? false;\n const enableClickZoom = zoomConfig?.enableClickZoom ?? false;\n\n // 3. Scales from view domain (semantic zoom — ticks update automatically)\n const { xScale, yScale, xTicks, yTicks } = useScales(\n data,\n plotWidth,\n plotHeight,\n xAxisConfig,\n yAxisConfig,\n zoomEnabled ? zoomState.viewXDomain : undefined,\n zoomEnabled ? zoomState.viewYDomain : undefined\n );\n\n // Tooltip\n const tooltipEnabled = tooltipConfig?.enabled !== false;\n const tooltipState = useTooltip(\n svgRef,\n data,\n xScale,\n yScale,\n margin.left,\n margin.top,\n plotWidth,\n tooltipConfig\n );\n\n // Hover highlight: compute opacity per series\n const activeSeriesId = tooltipState.activeSeriesId;\n const hasHover = tooltipState.tooltipVisible && activeSeriesId !== null;\n\n const getHighlightOpacity = (seriesId: string): number => {\n if (!hasHover) return 1;\n return seriesId === activeSeriesId ? 1 : 0.2;\n };\n\n // FIX #1: Native wheel listener to prevent page scroll during Ctrl+zoom\n const requireCtrlKey = zoomConfig?.requireCtrlKey ?? true;\n const enableWheel = zoomConfig?.enableWheel ?? true;\n\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg || !zoomEnabled || !enableWheel) return;\n\n const handleNativeWheel = (e: WheelEvent) => {\n // When Ctrl is held and zoom is enabled, prevent page scroll\n if (requireCtrlKey) {\n if (e.ctrlKey || e.metaKey) {\n e.preventDefault();\n }\n } else {\n // If requireCtrlKey is false, always prevent scroll on chart\n e.preventDefault();\n }\n };\n\n svg.addEventListener(\"wheel\", handleNativeWheel, { passive: false });\n return () => svg.removeEventListener(\"wheel\", handleNativeWheel);\n }, [zoomEnabled, enableWheel, requireCtrlKey]);\n\n // FIX #4: Click to zoom at point + double-click to reset\n const handleChartClick = useCallback(\n (e: React.MouseEvent) => {\n if (!zoomEnabled || !enableClickZoom) return;\n if (zoomState.isPanning) return;\n\n const svg = svgRef.current;\n if (!svg) return;\n\n const rect = svg.getBoundingClientRect();\n const mouseX = e.clientX - rect.left - margin.left;\n const mouseY = e.clientY - rect.top - margin.top;\n\n if (mouseX < 0 || mouseX > plotWidth || mouseY < 0 || mouseY > plotHeight) return;\n\n // Find nearest data point\n let nearestX = 0;\n let nearestY = 0;\n let nearestDist = Infinity;\n for (const series of data) {\n for (const pt of series.data) {\n const px = xScale(pt.x);\n const py = yScale(pt.y);\n const dist = Math.hypot(px - mouseX, py - mouseY);\n if (dist < nearestDist) {\n nearestDist = dist;\n nearestX = pt.x;\n nearestY = pt.y;\n }\n }\n }\n\n zoomState.zoomToPoint(nearestX, nearestY);\n },\n [zoomEnabled, enableClickZoom, zoomState, data, xScale, yScale, margin.left, margin.top, plotWidth, plotHeight]\n );\n\n const handleDoubleClick = useCallback(() => {\n if (!zoomEnabled || !enableClickZoom) return;\n zoomState.resetZoom();\n }, [zoomEnabled, enableClickZoom, zoomState]);\n\n // Context menu state\n const [ctxMenu, setCtxMenu] = useState<{\n visible: boolean;\n clientX: number;\n clientY: number;\n nearestX: number;\n }>({ visible: false, clientX: 0, clientY: 0, nearestX: 0 });\n\n const handleContextMenu = useCallback(\n (e: React.MouseEvent) => {\n if (!contextMenuConfig?.enabled) return;\n e.preventDefault();\n\n const svg = svgRef.current;\n if (!svg) return;\n\n const rect = svg.getBoundingClientRect();\n const mouseX = e.clientX - rect.left - margin.left;\n\n if (mouseX < 0 || mouseX > plotWidth) return;\n\n // Find nearest X value across all series\n let nearestX = 0;\n let nearestDist = Infinity;\n for (const series of data) {\n for (const pt of series.data) {\n const px = xScale(pt.x);\n const dist = Math.abs(px - mouseX);\n if (dist < nearestDist) {\n nearestDist = dist;\n nearestX = pt.x;\n }\n }\n }\n\n setCtxMenu({\n visible: true,\n clientX: e.clientX,\n clientY: e.clientY,\n nearestX,\n });\n },\n [contextMenuConfig?.enabled, data, xScale, margin.left, plotWidth]\n );\n\n const closeContextMenu = useCallback(() => {\n setCtxMenu((prev) => ({ ...prev, visible: false }));\n }, []);\n\n const showXGrid = xAxisConfig?.gridLines ?? false;\n const showYGrid = yAxisConfig?.gridLines ?? true;\n\n const getColor = (index: number) => getSeriesColor(index, colorPalette);\n\n // Don't render until we have dimensions\n if (svgWidth === 0 || svgHeight === 0) {\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n width: providedWidth ?? \"100%\",\n height: providedHeight ?? DEFAULT_HEIGHT,\n backgroundColor: chartStyle?.backgroundColor,\n }}\n />\n );\n }\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n width: providedWidth ?? \"100%\",\n height: providedHeight ?? DEFAULT_HEIGHT,\n backgroundColor: chartStyle?.backgroundColor,\n position: \"relative\",\n }}\n >\n <svg\n ref={svgRef}\n width={svgWidth}\n height={svgHeight}\n role=\"img\"\n aria-label={ariaLabel ?? \"Line chart\"}\n onMouseMove={\n tooltipEnabled && !zoomState.isPanning\n ? tooltipState.handleMouseMove\n : undefined\n }\n onMouseLeave={tooltipEnabled ? tooltipState.handleMouseLeave : undefined}\n onWheel={zoomEnabled ? zoomState.handleWheel : undefined}\n onMouseDown={zoomEnabled ? zoomState.handlePanStart : undefined}\n onMouseUp={zoomEnabled ? zoomState.handlePanEnd : undefined}\n onClick={handleChartClick}\n onDoubleClick={handleDoubleClick}\n onContextMenu={handleContextMenu}\n style={{\n userSelect: zoomState.isPanning ? \"none\" : undefined,\n cursor: zoomState.isPanning\n ? \"grabbing\"\n : zoomEnabled && zoomState.scale > 1\n ? \"grab\"\n : undefined,\n }}\n >\n <defs>\n <clipPath id={clipId}>\n <rect width={plotWidth} height={plotHeight} />\n </clipPath>\n </defs>\n\n <g transform={`translate(${margin.left}, ${margin.top})`}>\n {/* Axes — always crisp, ticks update with zoom */}\n <Axes\n xTicks={xTicks}\n yTicks={yTicks}\n xScale={xScale}\n yScale={yScale}\n plotWidth={plotWidth}\n plotHeight={plotHeight}\n xAxisConfig={xAxisConfig}\n yAxisConfig={yAxisConfig}\n style={chartStyle}\n />\n\n {/* Grid lines */}\n <GridLines\n xTicks={xTicks}\n yTicks={yTicks}\n xScale={xScale}\n yScale={yScale}\n plotWidth={plotWidth}\n plotHeight={plotHeight}\n showXGrid={showXGrid}\n showYGrid={showYGrid}\n xGridColor={xAxisConfig?.gridLineColor}\n yGridColor={yAxisConfig?.gridLineColor}\n />\n\n {/* Clipped data area — no CSS transform, scales handle zoom */}\n <g clipPath={`url(#${clipId})`}>\n {zoomEnabled ? (\n <g onMouseMove={zoomState.handlePanMove}>\n {data.map((series, i) => (\n <LinePath\n key={series.id}\n series={series}\n xScale={xScale}\n yScale={yScale}\n color={series.color ?? getColor(i)}\n animation={animation}\n onPointClick={onPointClick}\n curveType={globalCurveType}\n highlightOpacity={getHighlightOpacity(series.id)}\n />\n ))}\n </g>\n ) : (\n data.map((series, i) => (\n <LinePath\n key={series.id}\n series={series}\n xScale={xScale}\n yScale={yScale}\n color={series.color ?? getColor(i)}\n animation={animation}\n onPointClick={onPointClick}\n curveType={globalCurveType}\n highlightOpacity={getHighlightOpacity(series.id)}\n />\n ))\n )}\n\n {/* Peak markers */}\n {peaks && peaks.length > 0 && (\n <PeakMarkers\n peaks={peaks}\n data={data}\n xScale={xScale}\n yScale={yScale}\n colorPalette={colorPalette}\n />\n )}\n </g>\n\n {/* Tooltip renders above everything in plot area */}\n {tooltipEnabled && (\n <Tooltip\n visible={tooltipState.tooltipVisible}\n x={tooltipState.tooltipX}\n y={tooltipState.tooltipY}\n point={tooltipState.activePoint}\n series={tooltipState.activeSeries}\n plotHeight={plotHeight}\n plotWidth={plotWidth}\n config={tooltipConfig}\n seriesColor={\n tooltipState.activeSeries?.color ??\n getColor(\n data.findIndex(\n (s) => s.id === tooltipState.activeSeries?.id\n )\n )\n }\n />\n )}\n </g>\n\n {/* Zoom controls render at SVG root level */}\n <ZoomControls\n config={zoomConfig}\n svgWidth={svgWidth}\n svgHeight={svgHeight}\n margin={margin}\n scale={zoomState.scale}\n onZoomIn={zoomState.zoomIn}\n onZoomOut={zoomState.zoomOut}\n onReset={zoomState.resetZoom}\n />\n\n {/* Export button */}\n <ExportButton\n config={exportConfig}\n svgRef={svgRef}\n svgWidth={svgWidth}\n svgHeight={svgHeight}\n margin={margin}\n />\n </svg>\n\n {/* Zoom hint overlay */}\n {zoomState.showZoomHint && (\n <div\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n background: \"rgba(0, 0, 0, 0.7)\",\n color: \"#fff\",\n padding: \"8px 16px\",\n borderRadius: 6,\n fontSize: 13,\n fontFamily: \"system-ui, -apple-system, sans-serif\",\n pointerEvents: \"none\",\n whiteSpace: \"nowrap\",\n zIndex: 10,\n }}\n >\n Hold Ctrl + scroll to zoom\n </div>\n )}\n\n {/* Right-click context menu */}\n {contextMenuConfig?.enabled && (\n <ContextMenu\n visible={ctxMenu.visible}\n x={ctxMenu.clientX}\n y={ctxMenu.clientY}\n nearestX={ctxMenu.nearestX}\n data={data}\n config={contextMenuConfig}\n getSeriesColor={getColor}\n onClose={closeContextMenu}\n />\n )}\n </div>\n );\n};\n\nLineChart.displayName = \"LineChart\";\n"],"names":["DEFAULT_PALETTE","useChartDimensions","containerRef","providedWidth","providedHeight","dimensions","setDimensions","useState","width","height","isFixed","undefined","useLayoutEffect","node","current","measure","rect","getBoundingClientRect","observer","ResizeObserver","observe","disconnect","clamp","value","min","max","Math","lerp","a","b","t","niceNumber","range","round","exponent","floor","log10","fraction","pow","niceFraction","niceRange","tickCount","offset","abs","tickStep","niceMin","niceMax","ceil","linearScale","domainMin","domainMax","rangeMin","rangeMax","inverseLerp","generateTicks","ticks","v","push","parseFloat","toPrecision","useDataDomain","data","xAxisConfig","yAxisConfig","useMemo","xMin","Infinity","xMax","yMin","yMax","series","point","x","y","isFinite","xTickCount","yTickCount","xNice","yNice","fullXDomain","fullYDomain","useScales","plotWidth","plotHeight","viewXDomain","viewYDomain","xDomainMin","xDomainMax","yDomainMin","yDomainMax","xViewNice","yViewNice","xScale","yScale","xTickStep","xTickMin","xTickMax","integerTicks","yTickStep","yTickMin","yTickMax","xTicks","filter","Number","isInteger","yTicks","xDomain","yDomain","useZoom","config","enabled","maxScale","step","enableWheel","enablePan","requireCtrlKey","setViewXDomain","setViewYDomain","isPanning","setIsPanning","showZoomHint","setShowZoomHint","panStart","useRef","hintTimer","lastFullDomain","fullXRange","viewXRange","scale","clampDomain","useCallback","domain","fullDomain","showHint","clearTimeout","setTimeout","zoomByFactor","factor","centerXFrac","centerYFrac","cxFrac","cyFrac","prev","newRange","minRange","clampedRange","finalRange","newMin","fullYRange","zoomIn","zoomOut","resetZoom","zoomToPoint","dataX","dataY","handleWheel","e","ctrlKey","metaKey","preventDefault","currentTarget","mouseX","clientX","left","mouseY","clientY","top","delta","deltaY","handlePanStart","button","handlePanMove","dx","dy","prevXDomain","prevYDomain","xRange","yRange","domainDx","domainDy","newXDomain","newYDomain","handlePanEnd","findNearestByPixelX","sorted","length","low","high","mid","nearest","minDist","dist","useTooltip","svgRef","marginLeft","marginTop","_config","tooltipVisible","setTooltipVisible","tooltipX","setTooltipX","tooltipY","setTooltipY","activePoint","setActivePoint","activeSeries","setActiveSeries","activeSeriesId","setActiveSeriesId","handleMouseMove","svg","nearestX","nearestXDist","closestPoint","closestSeries","closestYDist","bestPt","bestXDist","pt","xDist","pyY","yDist","id","handleMouseLeave","defaultTickFormat","toFixed","toString","Axes","React","memo","style","axisColor","tickColor","fontFamily","fontSize","xTickFormat","tickFormat","yTickFormat","_jsxs","className","_jsx","x1","y1","x2","y2","stroke","strokeWidth","map","tick","children","textAnchor","fill","label","fontWeight","dominantBaseline","transform","displayName","GridLines","showXGrid","showYGrid","xGridColor","yGridColor","strokeDasharray","strokeOpacity","buildLinePath","points","first","rest","p","join","buildSmoothPath","curveType","n","m","i","tangents","Array","alpha","beta","s","tau","sqrt","d","segLen","buildMonotonePath","solveSpline","vals","len","r","cp1","cp2","result","xs","ys","xCPs","yCPs","buildNaturalPath","LinePath","color","animation","onPointClick","highlightOpacity","pathRef","pathLength","setPathLength","animationDone","setAnimationDone","showDots","dotRadius","effectiveCurve","opacity","useEffect","getTotalLength","timer","duration","animStyle","strokeDashoffset","transition","easing","ref","strokeLinecap","strokeLinejoin","cx","cy","cursor","onClick","Tooltip","visible","seriesColor","bgColor","backgroundColor","textColor","content","renderCustom","formatter","name","tx","ty","pointerEvents","tooltipHeight","overflow","padding","borderRadius","whiteSpace","lineHeight","boxShadow","ZoomControls","svgWidth","svgHeight","margin","onZoomIn","onZoomOut","onReset","showControls","position","controlsPosition","btnSize","gx","gy","bottom","right","displayButtons","btn","rx","ArrowIcon","DiamondIcon","StarIcon","pts","outerAngle","PI","innerAngle","cos","sin","PeakMarkers","peaks","peak","targetX","maxY","bestSeriesIdx","si","closest","closestDist","seriesIndex","findYAtX","px","py","icon","iconY","ContextMenu","getSeriesColor","onClose","menuRef","handleClickOutside","contains","target","handleKeyDown","key","document","addEventListener","removeEventListener","borderColor","rows","yVal","findNearestY","zIndex","border","minWidth","borderBottom","marginBottom","toLocaleString","borderCollapse","textAlign","row","borderTop","display","alignItems","gap","flexShrink","fontVariantNumeric","cloneSvgWithStyles","svgElement","clone","cloneNode","setAttribute","String","allOriginal","querySelectorAll","allClone","orig","cloned","computed","window","getComputedStyle","importantProps","prop","val","getPropertyValue","setProperty","downloadBlob","blob","fileName","url","URL","createObjectURL","createElement","href","download","body","appendChild","click","removeChild","revokeObjectURL","async","svgToCanvas","canvas","ctx","getContext","Error","svgString","XMLSerializer","serializeToString","Blob","type","svgToDataUrl","Promise","resolve","reject","img","Image","onload","fillStyle","fillRect","drawImage","onerror","src","exportChartAsSvg","exportChartAsPng","toBlob","exportChartAsPdf","dataUrl","toDataURL","printWindow","open","write","close","exportChart","format","ExportButton","buttonPosition","handleExport","DEFAULT_MARGIN","LineChart","marginOverride","xAxis","yAxis","tooltip","tooltipConfig","zoom","zoomConfig","chartStyle","colorPalette","ariaLabel","globalCurveType","contextMenu","contextMenuConfig","export","exportConfig","clipId","useId","zoomState","zoomEnabled","enableClickZoom","tooltipEnabled","tooltipState","hasHover","getHighlightOpacity","seriesId","handleNativeWheel","passive","handleChartClick","nearestY","nearestDist","hypot","handleDoubleClick","ctxMenu","setCtxMenu","handleContextMenu","closeContextMenu","gridLines","getColor","index","palette","colors","role","onMouseMove","onMouseLeave","onWheel","onMouseDown","onMouseUp","onDoubleClick","onContextMenu","userSelect","gridLineColor","clipPath","findIndex","background"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{jsxs as e,jsx as t}from"react/jsx-runtime";import o,{useState as n,useLayoutEffect as i,useMemo as r,useRef as l,useCallback as a,useEffect as s,useId as c}from"react";const d=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"];function h(e,t){const o=t??d;return o[e%o.length]}function f(e,t,o){const[r,l]=n({width:t??0,height:o??0}),a=void 0!==t&&void 0!==o;return i(()=>{if(a)return void l({width:t,height:o});const n=e.current;if(!n)return;const i=()=>{const e=n.getBoundingClientRect();l({width:e.width,height:e.height})};i();const r=new ResizeObserver(()=>{i()});return r.observe(n),()=>r.disconnect()},[e,a,t,o]),r}function u(e,t,o){return Math.min(Math.max(e,t),o)}function x(e,t){const o=Math.floor(Math.log10(e)),n=e/Math.pow(10,o);let i;return i=t?n<1.5?1:n<3?2:n<7?5:10:n<=1?1:n<=2?2:n<=5?5:10,i*Math.pow(10,o)}function m(e,t,o){if(e===t){const o=0===e?1:.1*Math.abs(e);e-=o,t+=o}const n=x(t-e,!1),i=x(n/(o-1),!0);return{niceMin:Math.floor(e/i)*i,niceMax:Math.ceil(t/i)*i,tickStep:i}}function y(e,t,o,n){return i=>{if(t===e)return(o+n)/2;const r=function(e,t,o){return e===t?0:(o-e)/(t-e)}(e,t,i);return function(e,t,o){return e+(t-e)*o}(o,n,r)}}function g(e,t,o){const n=[];for(let i=e;i<=t+.5*o;i+=o)n.push(parseFloat(i.toPrecision(12)));return n}function p(e,t,o,n,i){return r(()=>{let r=1/0,l=-1/0,a=1/0,s=-1/0;for(const t of e)for(const e of t.data)e.x<r&&(r=e.x),e.x>l&&(l=e.x),e.y<a&&(a=e.y),e.y>s&&(s=e.y);isFinite(r)||(r=0,l=1,a=0,s=1),void 0!==n?.min&&(r=n.min),void 0!==n?.max&&(l=n.max),void 0!==i?.min&&(a=i.min),void 0!==i?.max&&(s=i.max);const c=n?.tickCount??6,d=i?.tickCount??6,h=m(r,l,c),f=m(a,s,d);return{xScale:y(h.niceMin,h.niceMax,0,t),yScale:y(f.niceMin,f.niceMax,o,0),xTicks:g(h.niceMin,h.niceMax,h.tickStep),yTicks:g(f.niceMin,f.niceMax,f.tickStep),xDomain:[h.niceMin,h.niceMax],yDomain:[f.niceMin,f.niceMax]}},[e,t,o,n,i])}function b(e,t,o){const i=e?.enabled??!1,r=e?.minScale??1,s=e?.maxScale??10,c=e?.step??.5,d=e?.enableWheel??!0,h=e?.enablePan??!0,[f,x]=n(1),[m,y]=n(0),[g,p]=n(0),[b,k]=n(!1),M=l({x:0,y:0,offsetX:0,offsetY:0}),v=a((e,n,i)=>{const r=o*(i-1);return{x:u(e,-(t*(i-1)),0),y:u(n,-r,0)}},[t,o]),C=a(()=>{i&&x(e=>{const n=u(e+c,r,s);return y(e=>v(e-t*c/2,0,n).x),p(e=>v(0,e-o*c/2,n).y),n})},[i,c,r,s,t,o,v]),S=a(()=>{i&&x(e=>{const n=u(e-c,r,s);return y(e=>v(e+t*c/2,0,n).x),p(e=>v(0,e+o*c/2,n).y),n})},[i,c,r,s,t,o,v]),w=a(()=>{x(1),y(0),p(0)},[]),$=a(e=>{if(!i||!d)return;e.preventDefault();const t=e.currentTarget.getBoundingClientRect(),o=e.clientX-t.left,n=e.clientY-t.top,l=e.deltaY<0?c:-c;x(e=>{const t=u(e+l,r,s),i=t/e;return y(e=>v(o-i*(o-e),0,t).x),p(e=>v(0,n-i*(n-e),t).y),t})},[i,d,c,r,s,v]),W=a(e=>{!i||!h||f<=1||0===e.button&&(k(!0),M.current={x:e.clientX,y:e.clientY,offsetX:m,offsetY:g})},[i,h,f,m,g]),P=a(e=>{if(!b)return;const t=e.clientX-M.current.x,o=e.clientY-M.current.y,n=v(M.current.offsetX+t,M.current.offsetY+o,f);y(n.x),p(n.y)},[b,f,v]),T=a(()=>{k(!1)},[]);return{scale:f,offsetX:m,offsetY:g,isPanning:b,zoomIn:C,zoomOut:S,resetZoom:w,handleWheel:$,handlePanStart:W,handlePanMove:P,handlePanEnd:T}}function k(e,t,o){if(0===e.length)return null;let n=0,i=e.length-1;for(;n<i;){const r=Math.floor((n+i)/2);o(e[r].x)<t?n=r+1:i=r}let r=e[n],l=Math.abs(o(r.x)-t);if(n>0){const i=Math.abs(o(e[n-1].x)-t);i<l&&(l=i,r=e[n-1])}return r}function M(e,t,o,i,r,l,s,c){const[d,h]=n(!1),[f,u]=n(0),[x,m]=n(0),[y,g]=n(null),[p,b]=n(null);return{tooltipVisible:d,tooltipX:f,tooltipY:x,activePoint:y,activeSeries:p,handleMouseMove:a(n=>{const l=e.current;if(!l)return;const a=l.getBoundingClientRect(),c=n.clientX-a.left-r;if(c<0||c>s)return void h(!1);let d=null,f=null,x=1/0;for(const e of t){const t=k(e.data,c,o);if(!t)continue;const n=Math.abs(o(t.x)-c);n<x&&(x=n,d=t,f=e)}d&&f&&(u(o(d.x)),m(i(d.y)),g(d),b(f),h(!0))},[e,t,o,i,r,s]),handleMouseLeave:a(()=>{h(!1),g(null),b(null)},[])}}const v=e=>Math.abs(e)>=1e6?`${(e/1e6).toFixed(1)}M`:Math.abs(e)>=1e3?`${(e/1e3).toFixed(1)}K`:Number.isInteger(e)?e.toString():e.toFixed(1),C=o.memo(({xTicks:o,yTicks:n,xScale:i,yScale:r,plotWidth:l,plotHeight:a,xAxisConfig:s,yAxisConfig:c,style:d})=>{const h=d?.axisColor??"#333",f=d?.tickColor??"#666",u=d?.fontFamily??"sans-serif",x=d?.fontSize??11,m=s?.tickFormat??v,y=c?.tickFormat??v;return e("g",{className:"chartifypdf-axes",children:[t("line",{x1:0,y1:a,x2:l,y2:a,stroke:h,strokeWidth:1}),o.map(o=>{const n=i(o);return e("g",{children:[t("line",{x1:n,y1:a,x2:n,y2:a+6,stroke:h,strokeWidth:1}),t("text",{x:n,y:a+18,textAnchor:"middle",fill:f,fontFamily:u,fontSize:x,children:m(o)})]},`x-tick-${o}`)}),s?.label&&t("text",{x:l/2,y:a+38,textAnchor:"middle",fill:h,fontFamily:u,fontSize:x+1,fontWeight:"bold",children:s.label}),t("line",{x1:0,y1:0,x2:0,y2:a,stroke:h,strokeWidth:1}),n.map(o=>{const n=r(o);return e("g",{children:[t("line",{x1:-6,y1:n,x2:0,y2:n,stroke:h,strokeWidth:1}),t("text",{x:-10,y:n,textAnchor:"end",dominantBaseline:"middle",fill:f,fontFamily:u,fontSize:x,children:y(o)})]},`y-tick-${o}`)}),c?.label&&t("text",{x:0,y:0,textAnchor:"middle",fill:h,fontFamily:u,fontSize:x+1,fontWeight:"bold",transform:`translate(-40, ${a/2}) rotate(-90)`,children:c.label})]})});C.displayName="Axes";const S=o.memo(({xTicks:o,yTicks:n,xScale:i,yScale:r,plotWidth:l,plotHeight:a,showXGrid:s,showYGrid:c,xGridColor:d="#e0e0e0",yGridColor:h="#e0e0e0"})=>e("g",{className:"chartifypdf-grid",children:[c&&n.map(e=>{const o=r(e);return t("line",{x1:0,y1:o,x2:l,y2:o,stroke:h,strokeDasharray:"4 4",strokeOpacity:.5},`y-grid-${e}`)}),s&&o.map(e=>{const o=i(e);return t("line",{x1:o,y1:0,x2:o,y2:a,stroke:d,strokeDasharray:"4 4",strokeOpacity:.5},`x-grid-${e}`)})]}));S.displayName="GridLines";const w=o.memo(({series:o,xScale:i,yScale:r,color:a,animation:c,onPointClick:d})=>{const h=l(null),[f,u]=n(0),[x,m]=n(!c?.enabled),y=o.strokeWidth??2,g=o.showDots??!1,p=o.dotRadius??3.5,b=function(e){if(0===e.length)return"";const[t,...o]=e;return`M${t.x},${t.y}`+o.map(e=>`L${e.x},${e.y}`).join("")}(o.data.map(e=>({x:i(e.x),y:r(e.y)})));s(()=>{if(c?.enabled&&h.current){const e=h.current.getTotalLength();u(e),m(!1);const t=setTimeout(()=>{m(!0)},c.duration??800);return()=>clearTimeout(t)}},[b,c?.enabled,c?.duration]);const k=c?.enabled&&f>0?{strokeDasharray:f,strokeDashoffset:x?0:f,transition:`stroke-dashoffset ${c.duration??800}ms ${c.easing??"ease-in-out"}`}:{};return e("g",{className:"chartifypdf-line-path",children:[t("path",{ref:h,d:b,fill:"none",stroke:a,strokeWidth:y,strokeDasharray:o.strokeDasharray,strokeLinecap:"round",strokeLinejoin:"round",style:k}),g&&o.data.map((e,n)=>t("circle",{cx:i(e.x),cy:r(e.y),r:p,fill:a,stroke:"#fff",strokeWidth:1.5,style:{cursor:d?"pointer":"default"},onClick:d?()=>d(e,o):void 0},`dot-${o.id}-${n}`))]})});w.displayName="LinePath";const $=o.memo(({visible:o,x:n,y:i,point:r,series:l,plotHeight:a,plotWidth:s,config:c,seriesColor:d})=>{if(!o||!r||!l)return null;const h=c?.backgroundColor??"rgba(0, 0, 0, 0.8)",f=c?.textColor??"#fff";let u;if(c?.renderCustom)u=c.renderCustom(r,l);else{u=c?.formatter?c.formatter(r,l):`${l.name}: (${r.x}, ${r.y})`}let x=n+12,m=i-40-8;return x+140>s&&(x=n-140-12),m<0&&(m=i+12),e("g",{className:"chartifypdf-tooltip",pointerEvents:"none",children:[t("line",{x1:n,y1:0,x2:n,y2:a,stroke:"#999",strokeWidth:1,strokeDasharray:"4 4",opacity:.6}),t("circle",{cx:n,cy:i,r:5,fill:d,stroke:"#fff",strokeWidth:2}),t("foreignObject",{x:x,y:m,width:140,height:60,overflow:"visible",children:t("div",{style:{backgroundColor:h,color:f,padding:"6px 10px",borderRadius:"4px",fontSize:"12px",fontFamily:"sans-serif",whiteSpace:"nowrap",lineHeight:1.4,boxShadow:"0 2px 6px rgba(0,0,0,0.2)"},children:u})})]})});$.displayName="Tooltip";const W=o.memo(({config:o,svgWidth:n,svgHeight:i,margin:r,scale:l,onZoomIn:a,onZoomOut:s,onReset:c})=>{if(!o?.enabled||!1===o.showControls)return null;const d=o.controlsPosition??"top-right",h=24;let f,u;switch(d){case"top-left":f=r.left+8,u=r.top+8;break;case"bottom-left":f=r.left+8,u=i-r.bottom-24-8;break;case"bottom-right":f=n-r.right-80-8,u=i-r.bottom-24-8;break;default:f=n-r.right-80-8,u=r.top+8}return t("g",{className:"chartifypdf-zoom-controls",transform:`translate(${f}, ${u})`,children:(l>1?[{label:"+",onClick:a},{label:"−",onClick:s},{label:"↺",onClick:c}]:[{label:"+",onClick:a},{label:"−",onClick:s}]).map((o,n)=>e("g",{transform:`translate(${28*n}, 0)`,onClick:o.onClick,style:{cursor:"pointer"},children:[t("rect",{width:h,height:h,rx:4,fill:"rgba(255,255,255,0.9)",stroke:"#ccc",strokeWidth:1}),t("text",{x:12,y:12,textAnchor:"middle",dominantBaseline:"central",fontSize:14,fontFamily:"sans-serif",fill:"#333",fontWeight:"bold",children:o.label})]},o.label))})});W.displayName="ZoomControls";const P={top:20,right:20,bottom:50,left:60},T=({data:o,width:n,height:i,margin:r,xAxis:a,yAxis:s,tooltip:d,zoom:u,style:x,animation:m,colorPalette:y,className:g,onPointClick:k,ariaLabel:v})=>{const T=l(null),L=l(null),N=c(),z={...P,...r},F=f(T,n,i??400),{width:X,height:Y}=F,A=Math.max(0,X-z.left-z.right),D=Math.max(0,Y-z.top-z.bottom),{xScale:G,yScale:H,xTicks:O,yTicks:R}=p(o,A,D,a,s),Z=b(u,A,D),I=!1!==d?.enabled,B=M(L,o,G,H,z.left,z.top,A),j=a?.gridLines??!1,E=s?.gridLines??!0;return t("div",0===X||0===Y?{ref:T,className:g,style:{width:n??"100%",height:i??400,backgroundColor:x?.backgroundColor}}:{ref:T,className:g,style:{width:n??"100%",height:i??400,backgroundColor:x?.backgroundColor},children:e("svg",{ref:L,width:X,height:Y,role:"img","aria-label":v??"Line chart",onMouseMove:I&&!Z.isPanning?B.handleMouseMove:void 0,onMouseLeave:I?B.handleMouseLeave:void 0,onWheel:u?.enabled?Z.handleWheel:void 0,onMouseDown:u?.enabled?Z.handlePanStart:void 0,onMouseUp:u?.enabled?Z.handlePanEnd:void 0,style:{userSelect:Z.isPanning?"none":void 0,cursor:Z.isPanning?"grabbing":u?.enabled&&Z.scale>1?"grab":void 0},children:[t("defs",{children:t("clipPath",{id:N,children:t("rect",{width:A,height:D})})}),e("g",{transform:`translate(${z.left}, ${z.top})`,children:[t(C,{xTicks:O,yTicks:R,xScale:G,yScale:H,plotWidth:A,plotHeight:D,xAxisConfig:a,yAxisConfig:s,style:x}),t(S,{xTicks:O,yTicks:R,xScale:G,yScale:H,plotWidth:A,plotHeight:D,showXGrid:j,showYGrid:E,xGridColor:a?.gridLineColor,yGridColor:s?.gridLineColor}),t("g",{clipPath:`url(#${N})`,children:t("g",{transform:`translate(${Z.offsetX}, ${Z.offsetY}) scale(${Z.scale})`,onMouseMove:u?.enabled?Z.handlePanMove:void 0,children:o.map((e,o)=>t(w,{series:e,xScale:G,yScale:H,color:e.color??h(o,y),animation:m,onPointClick:k},e.id))})}),I&&t($,{visible:B.tooltipVisible,x:B.tooltipX,y:B.tooltipY,point:B.activePoint,series:B.activeSeries,plotHeight:D,plotWidth:A,config:d,seriesColor:B.activeSeries?.color??h(o.findIndex(e=>e.id===B.activeSeries?.id),y)})]}),t(W,{config:u,svgWidth:X,svgHeight:Y,margin:z,scale:Z.scale,onZoomIn:Z.zoomIn,onZoomOut:Z.zoomOut,onReset:Z.resetZoom})]})})};T.displayName="LineChart";export{T as LineChart,f as useChartDimensions,p as useScales,M as useTooltip,b as useZoom};
|
|
1
|
+
import{jsxs as t,jsx as e}from"react/jsx-runtime";import n,{useState as o,useLayoutEffect as i,useMemo as r,useRef as l,useCallback as a,useEffect as c,useId as s}from"react";const d=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"];function h(t,e,n){const[r,l]=o({width:e??0,height:n??0}),a=void 0!==e&&void 0!==n;return i(()=>{if(a)return void l({width:e,height:n});const o=t.current;if(!o)return;const i=()=>{const t=o.getBoundingClientRect();l({width:t.width,height:t.height})};i();const r=new ResizeObserver(()=>{i()});return r.observe(o),()=>r.disconnect()},[t,a,e,n]),r}function f(t,e,n){return Math.min(Math.max(t,e),n)}function u(t,e,n){return t+(e-t)*n}function m(t,e){const n=Math.floor(Math.log10(t)),o=t/Math.pow(10,n);let i;return i=e?o<1.5?1:o<3?2:o<7?5:10:o<=1?1:o<=2?2:o<=5?5:10,i*Math.pow(10,n)}function g(t,e,n){if(t===e){const n=0===t?1:.1*Math.abs(t);t-=n,e+=n}const o=m(e-t,!1),i=m(o/(n-1),!0);return{niceMin:Math.floor(t/i)*i,niceMax:Math.ceil(e/i)*i,tickStep:i}}function y(t,e,n,o){return i=>{if(e===t)return(n+o)/2;const r=function(t,e,n){return t===e?0:(n-t)/(e-t)}(t,e,i);return u(n,o,r)}}function p(t,e,n){const o=[];for(let i=t;i<=e+.5*n;i+=n)o.push(parseFloat(i.toPrecision(12)));return o}function x(t,e,n){return r(()=>{let o=1/0,i=-1/0,r=1/0,l=-1/0;for(const e of t)for(const t of e.data)t.x<o&&(o=t.x),t.x>i&&(i=t.x),t.y<r&&(r=t.y),t.y>l&&(l=t.y);isFinite(o)||(o=0,i=1,r=0,l=1),void 0!==e?.min&&(o=e.min),void 0!==e?.max&&(i=e.max),void 0!==n?.min&&(r=n.min),void 0!==n?.max&&(l=n.max);const a=e?.tickCount??6,c=n?.tickCount??6,s=g(o,i,a),d=g(r,l,c);return{fullXDomain:[s.niceMin,s.niceMax],fullYDomain:[d.niceMin,d.niceMax]}},[t,e,n])}function b(t,e,n,o,i,l,a){return r(()=>{let r=1/0,c=-1/0,s=1/0,d=-1/0;for(const e of t)for(const t of e.data)t.x<r&&(r=t.x),t.x>c&&(c=t.x),t.y<s&&(s=t.y),t.y>d&&(d=t.y);isFinite(r)||(r=0,c=1,s=0,d=1),void 0!==o?.min&&(r=o.min),void 0!==o?.max&&(c=o.max),void 0!==i?.min&&(s=i.min),void 0!==i?.max&&(d=i.max);const h=o?.tickCount??6,f=i?.tickCount??6;let u,m,x,b;if(l)u=l[0],m=l[1];else{const t=g(r,c,h);u=t.niceMin,m=t.niceMax}if(a)x=a[0],b=a[1];else{const t=g(s,d,f);x=t.niceMin,b=t.niceMax}const k=g(u,m,h),v=g(x,b,f),w=y(u,m,0,e),M=y(x,b,n,0);let C=k.tickStep,$=k.niceMin,S=k.niceMax;o?.integerTicks&&(C=Math.max(1,Math.ceil(C)),$=Math.ceil(u),S=Math.floor(m));let L=v.tickStep,P=v.niceMin,T=v.niceMax;i?.integerTicks&&(L=Math.max(1,Math.ceil(L)),P=Math.ceil(x),T=Math.floor(b));return{xScale:w,yScale:M,xTicks:p($,S,C).filter(t=>t>=u&&t<=m).filter(t=>!o?.integerTicks||Number.isInteger(t)),yTicks:p(P,T,L).filter(t=>t>=x&&t<=b).filter(t=>!i?.integerTicks||Number.isInteger(t)),xDomain:[u,m],yDomain:[x,b]}},[t,e,n,o,i,l,a])}function k(t,e,n,i,r){const c=t?.enabled??!1,s=t?.maxScale??10,d=t?.step??.5,h=t?.enableWheel??!0,m=t?.enablePan??!0,g=t?.requireCtrlKey??!0,[y,p]=o(e),[x,b]=o(n),[k,v]=o(!1),[w,M]=o(!1),C=l({x:0,y:0,xDomain:e,yDomain:n}),$=l(null),S=l({x:e,y:n});e[0]===S.current.x[0]&&e[1]===S.current.x[1]&&n[0]===S.current.y[0]&&n[1]===S.current.y[1]||(S.current={x:e,y:n},p(e),b(n));const L=e[1]-e[0],P=y[1]-y[0],T=L>0&&P>0?L/P:1,W=a((t,e)=>{const n=t[1]-t[0];if(n>=e[1]-e[0])return e;let o=t[0],i=t[1];return o<e[0]&&(o=e[0],i=o+n),i>e[1]&&(i=e[1],o=i-n),[o,i]},[]),D=a(()=>{$.current&&clearTimeout($.current),M(!0),$.current=setTimeout(()=>M(!1),2e3)},[]),R=a((t,o,i)=>{if(!c)return;const r=o??.5,l=i??.5;p(n=>{const o=(n[1]-n[0])/t,i=L/s,l=Math.max(o,i),a=Math.min(l,L),c=u(n[0],n[1],r)-a*r;return W([c,c+a],e)}),b(e=>{const o=e[1]-e[0],i=n[1]-n[0],r=o/t,a=i/s,c=Math.max(r,a),d=Math.min(c,i),h=u(e[0],e[1],l)-d*l;return W([h,h+d],n)})},[c,e,n,L,s,W]),z=a(()=>{R(1+d)},[d,R]),A=a(()=>{R(1/(1+d))},[d,R]),N=a(()=>{p(e),b(n)},[e,n]),X=a((t,o)=>{if(!c)return;p(n=>{const o=(n[1]-n[0])/2,i=L/s,r=Math.max(o,i),l=Math.min(r,L),a=t-l/2;return W([a,a+l],e)}),b(t=>{const e=t[1]-t[0],i=n[1]-n[0],r=e/2,l=i/s,a=Math.max(r,l),c=Math.min(a,i),d=o-c/2;return W([d,d+c],n)})},[c,e,n,L,s,W]),Y=a(t=>{if(!c||!h)return;if(g&&!t.ctrlKey&&!t.metaKey)return void D();t.preventDefault();const e=t.currentTarget.getBoundingClientRect(),n=t.clientX-e.left,o=t.clientY-e.top,i=f(n/(e.width||1),0,1),r=f(o/(e.height||1),0,1),l=t.deltaY<0?1+d:1/(1+d);R(l,i,r)},[c,h,g,d,D,R]),E=a(t=>{!c||!m||T<=1||0===t.button&&(v(!0),C.current={x:t.clientX,y:t.clientY,xDomain:y,yDomain:x})},[c,m,T,y,x]),F=a(t=>{if(!k)return;const o=t.clientX-C.current.x,l=t.clientY-C.current.y,a=C.current.xDomain,c=C.current.yDomain,s=a[1]-a[0],d=c[1]-c[0],h=-o/i*s,f=l/r*d,u=[a[0]+h,a[1]+h],m=[c[0]+f,c[1]+f];p(W(u,e)),b(W(m,n))},[k,i,r,e,n,W]),I=a(()=>{v(!1)},[]);return{viewXDomain:y,viewYDomain:x,scale:T,isPanning:k,showZoomHint:w,zoomIn:z,zoomOut:A,resetZoom:N,zoomToPoint:X,handleWheel:Y,handlePanStart:E,handlePanMove:F,handlePanEnd:I}}function v(t,e,n){if(0===t.length)return null;let o=0,i=t.length-1;for(;o<i;){const r=Math.floor((o+i)/2);n(t[r].x)<e?o=r+1:i=r}let r=t[o],l=Math.abs(n(r.x)-e);if(o>0){const i=Math.abs(n(t[o-1].x)-e);i<l&&(l=i,r=t[o-1])}return r}function w(t,e,n,i,r,l,c,s){const[d,h]=o(!1),[f,u]=o(0),[m,g]=o(0),[y,p]=o(null),[x,b]=o(null),[k,w]=o(null);return{tooltipVisible:d,tooltipX:f,tooltipY:m,activePoint:y,activeSeries:x,activeSeriesId:k,handleMouseMove:a(o=>{const a=t.current;if(!a)return;const s=a.getBoundingClientRect(),d=o.clientX-s.left-r,f=o.clientY-s.top-l;if(d<0||d>c)return h(!1),void w(null);let m=0,y=1/0;for(const t of e){const e=v(t.data,d,n);if(!e)continue;const o=Math.abs(n(e.x)-d);o<y&&(y=o,m=e.x)}let x=null,k=null,M=1/0;for(const t of e){let e=null,n=1/0;for(const o of t.data){const t=Math.abs(o.x-m);t<n&&(n=t,e=o)}if(!e)continue;const o=i(e.y),r=Math.abs(o-f);r<M&&(M=r,x=e,k=t)}x&&k&&(u(n(x.x)),g(i(x.y)),p(x),b(k),w(k.id),h(!0))},[t,e,n,i,r,l,c]),handleMouseLeave:a(()=>{h(!1),p(null),b(null),w(null)},[])}}const M=t=>Math.abs(t)>=1e6?`${(t/1e6).toFixed(1)}M`:Math.abs(t)>=1e3?`${(t/1e3).toFixed(1)}K`:Number.isInteger(t)?t.toString():t.toFixed(1),C=n.memo(({xTicks:n,yTicks:o,xScale:i,yScale:r,plotWidth:l,plotHeight:a,xAxisConfig:c,yAxisConfig:s,style:d})=>{const h=d?.axisColor??"#333",f=d?.tickColor??"#666",u=d?.fontFamily??"sans-serif",m=d?.fontSize??11,g=c?.tickFormat??M,y=s?.tickFormat??M;return t("g",{className:"chartifypdf-axes",children:[e("line",{x1:0,y1:a,x2:l,y2:a,stroke:h,strokeWidth:1}),n.map(n=>{const o=i(n);return t("g",{children:[e("line",{x1:o,y1:a,x2:o,y2:a+6,stroke:h,strokeWidth:1}),e("text",{x:o,y:a+18,textAnchor:"middle",fill:f,fontFamily:u,fontSize:m,children:g(n)})]},`x-tick-${n}`)}),c?.label&&e("text",{x:l/2,y:a+38,textAnchor:"middle",fill:h,fontFamily:u,fontSize:m+1,fontWeight:"bold",children:c.label}),e("line",{x1:0,y1:0,x2:0,y2:a,stroke:h,strokeWidth:1}),o.map(n=>{const o=r(n);return t("g",{children:[e("line",{x1:-6,y1:o,x2:0,y2:o,stroke:h,strokeWidth:1}),e("text",{x:-10,y:o,textAnchor:"end",dominantBaseline:"middle",fill:f,fontFamily:u,fontSize:m,children:y(n)})]},`y-tick-${n}`)}),s?.label&&e("text",{x:0,y:0,textAnchor:"middle",fill:h,fontFamily:u,fontSize:m+1,fontWeight:"bold",transform:`translate(-40, ${a/2}) rotate(-90)`,children:s.label})]})});C.displayName="Axes";const $=n.memo(({xTicks:n,yTicks:o,xScale:i,yScale:r,plotWidth:l,plotHeight:a,showXGrid:c,showYGrid:s,xGridColor:d="#e0e0e0",yGridColor:h="#e0e0e0"})=>t("g",{className:"chartifypdf-grid",children:[s&&o.map(t=>{const n=r(t);return e("line",{x1:0,y1:n,x2:l,y2:n,stroke:h,strokeDasharray:"4 4",strokeOpacity:.5},`y-grid-${t}`)}),c&&n.map(t=>{const n=i(t);return e("line",{x1:n,y1:0,x2:n,y2:a,stroke:d,strokeDasharray:"4 4",strokeOpacity:.5},`x-grid-${t}`)})]}));function S(t){if(0===t.length)return"";const[e,...n]=t;return`M${e.x},${e.y}`+n.map(t=>`L${t.x},${t.y}`).join("")}function L(t,e){switch(e){case"monotone":return function(t){const e=t.length;if(e<2)return S(t);if(2===e)return S(t);const n=[],o=[],i=[];for(let r=0;r<e-1;r++)n.push(t[r+1].x-t[r].x),o.push(t[r+1].y-t[r].y),i.push(0===n[r]?0:o[r]/n[r]);const r=new Array(e);r[0]=i[0],r[e-1]=i[e-2];for(let t=1;t<e-1;t++)i[t-1]*i[t]<=0?r[t]=0:r[t]=2/(1/i[t-1]+1/i[t]);for(let t=0;t<e-1;t++)if(0===i[t])r[t]=0,r[t+1]=0;else{const e=r[t]/i[t],n=r[t+1]/i[t],o=e*e+n*n;if(o>9){const l=3/Math.sqrt(o);r[t]=l*e*i[t],r[t+1]=l*n*i[t]}}let l=`M${t[0].x},${t[0].y}`;for(let o=0;o<e-1;o++){const e=n[o]/3;l+=`C${t[o].x+e},${t[o].y+r[o]*e},${t[o+1].x-e},${t[o+1].y-r[o+1]*e},${t[o+1].x},${t[o+1].y}`}return l}(t);case"natural":return function(t){const e=t.length;if(e<2)return S(t);if(2===e)return S(t);function n(t){const e=t.length-1,n=new Array(e),o=new Array(e),i=new Array(e);n[0]=0,o[0]=2,i[0]=t[0]+2*t[1];for(let r=1;r<e-1;r++)n[r]=1,o[r]=4,i[r]=4*t[r]+2*t[r+1];n[e-1]=2,o[e-1]=7,i[e-1]=8*t[e-1]+t[e];for(let t=1;t<e;t++){const e=n[t]/o[t-1];o[t]-=e,i[t]-=e*i[t-1]}const r=new Array(e);r[e-1]=i[e-1]/o[e-1];for(let t=e-2;t>=0;t--)r[t]=(i[t]-r[t+1])/o[t];const l=new Array(e);for(let n=0;n<e-1;n++)l[n]=2*t[n+1]-r[n+1];l[e-1]=(t[e]+r[e-1])/2;const a=[];for(let t=0;t<e;t++)a.push({cp1:r[t],cp2:l[t]});return a}const o=t.map(t=>t.x),i=t.map(t=>t.y),r=n(o),l=n(i);let a=`M${t[0].x},${t[0].y}`;for(let n=0;n<e-1;n++)a+=`C${r[n].cp1},${l[n].cp1},${r[n].cp2},${l[n].cp2},${t[n+1].x},${t[n+1].y}`;return a}(t);default:return S(t)}}$.displayName="GridLines";const P=n.memo(({series:n,xScale:i,yScale:r,color:a,animation:s,onPointClick:d,curveType:h,highlightOpacity:f})=>{const u=l(null),[m,g]=o(0),[y,p]=o(!s?.enabled),x=n.strokeWidth??2,b=n.showDots??!1,k=n.dotRadius??3.5,v=n.curveType??h??"linear",w=f??1,M=n.data.map(t=>({x:i(t.x),y:r(t.y)})),C="linear"===v?S(M):L(M,v);c(()=>{if(s?.enabled&&u.current){const t=u.current.getTotalLength();g(t),p(!1);const e=setTimeout(()=>{p(!0)},s.duration??800);return()=>clearTimeout(e)}},[C,s?.enabled,s?.duration]);const $=s?.enabled&&m>0?{strokeDasharray:m,strokeDashoffset:y?0:m,transition:`stroke-dashoffset ${s.duration??800}ms ${s.easing??"ease-in-out"}`}:{};return t("g",{className:"chartifypdf-line-path",style:{opacity:w,transition:"opacity 150ms ease"},children:[e("path",{ref:u,d:C,fill:"none",stroke:a,strokeWidth:x,strokeDasharray:n.strokeDasharray,strokeLinecap:"round",strokeLinejoin:"round",style:$}),b&&n.data.map((t,o)=>e("circle",{cx:i(t.x),cy:r(t.y),r:k,fill:a,stroke:"#fff",strokeWidth:1.5,style:{cursor:d?"pointer":"default"},onClick:d?()=>d(t,n):void 0},`dot-${n.id}-${o}`))]})});P.displayName="LinePath";const T=n.memo(({visible:n,x:o,y:i,point:r,series:l,plotHeight:a,plotWidth:c,config:s,seriesColor:d})=>{if(!n||!r||!l)return null;const h=s?.backgroundColor??"rgba(0, 0, 0, 0.8)",f=s?.textColor??"#fff";let u;if(s?.renderCustom)u=s.renderCustom(r,l);else{u=s?.formatter?s.formatter(r,l):`${l.name}: (${r.x}, ${r.y})`}let m=o+12,g=i-40-8;return m+140>c&&(m=o-140-12),g<0&&(g=i+12),t("g",{className:"chartifypdf-tooltip",pointerEvents:"none",children:[e("line",{x1:o,y1:0,x2:o,y2:a,stroke:"#999",strokeWidth:1,strokeDasharray:"4 4",opacity:.6}),e("circle",{cx:o,cy:i,r:5,fill:d,stroke:"#fff",strokeWidth:2}),e("foreignObject",{x:m,y:g,width:140,height:60,overflow:"visible",children:e("div",{style:{backgroundColor:h,color:f,padding:"6px 10px",borderRadius:"4px",fontSize:"12px",fontFamily:"sans-serif",whiteSpace:"nowrap",lineHeight:1.4,boxShadow:"0 2px 6px rgba(0,0,0,0.2)"},children:u})})]})});T.displayName="Tooltip";const W=n.memo(({config:n,svgWidth:o,svgHeight:i,margin:r,scale:l,onZoomIn:a,onZoomOut:c,onReset:s})=>{if(!n?.enabled||!1===n.showControls)return null;const d=n.controlsPosition??"top-right",h=24;let f,u;switch(d){case"top-left":f=r.left+8,u=r.top+8;break;case"bottom-left":f=r.left+8,u=i-r.bottom-24-8;break;case"bottom-right":f=o-r.right-80-8,u=i-r.bottom-24-8;break;default:f=o-r.right-80-8,u=r.top+8}return e("g",{className:"chartifypdf-zoom-controls",transform:`translate(${f}, ${u})`,children:(l>1?[{label:"+",onClick:a},{label:"−",onClick:c},{label:"↺",onClick:s}]:[{label:"+",onClick:a},{label:"−",onClick:c}]).map((n,o)=>t("g",{transform:`translate(${28*o}, 0)`,onClick:n.onClick,style:{cursor:"pointer"},children:[e("rect",{width:h,height:h,rx:4,fill:"rgba(255,255,255,0.9)",stroke:"#ccc",strokeWidth:1}),e("text",{x:12,y:12,textAnchor:"middle",dominantBaseline:"central",fontSize:14,fontFamily:"sans-serif",fill:"#333",fontWeight:"bold",children:n.label})]},n.label))})});function D({x:t,y:n,color:o}){return e("polygon",{points:`${t},${n} ${t-5},${n-10} ${t+5},${n-10}`,fill:o})}function R({x:t,y:n,color:o}){return e("polygon",{points:`${t},${n-6} ${t+5},${n} ${t},${n+6} ${t-5},${n}`,fill:o})}function z({x:t,y:n,color:o}){const i=[];for(let e=0;e<5;e++){const o=(72*e-90)*(Math.PI/180),r=(72*e+36-90)*(Math.PI/180);i.push(`${t+6*Math.cos(o)},${n+6*Math.sin(o)}`),i.push(`${t+3*Math.cos(r)},${n+3*Math.sin(r)}`)}return e("polygon",{points:i.join(" "),fill:o})}W.displayName="ZoomControls";const A=n.memo(({peaks:n,data:o,xScale:i,yScale:r})=>e("g",{className:"chartifypdf-peak-markers",children:n.map((n,l)=>{const a=function(t,e){let n=-1/0,o=-1;for(let i=0;i<t.length;i++){const r=t[i];let l=r.data[0],a=1/0;for(const t of r.data){const n=Math.abs(t.x-e);n<a&&(a=n,l=t)}l&&l.y>n&&(n=l.y,o=i)}return-1===o?null:{y:n,seriesIndex:o}}(o,n.x);if(!a)return null;const c=i(n.x),s=r(a.y),d=n.color??"#ef4444",h=n.icon??"arrow",f=n.label,u=s-14;return t("g",{children:[e("line",{x1:c,y1:s,x2:c,y2:u+6,stroke:d,strokeWidth:1,strokeDasharray:"3 2",opacity:.6}),"arrow"===h&&e(D,{x:c,y:u,color:d}),"diamond"===h&&e(R,{x:c,y:u,color:d}),"star"===h&&e(z,{x:c,y:u,color:d}),f&&e("text",{x:c,y:u-8,textAnchor:"middle",fill:d,fontSize:11,fontWeight:600,children:f})]},`peak-${l}`)})}));A.displayName="PeakMarkers";const N=({visible:n,x:o,y:i,nearestX:r,data:a,config:s,getSeriesColor:d,onClose:h})=>{const f=l(null);if(c(()=>{if(!n)return;const t=t=>{f.current&&!f.current.contains(t.target)&&h()},e=t=>{"Escape"===t.key&&h()},o=setTimeout(()=>{document.addEventListener("mousedown",t),document.addEventListener("keydown",e)},0);return()=>{clearTimeout(o),document.removeEventListener("mousedown",t),document.removeEventListener("keydown",e)}},[n,h]),!n)return null;const u=s?.backgroundColor??"#ffffff",m=s?.textColor??"#1f2937",g=s?.borderColor??"#e5e7eb",y=a.map((t,e)=>{const n=function(t,e){if(0===t.data.length)return null;let n=t.data[0],o=Math.abs(n.x-e);for(const i of t.data){const t=Math.abs(i.x-e);t<o&&(o=t,n=i)}return n.y}(t,r);return{name:t.name,color:t.color??d(e),x:r,y:n}});return t("div",{ref:f,style:{position:"fixed",left:o,top:i,zIndex:9999,backgroundColor:u,color:m,border:`1px solid ${g}`,borderRadius:6,boxShadow:"0 4px 12px rgba(0,0,0,0.15)",padding:"8px 0",minWidth:200,fontSize:12,fontFamily:"system-ui, -apple-system, sans-serif"},children:[t("div",{style:{padding:"4px 12px 8px",fontWeight:600,fontSize:11,color:m,opacity:.6,borderBottom:`1px solid ${g}`,marginBottom:4},children:["Data at X = ","number"==typeof r?r.toLocaleString():r]}),t("table",{style:{width:"100%",borderCollapse:"collapse"},children:[e("thead",{children:t("tr",{children:[e("th",{style:{textAlign:"left",padding:"4px 12px",fontWeight:600,opacity:.7},children:"Series"}),e("th",{style:{textAlign:"right",padding:"4px 12px",fontWeight:600,opacity:.7},children:"Y"})]})}),e("tbody",{children:y.map((n,o)=>t("tr",{style:{borderTop:o>0?`1px solid ${g}`:void 0},children:[t("td",{style:{padding:"4px 12px",display:"flex",alignItems:"center",gap:6},children:[e("span",{style:{display:"inline-block",width:8,height:8,borderRadius:"50%",backgroundColor:n.color,flexShrink:0}}),n.name]}),e("td",{style:{padding:"4px 12px",textAlign:"right",fontVariantNumeric:"tabular-nums"},children:null!==n.y?n.y.toLocaleString():"—"})]},o))})]})]})};function X(t){const e=t.cloneNode(!0),n=t.getBoundingClientRect();e.setAttribute("width",String(n.width)),e.setAttribute("height",String(n.height)),e.setAttribute("xmlns","http://www.w3.org/2000/svg"),e.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink");const o=t.querySelectorAll("*"),i=e.querySelectorAll("*");for(let t=0;t<o.length;t++){const e=o[t],n=i[t];if(!n.style)continue;const r=window.getComputedStyle(e),l=["fill","stroke","stroke-width","stroke-dasharray","opacity","font-family","font-size","font-weight","text-anchor","dominant-baseline","visibility","display"];for(const t of l){const e=r.getPropertyValue(t);e&&n.style.setProperty(t,e)}}return e}function Y(t,e){const n=URL.createObjectURL(t),o=document.createElement("a");o.href=n,o.download=e,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n)}async function E(t,e=2){const n=t.getBoundingClientRect(),o=n.width*e,i=n.height*e,r=document.createElement("canvas");r.width=o,r.height=i;const l=r.getContext("2d");if(!l)throw new Error("Cannot get canvas 2d context");l.scale(e,e);const a=function(t){const e=X(t),n=(new XMLSerializer).serializeToString(e),o=new Blob([n],{type:"image/svg+xml;charset=utf-8"});return URL.createObjectURL(o)}(t);return new Promise((t,e)=>{const o=new Image;o.onload=()=>{l.fillStyle="#ffffff",l.fillRect(0,0,n.width,n.height),l.drawImage(o,0,0,n.width,n.height),URL.revokeObjectURL(a),t(r)},o.onerror=()=>{URL.revokeObjectURL(a),e(new Error("Failed to load SVG as image"))},o.src=a})}async function F(t,e="chart"){const n=X(t),o=(new XMLSerializer).serializeToString(n);Y(new Blob([o],{type:"image/svg+xml;charset=utf-8"}),`${e}.svg`)}async function I(t,e="chart"){const n=await E(t);Y(await new Promise((t,e)=>{n.toBlob(n=>n?t(n):e(new Error("Canvas toBlob failed")),"image/png")}),`${e}.png`)}async function B(t,e="chart"){const n=(await E(t)).toDataURL("image/png"),o=t.getBoundingClientRect(),i=window.open("","_blank");if(!i)throw new Error("Popup blocked. Please allow popups to export PDF.");i.document.write(`\n <!DOCTYPE html>\n <html>\n <head>\n <title>${e}</title>\n <style>\n @page { size: landscape; margin: 10mm; }\n body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; }\n img { max-width: 100%; height: auto; }\n </style>\n </head>\n <body>\n <img src="${n}" width="${o.width}" height="${o.height}" />\n <script>\n window.onload = function() {\n setTimeout(function() { window.print(); window.close(); }, 300);\n };\n <\/script>\n </body>\n </html>\n `),i.document.close()}async function O(t,e="pdf",n="chart"){switch(e){case"svg":return F(t,n);case"png":return I(t,n);case"pdf":return B(t,n)}}N.displayName="ContextMenu";const H=n.memo(({config:n,svgRef:o,svgWidth:i,svgHeight:r,margin:l})=>{if(!n?.enabled)return null;let c,s;switch(n.buttonPosition??"top-right"){case"top-left":c=l.left+8,s=l.top+8+24+8;break;case"bottom-left":c=l.left+8,s=r-l.bottom-24-8;break;case"bottom-right":c=i-l.right-28-8,s=r-l.bottom-24-8;break;default:c=i-l.right-28-8,s=l.top+8+24+8}const d=a(()=>{const t=o.current;if(!t)return;O(t,n?.format??"pdf",n?.fileName??"chart")},[o,n?.format,n?.fileName]);return t("g",{className:"chartifypdf-export-button",transform:`translate(${c}, ${s})`,onClick:d,style:{cursor:"pointer"},children:[e("rect",{width:28,height:24,rx:4,fill:"rgba(255,255,255,0.9)",stroke:"#ccc",strokeWidth:1}),e("g",{transform:"translate(7, 4)",children:e("path",{d:"M7,2 L7,10 L4,7 M7,10 L10,7 M3,13 L11,13",fill:"none",stroke:"#333",strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"})})]})});H.displayName="ExportButton";const j={top:20,right:20,bottom:50,left:60},U=({data:n,width:i,height:r,margin:f,xAxis:u,yAxis:m,tooltip:g,zoom:y,style:p,animation:v,colorPalette:M,className:S,onPointClick:L,ariaLabel:D,curveType:R,peaks:z,contextMenu:X,export:Y})=>{const E=l(null),F=l(null),I=s(),B={...j,...f},O=h(E,i,r??400),{width:U,height:Z}=O,G=Math.max(0,U-B.left-B.right),K=Math.max(0,Z-B.top-B.bottom),{fullXDomain:V,fullYDomain:q}=x(n,u,m),_=k(y,V,q,G,K),J=y?.enabled??!1,Q=y?.enableClickZoom??!1,{xScale:tt,yScale:et,xTicks:nt,yTicks:ot}=b(n,G,K,u,m,J?_.viewXDomain:void 0,J?_.viewYDomain:void 0),it=!1!==g?.enabled,rt=w(F,n,tt,et,B.left,B.top,G),lt=rt.activeSeriesId,at=rt.tooltipVisible&&null!==lt,ct=t=>at?t===lt?1:.2:1,st=y?.requireCtrlKey??!0,dt=y?.enableWheel??!0;c(()=>{const t=F.current;if(!t||!J||!dt)return;const e=t=>{st?(t.ctrlKey||t.metaKey)&&t.preventDefault():t.preventDefault()};return t.addEventListener("wheel",e,{passive:!1}),()=>t.removeEventListener("wheel",e)},[J,dt,st]);const ht=a(t=>{if(!J||!Q)return;if(_.isPanning)return;const e=F.current;if(!e)return;const o=e.getBoundingClientRect(),i=t.clientX-o.left-B.left,r=t.clientY-o.top-B.top;if(i<0||i>G||r<0||r>K)return;let l=0,a=0,c=1/0;for(const t of n)for(const e of t.data){const t=tt(e.x),n=et(e.y),o=Math.hypot(t-i,n-r);o<c&&(c=o,l=e.x,a=e.y)}_.zoomToPoint(l,a)},[J,Q,_,n,tt,et,B.left,B.top,G,K]),ft=a(()=>{J&&Q&&_.resetZoom()},[J,Q,_]),[ut,mt]=o({visible:!1,clientX:0,clientY:0,nearestX:0}),gt=a(t=>{if(!X?.enabled)return;t.preventDefault();const e=F.current;if(!e)return;const o=e.getBoundingClientRect(),i=t.clientX-o.left-B.left;if(i<0||i>G)return;let r=0,l=1/0;for(const t of n)for(const e of t.data){const t=tt(e.x),n=Math.abs(t-i);n<l&&(l=n,r=e.x)}mt({visible:!0,clientX:t.clientX,clientY:t.clientY,nearestX:r})},[X?.enabled,n,tt,B.left,G]),yt=a(()=>{mt(t=>({...t,visible:!1}))},[]),pt=u?.gridLines??!1,xt=m?.gridLines??!0,bt=t=>function(t,e){const n=e??d;return n[t%n.length]}(t,M);return 0===U||0===Z?e("div",{ref:E,className:S,style:{width:i??"100%",height:r??400,backgroundColor:p?.backgroundColor}}):t("div",{ref:E,className:S,style:{width:i??"100%",height:r??400,backgroundColor:p?.backgroundColor,position:"relative"},children:[t("svg",{ref:F,width:U,height:Z,role:"img","aria-label":D??"Line chart",onMouseMove:it&&!_.isPanning?rt.handleMouseMove:void 0,onMouseLeave:it?rt.handleMouseLeave:void 0,onWheel:J?_.handleWheel:void 0,onMouseDown:J?_.handlePanStart:void 0,onMouseUp:J?_.handlePanEnd:void 0,onClick:ht,onDoubleClick:ft,onContextMenu:gt,style:{userSelect:_.isPanning?"none":void 0,cursor:_.isPanning?"grabbing":J&&_.scale>1?"grab":void 0},children:[e("defs",{children:e("clipPath",{id:I,children:e("rect",{width:G,height:K})})}),t("g",{transform:`translate(${B.left}, ${B.top})`,children:[e(C,{xTicks:nt,yTicks:ot,xScale:tt,yScale:et,plotWidth:G,plotHeight:K,xAxisConfig:u,yAxisConfig:m,style:p}),e($,{xTicks:nt,yTicks:ot,xScale:tt,yScale:et,plotWidth:G,plotHeight:K,showXGrid:pt,showYGrid:xt,xGridColor:u?.gridLineColor,yGridColor:m?.gridLineColor}),t("g",{clipPath:`url(#${I})`,children:[J?e("g",{onMouseMove:_.handlePanMove,children:n.map((t,n)=>e(P,{series:t,xScale:tt,yScale:et,color:t.color??bt(n),animation:v,onPointClick:L,curveType:R,highlightOpacity:ct(t.id)},t.id))}):n.map((t,n)=>e(P,{series:t,xScale:tt,yScale:et,color:t.color??bt(n),animation:v,onPointClick:L,curveType:R,highlightOpacity:ct(t.id)},t.id)),z&&z.length>0&&e(A,{peaks:z,data:n,xScale:tt,yScale:et,colorPalette:M})]}),it&&e(T,{visible:rt.tooltipVisible,x:rt.tooltipX,y:rt.tooltipY,point:rt.activePoint,series:rt.activeSeries,plotHeight:K,plotWidth:G,config:g,seriesColor:rt.activeSeries?.color??bt(n.findIndex(t=>t.id===rt.activeSeries?.id))})]}),e(W,{config:y,svgWidth:U,svgHeight:Z,margin:B,scale:_.scale,onZoomIn:_.zoomIn,onZoomOut:_.zoomOut,onReset:_.resetZoom}),e(H,{config:Y,svgRef:F,svgWidth:U,svgHeight:Z,margin:B})]}),_.showZoomHint&&e("div",{style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",background:"rgba(0, 0, 0, 0.7)",color:"#fff",padding:"8px 16px",borderRadius:6,fontSize:13,fontFamily:"system-ui, -apple-system, sans-serif",pointerEvents:"none",whiteSpace:"nowrap",zIndex:10},children:"Hold Ctrl + scroll to zoom"}),X?.enabled&&e(N,{visible:ut.visible,x:ut.clientX,y:ut.clientY,nearestX:ut.nearestX,data:n,config:X,getSeriesColor:bt,onClose:yt})]})};U.displayName="LineChart";export{U as LineChart,O as exportChart,B as exportChartAsPdf,I as exportChartAsPng,F as exportChartAsSvg,h as useChartDimensions,x as useDataDomain,b as useScales,w as useTooltip,k as useZoom};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|