apexify.js 5.2.2 → 5.2.6

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 (237) hide show
  1. package/CHANGELOG.md +173 -4
  2. package/README.md +66 -0
  3. package/dist/cjs/Canvas/ApexPainter.d.ts +191 -4
  4. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
  5. package/dist/cjs/Canvas/ApexPainter.js +201 -96
  6. package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
  7. package/dist/cjs/Canvas/extended/CanvasCreator.d.ts.map +1 -1
  8. package/dist/cjs/Canvas/extended/CanvasCreator.js +3 -18
  9. package/dist/cjs/Canvas/extended/CanvasCreator.js.map +1 -1
  10. package/dist/cjs/Canvas/extended/GIFCreator.d.ts +15 -3
  11. package/dist/cjs/Canvas/extended/GIFCreator.d.ts.map +1 -1
  12. package/dist/cjs/Canvas/extended/GIFCreator.js +107 -9
  13. package/dist/cjs/Canvas/extended/GIFCreator.js.map +1 -1
  14. package/dist/cjs/Canvas/extended/HitDetectionCreator.d.ts +148 -0
  15. package/dist/cjs/Canvas/extended/HitDetectionCreator.d.ts.map +1 -0
  16. package/dist/cjs/Canvas/extended/HitDetectionCreator.js +294 -0
  17. package/dist/cjs/Canvas/extended/HitDetectionCreator.js.map +1 -0
  18. package/dist/cjs/Canvas/extended/ImageCreator.d.ts +3 -2
  19. package/dist/cjs/Canvas/extended/ImageCreator.d.ts.map +1 -1
  20. package/dist/cjs/Canvas/extended/ImageCreator.js +43 -4
  21. package/dist/cjs/Canvas/extended/ImageCreator.js.map +1 -1
  22. package/dist/cjs/Canvas/extended/Path2DCreator.d.ts +166 -0
  23. package/dist/cjs/Canvas/extended/Path2DCreator.d.ts.map +1 -0
  24. package/dist/cjs/Canvas/extended/Path2DCreator.js +246 -0
  25. package/dist/cjs/Canvas/extended/Path2DCreator.js.map +1 -0
  26. package/dist/cjs/Canvas/extended/PixelDataCreator.d.ts +69 -0
  27. package/dist/cjs/Canvas/extended/PixelDataCreator.d.ts.map +1 -0
  28. package/dist/cjs/Canvas/extended/PixelDataCreator.js +232 -0
  29. package/dist/cjs/Canvas/extended/PixelDataCreator.js.map +1 -0
  30. package/dist/cjs/Canvas/extended/TextCreator.d.ts.map +1 -1
  31. package/dist/cjs/Canvas/extended/TextCreator.js +0 -7
  32. package/dist/cjs/Canvas/extended/TextCreator.js.map +1 -1
  33. package/dist/cjs/Canvas/extended/TextMetricsCreator.d.ts +18 -0
  34. package/dist/cjs/Canvas/extended/TextMetricsCreator.d.ts.map +1 -0
  35. package/dist/cjs/Canvas/extended/TextMetricsCreator.js +194 -0
  36. package/dist/cjs/Canvas/extended/TextMetricsCreator.js.map +1 -0
  37. package/dist/cjs/Canvas/extended/VideoCreator.d.ts.map +1 -1
  38. package/dist/cjs/Canvas/extended/VideoCreator.js +1 -42
  39. package/dist/cjs/Canvas/extended/VideoCreator.js.map +1 -1
  40. package/dist/cjs/Canvas/utils/Background/bg.d.ts.map +1 -1
  41. package/dist/cjs/Canvas/utils/Background/bg.js +2 -15
  42. package/dist/cjs/Canvas/utils/Background/bg.js.map +1 -1
  43. package/dist/cjs/Canvas/utils/Charts/barchart.d.ts.map +1 -1
  44. package/dist/cjs/Canvas/utils/Charts/barchart.js +32 -277
  45. package/dist/cjs/Canvas/utils/Charts/barchart.js.map +1 -1
  46. package/dist/cjs/Canvas/utils/Charts/comparisonchart.d.ts.map +1 -1
  47. package/dist/cjs/Canvas/utils/Charts/comparisonchart.js +0 -41
  48. package/dist/cjs/Canvas/utils/Charts/comparisonchart.js.map +1 -1
  49. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.d.ts.map +1 -1
  50. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.js +42 -241
  51. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.js.map +1 -1
  52. package/dist/cjs/Canvas/utils/Charts/linechart.d.ts.map +1 -1
  53. package/dist/cjs/Canvas/utils/Charts/linechart.js +16 -148
  54. package/dist/cjs/Canvas/utils/Charts/linechart.js.map +1 -1
  55. package/dist/cjs/Canvas/utils/Charts/piechart.d.ts.map +1 -1
  56. package/dist/cjs/Canvas/utils/Charts/piechart.js +21 -117
  57. package/dist/cjs/Canvas/utils/Charts/piechart.js.map +1 -1
  58. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts.map +1 -1
  59. package/dist/cjs/Canvas/utils/Custom/advancedLines.js +3 -16
  60. package/dist/cjs/Canvas/utils/Custom/advancedLines.js.map +1 -1
  61. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  62. package/dist/cjs/Canvas/utils/Custom/customLines.js +0 -7
  63. package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
  64. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -1
  65. package/dist/cjs/Canvas/utils/General/batchOperations.js +0 -4
  66. package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -1
  67. package/dist/cjs/Canvas/utils/General/conversion.d.ts.map +1 -1
  68. package/dist/cjs/Canvas/utils/General/conversion.js.map +1 -1
  69. package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
  70. package/dist/cjs/Canvas/utils/General/general functions.js +0 -19
  71. package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
  72. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts.map +1 -1
  73. package/dist/cjs/Canvas/utils/General/imageCompression.js +0 -18
  74. package/dist/cjs/Canvas/utils/General/imageCompression.js.map +1 -1
  75. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -1
  76. package/dist/cjs/Canvas/utils/General/imageStitching.js +0 -14
  77. package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -1
  78. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts.map +1 -1
  79. package/dist/cjs/Canvas/utils/Image/imageEffects.js +8 -16
  80. package/dist/cjs/Canvas/utils/Image/imageEffects.js.map +1 -1
  81. package/dist/cjs/Canvas/utils/Image/imageFilters.js +12 -30
  82. package/dist/cjs/Canvas/utils/Image/imageFilters.js.map +1 -1
  83. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -1
  84. package/dist/cjs/Canvas/utils/Image/imageMasking.js +0 -22
  85. package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -1
  86. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  87. package/dist/cjs/Canvas/utils/Image/imageProperties.js +2 -37
  88. package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
  89. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -1
  90. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js +6 -23
  91. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js.map +1 -1
  92. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -1
  93. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js +7 -16
  94. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -1
  95. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  96. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +1 -16
  97. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  98. package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts.map +1 -1
  99. package/dist/cjs/Canvas/utils/Shapes/shapes.js +120 -39
  100. package/dist/cjs/Canvas/utils/Shapes/shapes.js.map +1 -1
  101. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
  102. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +5 -58
  103. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
  104. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -1
  105. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js +1 -4
  106. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js.map +1 -1
  107. package/dist/cjs/Canvas/utils/Texts/textProperties.d.ts.map +1 -1
  108. package/dist/cjs/Canvas/utils/Texts/textProperties.js +0 -18
  109. package/dist/cjs/Canvas/utils/Texts/textProperties.js.map +1 -1
  110. package/dist/cjs/Canvas/utils/Video/videoHelpers.d.ts.map +1 -1
  111. package/dist/cjs/Canvas/utils/Video/videoHelpers.js +9 -110
  112. package/dist/cjs/Canvas/utils/Video/videoHelpers.js.map +1 -1
  113. package/dist/cjs/Canvas/utils/types.d.ts +136 -1
  114. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
  115. package/dist/cjs/Canvas/utils/types.js.map +1 -1
  116. package/dist/cjs/Canvas/utils/utils.d.ts +4 -2
  117. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
  118. package/dist/cjs/Canvas/utils/utils.js.map +1 -1
  119. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  120. package/dist/esm/Canvas/ApexPainter.d.ts +191 -4
  121. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
  122. package/dist/esm/Canvas/ApexPainter.js +201 -96
  123. package/dist/esm/Canvas/ApexPainter.js.map +1 -1
  124. package/dist/esm/Canvas/extended/CanvasCreator.d.ts.map +1 -1
  125. package/dist/esm/Canvas/extended/CanvasCreator.js +3 -18
  126. package/dist/esm/Canvas/extended/CanvasCreator.js.map +1 -1
  127. package/dist/esm/Canvas/extended/GIFCreator.d.ts +15 -3
  128. package/dist/esm/Canvas/extended/GIFCreator.d.ts.map +1 -1
  129. package/dist/esm/Canvas/extended/GIFCreator.js +107 -9
  130. package/dist/esm/Canvas/extended/GIFCreator.js.map +1 -1
  131. package/dist/esm/Canvas/extended/HitDetectionCreator.d.ts +148 -0
  132. package/dist/esm/Canvas/extended/HitDetectionCreator.d.ts.map +1 -0
  133. package/dist/esm/Canvas/extended/HitDetectionCreator.js +294 -0
  134. package/dist/esm/Canvas/extended/HitDetectionCreator.js.map +1 -0
  135. package/dist/esm/Canvas/extended/ImageCreator.d.ts +3 -2
  136. package/dist/esm/Canvas/extended/ImageCreator.d.ts.map +1 -1
  137. package/dist/esm/Canvas/extended/ImageCreator.js +43 -4
  138. package/dist/esm/Canvas/extended/ImageCreator.js.map +1 -1
  139. package/dist/esm/Canvas/extended/Path2DCreator.d.ts +166 -0
  140. package/dist/esm/Canvas/extended/Path2DCreator.d.ts.map +1 -0
  141. package/dist/esm/Canvas/extended/Path2DCreator.js +246 -0
  142. package/dist/esm/Canvas/extended/Path2DCreator.js.map +1 -0
  143. package/dist/esm/Canvas/extended/PixelDataCreator.d.ts +69 -0
  144. package/dist/esm/Canvas/extended/PixelDataCreator.d.ts.map +1 -0
  145. package/dist/esm/Canvas/extended/PixelDataCreator.js +232 -0
  146. package/dist/esm/Canvas/extended/PixelDataCreator.js.map +1 -0
  147. package/dist/esm/Canvas/extended/TextCreator.d.ts.map +1 -1
  148. package/dist/esm/Canvas/extended/TextCreator.js +0 -7
  149. package/dist/esm/Canvas/extended/TextCreator.js.map +1 -1
  150. package/dist/esm/Canvas/extended/TextMetricsCreator.d.ts +18 -0
  151. package/dist/esm/Canvas/extended/TextMetricsCreator.d.ts.map +1 -0
  152. package/dist/esm/Canvas/extended/TextMetricsCreator.js +194 -0
  153. package/dist/esm/Canvas/extended/TextMetricsCreator.js.map +1 -0
  154. package/dist/esm/Canvas/extended/VideoCreator.d.ts.map +1 -1
  155. package/dist/esm/Canvas/extended/VideoCreator.js +1 -42
  156. package/dist/esm/Canvas/extended/VideoCreator.js.map +1 -1
  157. package/dist/esm/Canvas/utils/Background/bg.d.ts.map +1 -1
  158. package/dist/esm/Canvas/utils/Background/bg.js +2 -15
  159. package/dist/esm/Canvas/utils/Background/bg.js.map +1 -1
  160. package/dist/esm/Canvas/utils/Charts/barchart.d.ts.map +1 -1
  161. package/dist/esm/Canvas/utils/Charts/barchart.js +32 -277
  162. package/dist/esm/Canvas/utils/Charts/barchart.js.map +1 -1
  163. package/dist/esm/Canvas/utils/Charts/comparisonchart.d.ts.map +1 -1
  164. package/dist/esm/Canvas/utils/Charts/comparisonchart.js +0 -41
  165. package/dist/esm/Canvas/utils/Charts/comparisonchart.js.map +1 -1
  166. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.d.ts.map +1 -1
  167. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.js +42 -241
  168. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.js.map +1 -1
  169. package/dist/esm/Canvas/utils/Charts/linechart.d.ts.map +1 -1
  170. package/dist/esm/Canvas/utils/Charts/linechart.js +16 -148
  171. package/dist/esm/Canvas/utils/Charts/linechart.js.map +1 -1
  172. package/dist/esm/Canvas/utils/Charts/piechart.d.ts.map +1 -1
  173. package/dist/esm/Canvas/utils/Charts/piechart.js +21 -117
  174. package/dist/esm/Canvas/utils/Charts/piechart.js.map +1 -1
  175. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts.map +1 -1
  176. package/dist/esm/Canvas/utils/Custom/advancedLines.js +3 -16
  177. package/dist/esm/Canvas/utils/Custom/advancedLines.js.map +1 -1
  178. package/dist/esm/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  179. package/dist/esm/Canvas/utils/Custom/customLines.js +0 -7
  180. package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
  181. package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -1
  182. package/dist/esm/Canvas/utils/General/batchOperations.js +0 -4
  183. package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -1
  184. package/dist/esm/Canvas/utils/General/conversion.d.ts.map +1 -1
  185. package/dist/esm/Canvas/utils/General/conversion.js.map +1 -1
  186. package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
  187. package/dist/esm/Canvas/utils/General/general functions.js +0 -19
  188. package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
  189. package/dist/esm/Canvas/utils/General/imageCompression.d.ts.map +1 -1
  190. package/dist/esm/Canvas/utils/General/imageCompression.js +0 -18
  191. package/dist/esm/Canvas/utils/General/imageCompression.js.map +1 -1
  192. package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -1
  193. package/dist/esm/Canvas/utils/General/imageStitching.js +0 -14
  194. package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -1
  195. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts.map +1 -1
  196. package/dist/esm/Canvas/utils/Image/imageEffects.js +8 -16
  197. package/dist/esm/Canvas/utils/Image/imageEffects.js.map +1 -1
  198. package/dist/esm/Canvas/utils/Image/imageFilters.js +12 -30
  199. package/dist/esm/Canvas/utils/Image/imageFilters.js.map +1 -1
  200. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -1
  201. package/dist/esm/Canvas/utils/Image/imageMasking.js +0 -22
  202. package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -1
  203. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  204. package/dist/esm/Canvas/utils/Image/imageProperties.js +2 -37
  205. package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
  206. package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -1
  207. package/dist/esm/Canvas/utils/Image/professionalImageFilters.js +6 -23
  208. package/dist/esm/Canvas/utils/Image/professionalImageFilters.js.map +1 -1
  209. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -1
  210. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js +7 -16
  211. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -1
  212. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  213. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +1 -16
  214. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  215. package/dist/esm/Canvas/utils/Shapes/shapes.d.ts.map +1 -1
  216. package/dist/esm/Canvas/utils/Shapes/shapes.js +120 -39
  217. package/dist/esm/Canvas/utils/Shapes/shapes.js.map +1 -1
  218. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
  219. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +5 -58
  220. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
  221. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -1
  222. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js +1 -4
  223. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js.map +1 -1
  224. package/dist/esm/Canvas/utils/Texts/textProperties.d.ts.map +1 -1
  225. package/dist/esm/Canvas/utils/Texts/textProperties.js +0 -18
  226. package/dist/esm/Canvas/utils/Texts/textProperties.js.map +1 -1
  227. package/dist/esm/Canvas/utils/Video/videoHelpers.d.ts.map +1 -1
  228. package/dist/esm/Canvas/utils/Video/videoHelpers.js +9 -110
  229. package/dist/esm/Canvas/utils/Video/videoHelpers.js.map +1 -1
  230. package/dist/esm/Canvas/utils/types.d.ts +136 -1
  231. package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
  232. package/dist/esm/Canvas/utils/types.js.map +1 -1
  233. package/dist/esm/Canvas/utils/utils.d.ts +4 -2
  234. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
  235. package/dist/esm/Canvas/utils/utils.js.map +1 -1
  236. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  237. package/package.json +1 -3
@@ -13,7 +13,6 @@ const imageProperties_1 = require("../Image/imageProperties");
13
13
  */
14
14
  async function renderEnhancedText(ctx, text, x, y, style, fontSize, color, textGradient) {
15
15
  ctx.save();
16
- // Preserve text alignment settings
17
16
  const savedTextAlign = ctx.textAlign;
18
17
  const savedTextBaseline = ctx.textBaseline;
19
18
  const effectiveFontSize = fontSize || style?.fontSize || 16;
@@ -25,10 +24,8 @@ async function renderEnhancedText(ctx, text, x, y, style, fontSize, color, textG
25
24
  fontString += 'italic ';
26
25
  fontString += `${effectiveFontSize}px "${fontFamily}"`;
27
26
  ctx.font = fontString;
28
- // Restore text alignment to ensure correct positioning
29
27
  ctx.textAlign = savedTextAlign;
30
28
  ctx.textBaseline = savedTextBaseline;
31
- // Register custom font if provided
32
29
  if (style?.fontPath && style?.fontName) {
33
30
  try {
34
31
  const { GlobalFonts } = await import('@napi-rs/canvas');
@@ -41,7 +38,6 @@ async function renderEnhancedText(ctx, text, x, y, style, fontSize, color, textG
41
38
  console.warn(`Failed to register font: ${style.fontPath}`, error);
42
39
  }
43
40
  }
44
- // Apply shadow
45
41
  if (style?.shadow) {
46
42
  ctx.shadowColor = style.shadow.color || 'rgba(0,0,0,0.5)';
47
43
  ctx.shadowOffsetX = style.shadow.offsetX || 2;
@@ -51,7 +47,6 @@ async function renderEnhancedText(ctx, text, x, y, style, fontSize, color, textG
51
47
  ctx.globalAlpha = style.shadow.opacity;
52
48
  }
53
49
  }
54
- // Set fill style (gradient or color)
55
50
  if (textGradient) {
56
51
  const metrics = ctx.measureText(text);
57
52
  ctx.fillStyle = (0, imageProperties_1.createGradientFill)(ctx, textGradient, {
@@ -61,9 +56,7 @@ async function renderEnhancedText(ctx, text, x, y, style, fontSize, color, textG
61
56
  else if (color) {
62
57
  ctx.fillStyle = color;
63
58
  }
64
- // Draw text
65
59
  ctx.fillText(text, x, y);
66
- // Apply stroke
67
60
  if (style?.stroke) {
68
61
  ctx.strokeStyle = style.stroke.color || '#000000';
69
62
  ctx.lineWidth = style.stroke.width || 1;
@@ -75,7 +68,6 @@ async function renderEnhancedText(ctx, text, x, y, style, fontSize, color, textG
75
68
  }
76
69
  ctx.strokeText(text, x, y);
77
70
  }
78
- // Reset shadow and alpha
79
71
  ctx.shadowColor = 'transparent';
80
72
  ctx.shadowOffsetX = 0;
81
73
  ctx.shadowOffsetY = 0;
@@ -99,10 +91,8 @@ function fillWithGradientOrColor(ctx, gradient, color, defaultColor = '#000000',
99
91
  */
100
92
  function drawBar(ctx, x, y, width, height, color, gradient, opacity, shadow, stroke, globalShadow, globalStroke) {
101
93
  ctx.save();
102
- // Apply opacity
103
94
  const effectiveOpacity = opacity !== undefined ? opacity : 1;
104
95
  ctx.globalAlpha = effectiveOpacity;
105
- // Apply shadow (segment/item shadow takes precedence over global)
106
96
  const effectiveShadow = shadow || globalShadow;
107
97
  if (effectiveShadow) {
108
98
  ctx.shadowColor = effectiveShadow.color || 'rgba(0,0,0,0.3)';
@@ -110,7 +100,6 @@ function drawBar(ctx, x, y, width, height, color, gradient, opacity, shadow, str
110
100
  ctx.shadowOffsetY = effectiveShadow.offsetY ?? 2;
111
101
  ctx.shadowBlur = effectiveShadow.blur ?? 4;
112
102
  }
113
- // Draw bar fill
114
103
  ctx.beginPath();
115
104
  ctx.rect(x, y, width, height);
116
105
  if (gradient) {
@@ -120,14 +109,12 @@ function drawBar(ctx, x, y, width, height, color, gradient, opacity, shadow, str
120
109
  ctx.fillStyle = color;
121
110
  }
122
111
  ctx.fill();
123
- // Reset shadow before stroke
124
112
  if (effectiveShadow) {
125
113
  ctx.shadowColor = 'transparent';
126
114
  ctx.shadowOffsetX = 0;
127
115
  ctx.shadowOffsetY = 0;
128
116
  ctx.shadowBlur = 0;
129
117
  }
130
- // Apply stroke (segment/item stroke takes precedence over global)
131
118
  const effectiveStroke = stroke || globalStroke;
132
119
  if (effectiveStroke && effectiveStroke.width && effectiveStroke.width > 0) {
133
120
  ctx.beginPath();
@@ -174,62 +161,48 @@ function drawYAxisTicks(ctx, originX, originY, axisEndY, minValue, maxValue, ste
174
161
  ctx.textBaseline = 'middle';
175
162
  const chartHeight = originY - axisEndY;
176
163
  if (customValues && customValues.length > 0) {
177
- // Use custom Y-axis values
178
164
  const actualMin = Math.min(...customValues);
179
165
  const actualMax = Math.max(...customValues);
180
166
  const range = actualMax - actualMin;
181
- // Always position ticks based on their actual values
182
- // valueSpacing is used only to prevent label overlap (skip labels that are too close)
183
167
  let lastLabelY = Infinity;
184
- const minLabelSpacing = valueSpacing && valueSpacing > 0 ? valueSpacing : 30; // Minimum pixels between labels
168
+ const minLabelSpacing = valueSpacing && valueSpacing > 0 ? valueSpacing : 30;
185
169
  customValues.forEach((value) => {
186
170
  const y = originY - ((value - actualMin) / range) * chartHeight;
187
- // Check if this label would overlap with the previous one
188
171
  if (Math.abs(y - lastLabelY) < minLabelSpacing) {
189
- // Skip this label to prevent overlap, but still draw the tick mark
190
172
  ctx.beginPath();
191
173
  ctx.moveTo(originX - 5, y);
192
174
  ctx.lineTo(originX, y);
193
175
  ctx.stroke();
194
176
  return;
195
177
  }
196
- // Draw tick mark
197
178
  ctx.beginPath();
198
179
  ctx.moveTo(originX - 5, y);
199
180
  ctx.lineTo(originX, y);
200
181
  ctx.stroke();
201
- // Draw label
202
182
  ctx.fillText(value.toFixed(1), originX - 10, y);
203
- lastLabelY = y; // Update last label position
183
+ lastLabelY = y;
204
184
  });
205
185
  }
206
186
  else {
207
- // Use regular step-based ticks
208
187
  const range = maxValue - minValue;
209
- // Always position ticks based on their actual values
210
- // valueSpacing is used only to prevent label overlap (skip labels that are too close)
211
188
  let lastLabelY = Infinity;
212
- const minLabelSpacing = valueSpacing && valueSpacing > 0 ? valueSpacing : 30; // Minimum pixels between labels
189
+ const minLabelSpacing = valueSpacing && valueSpacing > 0 ? valueSpacing : 30;
213
190
  for (let value = minValue; value <= maxValue; value += step) {
214
191
  const y = originY - ((value - minValue) / range) * chartHeight;
215
- // Check if this label would overlap with the previous one
216
192
  if (Math.abs(y - lastLabelY) < minLabelSpacing && value > minValue) {
217
- // Skip this label to prevent overlap, but still draw the tick mark
218
193
  ctx.beginPath();
219
194
  ctx.moveTo(originX - 5, y);
220
195
  ctx.lineTo(originX, y);
221
196
  ctx.stroke();
222
197
  continue;
223
198
  }
224
- // Draw tick mark
225
199
  ctx.beginPath();
226
200
  ctx.moveTo(originX - 5, y);
227
201
  ctx.lineTo(originX, y);
228
202
  ctx.stroke();
229
- // Draw label
230
203
  const labelText = value.toFixed(1);
231
204
  ctx.fillText(labelText, originX - 10, y);
232
- lastLabelY = y; // Update last label position
205
+ lastLabelY = y;
233
206
  }
234
207
  }
235
208
  ctx.restore();
@@ -245,98 +218,76 @@ function drawXAxisTicks(ctx, originX, originY, axisEndX, minValue, maxValue, ste
245
218
  ctx.textBaseline = 'top';
246
219
  const chartWidth = axisEndX - originX;
247
220
  if (customValues && customValues.length > 0) {
248
- // Use custom X-axis values
249
221
  if (valueSpacing && valueSpacing > 0) {
250
- // Use specified spacing - position ticks with exact pixel spacing
251
222
  let currentX = originX;
252
223
  customValues.forEach((value, index) => {
253
224
  if (index === 0) {
254
225
  currentX = originX;
255
226
  }
256
227
  else {
257
- currentX += valueSpacing; // Move right by spacing amount
228
+ currentX += valueSpacing;
258
229
  }
259
- // Clamp to chart area
260
230
  if (currentX >= originX && currentX <= axisEndX) {
261
- // Draw tick mark
262
231
  ctx.beginPath();
263
232
  ctx.moveTo(currentX, originY);
264
233
  ctx.lineTo(currentX, originY + 5);
265
234
  ctx.stroke();
266
- // Draw label
267
235
  ctx.fillText(value.toString(), currentX, originY + 10);
268
236
  }
269
237
  });
270
238
  }
271
239
  else {
272
- // Use index-based positioning (original behavior)
273
- // But check for label overlap and skip labels if they're too close
274
240
  const totalValues = customValues.length;
275
241
  const divisor = totalValues > 1 ? totalValues - 1 : 1;
276
242
  let lastLabelX = -Infinity;
277
- const minLabelSpacing = 40; // Minimum pixels between label centers
243
+ const minLabelSpacing = 40;
278
244
  customValues.forEach((value, index) => {
279
- // Position based on index in the array, not the numeric value
280
245
  const x = originX + (index / divisor) * chartWidth;
281
246
  const labelText = value.toString();
282
247
  const labelWidth = ctx.measureText(labelText).width;
283
- // Check if this label would overlap with the previous one
284
248
  if (x - lastLabelX < minLabelSpacing && index > 0) {
285
- // Skip this label to prevent overlap
286
249
  return;
287
250
  }
288
- // Draw tick mark
289
251
  ctx.beginPath();
290
252
  ctx.moveTo(x, originY);
291
253
  ctx.lineTo(x, originY + 5);
292
254
  ctx.stroke();
293
- // Draw label
294
255
  ctx.fillText(labelText, x, originY + 10);
295
- lastLabelX = x + labelWidth / 2; // Update last label position
256
+ lastLabelX = x + labelWidth / 2;
296
257
  });
297
258
  }
298
259
  }
299
260
  else {
300
- // Use regular step-based ticks
301
261
  const range = maxValue - minValue;
302
262
  if (valueSpacing && valueSpacing > 0) {
303
- // Use specified spacing - only show ticks that fit with spacing
304
263
  let currentX = originX;
305
264
  let currentValue = minValue;
306
265
  while (currentX <= axisEndX && currentValue <= maxValue) {
307
- // Draw tick mark
308
266
  ctx.beginPath();
309
267
  ctx.moveTo(currentX, originY);
310
268
  ctx.lineTo(currentX, originY + 5);
311
269
  ctx.stroke();
312
- // Draw label
313
270
  ctx.fillText(currentValue.toString(), currentX, originY + 10);
314
271
  currentX += valueSpacing;
315
272
  currentValue += step;
316
273
  }
317
274
  }
318
275
  else {
319
- // Original behavior - evenly distribute based on value
320
- // But check for label overlap and skip labels if they're too close
321
276
  let lastLabelX = -Infinity;
322
- const minLabelSpacing = 40; // Minimum pixels between label centers
277
+ const minLabelSpacing = 40;
323
278
  for (let value = minValue; value <= maxValue; value += step) {
324
279
  const x = originX + ((value - minValue) / range) * chartWidth;
325
280
  const labelText = value.toString();
326
281
  const labelWidth = ctx.measureText(labelText).width;
327
- // Check if this label would overlap with the previous one
328
282
  if (x - lastLabelX < minLabelSpacing && value > minValue) {
329
- // Skip this label to prevent overlap
330
283
  continue;
331
284
  }
332
- // Draw tick mark
333
285
  ctx.beginPath();
334
286
  ctx.moveTo(x, originY);
335
287
  ctx.lineTo(x, originY + 5);
336
288
  ctx.stroke();
337
- // Draw label
338
289
  ctx.fillText(labelText, x, originY + 10);
339
- lastLabelX = x + labelWidth / 2; // Update last label position
290
+ lastLabelX = x + labelWidth / 2;
340
291
  }
341
292
  }
342
293
  }
@@ -351,7 +302,6 @@ function calculateLegendDimensions(legend, fontSize, maxWidth, wrapTextEnabled =
351
302
  const boxSize = 15;
352
303
  const spacing = 10;
353
304
  const padding = paddingBox;
354
- // Create a temporary canvas to measure text
355
305
  const tempCanvas = (0, canvas_1.createCanvas)(1, 1);
356
306
  const tempCtx = tempCanvas.getContext('2d');
357
307
  tempCtx.font = `${fontSize}px Arial`;
@@ -411,12 +361,10 @@ async function drawLegendAtPosition(ctx, legend, legendX, legendY, fontSize, bac
411
361
  const spacing = 10;
412
362
  const padding = paddingBox ?? 8;
413
363
  ctx.font = `${fontSize}px Arial`;
414
- // Determine colors
415
364
  const isDarkBackground = backgroundColor === '#000000' || backgroundColor.toLowerCase() === 'black';
416
365
  const effectiveTextColor = textColor ?? (isDarkBackground ? '#FFFFFF' : '#000000');
417
366
  const effectiveBgColor = isDarkBackground ? 'rgba(0, 0, 0, 0.8)' : (backgroundColor.startsWith('rgba') || backgroundColor.startsWith('rgb') ? backgroundColor : 'rgba(255, 255, 255, 0.9)');
418
367
  const effectiveBorderColor = borderColor ?? (isDarkBackground ? '#FFFFFF' : '#000000');
419
- // Calculate dimensions with text wrapping support
420
368
  const textSpacing = 10;
421
369
  const effectiveMaxWidth = maxWidth ? maxWidth - padding * 2 - boxSize - textSpacing : undefined;
422
370
  let maxEntryWidth = 0;
@@ -427,7 +375,7 @@ async function drawLegendAtPosition(ctx, legend, legendX, legendY, fontSize, bac
427
375
  if (wrapTextEnabled && effectiveMaxWidth) {
428
376
  const wrappedLines = wrapText(ctx, entry.label, effectiveMaxWidth);
429
377
  textWidth = Math.max(...wrappedLines.map(line => ctx.measureText(line).width));
430
- textHeight = wrappedLines.length * fontSize * 1.2; // Line height multiplier
378
+ textHeight = wrappedLines.length * fontSize * 1.2;
431
379
  }
432
380
  else {
433
381
  textWidth = ctx.measureText(entry.label).width;
@@ -439,16 +387,13 @@ async function drawLegendAtPosition(ctx, legend, legendX, legendY, fontSize, bac
439
387
  });
440
388
  const legendWidth = maxWidth ? maxWidth : maxEntryWidth + padding * 2;
441
389
  const legendHeight = entryHeights.reduce((sum, h, i) => sum + h + (i < entryHeights.length - 1 ? spacing : 0), 0) + padding * 2;
442
- // Draw legend background (gradient or color)
443
390
  ctx.beginPath();
444
391
  ctx.rect(legendX, legendY, legendWidth, legendHeight);
445
392
  fillWithGradientOrColor(ctx, backgroundGradient, effectiveBgColor, effectiveBgColor, { x: legendX, y: legendY, w: legendWidth, h: legendHeight });
446
393
  ctx.fill();
447
- // Draw legend border
448
394
  ctx.strokeStyle = effectiveBorderColor;
449
395
  ctx.lineWidth = 1;
450
396
  ctx.strokeRect(legendX, legendY, legendWidth, legendHeight);
451
- // Draw legend entries
452
397
  ctx.textAlign = 'left';
453
398
  ctx.textBaseline = 'middle';
454
399
  ctx.fillStyle = effectiveTextColor;
@@ -457,16 +402,13 @@ async function drawLegendAtPosition(ctx, legend, legendX, legendY, fontSize, bac
457
402
  const entry = legend[index];
458
403
  const entryHeight = entryHeights[index];
459
404
  const centerY = currentY + entryHeight / 2;
460
- // Draw color box (gradient or color)
461
405
  ctx.beginPath();
462
406
  ctx.rect(legendX + padding, centerY - boxSize / 2, boxSize, boxSize);
463
407
  fillWithGradientOrColor(ctx, entry.gradient, entry.color || '#4A90E2', '#4A90E2', { x: legendX + padding, y: centerY - boxSize / 2, w: boxSize, h: boxSize });
464
408
  ctx.fill();
465
- // Draw box border
466
409
  ctx.strokeStyle = effectiveBorderColor;
467
410
  ctx.lineWidth = 1;
468
411
  ctx.strokeRect(legendX + padding, centerY - boxSize / 2, boxSize, boxSize);
469
- // Draw label (with wrapping if enabled) using enhanced text
470
412
  const textX = legendX + padding + boxSize + textSpacing;
471
413
  if (wrapTextEnabled && effectiveMaxWidth) {
472
414
  const wrappedLines = wrapText(ctx, entry.label, effectiveMaxWidth);
@@ -493,7 +435,6 @@ async function drawLegend(ctx, legend, position, width, height, padding, fontSiz
493
435
  const boxSize = 15;
494
436
  const spacing = 10;
495
437
  const paddingBox = 8;
496
- // Calculate legend dimensions
497
438
  ctx.font = `${fontSize}px Arial`;
498
439
  const maxLabelWidth = Math.max(...legend.map(e => ctx.measureText(e.label).width));
499
440
  const legendWidth = boxSize + spacing + maxLabelWidth + paddingBox * 2;
@@ -520,19 +461,15 @@ async function drawLegend(ctx, legend, position, width, height, padding, fontSiz
520
461
  legendX = width - padding.right - legendWidth - legendSpacing;
521
462
  legendY = padding.top + legendSpacing;
522
463
  }
523
- // Determine text color based on background
524
464
  const isDarkBackground = backgroundColor === '#000000' || backgroundColor.toLowerCase() === 'black';
525
465
  const textColor = isDarkBackground ? '#FFFFFF' : '#000000';
526
466
  const bgColor = isDarkBackground ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.9)';
527
467
  const borderColor = isDarkBackground ? '#FFFFFF' : '#000000';
528
- // Draw legend background
529
468
  ctx.fillStyle = bgColor;
530
469
  ctx.fillRect(legendX, legendY, legendWidth, legendHeight);
531
- // Draw legend border
532
470
  ctx.strokeStyle = borderColor;
533
471
  ctx.lineWidth = 1;
534
472
  ctx.strokeRect(legendX, legendY, legendWidth, legendHeight);
535
- // Draw legend entries
536
473
  ctx.font = `${fontSize}px Arial`;
537
474
  ctx.textAlign = 'left';
538
475
  ctx.textBaseline = 'middle';
@@ -540,16 +477,13 @@ async function drawLegend(ctx, legend, position, width, height, padding, fontSiz
540
477
  const entry = legend[index];
541
478
  const y = legendY + paddingBox + index * (boxSize + spacing) + boxSize / 2;
542
479
  const x = legendX + paddingBox;
543
- // Draw color box (gradient or color)
544
480
  ctx.beginPath();
545
481
  ctx.rect(x, y - boxSize / 2, boxSize, boxSize);
546
482
  fillWithGradientOrColor(ctx, entry.gradient, entry.color || '#4A90E2', '#4A90E2', { x, y: y - boxSize / 2, w: boxSize, h: boxSize });
547
483
  ctx.fill();
548
- // Draw box border
549
484
  ctx.strokeStyle = borderColor;
550
485
  ctx.lineWidth = 1;
551
486
  ctx.strokeRect(x, y - boxSize / 2, boxSize, boxSize);
552
- // Draw label
553
487
  ctx.fillStyle = textColor;
554
488
  ctx.fillText(entry.label, x + boxSize + spacing, y);
555
489
  }
@@ -562,10 +496,9 @@ function drawGrid(ctx, originX, originY, axisEndX, axisEndY, xMin, xMax, xStep,
562
496
  ctx.save();
563
497
  ctx.strokeStyle = gridColor;
564
498
  ctx.lineWidth = gridWidth;
565
- ctx.setLineDash([2, 2]); // Dashed lines for grid
499
+ ctx.setLineDash([2, 2]);
566
500
  const chartWidth = axisEndX - originX;
567
501
  const chartHeight = originY - axisEndY;
568
- // Draw vertical grid lines (based on X-axis)
569
502
  if (xAxisCustomValues && xAxisCustomValues.length > 0) {
570
503
  const totalValues = xAxisCustomValues.length;
571
504
  const divisor = totalValues > 1 ? totalValues - 1 : 1;
@@ -587,7 +520,6 @@ function drawGrid(ctx, originX, originY, axisEndX, axisEndY, xMin, xMax, xStep,
587
520
  ctx.stroke();
588
521
  }
589
522
  }
590
- // Draw horizontal grid lines (based on Y-axis)
591
523
  if (yAxisCustomValues && yAxisCustomValues.length > 0) {
592
524
  const actualMin = Math.min(...yAxisCustomValues);
593
525
  const actualMax = Math.max(...yAxisCustomValues);
@@ -620,15 +552,11 @@ function calculateResponsiveWidth(xAxisRange, options = {}, customValues) {
620
552
  const paddingLeft = padding.left ?? 100;
621
553
  const paddingRight = padding.right ?? 80;
622
554
  if (customValues && customValues.length > 0) {
623
- // Calculate width based on number of custom values
624
- // Use about 20-25 pixels per tick mark
625
555
  const minChartAreaWidth = Math.max(400, customValues.length * 20);
626
556
  return paddingLeft + minChartAreaWidth + paddingRight;
627
557
  }
628
- // Calculate width based on X-axis range
629
- // Use a reasonable scale: about 10-15 pixels per unit on X-axis
630
558
  const xRange = xAxisRange.max - xAxisRange.min;
631
- const minChartAreaWidth = Math.max(400, xRange * 10); // At least 400px, or 10px per unit
559
+ const minChartAreaWidth = Math.max(400, xRange * 10);
632
560
  return paddingLeft + minChartAreaWidth + paddingRight;
633
561
  }
634
562
  /**
@@ -648,35 +576,27 @@ function drawAxes(width = 800, height = 600, options = {}) {
648
576
  const paddingRight = padding.right ?? 80;
649
577
  const paddingBottom = padding.bottom ?? 80;
650
578
  const paddingLeft = padding.left ?? 100;
651
- // Create canvas
652
579
  const canvas = (0, canvas_1.createCanvas)(width, height);
653
580
  const ctx = canvas.getContext('2d');
654
- // Fill white background
655
581
  ctx.fillStyle = backgroundColor;
656
582
  ctx.fillRect(0, 0, width, height);
657
- // Calculate axis positions
658
583
  const originX = paddingLeft;
659
584
  const originY = height - paddingBottom;
660
585
  const axisEndX = width - paddingRight;
661
586
  const axisEndY = paddingTop;
662
- // Set axis style
663
587
  ctx.strokeStyle = axisColor;
664
588
  ctx.fillStyle = axisColor;
665
589
  ctx.lineWidth = axisWidth;
666
590
  ctx.lineCap = 'round';
667
- // Draw Y-axis (vertical line from origin to top)
668
591
  ctx.beginPath();
669
592
  ctx.moveTo(originX, originY);
670
593
  ctx.lineTo(originX, axisEndY);
671
594
  ctx.stroke();
672
- // Draw X-axis (horizontal line from origin to right)
673
595
  ctx.beginPath();
674
596
  ctx.moveTo(originX, originY);
675
597
  ctx.lineTo(axisEndX, originY);
676
598
  ctx.stroke();
677
- // Draw arrow on Y-axis (pointing up)
678
599
  drawArrow(ctx, originX, axisEndY, -Math.PI / 2, arrowSize);
679
- // Draw arrow on X-axis (pointing right)
680
600
  drawArrow(ctx, axisEndX, originY, 0, arrowSize);
681
601
  return { buffer: canvas.toBuffer('image/png'), ctx, canvas };
682
602
  }
@@ -687,18 +607,14 @@ function drawAxes(width = 800, height = 600, options = {}) {
687
607
  * @returns Canvas buffer
688
608
  */
689
609
  async function createBarChart(data, options = {}) {
690
- // Extract and map organized config to internal variables
691
- // Dimensions
692
610
  const height = options.dimensions?.height ?? 600;
693
611
  const padding = options.dimensions?.padding || {};
694
- // Appearance
695
612
  const backgroundColor = options.appearance?.backgroundColor ?? '#FFFFFF';
696
613
  const backgroundGradient = options.appearance?.backgroundGradient;
697
614
  const backgroundImage = options.appearance?.backgroundImage;
698
615
  const axisColor = options.appearance?.axisColor ?? options.axes?.x?.color ?? options.axes?.y?.color ?? '#000000';
699
616
  const axisWidth = options.appearance?.axisWidth ?? options.axes?.x?.width ?? options.axes?.y?.width ?? 2;
700
617
  const arrowSize = options.appearance?.arrowSize ?? 10;
701
- // Labels
702
618
  const chartTitle = options.labels?.title?.text;
703
619
  const chartTitleFontSize = options.labels?.title?.fontSize ?? 24;
704
620
  const showBarLabels = options.labels?.barLabelDefaults?.show ?? true;
@@ -707,7 +623,6 @@ async function createBarChart(data, options = {}) {
707
623
  const showValues = options.labels?.valueLabelDefaults?.show ?? true;
708
624
  const valueFontSize = options.labels?.valueLabelDefaults?.fontSize ?? 12;
709
625
  const valueColor = options.labels?.valueLabelDefaults?.defaultColor ?? '#000000';
710
- // Axes
711
626
  const xAxisLabel = options.axes?.x?.label;
712
627
  const yAxisLabel = options.axes?.y?.label;
713
628
  const axisLabelColor = options.axes?.x?.labelColor ?? options.axes?.y?.labelColor ?? '#000000';
@@ -715,23 +630,18 @@ async function createBarChart(data, options = {}) {
715
630
  const xAxisValues = options.axes?.x?.values;
716
631
  const yAxisRange = options.axes?.y?.range;
717
632
  const yAxisValues = options.axes?.y?.values;
718
- const baseline = options.axes?.y?.baseline ?? 0; // Custom baseline value (default: 0)
633
+ const baseline = options.axes?.y?.baseline ?? 0;
719
634
  const tickFontSize = options.axes?.x?.tickFontSize ?? options.axes?.y?.tickFontSize ?? 12;
720
635
  const xAxisValueSpacing = options.axes?.x?.valueSpacing;
721
636
  const yAxisValueSpacing = options.axes?.y?.valueSpacing;
722
- // Chart type
723
637
  const chartType = options.type ?? 'standard';
724
- // Waterfall chart options
725
638
  const initialValue = options.waterfall?.initialValue ?? 0;
726
- // Legend
727
639
  const showLegend = options.legend?.show ?? false;
728
640
  const legend = options.legend?.entries;
729
- const legendPosition = options.legend?.position ?? 'right'; // Default: right
730
- // Grid
641
+ const legendPosition = options.legend?.position ?? 'right';
731
642
  const showGrid = options.grid?.show ?? false;
732
643
  const gridColor = options.grid?.color ?? '#E0E0E0';
733
644
  const gridWidth = options.grid?.width ?? 1;
734
- // Bars
735
645
  const minBarWidth = options.bars?.minWidth ?? 20;
736
646
  const barSpacing = options.bars?.spacing;
737
647
  const groupSpacing = options.bars?.groupSpacing ?? 10;
@@ -745,11 +655,9 @@ async function createBarChart(data, options = {}) {
745
655
  const paddingRight = padding.right ?? 80;
746
656
  const paddingBottom = padding.bottom ?? 80;
747
657
  const paddingLeft = padding.left ?? 100;
748
- // Determine X-axis range from custom values, options, or data
749
658
  let xMin, xMax;
750
659
  let xAxisCustomValues = xAxisValues;
751
660
  if (xAxisCustomValues && xAxisCustomValues.length > 0) {
752
- // Use custom X-axis values
753
661
  xMin = Math.min(...xAxisCustomValues);
754
662
  xMax = Math.max(...xAxisCustomValues);
755
663
  }
@@ -758,7 +666,6 @@ async function createBarChart(data, options = {}) {
758
666
  xMax = xAxisRange.max;
759
667
  }
760
668
  else {
761
- // Auto-calculate from data
762
669
  if (data.length === 0) {
763
670
  xMin = 0;
764
671
  xMax = 100;
@@ -768,20 +675,17 @@ async function createBarChart(data, options = {}) {
768
675
  const allXEnds = data.map(d => d.xEnd);
769
676
  xMin = Math.min(...allXStarts, ...allXEnds);
770
677
  xMax = Math.max(...allXStarts, ...allXEnds);
771
- // Add some padding
772
678
  const xPadding = (xMax - xMin) * 0.1;
773
679
  xMin = Math.max(0, xMin - xPadding);
774
680
  xMax = xMax + xPadding;
775
681
  }
776
682
  }
777
- // Calculate responsive width based on X-axis range or custom values
778
683
  let baseWidth = calculateResponsiveWidth({ min: xMin, max: xMax }, options, xAxisCustomValues);
779
- // Calculate legend dimensions and adjust canvas size based on legend position
780
684
  let legendWidth = 0;
781
685
  let legendHeight = 0;
782
686
  let extraWidth = 0;
783
687
  let extraHeight = 0;
784
- const minLegendSpacing = 10; // Minimum spacing from chart area
688
+ const minLegendSpacing = 10;
785
689
  if (showLegend && legend && legend.length > 0) {
786
690
  const legendMaxWidth = options.legend?.maxWidth;
787
691
  const legendWrapText = options.legend?.wrapText !== false;
@@ -790,16 +694,12 @@ async function createBarChart(data, options = {}) {
790
694
  legendWidth = legendDims.width;
791
695
  legendHeight = legendDims.height;
792
696
  const legendSpacing = options.legend?.spacing ?? 20;
793
- // Adjust canvas dimensions based on legend position
794
- // For left position, add extra space for Y-axis labels
795
697
  if (legendPosition === 'left') {
796
- // Estimate Y-axis label width: measure potential large values
797
698
  const tempCanvas = (0, canvas_1.createCanvas)(1, 1);
798
699
  const tempCtx = tempCanvas.getContext('2d');
799
- let estimatedYAxisLabelWidth = 60; // Default estimate
700
+ let estimatedYAxisLabelWidth = 60;
800
701
  if (tempCtx) {
801
702
  tempCtx.font = `${tickFontSize}px Arial`;
802
- // Get max value from data to estimate label width
803
703
  const allValues = [];
804
704
  data.forEach(d => {
805
705
  if (d.values && d.values.length > 0) {
@@ -817,7 +717,6 @@ async function createBarChart(data, options = {}) {
817
717
  if (allValues.length > 0) {
818
718
  const maxValue = Math.max(...allValues);
819
719
  const minValue = Math.min(...allValues);
820
- // Measure potential labels
821
720
  const testLabels = [
822
721
  maxValue.toFixed(1),
823
722
  minValue.toFixed(1),
@@ -829,7 +728,6 @@ async function createBarChart(data, options = {}) {
829
728
  estimatedYAxisLabelWidth = Math.max(estimatedYAxisLabelWidth, width);
830
729
  });
831
730
  }
832
- // Add padding: 10px (label offset) + 5px (tick) + 15px (spacing) = 30px total
833
731
  estimatedYAxisLabelWidth += 30;
834
732
  }
835
733
  extraWidth = legendWidth + legendSpacing + estimatedYAxisLabelWidth + minLegendSpacing;
@@ -843,19 +741,15 @@ async function createBarChart(data, options = {}) {
843
741
  }
844
742
  const width = baseWidth + extraWidth;
845
743
  const adjustedHeight = height + extraHeight;
846
- // Create canvas
847
744
  const canvas = (0, canvas_1.createCanvas)(width, adjustedHeight);
848
745
  const ctx = canvas.getContext('2d');
849
- // Fill background (gradient, image, or color)
850
746
  if (backgroundImage) {
851
747
  try {
852
748
  const bgImage = await (0, canvas_1.loadImage)(backgroundImage);
853
- // Draw image to fill entire canvas
854
749
  ctx.drawImage(bgImage, 0, 0, width, adjustedHeight);
855
750
  }
856
751
  catch (error) {
857
752
  console.warn(`Failed to load background image: ${backgroundImage}`, error);
858
- // Fallback to gradient or color if image fails to load
859
753
  fillWithGradientOrColor(ctx, backgroundGradient, backgroundColor, backgroundColor, {
860
754
  x: 0, y: 0, w: width, h: adjustedHeight
861
755
  });
@@ -868,10 +762,8 @@ async function createBarChart(data, options = {}) {
868
762
  });
869
763
  ctx.fillRect(0, 0, width, adjustedHeight);
870
764
  }
871
- // Calculate axis positions
872
765
  const titleHeight = chartTitle ? chartTitleFontSize + 30 : 0;
873
766
  const axisLabelHeight = (xAxisLabel || yAxisLabel) ? axisLabelFontSize + 20 : 0;
874
- // Adjust chart area based on legend position
875
767
  let chartAreaLeft = paddingLeft;
876
768
  let chartAreaRight = baseWidth - paddingRight;
877
769
  let chartAreaTop = paddingTop + titleHeight;
@@ -879,13 +771,11 @@ async function createBarChart(data, options = {}) {
879
771
  if (showLegend && legend && legend.length > 0) {
880
772
  const legendSpacing = options.legend?.spacing ?? 20;
881
773
  if (legendPosition === 'left') {
882
- // Calculate actual Y-axis label width after we have value ranges
883
- let actualYAxisLabelWidth = 60; // Default estimate
774
+ let actualYAxisLabelWidth = 60;
884
775
  const tempCanvas = (0, canvas_1.createCanvas)(1, 1);
885
776
  const tempCtx = tempCanvas.getContext('2d');
886
777
  if (tempCtx) {
887
778
  tempCtx.font = `${tickFontSize}px Arial`;
888
- // Use the calculated min/max values if available, otherwise estimate
889
779
  const allValues = [];
890
780
  data.forEach(d => {
891
781
  if (d.values && d.values.length > 0) {
@@ -914,10 +804,8 @@ async function createBarChart(data, options = {}) {
914
804
  actualYAxisLabelWidth = Math.max(actualYAxisLabelWidth, width);
915
805
  });
916
806
  }
917
- // Add padding: 10px (label offset) + 5px (tick) + 15px (spacing)
918
807
  actualYAxisLabelWidth += 30;
919
808
  }
920
- // Position chart area to leave room for legend + Y-axis labels
921
809
  chartAreaLeft = paddingLeft + legendWidth + legendSpacing + actualYAxisLabelWidth;
922
810
  chartAreaRight = baseWidth - paddingRight;
923
811
  }
@@ -938,37 +826,27 @@ async function createBarChart(data, options = {}) {
938
826
  const originY = chartAreaBottom - axisLabelHeight;
939
827
  const axisEndX = chartAreaRight;
940
828
  const axisEndY = chartAreaTop;
941
- // Draw chart title if provided
942
829
  if (chartTitle) {
943
830
  ctx.save();
944
831
  ctx.textAlign = 'center';
945
832
  ctx.textBaseline = 'top';
946
- // Title positioned with proper spacing from top
947
833
  const titleY = paddingTop + 10;
948
834
  const titleX = width / 2;
949
835
  await renderEnhancedText(ctx, chartTitle, titleX, titleY, options.labels?.title?.textStyle, chartTitleFontSize, options.labels?.title?.color, options.labels?.title?.gradient);
950
836
  ctx.restore();
951
837
  }
952
- // Set axis style
953
838
  ctx.strokeStyle = axisColor;
954
839
  ctx.fillStyle = axisColor;
955
840
  ctx.lineWidth = axisWidth;
956
841
  ctx.lineCap = 'round';
957
- // Draw Y-axis
958
842
  ctx.beginPath();
959
843
  ctx.moveTo(originX, originY);
960
844
  ctx.lineTo(originX, axisEndY);
961
845
  ctx.stroke();
962
- // Draw arrows (X-axis will be drawn after calculating zero line)
963
846
  drawArrow(ctx, originX, axisEndY, -Math.PI / 2, arrowSize);
964
- // Calculate Y-axis value ranges
965
- // For grouped charts: find max value across all segments
966
- // For stacked charts: find max sum of values per category
967
- // For waterfall charts: find cumulative min/max across all bars
968
847
  let allValues = [];
969
848
  if (chartType === 'grouped' || chartType === 'stacked' || chartType === 'waterfall') {
970
849
  if (chartType === 'grouped') {
971
- // For grouped: find max value across all segments
972
850
  data.forEach(d => {
973
851
  if (d.values && d.values.length > 0) {
974
852
  d.values.forEach(seg => allValues.push(seg.value));
@@ -979,24 +857,20 @@ async function createBarChart(data, options = {}) {
979
857
  });
980
858
  }
981
859
  else if (chartType === 'waterfall') {
982
- // For waterfall: calculate all cumulative values (initial + each step's cumulative total)
983
860
  let cumulativeValue = initialValue;
984
- allValues.push(initialValue); // Include initial value
861
+ allValues.push(initialValue);
985
862
  data.forEach(d => {
986
863
  if (d.values && d.values.length > 0) {
987
- // Sum all segments for this item
988
864
  const itemTotal = d.values.reduce((sum, seg) => sum + seg.value, 0);
989
865
  cumulativeValue += itemTotal;
990
866
  }
991
867
  else if (d.value !== undefined) {
992
868
  cumulativeValue += d.value;
993
869
  }
994
- // Add each cumulative total to allValues
995
870
  allValues.push(cumulativeValue);
996
871
  });
997
872
  }
998
873
  else {
999
- // For stacked: find max sum per category
1000
874
  data.forEach(d => {
1001
875
  if (d.values && d.values.length > 0) {
1002
876
  const sum = d.values.reduce((acc, seg) => acc + seg.value, 0);
@@ -1009,7 +883,6 @@ async function createBarChart(data, options = {}) {
1009
883
  }
1010
884
  }
1011
885
  else {
1012
- // Standard chart: use value directly
1013
886
  allValues = data.map(d => d.value ?? 0).filter(v => v !== undefined && v !== null);
1014
887
  }
1015
888
  let minValue, maxValue, yStep;
@@ -1017,28 +890,21 @@ async function createBarChart(data, options = {}) {
1017
890
  const hasExplicitYRange = yAxisRange && yAxisRange.min !== undefined && yAxisRange.max !== undefined;
1018
891
  const hasExplicitXRange = xAxisRange && xAxisRange.min !== undefined && xAxisRange.max !== undefined;
1019
892
  if (yAxisCustomValues && yAxisCustomValues.length > 0) {
1020
- // Use custom Y-axis values
1021
893
  minValue = Math.min(...yAxisCustomValues);
1022
894
  maxValue = Math.max(...yAxisCustomValues);
1023
- yStep = 1; // Not used when custom values are provided
895
+ yStep = 1;
1024
896
  }
1025
897
  else if (hasExplicitYRange) {
1026
- // Use Y-axis range, but for waterfall charts, ensure it includes all cumulative values
1027
- // TypeScript narrowing: hasExplicitYRange ensures min and max are defined
1028
898
  minValue = yAxisRange.min;
1029
899
  maxValue = yAxisRange.max;
1030
- // Ensure baseline is within range
1031
900
  const effectiveBaseline = baseline !== undefined ? baseline : 0;
1032
901
  minValue = Math.min(minValue, effectiveBaseline);
1033
902
  maxValue = Math.max(maxValue, effectiveBaseline);
1034
- // For waterfall charts, expand range if needed to include all cumulative values
1035
903
  if (chartType === 'waterfall' && allValues.length > 0) {
1036
904
  const dataMin = Math.min(...allValues);
1037
905
  const dataMax = Math.max(...allValues);
1038
- // Ensure the range includes all data values
1039
906
  minValue = Math.min(minValue, dataMin);
1040
907
  maxValue = Math.max(maxValue, dataMax);
1041
- // Add padding, but ensure baseline is always included
1042
908
  const range = maxValue - minValue;
1043
909
  const padding = range * 0.1;
1044
910
  minValue = Math.min(minValue - padding, effectiveBaseline);
@@ -1047,20 +913,16 @@ async function createBarChart(data, options = {}) {
1047
913
  yStep = yAxisRange.step ?? Math.ceil((maxValue - minValue) / 10);
1048
914
  }
1049
915
  else {
1050
- // Auto-calculate from data
1051
916
  if (allValues.length > 0) {
1052
917
  minValue = Math.min(...allValues);
1053
918
  maxValue = Math.max(...allValues);
1054
- // Ensure baseline is within range for waterfall charts
1055
919
  if (chartType === 'waterfall') {
1056
920
  minValue = Math.min(minValue, initialValue);
1057
921
  maxValue = Math.max(maxValue, initialValue);
1058
922
  }
1059
- // Add some padding, but ensure baseline is always included in the range
1060
923
  const range = maxValue - minValue;
1061
924
  const padding = range * 0.1;
1062
925
  const effectiveBaseline = baseline !== undefined ? baseline : 0;
1063
- // Ensure baseline is within the range
1064
926
  minValue = Math.min(minValue - padding, effectiveBaseline);
1065
927
  maxValue = maxValue + padding;
1066
928
  }
@@ -1070,7 +932,6 @@ async function createBarChart(data, options = {}) {
1070
932
  }
1071
933
  yStep = Math.ceil((maxValue - minValue) / 10);
1072
934
  }
1073
- // Validate data values against explicit axis ranges
1074
935
  if (hasExplicitXRange || xAxisCustomValues) {
1075
936
  const effectiveXMin = xAxisCustomValues ? Math.min(...xAxisCustomValues) : xAxisRange.min;
1076
937
  const effectiveXMax = xAxisCustomValues ? Math.max(...xAxisCustomValues) : xAxisRange.max;
@@ -1110,7 +971,6 @@ async function createBarChart(data, options = {}) {
1110
971
  }
1111
972
  }
1112
973
  else if (chartType === 'waterfall') {
1113
- // For waterfall, check individual segment values and cumulative totals
1114
974
  if (item.values && item.values.length > 0) {
1115
975
  item.values.forEach((seg, segIndex) => {
1116
976
  if (seg.value < effectiveYMin || seg.value > effectiveYMax) {
@@ -1129,7 +989,6 @@ async function createBarChart(data, options = {}) {
1129
989
  }
1130
990
  }
1131
991
  else {
1132
- // Standard chart
1133
992
  if (item.value !== undefined && (item.value < effectiveYMin || item.value > effectiveYMax)) {
1134
993
  throw new Error(`Bar Chart Error: Data value out of Y-axis bounds.\n` +
1135
994
  `Bar ${itemIndex} "${item.label || `at index ${itemIndex}`}" has value ${item.value}, ` +
@@ -1138,37 +997,26 @@ async function createBarChart(data, options = {}) {
1138
997
  }
1139
998
  });
1140
999
  }
1141
- // Draw Y-axis ticks and labels (with custom values if provided)
1142
1000
  drawYAxisTicks(ctx, originX, originY, axisEndY, minValue, maxValue, yStep, tickFontSize, yAxisCustomValues, yAxisValueSpacing);
1143
- // Calculate chart area dimensions (needed for baseline calculation)
1144
1001
  const chartAreaHeight = originY - axisEndY;
1145
- // Calculate baseline position (custom baseline value, default is 0)
1146
- // Position the baseline within the chart area based on minValue, maxValue, and baseline
1147
1002
  const baselineY = originY - ((baseline - minValue) / (maxValue - minValue)) * chartAreaHeight;
1148
- // Draw X-axis at baseline position
1149
1003
  const xAxisY = baselineY;
1150
1004
  ctx.beginPath();
1151
1005
  ctx.moveTo(originX, xAxisY);
1152
1006
  ctx.lineTo(axisEndX, xAxisY);
1153
1007
  ctx.stroke();
1154
- // Draw X-axis arrow
1155
1008
  drawArrow(ctx, axisEndX, xAxisY, 0, arrowSize);
1156
- // Calculate X-axis step
1157
1009
  const xStep = xAxisRange?.step ?? Math.ceil((xMax - xMin) / 10);
1158
- // Draw X-axis ticks and labels at baseline position
1159
1010
  drawXAxisTicks(ctx, originX, xAxisY, axisEndX, xMin, xMax, xStep, tickFontSize, xAxisCustomValues, xAxisValueSpacing);
1160
- // Draw grid lines if enabled (before calculating zero line, but will use correct Y position)
1161
1011
  if (showGrid) {
1162
1012
  drawGrid(ctx, originX, originY, axisEndX, axisEndY, xMin, xMax, xStep, minValue, maxValue, yStep, xAxisCustomValues, yAxisCustomValues, gridColor, gridWidth);
1163
1013
  }
1164
- // Draw X-axis label if provided
1165
1014
  if (xAxisLabel) {
1166
1015
  ctx.save();
1167
1016
  ctx.fillStyle = axisLabelColor;
1168
1017
  ctx.font = `${axisLabelFontSize}px Arial`;
1169
1018
  ctx.textAlign = 'center';
1170
1019
  ctx.textBaseline = 'top';
1171
- // Position label below X-axis ticks (ticks are at xAxisY + 10, so add more spacing)
1172
1020
  ctx.fillText(xAxisLabel, (originX + axisEndX) / 2, xAxisY + 25);
1173
1021
  ctx.restore();
1174
1022
  }
@@ -1178,7 +1026,6 @@ async function createBarChart(data, options = {}) {
1178
1026
  ctx.font = `${axisLabelFontSize}px Arial`;
1179
1027
  ctx.textAlign = 'center';
1180
1028
  ctx.textBaseline = 'bottom';
1181
- // Rotate for vertical text
1182
1029
  const labelX = originX - 30;
1183
1030
  const labelY = (originY + axisEndY) / 2;
1184
1031
  ctx.translate(labelX, labelY);
@@ -1186,7 +1033,6 @@ async function createBarChart(data, options = {}) {
1186
1033
  ctx.fillText(yAxisLabel, 0, 0);
1187
1034
  ctx.restore();
1188
1035
  }
1189
- // Draw legend if provided - positioned based on legendPosition option
1190
1036
  if (showLegend && legend && legend.length > 0) {
1191
1037
  const legendSpacing = options.legend?.spacing ?? 20;
1192
1038
  const legendFontSize = options.legend?.fontSize ?? 16;
@@ -1196,111 +1042,85 @@ async function createBarChart(data, options = {}) {
1196
1042
  const legendPadding = options.legend?.padding;
1197
1043
  const legendMaxWidth = options.legend?.maxWidth;
1198
1044
  const legendWrapText = options.legend?.wrapText !== false;
1199
- // Calculate legend position based on legendPosition option
1200
1045
  let legendX, legendY;
1201
1046
  const chartAreaHeight = originY - axisEndY;
1202
1047
  const chartAreaWidth = axisEndX - originX;
1203
1048
  switch (legendPosition) {
1204
1049
  case 'top':
1205
- legendX = (width - legendWidth) / 2; // Centered horizontally
1050
+ legendX = (width - legendWidth) / 2;
1206
1051
  legendY = paddingTop + titleHeight + minLegendSpacing;
1207
1052
  break;
1208
1053
  case 'bottom':
1209
- legendX = (width - legendWidth) / 2; // Centered horizontally
1054
+ legendX = (width - legendWidth) / 2;
1210
1055
  legendY = adjustedHeight - paddingBottom - legendHeight - minLegendSpacing;
1211
1056
  break;
1212
1057
  case 'left':
1213
- // Position legend further left to make room for Y-axis labels
1214
- // Position legend on the left side
1215
1058
  legendX = paddingLeft + minLegendSpacing;
1216
- legendY = axisEndY + (chartAreaHeight - legendHeight) / 2; // Vertically centered in chart area
1059
+ legendY = axisEndY + (chartAreaHeight - legendHeight) / 2;
1217
1060
  break;
1218
1061
  case 'right':
1219
1062
  default:
1220
1063
  legendX = axisEndX + minLegendSpacing;
1221
- legendY = axisEndY + (chartAreaHeight - legendHeight) / 2; // Vertically centered in chart area
1064
+ legendY = axisEndY + (chartAreaHeight - legendHeight) / 2;
1222
1065
  break;
1223
1066
  }
1224
1067
  await drawLegendAtPosition(ctx, legend, legendX, legendY, legendFontSize, legendBgColor || backgroundColor, legendTextColor, legendBorderColor, legendPadding, legendMaxWidth, legendWrapText, options.legend?.backgroundGradient, options.legend?.textGradient, options.legend?.textStyle);
1225
1068
  }
1226
- // Calculate chart area dimensions
1227
1069
  const chartAreaWidth = axisEndX - originX;
1228
1070
  const labelsToDraw = [];
1229
- // Track value label positions per bar (for adjusting bar label positions)
1230
1071
  const valueLabelPositions = new Map();
1231
- // First pass: Draw all bars (no labels)
1232
1072
  data.forEach((item, itemIndex) => {
1233
- // Calculate bar position and width based on X-axis range
1234
- // If custom X-axis values are provided, map to those positions
1235
1073
  let barXStart, barXEnd;
1236
1074
  if (xAxisCustomValues && xAxisCustomValues.length > 0) {
1237
- // Map to custom X-axis values
1238
1075
  const actualMin = Math.min(...xAxisCustomValues);
1239
1076
  const actualMax = Math.max(...xAxisCustomValues);
1240
1077
  const xRange = actualMax - actualMin;
1241
- // Find the position of xStart and xEnd in the custom values array
1242
- // If xStart equals xEnd, it's a single-position bar
1243
1078
  const startIndex = xAxisCustomValues.indexOf(item.xStart);
1244
1079
  const endIndex = xAxisCustomValues.indexOf(item.xEnd);
1245
1080
  if (startIndex !== -1 && endIndex !== -1) {
1246
- // Both values found in custom array - use index-based positioning
1247
1081
  const totalValues = xAxisCustomValues.length;
1248
1082
  const divisor = totalValues > 1 ? totalValues - 1 : 1;
1249
1083
  barXStart = originX + (startIndex / divisor) * chartAreaWidth;
1250
1084
  barXEnd = originX + (endIndex / divisor) * chartAreaWidth;
1251
1085
  }
1252
1086
  else {
1253
- // Fallback to range-based positioning
1254
1087
  barXStart = originX + ((item.xStart - actualMin) / xRange) * chartAreaWidth;
1255
1088
  barXEnd = originX + ((item.xEnd - actualMin) / xRange) * chartAreaWidth;
1256
1089
  }
1257
1090
  }
1258
1091
  else {
1259
- // Use regular range mapping
1260
1092
  const xRange = xMax - xMin;
1261
1093
  barXStart = originX + ((item.xStart - xMin) / xRange) * chartAreaWidth;
1262
1094
  barXEnd = originX + ((item.xEnd - xMin) / xRange) * chartAreaWidth;
1263
1095
  }
1264
- // If xStart equals xEnd, use a minimum bar width
1265
1096
  const groupWidth = Math.max(barXEnd - barXStart, minBarWidth);
1266
1097
  if (item.xStart === item.xEnd) {
1267
- // Center the bar at the position
1268
1098
  const centerX = barXStart;
1269
1099
  barXStart = centerX - groupWidth / 2;
1270
1100
  }
1271
- // Handle grouped/stacked/waterfall vs standard charts
1272
1101
  if ((chartType === 'grouped' || chartType === 'stacked' || chartType === 'waterfall') && item.values && item.values.length > 0) {
1273
- // Grouped, stacked, or waterfall chart
1274
1102
  const segments = item.values;
1275
1103
  const numSegments = segments.length;
1276
1104
  if (chartType === 'grouped') {
1277
- // Grouped: bars side-by-side
1278
1105
  const segmentWidth = (groupWidth - (groupSpacing * (numSegments - 1))) / numSegments;
1279
- // Track the highest value label Y position for this grouped bar
1280
1106
  let highestValueLabelY = null;
1281
1107
  segments.forEach((segment, segIndex) => {
1282
1108
  const segXStart = barXStart + (segIndex * (segmentWidth + groupSpacing));
1283
- // Calculate bar position relative to baseline
1284
1109
  let barY, barHeight;
1285
1110
  if (segment.value >= baseline) {
1286
- // Bar extends above baseline
1287
1111
  const positiveRatio = (segment.value - baseline) / (maxValue - minValue);
1288
1112
  barHeight = positiveRatio * chartAreaHeight;
1289
1113
  barY = baselineY - barHeight;
1290
1114
  }
1291
1115
  else {
1292
- // Bar extends below baseline
1293
1116
  const negativeRatio = (baseline - segment.value) / (maxValue - minValue);
1294
1117
  barHeight = negativeRatio * chartAreaHeight;
1295
1118
  barY = baselineY;
1296
1119
  }
1297
- // Draw segment bar with gradient, opacity, shadow, and stroke
1298
1120
  drawBar(ctx, segXStart, barY, segmentWidth, barHeight, segment.color || item.color || '#4A90E2', segment.gradient || item.gradient, segment.opacity ?? item.opacity ?? globalBarOpacity, segment.shadow || item.shadow, segment.stroke || item.stroke, globalBarShadow, globalBarStroke);
1299
- // Store value label for later drawing
1300
1121
  const shouldShowValue = segment.showValue !== undefined ? segment.showValue : showValues;
1301
1122
  if (shouldShowValue) {
1302
1123
  const valueLabelY = barY - 5;
1303
- // Track the highest (smallest Y value = highest on screen) value label
1304
1124
  if (segment.value >= baseline && (highestValueLabelY === null || valueLabelY < highestValueLabelY)) {
1305
1125
  highestValueLabelY = valueLabelY;
1306
1126
  }
@@ -1316,20 +1136,16 @@ async function createBarChart(data, options = {}) {
1316
1136
  });
1317
1137
  }
1318
1138
  });
1319
- // Store the highest value label position for this grouped bar (for adjusting bar label position)
1320
1139
  if (highestValueLabelY !== null) {
1321
1140
  valueLabelPositions.set(data.indexOf(item), { y: highestValueLabelY, fontSize: valueFontSize, baseline: 'bottom' });
1322
1141
  }
1323
1142
  }
1324
1143
  else if (chartType === 'waterfall') {
1325
- // Waterfall: each bar starts from cumulative total of previous bars
1326
- // Calculate cumulative value up to this point
1327
1144
  let cumulativeValue = initialValue;
1328
1145
  const currentIndex = data.indexOf(item);
1329
1146
  for (let i = 0; i < currentIndex; i++) {
1330
1147
  const prevItem = data[i];
1331
1148
  if (prevItem.values && prevItem.values.length > 0) {
1332
- // Sum all segments for previous item
1333
1149
  const prevTotal = prevItem.values.reduce((sum, seg) => sum + seg.value, 0);
1334
1150
  cumulativeValue += prevTotal;
1335
1151
  }
@@ -1337,9 +1153,7 @@ async function createBarChart(data, options = {}) {
1337
1153
  cumulativeValue += prevItem.value;
1338
1154
  }
1339
1155
  }
1340
- // Calculate baseline Y position for this cumulative value
1341
1156
  const cumulativeBaselineY = originY - ((cumulativeValue - minValue) / (maxValue - minValue)) * chartAreaHeight;
1342
- // Separate positive and negative segments
1343
1157
  const positiveSegments = [];
1344
1158
  const negativeSegments = [];
1345
1159
  segments.forEach(seg => {
@@ -1350,17 +1164,14 @@ async function createBarChart(data, options = {}) {
1350
1164
  negativeSegments.push(seg);
1351
1165
  }
1352
1166
  });
1353
- // Draw positive segments (stacked upward from cumulative baseline)
1354
1167
  let accumulatedPositiveHeight = 0;
1355
1168
  positiveSegments.forEach((segment) => {
1356
1169
  const positiveRatio = segment.value / (maxValue - minValue);
1357
1170
  const segmentHeight = positiveRatio * chartAreaHeight;
1358
1171
  const barY = cumulativeBaselineY - accumulatedPositiveHeight - segmentHeight;
1359
- // Ensure bar stays within chart area bounds
1360
1172
  const clampedBarY = Math.max(axisEndY, barY);
1361
1173
  const clampedBarHeight = Math.min(segmentHeight, cumulativeBaselineY - accumulatedPositiveHeight - clampedBarY);
1362
1174
  if (clampedBarHeight > 0) {
1363
- // Ensure bar doesn't exceed X-axis bounds
1364
1175
  const clampedBarXStart = Math.max(originX, Math.min(barXStart, axisEndX));
1365
1176
  const clampedGroupWidth = Math.min(groupWidth, axisEndX - clampedBarXStart);
1366
1177
  if (clampedGroupWidth > 0) {
@@ -1382,17 +1193,14 @@ async function createBarChart(data, options = {}) {
1382
1193
  }
1383
1194
  accumulatedPositiveHeight += segmentHeight;
1384
1195
  });
1385
- // Draw negative segments (stacked downward from cumulative baseline)
1386
1196
  let accumulatedNegativeHeight = 0;
1387
1197
  negativeSegments.forEach((segment) => {
1388
1198
  const negativeRatio = Math.abs(segment.value) / (maxValue - minValue);
1389
1199
  const segmentHeight = negativeRatio * chartAreaHeight;
1390
1200
  const barY = cumulativeBaselineY + accumulatedNegativeHeight;
1391
- // Ensure bar stays within chart area bounds
1392
1201
  const clampedBarY = Math.max(barY, axisEndY);
1393
1202
  const clampedBarHeight = Math.min(segmentHeight, originY - clampedBarY);
1394
1203
  if (clampedBarHeight > 0) {
1395
- // Ensure bar doesn't exceed X-axis bounds
1396
1204
  const clampedBarXStart = Math.max(originX, Math.min(barXStart, axisEndX));
1397
1205
  const clampedGroupWidth = Math.min(groupWidth, axisEndX - clampedBarXStart);
1398
1206
  if (clampedGroupWidth > 0) {
@@ -1416,8 +1224,6 @@ async function createBarChart(data, options = {}) {
1416
1224
  });
1417
1225
  }
1418
1226
  else {
1419
- // Stacked: bars on top of each other
1420
- // For stacked with negatives, we need to separate positive and negative segments
1421
1227
  const positiveSegments = [];
1422
1228
  const negativeSegments = [];
1423
1229
  segments.forEach(seg => {
@@ -1428,7 +1234,6 @@ async function createBarChart(data, options = {}) {
1428
1234
  negativeSegments.push(seg);
1429
1235
  }
1430
1236
  });
1431
- // Draw positive segments (stacked upward from baseline)
1432
1237
  let accumulatedPositiveHeight = 0;
1433
1238
  positiveSegments.forEach((segment) => {
1434
1239
  const positiveRatio = (segment.value - baseline) / (maxValue - minValue);
@@ -1450,7 +1255,6 @@ async function createBarChart(data, options = {}) {
1450
1255
  }
1451
1256
  accumulatedPositiveHeight += segmentHeight;
1452
1257
  });
1453
- // Draw negative segments (stacked downward from baseline)
1454
1258
  let accumulatedNegativeHeight = 0;
1455
1259
  negativeSegments.forEach((segment) => {
1456
1260
  const negativeRatio = (baseline - segment.value) / (maxValue - minValue);
@@ -1472,13 +1276,11 @@ async function createBarChart(data, options = {}) {
1472
1276
  }
1473
1277
  accumulatedNegativeHeight += segmentHeight;
1474
1278
  });
1475
- // Store total value label for later drawing
1476
1279
  const totalValue = segments.reduce((sum, seg) => sum + seg.value, 0);
1477
1280
  const shouldShowValue = item.showValue !== undefined ? item.showValue : showValues;
1478
1281
  if (shouldShowValue) {
1479
1282
  const totalValueY = totalValue >= baseline ? baselineY - accumulatedPositiveHeight - 5 : baselineY + accumulatedNegativeHeight + 5;
1480
1283
  const totalValueBaseline = totalValue >= baseline ? 'bottom' : 'top';
1481
- // Store value label position for this bar (for adjusting bar label position)
1482
1284
  if (totalValue >= baseline) {
1483
1285
  valueLabelPositions.set(data.indexOf(item), { y: totalValueY, fontSize: valueFontSize, baseline: totalValueBaseline });
1484
1286
  }
@@ -1496,22 +1298,17 @@ async function createBarChart(data, options = {}) {
1496
1298
  }
1497
1299
  }
1498
1300
  else if (chartType === 'lollipop') {
1499
- // Lollipop chart: line with dot at end
1500
1301
  const barCenterX = barXStart + groupWidth / 2;
1501
1302
  const value = item.value ?? baseline;
1502
- // Calculate value Y position
1503
1303
  let valueY;
1504
1304
  if (value >= baseline) {
1505
- // Value above baseline
1506
1305
  const positiveRatio = (value - baseline) / (maxValue - minValue);
1507
1306
  valueY = baselineY - positiveRatio * chartAreaHeight;
1508
1307
  }
1509
1308
  else {
1510
- // Value below baseline
1511
1309
  const negativeRatio = (baseline - value) / (maxValue - minValue);
1512
1310
  valueY = baselineY + negativeRatio * chartAreaHeight;
1513
1311
  }
1514
- // Draw line from baseline to value position
1515
1312
  ctx.save();
1516
1313
  ctx.strokeStyle = item.color || '#4A90E2';
1517
1314
  ctx.lineWidth = lollipopLineWidth;
@@ -1519,13 +1316,11 @@ async function createBarChart(data, options = {}) {
1519
1316
  ctx.moveTo(barCenterX, baselineY);
1520
1317
  ctx.lineTo(barCenterX, valueY);
1521
1318
  ctx.stroke();
1522
- // Draw dot/circle at value position with opacity, shadow, and stroke
1523
1319
  ctx.save();
1524
1320
  const dotOpacity = item.opacity ?? globalBarOpacity;
1525
1321
  if (dotOpacity !== undefined) {
1526
1322
  ctx.globalAlpha = dotOpacity;
1527
1323
  }
1528
- // Apply shadow
1529
1324
  const dotShadow = item.shadow || globalBarShadow;
1530
1325
  if (dotShadow) {
1531
1326
  ctx.shadowColor = dotShadow.color || 'rgba(0,0,0,0.3)';
@@ -1537,14 +1332,12 @@ async function createBarChart(data, options = {}) {
1537
1332
  ctx.arc(barCenterX, valueY, lollipopDotSize / 2, 0, Math.PI * 2);
1538
1333
  fillWithGradientOrColor(ctx, item.gradient, item.color || '#4A90E2', '#4A90E2', { x: barCenterX - lollipopDotSize / 2, y: valueY - lollipopDotSize / 2, w: lollipopDotSize, h: lollipopDotSize });
1539
1334
  ctx.fill();
1540
- // Reset shadow before stroke
1541
1335
  if (dotShadow) {
1542
1336
  ctx.shadowColor = 'transparent';
1543
1337
  ctx.shadowOffsetX = 0;
1544
1338
  ctx.shadowOffsetY = 0;
1545
1339
  ctx.shadowBlur = 0;
1546
1340
  }
1547
- // Draw dot border/stroke
1548
1341
  const dotStroke = item.stroke || globalBarStroke;
1549
1342
  if (dotStroke && dotStroke.width && dotStroke.width > 0) {
1550
1343
  ctx.beginPath();
@@ -1564,13 +1357,11 @@ async function createBarChart(data, options = {}) {
1564
1357
  ctx.stroke();
1565
1358
  }
1566
1359
  else {
1567
- // Default border for better visibility if no stroke specified
1568
1360
  ctx.strokeStyle = item.color || '#4A90E2';
1569
1361
  ctx.lineWidth = 1;
1570
1362
  ctx.stroke();
1571
1363
  }
1572
1364
  ctx.restore();
1573
- // Store value label for later drawing
1574
1365
  const shouldShowValue = item.showValue !== undefined ? item.showValue : showValues;
1575
1366
  if (shouldShowValue) {
1576
1367
  labelsToDraw.push({
@@ -1586,32 +1377,25 @@ async function createBarChart(data, options = {}) {
1586
1377
  }
1587
1378
  }
1588
1379
  else {
1589
- // Standard chart: single bar
1590
1380
  const barWidth = groupWidth;
1591
1381
  const value = item.value ?? baseline;
1592
- // Calculate bar height and position based on value relative to baseline
1593
1382
  let barHeight;
1594
1383
  let barY;
1595
1384
  if (value >= baseline) {
1596
- // Value above baseline: bar goes up from baseline
1597
1385
  const positiveRatio = (value - baseline) / (maxValue - minValue);
1598
1386
  barHeight = positiveRatio * chartAreaHeight;
1599
1387
  barY = baselineY - barHeight;
1600
1388
  }
1601
1389
  else {
1602
- // Value below baseline: bar goes down from baseline
1603
1390
  const negativeRatio = (baseline - value) / (maxValue - minValue);
1604
1391
  barHeight = negativeRatio * chartAreaHeight;
1605
1392
  barY = baselineY;
1606
1393
  }
1607
- // Draw bar with gradient, opacity, shadow, and stroke
1608
1394
  drawBar(ctx, barXStart, barY, barWidth, barHeight, item.color || '#4A90E2', item.gradient, item.opacity ?? globalBarOpacity, item.shadow, item.stroke, globalBarShadow, globalBarStroke);
1609
- // Store value label for later drawing
1610
1395
  const shouldShowValue = item.showValue !== undefined ? item.showValue : showValues;
1611
1396
  if (shouldShowValue) {
1612
1397
  const valueLabelY = value >= baseline ? barY - 5 : barY + barHeight + 5;
1613
1398
  const valueLabelBaseline = value >= baseline ? 'bottom' : 'top';
1614
- // Store value label position for this bar (for adjusting bar label position)
1615
1399
  if (value >= baseline) {
1616
1400
  valueLabelPositions.set(data.indexOf(item), { y: valueLabelY, fontSize: valueFontSize, baseline: valueLabelBaseline });
1617
1401
  }
@@ -1627,7 +1411,6 @@ async function createBarChart(data, options = {}) {
1627
1411
  });
1628
1412
  }
1629
1413
  }
1630
- // Store bar label information for later drawing
1631
1414
  if (showBarLabels) {
1632
1415
  ctx.save();
1633
1416
  ctx.fillStyle = item.labelColor || '#000000';
@@ -1635,33 +1418,26 @@ async function createBarChart(data, options = {}) {
1635
1418
  let labelX, labelY;
1636
1419
  let textAlign = 'center';
1637
1420
  let textBaseline = 'middle';
1638
- // Calculate bar center - use groupWidth for all chart types
1639
1421
  const barCenterX = barXStart + groupWidth / 2;
1640
- // For grouped/stacked, calculate appropriate center Y
1641
1422
  let barCenterY;
1642
1423
  if ((chartType === 'grouped' || chartType === 'stacked') && item.values && item.values.length > 0) {
1643
1424
  if (chartType === 'stacked') {
1644
- // For stacked, use the total height
1645
1425
  const totalValue = item.values.reduce((sum, seg) => sum + seg.value, 0);
1646
1426
  const totalHeight = ((totalValue - minValue) / (maxValue - minValue)) * chartAreaHeight;
1647
1427
  barCenterY = originY - totalHeight / 2;
1648
1428
  }
1649
1429
  else {
1650
- // For grouped, use the max value height
1651
1430
  const maxSegValue = Math.max(...item.values.map(seg => seg.value));
1652
1431
  const maxHeight = ((maxSegValue - minValue) / (maxValue - minValue)) * chartAreaHeight;
1653
1432
  barCenterY = originY - maxHeight / 2;
1654
1433
  }
1655
1434
  }
1656
1435
  else {
1657
- // Standard chart
1658
1436
  const value = item.value ?? 0;
1659
1437
  const barHeight = ((value - minValue) / (maxValue - minValue)) * chartAreaHeight;
1660
1438
  barCenterY = originY - barHeight / 2;
1661
1439
  }
1662
- // Use individual bar label position if provided, otherwise use global setting
1663
1440
  const currentLabelPosition = item.labelPosition ?? barLabelPosition;
1664
- // Calculate top Y position for label
1665
1441
  let topBarY;
1666
1442
  if ((chartType === 'grouped' || chartType === 'stacked') && item.values && item.values.length > 0) {
1667
1443
  if (chartType === 'stacked') {
@@ -1683,13 +1459,9 @@ async function createBarChart(data, options = {}) {
1683
1459
  switch (currentLabelPosition) {
1684
1460
  case 'top':
1685
1461
  labelX = barCenterX;
1686
- // Check if there's a value label at the top - if so, position bar label below it
1687
1462
  const valueLabelInfo = valueLabelPositions.get(data.indexOf(item));
1688
1463
  if (valueLabelInfo && valueLabelInfo.baseline === 'bottom') {
1689
- // Value label is at top, so position bar label below it
1690
- // Value label uses 'bottom' baseline, so its top is at valueLabelInfo.y
1691
- // Bar label uses 'bottom' baseline, so position it below the value label
1692
- const spacing = 5; // Gap between value and bar label
1464
+ const spacing = 5;
1693
1465
  labelY = valueLabelInfo.y - valueLabelInfo.fontSize - spacing;
1694
1466
  }
1695
1467
  else {
@@ -1721,9 +1493,7 @@ async function createBarChart(data, options = {}) {
1721
1493
  labelY = barCenterY;
1722
1494
  textAlign = 'center';
1723
1495
  textBaseline = 'middle';
1724
- // Use white or black text based on bar color for better visibility
1725
1496
  const barColor = item.color || '#4A90E2';
1726
- // Simple brightness check - if bar is dark, use white text
1727
1497
  const isDark = barColor === '#000000' || barColor.toLowerCase().includes('dark') ||
1728
1498
  (barColor.startsWith('#') && parseInt(barColor.slice(1, 3), 16) < 128);
1729
1499
  ctx.fillStyle = isDark ? '#FFFFFF' : (item.labelColor || '#000000');
@@ -1734,7 +1504,6 @@ async function createBarChart(data, options = {}) {
1734
1504
  textAlign = 'center';
1735
1505
  textBaseline = 'top';
1736
1506
  }
1737
- // Calculate label color (for 'inside' position, check if bar is dark)
1738
1507
  let labelColor = item.labelColor || '#000000';
1739
1508
  if (currentLabelPosition === 'inside') {
1740
1509
  const barColor = item.color || '#4A90E2';
@@ -1742,7 +1511,6 @@ async function createBarChart(data, options = {}) {
1742
1511
  (barColor.startsWith('#') && parseInt(barColor.slice(1, 3), 16) < 128);
1743
1512
  labelColor = isDark ? '#FFFFFF' : (item.labelColor || '#000000');
1744
1513
  }
1745
- // Store bar label for later drawing
1746
1514
  labelsToDraw.push({
1747
1515
  type: 'bar',
1748
1516
  text: item.label,
@@ -1755,12 +1523,10 @@ async function createBarChart(data, options = {}) {
1755
1523
  });
1756
1524
  }
1757
1525
  });
1758
- // Second pass: Draw all labels (values and bar labels) on top of everything
1759
1526
  for (const label of labelsToDraw) {
1760
1527
  ctx.save();
1761
1528
  ctx.textAlign = label.align;
1762
1529
  ctx.textBaseline = label.baseline;
1763
- // Determine text style and gradient based on label type
1764
1530
  let textStyle;
1765
1531
  let textGradient;
1766
1532
  if (label.type === 'bar') {
@@ -1776,7 +1542,6 @@ async function createBarChart(data, options = {}) {
1776
1542
  }
1777
1543
  return canvas.toBuffer('image/png');
1778
1544
  }
1779
- // Example usage with organized, categorized configuration:
1780
1545
  (async () => {
1781
1546
  const chart = await createBarChart([
1782
1547
  {
@@ -1813,9 +1578,7 @@ async function createBarChart(data, options = {}) {
1813
1578
  showValue: true
1814
1579
  }
1815
1580
  ], {
1816
- // Chart Type
1817
- type: 'standard', // 'standard' | 'grouped' | 'stacked' | 'horizontal'
1818
- // Dimensions
1581
+ type: 'standard',
1819
1582
  dimensions: {
1820
1583
  height: 600,
1821
1584
  padding: {
@@ -1825,34 +1588,28 @@ async function createBarChart(data, options = {}) {
1825
1588
  left: 100
1826
1589
  }
1827
1590
  },
1828
- // Appearance
1829
1591
  appearance: {
1830
1592
  backgroundColor: 'white',
1831
- // backgroundImage: './path/to/background.png', // Optional
1832
1593
  axisColor: '#000000',
1833
1594
  axisWidth: 2,
1834
1595
  arrowSize: 10
1835
1596
  },
1836
- // Axes Configuration
1837
1597
  axes: {
1838
1598
  x: {
1839
1599
  label: 'Day',
1840
1600
  labelColor: 'black',
1841
1601
  values: [24, 25, 26, 27, 28, 29, 30, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
1842
- // OR use range: { min: 0, max: 100, step: 20 }
1843
1602
  tickFontSize: 10,
1844
- valueSpacing: 5 // Pixel spacing between each tick value (e.g., 5px gap between value 1 and 2)
1603
+ valueSpacing: 5
1845
1604
  },
1846
1605
  y: {
1847
1606
  label: 'Count',
1848
1607
  labelColor: 'black',
1849
1608
  values: [0, 2, 4, 6, 8, 10, 12, 14],
1850
- // OR use range: { min: 0, max: 14, step: 2 }
1851
1609
  tickFontSize: 10,
1852
- valueSpacing: 3 // Pixel spacing between each tick value (e.g., 3px gap between value 0 and 2)
1610
+ valueSpacing: 3
1853
1611
  }
1854
1612
  },
1855
- // Labels & Text
1856
1613
  labels: {
1857
1614
  title: {
1858
1615
  text: 'Joined Members',
@@ -1860,18 +1617,17 @@ async function createBarChart(data, options = {}) {
1860
1617
  color: '#000000'
1861
1618
  },
1862
1619
  barLabelDefaults: {
1863
- show: true, // Global show/hide - each bar's label is defined in data
1864
- defaultPosition: 'bottom', // Default when bar doesn't specify labelPosition
1620
+ show: true,
1621
+ defaultPosition: 'bottom',
1865
1622
  fontSize: 12,
1866
- defaultColor: '#000000' // Default when bar doesn't specify labelColor
1623
+ defaultColor: '#000000'
1867
1624
  },
1868
1625
  valueLabelDefaults: {
1869
- show: true, // Global show/hide - each bar can override with showValue
1626
+ show: true,
1870
1627
  fontSize: 11,
1871
- defaultColor: '#000000' // Default when bar doesn't specify valueColor
1628
+ defaultColor: '#000000'
1872
1629
  }
1873
1630
  },
1874
- // Legend (always positioned at top)
1875
1631
  legend: {
1876
1632
  show: true,
1877
1633
  entries: [
@@ -1879,7 +1635,6 @@ async function createBarChart(data, options = {}) {
1879
1635
  { color: '#4A90E2', label: 'Bots' }
1880
1636
  ]
1881
1637
  },
1882
- // Grid
1883
1638
  grid: {
1884
1639
  show: true,
1885
1640
  color: '#E0E0E0',