apexify.js 5.1.1 → 5.2.0

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 (236) hide show
  1. package/CHANGELOG.md +240 -0
  2. package/README.md +248 -1105
  3. package/dist/cjs/Canvas/ApexPainter.d.ts +182 -204
  4. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
  5. package/dist/cjs/Canvas/ApexPainter.js +482 -1286
  6. package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
  7. package/dist/cjs/Canvas/extended/CanvasCreator.d.ts +33 -0
  8. package/dist/cjs/Canvas/extended/CanvasCreator.d.ts.map +1 -0
  9. package/dist/cjs/Canvas/extended/CanvasCreator.js +223 -0
  10. package/dist/cjs/Canvas/extended/CanvasCreator.js.map +1 -0
  11. package/dist/cjs/Canvas/extended/ChartCreator.d.ts +26 -0
  12. package/dist/cjs/Canvas/extended/ChartCreator.d.ts.map +1 -0
  13. package/dist/cjs/Canvas/extended/ChartCreator.js +50 -0
  14. package/dist/cjs/Canvas/extended/ChartCreator.js.map +1 -0
  15. package/dist/cjs/Canvas/extended/GIFCreator.d.ts +43 -0
  16. package/dist/cjs/Canvas/extended/GIFCreator.d.ts.map +1 -0
  17. package/dist/cjs/Canvas/extended/GIFCreator.js +157 -0
  18. package/dist/cjs/Canvas/extended/GIFCreator.js.map +1 -0
  19. package/dist/cjs/Canvas/extended/ImageCreator.d.ts +83 -0
  20. package/dist/cjs/Canvas/extended/ImageCreator.d.ts.map +1 -0
  21. package/dist/cjs/Canvas/extended/ImageCreator.js +479 -0
  22. package/dist/cjs/Canvas/extended/ImageCreator.js.map +1 -0
  23. package/dist/cjs/Canvas/extended/TextCreator.d.ts +35 -0
  24. package/dist/cjs/Canvas/extended/TextCreator.d.ts.map +1 -0
  25. package/dist/cjs/Canvas/extended/TextCreator.js +98 -0
  26. package/dist/cjs/Canvas/extended/TextCreator.js.map +1 -0
  27. package/dist/cjs/Canvas/extended/VideoCreator.d.ts +370 -0
  28. package/dist/cjs/Canvas/extended/VideoCreator.d.ts.map +1 -0
  29. package/dist/cjs/Canvas/extended/VideoCreator.js +478 -0
  30. package/dist/cjs/Canvas/extended/VideoCreator.js.map +1 -0
  31. package/dist/cjs/Canvas/utils/Background/bg.d.ts +1 -1
  32. package/dist/cjs/Canvas/utils/Background/bg.d.ts.map +1 -1
  33. package/dist/cjs/Canvas/utils/Background/bg.js +43 -7
  34. package/dist/cjs/Canvas/utils/Background/bg.js.map +1 -1
  35. package/dist/cjs/Canvas/utils/Charts/barchart.d.ts +230 -0
  36. package/dist/cjs/Canvas/utils/Charts/barchart.d.ts.map +1 -0
  37. package/dist/cjs/Canvas/utils/Charts/barchart.js +1891 -0
  38. package/dist/cjs/Canvas/utils/Charts/barchart.js.map +1 -0
  39. package/dist/cjs/Canvas/utils/Charts/comparisonchart.d.ts +103 -0
  40. package/dist/cjs/Canvas/utils/Charts/comparisonchart.d.ts.map +1 -0
  41. package/dist/cjs/Canvas/utils/Charts/comparisonchart.js +368 -0
  42. package/dist/cjs/Canvas/utils/Charts/comparisonchart.js.map +1 -0
  43. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.d.ts +178 -0
  44. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.d.ts.map +1 -0
  45. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.js +1389 -0
  46. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.js.map +1 -0
  47. package/dist/cjs/Canvas/utils/Charts/index.d.ts +45 -0
  48. package/dist/cjs/Canvas/utils/Charts/index.d.ts.map +1 -0
  49. package/dist/cjs/Canvas/utils/Charts/index.js +17 -0
  50. package/dist/cjs/Canvas/utils/Charts/index.js.map +1 -0
  51. package/dist/cjs/Canvas/utils/Charts/linechart.d.ts +216 -0
  52. package/dist/cjs/Canvas/utils/Charts/linechart.d.ts.map +1 -0
  53. package/dist/cjs/Canvas/utils/Charts/linechart.js +1761 -0
  54. package/dist/cjs/Canvas/utils/Charts/linechart.js.map +1 -0
  55. package/dist/cjs/Canvas/utils/Charts/piechart.d.ts +167 -0
  56. package/dist/cjs/Canvas/utils/Charts/piechart.d.ts.map +1 -0
  57. package/dist/cjs/Canvas/utils/Charts/piechart.js +794 -0
  58. package/dist/cjs/Canvas/utils/Charts/piechart.js.map +1 -0
  59. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -1
  60. package/dist/cjs/Canvas/utils/General/batchOperations.js +3 -4
  61. package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -1
  62. package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
  63. package/dist/cjs/Canvas/utils/General/general functions.js +62 -33
  64. package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
  65. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -1
  66. package/dist/cjs/Canvas/utils/General/imageStitching.js +3 -6
  67. package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -1
  68. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -1
  69. package/dist/cjs/Canvas/utils/Image/imageMasking.js +5 -12
  70. package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -1
  71. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts +4 -4
  72. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  73. package/dist/cjs/Canvas/utils/Image/imageProperties.js +44 -9
  74. package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
  75. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +5 -0
  76. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
  77. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +48 -5
  78. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
  79. package/dist/cjs/Canvas/utils/Texts/textProperties.d.ts +1 -1
  80. package/dist/cjs/Canvas/utils/Texts/textProperties.d.ts.map +1 -1
  81. package/dist/cjs/Canvas/utils/Texts/textProperties.js +48 -5
  82. package/dist/cjs/Canvas/utils/Texts/textProperties.js.map +1 -1
  83. package/dist/cjs/Canvas/utils/Video/videoHelpers.d.ts +489 -0
  84. package/dist/cjs/Canvas/utils/Video/videoHelpers.d.ts.map +1 -0
  85. package/dist/cjs/Canvas/utils/Video/videoHelpers.js +1835 -0
  86. package/dist/cjs/Canvas/utils/Video/videoHelpers.js.map +1 -0
  87. package/dist/cjs/Canvas/utils/errorUtils.d.ts +15 -0
  88. package/dist/cjs/Canvas/utils/errorUtils.d.ts.map +1 -0
  89. package/dist/cjs/Canvas/utils/errorUtils.js +26 -0
  90. package/dist/cjs/Canvas/utils/errorUtils.js.map +1 -0
  91. package/dist/cjs/Canvas/utils/types.d.ts +17 -178
  92. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
  93. package/dist/cjs/Canvas/utils/types.js.map +1 -1
  94. package/dist/cjs/Canvas/utils/utils.d.ts +4 -3
  95. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
  96. package/dist/cjs/Canvas/utils/utils.js +40 -6
  97. package/dist/cjs/Canvas/utils/utils.js.map +1 -1
  98. package/dist/cjs/index.d.ts +1 -8
  99. package/dist/cjs/index.d.ts.map +1 -1
  100. package/dist/cjs/index.js +14 -45
  101. package/dist/cjs/index.js.map +1 -1
  102. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  103. package/dist/esm/Canvas/ApexPainter.d.ts +182 -204
  104. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
  105. package/dist/esm/Canvas/ApexPainter.js +482 -1286
  106. package/dist/esm/Canvas/ApexPainter.js.map +1 -1
  107. package/dist/esm/Canvas/extended/CanvasCreator.d.ts +33 -0
  108. package/dist/esm/Canvas/extended/CanvasCreator.d.ts.map +1 -0
  109. package/dist/esm/Canvas/extended/CanvasCreator.js +223 -0
  110. package/dist/esm/Canvas/extended/CanvasCreator.js.map +1 -0
  111. package/dist/esm/Canvas/extended/ChartCreator.d.ts +26 -0
  112. package/dist/esm/Canvas/extended/ChartCreator.d.ts.map +1 -0
  113. package/dist/esm/Canvas/extended/ChartCreator.js +50 -0
  114. package/dist/esm/Canvas/extended/ChartCreator.js.map +1 -0
  115. package/dist/esm/Canvas/extended/GIFCreator.d.ts +43 -0
  116. package/dist/esm/Canvas/extended/GIFCreator.d.ts.map +1 -0
  117. package/dist/esm/Canvas/extended/GIFCreator.js +157 -0
  118. package/dist/esm/Canvas/extended/GIFCreator.js.map +1 -0
  119. package/dist/esm/Canvas/extended/ImageCreator.d.ts +83 -0
  120. package/dist/esm/Canvas/extended/ImageCreator.d.ts.map +1 -0
  121. package/dist/esm/Canvas/extended/ImageCreator.js +479 -0
  122. package/dist/esm/Canvas/extended/ImageCreator.js.map +1 -0
  123. package/dist/esm/Canvas/extended/TextCreator.d.ts +35 -0
  124. package/dist/esm/Canvas/extended/TextCreator.d.ts.map +1 -0
  125. package/dist/esm/Canvas/extended/TextCreator.js +98 -0
  126. package/dist/esm/Canvas/extended/TextCreator.js.map +1 -0
  127. package/dist/esm/Canvas/extended/VideoCreator.d.ts +370 -0
  128. package/dist/esm/Canvas/extended/VideoCreator.d.ts.map +1 -0
  129. package/dist/esm/Canvas/extended/VideoCreator.js +478 -0
  130. package/dist/esm/Canvas/extended/VideoCreator.js.map +1 -0
  131. package/dist/esm/Canvas/utils/Background/bg.d.ts +1 -1
  132. package/dist/esm/Canvas/utils/Background/bg.d.ts.map +1 -1
  133. package/dist/esm/Canvas/utils/Background/bg.js +43 -7
  134. package/dist/esm/Canvas/utils/Background/bg.js.map +1 -1
  135. package/dist/esm/Canvas/utils/Charts/barchart.d.ts +230 -0
  136. package/dist/esm/Canvas/utils/Charts/barchart.d.ts.map +1 -0
  137. package/dist/esm/Canvas/utils/Charts/barchart.js +1891 -0
  138. package/dist/esm/Canvas/utils/Charts/barchart.js.map +1 -0
  139. package/dist/esm/Canvas/utils/Charts/comparisonchart.d.ts +103 -0
  140. package/dist/esm/Canvas/utils/Charts/comparisonchart.d.ts.map +1 -0
  141. package/dist/esm/Canvas/utils/Charts/comparisonchart.js +368 -0
  142. package/dist/esm/Canvas/utils/Charts/comparisonchart.js.map +1 -0
  143. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.d.ts +178 -0
  144. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.d.ts.map +1 -0
  145. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.js +1389 -0
  146. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.js.map +1 -0
  147. package/dist/esm/Canvas/utils/Charts/index.d.ts +45 -0
  148. package/dist/esm/Canvas/utils/Charts/index.d.ts.map +1 -0
  149. package/dist/esm/Canvas/utils/Charts/index.js +17 -0
  150. package/dist/esm/Canvas/utils/Charts/index.js.map +1 -0
  151. package/dist/esm/Canvas/utils/Charts/linechart.d.ts +216 -0
  152. package/dist/esm/Canvas/utils/Charts/linechart.d.ts.map +1 -0
  153. package/dist/esm/Canvas/utils/Charts/linechart.js +1761 -0
  154. package/dist/esm/Canvas/utils/Charts/linechart.js.map +1 -0
  155. package/dist/esm/Canvas/utils/Charts/piechart.d.ts +167 -0
  156. package/dist/esm/Canvas/utils/Charts/piechart.d.ts.map +1 -0
  157. package/dist/esm/Canvas/utils/Charts/piechart.js +794 -0
  158. package/dist/esm/Canvas/utils/Charts/piechart.js.map +1 -0
  159. package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -1
  160. package/dist/esm/Canvas/utils/General/batchOperations.js +3 -4
  161. package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -1
  162. package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
  163. package/dist/esm/Canvas/utils/General/general functions.js +62 -33
  164. package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
  165. package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -1
  166. package/dist/esm/Canvas/utils/General/imageStitching.js +3 -6
  167. package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -1
  168. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -1
  169. package/dist/esm/Canvas/utils/Image/imageMasking.js +5 -12
  170. package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -1
  171. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts +4 -4
  172. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  173. package/dist/esm/Canvas/utils/Image/imageProperties.js +44 -9
  174. package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
  175. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +5 -0
  176. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
  177. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +48 -5
  178. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
  179. package/dist/esm/Canvas/utils/Texts/textProperties.d.ts +1 -1
  180. package/dist/esm/Canvas/utils/Texts/textProperties.d.ts.map +1 -1
  181. package/dist/esm/Canvas/utils/Texts/textProperties.js +48 -5
  182. package/dist/esm/Canvas/utils/Texts/textProperties.js.map +1 -1
  183. package/dist/esm/Canvas/utils/Video/videoHelpers.d.ts +489 -0
  184. package/dist/esm/Canvas/utils/Video/videoHelpers.d.ts.map +1 -0
  185. package/dist/esm/Canvas/utils/Video/videoHelpers.js +1835 -0
  186. package/dist/esm/Canvas/utils/Video/videoHelpers.js.map +1 -0
  187. package/dist/esm/Canvas/utils/errorUtils.d.ts +15 -0
  188. package/dist/esm/Canvas/utils/errorUtils.d.ts.map +1 -0
  189. package/dist/esm/Canvas/utils/errorUtils.js +26 -0
  190. package/dist/esm/Canvas/utils/errorUtils.js.map +1 -0
  191. package/dist/esm/Canvas/utils/types.d.ts +17 -178
  192. package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
  193. package/dist/esm/Canvas/utils/types.js.map +1 -1
  194. package/dist/esm/Canvas/utils/utils.d.ts +4 -3
  195. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
  196. package/dist/esm/Canvas/utils/utils.js +40 -6
  197. package/dist/esm/Canvas/utils/utils.js.map +1 -1
  198. package/dist/esm/index.d.ts +1 -8
  199. package/dist/esm/index.d.ts.map +1 -1
  200. package/dist/esm/index.js +14 -45
  201. package/dist/esm/index.js.map +1 -1
  202. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  203. package/package.json +234 -198
  204. package/dist/cjs/Canvas/utils/Charts/charts.d.ts +0 -13
  205. package/dist/cjs/Canvas/utils/Charts/charts.d.ts.map +0 -1
  206. package/dist/cjs/Canvas/utils/Charts/charts.js +0 -466
  207. package/dist/cjs/Canvas/utils/Charts/charts.js.map +0 -1
  208. package/dist/esm/Canvas/utils/Charts/charts.d.ts +0 -13
  209. package/dist/esm/Canvas/utils/Charts/charts.d.ts.map +0 -1
  210. package/dist/esm/Canvas/utils/Charts/charts.js +0 -466
  211. package/dist/esm/Canvas/utils/Charts/charts.js.map +0 -1
  212. package/lib/Canvas/ApexPainter.ts +0 -5414
  213. package/lib/Canvas/utils/Background/bg.ts +0 -285
  214. package/lib/Canvas/utils/Charts/charts.ts +0 -548
  215. package/lib/Canvas/utils/Custom/advancedLines.ts +0 -387
  216. package/lib/Canvas/utils/Custom/customLines.ts +0 -206
  217. package/lib/Canvas/utils/General/batchOperations.ts +0 -103
  218. package/lib/Canvas/utils/General/conversion.ts +0 -34
  219. package/lib/Canvas/utils/General/general functions.ts +0 -726
  220. package/lib/Canvas/utils/General/imageCompression.ts +0 -316
  221. package/lib/Canvas/utils/General/imageStitching.ts +0 -252
  222. package/lib/Canvas/utils/Image/imageEffects.ts +0 -175
  223. package/lib/Canvas/utils/Image/imageFilters.ts +0 -356
  224. package/lib/Canvas/utils/Image/imageMasking.ts +0 -335
  225. package/lib/Canvas/utils/Image/imageProperties.ts +0 -587
  226. package/lib/Canvas/utils/Image/professionalImageFilters.ts +0 -391
  227. package/lib/Canvas/utils/Image/simpleProfessionalFilters.ts +0 -229
  228. package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +0 -455
  229. package/lib/Canvas/utils/Shapes/shapes.ts +0 -528
  230. package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +0 -716
  231. package/lib/Canvas/utils/Texts/textPathRenderer.ts +0 -320
  232. package/lib/Canvas/utils/Texts/textProperties.ts +0 -231
  233. package/lib/Canvas/utils/types.ts +0 -983
  234. package/lib/Canvas/utils/utils.ts +0 -135
  235. package/lib/index.ts +0 -81
  236. package/lib/utils.ts +0 -5
@@ -0,0 +1,794 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPieChart = createPieChart;
4
+ const canvas_1 = require("@napi-rs/canvas");
5
+ const imageProperties_1 = require("../Image/imageProperties");
6
+ /**
7
+ * Helper function to render enhanced text with custom fonts, gradients, shadows, strokes
8
+ */
9
+ async function renderEnhancedText(ctx, text, x, y, style, fontSize, color, textGradient) {
10
+ ctx.save();
11
+ // Preserve text alignment settings
12
+ const savedTextAlign = ctx.textAlign;
13
+ const savedTextBaseline = ctx.textBaseline;
14
+ const effectiveFontSize = fontSize || style?.fontSize || 16;
15
+ const fontFamily = style?.fontFamily || style?.fontName || 'Arial';
16
+ let fontString = '';
17
+ if (style?.bold)
18
+ fontString += 'bold ';
19
+ if (style?.italic)
20
+ fontString += 'italic ';
21
+ fontString += `${effectiveFontSize}px "${fontFamily}"`;
22
+ ctx.font = fontString;
23
+ // Restore text alignment to ensure correct positioning
24
+ ctx.textAlign = savedTextAlign;
25
+ ctx.textBaseline = savedTextBaseline;
26
+ // Register custom font if provided
27
+ if (style?.fontPath && style?.fontName) {
28
+ try {
29
+ const { GlobalFonts } = await import('@napi-rs/canvas');
30
+ const path = await import('path');
31
+ const fullPath = path.join(process.cwd(), style.fontPath);
32
+ GlobalFonts.registerFromPath(fullPath, style.fontName);
33
+ ctx.font = fontString.replace(`"${fontFamily}"`, `"${style.fontName}"`);
34
+ }
35
+ catch (error) {
36
+ console.warn(`Failed to register font: ${style.fontPath}`, error);
37
+ }
38
+ }
39
+ // Apply shadow
40
+ if (style?.shadow) {
41
+ ctx.shadowColor = style.shadow.color || 'rgba(0,0,0,0.5)';
42
+ ctx.shadowOffsetX = style.shadow.offsetX || 2;
43
+ ctx.shadowOffsetY = style.shadow.offsetY || 2;
44
+ ctx.shadowBlur = style.shadow.blur || 4;
45
+ if (style.shadow.opacity !== undefined) {
46
+ ctx.globalAlpha = style.shadow.opacity;
47
+ }
48
+ }
49
+ // Set fill style (gradient or color)
50
+ if (textGradient) {
51
+ const metrics = ctx.measureText(text);
52
+ ctx.fillStyle = (0, imageProperties_1.createGradientFill)(ctx, textGradient, {
53
+ x, y, w: metrics.width, h: effectiveFontSize
54
+ });
55
+ }
56
+ else if (color) {
57
+ ctx.fillStyle = color;
58
+ }
59
+ // Draw text
60
+ ctx.fillText(text, x, y);
61
+ // Apply stroke
62
+ if (style?.stroke) {
63
+ ctx.strokeStyle = style.stroke.color || '#000000';
64
+ ctx.lineWidth = style.stroke.width || 1;
65
+ if (style.stroke.gradient) {
66
+ const metrics = ctx.measureText(text);
67
+ ctx.strokeStyle = (0, imageProperties_1.createGradientFill)(ctx, style.stroke.gradient, {
68
+ x, y, w: metrics.width, h: effectiveFontSize
69
+ });
70
+ }
71
+ ctx.strokeText(text, x, y);
72
+ }
73
+ // Reset shadow and alpha
74
+ ctx.shadowColor = 'transparent';
75
+ ctx.shadowOffsetX = 0;
76
+ ctx.shadowOffsetY = 0;
77
+ ctx.shadowBlur = 0;
78
+ ctx.globalAlpha = 1;
79
+ ctx.restore();
80
+ }
81
+ /**
82
+ * Helper function to fill a shape with gradient or color
83
+ */
84
+ function fillWithGradientOrColor(ctx, gradient, color, defaultColor = '#000000', rect) {
85
+ if (gradient && rect) {
86
+ ctx.fillStyle = (0, imageProperties_1.createGradientFill)(ctx, gradient, rect);
87
+ }
88
+ else {
89
+ ctx.fillStyle = color || defaultColor;
90
+ }
91
+ }
92
+ /**
93
+ * Wraps text to fit within a maximum width
94
+ */
95
+ function wrapText(ctx, text, maxWidth) {
96
+ const words = text.split(' ');
97
+ const lines = [];
98
+ let currentLine = words[0];
99
+ for (let i = 1; i < words.length; i++) {
100
+ const word = words[i];
101
+ const width = ctx.measureText(currentLine + ' ' + word).width;
102
+ if (width < maxWidth) {
103
+ currentLine += ' ' + word;
104
+ }
105
+ else {
106
+ lines.push(currentLine);
107
+ currentLine = word;
108
+ }
109
+ }
110
+ lines.push(currentLine);
111
+ return lines;
112
+ }
113
+ /**
114
+ * Calculates dimensions for standard legend with text wrapping
115
+ */
116
+ function calculateStandardLegendDimensions(entries, fontSize, spacing, padding, maxWidth) {
117
+ // Create a temporary canvas to measure text
118
+ const tempCanvas = (0, canvas_1.createCanvas)(1, 1);
119
+ const tempCtx = tempCanvas.getContext('2d');
120
+ tempCtx.font = `${fontSize}px Arial`;
121
+ // Box size scales with font size for better proportions
122
+ const boxSize = Math.max(18, fontSize * 1.2);
123
+ const textSpacing = 12;
124
+ const effectiveMaxWidth = maxWidth ? maxWidth - padding * 2 - boxSize - textSpacing : undefined;
125
+ let maxEntryWidth = 0;
126
+ const entryHeights = [];
127
+ entries.forEach(entry => {
128
+ let textWidth;
129
+ let textHeight;
130
+ if (effectiveMaxWidth) {
131
+ const wrappedLines = wrapText(tempCtx, entry.label, effectiveMaxWidth);
132
+ textWidth = Math.max(...wrappedLines.map(line => tempCtx.measureText(line).width));
133
+ textHeight = wrappedLines.length * fontSize * 1.2; // Line height multiplier
134
+ }
135
+ else {
136
+ textWidth = tempCtx.measureText(entry.label).width;
137
+ textHeight = fontSize;
138
+ }
139
+ const entryWidth = boxSize + textSpacing + textWidth;
140
+ maxEntryWidth = Math.max(maxEntryWidth, entryWidth);
141
+ entryHeights.push(Math.max(boxSize, textHeight));
142
+ });
143
+ const width = maxWidth ? maxWidth : maxEntryWidth + padding * 2;
144
+ const height = entryHeights.reduce((sum, h, i) => sum + h + (i < entryHeights.length - 1 ? spacing : 0), 0) + padding * 2;
145
+ return { width, height, entryHeights };
146
+ }
147
+ /**
148
+ * Draws standard legend (Type 1)
149
+ */
150
+ async function drawStandardLegend(ctx, entries, config, canvasWidth, canvasHeight, padding, chartArea, legendSpacing, titleHeight) {
151
+ if (!config.show || !entries || entries.length === 0)
152
+ return;
153
+ ctx.save();
154
+ const fontSize = config.fontSize ?? 16; // Increased default from 12 to 16
155
+ const spacing = config.spacing ?? 18; // Increased default spacing
156
+ const paddingBox = config.padding ?? 10; // Increased default padding
157
+ const backgroundColor = config.backgroundColor ?? 'rgba(255, 255, 255, 0.9)';
158
+ const borderColor = config.borderColor ?? '#000000';
159
+ const textColor = config.textColor ?? '#000000';
160
+ const maxWidth = config.maxWidth;
161
+ const wrapTextEnabled = config.wrapText !== false;
162
+ ctx.font = `${fontSize}px Arial`;
163
+ // Calculate dimensions
164
+ const { width, height, entryHeights } = calculateStandardLegendDimensions(entries, fontSize, spacing, paddingBox, maxWidth);
165
+ // Determine position - position relative to chart area if provided, otherwise use canvas edges
166
+ let legendX, legendY;
167
+ const position = config.position ?? 'right';
168
+ const gap = legendSpacing ?? 20;
169
+ if (chartArea) {
170
+ // Position relative to chart area for better spacing
171
+ switch (position) {
172
+ case 'top':
173
+ legendX = (canvasWidth - width) / 2;
174
+ // Position legend below title if title exists
175
+ legendY = padding.top + (titleHeight ?? 0);
176
+ break;
177
+ case 'bottom':
178
+ legendX = (canvasWidth - width) / 2;
179
+ legendY = canvasHeight - padding.bottom - height;
180
+ break;
181
+ case 'left':
182
+ legendX = chartArea.left - width - gap;
183
+ legendY = (canvasHeight - height) / 2;
184
+ break;
185
+ case 'right':
186
+ legendX = chartArea.right + gap;
187
+ legendY = (canvasHeight - height) / 2;
188
+ break;
189
+ default:
190
+ legendX = chartArea.right + gap;
191
+ legendY = (canvasHeight - height) / 2;
192
+ }
193
+ }
194
+ else {
195
+ // Fallback to original positioning
196
+ switch (position) {
197
+ case 'top':
198
+ legendX = (canvasWidth - width) / 2;
199
+ legendY = padding.top;
200
+ break;
201
+ case 'bottom':
202
+ legendX = (canvasWidth - width) / 2;
203
+ legendY = canvasHeight - padding.bottom - height;
204
+ break;
205
+ case 'left':
206
+ legendX = padding.left;
207
+ legendY = (canvasHeight - height) / 2;
208
+ break;
209
+ case 'right':
210
+ legendX = canvasWidth - padding.right - width;
211
+ legendY = (canvasHeight - height) / 2;
212
+ break;
213
+ default:
214
+ legendX = canvasWidth - padding.right - width;
215
+ legendY = (canvasHeight - height) / 2;
216
+ }
217
+ }
218
+ // Draw legend background with gradient or color
219
+ if (config.backgroundGradient) {
220
+ fillWithGradientOrColor(ctx, config.backgroundGradient, backgroundColor, backgroundColor, {
221
+ x: legendX, y: legendY, w: width, h: height
222
+ });
223
+ ctx.fillRect(legendX, legendY, width, height);
224
+ }
225
+ else {
226
+ ctx.fillStyle = backgroundColor;
227
+ ctx.fillRect(legendX, legendY, width, height);
228
+ }
229
+ // Draw legend border
230
+ ctx.strokeStyle = borderColor;
231
+ ctx.lineWidth = 1;
232
+ ctx.strokeRect(legendX, legendY, width, height);
233
+ // Draw legend entries
234
+ ctx.textAlign = 'left';
235
+ ctx.textBaseline = 'middle';
236
+ // Box size scales with font size for better proportions
237
+ const boxSize = Math.max(18, fontSize * 1.2);
238
+ const textSpacing = 12;
239
+ const effectiveMaxWidth = maxWidth ? maxWidth - paddingBox * 2 - boxSize - textSpacing : undefined;
240
+ let currentY = legendY + paddingBox;
241
+ for (let index = 0; index < entries.length; index++) {
242
+ const entry = entries[index];
243
+ const entryHeight = entryHeights[index];
244
+ const centerY = currentY + entryHeight / 2;
245
+ // Draw color box with gradient or color
246
+ if (entry.gradient) {
247
+ fillWithGradientOrColor(ctx, entry.gradient, entry.color, '#000000', {
248
+ x: legendX + paddingBox, y: centerY - boxSize / 2, w: boxSize, h: boxSize
249
+ });
250
+ ctx.fillRect(legendX + paddingBox, centerY - boxSize / 2, boxSize, boxSize);
251
+ }
252
+ else {
253
+ ctx.fillStyle = entry.color || '#000000';
254
+ ctx.fillRect(legendX + paddingBox, centerY - boxSize / 2, boxSize, boxSize);
255
+ }
256
+ // Draw box border
257
+ ctx.strokeStyle = borderColor;
258
+ ctx.lineWidth = 1;
259
+ ctx.strokeRect(legendX + paddingBox, centerY - boxSize / 2, boxSize, boxSize);
260
+ // Draw label with enhanced styling
261
+ const textX = legendX + paddingBox + boxSize + textSpacing;
262
+ const labelTextColor = config.textGradient ? undefined : (config.textColor || textColor);
263
+ if (wrapTextEnabled && effectiveMaxWidth) {
264
+ const wrappedLines = wrapText(ctx, entry.label, effectiveMaxWidth);
265
+ const lineHeight = fontSize * 1.2;
266
+ const startY = centerY - (wrappedLines.length - 1) * lineHeight / 2;
267
+ for (let lineIndex = 0; lineIndex < wrappedLines.length; lineIndex++) {
268
+ await renderEnhancedText(ctx, wrappedLines[lineIndex], textX, startY + lineIndex * lineHeight, config.textStyle, fontSize, labelTextColor, config.textGradient);
269
+ }
270
+ }
271
+ else {
272
+ await renderEnhancedText(ctx, entry.label, textX, centerY, config.textStyle, fontSize, labelTextColor, config.textGradient);
273
+ }
274
+ currentY += entryHeight + spacing;
275
+ }
276
+ ctx.restore();
277
+ }
278
+ /**
279
+ * Draws connected legend (Type 2) - labels with lines connecting to pie slices
280
+ */
281
+ function drawConnectedLegend(ctx, entries, sliceAngles, centerX, centerY, radius, innerRadius, config) {
282
+ if (!config.show || !entries || entries.length === 0)
283
+ return;
284
+ ctx.save();
285
+ const fontSize = config.fontSize ?? 12;
286
+ const padding = config.padding ?? 5;
287
+ const backgroundColor = config.backgroundColor ?? 'rgba(255, 255, 255, 0.9)';
288
+ const borderColor = config.borderColor ?? '#000000';
289
+ const textColor = config.textColor ?? '#000000';
290
+ const lineColor = config.lineColor ?? '#000000';
291
+ const lineWidth = config.lineWidth ?? 1;
292
+ const maxWidth = config.maxWidth ?? 150;
293
+ const wrapTextEnabled = config.wrapText !== false;
294
+ ctx.font = `${fontSize}px Arial`;
295
+ // Calculate midpoint angles for each slice
296
+ const midAngles = sliceAngles.map(slice => (slice.startAngle + slice.endAngle) / 2);
297
+ const labelPositions = [];
298
+ entries.forEach((entry, index) => {
299
+ if (index >= midAngles.length)
300
+ return;
301
+ const angle = midAngles[index];
302
+ const labelRadius = radius + 20; // Distance from pie edge to label
303
+ const labelX = centerX + Math.cos(angle) * labelRadius;
304
+ const labelY = centerY + Math.sin(angle) * labelRadius;
305
+ // Determine label position (left or right of pie)
306
+ const isLeftSide = Math.cos(angle) < 0;
307
+ // Calculate text dimensions with wrapping
308
+ const textMaxWidth = maxWidth - padding * 2;
309
+ let wrappedLines;
310
+ let textHeight;
311
+ let textWidth;
312
+ if (wrapTextEnabled) {
313
+ wrappedLines = wrapText(ctx, entry.label, textMaxWidth);
314
+ textWidth = Math.max(...wrappedLines.map(line => ctx.measureText(line).width));
315
+ textHeight = wrappedLines.length * fontSize * 1.2;
316
+ }
317
+ else {
318
+ wrappedLines = [entry.label];
319
+ textWidth = ctx.measureText(entry.label).width;
320
+ textHeight = fontSize;
321
+ }
322
+ const boxWidth = Math.min(maxWidth, textWidth + padding * 2);
323
+ const boxHeight = textHeight + padding * 2;
324
+ let labelBoxX = isLeftSide ? labelX - boxWidth : labelX;
325
+ let labelBoxY = labelY;
326
+ // Check for overlaps with previous labels and adjust position
327
+ const minSpacing = 5;
328
+ for (const prevPos of labelPositions) {
329
+ const overlapX = !(labelBoxX + boxWidth < prevPos.boxX || labelBoxX > prevPos.boxX + prevPos.boxWidth);
330
+ const overlapY = !(labelBoxY + boxHeight / 2 < prevPos.boxY - prevPos.boxHeight / 2 || labelBoxY - boxHeight / 2 > prevPos.boxY + prevPos.boxHeight / 2);
331
+ if (overlapX && overlapY) {
332
+ // Adjust vertically to avoid overlap
333
+ if (labelBoxY < prevPos.boxY) {
334
+ labelBoxY = prevPos.boxY - prevPos.boxHeight / 2 - boxHeight / 2 - minSpacing;
335
+ }
336
+ else {
337
+ labelBoxY = prevPos.boxY + prevPos.boxHeight / 2 + boxHeight / 2 + minSpacing;
338
+ }
339
+ }
340
+ }
341
+ labelPositions.push({
342
+ angle,
343
+ x: labelX,
344
+ y: labelY,
345
+ boxX: labelBoxX,
346
+ boxY: labelBoxY,
347
+ boxWidth,
348
+ boxHeight,
349
+ isLeftSide
350
+ });
351
+ });
352
+ // Draw labels
353
+ entries.forEach((entry, index) => {
354
+ if (index >= labelPositions.length)
355
+ return;
356
+ const pos = labelPositions[index];
357
+ const angle = pos.angle;
358
+ // Calculate text dimensions with wrapping
359
+ const textMaxWidth = maxWidth - padding * 2;
360
+ let wrappedLines;
361
+ if (wrapTextEnabled) {
362
+ wrappedLines = wrapText(ctx, entry.label, textMaxWidth);
363
+ }
364
+ else {
365
+ wrappedLines = [entry.label];
366
+ }
367
+ // Draw connecting line
368
+ ctx.strokeStyle = lineColor;
369
+ ctx.lineWidth = lineWidth;
370
+ ctx.beginPath();
371
+ // Calculate line start point (on pie edge)
372
+ const lineStartX = centerX + Math.cos(angle) * radius;
373
+ const lineStartY = centerY + Math.sin(angle) * radius;
374
+ // Calculate line end point (at label box edge)
375
+ const lineEndX = pos.isLeftSide ? pos.boxX + pos.boxWidth : pos.boxX;
376
+ const lineEndY = pos.boxY;
377
+ ctx.moveTo(lineStartX, lineStartY);
378
+ ctx.lineTo(lineEndX, lineEndY);
379
+ ctx.stroke();
380
+ // Draw label box background
381
+ ctx.fillStyle = backgroundColor;
382
+ ctx.fillRect(pos.boxX, pos.boxY - pos.boxHeight / 2, pos.boxWidth, pos.boxHeight);
383
+ // Draw label box border
384
+ ctx.strokeStyle = borderColor;
385
+ ctx.lineWidth = 1;
386
+ ctx.strokeRect(pos.boxX, pos.boxY - pos.boxHeight / 2, pos.boxWidth, pos.boxHeight);
387
+ // Draw label text
388
+ ctx.fillStyle = textColor;
389
+ ctx.textAlign = pos.isLeftSide ? 'right' : 'left';
390
+ ctx.textBaseline = 'middle';
391
+ if (wrapTextEnabled && wrappedLines.length > 1) {
392
+ const lineHeight = fontSize * 1.2;
393
+ const startY = pos.boxY - (wrappedLines.length - 1) * lineHeight / 2;
394
+ const textX = pos.isLeftSide ? pos.boxX + pos.boxWidth - padding : pos.boxX + padding;
395
+ wrappedLines.forEach((line, lineIndex) => {
396
+ ctx.fillText(line, textX, startY + lineIndex * lineHeight);
397
+ });
398
+ }
399
+ else {
400
+ const textX = pos.isLeftSide ? pos.boxX + pos.boxWidth - padding : pos.boxX + padding;
401
+ ctx.fillText(entry.label, textX, pos.boxY);
402
+ }
403
+ });
404
+ ctx.restore();
405
+ }
406
+ /**
407
+ * Creates a pie or donut chart
408
+ */
409
+ async function createPieChart(data, options = {}) {
410
+ // Extract options with defaults
411
+ const width = options.dimensions?.width ?? 800;
412
+ const height = options.dimensions?.height ?? 600;
413
+ const padding = options.dimensions?.padding || {};
414
+ const paddingTop = padding.top ?? 60;
415
+ const paddingRight = padding.right ?? 80;
416
+ const paddingBottom = padding.bottom ?? 80;
417
+ const paddingLeft = padding.left ?? 80;
418
+ const backgroundColor = options.appearance?.backgroundColor ?? '#FFFFFF';
419
+ const backgroundGradient = options.appearance?.backgroundGradient;
420
+ const backgroundImage = options.appearance?.backgroundImage;
421
+ const chartType = options.type ?? 'pie';
422
+ const donutInnerRadiusRatio = options.donutInnerRadius ?? 0.6;
423
+ const chartTitle = options.labels?.title?.text;
424
+ const chartTitleFontSize = options.labels?.title?.fontSize ?? 24; // Increased default like Chart.js
425
+ const chartTitleColor = options.labels?.title?.color ?? '#000000';
426
+ // Calculate title height to account for it in layout
427
+ const titleHeight = chartTitle ? chartTitleFontSize + 30 : 0; // Font size + spacing
428
+ const showValues = options.labels?.showValues ?? true;
429
+ const showLabels = options.labels?.showLabels ?? false;
430
+ const valueFormatter = options.labels?.valueFormat;
431
+ const standardLegendConfig = options.legends?.standard;
432
+ const connectedLegendConfig = options.legends?.connected;
433
+ const globalSliceOpacity = options.slices?.opacity;
434
+ const globalSliceShadow = options.slices?.shadow;
435
+ const globalSliceStroke = options.slices?.stroke;
436
+ // Calculate total value
437
+ const total = data.reduce((sum, slice) => sum + slice.value, 0);
438
+ if (total === 0) {
439
+ throw new Error('Pie Chart Error: Total value of all slices must be greater than 0');
440
+ }
441
+ // Create temporary canvas to calculate legend dimensions
442
+ const tempCanvas = (0, canvas_1.createCanvas)(1, 1);
443
+ const tempCtx = tempCanvas.getContext('2d');
444
+ // Calculate legend dimensions BEFORE drawing to reserve proper space
445
+ let standardLegendWidth = 0;
446
+ let standardLegendHeight = 0;
447
+ let connectedLegendSpace = { left: 0, right: 0, top: 0, bottom: 0 };
448
+ const legendEntries = data.map((slice, index) => {
449
+ const defaultColors = ['#4A90E2', '#50C878', '#FF6B6B', '#FFA500', '#9B59B6', '#F39C12', '#1ABC9C', '#E74C3C'];
450
+ return {
451
+ color: slice.color || defaultColors[index % defaultColors.length],
452
+ label: slice.label
453
+ };
454
+ });
455
+ // Calculate standard legend dimensions
456
+ if (standardLegendConfig?.show && legendEntries.length > 0) {
457
+ const legendFontSize = standardLegendConfig.fontSize ?? 16; // Increased default
458
+ const legendSpacing = standardLegendConfig.spacing ?? 18; // Increased default
459
+ const legendPadding = standardLegendConfig.padding ?? 10; // Increased default
460
+ const legendMaxWidth = standardLegendConfig.maxWidth;
461
+ const legendWrapText = standardLegendConfig.wrapText !== false;
462
+ const { width: legWidth, height: legHeight } = calculateStandardLegendDimensions(legendEntries, legendFontSize, legendSpacing, legendPadding, legendMaxWidth);
463
+ standardLegendWidth = legWidth;
464
+ standardLegendHeight = legHeight;
465
+ }
466
+ // Calculate connected legend space needed (approximate based on max label width)
467
+ if (connectedLegendConfig?.show && legendEntries.length > 0) {
468
+ const connectedFontSize = connectedLegendConfig.fontSize ?? 12;
469
+ const connectedMaxWidth = connectedLegendConfig.maxWidth ?? 150;
470
+ const connectedPadding = connectedLegendConfig.padding ?? 5;
471
+ tempCtx.font = `${connectedFontSize}px Arial`;
472
+ const maxLabelWidth = Math.max(...legendEntries.map(e => tempCtx.measureText(e.label).width));
473
+ const boxWidth = Math.min(connectedMaxWidth, maxLabelWidth + connectedPadding * 2);
474
+ const boxHeight = connectedFontSize + connectedPadding * 2;
475
+ // Connected labels extend from pie edge, so we need space around the pie
476
+ // Estimate: labels can extend up to boxWidth from pie edge, plus some margin
477
+ connectedLegendSpace.left = boxWidth + 30;
478
+ connectedLegendSpace.right = boxWidth + 30;
479
+ connectedLegendSpace.top = boxHeight / 2 + 20;
480
+ connectedLegendSpace.bottom = boxHeight / 2 + 20;
481
+ }
482
+ // Calculate chart area - DO NOT shrink, add width/height for legends instead
483
+ const standardLegendPosition = standardLegendConfig?.position ?? 'right';
484
+ const legendGap = 20;
485
+ // Calculate how much extra space we need for legends
486
+ let extraWidth = 0;
487
+ let extraHeight = 0;
488
+ // Add extra width/height for standard legend instead of shrinking
489
+ if (standardLegendConfig?.show) {
490
+ if (standardLegendPosition === 'left') {
491
+ extraWidth = standardLegendWidth + legendGap;
492
+ }
493
+ else if (standardLegendPosition === 'right') {
494
+ extraWidth = standardLegendWidth + legendGap;
495
+ }
496
+ else if (standardLegendPosition === 'top') {
497
+ extraHeight = standardLegendHeight + legendGap;
498
+ }
499
+ else if (standardLegendPosition === 'bottom') {
500
+ extraHeight = standardLegendHeight + legendGap;
501
+ }
502
+ }
503
+ // Add extra space for connected legend
504
+ extraWidth = Math.max(extraWidth, connectedLegendSpace.left, connectedLegendSpace.right);
505
+ extraHeight = Math.max(extraHeight, connectedLegendSpace.top, connectedLegendSpace.bottom);
506
+ // Adjust canvas dimensions to accommodate legends
507
+ const adjustedWidth = width + extraWidth;
508
+ const adjustedHeight = height + extraHeight;
509
+ // Calculate chart area - reposition chart based on legend position
510
+ let chartAreaLeft = paddingLeft;
511
+ let chartAreaRight = width - paddingRight;
512
+ let chartAreaTop = paddingTop + titleHeight; // Account for title
513
+ let chartAreaBottom = height - paddingBottom;
514
+ // Reposition chart based on legend position
515
+ if (standardLegendConfig?.show) {
516
+ if (standardLegendPosition === 'left') {
517
+ // Legend left → Chart shifts right
518
+ chartAreaLeft = paddingLeft + standardLegendWidth + legendGap;
519
+ chartAreaRight = adjustedWidth - paddingRight;
520
+ }
521
+ else if (standardLegendPosition === 'right') {
522
+ // Legend right → Chart shifts left (stays in original position, legend goes to right)
523
+ chartAreaLeft = paddingLeft;
524
+ chartAreaRight = width - paddingRight; // Keep original right edge
525
+ }
526
+ else if (standardLegendPosition === 'top') {
527
+ // Legend top → Chart shifts down
528
+ chartAreaTop = paddingTop + titleHeight + standardLegendHeight + legendGap;
529
+ chartAreaBottom = adjustedHeight - paddingBottom;
530
+ }
531
+ else if (standardLegendPosition === 'bottom') {
532
+ // Legend bottom → Chart shifts up
533
+ chartAreaTop = paddingTop + titleHeight;
534
+ chartAreaBottom = height - paddingBottom; // Keep original bottom edge
535
+ }
536
+ }
537
+ // Create canvas with adjusted dimensions
538
+ const canvas = (0, canvas_1.createCanvas)(adjustedWidth, adjustedHeight);
539
+ const ctx = canvas.getContext('2d');
540
+ // Fill background with gradient, color, or image
541
+ if (backgroundImage) {
542
+ try {
543
+ const bgImage = await (0, canvas_1.loadImage)(backgroundImage);
544
+ ctx.drawImage(bgImage, 0, 0, adjustedWidth, adjustedHeight);
545
+ }
546
+ catch (error) {
547
+ console.warn(`Failed to load background image: ${backgroundImage}`, error);
548
+ // Fallback to gradient or color
549
+ if (backgroundGradient) {
550
+ fillWithGradientOrColor(ctx, backgroundGradient, backgroundColor, backgroundColor, {
551
+ x: 0, y: 0, w: adjustedWidth, h: adjustedHeight
552
+ });
553
+ ctx.fillRect(0, 0, adjustedWidth, adjustedHeight);
554
+ }
555
+ else {
556
+ ctx.fillStyle = backgroundColor;
557
+ ctx.fillRect(0, 0, adjustedWidth, adjustedHeight);
558
+ }
559
+ }
560
+ }
561
+ else if (backgroundGradient) {
562
+ fillWithGradientOrColor(ctx, backgroundGradient, backgroundColor, backgroundColor, {
563
+ x: 0, y: 0, w: adjustedWidth, h: adjustedHeight
564
+ });
565
+ ctx.fillRect(0, 0, adjustedWidth, adjustedHeight);
566
+ }
567
+ else {
568
+ ctx.fillStyle = backgroundColor;
569
+ ctx.fillRect(0, 0, adjustedWidth, adjustedHeight);
570
+ }
571
+ // Draw chart title with enhanced styling
572
+ if (chartTitle) {
573
+ const titleStyle = options.labels?.title?.textStyle;
574
+ const titleGradient = options.labels?.title?.gradient;
575
+ const titleY = paddingTop + 10;
576
+ await renderEnhancedText(ctx, chartTitle, adjustedWidth / 2, titleY, titleStyle, chartTitleFontSize, chartTitleColor, titleGradient);
577
+ // Set alignment for title
578
+ ctx.save();
579
+ ctx.textAlign = 'center';
580
+ ctx.textBaseline = 'top';
581
+ ctx.restore();
582
+ }
583
+ // Calculate pie center and radius
584
+ const chartAreaWidth = chartAreaRight - chartAreaLeft;
585
+ const chartAreaHeight = chartAreaBottom - chartAreaTop;
586
+ // Chart.js approach: maxRadius = min(width, height) / 2, then use 100% of that
587
+ const maxRadius = Math.max(Math.min(chartAreaWidth, chartAreaHeight) / 2, 0);
588
+ const radius = maxRadius; // Use 100% of available space (Chart.js default)
589
+ const centerX = chartAreaLeft + chartAreaWidth / 2;
590
+ const centerY = chartAreaTop + chartAreaHeight / 2;
591
+ const innerRadius = chartType === 'donut' ? radius * donutInnerRadiusRatio : 0;
592
+ // Calculate slice angles
593
+ let currentAngle = -Math.PI / 2; // Start at top
594
+ const sliceAngles = [];
595
+ data.forEach(slice => {
596
+ const sliceAngle = (slice.value / total) * Math.PI * 2;
597
+ sliceAngles.push({
598
+ startAngle: currentAngle,
599
+ endAngle: currentAngle + sliceAngle
600
+ });
601
+ currentAngle += sliceAngle;
602
+ });
603
+ // Draw pie slices
604
+ const defaultColors = ['#4A90E2', '#50C878', '#FF6B6B', '#FFA500', '#9B59B6', '#F39C12', '#1ABC9C', '#E74C3C'];
605
+ for (let index = 0; index < data.length; index++) {
606
+ const slice = data[index];
607
+ const defaultColor = slice.color || defaultColors[index % defaultColors.length];
608
+ const angles = sliceAngles[index];
609
+ // Draw slice with gradient, opacity, shadow, and stroke support
610
+ ctx.save();
611
+ // Apply opacity
612
+ const effectiveOpacity = slice.opacity ?? globalSliceOpacity;
613
+ if (effectiveOpacity !== undefined) {
614
+ ctx.globalAlpha = effectiveOpacity;
615
+ }
616
+ // Apply shadow (slice shadow takes precedence over global)
617
+ const effectiveShadow = slice.shadow || globalSliceShadow;
618
+ if (effectiveShadow) {
619
+ ctx.shadowColor = effectiveShadow.color || 'rgba(0,0,0,0.3)';
620
+ ctx.shadowOffsetX = effectiveShadow.offsetX ?? 2;
621
+ ctx.shadowOffsetY = effectiveShadow.offsetY ?? 2;
622
+ ctx.shadowBlur = effectiveShadow.blur ?? 4;
623
+ }
624
+ ctx.beginPath();
625
+ if (chartType === 'donut') {
626
+ // Draw donut slice: outer arc -> line to inner -> inner arc (reverse) -> line back
627
+ ctx.arc(centerX, centerY, radius, angles.startAngle, angles.endAngle);
628
+ ctx.lineTo(centerX + Math.cos(angles.endAngle) * innerRadius, centerY + Math.sin(angles.endAngle) * innerRadius);
629
+ ctx.arc(centerX, centerY, innerRadius, angles.endAngle, angles.startAngle, true);
630
+ ctx.closePath();
631
+ }
632
+ else {
633
+ // Draw pie slice: center -> outer arc -> back to center
634
+ ctx.moveTo(centerX, centerY);
635
+ ctx.arc(centerX, centerY, radius, angles.startAngle, angles.endAngle);
636
+ ctx.closePath();
637
+ }
638
+ // Fill with gradient or color
639
+ if (slice.gradient) {
640
+ // Calculate bounding box for gradient
641
+ const midAngle = (angles.startAngle + angles.endAngle) / 2;
642
+ const gradientRect = {
643
+ x: centerX - radius,
644
+ y: centerY - radius,
645
+ w: radius * 2,
646
+ h: radius * 2
647
+ };
648
+ fillWithGradientOrColor(ctx, slice.gradient, defaultColor, defaultColor, gradientRect);
649
+ }
650
+ else {
651
+ ctx.fillStyle = defaultColor;
652
+ }
653
+ ctx.fill();
654
+ // Reset shadow before stroke
655
+ if (effectiveShadow) {
656
+ ctx.shadowColor = 'transparent';
657
+ ctx.shadowOffsetX = 0;
658
+ ctx.shadowOffsetY = 0;
659
+ ctx.shadowBlur = 0;
660
+ }
661
+ // Draw slice border/stroke (slice stroke takes precedence over global)
662
+ const effectiveStroke = slice.stroke || globalSliceStroke;
663
+ if (effectiveStroke && effectiveStroke.width && effectiveStroke.width > 0) {
664
+ ctx.beginPath();
665
+ // Outer arc
666
+ ctx.arc(centerX, centerY, radius, angles.startAngle, angles.endAngle);
667
+ if (chartType === 'donut') {
668
+ // Inner arc (reverse direction for donut)
669
+ ctx.arc(centerX, centerY, innerRadius, angles.endAngle, angles.startAngle, true);
670
+ }
671
+ else {
672
+ // Line back to center for pie
673
+ ctx.lineTo(centerX, centerY);
674
+ }
675
+ ctx.closePath();
676
+ if (effectiveStroke.gradient) {
677
+ const gradientRect = {
678
+ x: centerX - radius,
679
+ y: centerY - radius,
680
+ w: radius * 2,
681
+ h: radius * 2
682
+ };
683
+ ctx.strokeStyle = (0, imageProperties_1.createGradientFill)(ctx, effectiveStroke.gradient, gradientRect);
684
+ }
685
+ else {
686
+ ctx.strokeStyle = effectiveStroke.color || '#FFFFFF';
687
+ }
688
+ ctx.lineWidth = effectiveStroke.width;
689
+ ctx.lineJoin = 'bevel';
690
+ ctx.stroke();
691
+ }
692
+ else {
693
+ // Default border if no stroke specified (backward compatibility)
694
+ ctx.beginPath();
695
+ // Outer arc
696
+ ctx.arc(centerX, centerY, radius, angles.startAngle, angles.endAngle);
697
+ if (chartType === 'donut') {
698
+ // Inner arc (reverse direction for donut)
699
+ ctx.arc(centerX, centerY, innerRadius, angles.endAngle, angles.startAngle, true);
700
+ }
701
+ else {
702
+ // Line back to center for pie
703
+ ctx.lineTo(centerX, centerY);
704
+ }
705
+ ctx.closePath();
706
+ ctx.strokeStyle = '#FFFFFF'; // Chart.js default: white borders
707
+ ctx.lineWidth = 2; // Chart.js default: 2px
708
+ ctx.lineJoin = 'bevel';
709
+ ctx.stroke();
710
+ }
711
+ ctx.restore();
712
+ // Draw labels on slice if enabled
713
+ const percentage = (slice.value / total) * 100;
714
+ const sliceAngle = angles.endAngle - angles.startAngle;
715
+ const midAngle = (angles.startAngle + angles.endAngle) / 2;
716
+ // Build label text lines
717
+ const labelLines = [];
718
+ // Add slice label if enabled
719
+ if ((showLabels || slice.showLabel) && (slice.showLabel !== false)) {
720
+ labelLines.push(slice.label);
721
+ }
722
+ // Add value label if enabled (default format: "value percentage%" or custom format)
723
+ if (showValues && (slice.showValue !== false)) {
724
+ const valueText = slice.valueLabel || (valueFormatter ? valueFormatter(slice.value, percentage) : `${slice.value} ${percentage.toFixed(1)}%`);
725
+ labelLines.push(valueText);
726
+ }
727
+ // Draw labels - for small slices, position outside the slice edge
728
+ if (labelLines.length > 0) {
729
+ ctx.save();
730
+ // Determine if slice is small (less than 5% or very narrow angle)
731
+ const isSmallSlice = percentage < 5 || sliceAngle < 0.15; // ~8.6 degrees
732
+ let labelRadius;
733
+ let fontSize;
734
+ if (isSmallSlice) {
735
+ // For small slices: position labels OUTSIDE the slice edge (like wheel of fortune)
736
+ // Position text just outside the outer edge of the pie
737
+ labelRadius = radius + 15; // Outside the pie edge with some spacing
738
+ fontSize = 12; // Smaller font for external labels
739
+ }
740
+ else {
741
+ // For normal slices: position labels inside the slice
742
+ labelRadius = chartType === 'donut' ? (radius + innerRadius) / 2 : radius * 0.7;
743
+ fontSize = 14;
744
+ }
745
+ const labelX = centerX + Math.cos(midAngle) * labelRadius;
746
+ const labelY = centerY + Math.sin(midAngle) * labelRadius;
747
+ const lineHeight = fontSize;
748
+ // Rotate context to make text vertical (perpendicular to radius)
749
+ // Text is rotated vertically along the slice's angle direction
750
+ ctx.translate(labelX, labelY);
751
+ ctx.rotate(midAngle + Math.PI / 2); // Rotate 90 degrees to make text vertical
752
+ // Get label styling options
753
+ const sliceLabelStyle = options.labels?.sliceLabels?.textStyle;
754
+ const sliceLabelColor = options.labels?.sliceLabels?.color || '#000000';
755
+ const sliceLabelGradient = options.labels?.sliceLabels?.gradient;
756
+ const valueLabelStyle = options.labels?.valueLabels?.textStyle;
757
+ const valueLabelColor = options.labels?.valueLabels?.color || '#000000';
758
+ const valueLabelGradient = options.labels?.valueLabels?.gradient;
759
+ ctx.textAlign = 'center';
760
+ ctx.textBaseline = 'middle';
761
+ // Draw multiple lines if both label and value are shown
762
+ if (labelLines.length === 1) {
763
+ const isValueLabel = !showLabels || slice.showLabel === false;
764
+ const style = isValueLabel ? valueLabelStyle : sliceLabelStyle;
765
+ const color = isValueLabel ? valueLabelColor : sliceLabelColor;
766
+ const gradient = isValueLabel ? valueLabelGradient : sliceLabelGradient;
767
+ await renderEnhancedText(ctx, labelLines[0], 0, 0, style, fontSize, color, gradient);
768
+ }
769
+ else {
770
+ const startY = -(labelLines.length - 1) * lineHeight / 2;
771
+ for (let lineIndex = 0; lineIndex < labelLines.length; lineIndex++) {
772
+ const line = labelLines[lineIndex];
773
+ const isValueLabel = lineIndex > 0 || (!showLabels || slice.showLabel === false);
774
+ const style = isValueLabel ? valueLabelStyle : sliceLabelStyle;
775
+ const color = isValueLabel ? valueLabelColor : sliceLabelColor;
776
+ const gradient = isValueLabel ? valueLabelGradient : sliceLabelGradient;
777
+ await renderEnhancedText(ctx, line, 0, startY + lineIndex * lineHeight, style, fontSize, color, gradient);
778
+ }
779
+ }
780
+ ctx.restore();
781
+ }
782
+ }
783
+ // Draw connected legend (Type 2) - must be drawn after slices but before standard legend
784
+ if (connectedLegendConfig) {
785
+ drawConnectedLegend(ctx, legendEntries, sliceAngles, centerX, centerY, radius, innerRadius, connectedLegendConfig);
786
+ }
787
+ // Draw standard legend (Type 1)
788
+ if (standardLegendConfig) {
789
+ // Pass title height so legend can position correctly when at top
790
+ await drawStandardLegend(ctx, legendEntries, standardLegendConfig, adjustedWidth, adjustedHeight, { top: paddingTop, right: paddingRight, bottom: paddingBottom, left: paddingLeft }, { left: chartAreaLeft, right: chartAreaRight, top: chartAreaTop, bottom: chartAreaBottom }, legendGap, titleHeight);
791
+ }
792
+ return canvas.toBuffer('image/png');
793
+ }
794
+ //# sourceMappingURL=piechart.js.map