chartifypdf 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 thuankg1752
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # ChartifyPDF
2
+
3
+ A zero-dependency React line chart component built with pure SVG. Supports zoom, tooltips, responsive sizing, and customizable styling.
4
+
5
+ ## Features
6
+
7
+ - **Pure SVG** — no external charting libraries
8
+ - **TypeScript** — fully typed props and exports
9
+ - **Responsive** — auto-resizes via ResizeObserver, or set fixed dimensions
10
+ - **Zoom & Pan** — scroll to zoom, drag to pan, with configurable controls
11
+ - **Tooltips** — hover to see data values, with custom formatters and renderers
12
+ - **Multi-series** — plot multiple data lines on one chart
13
+ - **Customizable** — colors, axes, grid lines, fonts, margins, and more
14
+ - **Animation** — line drawing animation on mount
15
+ - **Tree-shakeable** — `sideEffects: false`, ESM + CJS dual output
16
+ - **Accessible** — `role="img"` with configurable `aria-label`
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install chartifypdf
22
+ ```
23
+
24
+ **Peer dependencies:** React >= 19.0.0, React DOM >= 19.0.0
25
+
26
+ ## Quick Start
27
+
28
+ ```tsx
29
+ import { LineChart } from "chartifypdf";
30
+
31
+ const data = [
32
+ {
33
+ id: "revenue",
34
+ name: "Revenue",
35
+ data: [
36
+ { x: 1, y: 100 },
37
+ { x: 2, y: 200 },
38
+ { x: 3, y: 150 },
39
+ { x: 4, y: 300 },
40
+ { x: 5, y: 280 },
41
+ ],
42
+ },
43
+ ];
44
+
45
+ function App() {
46
+ return <LineChart data={data} width={600} height={400} />;
47
+ }
48
+ ```
49
+
50
+ ## Props Reference
51
+
52
+ ### `LineChartProps`
53
+
54
+ | Prop | Type | Default | Description |
55
+ |------|------|---------|-------------|
56
+ | `data` | `DataSeries[]` | *required* | Array of data series to plot |
57
+ | `width` | `number` | auto (responsive) | Fixed width in pixels |
58
+ | `height` | `number` | `400` | Fixed height in pixels |
59
+ | `margin` | `Partial<ChartMargin>` | `{ top: 20, right: 20, bottom: 50, left: 60 }` | Chart margins |
60
+ | `xAxis` | `AxisConfig` | — | X-axis configuration |
61
+ | `yAxis` | `AxisConfig` | — | Y-axis configuration |
62
+ | `tooltip` | `TooltipConfig` | `{ enabled: true }` | Tooltip configuration |
63
+ | `zoom` | `ZoomConfig` | `{ enabled: false }` | Zoom & pan configuration |
64
+ | `style` | `ChartStyle` | — | Visual style overrides |
65
+ | `animation` | `AnimationConfig` | — | Line drawing animation |
66
+ | `colorPalette` | `string[]` | 10-color palette | Custom color palette |
67
+ | `className` | `string` | — | CSS class for container div |
68
+ | `onPointClick` | `(point, series) => void` | — | Click handler for data points |
69
+ | `ariaLabel` | `string` | `"Line chart"` | Accessibility label |
70
+
71
+ ### `DataSeries`
72
+
73
+ | Field | Type | Default | Description |
74
+ |-------|------|---------|-------------|
75
+ | `id` | `string` | *required* | Unique identifier |
76
+ | `name` | `string` | *required* | Display name (shown in tooltip) |
77
+ | `data` | `DataPoint[]` | *required* | Array of `{ x, y, label? }` |
78
+ | `color` | `string` | auto | Line color (from palette if not set) |
79
+ | `strokeWidth` | `number` | `2` | Line thickness |
80
+ | `strokeDasharray` | `string` | — | SVG dash pattern (e.g. `"5 3"`) |
81
+ | `showDots` | `boolean` | `false` | Show circles at data points |
82
+ | `dotRadius` | `number` | `3.5` | Dot circle radius |
83
+
84
+ ### `AxisConfig`
85
+
86
+ | Field | Type | Default | Description |
87
+ |-------|------|---------|-------------|
88
+ | `label` | `string` | — | Axis label text |
89
+ | `tickCount` | `number` | `6` | Approximate number of ticks |
90
+ | `tickFormat` | `(value) => string` | auto | Custom tick label formatter |
91
+ | `min` | `number` | auto | Override axis minimum |
92
+ | `max` | `number` | auto | Override axis maximum |
93
+ | `gridLines` | `boolean` | Y: `true`, X: `false` | Show grid lines |
94
+ | `gridLineColor` | `string` | `"#e0e0e0"` | Grid line color |
95
+
96
+ ### `TooltipConfig`
97
+
98
+ | Field | Type | Default | Description |
99
+ |-------|------|---------|-------------|
100
+ | `enabled` | `boolean` | `true` | Enable/disable tooltip |
101
+ | `backgroundColor` | `string` | `"rgba(0,0,0,0.8)"` | Tooltip background |
102
+ | `textColor` | `string` | `"#fff"` | Tooltip text color |
103
+ | `formatter` | `(point, series) => string` | auto | Custom text formatter |
104
+ | `renderCustom` | `(point, series) => ReactNode` | — | Full custom render |
105
+
106
+ ### `ZoomConfig`
107
+
108
+ | Field | Type | Default | Description |
109
+ |-------|------|---------|-------------|
110
+ | `enabled` | `boolean` | `false` | Enable zoom & pan |
111
+ | `minScale` | `number` | `1` | Minimum zoom level |
112
+ | `maxScale` | `number` | `10` | Maximum zoom level |
113
+ | `step` | `number` | `0.5` | Zoom step per action |
114
+ | `showControls` | `boolean` | `true` | Show +/-/reset buttons |
115
+ | `controlsPosition` | `string` | `"top-right"` | `"top-left"` \| `"top-right"` \| `"bottom-left"` \| `"bottom-right"` |
116
+ | `enableWheel` | `boolean` | `true` | Zoom with mouse wheel |
117
+ | `enablePan` | `boolean` | `true` | Pan by dragging |
118
+
119
+ ### `ChartStyle`
120
+
121
+ | Field | Type | Default | Description |
122
+ |-------|------|---------|-------------|
123
+ | `backgroundColor` | `string` | — | Chart background color |
124
+ | `fontFamily` | `string` | `"sans-serif"` | Font for labels/ticks |
125
+ | `fontSize` | `number` | `11` | Base font size (px) |
126
+ | `axisColor` | `string` | `"#333"` | Axis line color |
127
+ | `tickColor` | `string` | `"#666"` | Tick label color |
128
+
129
+ ### `AnimationConfig`
130
+
131
+ | Field | Type | Default | Description |
132
+ |-------|------|---------|-------------|
133
+ | `enabled` | `boolean` | `false` | Enable line draw animation |
134
+ | `duration` | `number` | `800` | Animation duration (ms) |
135
+ | `easing` | `string` | `"ease-in-out"` | CSS easing function |
136
+
137
+ ## Examples
138
+
139
+ ### Responsive Chart (auto width)
140
+
141
+ ```tsx
142
+ <LineChart data={data} height={300} />
143
+ ```
144
+
145
+ The chart fills its parent container's width and updates on resize.
146
+
147
+ ### Multi-series with Custom Colors
148
+
149
+ ```tsx
150
+ const data = [
151
+ {
152
+ id: "sales",
153
+ name: "Sales",
154
+ data: [{ x: 1, y: 30 }, { x: 2, y: 50 }, { x: 3, y: 45 }],
155
+ color: "#ff6384",
156
+ showDots: true,
157
+ },
158
+ {
159
+ id: "expenses",
160
+ name: "Expenses",
161
+ data: [{ x: 1, y: 20 }, { x: 2, y: 35 }, { x: 3, y: 40 }],
162
+ color: "#36a2eb",
163
+ strokeDasharray: "5 3",
164
+ },
165
+ ];
166
+
167
+ <LineChart data={data} width={800} height={400} />
168
+ ```
169
+
170
+ ### With Zoom and Custom Axes
171
+
172
+ ```tsx
173
+ <LineChart
174
+ data={data}
175
+ width={800}
176
+ height={400}
177
+ xAxis={{ label: "Month", tickCount: 12, gridLines: true }}
178
+ yAxis={{ label: "Revenue ($)", tickFormat: (v) => `$${v}` }}
179
+ zoom={{ enabled: true, maxScale: 5, controlsPosition: "top-left" }}
180
+ />
181
+ ```
182
+
183
+ ### Custom Tooltip
184
+
185
+ ```tsx
186
+ <LineChart
187
+ data={data}
188
+ tooltip={{
189
+ formatter: (point, series) =>
190
+ `${series.name}: ${point.y.toFixed(2)} at x=${point.x}`,
191
+ }}
192
+ />
193
+ ```
194
+
195
+ ### Custom Tooltip with React Node
196
+
197
+ ```tsx
198
+ <LineChart
199
+ data={data}
200
+ tooltip={{
201
+ renderCustom: (point, series) => (
202
+ <div>
203
+ <strong>{series.name}</strong>
204
+ <br />
205
+ X: {point.x} | Y: {point.y}
206
+ </div>
207
+ ),
208
+ }}
209
+ />
210
+ ```
211
+
212
+ ### With Animation
213
+
214
+ ```tsx
215
+ <LineChart
216
+ data={data}
217
+ animation={{ enabled: true, duration: 1200, easing: "ease-out" }}
218
+ />
219
+ ```
220
+
221
+ ### Point Click Handler
222
+
223
+ ```tsx
224
+ <LineChart
225
+ data={data}
226
+ onPointClick={(point, series) => {
227
+ console.log(`Clicked ${series.name}: (${point.x}, ${point.y})`);
228
+ }}
229
+ />
230
+ ```
231
+
232
+ Note: Set `showDots: true` on the series to make points clickable.
233
+
234
+ ## Exported Hooks
235
+
236
+ For advanced usage, you can import the hooks directly:
237
+
238
+ ```tsx
239
+ import { useChartDimensions, useScales, useZoom, useTooltip } from "chartifypdf";
240
+ ```
241
+
242
+ | Hook | Purpose |
243
+ |------|---------|
244
+ | `useChartDimensions` | Responsive container measurement via ResizeObserver |
245
+ | `useScales` | Compute linear scales, ticks, and domains from data |
246
+ | `useZoom` | Manage zoom/pan state and event handlers |
247
+ | `useTooltip` | Track mouse position and find nearest data point |
248
+
249
+ ## Browser Support
250
+
251
+ Works in all modern browsers that support:
252
+ - SVG
253
+ - ResizeObserver
254
+ - CSS transitions (for animation)
255
+
256
+ ## License
257
+
258
+ MIT
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import type { AxisConfig, ChartStyle } from "../types/chart.types";
3
+ interface AxesProps {
4
+ xTicks: number[];
5
+ yTicks: number[];
6
+ xScale: (v: number) => number;
7
+ yScale: (v: number) => number;
8
+ plotWidth: number;
9
+ plotHeight: number;
10
+ xAxisConfig?: AxisConfig;
11
+ yAxisConfig?: AxisConfig;
12
+ style?: ChartStyle;
13
+ }
14
+ export declare const Axes: React.FC<AxesProps>;
15
+ export {};
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ interface GridLinesProps {
3
+ xTicks: number[];
4
+ yTicks: number[];
5
+ xScale: (v: number) => number;
6
+ yScale: (v: number) => number;
7
+ plotWidth: number;
8
+ plotHeight: number;
9
+ showXGrid: boolean;
10
+ showYGrid: boolean;
11
+ xGridColor?: string;
12
+ yGridColor?: string;
13
+ }
14
+ export declare const GridLines: React.FC<GridLinesProps>;
15
+ export {};
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ import type { LineChartProps } from "../types/chart.types";
3
+ export declare const LineChart: React.FC<LineChartProps>;
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import type { DataSeries, DataPoint, AnimationConfig } from "../types/chart.types";
3
+ interface LinePathProps {
4
+ series: DataSeries;
5
+ xScale: (v: number) => number;
6
+ yScale: (v: number) => number;
7
+ color: string;
8
+ animation?: AnimationConfig;
9
+ onPointClick?: (point: DataPoint, series: DataSeries) => void;
10
+ }
11
+ export declare const LinePath: React.FC<LinePathProps>;
12
+ export {};
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import type { DataPoint, DataSeries, TooltipConfig } from "../types/chart.types";
3
+ interface TooltipProps {
4
+ visible: boolean;
5
+ x: number;
6
+ y: number;
7
+ point: DataPoint | null;
8
+ series: DataSeries | null;
9
+ plotHeight: number;
10
+ plotWidth: number;
11
+ config?: TooltipConfig;
12
+ seriesColor: string;
13
+ }
14
+ export declare const Tooltip: React.FC<TooltipProps>;
15
+ export {};
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import type { ZoomConfig } from "../types/chart.types";
3
+ interface ZoomControlsProps {
4
+ config?: ZoomConfig;
5
+ svgWidth: number;
6
+ svgHeight: number;
7
+ margin: {
8
+ top: number;
9
+ right: number;
10
+ bottom: number;
11
+ left: number;
12
+ };
13
+ scale: number;
14
+ onZoomIn: () => void;
15
+ onZoomOut: () => void;
16
+ onReset: () => void;
17
+ }
18
+ export declare const ZoomControls: React.FC<ZoomControlsProps>;
19
+ export {};
@@ -0,0 +1,5 @@
1
+ import { type RefObject } from "react";
2
+ export declare function useChartDimensions(containerRef: RefObject<HTMLDivElement | null>, providedWidth?: number, providedHeight?: number): {
3
+ width: number;
4
+ height: number;
5
+ };
@@ -0,0 +1,10 @@
1
+ import type { DataSeries, AxisConfig } from "../types/chart.types";
2
+ export interface ScalesResult {
3
+ xScale: (value: number) => number;
4
+ yScale: (value: number) => number;
5
+ xTicks: number[];
6
+ yTicks: number[];
7
+ xDomain: [number, number];
8
+ yDomain: [number, number];
9
+ }
10
+ export declare function useScales(data: DataSeries[], plotWidth: number, plotHeight: number, xAxisConfig?: AxisConfig, yAxisConfig?: AxisConfig): ScalesResult;
@@ -0,0 +1,12 @@
1
+ import { type RefObject } from "react";
2
+ import type { DataSeries, DataPoint, TooltipConfig } from "../types/chart.types";
3
+ export interface TooltipState {
4
+ tooltipVisible: boolean;
5
+ tooltipX: number;
6
+ tooltipY: number;
7
+ activePoint: DataPoint | null;
8
+ activeSeries: DataSeries | null;
9
+ handleMouseMove: (e: React.MouseEvent) => void;
10
+ handleMouseLeave: () => void;
11
+ }
12
+ export declare function useTooltip(svgRef: RefObject<SVGSVGElement | null>, data: DataSeries[], xScale: (v: number) => number, yScale: (v: number) => number, marginLeft: number, marginTop: number, plotWidth: number, _config?: TooltipConfig): TooltipState;
@@ -0,0 +1,15 @@
1
+ import type { ZoomConfig } from "../types/chart.types";
2
+ export interface ZoomState {
3
+ scale: number;
4
+ offsetX: number;
5
+ offsetY: number;
6
+ isPanning: boolean;
7
+ zoomIn: () => void;
8
+ zoomOut: () => void;
9
+ resetZoom: () => void;
10
+ handleWheel: (e: React.WheelEvent) => void;
11
+ handlePanStart: (e: React.MouseEvent) => void;
12
+ handlePanMove: (e: React.MouseEvent) => void;
13
+ handlePanEnd: () => void;
14
+ }
15
+ export declare function useZoom(config: ZoomConfig | undefined, plotWidth: number, plotHeight: number): ZoomState;
@@ -0,0 +1,123 @@
1
+ import React$1, { ReactNode, RefObject } from 'react';
2
+
3
+ interface DataPoint {
4
+ x: number;
5
+ y: number;
6
+ label?: string;
7
+ }
8
+ interface DataSeries {
9
+ id: string;
10
+ name: string;
11
+ data: DataPoint[];
12
+ color?: string;
13
+ strokeWidth?: number;
14
+ strokeDasharray?: string;
15
+ showDots?: boolean;
16
+ dotRadius?: number;
17
+ }
18
+ interface AxisConfig {
19
+ label?: string;
20
+ tickCount?: number;
21
+ tickFormat?: (value: number) => string;
22
+ min?: number;
23
+ max?: number;
24
+ gridLines?: boolean;
25
+ gridLineColor?: string;
26
+ }
27
+ interface TooltipConfig {
28
+ enabled?: boolean;
29
+ backgroundColor?: string;
30
+ textColor?: string;
31
+ formatter?: (point: DataPoint, series: DataSeries) => string;
32
+ renderCustom?: (point: DataPoint, series: DataSeries) => ReactNode;
33
+ }
34
+ interface ZoomConfig {
35
+ enabled?: boolean;
36
+ minScale?: number;
37
+ maxScale?: number;
38
+ step?: number;
39
+ showControls?: boolean;
40
+ controlsPosition?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
41
+ enableWheel?: boolean;
42
+ enablePan?: boolean;
43
+ }
44
+ interface ChartMargin {
45
+ top: number;
46
+ right: number;
47
+ bottom: number;
48
+ left: number;
49
+ }
50
+ interface ChartStyle {
51
+ backgroundColor?: string;
52
+ fontFamily?: string;
53
+ fontSize?: number;
54
+ axisColor?: string;
55
+ tickColor?: string;
56
+ }
57
+ interface AnimationConfig {
58
+ enabled?: boolean;
59
+ duration?: number;
60
+ easing?: string;
61
+ }
62
+ interface LineChartProps {
63
+ data: DataSeries[];
64
+ width?: number;
65
+ height?: number;
66
+ margin?: Partial<ChartMargin>;
67
+ xAxis?: AxisConfig;
68
+ yAxis?: AxisConfig;
69
+ tooltip?: TooltipConfig;
70
+ zoom?: ZoomConfig;
71
+ style?: ChartStyle;
72
+ animation?: AnimationConfig;
73
+ colorPalette?: string[];
74
+ className?: string;
75
+ onPointClick?: (point: DataPoint, series: DataSeries) => void;
76
+ ariaLabel?: string;
77
+ }
78
+
79
+ declare const LineChart: React$1.FC<LineChartProps>;
80
+
81
+ declare function useChartDimensions(containerRef: RefObject<HTMLDivElement | null>, providedWidth?: number, providedHeight?: number): {
82
+ width: number;
83
+ height: number;
84
+ };
85
+
86
+ interface ScalesResult {
87
+ xScale: (value: number) => number;
88
+ yScale: (value: number) => number;
89
+ xTicks: number[];
90
+ yTicks: number[];
91
+ xDomain: [number, number];
92
+ yDomain: [number, number];
93
+ }
94
+ declare function useScales(data: DataSeries[], plotWidth: number, plotHeight: number, xAxisConfig?: AxisConfig, yAxisConfig?: AxisConfig): ScalesResult;
95
+
96
+ interface ZoomState {
97
+ scale: number;
98
+ offsetX: number;
99
+ offsetY: number;
100
+ isPanning: boolean;
101
+ zoomIn: () => void;
102
+ zoomOut: () => void;
103
+ resetZoom: () => void;
104
+ handleWheel: (e: React.WheelEvent) => void;
105
+ handlePanStart: (e: React.MouseEvent) => void;
106
+ handlePanMove: (e: React.MouseEvent) => void;
107
+ handlePanEnd: () => void;
108
+ }
109
+ declare function useZoom(config: ZoomConfig | undefined, plotWidth: number, plotHeight: number): ZoomState;
110
+
111
+ interface TooltipState {
112
+ tooltipVisible: boolean;
113
+ tooltipX: number;
114
+ tooltipY: number;
115
+ activePoint: DataPoint | null;
116
+ activeSeries: DataSeries | null;
117
+ handleMouseMove: (e: React.MouseEvent) => void;
118
+ handleMouseLeave: () => void;
119
+ }
120
+ declare function useTooltip(svgRef: RefObject<SVGSVGElement | null>, data: DataSeries[], xScale: (v: number) => number, yScale: (v: number) => number, marginLeft: number, marginTop: number, plotWidth: number, _config?: TooltipConfig): TooltipState;
121
+
122
+ export { LineChart, useChartDimensions, useScales, useTooltip, useZoom };
123
+ export type { AnimationConfig, AxisConfig, ChartMargin, ChartStyle, DataPoint, DataSeries, LineChartProps, TooltipConfig, ZoomConfig };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("react/jsx-runtime"),t=require("react");const o=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"];function n(e,t){const n=t??o;return n[e%n.length]}function i(e,o,n){const[i,s]=t.useState({width:o??0,height:n??0}),r=void 0!==o&&void 0!==n;return t.useLayoutEffect(()=>{if(r)return void s({width:o,height:n});const t=e.current;if(!t)return;const i=()=>{const e=t.getBoundingClientRect();s({width:e.width,height:e.height})};i();const a=new ResizeObserver(()=>{i()});return a.observe(t),()=>a.disconnect()},[e,r,o,n]),i}function s(e,t,o){return Math.min(Math.max(e,t),o)}function r(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 a(e,t,o){if(e===t){const o=0===e?1:.1*Math.abs(e);e-=o,t+=o}const n=r(t-e,!1),i=r(n/(o-1),!0);return{niceMin:Math.floor(e/i)*i,niceMax:Math.ceil(t/i)*i,tickStep:i}}function l(e,t,o,n){return i=>{if(t===e)return(o+n)/2;const s=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,s)}}function c(e,t,o){const n=[];for(let i=e;i<=t+.5*o;i+=o)n.push(parseFloat(i.toPrecision(12)));return n}function d(e,o,n,i,s){return t.useMemo(()=>{let t=1/0,r=-1/0,d=1/0,h=-1/0;for(const o of e)for(const e of o.data)e.x<t&&(t=e.x),e.x>r&&(r=e.x),e.y<d&&(d=e.y),e.y>h&&(h=e.y);isFinite(t)||(t=0,r=1,d=0,h=1),void 0!==i?.min&&(t=i.min),void 0!==i?.max&&(r=i.max),void 0!==s?.min&&(d=s.min),void 0!==s?.max&&(h=s.max);const u=i?.tickCount??6,f=s?.tickCount??6,x=a(t,r,u),m=a(d,h,f);return{xScale:l(x.niceMin,x.niceMax,0,o),yScale:l(m.niceMin,m.niceMax,n,0),xTicks:c(x.niceMin,x.niceMax,x.tickStep),yTicks:c(m.niceMin,m.niceMax,m.tickStep),xDomain:[x.niceMin,x.niceMax],yDomain:[m.niceMin,m.niceMax]}},[e,o,n,i,s])}function h(e,o,n){const i=e?.enabled??!1,r=e?.minScale??1,a=e?.maxScale??10,l=e?.step??.5,c=e?.enableWheel??!0,d=e?.enablePan??!0,[h,u]=t.useState(1),[f,x]=t.useState(0),[m,y]=t.useState(0),[g,p]=t.useState(!1),b=t.useRef({x:0,y:0,offsetX:0,offsetY:0}),k=t.useCallback((e,t,i)=>{const r=n*(i-1);return{x:s(e,-(o*(i-1)),0),y:s(t,-r,0)}},[o,n]),C=t.useCallback(()=>{i&&u(e=>{const t=s(e+l,r,a);return x(e=>k(e-o*l/2,0,t).x),y(e=>k(0,e-n*l/2,t).y),t})},[i,l,r,a,o,n,k]),S=t.useCallback(()=>{i&&u(e=>{const t=s(e-l,r,a);return x(e=>k(e+o*l/2,0,t).x),y(e=>k(0,e+n*l/2,t).y),t})},[i,l,r,a,o,n,k]),v=t.useCallback(()=>{u(1),x(0),y(0)},[]),M=t.useCallback(e=>{if(!i||!c)return;e.preventDefault();const t=e.currentTarget.getBoundingClientRect(),o=e.clientX-t.left,n=e.clientY-t.top,d=e.deltaY<0?l:-l;u(e=>{const t=s(e+d,r,a),i=t/e;return x(e=>k(o-i*(o-e),0,t).x),y(e=>k(0,n-i*(n-e),t).y),t})},[i,c,l,r,a,k]),j=t.useCallback(e=>{!i||!d||h<=1||0===e.button&&(p(!0),b.current={x:e.clientX,y:e.clientY,offsetX:f,offsetY:m})},[i,d,h,f,m]),w=t.useCallback(e=>{if(!g)return;const t=e.clientX-b.current.x,o=e.clientY-b.current.y,n=k(b.current.offsetX+t,b.current.offsetY+o,h);x(n.x),y(n.y)},[g,h,k]),$=t.useCallback(()=>{p(!1)},[]);return{scale:h,offsetX:f,offsetY:m,isPanning:g,zoomIn:C,zoomOut:S,resetZoom:v,handleWheel:M,handlePanStart:j,handlePanMove:w,handlePanEnd:$}}function u(e,t,o){if(0===e.length)return null;let n=0,i=e.length-1;for(;n<i;){const s=Math.floor((n+i)/2);o(e[s].x)<t?n=s+1:i=s}let s=e[n],r=Math.abs(o(s.x)-t);if(n>0){const i=Math.abs(o(e[n-1].x)-t);i<r&&(r=i,s=e[n-1])}return s}function f(e,o,n,i,s,r,a,l){const[c,d]=t.useState(!1),[h,f]=t.useState(0),[x,m]=t.useState(0),[y,g]=t.useState(null),[p,b]=t.useState(null);return{tooltipVisible:c,tooltipX:h,tooltipY:x,activePoint:y,activeSeries:p,handleMouseMove:t.useCallback(t=>{const r=e.current;if(!r)return;const l=r.getBoundingClientRect(),c=t.clientX-l.left-s;if(c<0||c>a)return void d(!1);let h=null,x=null,y=1/0;for(const e of o){const t=u(e.data,c,n);if(!t)continue;const o=Math.abs(n(t.x)-c);o<y&&(y=o,h=t,x=e)}h&&x&&(f(n(h.x)),m(i(h.y)),g(h),b(x),d(!0))},[e,o,n,i,s,a]),handleMouseLeave:t.useCallback(()=>{d(!1),g(null),b(null)},[])}}const x=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),m=t.memo(({xTicks:t,yTicks:o,xScale:n,yScale:i,plotWidth:s,plotHeight:r,xAxisConfig:a,yAxisConfig:l,style:c})=>{const d=c?.axisColor??"#333",h=c?.tickColor??"#666",u=c?.fontFamily??"sans-serif",f=c?.fontSize??11,m=a?.tickFormat??x,y=l?.tickFormat??x;return e.jsxs("g",{className:"chartifypdf-axes",children:[e.jsx("line",{x1:0,y1:r,x2:s,y2:r,stroke:d,strokeWidth:1}),t.map(t=>{const o=n(t);return e.jsxs("g",{children:[e.jsx("line",{x1:o,y1:r,x2:o,y2:r+6,stroke:d,strokeWidth:1}),e.jsx("text",{x:o,y:r+18,textAnchor:"middle",fill:h,fontFamily:u,fontSize:f,children:m(t)})]},`x-tick-${t}`)}),a?.label&&e.jsx("text",{x:s/2,y:r+38,textAnchor:"middle",fill:d,fontFamily:u,fontSize:f+1,fontWeight:"bold",children:a.label}),e.jsx("line",{x1:0,y1:0,x2:0,y2:r,stroke:d,strokeWidth:1}),o.map(t=>{const o=i(t);return e.jsxs("g",{children:[e.jsx("line",{x1:-6,y1:o,x2:0,y2:o,stroke:d,strokeWidth:1}),e.jsx("text",{x:-10,y:o,textAnchor:"end",dominantBaseline:"middle",fill:h,fontFamily:u,fontSize:f,children:y(t)})]},`y-tick-${t}`)}),l?.label&&e.jsx("text",{x:0,y:0,textAnchor:"middle",fill:d,fontFamily:u,fontSize:f+1,fontWeight:"bold",transform:`translate(-40, ${r/2}) rotate(-90)`,children:l.label})]})});m.displayName="Axes";const y=t.memo(({xTicks:t,yTicks:o,xScale:n,yScale:i,plotWidth:s,plotHeight:r,showXGrid:a,showYGrid:l,xGridColor:c="#e0e0e0",yGridColor:d="#e0e0e0"})=>e.jsxs("g",{className:"chartifypdf-grid",children:[l&&o.map(t=>{const o=i(t);return e.jsx("line",{x1:0,y1:o,x2:s,y2:o,stroke:d,strokeDasharray:"4 4",strokeOpacity:.5},`y-grid-${t}`)}),a&&t.map(t=>{const o=n(t);return e.jsx("line",{x1:o,y1:0,x2:o,y2:r,stroke:c,strokeDasharray:"4 4",strokeOpacity:.5},`x-grid-${t}`)})]}));y.displayName="GridLines";const g=t.memo(({series:o,xScale:n,yScale:i,color:s,animation:r,onPointClick:a})=>{const l=t.useRef(null),[c,d]=t.useState(0),[h,u]=t.useState(!r?.enabled),f=o.strokeWidth??2,x=o.showDots??!1,m=o.dotRadius??3.5,y=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:n(e.x),y:i(e.y)})));t.useEffect(()=>{if(r?.enabled&&l.current){const e=l.current.getTotalLength();d(e),u(!1);const t=setTimeout(()=>{u(!0)},r.duration??800);return()=>clearTimeout(t)}},[y,r?.enabled,r?.duration]);const g=r?.enabled&&c>0?{strokeDasharray:c,strokeDashoffset:h?0:c,transition:`stroke-dashoffset ${r.duration??800}ms ${r.easing??"ease-in-out"}`}:{};return e.jsxs("g",{className:"chartifypdf-line-path",children:[e.jsx("path",{ref:l,d:y,fill:"none",stroke:s,strokeWidth:f,strokeDasharray:o.strokeDasharray,strokeLinecap:"round",strokeLinejoin:"round",style:g}),x&&o.data.map((t,r)=>e.jsx("circle",{cx:n(t.x),cy:i(t.y),r:m,fill:s,stroke:"#fff",strokeWidth:1.5,style:{cursor:a?"pointer":"default"},onClick:a?()=>a(t,o):void 0},`dot-${o.id}-${r}`))]})});g.displayName="LinePath";const p=t.memo(({visible:t,x:o,y:n,point:i,series:s,plotHeight:r,plotWidth:a,config:l,seriesColor:c})=>{if(!t||!i||!s)return null;const d=l?.backgroundColor??"rgba(0, 0, 0, 0.8)",h=l?.textColor??"#fff";let u;if(l?.renderCustom)u=l.renderCustom(i,s);else{u=l?.formatter?l.formatter(i,s):`${s.name}: (${i.x}, ${i.y})`}let f=o+12,x=n-40-8;return f+140>a&&(f=o-140-12),x<0&&(x=n+12),e.jsxs("g",{className:"chartifypdf-tooltip",pointerEvents:"none",children:[e.jsx("line",{x1:o,y1:0,x2:o,y2:r,stroke:"#999",strokeWidth:1,strokeDasharray:"4 4",opacity:.6}),e.jsx("circle",{cx:o,cy:n,r:5,fill:c,stroke:"#fff",strokeWidth:2}),e.jsx("foreignObject",{x:f,y:x,width:140,height:60,overflow:"visible",children:e.jsx("div",{style:{backgroundColor:d,color:h,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})})]})});p.displayName="Tooltip";const b=t.memo(({config:t,svgWidth:o,svgHeight:n,margin:i,scale:s,onZoomIn:r,onZoomOut:a,onReset:l})=>{if(!t?.enabled||!1===t.showControls)return null;const c=t.controlsPosition??"top-right",d=24;let h,u;switch(c){case"top-left":h=i.left+8,u=i.top+8;break;case"bottom-left":h=i.left+8,u=n-i.bottom-24-8;break;case"bottom-right":h=o-i.right-80-8,u=n-i.bottom-24-8;break;default:h=o-i.right-80-8,u=i.top+8}const f=s>1?[{label:"+",onClick:r},{label:"−",onClick:a},{label:"↺",onClick:l}]:[{label:"+",onClick:r},{label:"−",onClick:a}];return e.jsx("g",{className:"chartifypdf-zoom-controls",transform:`translate(${h}, ${u})`,children:f.map((t,o)=>e.jsxs("g",{transform:`translate(${28*o}, 0)`,onClick:t.onClick,style:{cursor:"pointer"},children:[e.jsx("rect",{width:d,height:d,rx:4,fill:"rgba(255,255,255,0.9)",stroke:"#ccc",strokeWidth:1}),e.jsx("text",{x:12,y:12,textAnchor:"middle",dominantBaseline:"central",fontSize:14,fontFamily:"sans-serif",fill:"#333",fontWeight:"bold",children:t.label})]},t.label))})});b.displayName="ZoomControls";const k={top:20,right:20,bottom:50,left:60},C=({data:o,width:s,height:r,margin:a,xAxis:l,yAxis:c,tooltip:u,zoom:x,style:C,animation:S,colorPalette:v,className:M,onPointClick:j,ariaLabel:w})=>{const $=t.useRef(null),W=t.useRef(null),P=t.useId(),L={...k,...a},T=i($,s,r??400),{width:N,height:z}=T,F=Math.max(0,N-L.left-L.right),D=Math.max(0,z-L.top-L.bottom),{xScale:X,yScale:Y,xTicks:A,yTicks:R}=d(o,F,D,l,c),G=h(x,F,D),H=!1!==u?.enabled,O=f(W,o,X,Y,L.left,L.top,F),Z=l?.gridLines??!1,I=c?.gridLines??!0;return 0===N||0===z?e.jsx("div",{ref:$,className:M,style:{width:s??"100%",height:r??400,backgroundColor:C?.backgroundColor}}):e.jsx("div",{ref:$,className:M,style:{width:s??"100%",height:r??400,backgroundColor:C?.backgroundColor},children:e.jsxs("svg",{ref:W,width:N,height:z,role:"img","aria-label":w??"Line chart",onMouseMove:H&&!G.isPanning?O.handleMouseMove:void 0,onMouseLeave:H?O.handleMouseLeave:void 0,onWheel:x?.enabled?G.handleWheel:void 0,onMouseDown:x?.enabled?G.handlePanStart:void 0,onMouseUp:x?.enabled?G.handlePanEnd:void 0,style:{userSelect:G.isPanning?"none":void 0,cursor:G.isPanning?"grabbing":x?.enabled&&G.scale>1?"grab":void 0},children:[e.jsx("defs",{children:e.jsx("clipPath",{id:P,children:e.jsx("rect",{width:F,height:D})})}),e.jsxs("g",{transform:`translate(${L.left}, ${L.top})`,children:[e.jsx(m,{xTicks:A,yTicks:R,xScale:X,yScale:Y,plotWidth:F,plotHeight:D,xAxisConfig:l,yAxisConfig:c,style:C}),e.jsx(y,{xTicks:A,yTicks:R,xScale:X,yScale:Y,plotWidth:F,plotHeight:D,showXGrid:Z,showYGrid:I,xGridColor:l?.gridLineColor,yGridColor:c?.gridLineColor}),e.jsx("g",{clipPath:`url(#${P})`,children:e.jsx("g",{transform:`translate(${G.offsetX}, ${G.offsetY}) scale(${G.scale})`,onMouseMove:x?.enabled?G.handlePanMove:void 0,children:o.map((t,o)=>e.jsx(g,{series:t,xScale:X,yScale:Y,color:t.color??n(o,v),animation:S,onPointClick:j},t.id))})}),H&&e.jsx(p,{visible:O.tooltipVisible,x:O.tooltipX,y:O.tooltipY,point:O.activePoint,series:O.activeSeries,plotHeight:D,plotWidth:F,config:u,seriesColor:O.activeSeries?.color??n(o.findIndex(e=>e.id===O.activeSeries?.id),v)})]}),e.jsx(b,{config:x,svgWidth:N,svgHeight:z,margin:L,scale:G.scale,onZoomIn:G.zoomIn,onZoomOut:G.zoomOut,onReset:G.resetZoom})]})})};C.displayName="LineChart",exports.LineChart=C,exports.useChartDimensions=i,exports.useScales=d,exports.useTooltip=f,exports.useZoom=h;
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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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"}
package/dist/index.mjs ADDED
@@ -0,0 +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};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","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","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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"}
@@ -0,0 +1,76 @@
1
+ import type { ReactNode } from "react";
2
+ export interface DataPoint {
3
+ x: number;
4
+ y: number;
5
+ label?: string;
6
+ }
7
+ export interface DataSeries {
8
+ id: string;
9
+ name: string;
10
+ data: DataPoint[];
11
+ color?: string;
12
+ strokeWidth?: number;
13
+ strokeDasharray?: string;
14
+ showDots?: boolean;
15
+ dotRadius?: number;
16
+ }
17
+ export interface AxisConfig {
18
+ label?: string;
19
+ tickCount?: number;
20
+ tickFormat?: (value: number) => string;
21
+ min?: number;
22
+ max?: number;
23
+ gridLines?: boolean;
24
+ gridLineColor?: string;
25
+ }
26
+ export interface TooltipConfig {
27
+ enabled?: boolean;
28
+ backgroundColor?: string;
29
+ textColor?: string;
30
+ formatter?: (point: DataPoint, series: DataSeries) => string;
31
+ renderCustom?: (point: DataPoint, series: DataSeries) => ReactNode;
32
+ }
33
+ export interface ZoomConfig {
34
+ enabled?: boolean;
35
+ minScale?: number;
36
+ maxScale?: number;
37
+ step?: number;
38
+ showControls?: boolean;
39
+ controlsPosition?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
40
+ enableWheel?: boolean;
41
+ enablePan?: boolean;
42
+ }
43
+ export interface ChartMargin {
44
+ top: number;
45
+ right: number;
46
+ bottom: number;
47
+ left: number;
48
+ }
49
+ export interface ChartStyle {
50
+ backgroundColor?: string;
51
+ fontFamily?: string;
52
+ fontSize?: number;
53
+ axisColor?: string;
54
+ tickColor?: string;
55
+ }
56
+ export interface AnimationConfig {
57
+ enabled?: boolean;
58
+ duration?: number;
59
+ easing?: string;
60
+ }
61
+ export interface LineChartProps {
62
+ data: DataSeries[];
63
+ width?: number;
64
+ height?: number;
65
+ margin?: Partial<ChartMargin>;
66
+ xAxis?: AxisConfig;
67
+ yAxis?: AxisConfig;
68
+ tooltip?: TooltipConfig;
69
+ zoom?: ZoomConfig;
70
+ style?: ChartStyle;
71
+ animation?: AnimationConfig;
72
+ colorPalette?: string[];
73
+ className?: string;
74
+ onPointClick?: (point: DataPoint, series: DataSeries) => void;
75
+ ariaLabel?: string;
76
+ }
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_PALETTE: string[];
2
+ export declare function getSeriesColor(index: number, palette?: string[]): string;
@@ -0,0 +1,11 @@
1
+ export declare function clamp(value: number, min: number, max: number): number;
2
+ export declare function lerp(a: number, b: number, t: number): number;
3
+ export declare function inverseLerp(a: number, b: number, value: number): number;
4
+ export declare function niceNumber(range: number, round: boolean): number;
5
+ export declare function niceRange(min: number, max: number, tickCount: number): {
6
+ niceMin: number;
7
+ niceMax: number;
8
+ tickStep: number;
9
+ };
10
+ export declare function linearScale(domainMin: number, domainMax: number, rangeMin: number, rangeMax: number): (value: number) => number;
11
+ export declare function generateTicks(niceMin: number, niceMax: number, tickStep: number): number[];
@@ -0,0 +1,4 @@
1
+ export declare function buildLinePath(points: {
2
+ x: number;
3
+ y: number;
4
+ }[]): string;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "chartifypdf",
3
+ "version": "0.1.0",
4
+ "description": "A zero-dependency React line chart component using pure SVG. Supports zoom, tooltip, responsive sizing, and customizable styling.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "sideEffects": false,
16
+ "files": [
17
+ "dist/",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "rollup -c rollup.config.mjs",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "peerDependencies": {
26
+ "react": ">=19.0.0",
27
+ "react-dom": ">=19.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.7.0",
31
+ "rollup": "^4.29.0",
32
+ "@rollup/plugin-typescript": "^12.1.0",
33
+ "@rollup/plugin-node-resolve": "^16.0.0",
34
+ "@rollup/plugin-commonjs": "^28.0.0",
35
+ "@rollup/plugin-terser": "^0.4.0",
36
+ "rollup-plugin-peer-deps-external": "^2.2.4",
37
+ "rollup-plugin-dts": "^6.1.0",
38
+ "tslib": "^2.8.0",
39
+ "@types/react": "^19.0.0",
40
+ "@types/react-dom": "^19.0.0",
41
+ "react": "^19.0.0",
42
+ "react-dom": "^19.0.0"
43
+ },
44
+ "keywords": [
45
+ "react",
46
+ "chart",
47
+ "line-chart",
48
+ "svg",
49
+ "typescript",
50
+ "responsive",
51
+ "zoom",
52
+ "tooltip"
53
+ ],
54
+ "license": "MIT",
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/thuankg1752/ChartifyPDF.git"
58
+ },
59
+ "author": "thuankg1752"
60
+ }