@allternit/viz 0.1.0 → 0.1.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.
@@ -1,580 +0,0 @@
1
- /**
2
- * Canvas Chart Renderer
3
- *
4
- * Renders charts to HTML Canvas with animation support.
5
- */
6
- import { palettes } from '../types';
7
- /**
8
- * Create Canvas chart renderer
9
- */
10
- export function createCanvasRenderer() {
11
- let currentCanvas = null;
12
- let currentCtx = null;
13
- const defaultWidth = 800;
14
- const defaultHeight = 400;
15
- const margin = { top: 60, right: 40, bottom: 60, left: 80 };
16
- /**
17
- * Create and setup canvas
18
- */
19
- function setupCanvas(width, height) {
20
- if (typeof document === 'undefined') {
21
- // Node.js environment - create mock canvas
22
- currentCanvas = {
23
- width,
24
- height,
25
- getContext: () => ({
26
- fillStyle: '',
27
- strokeStyle: '',
28
- lineWidth: 1,
29
- font: '',
30
- textAlign: 'left',
31
- textBaseline: 'alphabetic',
32
- fillRect: () => { },
33
- strokeRect: () => { },
34
- clearRect: () => { },
35
- beginPath: () => { },
36
- closePath: () => { },
37
- moveTo: () => { },
38
- lineTo: () => { },
39
- arc: () => { },
40
- fill: () => { },
41
- stroke: () => { },
42
- fillText: () => { },
43
- strokeText: () => { },
44
- save: () => { },
45
- restore: () => { },
46
- translate: () => { },
47
- scale: () => { },
48
- rotate: () => { },
49
- measureText: () => ({ width: 0 }),
50
- }),
51
- toDataURL: () => '',
52
- };
53
- currentCtx = currentCanvas.getContext('2d');
54
- }
55
- else {
56
- currentCanvas = document.createElement('canvas');
57
- currentCanvas.width = width;
58
- currentCanvas.height = height;
59
- currentCtx = currentCanvas.getContext('2d');
60
- }
61
- if (!currentCtx) {
62
- throw new Error('Failed to get canvas context');
63
- }
64
- return {
65
- ctx: currentCtx,
66
- width,
67
- height,
68
- chartWidth: width - margin.left - margin.right,
69
- chartHeight: height - margin.top - margin.bottom,
70
- margin,
71
- };
72
- }
73
- /**
74
- * Render chart to canvas
75
- */
76
- function render(config, series) {
77
- const width = config.width || defaultWidth;
78
- const height = config.height || defaultHeight;
79
- const palette = palettes[config.theme || 'default'];
80
- const rc = setupCanvas(width, height);
81
- const { ctx } = rc;
82
- // Clear background
83
- ctx.fillStyle = palette.background[0];
84
- ctx.fillRect(0, 0, width, height);
85
- // Title
86
- if (config.title) {
87
- ctx.fillStyle = palette.text.primary;
88
- ctx.font = 'bold 18px sans-serif';
89
- ctx.textAlign = 'center';
90
- ctx.fillText(config.title, width / 2, 30);
91
- }
92
- // Clip to chart area
93
- ctx.save();
94
- ctx.translate(margin.left, margin.top);
95
- // Render based on chart type
96
- switch (config.type) {
97
- case 'bar':
98
- renderBarChart(rc, series, palette, config);
99
- break;
100
- case 'line':
101
- renderLineChart(rc, series, palette, config);
102
- break;
103
- case 'pie':
104
- case 'donut':
105
- renderPieChart(rc, series, palette, config);
106
- break;
107
- case 'area':
108
- renderAreaChart(rc, series, palette, config);
109
- break;
110
- default:
111
- renderPlaceholder(rc, config.type, palette);
112
- }
113
- // Axes
114
- if (['bar', 'line', 'area'].includes(config.type)) {
115
- renderAxes(rc, series, config, palette);
116
- }
117
- ctx.restore();
118
- return currentCanvas;
119
- }
120
- /**
121
- * Render with animation
122
- */
123
- function renderAnimated(config, series, duration = 1000) {
124
- return new Promise((resolve) => {
125
- const startTime = performance.now();
126
- const width = config.width || defaultWidth;
127
- const height = config.height || defaultHeight;
128
- const palette = palettes[config.theme || 'default'];
129
- function animate(currentTime) {
130
- const elapsed = currentTime - startTime;
131
- const progress = Math.min(elapsed / duration, 1);
132
- const eased = easeOutCubic(progress);
133
- // Render with animation progress
134
- const rc = setupCanvas(width, height);
135
- const { ctx } = rc;
136
- // Clear background
137
- ctx.fillStyle = palette.background[0];
138
- ctx.fillRect(0, 0, width, height);
139
- // Title
140
- if (config.title) {
141
- ctx.fillStyle = palette.text.primary;
142
- ctx.font = 'bold 18px sans-serif';
143
- ctx.textAlign = 'center';
144
- ctx.fillText(config.title, width / 2, 30);
145
- }
146
- ctx.save();
147
- ctx.translate(margin.left, margin.top);
148
- // Render with animation progress
149
- switch (config.type) {
150
- case 'bar':
151
- renderBarChartAnimated(rc, series, palette, config, eased);
152
- break;
153
- case 'line':
154
- renderLineChartAnimated(rc, series, palette, config, eased);
155
- break;
156
- case 'pie':
157
- case 'donut':
158
- renderPieChartAnimated(rc, series, palette, config, eased);
159
- break;
160
- case 'area':
161
- renderAreaChartAnimated(rc, series, palette, config, eased);
162
- break;
163
- default:
164
- renderPlaceholder(rc, config.type, palette);
165
- }
166
- // Axes
167
- if (['bar', 'line', 'area'].includes(config.type)) {
168
- renderAxes(rc, series, config, palette);
169
- }
170
- ctx.restore();
171
- if (progress < 1) {
172
- requestAnimationFrame(animate);
173
- }
174
- else {
175
- resolve(currentCanvas);
176
- }
177
- }
178
- requestAnimationFrame(animate);
179
- });
180
- }
181
- /**
182
- * Easing function for smooth animations
183
- */
184
- function easeOutCubic(t) {
185
- return 1 - Math.pow(1 - t, 3);
186
- }
187
- /**
188
- * Render bar chart
189
- */
190
- function renderBarChart(rc, series, palette, config) {
191
- if (series.length === 0)
192
- return;
193
- const { ctx, chartWidth, chartHeight } = rc;
194
- const allData = series.flatMap((s) => s.data);
195
- const maxValue = Math.max(...allData.map((d) => (d.y || d.value || 0)));
196
- const categories = [...new Set(allData.map((d) => d.x || d.name))];
197
- const barWidth = (chartWidth / categories.length) * 0.6 / series.length;
198
- const scaleY = chartHeight / (maxValue * 1.1);
199
- series.forEach((s, seriesIndex) => {
200
- const color = s.color || palette.primary[seriesIndex % palette.primary.length];
201
- ctx.fillStyle = color;
202
- s.data.forEach((point, pointIndex) => {
203
- const value = (point.y || point.value || 0);
204
- const barHeight = value * scaleY;
205
- const x = (pointIndex + 0.2) * (chartWidth / categories.length) + seriesIndex * barWidth;
206
- const y = chartHeight - barHeight;
207
- ctx.fillRect(x, y, barWidth, barHeight);
208
- });
209
- });
210
- }
211
- /**
212
- * Render animated bar chart
213
- */
214
- function renderBarChartAnimated(rc, series, palette, config, progress) {
215
- if (series.length === 0)
216
- return;
217
- const { ctx, chartWidth, chartHeight } = rc;
218
- const allData = series.flatMap((s) => s.data);
219
- const maxValue = Math.max(...allData.map((d) => (d.y || d.value || 0)));
220
- const categories = [...new Set(allData.map((d) => d.x || d.name))];
221
- const barWidth = (chartWidth / categories.length) * 0.6 / series.length;
222
- const scaleY = chartHeight / (maxValue * 1.1);
223
- series.forEach((s, seriesIndex) => {
224
- const color = s.color || palette.primary[seriesIndex % palette.primary.length];
225
- ctx.fillStyle = color;
226
- s.data.forEach((point, pointIndex) => {
227
- const value = (point.y || point.value || 0);
228
- const barHeight = value * scaleY * progress;
229
- const x = (pointIndex + 0.2) * (chartWidth / categories.length) + seriesIndex * barWidth;
230
- const y = chartHeight - barHeight;
231
- ctx.fillRect(x, y, barWidth, barHeight);
232
- });
233
- });
234
- }
235
- /**
236
- * Render line chart
237
- */
238
- function renderLineChart(rc, series, palette, config) {
239
- if (series.length === 0)
240
- return;
241
- const { ctx, chartWidth, chartHeight } = rc;
242
- const allData = series.flatMap((s) => s.data);
243
- const maxValue = Math.max(...allData.map((d) => (d.y || 0)));
244
- const scaleX = chartWidth / (allData.length - 1 || 1);
245
- const scaleY = chartHeight / (maxValue * 1.1);
246
- series.forEach((s, seriesIndex) => {
247
- const color = s.color || palette.primary[seriesIndex % palette.primary.length];
248
- ctx.strokeStyle = color;
249
- ctx.lineWidth = 2;
250
- ctx.beginPath();
251
- s.data.forEach((point, index) => {
252
- const x = index * scaleX;
253
- const y = chartHeight - (point.y || 0) * scaleY;
254
- if (index === 0) {
255
- ctx.moveTo(x, y);
256
- }
257
- else {
258
- ctx.lineTo(x, y);
259
- }
260
- });
261
- ctx.stroke();
262
- // Data points
263
- if (config.dataLabels) {
264
- ctx.fillStyle = color;
265
- s.data.forEach((point, index) => {
266
- const x = index * scaleX;
267
- const y = chartHeight - (point.y || 0) * scaleY;
268
- ctx.beginPath();
269
- ctx.arc(x, y, 4, 0, Math.PI * 2);
270
- ctx.fill();
271
- });
272
- }
273
- });
274
- }
275
- /**
276
- * Render animated line chart
277
- */
278
- function renderLineChartAnimated(rc, series, palette, config, progress) {
279
- if (series.length === 0)
280
- return;
281
- const { ctx, chartWidth, chartHeight } = rc;
282
- const allData = series.flatMap((s) => s.data);
283
- const maxValue = Math.max(...allData.map((d) => (d.y || 0)));
284
- const scaleX = chartWidth / (allData.length - 1 || 1);
285
- const scaleY = chartHeight / (maxValue * 1.1);
286
- series.forEach((s, seriesIndex) => {
287
- const color = s.color || palette.primary[seriesIndex % palette.primary.length];
288
- ctx.strokeStyle = color;
289
- ctx.lineWidth = 2;
290
- ctx.beginPath();
291
- s.data.forEach((point, index) => {
292
- const x = index * scaleX;
293
- const y = chartHeight - (point.y || 0) * scaleY;
294
- if (index === 0) {
295
- ctx.moveTo(x, y);
296
- }
297
- else if (index / (s.data.length - 1) <= progress) {
298
- ctx.lineTo(x, y);
299
- }
300
- });
301
- ctx.stroke();
302
- });
303
- }
304
- /**
305
- * Render pie/donut chart
306
- */
307
- function renderPieChart(rc, series, palette, config) {
308
- if (series.length === 0)
309
- return;
310
- const { ctx, chartWidth, chartHeight } = rc;
311
- const data = series[0].data;
312
- const total = data.reduce((sum, d) => sum + (d.y || d.value || 0), 0);
313
- const centerX = chartWidth / 2;
314
- const centerY = chartHeight / 2;
315
- const radius = Math.min(chartWidth, chartHeight) / 2 * 0.8;
316
- const innerRadius = config.type === 'donut' ? radius * 0.5 : 0;
317
- let currentAngle = -Math.PI / 2;
318
- data.forEach((point, index) => {
319
- const value = (point.y || point.value || 0);
320
- const angle = (value / total) * 2 * Math.PI;
321
- const color = palette.primary[index % palette.primary.length];
322
- ctx.fillStyle = color;
323
- ctx.beginPath();
324
- if (innerRadius > 0) {
325
- // Donut
326
- ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + angle);
327
- ctx.arc(centerX, centerY, innerRadius, currentAngle + angle, currentAngle, true);
328
- }
329
- else {
330
- // Pie
331
- ctx.moveTo(centerX, centerY);
332
- ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + angle);
333
- }
334
- ctx.closePath();
335
- ctx.fill();
336
- currentAngle += angle;
337
- });
338
- }
339
- /**
340
- * Render animated pie/donut chart
341
- */
342
- function renderPieChartAnimated(rc, series, palette, config, progress) {
343
- if (series.length === 0)
344
- return;
345
- const { ctx, chartWidth, chartHeight } = rc;
346
- const data = series[0].data;
347
- const total = data.reduce((sum, d) => sum + (d.y || d.value || 0), 0);
348
- const centerX = chartWidth / 2;
349
- const centerY = chartHeight / 2;
350
- const radius = Math.min(chartWidth, chartHeight) / 2 * 0.8 * progress;
351
- const innerRadius = config.type === 'donut' ? radius * 0.5 : 0;
352
- let currentAngle = -Math.PI / 2;
353
- data.forEach((point, index) => {
354
- const value = (point.y || point.value || 0);
355
- const angle = (value / total) * 2 * Math.PI;
356
- const color = palette.primary[index % palette.primary.length];
357
- ctx.fillStyle = color;
358
- ctx.beginPath();
359
- if (innerRadius > 0) {
360
- ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + angle);
361
- ctx.arc(centerX, centerY, innerRadius, currentAngle + angle, currentAngle, true);
362
- }
363
- else {
364
- ctx.moveTo(centerX, centerY);
365
- ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + angle);
366
- }
367
- ctx.closePath();
368
- ctx.fill();
369
- currentAngle += angle;
370
- });
371
- }
372
- /**
373
- * Render area chart
374
- */
375
- function renderAreaChart(rc, series, palette, config) {
376
- if (series.length === 0)
377
- return;
378
- const { ctx, chartWidth, chartHeight } = rc;
379
- const allData = series.flatMap((s) => s.data);
380
- const maxValue = Math.max(...allData.map((d) => (d.y || 0)));
381
- const scaleX = chartWidth / (allData.length - 1 || 1);
382
- const scaleY = chartHeight / (maxValue * 1.1);
383
- series.forEach((s, seriesIndex) => {
384
- const color = s.color || palette.primary[seriesIndex % palette.primary.length];
385
- ctx.fillStyle = color + '40'; // 25% opacity
386
- ctx.strokeStyle = color;
387
- ctx.lineWidth = 2;
388
- ctx.beginPath();
389
- s.data.forEach((point, index) => {
390
- const x = index * scaleX;
391
- const y = chartHeight - (point.y || 0) * scaleY;
392
- if (index === 0) {
393
- ctx.moveTo(x, y);
394
- }
395
- else {
396
- ctx.lineTo(x, y);
397
- }
398
- });
399
- ctx.lineTo(chartWidth, chartHeight);
400
- ctx.lineTo(0, chartHeight);
401
- ctx.closePath();
402
- ctx.fill();
403
- ctx.stroke();
404
- });
405
- }
406
- /**
407
- * Render animated area chart
408
- */
409
- function renderAreaChartAnimated(rc, series, palette, config, progress) {
410
- if (series.length === 0)
411
- return;
412
- const { ctx, chartWidth, chartHeight } = rc;
413
- const allData = series.flatMap((s) => s.data);
414
- const maxValue = Math.max(...allData.map((d) => (d.y || 0)));
415
- const scaleX = chartWidth / (allData.length - 1 || 1);
416
- const scaleY = (chartHeight / (maxValue * 1.1)) * progress;
417
- series.forEach((s, seriesIndex) => {
418
- const color = s.color || palette.primary[seriesIndex % palette.primary.length];
419
- ctx.fillStyle = color + '40';
420
- ctx.strokeStyle = color;
421
- ctx.lineWidth = 2;
422
- ctx.beginPath();
423
- s.data.forEach((point, index) => {
424
- const x = index * scaleX;
425
- const y = chartHeight - (point.y || 0) * scaleY;
426
- if (index === 0) {
427
- ctx.moveTo(x, y);
428
- }
429
- else {
430
- ctx.lineTo(x, y);
431
- }
432
- });
433
- ctx.lineTo(chartWidth, chartHeight);
434
- ctx.lineTo(0, chartHeight);
435
- ctx.closePath();
436
- ctx.fill();
437
- ctx.stroke();
438
- });
439
- }
440
- /**
441
- * Render placeholder for unsupported chart types
442
- */
443
- function renderPlaceholder(rc, type, palette) {
444
- const { ctx, chartWidth, chartHeight } = rc;
445
- ctx.fillStyle = '#e5e7eb';
446
- ctx.fillRect(chartWidth / 4, chartHeight / 3, chartWidth / 2, chartHeight / 3);
447
- ctx.fillStyle = '#6b7280';
448
- ctx.font = '14px sans-serif';
449
- ctx.textAlign = 'center';
450
- ctx.fillText(`${type} chart`, chartWidth / 2, chartHeight / 2 + 5);
451
- }
452
- /**
453
- * Render axes
454
- */
455
- function renderAxes(rc, series, config, palette) {
456
- const { ctx, chartWidth, chartHeight } = rc;
457
- ctx.strokeStyle = palette.background[2];
458
- ctx.lineWidth = 1;
459
- ctx.fillStyle = palette.text.secondary;
460
- ctx.font = '12px sans-serif';
461
- ctx.textAlign = 'right';
462
- ctx.textBaseline = 'middle';
463
- // Y-axis
464
- ctx.beginPath();
465
- ctx.moveTo(0, 0);
466
- ctx.lineTo(0, chartHeight);
467
- ctx.stroke();
468
- // X-axis
469
- ctx.beginPath();
470
- ctx.moveTo(0, chartHeight);
471
- ctx.lineTo(chartWidth, chartHeight);
472
- ctx.stroke();
473
- // Y-axis labels
474
- const maxValue = Math.max(...series.flatMap((s) => s.data.map((d) => (d.y || 0))));
475
- const steps = 5;
476
- for (let i = 0; i <= steps; i++) {
477
- const value = (maxValue / steps) * i;
478
- const y = chartHeight - (chartHeight / steps) * i;
479
- ctx.fillText(String(Math.round(value)), -10, y);
480
- // Grid line
481
- if (config.xAxis?.grid?.enabled !== false && i > 0) {
482
- ctx.save();
483
- ctx.strokeStyle = palette.background[2];
484
- ctx.setLineDash([4, 4]);
485
- ctx.beginPath();
486
- ctx.moveTo(0, y);
487
- ctx.lineTo(chartWidth, y);
488
- ctx.stroke();
489
- ctx.restore();
490
- }
491
- }
492
- // Axis titles
493
- if (config.yAxis?.title) {
494
- ctx.save();
495
- ctx.translate(-margin.left + 20, chartHeight / 2);
496
- ctx.rotate(-Math.PI / 2);
497
- ctx.textAlign = 'center';
498
- ctx.fillText(config.yAxis.title, 0, 0);
499
- ctx.restore();
500
- }
501
- if (config.xAxis?.title) {
502
- ctx.textAlign = 'center';
503
- ctx.fillText(config.xAxis.title, chartWidth / 2, chartHeight + 40);
504
- }
505
- }
506
- /**
507
- * Get chart metadata
508
- */
509
- function getMetadata(type) {
510
- const metadata = {
511
- line: {
512
- type: 'line',
513
- name: 'Line Chart',
514
- description: 'Show trends over time or categories',
515
- axes: ['x', 'y'],
516
- dataStructure: 'Array of { x, y } points',
517
- useCases: ['Time series', 'Trends', 'Progress over time'],
518
- },
519
- bar: {
520
- type: 'bar',
521
- name: 'Bar Chart',
522
- description: 'Compare values across categories',
523
- axes: ['x', 'y'],
524
- dataStructure: 'Array of { x, y } points',
525
- useCases: ['Comparisons', 'Rankings', 'Category distribution'],
526
- },
527
- pie: {
528
- type: 'pie',
529
- name: 'Pie Chart',
530
- description: 'Show part-to-whole relationships',
531
- axes: [],
532
- dataStructure: 'Array of { name, value }',
533
- useCases: ['Composition', 'Percentage distribution'],
534
- },
535
- donut: {
536
- type: 'donut',
537
- name: 'Donut Chart',
538
- description: 'Show part-to-whole with center space',
539
- axes: [],
540
- dataStructure: 'Array of { name, value }',
541
- useCases: ['Composition', 'Percentage distribution'],
542
- },
543
- area: {
544
- type: 'area',
545
- name: 'Area Chart',
546
- description: 'Show cumulative totals over time',
547
- axes: ['x', 'y'],
548
- dataStructure: 'Array of { x, y } points',
549
- useCases: ['Volume over time', 'Cumulative data'],
550
- },
551
- };
552
- return metadata[type] || metadata.line;
553
- }
554
- /**
555
- * Export to data URL
556
- */
557
- function toDataURL(format = 'png', quality = 0.92) {
558
- if (!currentCanvas)
559
- return null;
560
- return currentCanvas.toDataURL(`image/${format}`, quality);
561
- }
562
- /**
563
- * Get canvas element
564
- */
565
- function getCanvas() {
566
- return currentCanvas;
567
- }
568
- return {
569
- render,
570
- renderAnimated,
571
- getMetadata,
572
- toDataURL,
573
- getCanvas,
574
- };
575
- }
576
- /**
577
- * Global Canvas renderer instance
578
- */
579
- export const globalCanvasRenderer = createCanvasRenderer();
580
- //# sourceMappingURL=canvas-renderer.js.map
@@ -1,21 +0,0 @@
1
- /**
2
- * SVG Chart Renderer
3
- *
4
- * Renders charts to SVG format.
5
- */
6
- import type { ChartConfig, DataSeries, ChartMetadata } from '../types';
7
- export interface SVGRenderer {
8
- /** Render chart to SVG string */
9
- render(config: ChartConfig, series: DataSeries[]): string;
10
- /** Get chart metadata */
11
- getMetadata(type: string): ChartMetadata;
12
- }
13
- /**
14
- * Create SVG chart renderer
15
- */
16
- export declare function createSVGRenderer(): SVGRenderer;
17
- /**
18
- * Global SVG renderer instance
19
- */
20
- export declare const globalSVGRenderer: SVGRenderer;
21
- //# sourceMappingURL=svg-renderer.d.ts.map