apexify.js 3.2.5 → 3.3.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 (75) hide show
  1. package/.tsbuildinfo +1 -1
  2. package/README.md +4 -806
  3. package/change logs.md +19 -0
  4. package/dist/ai/ApexAI.d.ts +15 -6
  5. package/dist/ai/ApexAI.d.ts.map +1 -1
  6. package/dist/ai/ApexAI.js +102 -29
  7. package/dist/ai/ApexAI.js.map +1 -1
  8. package/dist/ai/buttons/tools.d.ts.map +1 -1
  9. package/dist/ai/buttons/tools.js +1 -1
  10. package/dist/ai/buttons/tools.js.map +1 -1
  11. package/dist/ai/functions/aivoice.d.ts +1 -0
  12. package/dist/ai/functions/aivoice.d.ts.map +1 -0
  13. package/dist/ai/functions/aivoice.js +2 -0
  14. package/dist/ai/functions/aivoice.js.map +1 -0
  15. package/dist/ai/functions/draw.d.ts +1 -1
  16. package/dist/ai/functions/draw.d.ts.map +1 -1
  17. package/dist/ai/functions/draw.js +12 -1
  18. package/dist/ai/functions/draw.js.map +1 -1
  19. package/dist/ai/functions/generateVoiceResponse.d.ts +1 -1
  20. package/dist/ai/functions/generateVoiceResponse.d.ts.map +1 -1
  21. package/dist/ai/functions/generateVoiceResponse.js +3 -3
  22. package/dist/ai/functions/generateVoiceResponse.js.map +1 -1
  23. package/dist/ai/models.d.ts +1 -1
  24. package/dist/ai/models.d.ts.map +1 -1
  25. package/dist/ai/models.js +46 -28
  26. package/dist/ai/models.js.map +1 -1
  27. package/dist/ai/utils.d.ts.map +1 -1
  28. package/dist/ai/utils.js.map +1 -1
  29. package/dist/canvas/ApexPainter.d.ts +10 -10
  30. package/dist/canvas/ApexPainter.d.ts.map +1 -1
  31. package/dist/canvas/ApexPainter.js +21 -26
  32. package/dist/canvas/ApexPainter.js.map +1 -1
  33. package/dist/canvas/utils/bg.d.ts +1 -2
  34. package/dist/canvas/utils/bg.d.ts.map +1 -1
  35. package/dist/canvas/utils/bg.js +2 -5
  36. package/dist/canvas/utils/bg.js.map +1 -1
  37. package/dist/canvas/utils/charts.js +26 -26
  38. package/dist/canvas/utils/charts.js.map +1 -1
  39. package/dist/canvas/utils/general functions.d.ts +7 -7
  40. package/dist/canvas/utils/general functions.d.ts.map +1 -1
  41. package/dist/canvas/utils/general functions.js +47 -52
  42. package/dist/canvas/utils/general functions.js.map +1 -1
  43. package/dist/canvas/utils/types.d.ts +1 -3
  44. package/dist/canvas/utils/types.d.ts.map +1 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +45 -1
  47. package/dist/index.js.map +1 -1
  48. package/lib/ai/ApexAI.ts +550 -0
  49. package/lib/ai/buttons/drawMenu.ts +361 -0
  50. package/lib/ai/buttons/tools.ts +550 -0
  51. package/lib/ai/functions/chunkString.ts +3 -0
  52. package/lib/ai/functions/draw.ts +440 -0
  53. package/lib/ai/functions/generateVoiceResponse.ts +177 -0
  54. package/lib/ai/functions/imageReader.ts +24 -0
  55. package/lib/ai/functions/readFiles.ts +34 -0
  56. package/lib/ai/functions/readImagess.ts +41 -0
  57. package/lib/ai/functions/shouldDrawImage.ts +7 -0
  58. package/lib/ai/functions/typeWriter.ts +24 -0
  59. package/lib/ai/models.ts +589 -0
  60. package/lib/ai/utils.ts +23 -0
  61. package/lib/canvas/ApexPainter.ts +572 -0
  62. package/lib/canvas/utils/bg.ts +79 -0
  63. package/lib/canvas/utils/charts.ts +524 -0
  64. package/lib/canvas/utils/circular.ts +17 -0
  65. package/lib/canvas/utils/customLines.ts +49 -0
  66. package/lib/canvas/utils/general functions.ts +434 -0
  67. package/lib/canvas/utils/imageProperties.ts +403 -0
  68. package/lib/canvas/utils/radius.ts +26 -0
  69. package/lib/canvas/utils/textProperties.ts +68 -0
  70. package/lib/canvas/utils/types.ts +417 -0
  71. package/lib/canvas/utils/utils.ts +59 -0
  72. package/lib/index.ts +38 -0
  73. package/lib/utils.ts +8 -0
  74. package/package.json +15 -2
  75. package/tsconfig.json +21 -0
@@ -0,0 +1,524 @@
1
+ import { createCanvas, loadImage } from '@napi-rs/canvas';
2
+ import { barChart_1, bgConfig, DataItem, KeyBoxConfig, PieDataConfig, PieChartData, LineChartConfig, DataPoint } from "./types";
3
+ import path from 'path';
4
+ import axios from 'axios';
5
+
6
+
7
+ ////////////////////////////////////////BAR CHARTS////////////////////////////////////////
8
+
9
+ export async function verticalBarChart(data: barChart_1) {
10
+ try {
11
+ const { chartData, xLabels, yLabels, data: { xAxis, yAxis, keys, keyColor, xTitle, yTitle, labelStyle } } = data;
12
+
13
+ if (!xLabels || !yLabels || !xAxis || !yAxis) {
14
+ throw new Error('Required data is missing.');
15
+ }
16
+
17
+ xAxis.forEach(bar => {
18
+ if (bar.position.startsXLabel < Math.min(...xLabels) || bar.position.endsXLabel > Math.max(...xLabels)) {
19
+ throw new Error(`X-axis value range for bar '${bar.label}' is invalid.`);
20
+ }
21
+ if (bar.value < Math.min(...yAxis) || bar.value > Math.max(...yAxis)) {
22
+ throw new Error(`Y-axis value for bar '${bar.label}' is out of range.`);
23
+ }
24
+ });
25
+
26
+ const canvasWidth = chartData?.width || 800;
27
+ const canvasHeight = chartData?.height || 600;
28
+ let img: any;
29
+
30
+ if ((chartData?.widthPerc || 0.8) > 1 || (chartData?.widthPerc || 0.8) < 0) throw new Error(`widthPerc: Cannor be bigger than 1 or smaller than 0`)
31
+ if ((chartData?.heightPerc || 0.8) > 1 || (chartData?.heightPerc || 0.8) < 0) throw new Error(`widthPerc: Cannor be bigger than 1 or smaller than 0`)
32
+ const chartWidth = canvasWidth * (chartData?.widthPerc || 0.8);
33
+ const chartHeight = canvasHeight * (chartData?.heightPerc || 0.8);
34
+
35
+ if (chartData?.bg?.image) {
36
+ if (chartData.bg?.image.startsWith('http')) {
37
+ const response = await axios.get(chartData.bg?.image, { responseType: 'arraybuffer' });
38
+ img = await loadImage(Buffer.from(response.data, 'binary'));
39
+ } else {
40
+ const imagePath =path.join(process.cwd(), chartData.bg?.image);
41
+ img = await loadImage(imagePath);
42
+
43
+ }
44
+ }
45
+ const canvas = createCanvas(800, 600);
46
+ const ctx = canvas.getContext('2d');
47
+
48
+ if (chartData?.bg?.image) {
49
+ ctx.drawImage(img, 0, 0, 800, 600);
50
+ } else {
51
+ ctx.fillStyle = chartData?.bg?.bgColor || 'white';
52
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
53
+ }
54
+
55
+
56
+ ctx.fillStyle = `${chartData?.title?.color || 'black'}`;
57
+ ctx.font = `bold ${chartData?.title?.size || 12}px Arial`;
58
+ ctx.fillText(chartData?.title?.title || 'Sample Chart', (canvasWidth - ctx.measureText(chartData?.title?.title || 'Sample Chart').width) / 2, 30);
59
+
60
+ if (keys && Object.keys(keys).length > 0) {
61
+ const keysTopMargin = 70;
62
+ const keysLeftMargin = chartWidth + 60;
63
+ const keyWidth = 20;
64
+ const keyHeight = 20;
65
+ const keySpacing = 10;
66
+
67
+ let keyIndex = 0;
68
+ for (const color in keys) {
69
+ const keyX = keysLeftMargin;
70
+ const keyY = keysTopMargin + keyIndex * (keyHeight + keySpacing);
71
+ const keyColor = color || 'blue';
72
+ const keyLabel = keys[color].length > 15 ? keys[color].substring(0, 15) + '...' : keys[color];
73
+
74
+ ctx.fillStyle = keyColor;
75
+ ctx.fillRect(keyX, keyY, keyWidth, keyHeight);
76
+
77
+ ctx.fillStyle = `${keyColor || 'black'}`;
78
+ ctx.font = '12px Arial';
79
+ ctx.fillText(keyLabel, keyX + keyWidth + 5, keyY + keyHeight - 3);
80
+
81
+ keyIndex++;
82
+ }
83
+ }
84
+
85
+ const chartTopMargin = 50;
86
+ const chartLeftMargin = 50;
87
+
88
+ ctx.strokeStyle = chartData?.grid?.color || 'gray';
89
+ ctx.lineWidth = 1;
90
+ ctx.beginPath();
91
+ ctx.moveTo(chartLeftMargin, chartTopMargin);
92
+ ctx.lineTo(chartLeftMargin, chartTopMargin + chartHeight);
93
+ ctx.lineTo(chartLeftMargin + chartWidth, chartTopMargin + chartHeight);
94
+ ctx.stroke();
95
+
96
+ if (chartData?.grid?.enable) {
97
+ drawGridLines(ctx, chartLeftMargin, chartTopMargin, chartWidth, chartHeight, xLabels, yLabels, chartData.grid);
98
+ }
99
+
100
+ ctx.fillStyle = `${chartData?.labels?.color || 'black'}`;
101
+ ctx.font = `bold ${chartData?.labels?.fontSize || 16}px Arial`;
102
+ xLabels.forEach((label, index) => {
103
+ const x = chartLeftMargin + index * (chartWidth / (xLabels.length - 1));
104
+ const y = chartTopMargin + chartHeight + 20;
105
+ ctx.fillText(String(label), x - ctx.measureText(String(label)).width / 2, y);
106
+ });
107
+
108
+ ctx.textAlign = 'right';
109
+ yLabels.forEach((label, index) => {
110
+ const x = chartLeftMargin - 5;
111
+ const y = chartTopMargin + chartHeight - index * (chartHeight / (yLabels.length - 1));
112
+ ctx.fillText(String(label), x, y + 5);
113
+ });
114
+
115
+
116
+ ctx.fillStyle = `${chartData?.axis?.color|| 'black'}`;
117
+ ctx.font = `bold ${chartData?.axis?.size || 12}px Arial`;
118
+ ctx.fillText(xTitle || 'X Axis', 30 + canvasWidth / 2, canvasHeight - 15);
119
+
120
+ ctx.save();
121
+ ctx.translate(10, canvasHeight / 2);
122
+ ctx.rotate(-Math.PI / 2);
123
+ ctx.textAlign = 'center';
124
+ ctx.fillText(yTitle || 'Y Axis', 0, 10);
125
+ ctx.restore();
126
+
127
+ xAxis.forEach((bar, index) => {
128
+ const startX = chartLeftMargin + ((bar.position?.startsXLabel - Math.min(...xLabels)) / (Math.max(...xLabels) - Math.min(...xLabels))) * chartWidth;
129
+ const endX = chartLeftMargin + ((bar.position?.endsXLabel - Math.min(...xLabels)) / (Math.max(...xLabels) - Math.min(...xLabels))) * chartWidth;
130
+ const barWidth = endX - startX;
131
+ const barHeight = (bar.value / Math.max(...yAxis)) * chartHeight;
132
+ const y = chartTopMargin + chartHeight - barHeight;
133
+
134
+ ctx.fillStyle = bar.barColor || 'blue';
135
+ ctx.fillRect(startX, y, barWidth, barHeight);
136
+ ctx.strokeStyle = bar?.stroke?.color || 'black';
137
+ ctx.lineWidth = bar?.stroke?.width || 1;
138
+ ctx.strokeRect(startX, y, barWidth, barHeight);
139
+
140
+ ctx.fillStyle = `${labelStyle?.color || 'black'}`;
141
+ ctx.textAlign = 'center';
142
+ ctx.font = `bold ${labelStyle?.size || 16}px Arial`;
143
+ const labelX = startX + barWidth / 2;
144
+ const labelY = y - 5;
145
+ ctx.fillText(bar.label, labelX, labelY);
146
+ });
147
+
148
+ const buffer = canvas.toBuffer('image/png');
149
+ return buffer
150
+ } catch (error) {
151
+ console.error('An error occurred while drawing the bar chart:', error);
152
+ }
153
+ }
154
+
155
+ function drawGridLines(ctx: any, leftMargin: number, topMargin: number, width: number, height: number, xLabels: any, yLabels: any, grid: any) {
156
+ ctx.strokeStyle = grid.color || 'gray';
157
+ ctx.lineWidth = grid.width || 2;
158
+
159
+ xLabels.forEach((label: any, index: any) => {
160
+ const x = leftMargin + index * (width / (xLabels.length - 1));
161
+ ctx.beginPath();
162
+ ctx.moveTo(x, topMargin);
163
+ ctx.lineTo(x, topMargin + height);
164
+ ctx.stroke();
165
+ });
166
+
167
+ yLabels.forEach((label: any, index: any) => {
168
+ const y = topMargin + index * (height / (yLabels.length - 1));
169
+ ctx.beginPath();
170
+ ctx.moveTo(leftMargin, y);
171
+ ctx.lineTo(leftMargin + width, y);
172
+ ctx.stroke();
173
+ });
174
+ }
175
+
176
+
177
+ ////////////////////////////////////////PIE CHARTS////////////////////////////////////////
178
+
179
+
180
+ function drawPieChart(ctx: any, data: DataItem[], canvasConfig: bgConfig, pieDataConfig: PieDataConfig) {
181
+ const width = canvasConfig?.width ?? 1200;
182
+ const height = canvasConfig?.height ?? 400;
183
+ const centerX = width / 2 + (pieDataConfig?.x || 0);
184
+ const centerY = height / 2 + (pieDataConfig?.y || 0);
185
+ const radius = pieDataConfig?.radius || Math.min(width, height) * 0.35;
186
+ let startAngle = 0;
187
+ const totalValue = data.reduce((acc, { value }) => acc + value, 0);
188
+
189
+ for (const { label, color, value } of data) {
190
+ const sliceAngle = (value / totalValue) * (Math.PI * 2);
191
+ const midAngle = startAngle + sliceAngle / 2;
192
+ const labelDistance = pieDataConfig?.boxes?.labelDistance || 50;
193
+ const labelX = centerX + Math.cos(midAngle) * (radius + labelDistance);
194
+ const labelY = centerY + Math.sin(midAngle) * (radius + labelDistance);
195
+
196
+ ctx.strokeStyle = pieDataConfig?.boxes?.strokeColor || 'black';
197
+ ctx.beginPath();
198
+ ctx.moveTo(centerX, centerY);
199
+ ctx.lineTo(labelX, labelY);
200
+ ctx.stroke();
201
+
202
+ const boxWidth = pieDataConfig?.boxes?.width || 100;
203
+ const boxHeight = pieDataConfig?.boxes?.height || 40;
204
+ const boxX = labelX - boxWidth / 2;
205
+ const boxY = labelY - boxHeight / 2;
206
+ ctx.fillStyle = pieDataConfig?.boxes?.boxColor || 'black';
207
+ ctx.beginPath();
208
+ ctx.rect(boxX, boxY, boxWidth, boxHeight);
209
+ ctx.fill();
210
+ ctx.stroke();
211
+
212
+ // Draw the label text
213
+ ctx.fillStyle = pieDataConfig?.boxes?.labelColor || 'black';
214
+ ctx.font = `bold ${pieDataConfig?.boxes?.fontSize || 14}px Arial`;
215
+ ctx.textAlign = 'center';
216
+ ctx.textBaseline = 'middle';
217
+ ctx.fillText(label, labelX, labelY);
218
+
219
+ startAngle += sliceAngle;
220
+ }
221
+
222
+ startAngle = 0;
223
+ for (const { color, value } of data) {
224
+ const sliceAngle = (value / totalValue) * (Math.PI * 2);
225
+ ctx.fillStyle = color || 'black';
226
+ ctx.beginPath();
227
+ ctx.moveTo(centerX, centerY);
228
+ ctx.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
229
+ ctx.closePath();
230
+ ctx.fill();
231
+ startAngle += sliceAngle;
232
+ }
233
+
234
+ const strokeConfig = pieDataConfig.stroke || { color: 'transparent', size: 0 };
235
+ const { color: strokeColor, size: strokeWidth } = strokeConfig;
236
+ ctx.strokeStyle = strokeColor || 'transparent';
237
+ ctx.lineWidth = strokeWidth || 0;
238
+ ctx.beginPath();
239
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
240
+ ctx.stroke();
241
+
242
+ if (pieDataConfig.title) {
243
+ const { text, color, fontSize, x = 0, y = 0 } = pieDataConfig.title;
244
+ ctx.fillStyle = color || 'black';
245
+ ctx.font = `${fontSize || 20}px Arial`;
246
+ ctx.textAlign = 'center';
247
+ ctx.fillText(text || '', centerX + x, centerY + y);
248
+ }
249
+ }
250
+
251
+ function drawKeys(ctx: any, data: DataItem[], pieConfig: { keyBox: KeyBoxConfig; canvas: bgConfig }) {
252
+ const { bgcolor, width, height, radius, x, y, content } = pieConfig.keyBox || {};
253
+ const keyX = x || 0;
254
+ const keyY = y || 0;
255
+ ctx.fillStyle = bgcolor || 'white';
256
+ ctx.beginPath();
257
+ ctx.moveTo(keyX + (radius || 0), keyY);
258
+ ctx.lineTo(keyX + (width || 0) - (radius || 0), keyY);
259
+ ctx.arcTo(keyX + (width || 0), keyY, keyX + (width || 0), keyY + (radius || 0), radius || 0);
260
+ ctx.lineTo(keyX + (width || 0), keyY + (height || 0) - (radius || 0));
261
+ ctx.arcTo(keyX + (width || 0), keyY + (height || 0), keyX + (width || 0) - (radius || 0), keyY + (height || 0), radius || 0);
262
+ ctx.lineTo(keyX + (radius || 0), keyY + (height || 0));
263
+ ctx.arcTo(keyX, keyY + (height || 0), keyX, keyY + (height || 0) - (radius || 0), radius || 0);
264
+ ctx.lineTo(keyX, keyY + (radius || 0));
265
+ ctx.arcTo(keyX, keyY, keyX + (radius || 0), keyY, radius || 0);
266
+ ctx.closePath();
267
+ ctx.fill();
268
+
269
+ ctx.fillStyle = 'black';
270
+ ctx.font = `bold ${content?.keyTitle?.fontSize || 14}px Arial`;
271
+
272
+ const textWidth = ctx.measureText('Keys').width;
273
+ const textX = ((content?.keyTitle?.x || 0) + 20 + keyX + ((width || 0) - textWidth) / 2) || 0;
274
+ const textY = (keyY + 30 + (content?.keyTitle?.y || 0)) || 0;
275
+
276
+ ctx.fillText('Keys', textX, textY);
277
+
278
+ let fontSize = content?.keys?.fontSize || Math.min(16, Math.floor((width || 0) / 20));
279
+ ctx.font = `${fontSize}px Arial`;
280
+
281
+ data.forEach(({ label, color, key }, index) => {
282
+ ctx.fillStyle = color || 'black';
283
+ ctx.fillRect(keyX + 20, keyY + 60 + index * (fontSize + 10), 20, 20);
284
+ ctx.fillStyle = 'black';
285
+ ctx.fillText(`${key || ''}: ${label || ''}`, (keyX + 95 + (content?.keys?.x || 0)), (content?.keys?.y || 0) + keyY + 70 + index * (fontSize + 10));
286
+ });
287
+ }
288
+
289
+ export async function pieChart(pieChartData: PieChartData): Promise<Buffer> {
290
+ const { data = [], pieConfig = {} } = pieChartData;
291
+ const { canvas = {}, keyBox = {}, pieData = {} } = pieConfig;
292
+
293
+ const { width: canvasWidth = 1200, height: canvasHeight = 400, bgcolor: canvasBgcolor = 'gray' } = canvas || {};
294
+
295
+ const { width: keyBoxWidth = 200, height: keyBoxHeight = 300, radius: keyBoxRadius = 20, bgcolor: keyBoxBgcolor = '#ffffff', x: keyBoxX = 0, y: keyBoxY = 0, content: keyBoxContent } = keyBox || {};
296
+
297
+ const chartCanvas = createCanvas(canvasWidth, canvasHeight);
298
+ const ctx = chartCanvas.getContext('2d');
299
+
300
+ ctx.fillStyle = canvasBgcolor;
301
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
302
+
303
+ const pieConfigData: PieDataConfig = pieConfig.pieData ?? {};
304
+
305
+ drawPieChart(ctx, data, canvas, pieConfigData);
306
+
307
+ drawKeys(ctx, data, { keyBox: { bgcolor: keyBoxBgcolor, width: keyBoxWidth, height: keyBoxHeight, radius: keyBoxRadius, x: keyBoxX, y: keyBoxY, content: keyBoxContent }, canvas });
308
+
309
+ return chartCanvas.toBuffer('image/png');
310
+ }
311
+
312
+ ////////////////////////////////////////LINE CHARTS////////////////////////////////////////
313
+
314
+
315
+ export async function lineChart(data: { data: DataPoint[][], lineConfig: LineChartConfig }): Promise<Buffer> {
316
+ if (!data || !data.data || !Array.isArray(data.data) || data.data.length === 0 || !data.lineConfig) {
317
+ throw new Error('Invalid data object or missing data properties');
318
+ }
319
+
320
+ const lineConfig = data.lineConfig;
321
+ if (!lineConfig.yLabels || !Array.isArray(lineConfig.yLabels) || lineConfig.yLabels.length === 0) {
322
+ throw new Error('Missing or invalid yLabels property in line configuration');
323
+ }
324
+
325
+ if (!lineConfig.fillArea || !Array.isArray(lineConfig.fillArea) || lineConfig.fillArea.length !== data.data.length) {
326
+ throw new Error('Missing or invalid fillArea property in line configuration');
327
+ }
328
+
329
+ if (!lineConfig.lineColor || !Array.isArray(lineConfig.lineColor) || lineConfig.lineColor.length !== data.data.length) {
330
+ throw new Error('Missing or invalid lineColor property in line configuration');
331
+ }
332
+
333
+ if (!lineConfig.plot || typeof lineConfig.plot !== 'object') {
334
+ throw new Error('Missing or invalid plot property in line configuration');
335
+ }
336
+
337
+ if (!lineConfig.lineTension || !Array.isArray(lineConfig.lineTension) || lineConfig.lineTension.length !== data.data.length) {
338
+ throw new Error('Missing or invalid lineTension property in line configuration');
339
+ }
340
+
341
+ if (!lineConfig.grid || typeof lineConfig.grid !== 'object') {
342
+ throw new Error('Missing or invalid grid property in line configuration');
343
+ }
344
+
345
+ const canvasWidth = Math.max(data.lineConfig?.canvas?.width || 800, (data.data[0]?.length - 1) * 80 + 100);
346
+ const canvasHeight = Math.max(data.lineConfig?.canvas?.height || 600, (lineConfig.yLabels.length - 1) * 60 + 100);
347
+
348
+ let img: any;
349
+ if (data.lineConfig?.canvas?.image) {
350
+ if (data.lineConfig?.canvas?.image.startsWith('http')) {
351
+ const response = await axios.get(data.lineConfig?.canvas?.image, { responseType: 'arraybuffer' });
352
+ img = await loadImage(Buffer.from(response.data, 'binary'));
353
+ } else {
354
+ const imagePath = path.join(process.cwd(), data.lineConfig?.canvas?.image);
355
+ img = await loadImage(imagePath);
356
+
357
+ }
358
+ }
359
+
360
+
361
+ const canvas = createCanvas(canvasWidth, canvasHeight);
362
+ const ctx = canvas.getContext('2d');
363
+
364
+ const xAxisLabels = data.data[0].map(point => point.label);
365
+ const yAxisLabel = 'Y Axis';
366
+ ctx.font = '16px Arial';
367
+
368
+ if (data.lineConfig?.canvas?.image) {
369
+ ctx.drawImage(img, 0, 0, 800, 600);
370
+ } else {
371
+ ctx.fillStyle = data.lineConfig?.canvas?.bgColor || '#f0f0f0';
372
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
373
+ }
374
+
375
+ if (data.lineConfig?.grid) {
376
+ const grid = data.lineConfig.grid;
377
+ ctx.strokeStyle = grid.color || '#ccc';
378
+ ctx.lineWidth = grid.width || 1;
379
+
380
+ if (grid.type === 'vertical' || grid.type === 'both') {
381
+ for (let i = 1; i < xAxisLabels.length; i++) {
382
+ const x = i * (canvasWidth - 100) / (xAxisLabels.length - 1) + 50;
383
+ ctx.beginPath();
384
+ ctx.moveTo(x, 50);
385
+ ctx.lineTo(x, canvasHeight - 50);
386
+ ctx.stroke();
387
+ }
388
+ }
389
+
390
+ if (grid.type === 'horizontal' || grid.type === 'both') {
391
+ for (let i = 1; i < lineConfig.yLabels.length; i++) {
392
+ const y = i * (canvasHeight - 100) / (lineConfig.yLabels.length - 1) + 50;
393
+ ctx.beginPath();
394
+ ctx.moveTo(50, y);
395
+ ctx.lineTo(canvasWidth - 50, y);
396
+ ctx.stroke();
397
+ }
398
+ }
399
+ }
400
+
401
+ ctx.save();
402
+ ctx.translate(20, canvasHeight / 2);
403
+ ctx.rotate(-Math.PI / 2);
404
+ ctx.fillText(yAxisLabel, 0, 0);
405
+ ctx.restore();
406
+
407
+ let maxY = 0;
408
+ data.data.forEach(line => {
409
+ const maxLineY = Math.max(...line.map(point => point.y));
410
+ if (maxLineY > maxY) maxY = maxLineY;
411
+ });
412
+
413
+ const segmentWidth = (canvasWidth - 100) / (xAxisLabels.length - 1);
414
+
415
+ ctx.beginPath();
416
+ ctx.moveTo(50, 50);
417
+ ctx.lineTo(50, canvasHeight - 50);
418
+ ctx.lineTo(canvasWidth - 50, canvasHeight - 50);
419
+ ctx.stroke();
420
+
421
+ data.data.forEach((line, index) => {
422
+ ctx.beginPath();
423
+ ctx.strokeStyle = lineConfig?.lineColor[index] || 'blue';
424
+ ctx.lineWidth = 2;
425
+
426
+ const tension = lineConfig.lineTension[index] || 0.1;
427
+
428
+ line.forEach((point, index) => {
429
+ const x = index * segmentWidth + 50;
430
+ const y = canvasHeight - (point.y / maxY) * (canvasHeight - 100) - 50;
431
+ if (index === 0) {
432
+ ctx.moveTo(x, y);
433
+ } else {
434
+ const prevX = (index - 1) * segmentWidth + 50;
435
+ const prevY = canvasHeight - (line[index - 1].y / maxY) * (canvasHeight - 100) - 50;
436
+ const cpX1 = prevX + (x - prevX) * tension;
437
+ const cpY1 = prevY;
438
+ const cpX2 = x - (x - prevX) * tension;
439
+ const cpY2 = y;
440
+ ctx.bezierCurveTo(cpX1, cpY1, cpX2, cpY2, x, y);
441
+ }
442
+ });
443
+
444
+ ctx.stroke();
445
+
446
+ if (lineConfig.fillArea[index]) {
447
+ const lastPoint = line[line.length - 1];
448
+ const lastX = (line.length - 1) * segmentWidth + 50;
449
+ const lastY = canvasHeight - (lastPoint.y / maxY) * (canvasHeight - 100) - 50;
450
+
451
+ ctx.lineTo(lastX, canvasHeight - 50);
452
+ ctx.lineTo(50, canvasHeight - 50);
453
+ ctx.closePath();
454
+ ctx.fillStyle = lineConfig.fillArea[index].color || 'rgba(0, 0, 255, 0.1)';
455
+ ctx.fill();
456
+ }
457
+
458
+ if (data.lineConfig?.plot && data.lineConfig?.plot?.enable) {
459
+ const plotConfig = data.lineConfig?.plot;
460
+ ctx.fillStyle = plotConfig.color ? plotConfig.color[index] || 'red' : 'red';
461
+ line.forEach((point, index) => {
462
+ const x = index * segmentWidth + 50;
463
+ const y = canvasHeight - (point.y / maxY) * (canvasHeight - 100) - 50;
464
+ ctx.beginPath();
465
+ ctx.arc(x, y, plotConfig.size || 4, 0, 2 * Math.PI);
466
+ ctx.fill();
467
+ });
468
+ }
469
+ });
470
+
471
+
472
+ ctx.fillStyle = data.lineConfig?.canvas?.fontColor || 'black';
473
+ ctx.font = `${data.lineConfig?.canvas?.fontSize || 16}px Arial`;
474
+ ctx.textAlign = 'center';
475
+ ctx.textBaseline = 'top';
476
+ xAxisLabels.forEach((label, index) => {
477
+ const x = index * segmentWidth + 50;
478
+ const y = canvasHeight - 30;
479
+ ctx.fillText(label, x, y);
480
+ });
481
+
482
+ ctx.textAlign = 'right';
483
+ ctx.textBaseline = 'middle';
484
+ lineConfig.yLabels.forEach((label, index) => {
485
+ const y = canvasHeight - index * (canvasHeight - 100) / (lineConfig.yLabels.length - 1) - 50;
486
+ ctx.fillText(label, 40, y);
487
+ });
488
+
489
+
490
+ if (lineConfig.keys && typeof lineConfig.keys === 'object') {
491
+ const keyRadius = lineConfig.keysConfig?.radius || 10;
492
+ const keyPadding = lineConfig.keysConfig?.keyPadding || 30;
493
+ const textPadding = lineConfig.keysConfig?.textPadding || 60;
494
+ const lineLength = 2 * keyRadius + 9 + (lineConfig.keysConfig?.textPadding || 0);
495
+ const lineWidth = lineConfig.keysConfig?.lineWidth || 3;
496
+ let totalKeyWidth = 0;
497
+ for (const color in lineConfig.keys) {
498
+ const keyText = lineConfig.keys[color];
499
+ totalKeyWidth += ctx.measureText(keyText).width + keyPadding + (2 * keyRadius) + textPadding;
500
+ }
501
+ let startX = (canvasWidth - totalKeyWidth) / 2;
502
+ for (const color in lineConfig.keys) {
503
+ const keyText = lineConfig.keys[color];
504
+ ctx.beginPath();
505
+ ctx.arc(startX + keyRadius, 20 + keyRadius, keyRadius, 0, 2 * Math.PI);
506
+ ctx.fillStyle = color;
507
+ ctx.fill();
508
+ ctx.closePath();
509
+ ctx.beginPath();
510
+ ctx.moveTo(startX + keyRadius - lineLength / 2, 20 + keyRadius);
511
+ ctx.lineTo(startX + keyRadius + lineLength / 2, 20 + keyRadius);
512
+ ctx.strokeStyle = color;
513
+ ctx.lineWidth = lineWidth;
514
+ ctx.stroke();
515
+ ctx.closePath();
516
+ ctx.fillStyle = lineConfig.keysConfig?.fontColor || 'black';
517
+ ctx.fillText(keyText, startX + (2 * keyRadius) + keyPadding + textPadding, 20 + keyRadius);
518
+ startX += ctx.measureText(keyText).width + keyPadding + (2 * keyRadius) + textPadding + keyPadding;
519
+ }
520
+ }
521
+
522
+
523
+ return canvas.toBuffer('image/png');
524
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Applies a circular border to the canvas context.
3
+ * @param ctx The canvas rendering context.
4
+ * @param width The width of the canvas.
5
+ * @param height The height of the canvas.
6
+ * @param radius The radius of the circular border.
7
+ * @returns A void.
8
+ */
9
+ export function circularBorder(ctx: any, width: number, height: number, radius: number = 0): void {
10
+ const minDimension = Math.min(width, height);
11
+ const clipRadius = minDimension / 2 + radius;
12
+ ctx.save();
13
+ ctx.beginPath();
14
+ ctx.arc(width / 2, height / 2, clipRadius, 0, Math.PI * 2);
15
+ ctx.closePath();
16
+ ctx.clip();
17
+ }
@@ -0,0 +1,49 @@
1
+
2
+ export function customLines (ctx: any, options: any) {
3
+ for (const customOption of options) {
4
+ ctx.beginPath();
5
+ ctx.moveTo(customOption.startCoordinates.x, customOption.startCoordinates.y);
6
+ ctx.lineTo(customOption.endCoordinates.x, customOption.endCoordinates.y);
7
+ ctx.lineWidth = customOption.lineStyle.width || 1;
8
+ ctx.strokeStyle = customOption.lineStyle.color || 'black';
9
+
10
+ if (typeof customOption.lineStyle.lineRadius === 'number') {
11
+ const borderRadius = customOption.lineStyle.lineRadius;
12
+ const dx = customOption.endCoordinates.x - customOption.startCoordinates.x;
13
+ const dy = customOption.endCoordinates.y - customOption.startCoordinates.y;
14
+ const angle = Math.atan2(dy, dx);
15
+ const offsetX = Math.cos(angle) * borderRadius;
16
+ const offsetY = Math.sin(angle) * borderRadius;
17
+ ctx.moveTo(customOption.startCoordinates.x + offsetX, customOption.startCoordinates.y + offsetY);
18
+ ctx.lineTo(customOption.endCoordinates.x - offsetX, customOption.endCoordinates.y - offsetY);
19
+ } else if (customOption.lineStyle.lineRadius === 'circular') {
20
+ const dx = customOption.endCoordinates.x - customOption.startCoordinates.x;
21
+ const dy = customOption.endCoordinates.y - customOption.startCoordinates.y;
22
+ const length = Math.sqrt(dx * dx + dy * dy);
23
+ const angle = Math.atan2(dy, dx);
24
+ const halfWidth = customOption.lineStyle.width ? customOption.lineStyle.width / 2 : 1;
25
+ ctx.moveTo(customOption.startCoordinates.x + Math.cos(angle - Math.PI / 2) * halfWidth, customOption.startCoordinates.y + Math.sin(angle - Math.PI / 2) * halfWidth);
26
+ ctx.arc(customOption.startCoordinates.x, customOption.startCoordinates.y, length / 2, angle - Math.PI / 2, angle + Math.PI / 2);
27
+ ctx.lineTo(customOption.endCoordinates.x + Math.cos(angle + Math.PI / 2) * halfWidth, customOption.endCoordinates.y + Math.sin(angle + Math.PI / 2) * halfWidth);
28
+ ctx.arc(customOption.endCoordinates.x, customOption.endCoordinates.y, length / 2, angle + Math.PI / 2, angle - Math.PI / 2, true);
29
+ ctx.closePath();
30
+ }
31
+
32
+ ctx.stroke();
33
+
34
+ if (customOption.lineStyle.stroke) {
35
+ const stroke = customOption.lineStyle.stroke;
36
+ ctx.strokeStyle = stroke.color || 'black';
37
+ ctx.lineWidth = stroke.width || 1;
38
+ ctx.stroke();
39
+ }
40
+
41
+ if (customOption.lineStyle.shadow) {
42
+ const shadow = customOption.lineStyle.shadow;
43
+ ctx.shadowOffsetX = shadow.offsetX || 0;
44
+ ctx.shadowOffsetY = shadow.offsetY || 0;
45
+ ctx.shadowBlur = shadow.blur || 0;
46
+ ctx.shadowColor = shadow.color || 'black';
47
+ }
48
+ }
49
+ }