@meonode/canvas 2.0.1 → 2.0.3

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 (121) hide show
  1. package/dist/cjs/canvas/canvas.helper.js +0 -230
  2. package/dist/cjs/canvas/canvas.helper.js.map +1 -1
  3. package/dist/cjs/canvas/chart.canvas.js +70 -144
  4. package/dist/cjs/canvas/chart.canvas.js.map +1 -1
  5. package/dist/cjs/canvas/grid.canvas.js.map +1 -1
  6. package/dist/cjs/canvas/image.canvas.js +2 -2
  7. package/dist/cjs/canvas/image.canvas.js.map +1 -1
  8. package/dist/cjs/canvas/layout.canvas.js +23 -16
  9. package/dist/cjs/canvas/layout.canvas.js.map +1 -1
  10. package/dist/cjs/canvas/root.canvas.js +23 -117
  11. package/dist/cjs/canvas/root.canvas.js.map +1 -1
  12. package/dist/cjs/canvas/text.canvas.js +2 -2
  13. package/dist/cjs/canvas/text.canvas.js.map +1 -1
  14. package/dist/cjs/constant/common.const.js.map +1 -1
  15. package/dist/cjs/{canvas → src/canvas}/canvas.helper.d.ts +1 -20
  16. package/dist/cjs/src/canvas/canvas.helper.d.ts.map +1 -0
  17. package/dist/{esm → cjs/src}/canvas/canvas.type.d.ts +1 -12
  18. package/dist/cjs/src/canvas/canvas.type.d.ts.map +1 -0
  19. package/dist/cjs/{canvas → src/canvas}/chart.canvas.d.ts +1 -1
  20. package/dist/cjs/src/canvas/chart.canvas.d.ts.map +1 -0
  21. package/dist/cjs/src/canvas/grid.canvas.d.ts.map +1 -0
  22. package/dist/{esm → cjs/src}/canvas/image.canvas.d.ts +1 -1
  23. package/dist/cjs/src/canvas/image.canvas.d.ts.map +1 -0
  24. package/dist/{esm → cjs/src}/canvas/layout.canvas.d.ts +2 -2
  25. package/dist/cjs/src/canvas/layout.canvas.d.ts.map +1 -0
  26. package/dist/{esm → cjs/src}/canvas/root.canvas.d.ts +3 -2
  27. package/dist/cjs/src/canvas/root.canvas.d.ts.map +1 -0
  28. package/dist/cjs/{canvas → src/canvas}/text.canvas.d.ts +1 -1
  29. package/dist/cjs/src/canvas/text.canvas.d.ts.map +1 -0
  30. package/dist/cjs/src/constant/common.const.d.ts +18 -0
  31. package/dist/cjs/src/constant/common.const.d.ts.map +1 -0
  32. package/dist/cjs/src/index.d.ts.map +1 -0
  33. package/dist/cjs/src/util/disk.cache.d.ts.map +1 -0
  34. package/dist/cjs/src/worker/comlink.pool.d.ts +30 -0
  35. package/dist/cjs/src/worker/comlink.pool.d.ts.map +1 -0
  36. package/dist/cjs/src/worker/comlink.setup.d.ts +4 -0
  37. package/dist/cjs/src/worker/comlink.setup.d.ts.map +1 -0
  38. package/dist/cjs/src/worker/render.worker.d.ts.map +1 -0
  39. package/dist/cjs/src/worker/worker.types.d.ts +13 -0
  40. package/dist/cjs/src/worker/worker.types.d.ts.map +1 -0
  41. package/dist/cjs/util/disk.cache.js.map +1 -1
  42. package/dist/cjs/worker/comlink.pool.js +164 -0
  43. package/dist/cjs/worker/comlink.pool.js.map +1 -0
  44. package/dist/cjs/worker/comlink.setup.js +53 -0
  45. package/dist/cjs/worker/comlink.setup.js.map +1 -0
  46. package/dist/cjs/worker/render.worker.js +58 -61
  47. package/dist/cjs/worker/render.worker.js.map +1 -1
  48. package/dist/esm/canvas/canvas.helper.js +1 -230
  49. package/dist/esm/canvas/chart.canvas.js +71 -145
  50. package/dist/esm/canvas/image.canvas.js +2 -2
  51. package/dist/esm/canvas/layout.canvas.js +23 -16
  52. package/dist/esm/canvas/root.canvas.js +23 -116
  53. package/dist/esm/canvas/text.canvas.js +2 -2
  54. package/dist/esm/{canvas → src/canvas}/canvas.helper.d.ts +1 -20
  55. package/dist/esm/src/canvas/canvas.helper.d.ts.map +1 -0
  56. package/dist/{cjs → esm/src}/canvas/canvas.type.d.ts +1 -12
  57. package/dist/esm/src/canvas/canvas.type.d.ts.map +1 -0
  58. package/dist/esm/{canvas → src/canvas}/chart.canvas.d.ts +1 -1
  59. package/dist/esm/src/canvas/chart.canvas.d.ts.map +1 -0
  60. package/dist/esm/src/canvas/grid.canvas.d.ts.map +1 -0
  61. package/dist/{cjs → esm/src}/canvas/image.canvas.d.ts +1 -1
  62. package/dist/esm/src/canvas/image.canvas.d.ts.map +1 -0
  63. package/dist/{cjs → esm/src}/canvas/layout.canvas.d.ts +2 -2
  64. package/dist/esm/src/canvas/layout.canvas.d.ts.map +1 -0
  65. package/dist/{cjs → esm/src}/canvas/root.canvas.d.ts +3 -2
  66. package/dist/esm/src/canvas/root.canvas.d.ts.map +1 -0
  67. package/dist/esm/{canvas → src/canvas}/text.canvas.d.ts +1 -1
  68. package/dist/esm/src/canvas/text.canvas.d.ts.map +1 -0
  69. package/dist/esm/src/constant/common.const.d.ts +18 -0
  70. package/dist/esm/src/constant/common.const.d.ts.map +1 -0
  71. package/dist/esm/src/index.d.ts.map +1 -0
  72. package/dist/esm/src/util/disk.cache.d.ts.map +1 -0
  73. package/dist/esm/src/worker/comlink.pool.d.ts +30 -0
  74. package/dist/esm/src/worker/comlink.pool.d.ts.map +1 -0
  75. package/dist/esm/src/worker/comlink.setup.d.ts +4 -0
  76. package/dist/esm/src/worker/comlink.setup.d.ts.map +1 -0
  77. package/dist/esm/src/worker/render.worker.d.ts.map +1 -0
  78. package/dist/esm/src/worker/worker.types.d.ts +13 -0
  79. package/dist/esm/src/worker/worker.types.d.ts.map +1 -0
  80. package/dist/esm/worker/comlink.pool.js +139 -0
  81. package/dist/esm/worker/comlink.setup.js +30 -0
  82. package/dist/esm/worker/render.worker.js +38 -60
  83. package/package.json +9 -8
  84. package/dist/cjs/canvas/canvas.helper.d.ts.map +0 -1
  85. package/dist/cjs/canvas/canvas.type.d.ts.map +0 -1
  86. package/dist/cjs/canvas/chart.canvas.d.ts.map +0 -1
  87. package/dist/cjs/canvas/grid.canvas.d.ts.map +0 -1
  88. package/dist/cjs/canvas/image.canvas.d.ts.map +0 -1
  89. package/dist/cjs/canvas/layout.canvas.d.ts.map +0 -1
  90. package/dist/cjs/canvas/root.canvas.d.ts.map +0 -1
  91. package/dist/cjs/canvas/text.canvas.d.ts.map +0 -1
  92. package/dist/cjs/constant/common.const.d.ts +0 -37
  93. package/dist/cjs/constant/common.const.d.ts.map +0 -1
  94. package/dist/cjs/index.d.ts.map +0 -1
  95. package/dist/cjs/util/disk.cache.d.ts.map +0 -1
  96. package/dist/cjs/worker/render.worker.d.ts.map +0 -1
  97. package/dist/cjs/worker/worker.types.d.ts +0 -76
  98. package/dist/cjs/worker/worker.types.d.ts.map +0 -1
  99. package/dist/esm/canvas/canvas.helper.d.ts.map +0 -1
  100. package/dist/esm/canvas/canvas.type.d.ts.map +0 -1
  101. package/dist/esm/canvas/chart.canvas.d.ts.map +0 -1
  102. package/dist/esm/canvas/grid.canvas.d.ts.map +0 -1
  103. package/dist/esm/canvas/image.canvas.d.ts.map +0 -1
  104. package/dist/esm/canvas/layout.canvas.d.ts.map +0 -1
  105. package/dist/esm/canvas/root.canvas.d.ts.map +0 -1
  106. package/dist/esm/canvas/text.canvas.d.ts.map +0 -1
  107. package/dist/esm/constant/common.const.d.ts +0 -37
  108. package/dist/esm/constant/common.const.d.ts.map +0 -1
  109. package/dist/esm/index.d.ts.map +0 -1
  110. package/dist/esm/util/disk.cache.d.ts.map +0 -1
  111. package/dist/esm/worker/render.worker.d.ts.map +0 -1
  112. package/dist/esm/worker/worker.types.d.ts +0 -76
  113. package/dist/esm/worker/worker.types.d.ts.map +0 -1
  114. /package/dist/cjs/{canvas → src/canvas}/grid.canvas.d.ts +0 -0
  115. /package/dist/cjs/{index.d.ts → src/index.d.ts} +0 -0
  116. /package/dist/cjs/{util → src/util}/disk.cache.d.ts +0 -0
  117. /package/dist/cjs/{worker → src/worker}/render.worker.d.ts +0 -0
  118. /package/dist/esm/{canvas → src/canvas}/grid.canvas.d.ts +0 -0
  119. /package/dist/esm/{index.d.ts → src/index.d.ts} +0 -0
  120. /package/dist/esm/{util → src/util}/disk.cache.d.ts +0 -0
  121. /package/dist/esm/{worker → src/worker}/render.worker.d.ts +0 -0
@@ -1,29 +1,7 @@
1
- import { BoxNode, RowNode, ColumnNode } from './layout.canvas.js';
1
+ import { BoxNode, RowNode } from './layout.canvas.js';
2
2
  import { Style } from '../constant/common.const.js';
3
3
  import { TextNode } from './text.canvas.js';
4
- import { ImageNode } from './image.canvas.js';
5
4
 
6
- /**
7
- * Local buildTree for pre-computed render function results.
8
- * Handles only node types that render functions would return (Box, Column, Row, Text, Image).
9
- * Avoids circular dependency with root.canvas.ts.
10
- */
11
- function buildDescriptorTree(descriptor) {
12
- switch (descriptor.__type) {
13
- case 'Box':
14
- return new BoxNode({ ...descriptor.props, children: descriptor.children?.map(buildDescriptorTree) });
15
- case 'Column':
16
- return new ColumnNode({ ...descriptor.props, children: descriptor.children?.map(buildDescriptorTree) });
17
- case 'Row':
18
- return new RowNode({ ...descriptor.props, children: descriptor.children?.map(buildDescriptorTree) });
19
- case 'Image':
20
- return new ImageNode(descriptor.props);
21
- case 'Text':
22
- return new TextNode(descriptor.text, descriptor.props);
23
- default:
24
- return new BoxNode({});
25
- }
26
- }
27
5
  class ChartNode extends BoxNode {
28
6
  chartData;
29
7
  chartType;
@@ -68,9 +46,9 @@ class ChartNode extends BoxNode {
68
46
  }
69
47
  }
70
48
  }
71
- _renderContent(ctx, x, y, width, height) {
49
+ async _renderContent(ctx, x, y, width, height) {
72
50
  // First render background/borders from parent
73
- super._renderContent(ctx, x, y, width, height);
51
+ await super._renderContent(ctx, x, y, width, height);
74
52
  // Then render chart-specific content
75
53
  const paddingLeft = this.node.getComputedPadding(Style.Edge.Left);
76
54
  const paddingRight = this.node.getComputedPadding(Style.Edge.Right);
@@ -82,16 +60,16 @@ class ChartNode extends BoxNode {
82
60
  const contentHeight = height - paddingTop - paddingBottom;
83
61
  switch (this.chartType) {
84
62
  case 'bar':
85
- this.renderBarChart(ctx, contentX, contentY, contentWidth, contentHeight);
63
+ await this.renderBarChart(ctx, contentX, contentY, contentWidth, contentHeight);
86
64
  break;
87
65
  case 'line':
88
- this.renderLineChart(ctx, contentX, contentY, contentWidth, contentHeight);
66
+ await this.renderLineChart(ctx, contentX, contentY, contentWidth, contentHeight);
89
67
  break;
90
68
  case 'pie':
91
- this.renderPieChart(ctx, contentX, contentY, contentWidth, contentHeight);
69
+ await this.renderPieChart(ctx, contentX, contentY, contentWidth, contentHeight);
92
70
  break;
93
71
  case 'doughnut':
94
- this.renderDoughnutChart(ctx, contentX, contentY, contentWidth, contentHeight);
72
+ await this.renderDoughnutChart(ctx, contentX, contentY, contentWidth, contentHeight);
95
73
  break;
96
74
  }
97
75
  }
@@ -211,7 +189,7 @@ class ChartNode extends BoxNode {
211
189
  chartY: chartAreaY,
212
190
  };
213
191
  }
214
- renderBarChart(ctx, x, y, width, height) {
192
+ async renderBarChart(ctx, x, y, width, height) {
215
193
  if (this.chartType !== 'bar')
216
194
  return;
217
195
  const chartData = this.chartData;
@@ -226,8 +204,7 @@ class ChartNode extends BoxNode {
226
204
  if (chartOptions?.showYAxis) {
227
205
  const fontSize = chartOptions.yAxisFontSize || 12;
228
206
  ctx.font = `${fontSize}px ${this.props.fontFamily || 'sans-serif'}`;
229
- const maxLabel = chartOptions._preComputedYAxisLabels?.[0] ??
230
- (chartOptions.yAxisLabelFormatter ? chartOptions.yAxisLabelFormatter(maxValue) : this.getSmartYAxisFormatter(maxValue)(maxValue));
207
+ const maxLabel = chartOptions.yAxisLabelFormatter ? await chartOptions.yAxisLabelFormatter(maxValue) : this.getSmartYAxisFormatter(maxValue)(maxValue);
231
208
  const yAxisWidth = ctx.measureText(maxLabel).width + 10;
232
209
  chartX += yAxisWidth;
233
210
  chartWidth -= yAxisWidth;
@@ -261,8 +238,7 @@ class ChartNode extends BoxNode {
261
238
  ctx.stroke();
262
239
  if (chartOptions?.showYAxis) {
263
240
  const value = maxValue - (maxValue / 5) * i;
264
- const label = chartOptions._preComputedYAxisLabels?.[i] ??
265
- (chartOptions.yAxisLabelFormatter ? chartOptions.yAxisLabelFormatter(value) : this.getSmartYAxisFormatter(maxValue)(value));
241
+ const label = chartOptions.yAxisLabelFormatter ? await chartOptions.yAxisLabelFormatter(value) : this.getSmartYAxisFormatter(maxValue)(value);
266
242
  TextNode.renderSimpleText(ctx, label, chartX - 5, gridY, {
267
243
  color: chartOptions.yAxisColor || chartOptions.axisColor || '#000',
268
244
  fontSize: chartOptions.yAxisFontSize || 12,
@@ -275,9 +251,11 @@ class ChartNode extends BoxNode {
275
251
  ctx.setLineDash([]);
276
252
  }
277
253
  // Render bars
278
- labels.forEach((label, index) => {
254
+ for (let index = 0; index < labels.length; index++) {
255
+ const label = labels[index];
279
256
  const groupX = chartX + index * groupWidth + barSpacing / 2;
280
- datasets.forEach((dataset, datasetIndex) => {
257
+ for (let datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
258
+ const dataset = datasets[datasetIndex];
281
259
  const barHeight = (dataset.data[index] / maxValue) * finalChartHeight;
282
260
  const barX = groupX + datasetIndex * barWidth;
283
261
  const barY = chartY + finalChartHeight - barHeight;
@@ -288,21 +266,13 @@ class ChartNode extends BoxNode {
288
266
  const value = dataset.data[index];
289
267
  const valueX = barX + barWidth / 2;
290
268
  const valueY = barY - 5; // 5px padding above bar
291
- const preComputedValueDesc = chartOptions._preComputedValueItems?.[datasetIndex]?.[index];
292
- if (preComputedValueDesc) {
293
- const valueNode = buildDescriptorTree(preComputedValueDesc);
294
- valueNode.processInitialChildren();
295
- valueNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
296
- const layout = valueNode.node.getComputedLayout();
297
- valueNode.render(ctx, valueX - layout.width / 2, valueY - layout.height);
298
- }
299
- else if (chartOptions.renderValueItem) {
300
- const valueNode = chartOptions.renderValueItem({ item: value, index, datasetIndex });
269
+ if (chartOptions.renderValueItem) {
270
+ const valueNode = await chartOptions.renderValueItem({ item: value, index, datasetIndex });
301
271
  if (valueNode) {
302
272
  valueNode.processInitialChildren();
303
273
  valueNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
304
274
  const layout = valueNode.node.getComputedLayout();
305
- valueNode.render(ctx, valueX - layout.width / 2, valueY - layout.height);
275
+ await valueNode.render(ctx, valueX - layout.width / 2, valueY - layout.height);
306
276
  }
307
277
  }
308
278
  else {
@@ -315,25 +285,17 @@ class ChartNode extends BoxNode {
315
285
  });
316
286
  }
317
287
  }
318
- });
288
+ }
319
289
  // Render labels
320
290
  if (chartOptions?.showLabels) {
321
- const displayLabel = chartOptions._preComputedXAxisLabels?.[index] ?? (chartOptions.xAxisLabelFormatter ? chartOptions.xAxisLabelFormatter(label, index) : label);
322
- const preComputedLabelDesc = chartOptions._preComputedLabelItems?.[index];
323
- if (preComputedLabelDesc) {
324
- const labelNode = buildDescriptorTree(preComputedLabelDesc);
325
- labelNode.processInitialChildren();
326
- labelNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
327
- const layout = labelNode.node.getComputedLayout();
328
- labelNode.render(ctx, groupX + (groupWidth - barSpacing) / 2 - layout.width / 2, chartY + finalChartHeight + labelHeight / 2 - layout.height / 2);
329
- }
330
- else if (chartOptions.renderLabelItem) {
331
- const labelNode = chartOptions.renderLabelItem({ item: label, index });
291
+ const displayLabel = chartOptions.xAxisLabelFormatter ? await chartOptions.xAxisLabelFormatter(label, index) : label;
292
+ if (chartOptions.renderLabelItem) {
293
+ const labelNode = await chartOptions.renderLabelItem({ item: label, index });
332
294
  if (labelNode) {
333
295
  labelNode.processInitialChildren();
334
296
  labelNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
335
297
  const layout = labelNode.node.getComputedLayout();
336
- labelNode.render(ctx, groupX + (groupWidth - barSpacing) / 2 - layout.width / 2, chartY + finalChartHeight + labelHeight / 2 - layout.height / 2);
298
+ await labelNode.render(ctx, groupX + (groupWidth - barSpacing) / 2 - layout.width / 2, chartY + finalChartHeight + labelHeight / 2 - layout.height / 2);
337
299
  }
338
300
  }
339
301
  else {
@@ -346,13 +308,13 @@ class ChartNode extends BoxNode {
346
308
  });
347
309
  }
348
310
  }
349
- });
311
+ }
350
312
  // Render legend
351
313
  if (chartOptions?.showLegend) {
352
- this.renderLegend(ctx, x + legendLayout.x, y + legendLayout.y, legendLayout.width, legendLayout.height);
314
+ await this.renderLegend(ctx, x + legendLayout.x, y + legendLayout.y, legendLayout.width, legendLayout.height);
353
315
  }
354
316
  }
355
- renderLineChart(ctx, x, y, width, height) {
317
+ async renderLineChart(ctx, x, y, width, height) {
356
318
  if (this.chartType !== 'line')
357
319
  return;
358
320
  const chartData = this.chartData;
@@ -367,8 +329,7 @@ class ChartNode extends BoxNode {
367
329
  if (chartOptions?.showYAxis) {
368
330
  const fontSize = chartOptions.yAxisFontSize || 12;
369
331
  ctx.font = `${fontSize}px ${this.props.fontFamily || 'sans-serif'}`;
370
- const maxLabel = chartOptions._preComputedYAxisLabels?.[0] ??
371
- (chartOptions.yAxisLabelFormatter ? chartOptions.yAxisLabelFormatter(maxValue) : this.getSmartYAxisFormatter(maxValue)(maxValue));
332
+ const maxLabel = chartOptions.yAxisLabelFormatter ? await chartOptions.yAxisLabelFormatter(maxValue) : this.getSmartYAxisFormatter(maxValue)(maxValue);
372
333
  const yAxisWidth = ctx.measureText(maxLabel).width + 10;
373
334
  chartX += yAxisWidth;
374
335
  chartWidth -= yAxisWidth;
@@ -400,8 +361,7 @@ class ChartNode extends BoxNode {
400
361
  ctx.stroke();
401
362
  if (chartOptions?.showYAxis) {
402
363
  const value = maxValue - (maxValue / 5) * i;
403
- const label = chartOptions._preComputedYAxisLabels?.[i] ??
404
- (chartOptions.yAxisLabelFormatter ? chartOptions.yAxisLabelFormatter(value) : this.getSmartYAxisFormatter(maxValue)(value));
364
+ const label = chartOptions.yAxisLabelFormatter ? await chartOptions.yAxisLabelFormatter(value) : this.getSmartYAxisFormatter(maxValue)(value);
405
365
  TextNode.renderSimpleText(ctx, label, chartX - 5, gridY, {
406
366
  color: chartOptions.yAxisColor || chartOptions.axisColor || '#000',
407
367
  fontSize: chartOptions.yAxisFontSize || 12,
@@ -414,11 +374,13 @@ class ChartNode extends BoxNode {
414
374
  ctx.setLineDash([]);
415
375
  }
416
376
  // Render lines and points
417
- datasets.forEach((dataset, datasetIndex) => {
377
+ for (let datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
378
+ const dataset = datasets[datasetIndex];
418
379
  ctx.strokeStyle = dataset.color || this.generateColor(datasetIndex);
419
380
  ctx.lineWidth = 2;
420
381
  ctx.beginPath();
421
- dataset.data.forEach((value, index) => {
382
+ for (let index = 0; index < dataset.data.length; index++) {
383
+ const value = dataset.data[index];
422
384
  const pointX = chartX + index * pointSpacing;
423
385
  const pointY = chartY + finalChartHeight - (value / maxValue) * finalChartHeight;
424
386
  if (index === 0) {
@@ -427,38 +389,31 @@ class ChartNode extends BoxNode {
427
389
  else {
428
390
  ctx.lineTo(pointX, pointY);
429
391
  }
430
- });
392
+ }
431
393
  ctx.stroke();
432
394
  // Render points
433
- dataset.data.forEach((value, index) => {
395
+ for (let index = 0; index < dataset.data.length; index++) {
434
396
  const pointX = chartX + index * pointSpacing;
435
- const pointY = chartY + finalChartHeight - (value / maxValue) * finalChartHeight;
397
+ const pointY = chartY + finalChartHeight - (dataset.data[index] / maxValue) * finalChartHeight;
436
398
  ctx.fillStyle = dataset.color || this.generateColor(datasetIndex);
437
399
  ctx.beginPath();
438
400
  ctx.arc(pointX, pointY, 4, 0, Math.PI * 2);
439
401
  ctx.fill();
440
- });
441
- });
402
+ }
403
+ }
442
404
  // Render labels
443
405
  if (chartOptions?.showLabels) {
444
- labels.forEach((label, index) => {
406
+ for (let index = 0; index < labels.length; index++) {
407
+ const label = labels[index];
445
408
  const pointX = chartX + index * pointSpacing;
446
- const displayLabel = chartOptions._preComputedXAxisLabels?.[index] ?? (chartOptions.xAxisLabelFormatter ? chartOptions.xAxisLabelFormatter(label, index) : label);
447
- const preComputedLabelDesc = chartOptions._preComputedLabelItems?.[index];
448
- if (preComputedLabelDesc) {
449
- const labelNode = buildDescriptorTree(preComputedLabelDesc);
450
- labelNode.processInitialChildren();
451
- labelNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
452
- const layout = labelNode.node.getComputedLayout();
453
- labelNode.render(ctx, pointX - layout.width / 2, chartY + finalChartHeight + labelHeight / 2 - layout.height / 2);
454
- }
455
- else if (chartOptions.renderLabelItem) {
456
- const labelNode = chartOptions.renderLabelItem({ item: label, index });
409
+ const displayLabel = chartOptions.xAxisLabelFormatter ? await chartOptions.xAxisLabelFormatter(label, index) : label;
410
+ if (chartOptions.renderLabelItem) {
411
+ const labelNode = await chartOptions.renderLabelItem({ item: label, index });
457
412
  if (labelNode) {
458
413
  labelNode.processInitialChildren();
459
414
  labelNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
460
415
  const layout = labelNode.node.getComputedLayout();
461
- labelNode.render(ctx, pointX - layout.width / 2, chartY + finalChartHeight + labelHeight / 2 - layout.height / 2);
416
+ await labelNode.render(ctx, pointX - layout.width / 2, chartY + finalChartHeight + labelHeight / 2 - layout.height / 2);
462
417
  }
463
418
  }
464
419
  else {
@@ -470,13 +425,13 @@ class ChartNode extends BoxNode {
470
425
  textBaseline: 'middle',
471
426
  });
472
427
  }
473
- });
428
+ }
474
429
  }
475
430
  if (chartOptions?.showLegend) {
476
- this.renderLegend(ctx, x + legendLayout.x, y + legendLayout.y, legendLayout.width, legendLayout.height);
431
+ await this.renderLegend(ctx, x + legendLayout.x, y + legendLayout.y, legendLayout.width, legendLayout.height);
477
432
  }
478
433
  }
479
- renderPieChart(ctx, x, y, width, height) {
434
+ async renderPieChart(ctx, x, y, width, height) {
480
435
  if (this.chartType !== 'pie')
481
436
  return;
482
437
  const data = this.chartData;
@@ -491,7 +446,8 @@ class ChartNode extends BoxNode {
491
446
  const radius = Math.min(chartWidth, chartHeight) / 2 - 10;
492
447
  const total = data.reduce((sum, point) => sum + point.value, 0);
493
448
  let currentAngle = -Math.PI / 2; // Start at top
494
- data.forEach((point, index) => {
449
+ for (let index = 0; index < data.length; index++) {
450
+ const point = data[index];
495
451
  const sliceAngle = (point.value / total) * Math.PI * 2;
496
452
  const startAngle = currentAngle;
497
453
  const endAngle = currentAngle + sliceAngle;
@@ -511,21 +467,13 @@ class ChartNode extends BoxNode {
511
467
  const labelRadius = radius * 0.7;
512
468
  const labelX = centerX + Math.cos(labelAngle) * labelRadius;
513
469
  const labelY = centerY + Math.sin(labelAngle) * labelRadius;
514
- const preComputedLabelDesc = chartOptions._preComputedLabelItems?.[index];
515
- if (preComputedLabelDesc) {
516
- const labelNode = buildDescriptorTree(preComputedLabelDesc);
517
- labelNode.processInitialChildren();
518
- labelNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
519
- const layout = labelNode.node.getComputedLayout();
520
- labelNode.render(ctx, labelX - layout.width / 2, labelY - layout.height / 2);
521
- }
522
- else if (chartOptions.renderLabelItem) {
523
- const labelNode = chartOptions.renderLabelItem({ item: point, index });
470
+ if (chartOptions.renderLabelItem) {
471
+ const labelNode = await chartOptions.renderLabelItem({ item: point, index });
524
472
  if (labelNode) {
525
473
  labelNode.processInitialChildren();
526
474
  labelNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
527
475
  const layout = labelNode.node.getComputedLayout();
528
- labelNode.render(ctx, labelX - layout.width / 2, labelY - layout.height / 2);
476
+ await labelNode.render(ctx, labelX - layout.width / 2, labelY - layout.height / 2);
529
477
  }
530
478
  }
531
479
  else {
@@ -539,12 +487,12 @@ class ChartNode extends BoxNode {
539
487
  }
540
488
  }
541
489
  currentAngle = endAngle;
542
- });
490
+ }
543
491
  if (chartOptions?.showLegend) {
544
- this.renderLegend(ctx, x + legendLayout.x, y + legendLayout.y, legendLayout.width, legendLayout.height);
492
+ await this.renderLegend(ctx, x + legendLayout.x, y + legendLayout.y, legendLayout.width, legendLayout.height);
545
493
  }
546
494
  }
547
- renderDoughnutChart(ctx, x, y, width, height) {
495
+ async renderDoughnutChart(ctx, x, y, width, height) {
548
496
  if (this.chartType !== 'doughnut')
549
497
  return;
550
498
  const data = this.chartData;
@@ -560,7 +508,8 @@ class ChartNode extends BoxNode {
560
508
  const innerRadius = outerRadius * (chartOptions?.innerRadius ?? 0.6);
561
509
  const total = data.reduce((sum, point) => sum + point.value, 0);
562
510
  let currentAngle = -Math.PI / 2;
563
- data.forEach((point, index) => {
511
+ for (let index = 0; index < data.length; index++) {
512
+ const point = data[index];
564
513
  const sliceAngle = (point.value / total) * Math.PI * 2;
565
514
  const startAngle = currentAngle;
566
515
  const endAngle = currentAngle + sliceAngle;
@@ -579,21 +528,13 @@ class ChartNode extends BoxNode {
579
528
  const labelRadius = innerRadius + (outerRadius - innerRadius) / 2;
580
529
  const labelX = centerX + Math.cos(labelAngle) * labelRadius;
581
530
  const labelY = centerY + Math.sin(labelAngle) * labelRadius;
582
- const preComputedLabelDesc = chartOptions._preComputedLabelItems?.[index];
583
- if (preComputedLabelDesc) {
584
- const labelNode = buildDescriptorTree(preComputedLabelDesc);
585
- labelNode.processInitialChildren();
586
- labelNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
587
- const layout = labelNode.node.getComputedLayout();
588
- labelNode.render(ctx, labelX - layout.width / 2, labelY - layout.height / 2);
589
- }
590
- else if (chartOptions.renderLabelItem) {
591
- const labelNode = chartOptions.renderLabelItem({ item: point, index });
531
+ if (chartOptions.renderLabelItem) {
532
+ const labelNode = await chartOptions.renderLabelItem({ item: point, index });
592
533
  if (labelNode) {
593
534
  labelNode.processInitialChildren();
594
535
  labelNode.node.calculateLayout(undefined, undefined, Style.Direction.LTR);
595
536
  const layout = labelNode.node.getComputedLayout();
596
- labelNode.render(ctx, labelX - layout.width / 2, labelY - layout.height / 2);
537
+ await labelNode.render(ctx, labelX - layout.width / 2, labelY - layout.height / 2);
597
538
  }
598
539
  }
599
540
  else {
@@ -607,48 +548,31 @@ class ChartNode extends BoxNode {
607
548
  }
608
549
  }
609
550
  currentAngle = endAngle;
610
- });
551
+ }
611
552
  if (chartOptions?.showLegend) {
612
- this.renderLegend(ctx, x + legendLayout.x, y + legendLayout.y, legendLayout.width, legendLayout.height);
553
+ await this.renderLegend(ctx, x + legendLayout.x, y + legendLayout.y, legendLayout.width, legendLayout.height);
613
554
  }
614
555
  }
615
- renderLegend(ctx, x, y, width, height) {
616
- const { renderLegendItem, _preComputedLegendItems } = this.chartOptions;
617
- if (_preComputedLegendItems) {
618
- const finalNodes = _preComputedLegendItems.filter((desc) => !!desc).map(desc => buildDescriptorTree(desc));
619
- if (finalNodes.length > 0) {
620
- const legendContainer = new RowNode({
621
- children: finalNodes,
622
- width,
623
- height,
624
- justifyContent: Style.Justify.Center,
625
- alignItems: Style.Align.Center,
626
- flexWrap: Style.Wrap.Wrap,
627
- gap: 10,
628
- });
629
- legendContainer.processInitialChildren();
630
- legendContainer.node.calculateLayout(width, height, Style.Direction.LTR);
631
- legendContainer.render(ctx, x, y);
632
- }
633
- return;
634
- }
556
+ async renderLegend(ctx, x, y, width, height) {
557
+ const chartOptions = this.chartOptions;
558
+ const renderLegendItem = chartOptions?.renderLegendItem;
635
559
  if (renderLegendItem) {
636
560
  let legendNodes;
637
561
  if (this.chartType === 'bar' || this.chartType === 'line') {
638
562
  const items = this.chartData.datasets;
639
563
  const render = renderLegendItem;
640
- legendNodes = items.map((item, index) => {
564
+ legendNodes = await Promise.all(items.map(async (item, index) => {
641
565
  const color = item.color || this.generateColor(index);
642
566
  return render({ item, index, color });
643
- });
567
+ }));
644
568
  }
645
569
  else {
646
570
  const items = this.chartData;
647
571
  const render = renderLegendItem;
648
- legendNodes = items.map((item, index) => {
572
+ legendNodes = await Promise.all(items.map(async (item, index) => {
649
573
  const color = item.color || this.generateColor(index);
650
574
  return render({ item, index, color });
651
- });
575
+ }));
652
576
  }
653
577
  const finalNodes = legendNodes.filter((node) => !!node);
654
578
  if (finalNodes.length > 0) {
@@ -663,11 +587,13 @@ class ChartNode extends BoxNode {
663
587
  });
664
588
  legendContainer.processInitialChildren();
665
589
  legendContainer.node.calculateLayout(width, height, Style.Direction.LTR);
666
- legendContainer.render(ctx, x, y);
590
+ await legendContainer.render(ctx, x, y);
667
591
  }
668
592
  return;
669
593
  }
670
594
  // Fallback to default rendering if renderLegendItem is not provided
595
+ if (!this.chartData)
596
+ return;
671
597
  const legendItems = 'datasets' in this.chartData
672
598
  ? this.chartData.datasets.map(d => ({ label: d.label, value: d.data.reduce((a, b) => a + b, 0) }))
673
599
  : this.chartData;
@@ -677,7 +603,7 @@ class ChartNode extends BoxNode {
677
603
  const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
678
604
  const itemHeight = Math.ceil(textHeight + 8);
679
605
  const boxSize = Math.min(15, itemHeight - 2);
680
- const position = this.chartOptions.legendPosition;
606
+ const position = this.chartOptions?.legendPosition;
681
607
  if (position === 'top' || position === 'bottom') {
682
608
  const itemPadding = 20; // horizontal padding between items
683
609
  const rows = [];
@@ -203,8 +203,8 @@ class ImageNode extends BoxNode {
203
203
  * Renders the image with correct sizing, clipping, and positioning.
204
204
  * Handles object-fit, object-position, and visual effects like saturation.
205
205
  */
206
- _renderContent(ctx, x, y, width, height) {
207
- super._renderContent(ctx, x, y, width, height);
206
+ async _renderContent(ctx, x, y, width, height) {
207
+ await super._renderContent(ctx, x, y, width, height);
208
208
  if (!this.loadedImage || width <= 0 || height <= 0)
209
209
  return;
210
210
  const img = this.loadedImage;
@@ -95,9 +95,12 @@ class BoxNode {
95
95
  'maxLines',
96
96
  'fontVariant',
97
97
  ];
98
+ const initialPropsRec = this.initialProps;
99
+ const parentPropsRec = parentProps;
100
+ const propsRec = this.props;
98
101
  for (const key of inheritableKeys) {
99
- if (this.initialProps[key] === undefined && parentProps[key] !== undefined) {
100
- this.props[key] = parentProps[key];
102
+ if (initialPropsRec[key] === undefined && parentPropsRec[key] !== undefined) {
103
+ propsRec[key] = parentPropsRec[key];
101
104
  }
102
105
  }
103
106
  if (!this.node.isDirty()) {
@@ -198,11 +201,12 @@ class BoxNode {
198
201
  else {
199
202
  for (const [edge, value] of Object.entries(position)) {
200
203
  if (edge in Style.Edge) {
204
+ const edgeKey = edge;
201
205
  if (typeof value === 'string' && value.endsWith('%')) {
202
- this.node.setPositionPercent(Style.Edge[edge], parseFloat(value));
206
+ this.node.setPositionPercent(Style.Edge[edgeKey], parseFloat(value));
203
207
  }
204
208
  else {
205
- this.node.setPosition(Style.Edge[edge], value);
209
+ this.node.setPosition(Style.Edge[edgeKey], value);
206
210
  }
207
211
  }
208
212
  }
@@ -218,11 +222,12 @@ class BoxNode {
218
222
  else {
219
223
  for (const [gutter, value] of Object.entries(gap)) {
220
224
  if (gutter in Style.Gutter) {
225
+ const gutterKey = gutter;
221
226
  if (typeof value === 'string' && value.endsWith('%')) {
222
- this.node.setGapPercent(Style.Gutter[gutter], parseFloat(value));
227
+ this.node.setGapPercent(Style.Gutter[gutterKey], parseFloat(value));
223
228
  }
224
229
  else {
225
- this.node.setGap(Style.Gutter[gutter], value);
230
+ this.node.setGap(Style.Gutter[gutterKey], value);
226
231
  }
227
232
  }
228
233
  }
@@ -238,11 +243,12 @@ class BoxNode {
238
243
  else {
239
244
  for (const [edge, value] of Object.entries(margin)) {
240
245
  if (edge in Style.Edge) {
246
+ const edgeKey = edge;
241
247
  if (typeof value === 'string' && value.endsWith('%')) {
242
- this.node.setMarginPercent(Style.Edge[edge], parseFloat(value));
248
+ this.node.setMarginPercent(Style.Edge[edgeKey], parseFloat(value));
243
249
  }
244
250
  else {
245
- this.node.setMargin(Style.Edge[edge], value);
251
+ this.node.setMargin(Style.Edge[edgeKey], value);
246
252
  }
247
253
  }
248
254
  }
@@ -258,11 +264,12 @@ class BoxNode {
258
264
  else {
259
265
  for (const [edge, value] of Object.entries(padding)) {
260
266
  if (edge in Style.Edge) {
267
+ const edgeKey = edge;
261
268
  if (typeof value === 'string' && value.endsWith('%')) {
262
- this.node.setPaddingPercent(Style.Edge[edge], parseFloat(value));
269
+ this.node.setPaddingPercent(Style.Edge[edgeKey], parseFloat(value));
263
270
  }
264
271
  else {
265
- this.node.setPadding(Style.Edge[edge], value);
272
+ this.node.setPadding(Style.Edge[edgeKey], value);
266
273
  }
267
274
  }
268
275
  }
@@ -299,7 +306,7 @@ class BoxNode {
299
306
  * @param {number} offsetX X offset for rendering.
300
307
  * @param {number} offsetY Y offset for rendering.
301
308
  */
302
- render(ctx, offsetX = 0, offsetY = 0) {
309
+ async render(ctx, offsetX = 0, offsetY = 0) {
303
310
  const layout = this.node.getComputedLayout();
304
311
  const x = layout.left + offsetX;
305
312
  const y = layout.top + offsetY;
@@ -354,7 +361,7 @@ class BoxNode {
354
361
  // --- End Transformation Setup ---
355
362
  // --- Step 1: Render Parent Background/Borders/Content ---
356
363
  // This renders the current node's own visual appearance first.
357
- this._renderContent(ctx, x, y, width, height);
364
+ await this._renderContent(ctx, x, y, width, height);
358
365
  // --- Step 2: Prepare Children for Stacking ---
359
366
  const positionedChildren = [];
360
367
  const inFlowChildren = [];
@@ -411,19 +418,19 @@ class BoxNode {
411
418
  for (const item of positionedChildren) {
412
419
  if (item.zIndex < 0) {
413
420
  // Pass parent's layout origin (x, y) as offset
414
- item.node.render(ctx, x, y);
421
+ await item.node.render(ctx, x, y);
415
422
  }
416
423
  }
417
424
  // 4b: Render in-flow children (recursively)
418
425
  for (const child of inFlowChildren) {
419
426
  // Pass parent's layout origin (x, y) as offset
420
- child.render(ctx, x, y);
427
+ await child.render(ctx, x, y);
421
428
  }
422
429
  // 4c: Render positioned children with zero or positive zIndex
423
430
  for (const item of positionedChildren) {
424
431
  if (item.zIndex >= 0) {
425
432
  // Pass parent's layout origin (x, y) as offset
426
- item.node.render(ctx, x, y);
433
+ await item.node.render(ctx, x, y);
427
434
  }
428
435
  }
429
436
  // --- End Child Rendering ---
@@ -455,7 +462,7 @@ class BoxNode {
455
462
  * @param width The width of the content area to render
456
463
  * @param height The height of the content area to render
457
464
  */
458
- _renderContent(ctx, x, y, width, height) {
465
+ async _renderContent(ctx, x, y, width, height) {
459
466
  // Calculate border radius values for all corners
460
467
  const radii = { TopLeft: 0, TopRight: 0, BottomRight: 0, BottomLeft: 0 };
461
468
  if (this.props.borderRadius) {