chartforge 0.0.1

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.
Files changed (2) hide show
  1. package/README.md +1339 -0
  2. package/package.json +66 -0
package/README.md ADDED
@@ -0,0 +1,1339 @@
1
+ # ⬡ ChartForge
2
+
3
+ > **Production-grade, modular, zero-dependency SVG charting library — built with TypeScript.**
4
+ >
5
+ > Works in vanilla HTML, React, Vue, Angular, Laravel, Node.js (SSR), and any bundler or CDN setup.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ - [⬡ ChartForge](#-chartforge)
12
+ - [Table of Contents](#table-of-contents)
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [Framework Guides](#framework-guides)
16
+ - [Vanilla HTML / CDN](#vanilla-html--cdn)
17
+ - [Via `<script type="module">` (modern)](#via-script-typemodule-modern)
18
+ - [Via UMD `<script>` tag (legacy / Laravel CDN)](#via-umd-script-tag-legacy--laravel-cdn)
19
+ - [React](#react)
20
+ - [React Hook](#react-hook)
21
+ - [Vue 3](#vue-3)
22
+ - [Vue Composable](#vue-composable)
23
+ - [Angular](#angular)
24
+ - [Laravel (Blade + Vite)](#laravel-blade--vite)
25
+ - [1. Install via npm (Laravel project root)](#1-install-via-npm-laravel-project-root)
26
+ - [2. Add to `resources/js/app.js`](#2-add-to-resourcesjsappjs)
27
+ - [3. Blade template](#3-blade-template)
28
+ - [4. Or as a self-contained Blade component](#4-or-as-a-self-contained-blade-component)
29
+ - [Node.js / SSR](#nodejs--ssr)
30
+ - [Chart Types](#chart-types)
31
+ - [Configuration Reference](#configuration-reference)
32
+ - [Themes](#themes)
33
+ - [Built-in themes](#built-in-themes)
34
+ - [Custom theme](#custom-theme)
35
+ - [Plugins](#plugins)
36
+ - [Usage pattern](#usage-pattern)
37
+ - [Tooltip Plugin](#tooltip-plugin)
38
+ - [Legend Plugin](#legend-plugin)
39
+ - [Axis Plugin](#axis-plugin)
40
+ - [Grid Plugin](#grid-plugin)
41
+ - [Crosshair Plugin](#crosshair-plugin)
42
+ - [Data Labels Plugin](#data-labels-plugin)
43
+ - [Export Plugin](#export-plugin)
44
+ - [Zoom \& Pan Plugin](#zoom--pan-plugin)
45
+ - [Annotation Plugin](#annotation-plugin)
46
+ - [Adapters (Real-Time Data)](#adapters-real-time-data)
47
+ - [WebSocket Adapter](#websocket-adapter)
48
+ - [Polling Adapter](#polling-adapter)
49
+ - [Custom Adapter](#custom-adapter)
50
+ - [Events \& API](#events--api)
51
+ - [Chart Events](#chart-events)
52
+ - [Instance API](#instance-api)
53
+ - [Static API](#static-api)
54
+ - [Advanced Usage](#advanced-usage)
55
+ - [Middleware](#middleware)
56
+ - [Data Pipeline (Transformers)](#data-pipeline-transformers)
57
+ - [Virtual Rendering](#virtual-rendering)
58
+ - [Extending ChartForge](#extending-chartforge)
59
+ - [Custom Plugin](#custom-plugin)
60
+ - [Custom Renderer](#custom-renderer)
61
+ - [Custom Theme](#custom-theme-1)
62
+ - [Custom Adapter](#custom-adapter-1)
63
+ - [Architecture](#architecture)
64
+ - [Build Reference](#build-reference)
65
+ - [Build outputs](#build-outputs)
66
+ - [License](#license)
67
+
68
+ ---
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ npm install chartforge
74
+ # or
75
+ yarn add chartforge
76
+ # or
77
+ pnpm add chartforge
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Quick Start
83
+
84
+ ```ts
85
+ import { ChartForge } from 'chartforge';
86
+ import { TooltipPlugin, AxisPlugin, GridPlugin } from 'chartforge/plugins';
87
+
88
+ const chart = new ChartForge('#chart', {
89
+ type: 'column',
90
+ theme: 'dark',
91
+ data: {
92
+ labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
93
+ series: [{ name: 'Revenue', data: [65, 78, 72, 85, 92, 88] }],
94
+ },
95
+ });
96
+
97
+ chart.use('tooltip', TooltipPlugin)
98
+ .use('axis', AxisPlugin)
99
+ .use('grid', GridPlugin);
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Framework Guides
105
+
106
+ ### Vanilla HTML / CDN
107
+
108
+ #### Via `<script type="module">` (modern)
109
+
110
+ ```html
111
+ <!DOCTYPE html>
112
+ <html>
113
+ <head>
114
+ <style> #chart { width: 100%; height: 400px; } </style>
115
+ </head>
116
+ <body>
117
+ <div id="chart"></div>
118
+
119
+ <script type="module">
120
+ // From CDN (replace with actual CDN URL after publishing)
121
+ import { ChartForge } from 'https://unpkg.com/chartforge/dist/chartforge.js';
122
+ import { TooltipPlugin } from 'https://unpkg.com/chartforge/dist/plugins.js';
123
+
124
+ const chart = new ChartForge('#chart', {
125
+ type: 'line',
126
+ theme: 'dark',
127
+ data: {
128
+ labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
129
+ series: [
130
+ { name: 'Sales', data: [100, 120, 115, 134, 168] },
131
+ { name: 'Visits', data: [200, 240, 220, 260, 310] },
132
+ ],
133
+ },
134
+ animation: { enabled: true, duration: 800, easing: 'easeOutElastic' },
135
+ });
136
+
137
+ chart.use('tooltip', TooltipPlugin);
138
+ </script>
139
+ </body>
140
+ </html>
141
+ ```
142
+
143
+ #### Via UMD `<script>` tag (legacy / Laravel CDN)
144
+
145
+ ```html
146
+ <!-- UMD build — exposes window.ChartForge -->
147
+ <script src="https://unpkg.com/chartforge/dist/chartforge.umd.cjs"></script>
148
+ <script src="https://unpkg.com/chartforge/dist/plugins.umd.cjs"></script>
149
+
150
+ <div id="chart" style="height:400px"></div>
151
+
152
+ <script>
153
+ const { ChartForge } = window.ChartForge;
154
+ const { TooltipPlugin } = window.ChartForgePlugins;
155
+
156
+ const chart = new ChartForge('#chart', {
157
+ type: 'bar',
158
+ data: {
159
+ labels: ['Alpha', 'Beta', 'Gamma'],
160
+ series: [{ data: [42, 75, 38] }],
161
+ },
162
+ });
163
+
164
+ chart.use('tooltip', TooltipPlugin);
165
+ </script>
166
+ ```
167
+
168
+ ---
169
+
170
+ ### React
171
+
172
+ ```tsx
173
+ // components/Chart.tsx
174
+ import { useEffect, useRef } from 'react';
175
+ import { ChartForge } from 'chartforge';
176
+ import { TooltipPlugin } from 'chartforge/plugins';
177
+ import { LegendPlugin } from 'chartforge/plugins';
178
+ import type { ChartConfig } from 'chartforge';
179
+
180
+ interface ChartProps {
181
+ config: ChartConfig;
182
+ }
183
+
184
+ export function Chart({ config }: ChartProps) {
185
+ const ref = useRef<HTMLDivElement>(null);
186
+ const chartRef = useRef<ChartForge | null>(null);
187
+
188
+ useEffect(() => {
189
+ if (!ref.current) return;
190
+
191
+ chartRef.current = new ChartForge(ref.current, config);
192
+ chartRef.current
193
+ .use('tooltip', TooltipPlugin)
194
+ .use('legend', LegendPlugin);
195
+
196
+ return () => {
197
+ chartRef.current?.destroy();
198
+ chartRef.current = null;
199
+ };
200
+ }, []); // mount/unmount only
201
+
202
+ // Update data without re-mounting
203
+ useEffect(() => {
204
+ chartRef.current?.updateData(config.data);
205
+ }, [config.data]);
206
+
207
+ return <div ref={ref} style={{ width: '100%', height: 400 }} />;
208
+ }
209
+ ```
210
+
211
+ ```tsx
212
+ // App.tsx
213
+ import { useState } from 'react';
214
+ import { Chart } from './components/Chart';
215
+
216
+ const BASE_CONFIG = {
217
+ type: 'line' as const,
218
+ theme: 'dark',
219
+ data: {
220
+ labels: ['Jan', 'Feb', 'Mar', 'Apr'],
221
+ series: [{ name: 'Revenue', data: [100, 120, 115, 134] }],
222
+ },
223
+ };
224
+
225
+ export default function App() {
226
+ const [config, setConfig] = useState(BASE_CONFIG);
227
+
228
+ const randomize = () =>
229
+ setConfig(c => ({
230
+ ...c,
231
+ data: {
232
+ ...c.data,
233
+ series: [{ name: 'Revenue', data: Array.from({ length: 4 }, () => Math.random() * 150 + 50) }],
234
+ },
235
+ }));
236
+
237
+ return (
238
+ <div>
239
+ <button onClick={randomize}>Refresh</button>
240
+ <Chart config={config} />
241
+ </div>
242
+ );
243
+ }
244
+ ```
245
+
246
+ #### React Hook
247
+
248
+ ```ts
249
+ // hooks/useChart.ts
250
+ import { useEffect, useRef } from 'react';
251
+ import { ChartForge } from 'chartforge';
252
+ import type { ChartConfig, PluginConstructor } from 'chartforge';
253
+
254
+ export function useChart(
255
+ config: ChartConfig,
256
+ plugins: Array<[string, PluginConstructor, unknown?]> = []
257
+ ) {
258
+ const containerRef = useRef<HTMLDivElement>(null);
259
+ const chartRef = useRef<ChartForge | null>(null);
260
+
261
+ useEffect(() => {
262
+ if (!containerRef.current) return;
263
+ const chart = new ChartForge(containerRef.current, config);
264
+ plugins.forEach(([name, Plugin, cfg]) => chart.use(name, Plugin, cfg));
265
+ chartRef.current = chart;
266
+ return () => { chart.destroy(); chartRef.current = null; };
267
+ }, []);
268
+
269
+ useEffect(() => { chartRef.current?.updateData(config.data); }, [config.data]);
270
+ useEffect(() => { chartRef.current?.setTheme(config.theme ?? 'light'); }, [config.theme]);
271
+
272
+ return { containerRef, chart: chartRef };
273
+ }
274
+
275
+ // Usage:
276
+ // const { containerRef } = useChart(config, [['tooltip', TooltipPlugin]]);
277
+ // return <div ref={containerRef} style={{ height: 400 }} />;
278
+ ```
279
+
280
+ ---
281
+
282
+ ### Vue 3
283
+
284
+ ```vue
285
+ <!-- components/ChartForge.vue -->
286
+ <template>
287
+ <div ref="containerRef" :style="{ width: '100%', height: height + 'px' }" />
288
+ </template>
289
+
290
+ <script setup lang="ts">
291
+ import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
292
+ import { ChartForge } from 'chartforge';
293
+ import { TooltipPlugin } from 'chartforge/plugins';
294
+ import type { ChartConfig } from 'chartforge';
295
+
296
+ const props = withDefaults(defineProps<{
297
+ config: ChartConfig;
298
+ height?: number;
299
+ }>(), { height: 400 });
300
+
301
+ const containerRef = ref<HTMLDivElement | null>(null);
302
+ let chart: ChartForge | null = null;
303
+
304
+ onMounted(() => {
305
+ if (!containerRef.value) return;
306
+ chart = new ChartForge(containerRef.value, props.config);
307
+ chart.use('tooltip', TooltipPlugin);
308
+ });
309
+
310
+ onBeforeUnmount(() => {
311
+ chart?.destroy();
312
+ chart = null;
313
+ });
314
+
315
+ // React to data changes
316
+ watch(() => props.config.data, (newData) => {
317
+ chart?.updateData(newData);
318
+ }, { deep: true });
319
+
320
+ // React to theme changes
321
+ watch(() => props.config.theme, (theme) => {
322
+ chart?.setTheme(theme ?? 'dark');
323
+ });
324
+ </script>
325
+ ```
326
+
327
+ ```vue
328
+ <!-- App.vue -->
329
+ <template>
330
+ <ChartForgeVue :config="config" :height="350" />
331
+ <button @click="refresh">Refresh</button>
332
+ </template>
333
+
334
+ <script setup lang="ts">
335
+ import { ref } from 'vue';
336
+ import ChartForgeVue from './components/ChartForge.vue';
337
+
338
+ const config = ref({
339
+ type: 'pie' as const,
340
+ theme: 'dark',
341
+ data: {
342
+ labels: ['Desktop', 'Mobile', 'Tablet'],
343
+ series: [{ data: [450, 320, 180] }],
344
+ },
345
+ });
346
+
347
+ function refresh() {
348
+ config.value = {
349
+ ...config.value,
350
+ data: {
351
+ ...config.value.data,
352
+ series: [{ data: [Math.random() * 500, Math.random() * 400, Math.random() * 200] }],
353
+ },
354
+ };
355
+ }
356
+ </script>
357
+ ```
358
+
359
+ #### Vue Composable
360
+
361
+ ```ts
362
+ // composables/useChart.ts
363
+ import { ref, onMounted, onBeforeUnmount } from 'vue';
364
+ import { ChartForge } from 'chartforge';
365
+ import type { ChartConfig } from 'chartforge';
366
+
367
+ export function useChart(config: ChartConfig) {
368
+ const containerRef = ref<HTMLDivElement | null>(null);
369
+ let instance: ChartForge | null = null;
370
+
371
+ onMounted(() => {
372
+ if (!containerRef.value) return;
373
+ instance = new ChartForge(containerRef.value, config);
374
+ });
375
+
376
+ onBeforeUnmount(() => { instance?.destroy(); });
377
+
378
+ return {
379
+ containerRef,
380
+ updateData: (data: Partial<ChartConfig['data']>) => instance?.updateData(data),
381
+ setTheme: (name: string) => instance?.setTheme(name),
382
+ instance: () => instance,
383
+ };
384
+ }
385
+ ```
386
+
387
+ ---
388
+
389
+ ### Angular
390
+
391
+ ```ts
392
+ // chart.component.ts
393
+ import { Component, Input, ElementRef, OnInit, OnDestroy, OnChanges } from '@angular/core';
394
+ import { ChartForge } from 'chartforge';
395
+ import { TooltipPlugin, LegendPlugin } from 'chartforge/plugins';
396
+ import type { ChartConfig } from 'chartforge';
397
+
398
+ @Component({
399
+ selector: 'app-chart',
400
+ template: `<div [style.height.px]="height" style="width:100%"></div>`,
401
+ })
402
+ export class ChartComponent implements OnInit, OnDestroy, OnChanges {
403
+ @Input() config!: ChartConfig;
404
+ @Input() height = 400;
405
+
406
+ private chart: ChartForge | null = null;
407
+
408
+ constructor(private el: ElementRef<HTMLElement>) {}
409
+
410
+ ngOnInit(): void {
411
+ const container = this.el.nativeElement.querySelector('div')!;
412
+ this.chart = new ChartForge(container, this.config);
413
+ this.chart.use('tooltip', TooltipPlugin).use('legend', LegendPlugin);
414
+ }
415
+
416
+ ngOnChanges(): void {
417
+ this.chart?.updateData(this.config.data);
418
+ }
419
+
420
+ ngOnDestroy(): void {
421
+ this.chart?.destroy();
422
+ this.chart = null;
423
+ }
424
+ }
425
+ ```
426
+
427
+ ---
428
+
429
+ ### Laravel (Blade + Vite)
430
+
431
+ #### 1. Install via npm (Laravel project root)
432
+
433
+ ```bash
434
+ npm install chartforge
435
+ ```
436
+
437
+ #### 2. Add to `resources/js/app.js`
438
+
439
+ ```js
440
+ // resources/js/app.js
441
+ import { ChartForge } from 'chartforge';
442
+ import { TooltipPlugin } from 'chartforge/plugins';
443
+
444
+ window.ChartForge = ChartForge;
445
+ window.TooltipPlugin = TooltipPlugin;
446
+ ```
447
+
448
+ #### 3. Blade template
449
+
450
+ ```blade
451
+ {{-- resources/views/dashboard.blade.php --}}
452
+ @extends('layouts.app')
453
+
454
+ @section('content')
455
+ <div id="revenue-chart" style="height: 400px; background:#1a1a2e; border-radius:12px;"></div>
456
+
457
+ @push('scripts')
458
+ <script>
459
+ const chart = new window.ChartForge('#revenue-chart', {
460
+ type: 'column',
461
+ theme: 'dark',
462
+ data: {
463
+ labels: {!! json_encode($labels) !!},
464
+ series: [{
465
+ name: 'Revenue',
466
+ data: {!! json_encode($revenues) !!},
467
+ }],
468
+ },
469
+ });
470
+
471
+ chart.use('tooltip', window.TooltipPlugin);
472
+
473
+ // Listen to click events
474
+ chart.on('click', ({ index, value }) => {
475
+ console.log('Clicked bar:', index, 'Value:', value);
476
+ });
477
+ </script>
478
+ @endpush
479
+ @endsection
480
+ ```
481
+
482
+ #### 4. Or as a self-contained Blade component
483
+
484
+ ```blade
485
+ {{-- resources/views/components/chart.blade.php --}}
486
+ <div
487
+ wire:ignore
488
+ id="{{ $id }}"
489
+ style="height: {{ $height ?? 400 }}px"
490
+ x-data
491
+ x-init="
492
+ const chart = new window.ChartForge('#{{ $id }}', {
493
+ type: '{{ $type }}',
494
+ theme: '{{ $theme ?? 'dark' }}',
495
+ data: {{ Js::from($data) }},
496
+ });
497
+ chart.use('tooltip', window.TooltipPlugin);
498
+ "
499
+ ></div>
500
+ ```
501
+
502
+ ---
503
+
504
+ ### Node.js / SSR
505
+
506
+ ChartForge requires a DOM. In Node.js environments, use a virtual DOM library:
507
+
508
+ ```bash
509
+ npm install jsdom
510
+ ```
511
+
512
+ ```js
513
+ // generate-chart.mjs
514
+ import { JSDOM } from 'jsdom';
515
+
516
+ // Shim browser globals
517
+ const dom = new JSDOM('<!DOCTYPE html><body></body>');
518
+ global.window = dom.window;
519
+ global.document = dom.window.document;
520
+ global.SVGElement = dom.window.SVGElement;
521
+ global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
522
+ global.cancelAnimationFrame = clearTimeout;
523
+
524
+ const { ChartForge } = await import('chartforge');
525
+
526
+ const container = document.createElement('div');
527
+ document.body.appendChild(container);
528
+
529
+ const chart = new ChartForge(container, {
530
+ type: 'line',
531
+ theme: 'dark',
532
+ data: {
533
+ labels: ['Jan', 'Feb', 'Mar'],
534
+ series: [{ name: 'Sales', data: [100, 150, 130] }],
535
+ },
536
+ animation: { enabled: false }, // disable animation for SSR
537
+ });
538
+
539
+ await chart.render();
540
+
541
+ // Export SVG string
542
+ const svgStr = container.querySelector('svg').outerHTML;
543
+
544
+ // Save to file
545
+ import { writeFileSync } from 'fs';
546
+ writeFileSync('chart.svg', svgStr);
547
+
548
+ console.log('Chart saved to chart.svg');
549
+ ```
550
+
551
+ ---
552
+
553
+ ## Chart Types
554
+
555
+ | Type | Description | Required `data` shape |
556
+ | --------------- | --------------------------- | ---------------------------------------------- |
557
+ | `column` | Vertical bars | `series[0].data: number[]` |
558
+ | `bar` / `row` | Horizontal bars | `series[0].data: number[]` |
559
+ | `line` | Line chart, multiple series | `series[].data: number[]` |
560
+ | `pie` | Pie chart | `series[0].data: number[]` |
561
+ | `donut` | Donut chart | `series[0].data: number[]` |
562
+ | `scatter` | Scatter/bubble plot | `series[].data: { x, y, r? }[]` |
563
+ | `stackedColumn` | Stacked vertical bars | `series[].data: number[]` |
564
+ | `stackedBar` | Stacked horizontal bars | `series[].data: number[]` |
565
+ | `funnel` | Funnel/conversion chart | `series[0].data: number[]` |
566
+ | `heatmap` | 2D heatmap grid | `series[0].data: number[][]` |
567
+ | `candlestick` | OHLC/candlestick chart | `series[0].data: { open, high, low, close }[]` |
568
+
569
+ ---
570
+
571
+ ## Configuration Reference
572
+
573
+ ```ts
574
+ const chart = new ChartForge('#container', {
575
+ // Required
576
+ type: 'column', // Chart type
577
+ data: { labels: [...], series: [...] },
578
+
579
+ // Layout
580
+ width: 'auto', // number | 'auto' (follows container)
581
+ height: 400, // number (pixels)
582
+ responsive: true, // Auto-resize on container resize
583
+ padding: {
584
+ top: 40, right: 40, bottom: 60, left: 60,
585
+ },
586
+
587
+ // Appearance
588
+ theme: 'dark', // 'light' | 'dark' | 'neon' | your custom theme name
589
+
590
+ // Animation
591
+ animation: {
592
+ enabled: true,
593
+ duration: 750, // ms
594
+ easing: 'easeOutQuad',
595
+ // All easings: 'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad',
596
+ // 'easeInCubic', 'easeOutCubic', 'easeInOutCubic',
597
+ // 'easeInElastic', 'easeOutElastic', 'easeInBounce', 'easeOutBounce'
598
+ },
599
+
600
+ // Virtual rendering (for very large datasets)
601
+ virtual: {
602
+ enabled: false,
603
+ threshold: 10_000, // auto-enable when data points exceed this
604
+ },
605
+
606
+ // Middleware (runs before every render)
607
+ middleware: [],
608
+ });
609
+ ```
610
+
611
+ ---
612
+
613
+ ## Themes
614
+
615
+ ### Built-in themes
616
+
617
+ ```ts
618
+ import { ChartForge } from 'chartforge';
619
+
620
+ // 'light' | 'dark' | 'neon'
621
+ const chart = new ChartForge('#c', { type: 'line', theme: 'neon', data: { ... } });
622
+
623
+ // Switch at runtime
624
+ chart.setTheme('light');
625
+ ```
626
+
627
+ ### Custom theme
628
+
629
+ ```ts
630
+ import { ChartForge } from 'chartforge';
631
+ import type { Theme } from 'chartforge';
632
+
633
+ const brandTheme: Theme = {
634
+ background: '#0f1923',
635
+ foreground: '#ffffff',
636
+ grid: '#1e2d3d',
637
+ text: '#c8d8e8',
638
+ textSecondary: '#5a7a9a',
639
+ colors: ['#00d4ff', '#ff6b6b', '#51cf66', '#ffd43b', '#cc5de8'],
640
+ tooltip: {
641
+ background: '#0f1923',
642
+ text: '#c8d8e8',
643
+ border: '#1e2d3d',
644
+ shadow: 'rgba(0, 212, 255, 0.15)',
645
+ },
646
+ legend: {
647
+ text: '#c8d8e8',
648
+ hover: '#ffffff',
649
+ },
650
+ axis: {
651
+ line: '#1e2d3d',
652
+ text: '#5a7a9a',
653
+ grid: '#121e29',
654
+ },
655
+ };
656
+
657
+ // Register globally (available to all new ChartForge instances)
658
+ ChartForge.registerTheme('brand', brandTheme);
659
+
660
+ const chart = new ChartForge('#c', { type: 'bar', theme: 'brand', data: { ... } });
661
+
662
+ // Or register per-instance
663
+ chart.themeManager.register('brand', brandTheme);
664
+ chart.setTheme('brand');
665
+ ```
666
+
667
+ ---
668
+
669
+ ## Plugins
670
+
671
+ ### Usage pattern
672
+
673
+ ```ts
674
+ // Method 1: Fluent chain
675
+ chart
676
+ .use('tooltip', TooltipPlugin, { shadow: true })
677
+ .use('legend', LegendPlugin, { position: 'bottom' })
678
+ .use('axis', AxisPlugin, { y: { label: 'Revenue ($)' } })
679
+ .use('grid', GridPlugin)
680
+ .use('crosshair', CrosshairPlugin);
681
+
682
+ // Method 2: pluginManager
683
+ chart.pluginManager.register('tooltip', TooltipPlugin, { fontSize: 14 });
684
+
685
+ // Get plugin instance later
686
+ const tooltip = chart.getPlugin<TooltipPlugin>('tooltip');
687
+ ```
688
+
689
+ ---
690
+
691
+ ### Tooltip Plugin
692
+
693
+ ```ts
694
+ import { TooltipPlugin } from 'chartforge/plugins';
695
+
696
+ chart.use('tooltip', TooltipPlugin, {
697
+ enabled: true,
698
+ backgroundColor: '#1a1a2e',
699
+ textColor: '#e0e0ff',
700
+ borderColor: '#3a3a6e',
701
+ borderRadius: 8,
702
+ padding: 12,
703
+ fontSize: 13,
704
+ shadow: true,
705
+ followCursor: true,
706
+ offset: { x: 14, y: 14 },
707
+
708
+ // Custom formatter (receives the raw hover event data)
709
+ formatter: (data) => {
710
+ if (data.type === 'column') {
711
+ return `<strong>${data.value}</strong> units sold`;
712
+ }
713
+ return String(data.value);
714
+ },
715
+ });
716
+ ```
717
+
718
+ ---
719
+
720
+ ### Legend Plugin
721
+
722
+ ```ts
723
+ import { LegendPlugin } from 'chartforge/plugins';
724
+
725
+ chart.use('legend', LegendPlugin, {
726
+ enabled: true,
727
+ position: 'bottom', // 'top' | 'bottom' | 'left' | 'right'
728
+ align: 'center', // 'start' | 'center' | 'end'
729
+ layout: 'horizontal', // 'horizontal' | 'vertical'
730
+ fontSize: 12,
731
+ itemSpacing: 12,
732
+ markerSize: 12,
733
+ markerType: 'square', // 'square' | 'circle' | 'line'
734
+ clickable: true, // Toggle series visibility on click
735
+ });
736
+ ```
737
+
738
+ ---
739
+
740
+ ### Axis Plugin
741
+
742
+ ```ts
743
+ import { AxisPlugin } from 'chartforge/plugins';
744
+
745
+ chart.use('axis', AxisPlugin, {
746
+ x: {
747
+ enabled: true,
748
+ label: 'Month',
749
+ fontSize: 11,
750
+ tickLength: 5,
751
+ },
752
+ y: {
753
+ enabled: true,
754
+ label: 'Revenue ($)',
755
+ fontSize: 11,
756
+ tickLength: 5,
757
+ ticks: 5, // Number of Y-axis tick marks
758
+ },
759
+ });
760
+ ```
761
+
762
+ ---
763
+
764
+ ### Grid Plugin
765
+
766
+ ```ts
767
+ import { GridPlugin } from 'chartforge/plugins';
768
+
769
+ chart.use('grid', GridPlugin, {
770
+ enabled: true,
771
+ x: { enabled: true, color: '#2a2a3a', dashArray: '3,3', strokeWidth: 1 },
772
+ y: { enabled: true, color: '#2a2a3a', dashArray: '3,3', strokeWidth: 1, ticks: 5 },
773
+ });
774
+ ```
775
+
776
+ ---
777
+
778
+ ### Crosshair Plugin
779
+
780
+ Draws intersecting reference lines following the cursor inside the chart area.
781
+
782
+ ```ts
783
+ import { CrosshairPlugin } from 'chartforge/plugins';
784
+
785
+ chart.use('crosshair', CrosshairPlugin, {
786
+ enabled: true,
787
+ x: { enabled: true, color: '#888', dashArray: '4,4', width: 1 },
788
+ y: { enabled: true, color: '#888', dashArray: '4,4', width: 1 },
789
+ });
790
+ ```
791
+
792
+ ---
793
+
794
+ ### Data Labels Plugin
795
+
796
+ Show values directly on top of chart elements.
797
+
798
+ ```ts
799
+ import { DataLabelsPlugin } from 'chartforge/plugins';
800
+
801
+ chart.use('dataLabels', DataLabelsPlugin, {
802
+ enabled: true,
803
+ fontSize: 11,
804
+ color: '#ffffff',
805
+ anchor: 'top', // 'top' | 'center' | 'bottom'
806
+ offset: 5, // px offset from element
807
+ rotation: -45, // label rotation in degrees
808
+ formatter: (value) => `$${value.toLocaleString()}`,
809
+ });
810
+ ```
811
+
812
+ ---
813
+
814
+ ### Export Plugin
815
+
816
+ Adds SVG / PNG / CSV download buttons above the chart.
817
+
818
+ ```ts
819
+ import { ExportPlugin } from 'chartforge/plugins';
820
+
821
+ chart.use('export', ExportPlugin, {
822
+ filename: 'revenue-q1', // download filename (no extension)
823
+ svgButton: true,
824
+ pngButton: true,
825
+ csvButton: true,
826
+ });
827
+
828
+ // Or trigger exports programmatically
829
+ const exporter = chart.getPlugin<ExportPlugin>('export');
830
+ exporter?.exportSVG();
831
+ await exporter?.exportPNG(3); // 3x scale for retina
832
+ exporter?.exportCSV();
833
+ ```
834
+
835
+ ---
836
+
837
+ ### Zoom & Pan Plugin
838
+
839
+ Mouse wheel to zoom, drag to pan, double-click to reset.
840
+
841
+ ```ts
842
+ import { ZoomPlugin } from 'chartforge/plugins';
843
+
844
+ chart.use('zoom', ZoomPlugin, {
845
+ enabled: true,
846
+ type: 'xy', // 'x' | 'y' | 'xy'
847
+ minZoom: 0.5,
848
+ maxZoom: 10,
849
+ resetOnDblClick: true,
850
+ });
851
+
852
+ // Reset programmatically
853
+ const zoom = chart.getPlugin<ZoomPlugin>('zoom');
854
+ zoom?.reset();
855
+ ```
856
+
857
+ ---
858
+
859
+ ### Annotation Plugin
860
+
861
+ Add horizontal/vertical reference lines, shaded regions, and text labels to any chart.
862
+
863
+ ```ts
864
+ import { AnnotationPlugin } from 'chartforge/plugins';
865
+
866
+ chart.use('annotations', AnnotationPlugin, {
867
+ markLines: [
868
+ { type: 'horizontal', value: 100, label: 'Target', color: '#10b981', dashArray: '5,3' },
869
+ { type: 'horizontal', value: 50, label: 'Baseline', color: '#ef4444' },
870
+ { type: 'vertical', value: 2, label: 'Campaign', color: '#f59e0b' },
871
+ ],
872
+ markAreas: [
873
+ { xStart: 1, xEnd: 3, color: '#3b82f6', opacity: 0.1, label: 'Peak period' },
874
+ { yStart: 80, yEnd: 120, color: '#10b981', opacity: 0.08 },
875
+ ],
876
+ texts: [
877
+ { x: 2, y: 150, text: '🚀 New product launch', color: '#fff', background: '#3b82f6' },
878
+ ],
879
+ });
880
+
881
+ // Add annotations dynamically
882
+ const ann = chart.getPlugin<AnnotationPlugin>('annotations');
883
+ ann?.addMarkLine({ type: 'horizontal', value: 200, label: 'Record', color: '#ff6b6b' });
884
+ ann?.addText({ x: 4, y: 180, text: 'All-time high', color: '#ff6b6b' });
885
+ ```
886
+
887
+ ---
888
+
889
+ ## Adapters (Real-Time Data)
890
+
891
+ ChartForge ships with two built-in real-time adapters.
892
+
893
+ ### WebSocket Adapter
894
+
895
+ ```ts
896
+ // Connect to a WebSocket feed
897
+ chart.realTime.connect('websocket', {
898
+ url: 'wss://api.example.com/live-data',
899
+ });
900
+
901
+ // Your server should push: { series: [{ data: [...] }], labels: [...] }
902
+
903
+ // Disconnect when done
904
+ chart.realTime.disconnect('websocket');
905
+ ```
906
+
907
+ ### Polling Adapter
908
+
909
+ ```ts
910
+ chart.realTime.connect('polling', {
911
+ url: '/api/live-metrics',
912
+ interval: 3000, // ms — defaults to 5000
913
+ });
914
+ ```
915
+
916
+ ### Custom Adapter
917
+
918
+ ```ts
919
+ import type { IAdapter, EventHandler, ChartData } from 'chartforge';
920
+
921
+ class SSEAdapter implements IAdapter {
922
+ private _es: EventSource | null = null;
923
+ private _listeners = new Map<string, EventHandler[]>();
924
+
925
+ constructor(private _url: string) {}
926
+
927
+ on(event: string, handler: EventHandler): void {
928
+ if (!this._listeners.has(event)) this._listeners.set(event, []);
929
+ this._listeners.get(event)!.push(handler);
930
+ }
931
+
932
+ connect(): void {
933
+ this._es = new EventSource(this._url);
934
+ this._es.addEventListener('message', (e) => {
935
+ const data = JSON.parse(e.data) as ChartData;
936
+ this._listeners.get('data')?.forEach(h => h(data));
937
+ });
938
+ }
939
+
940
+ disconnect(): void {
941
+ this._es?.close();
942
+ this._es = null;
943
+ }
944
+ }
945
+
946
+ // Register globally
947
+ chart.realTime.registerAdapter('sse', SSEAdapter);
948
+ chart.realTime.connect('sse', 'https://api.example.com/stream');
949
+ ```
950
+
951
+ ---
952
+
953
+ ## Events & API
954
+
955
+ ### Chart Events
956
+
957
+ ```ts
958
+ // Hover over a data element
959
+ chart.on('hover', ({ type, index, value, seriesIndex, point, candle, row, col }) => {
960
+ console.log('Hovered:', type, value);
961
+ });
962
+
963
+ // Click on a data element
964
+ chart.on('click', ({ type, index, value }) => {
965
+ console.log('Clicked:', type, value);
966
+ });
967
+
968
+ // Before/after render
969
+ chart.on('beforeRender', (ctx) => { /* ctx: { data, theme, svg, mainGroup } */ });
970
+ chart.on('afterRender', (ctx) => { /* DOM is updated */ });
971
+
972
+ // Unsubscribe
973
+ const unsub = chart.on('click', handler);
974
+ unsub(); // removes the listener
975
+ ```
976
+
977
+ ### Instance API
978
+
979
+ ```ts
980
+ // Update data (re-renders)
981
+ chart.updateData({
982
+ labels: ['A', 'B', 'C'],
983
+ series: [{ name: 'Sales', data: [10, 20, 15] }],
984
+ });
985
+
986
+ // Partial config update
987
+ chart.updateConfig({ type: 'bar', theme: 'neon' });
988
+
989
+ // Switch theme
990
+ chart.setTheme('dark');
991
+
992
+ // Get plugin instance
993
+ const tooltip = chart.getPlugin<TooltipPlugin>('tooltip');
994
+
995
+ // Trigger manual resize
996
+ chart.resize();
997
+
998
+ // Viewport (virtual rendering)
999
+ chart.setViewport(0, 100); // show data points [0, 100)
1000
+
1001
+ // Destroy (cleans up DOM, events, RAF, WebSocket)
1002
+ chart.destroy();
1003
+ ```
1004
+
1005
+ ### Static API
1006
+
1007
+ ```ts
1008
+ // Factory method (same as new ChartForge)
1009
+ const chart = ChartForge.create('#container', { type: 'pie', data: { ... } });
1010
+
1011
+ // Register a theme available to all instances
1012
+ ChartForge.registerTheme('brand', myTheme);
1013
+ ```
1014
+
1015
+ ---
1016
+
1017
+ ## Advanced Usage
1018
+
1019
+ ### Middleware
1020
+
1021
+ Middleware runs before every render. Use it for logging, auth, data transformation, etc.
1022
+
1023
+ ```ts
1024
+ const chart = new ChartForge('#c', {
1025
+ type: 'line',
1026
+ data: { ... },
1027
+ middleware: [
1028
+ async (ctx, next) => {
1029
+ console.time('render');
1030
+ await next(); // call next to continue the pipeline
1031
+ console.timeEnd('render');
1032
+ },
1033
+ async (ctx, next) => {
1034
+ // Transform data before rendering
1035
+ ctx.data.series = ctx.data.series.map(s => ({
1036
+ ...s,
1037
+ data: (s.data as number[]).map(v => v * 1.1), // +10% adjustment
1038
+ }));
1039
+ await next();
1040
+ },
1041
+ ],
1042
+ });
1043
+
1044
+ // Add middleware after construction
1045
+ chart.middleware.use(async (ctx, next) => {
1046
+ ctx.theme = { ...ctx.theme, background: '#ff0000' }; // override theme for this render
1047
+ await next();
1048
+ });
1049
+ ```
1050
+
1051
+ ### Data Pipeline (Transformers)
1052
+
1053
+ Named transformers that process data before it reaches the renderer.
1054
+
1055
+ ```ts
1056
+ // Add a normalizer
1057
+ chart.dataPipeline.addTransformer('normalize', (data) => ({
1058
+ ...data,
1059
+ series: data.series.map(s => {
1060
+ const max = Math.max(...s.data as number[]);
1061
+ return { ...s, data: (s.data as number[]).map(v => v / max) };
1062
+ }),
1063
+ }));
1064
+
1065
+ // Add a sorter
1066
+ chart.dataPipeline.addTransformer('sort', (data) => ({
1067
+ ...data,
1068
+ series: data.series.map(s => ({
1069
+ ...s,
1070
+ data: [...s.data as number[]].sort((a, b) => b - a),
1071
+ })),
1072
+ }));
1073
+
1074
+ // Remove a transformer
1075
+ chart.dataPipeline.removeTransformer('sort');
1076
+ ```
1077
+
1078
+ ### Virtual Rendering
1079
+
1080
+ For datasets with tens of thousands of points, enable virtual rendering to only draw visible points:
1081
+
1082
+ ```ts
1083
+ const chart = new ChartForge('#c', {
1084
+ type: 'line',
1085
+ data: { series: [{ data: Array.from({ length: 100_000 }, () => Math.random()) }] },
1086
+ virtual: {
1087
+ enabled: true,
1088
+ threshold: 5_000, // Auto-enable once series total exceeds this
1089
+ },
1090
+ });
1091
+
1092
+ // Pan the viewport
1093
+ chart.setViewport(0, 500); // first 500 points
1094
+ chart.setViewport(500, 1000); // next 500 points
1095
+ ```
1096
+
1097
+ ---
1098
+
1099
+ ## Extending ChartForge
1100
+
1101
+ ### Custom Plugin
1102
+
1103
+ ```ts
1104
+ import { BasePlugin } from 'chartforge/plugins';
1105
+ import type { Theme } from 'chartforge';
1106
+
1107
+ interface WatermarkConfig {
1108
+ text: string;
1109
+ opacity?: number;
1110
+ color?: string;
1111
+ fontSize?: number;
1112
+ }
1113
+
1114
+ interface ChartLike {
1115
+ theme: Theme;
1116
+ svg: SVGSVGElement;
1117
+ on: (event: string, h: () => void) => void;
1118
+ }
1119
+
1120
+ class WatermarkPlugin extends BasePlugin {
1121
+ private readonly _cfg: Required<WatermarkConfig>;
1122
+
1123
+ constructor(chart: unknown, cfg: WatermarkConfig) {
1124
+ super(chart, cfg);
1125
+ this._cfg = {
1126
+ opacity: 0.1,
1127
+ color: (chart as ChartLike).theme.text,
1128
+ fontSize: 48,
1129
+ ...cfg,
1130
+ };
1131
+ }
1132
+
1133
+ init(): void {
1134
+ const c = this._chart as ChartLike;
1135
+ const svg = c.svg;
1136
+
1137
+ const draw = () => {
1138
+ const existing = svg.querySelector('.cf-watermark');
1139
+ if (existing) svg.removeChild(existing);
1140
+
1141
+ const vb = svg.getAttribute('viewBox')!.split(' ').map(Number);
1142
+ const txt = document.createElementNS('http://www.w3.org/2000/svg', 'text');
1143
+ Object.assign(txt, {});
1144
+ txt.setAttribute('class', 'cf-watermark');
1145
+ txt.setAttribute('x', String(vb[2] / 2));
1146
+ txt.setAttribute('y', String(vb[3] / 2));
1147
+ txt.setAttribute('text-anchor', 'middle');
1148
+ txt.setAttribute('dominant-baseline', 'middle');
1149
+ txt.setAttribute('fill', this._cfg.color);
1150
+ txt.setAttribute('font-size', String(this._cfg.fontSize));
1151
+ txt.setAttribute('opacity', String(this._cfg.opacity));
1152
+ txt.setAttribute('pointer-events', 'none');
1153
+ txt.setAttribute('font-weight', 'bold');
1154
+ txt.setAttribute('transform', `rotate(-30,${vb[2]/2},${vb[3]/2})`);
1155
+ txt.textContent = this._cfg.text;
1156
+ svg.appendChild(txt);
1157
+ };
1158
+
1159
+ draw();
1160
+ c.on('afterRender', draw);
1161
+ }
1162
+ }
1163
+
1164
+ // Use it
1165
+ chart.use('watermark', WatermarkPlugin, { text: 'CONFIDENTIAL' });
1166
+ ```
1167
+
1168
+ ---
1169
+
1170
+ ### Custom Renderer
1171
+
1172
+ ```ts
1173
+ import { BaseRenderer, RENDERERS } from 'chartforge';
1174
+ import type { ChartLike } from 'chartforge';
1175
+
1176
+ class RadarRenderer extends BaseRenderer {
1177
+ render(): void {
1178
+ const d = this.dims();
1179
+ const cx = d.totalWidth / 2;
1180
+ const cy = d.totalHeight / 2;
1181
+ const r = Math.min(d.width, d.height) / 2 - 20;
1182
+ const series = this.data.series[0].data as number[];
1183
+ const n = series.length;
1184
+ const maxVal = Math.max(...series);
1185
+ const step = (Math.PI * 2) / n;
1186
+
1187
+ const group = this.g('chartforge-radar');
1188
+ this.group.appendChild(group);
1189
+
1190
+ // Draw spokes
1191
+ for (let i = 0; i < n; i++) {
1192
+ const angle = step * i - Math.PI / 2;
1193
+ const x2 = cx + r * Math.cos(angle);
1194
+ const y2 = cy + r * Math.sin(angle);
1195
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
1196
+ line.setAttribute('x1', String(cx)); line.setAttribute('y1', String(cy));
1197
+ line.setAttribute('x2', String(x2)); line.setAttribute('y2', String(y2));
1198
+ line.setAttribute('stroke', this.theme.grid); line.setAttribute('stroke-width', '1');
1199
+ group.appendChild(line);
1200
+ }
1201
+
1202
+ // Draw data polygon
1203
+ const pts = series.map((v, i) => {
1204
+ const angle = step * i - Math.PI / 2;
1205
+ const rv = (v / maxVal) * r;
1206
+ return `${cx + rv * Math.cos(angle)},${cy + rv * Math.sin(angle)}`;
1207
+ }).join(' ');
1208
+
1209
+ const poly = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
1210
+ poly.setAttribute('points', pts);
1211
+ poly.setAttribute('fill', this.color(0) + '55');
1212
+ poly.setAttribute('stroke', this.color(0));
1213
+ poly.setAttribute('stroke-width', '2');
1214
+ group.appendChild(poly);
1215
+ }
1216
+ }
1217
+
1218
+ // Register the renderer globally
1219
+ RENDERERS['radar' as never] = RadarRenderer as never;
1220
+
1221
+ // Now use it
1222
+ const chart = new ChartForge('#c', {
1223
+ type: 'radar' as never,
1224
+ data: { series: [{ data: [80, 60, 90, 75, 85, 70] }] },
1225
+ });
1226
+ ```
1227
+
1228
+ ---
1229
+
1230
+ ### Custom Theme
1231
+
1232
+ See the [Themes](#themes) section above for a full custom theme example.
1233
+
1234
+ ### Custom Adapter
1235
+
1236
+ See the [Adapters](#adapters-real-time-data) section above for a Server-Sent Events adapter example.
1237
+
1238
+ ---
1239
+
1240
+ ## Architecture
1241
+
1242
+ ```
1243
+ src/
1244
+ ├── ChartForge.ts # Main orchestrator class
1245
+ ├── types.ts # All shared interfaces — single source of truth
1246
+ ├── index.ts # Public barrel — tree-shakeable
1247
+
1248
+ ├── core/ # Sub-systems (individually importable)
1249
+ │ ├── EventBus.ts # Priority-based pub/sub
1250
+ │ ├── MiddlewarePipeline.ts # Async middleware chain
1251
+ │ ├── DataPipeline.ts # Named data transformers
1252
+ │ ├── AnimationEngine.ts # RAF-based tweening with 11 easings
1253
+ │ ├── ThemeManager.ts # Theme registry + apply
1254
+ │ ├── PluginManager.ts # Plugin lifecycle management
1255
+ │ └── VirtualRenderer.ts # Viewport slicing for large datasets
1256
+
1257
+ ├── renderers/ # One file per chart type (tree-shakeable)
1258
+ │ ├── BaseRenderer.ts # Abstract base with shared geometry
1259
+ │ ├── ColumnRenderer.ts
1260
+ │ ├── BarRenderer.ts
1261
+ │ ├── LineRenderer.ts
1262
+ │ ├── PieRenderer.ts
1263
+ │ ├── DonutRenderer.ts
1264
+ │ ├── ScatterRenderer.ts
1265
+ │ ├── StackedColumnRenderer.ts
1266
+ │ ├── StackedBarRenderer.ts
1267
+ │ ├── FunnelRenderer.ts
1268
+ │ ├── HeatmapRenderer.ts
1269
+ │ ├── CandlestickRenderer.ts
1270
+ │ └── index.ts # RENDERERS registry
1271
+
1272
+ ├── plugins/ # One file per plugin (tree-shakeable)
1273
+ │ ├── BasePlugin.ts
1274
+ │ ├── TooltipPlugin.ts # Smart tooltip with per-type formatting
1275
+ │ ├── LegendPlugin.ts # Clickable, snapshotted series toggle
1276
+ │ ├── AxisPlugin.ts # X/Y axes with labels
1277
+ │ ├── GridPlugin.ts # Background grid lines
1278
+ │ ├── CrosshairPlugin.ts # Cursor crosshair lines
1279
+ │ ├── DataLabelsPlugin.ts # Values on elements
1280
+ │ ├── ExportPlugin.ts # SVG/PNG/CSV export
1281
+ │ ├── ZoomPlugin.ts # Wheel zoom + drag pan
1282
+ │ ├── AnnotationPlugin.ts # Mark lines, areas, text
1283
+ │ └── index.ts
1284
+
1285
+ ├── themes/
1286
+ │ ├── builtins.ts # light | dark | neon
1287
+ │ └── index.ts
1288
+
1289
+ ├── adapters/ # Real-time data feeds
1290
+ │ ├── WebSocketAdapter.ts
1291
+ │ ├── PollingAdapter.ts
1292
+ │ ├── RealTimeModule.ts
1293
+ │ └── index.ts
1294
+
1295
+ └── utils/
1296
+ ├── dom.ts # SVG element creation, polar coords
1297
+ ├── misc.ts # uid, merge, clamp, debounce, throttle
1298
+ └── index.ts
1299
+ ```
1300
+
1301
+ ---
1302
+
1303
+ ## Build Reference
1304
+
1305
+ ```bash
1306
+ # Install dependencies
1307
+ npm install
1308
+
1309
+ # Start dev server with HMR (demo app at localhost:5173)
1310
+ npm run dev
1311
+
1312
+ # Build library — ES + UMD, minified + obfuscated for production
1313
+ NODE_ENV=production npm run build:lib
1314
+
1315
+ # Type-check
1316
+ npm run typecheck
1317
+
1318
+ # Lint
1319
+ npm run lint
1320
+
1321
+ # Preview production build
1322
+ npm run preview
1323
+ ```
1324
+
1325
+ ### Build outputs
1326
+
1327
+ | File | Format | Minified | Use case |
1328
+ | ------------------------- | ------- | --------- | --------------------------------------- |
1329
+ | `dist/chartforge.js` | ESM | prod only | Modern bundlers (Webpack, Vite, Rollup) |
1330
+ | `dist/chartforge.umd.cjs` | UMD/CJS | prod only | `<script>` tag, `require()`, Laravel |
1331
+ | `dist/plugins.js` | ESM | prod only | Tree-shakeable plugin imports |
1332
+ | `dist/themes.js` | ESM | prod only | Tree-shakeable theme imports |
1333
+ | `dist/types/` | `.d.ts` | — | TypeScript consumers |
1334
+
1335
+ ---
1336
+
1337
+ ## License
1338
+
1339
+ MIT © Anand Pilania
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "chartforge",
3
+ "version": "0.0.1",
4
+ "description": "Production-grade pluggable SVG charting library — zero dependencies",
5
+ "type": "module",
6
+ "main": "./dist/chartforge.umd.cjs",
7
+ "module": "./dist/chartforge.js",
8
+ "types": "./dist/types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/chartforge.js",
12
+ "require": "./dist/chartforge.umd.cjs",
13
+ "types": "./dist/types/index.d.ts"
14
+ },
15
+ "./plugins": {
16
+ "import": "./dist/plugins.js",
17
+ "require": "./dist/plugins.umd.cjs"
18
+ },
19
+ "./themes": {
20
+ "import": "./dist/themes.js",
21
+ "require": "./dist/themes.umd.cjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md"
27
+ ],
28
+ "scripts": {
29
+ "dev": "vite serve demo --config vite.config.ts",
30
+ "build:lib:dev": "",
31
+ "build": "vite build --config vite.config.ts && tsc --emitDeclarationOnly --declarationDir dist/types",
32
+ "build:lib": "vite build --config vite.lib.config.ts",
33
+ "preview": "vite preview demo",
34
+ "typecheck": "tsc --noEmit",
35
+ "lint": "eslint src --ext .ts"
36
+ },
37
+ "devDependencies": {
38
+ "vite": "^5.4.0",
39
+ "vite-plugin-dts": "^4.0.0",
40
+ "@vitejs/plugin-legacy": "^5.0.0",
41
+ "typescript": "^5.5.0",
42
+ "@types/node": "^22.0.0",
43
+ "terser": "^5.31.0",
44
+ "eslint": "^9.0.0",
45
+ "globals": "^15.0.0"
46
+ },
47
+ "keywords": [
48
+ "chart",
49
+ "charting",
50
+ "visualization",
51
+ "svg",
52
+ "graphs",
53
+ "data-visualization",
54
+ "pluggable",
55
+ "zero-dependencies",
56
+ "real-time",
57
+ "websocket",
58
+ "animation"
59
+ ],
60
+ "author": "Anand Pilania <pilaniaanand@gmail.com>",
61
+ "license": "MIT",
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "https://github.com/anandpilania/chartforge"
65
+ }
66
+ }