@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.
- package/README.md +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +4 -12
- package/src/index.ts +2 -2
- package/src/types.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/charts/canvas-renderer.d.ts.bak +0 -40
- package/dist/charts/canvas-renderer.js.bak +0 -580
- package/dist/charts/svg-renderer.d.ts.bak +0 -21
- package/dist/charts/svg-renderer.js.bak +0 -285
- package/dist/index.d.ts.bak +0 -34
- package/dist/index.js.bak +0 -35
- package/dist/types.d.ts.bak +0 -342
- package/dist/types.js.bak +0 -39
- package/dist/utils/data-processor.d.ts.bak +0 -64
- package/dist/utils/data-processor.js.bak +0 -326
- package/src/charts/canvas-renderer.ts.bak +0 -735
- package/src/charts/svg-renderer.test.ts.bak +0 -837
- package/src/charts/svg-renderer.ts.bak +0 -380
- package/src/index.ts.bak +0 -64
- package/src/types.ts.bak +0 -410
- package/src/utils/data-processor.test.ts.bak +0 -720
- package/src/utils/data-processor.ts.bak +0 -420
|
@@ -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
|