drizzle-cube 0.4.41 → 0.4.43

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 (188) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/express/index.cjs +1 -1
  3. package/dist/adapters/express/index.js +2 -2
  4. package/dist/adapters/fastify/index.cjs +1 -1
  5. package/dist/adapters/fastify/index.js +2 -2
  6. package/dist/adapters/{handler-dHHEEbG9.cjs → handler-BneUGY0k.cjs} +1 -1
  7. package/dist/adapters/{handler-fto6TSVn.js → handler-CiQoQsnP.js} +1 -1
  8. package/dist/adapters/hono/index.cjs +1 -1
  9. package/dist/adapters/hono/index.js +5 -5
  10. package/dist/adapters/{mcp-transport-tB5a7Het.js → mcp-transport-CC3hQOnb.js} +1099 -1338
  11. package/dist/adapters/mcp-transport-mmuPvjfR.cjs +255 -0
  12. package/dist/adapters/nextjs/index.cjs +1 -1
  13. package/dist/adapters/nextjs/index.js +2 -2
  14. package/dist/adapters/{openai-Ckpe7iU7.js → openai-CQfaz257.js} +1 -1
  15. package/dist/{server/openai-DKpZPCay.cjs → adapters/openai-CZ74gWd4.cjs} +1 -1
  16. package/dist/adapters/{openai-Bc8qwEKW.cjs → openai-DLfardui.cjs} +1 -1
  17. package/dist/adapters/{openai-DxeVtl8X.js → openai-DaxuhJLS.js} +1 -1
  18. package/dist/client/charts.js +12 -12
  19. package/dist/client/chunks/{DashboardEditModal-D_7J62sG.js → DashboardEditModal-BTdV528l.js} +1421 -1396
  20. package/dist/client/chunks/DashboardEditModal-BTdV528l.js.map +1 -0
  21. package/dist/client/chunks/{FieldSearchModal-5Qz6vvTz.js → FieldSearchModal-D75vy4Wb.js} +4 -4
  22. package/dist/client/chunks/{FieldSearchModal-5Qz6vvTz.js.map → FieldSearchModal-D75vy4Wb.js.map} +1 -1
  23. package/dist/client/chunks/KpiDelta-Bk8bzKYM.js +2 -0
  24. package/dist/client/chunks/KpiNumber-CKF-8e_T.js +2 -0
  25. package/dist/client/chunks/KpiText-Iz1vIvu_.js +2 -0
  26. package/dist/client/chunks/{RetentionCombinedChart-B1hUYaXt.js → RetentionCombinedChart-DIhK5pD8.js} +3 -3
  27. package/dist/client/chunks/{RetentionCombinedChart-B1hUYaXt.js.map → RetentionCombinedChart-DIhK5pD8.js.map} +1 -1
  28. package/dist/client/chunks/{RetentionHeatmap-Dn2ocjVf.js → RetentionHeatmap-CyREolyP.js} +1 -1
  29. package/dist/client/chunks/{RetentionHeatmap-Dn2ocjVf.js.map → RetentionHeatmap-CyREolyP.js.map} +1 -1
  30. package/dist/client/chunks/SchemaVisualization-B1GUT-FM.js +2 -0
  31. package/dist/client/chunks/SchemaVisualizationLazy-DymwT34e.js +2 -0
  32. package/dist/client/chunks/{analysis-builder-DPJJiHVx.js → analysis-builder-C1CJ0c7L.js} +415 -406
  33. package/dist/client/chunks/analysis-builder-C1CJ0c7L.js.map +1 -0
  34. package/dist/client/chunks/{analysis-builder-shared-C-C-rOgu.js → analysis-builder-shared-rkjJfWLT.js} +9 -9
  35. package/dist/client/chunks/{analysis-builder-shared-C-C-rOgu.js.map → analysis-builder-shared-rkjJfWLT.js.map} +1 -1
  36. package/dist/client/chunks/{chart-activity-grid-wR2Twpo7.js → chart-activity-grid-DLktOINm.js} +2 -2
  37. package/dist/client/chunks/{chart-activity-grid-wR2Twpo7.js.map → chart-activity-grid-DLktOINm.js.map} +1 -1
  38. package/dist/client/chunks/{chart-area-e9ysnatQ.js → chart-area-BwYaflNk.js} +3 -3
  39. package/dist/client/chunks/{chart-area-e9ysnatQ.js.map → chart-area-BwYaflNk.js.map} +1 -1
  40. package/dist/client/chunks/{chart-bar-DVzmau1G.js → chart-bar-BiENfFgE.js} +4 -4
  41. package/dist/client/chunks/{chart-bar-DVzmau1G.js.map → chart-bar-BiENfFgE.js.map} +1 -1
  42. package/dist/client/chunks/{chart-box-plot-CCmbHv1Y.js → chart-box-plot-BJF1tBXC.js} +3 -3
  43. package/dist/client/chunks/{chart-box-plot-CCmbHv1Y.js.map → chart-box-plot-BJF1tBXC.js.map} +1 -1
  44. package/dist/client/chunks/{chart-bubble-S6qSwWdK.js → chart-bubble-DQQhGVDJ.js} +3 -3
  45. package/dist/client/chunks/{chart-bubble-S6qSwWdK.js.map → chart-bubble-DQQhGVDJ.js.map} +1 -1
  46. package/dist/client/chunks/{chart-candlestick-BNKbDruo.js → chart-candlestick-C2UuXbLe.js} +3 -3
  47. package/dist/client/chunks/{chart-candlestick-BNKbDruo.js.map → chart-candlestick-C2UuXbLe.js.map} +1 -1
  48. package/dist/client/chunks/{chart-config-activity-grid-BSWS08cI.js → chart-config-activity-grid-DJOU3TEz.js} +2 -2
  49. package/dist/client/chunks/{chart-config-activity-grid-BSWS08cI.js.map → chart-config-activity-grid-DJOU3TEz.js.map} +1 -1
  50. package/dist/client/chunks/{chart-config-area-DKwgcHp4.js → chart-config-area-CWnWVT6a.js} +2 -2
  51. package/dist/client/chunks/{chart-config-area-DKwgcHp4.js.map → chart-config-area-CWnWVT6a.js.map} +1 -1
  52. package/dist/client/chunks/{chart-config-bar-deTjEhap.js → chart-config-bar-C-7Dr1Ho.js} +2 -2
  53. package/dist/client/chunks/{chart-config-bar-deTjEhap.js.map → chart-config-bar-C-7Dr1Ho.js.map} +1 -1
  54. package/dist/client/chunks/{chart-config-box-plot-DU4iWk3V.js → chart-config-box-plot-mVOwmLdu.js} +2 -2
  55. package/dist/client/chunks/{chart-config-box-plot-DU4iWk3V.js.map → chart-config-box-plot-mVOwmLdu.js.map} +1 -1
  56. package/dist/client/chunks/{chart-config-bubble-B8FSHSW-.js → chart-config-bubble-BPE2CeiD.js} +2 -2
  57. package/dist/client/chunks/{chart-config-bubble-B8FSHSW-.js.map → chart-config-bubble-BPE2CeiD.js.map} +1 -1
  58. package/dist/client/chunks/{chart-config-candlestick-BGfyWFft.js → chart-config-candlestick-BSB9DRy2.js} +2 -2
  59. package/dist/client/chunks/{chart-config-candlestick-BGfyWFft.js.map → chart-config-candlestick-BSB9DRy2.js.map} +1 -1
  60. package/dist/client/chunks/{chart-config-data-table-DKRcGa8t.js → chart-config-data-table-Bhdx5Hem.js} +2 -2
  61. package/dist/client/chunks/{chart-config-data-table-DKRcGa8t.js.map → chart-config-data-table-Bhdx5Hem.js.map} +1 -1
  62. package/dist/client/chunks/{chart-config-funnel-Bt4iGFo_.js → chart-config-funnel-Cl-v-bm1.js} +2 -2
  63. package/dist/client/chunks/{chart-config-funnel-Bt4iGFo_.js.map → chart-config-funnel-Cl-v-bm1.js.map} +1 -1
  64. package/dist/client/chunks/{chart-config-gauge-Bk4Jjp3W.js → chart-config-gauge-CdrUTJMJ.js} +2 -2
  65. package/dist/client/chunks/{chart-config-gauge-Bk4Jjp3W.js.map → chart-config-gauge-CdrUTJMJ.js.map} +1 -1
  66. package/dist/client/chunks/{chart-config-heat-map-CkHsqkrY.js → chart-config-heat-map-DGE3NzoF.js} +2 -2
  67. package/dist/client/chunks/{chart-config-heat-map-CkHsqkrY.js.map → chart-config-heat-map-DGE3NzoF.js.map} +1 -1
  68. package/dist/client/chunks/{chart-config-kpi-delta-CkUX98JV.js → chart-config-kpi-delta-DMrQerUW.js} +2 -2
  69. package/dist/client/chunks/{chart-config-kpi-delta-CkUX98JV.js.map → chart-config-kpi-delta-DMrQerUW.js.map} +1 -1
  70. package/dist/client/chunks/{chart-config-kpi-number-DcxyiUgs.js → chart-config-kpi-number-DCytCytn.js} +2 -2
  71. package/dist/client/chunks/{chart-config-kpi-number-DcxyiUgs.js.map → chart-config-kpi-number-DCytCytn.js.map} +1 -1
  72. package/dist/client/chunks/{chart-config-kpi-text-DI4mj8CN.js → chart-config-kpi-text-KdKQUvHo.js} +2 -2
  73. package/dist/client/chunks/{chart-config-kpi-text-DI4mj8CN.js.map → chart-config-kpi-text-KdKQUvHo.js.map} +1 -1
  74. package/dist/client/chunks/{chart-config-line--j7-dLue.js → chart-config-line-Bl9VDAdz.js} +2 -2
  75. package/dist/client/chunks/{chart-config-line--j7-dLue.js.map → chart-config-line-Bl9VDAdz.js.map} +1 -1
  76. package/dist/client/chunks/{chart-config-markdown-DUjvVjV4.js → chart-config-markdown-BX26b94i.js} +2 -2
  77. package/dist/client/chunks/{chart-config-markdown-DUjvVjV4.js.map → chart-config-markdown-BX26b94i.js.map} +1 -1
  78. package/dist/client/chunks/{chart-config-measure-profile-B9FKBNGc.js → chart-config-measure-profile-DwtRhEFh.js} +2 -2
  79. package/dist/client/chunks/{chart-config-measure-profile-B9FKBNGc.js.map → chart-config-measure-profile-DwtRhEFh.js.map} +1 -1
  80. package/dist/client/chunks/{chart-config-pie-yU4jipl9.js → chart-config-pie-BzBcqPMJ.js} +2 -2
  81. package/dist/client/chunks/{chart-config-pie-yU4jipl9.js.map → chart-config-pie-BzBcqPMJ.js.map} +1 -1
  82. package/dist/client/chunks/{chart-config-radar-R9Fkc8wL.js → chart-config-radar-6ZOgt__z.js} +2 -2
  83. package/dist/client/chunks/{chart-config-radar-R9Fkc8wL.js.map → chart-config-radar-6ZOgt__z.js.map} +1 -1
  84. package/dist/client/chunks/{chart-config-radial-bar-DeoXfpIp.js → chart-config-radial-bar-Df6Eta7N.js} +2 -2
  85. package/dist/client/chunks/{chart-config-radial-bar-DeoXfpIp.js.map → chart-config-radial-bar-Df6Eta7N.js.map} +1 -1
  86. package/dist/client/chunks/{chart-config-sankey-CXEsxo6s.js → chart-config-sankey-DgqKBFvN.js} +2 -2
  87. package/dist/client/chunks/{chart-config-sankey-CXEsxo6s.js.map → chart-config-sankey-DgqKBFvN.js.map} +1 -1
  88. package/dist/client/chunks/{chart-config-scatter-MVUFupub.js → chart-config-scatter-D5nVLDvi.js} +2 -2
  89. package/dist/client/chunks/{chart-config-scatter-MVUFupub.js.map → chart-config-scatter-D5nVLDvi.js.map} +1 -1
  90. package/dist/client/chunks/{chart-config-sunburst-Z_gqIY5u.js → chart-config-sunburst-Ca3FX9nW.js} +2 -2
  91. package/dist/client/chunks/{chart-config-sunburst-Z_gqIY5u.js.map → chart-config-sunburst-Ca3FX9nW.js.map} +1 -1
  92. package/dist/client/chunks/{chart-config-tree-map-BD-xAeIy.js → chart-config-tree-map-Bjy4QNa3.js} +2 -2
  93. package/dist/client/chunks/{chart-config-tree-map-BD-xAeIy.js.map → chart-config-tree-map-Bjy4QNa3.js.map} +1 -1
  94. package/dist/client/chunks/{chart-config-waterfall-CHwVkXZc.js → chart-config-waterfall-C5K2eqR7.js} +2 -2
  95. package/dist/client/chunks/{chart-config-waterfall-CHwVkXZc.js.map → chart-config-waterfall-C5K2eqR7.js.map} +1 -1
  96. package/dist/client/chunks/{chart-data-table-D4s27-U3.js → chart-data-table-2iCsn0CF.js} +1099 -933
  97. package/dist/client/chunks/chart-data-table-2iCsn0CF.js.map +1 -0
  98. package/dist/client/chunks/{chart-funnel-C9JRW79j.js → chart-funnel-poyOf7-e.js} +3 -3
  99. package/dist/client/chunks/{chart-funnel-C9JRW79j.js.map → chart-funnel-poyOf7-e.js.map} +1 -1
  100. package/dist/client/chunks/{chart-gauge-BFhc4i_f.js → chart-gauge-D5J4gRky.js} +3 -3
  101. package/dist/client/chunks/{chart-gauge-BFhc4i_f.js.map → chart-gauge-D5J4gRky.js.map} +1 -1
  102. package/dist/client/chunks/{chart-heat-map-BOMQeUDL.js → chart-heat-map-BAMVhLGG.js} +3 -3
  103. package/dist/client/chunks/{chart-heat-map-BOMQeUDL.js.map → chart-heat-map-BAMVhLGG.js.map} +1 -1
  104. package/dist/client/chunks/{chart-kpi-delta-DzGNnIcW.js → chart-kpi-delta-KQjUIeal.js} +71 -73
  105. package/dist/client/chunks/{chart-kpi-delta-DzGNnIcW.js.map → chart-kpi-delta-KQjUIeal.js.map} +1 -1
  106. package/dist/client/chunks/{chart-kpi-number-sHtgbE_f.js → chart-kpi-number-CsQgV_x3.js} +62 -63
  107. package/dist/client/chunks/{chart-kpi-number-sHtgbE_f.js.map → chart-kpi-number-CsQgV_x3.js.map} +1 -1
  108. package/dist/client/chunks/{chart-kpi-text-Bmk-GzVJ.js → chart-kpi-text-BR0IyeUU.js} +26 -27
  109. package/dist/client/chunks/{chart-kpi-text-Bmk-GzVJ.js.map → chart-kpi-text-BR0IyeUU.js.map} +1 -1
  110. package/dist/client/chunks/{chart-line-Bkl5WQAw.js → chart-line-B5_WntY5.js} +4 -4
  111. package/dist/client/chunks/{chart-line-Bkl5WQAw.js.map → chart-line-B5_WntY5.js.map} +1 -1
  112. package/dist/client/chunks/{chart-markdown-7MNetRtX.js → chart-markdown-B6bENbel.js} +648 -645
  113. package/dist/client/chunks/chart-markdown-B6bENbel.js.map +1 -0
  114. package/dist/client/chunks/{chart-measure-profile-B7h6vQo4.js → chart-measure-profile-yWk-obNb.js} +4 -4
  115. package/dist/client/chunks/{chart-measure-profile-B7h6vQo4.js.map → chart-measure-profile-yWk-obNb.js.map} +1 -1
  116. package/dist/client/chunks/{chart-pie-Do2YnCxl.js → chart-pie-BodrUoHv.js} +4 -4
  117. package/dist/client/chunks/{chart-pie-Do2YnCxl.js.map → chart-pie-BodrUoHv.js.map} +1 -1
  118. package/dist/client/chunks/{chart-radar-C7gQkH70.js → chart-radar-gG3zfLud.js} +4 -4
  119. package/dist/client/chunks/{chart-radar-C7gQkH70.js.map → chart-radar-gG3zfLud.js.map} +1 -1
  120. package/dist/client/chunks/{chart-radial-bar-DHqCck3x.js → chart-radial-bar-C2IPCV8c.js} +4 -4
  121. package/dist/client/chunks/{chart-radial-bar-DHqCck3x.js.map → chart-radial-bar-C2IPCV8c.js.map} +1 -1
  122. package/dist/client/chunks/{chart-sankey-8nRYnupt.js → chart-sankey-BOyxfG1Q.js} +3 -3
  123. package/dist/client/chunks/{chart-sankey-8nRYnupt.js.map → chart-sankey-BOyxfG1Q.js.map} +1 -1
  124. package/dist/client/chunks/{chart-scatter-YSHOUfXf.js → chart-scatter-B8OwlsAX.js} +4 -4
  125. package/dist/client/chunks/{chart-scatter-YSHOUfXf.js.map → chart-scatter-B8OwlsAX.js.map} +1 -1
  126. package/dist/client/chunks/{chart-sunburst-BGhJ4fui.js → chart-sunburst-D9lGEOCc.js} +4 -4
  127. package/dist/client/chunks/{chart-sunburst-BGhJ4fui.js.map → chart-sunburst-D9lGEOCc.js.map} +1 -1
  128. package/dist/client/chunks/{chart-tree-map-BlhcXK1F.js → chart-tree-map-DZaKy9he.js} +4 -4
  129. package/dist/client/chunks/{chart-tree-map-BlhcXK1F.js.map → chart-tree-map-DZaKy9he.js.map} +1 -1
  130. package/dist/client/chunks/{chart-waterfall-BWCAzlPq.js → chart-waterfall-BCdUx4DC.js} +4 -4
  131. package/dist/client/chunks/{chart-waterfall-BWCAzlPq.js.map → chart-waterfall-BCdUx4DC.js.map} +1 -1
  132. package/dist/client/chunks/{charts-core-Cy3rHADX.js → charts-core-C5Yokk-x.js} +2 -2
  133. package/dist/client/chunks/{charts-core-Cy3rHADX.js.map → charts-core-C5Yokk-x.js.map} +1 -1
  134. package/dist/client/chunks/{core-BdWfCZ3y.js → core-DcfMGTVa.js} +2 -2
  135. package/dist/client/chunks/{core-BdWfCZ3y.js.map → core-DcfMGTVa.js.map} +1 -1
  136. package/dist/client/chunks/{dist-BWPE2m_X.js → dist-eZurnOde.js} +1 -1
  137. package/dist/client/chunks/{dist-BWPE2m_X.js.map → dist-eZurnOde.js.map} +1 -1
  138. package/dist/client/chunks/exceljs.min-CcjgM-qg.js +31986 -0
  139. package/dist/client/chunks/exceljs.min-CcjgM-qg.js.map +1 -0
  140. package/dist/client/chunks/{javascript-O1RIRkZr.js → javascript-DFvvCuoP.js} +1 -1
  141. package/dist/client/chunks/{javascript-O1RIRkZr.js.map → javascript-DFvvCuoP.js.map} +1 -1
  142. package/dist/client/chunks/{json-C5bX2tt1.js → json-BBm9TlrA.js} +1 -1
  143. package/dist/client/chunks/{json-C5bX2tt1.js.map → json-BBm9TlrA.js.map} +1 -1
  144. package/dist/client/chunks/{rolldown-runtime-lc2dmIiU.js → rolldown-runtime-CCl2IeXn.js} +5 -2
  145. package/dist/client/chunks/{schema-visualization-BJ8HrNqB.js → schema-visualization-t1KiOORo.js} +259 -267
  146. package/dist/client/chunks/schema-visualization-t1KiOORo.js.map +1 -0
  147. package/dist/client/chunks/{sql-D2qikO5q.js → sql-k0GA6oZ_.js} +1 -1
  148. package/dist/client/chunks/{sql-D2qikO5q.js.map → sql-k0GA6oZ_.js.map} +1 -1
  149. package/dist/client/chunks/{syntaxHighlighting-BYYWYmjr.js → syntaxHighlighting-CnDujqwg.js} +2 -2
  150. package/dist/client/chunks/{syntaxHighlighting-BYYWYmjr.js.map → syntaxHighlighting-CnDujqwg.js.map} +1 -1
  151. package/dist/client/chunks/{useDebounce-EWynD0lC.js → useDebounce-CKqkM42n.js} +11 -14
  152. package/dist/client/chunks/{useDebounce-EWynD0lC.js.map → useDebounce-CKqkM42n.js.map} +1 -1
  153. package/dist/client/chunks/{useExplainAI-BBTJWQVu.js → useExplainAI-DBIfYwz-.js} +12 -12
  154. package/dist/client/chunks/{useExplainAI-BBTJWQVu.js.map → useExplainAI-DBIfYwz-.js.map} +1 -1
  155. package/dist/client/chunks/{utils-DMyRayr_.js → utils--qCr8Yn5.js} +2 -2
  156. package/dist/client/chunks/utils--qCr8Yn5.js.map +1 -0
  157. package/dist/client/chunks/{vendor-iY25ogTA.js → vendor-BRlsCGnK.js} +46 -46
  158. package/dist/client/chunks/{vendor-iY25ogTA.js.map → vendor-BRlsCGnK.js.map} +1 -1
  159. package/dist/client/components.js +3 -3
  160. package/dist/client/hooks.js +3 -3
  161. package/dist/client/icons/types.d.ts +1 -0
  162. package/dist/client/icons.js +1 -1
  163. package/dist/client/index.d.ts +2 -2
  164. package/dist/client/index.js +168 -169
  165. package/dist/client/index.js.map +1 -1
  166. package/dist/client/providers.js +1 -1
  167. package/dist/client/schema.d.ts +13 -0
  168. package/dist/client/schema.js +2 -0
  169. package/dist/client/styles.css +1 -1
  170. package/dist/client/types.d.ts +6 -0
  171. package/dist/client/utils/exportXlsx.d.ts +20 -0
  172. package/dist/client/utils/index.d.ts +1 -0
  173. package/dist/client/utils.js +7 -7
  174. package/dist/client-bundle-stats.html +1 -1
  175. package/dist/server/index.cjs +1 -1
  176. package/dist/server/index.js +1 -1
  177. package/dist/server/{openai-DqCEogm0.js → openai-CqZg6zYL.js} +1 -1
  178. package/dist/server/{openai-0HbLlZq6.cjs → openai-D9Zjuby1.cjs} +1 -1
  179. package/dist/{adapters/openai-C96O8M75.cjs → server/openai-DmuEbFd6.cjs} +1 -1
  180. package/dist/server/{openai-DnGeU9PT.js → openai-rwauPzCT.js} +1 -1
  181. package/package.json +10 -1
  182. package/dist/adapters/mcp-transport-ro4OL4BW.cjs +0 -255
  183. package/dist/client/chunks/DashboardEditModal-D_7J62sG.js.map +0 -1
  184. package/dist/client/chunks/analysis-builder-DPJJiHVx.js.map +0 -1
  185. package/dist/client/chunks/chart-data-table-D4s27-U3.js.map +0 -1
  186. package/dist/client/chunks/chart-markdown-7MNetRtX.js.map +0 -1
  187. package/dist/client/chunks/schema-visualization-BJ8HrNqB.js.map +0 -1
  188. package/dist/client/chunks/utils-DMyRayr_.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"chart-heat-map-BOMQeUDL.js","names":[],"sources":["../../../src/client/components/charts/HeatMapChart.tsx"],"sourcesContent":["/**\n * HeatMapChart Component\n *\n * Visualizes intensity across two categorical dimensions using a color matrix.\n * Uses @nivo/heatmap for rendering.\n *\n * The chart displays:\n * - Rows: Y-axis dimension values\n * - Columns: X-axis dimension values\n * - Cell color: Intensity based on measure value\n */\n\nimport React, { useMemo } from 'react'\nimport { ResponsiveHeatMap } from '@nivo/heatmap'\nimport { formatTimeValue, getFieldGranularity, formatAxisValue } from '../../utils/chartUtils'\nimport type { AxisFormatConfig } from '../../types'\nimport type { ChartProps } from '../../types'\n\n/**\n * Parse color string (hex or rgb) to RGB values\n */\nfunction parseColor(color: string): { r: number; g: number; b: number } | null {\n // Handle hex colors\n if (color.startsWith('#')) {\n const hex = color.slice(1)\n return {\n r: parseInt(hex.substring(0, 2), 16),\n g: parseInt(hex.substring(2, 4), 16),\n b: parseInt(hex.substring(4, 6), 16),\n }\n }\n\n // Handle rgb/rgba colors\n const rgbMatch = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (rgbMatch) {\n return {\n r: parseInt(rgbMatch[1], 10),\n g: parseInt(rgbMatch[2], 10),\n b: parseInt(rgbMatch[3], 10),\n }\n }\n\n return null\n}\n\n/**\n * Calculate relative luminance of a color\n * Returns value between 0 (black) and 1 (white)\n */\nfunction getLuminance(color: string): number {\n const rgb = parseColor(color)\n if (!rgb) return 0.5 // Default to mid-gray if parsing fails\n\n const r = rgb.r / 255\n const g = rgb.g / 255\n const b = rgb.b / 255\n\n // Apply gamma correction\n const rLinear = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4)\n const gLinear = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4)\n const bLinear = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4)\n\n // Calculate luminance using WCAG formula\n return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear\n}\n\n/**\n * Get contrasting text color (white or dark) based on background color\n */\nfunction getContrastingTextColor(bgColor: string): string {\n const luminance = getLuminance(bgColor)\n // Use white text on dark backgrounds, dark text on light backgrounds\n return luminance < 0.4 ? '#ffffff' : '#1f2937'\n}\n\n/**\n * Maximum dimensions for heatmap to prevent browser lockup\n * 50x50 = 2500 cells max\n */\nconst MAX_HEATMAP_ROWS = 50\nconst MAX_HEATMAP_COLS = 50\n\n/**\n * Nivo heatmap data format\n */\ninterface HeatMapDatum {\n x: string\n y: number | null\n}\n\ninterface HeatMapSerie {\n id: string\n data: HeatMapDatum[]\n}\n\n/**\n * Result of heatmap transformation including truncation info\n */\ninterface HeatMapTransformResult {\n data: HeatMapSerie[]\n truncated: boolean\n originalRows: number\n originalCols: number\n}\n\n/**\n * Transform drizzle-cube flat query results to nivo heatmap format\n *\n * Input (drizzle-cube):\n * [\n * { \"Region.name\": \"East\", \"Product.category\": \"Electronics\", \"Sales.total\": 1500 },\n * { \"Region.name\": \"East\", \"Product.category\": \"Clothing\", \"Sales.total\": 800 },\n * ]\n *\n * Output (nivo format):\n * [\n * { id: \"East\", data: [{ x: \"Electronics\", y: 1500 }, { x: \"Clothing\", y: 800 }] }\n * ]\n *\n * Data is truncated to MAX_HEATMAP_ROWS x MAX_HEATMAP_COLS to prevent browser lockup\n */\nfunction transformToHeatMapFormat(\n data: Record<string, unknown>[],\n xAxisField: string | undefined,\n yAxisField: string | undefined,\n valueField: string | undefined,\n xGranularity?: string,\n yGranularity?: string\n): HeatMapTransformResult {\n if (!xAxisField || !yAxisField || !valueField) {\n return { data: [], truncated: false, originalRows: 0, originalCols: 0 }\n }\n\n // Group data by Y-axis dimension\n const groupedByY = new Map<string, Map<string, number>>()\n const allXValues = new Set<string>()\n // Keep original values for sorting timestamps correctly\n const xValueOriginals = new Map<string, unknown>()\n\n for (const row of data) {\n const rawYValue = row[yAxisField]\n const rawXValue = row[xAxisField]\n\n // Format time values based on granularity\n const yValue = formatTimeValue(rawYValue, yGranularity) || String(rawYValue ?? '(empty)')\n const xValue = formatTimeValue(rawXValue, xGranularity) || String(rawXValue ?? '(empty)')\n const value = Number(row[valueField]) || 0\n\n allXValues.add(xValue)\n // Store original for sorting\n if (!xValueOriginals.has(xValue)) {\n xValueOriginals.set(xValue, rawXValue)\n }\n\n if (!groupedByY.has(yValue)) {\n groupedByY.set(yValue, new Map())\n }\n groupedByY.get(yValue)!.set(xValue, value)\n }\n\n // Sort X values - try to sort by original timestamp if available\n const xValueArray = Array.from(allXValues).sort((a, b) => {\n const origA = xValueOriginals.get(a)\n const origB = xValueOriginals.get(b)\n // If both are date strings, sort chronologically\n if (typeof origA === 'string' && typeof origB === 'string' &&\n origA.match(/^\\d{4}-\\d{2}-\\d{2}/) && origB.match(/^\\d{4}-\\d{2}-\\d{2}/)) {\n return origA.localeCompare(origB)\n }\n // Otherwise sort alphabetically by formatted value\n return a.localeCompare(b)\n })\n\n // Track original dimensions for truncation warning\n const originalRows = groupedByY.size\n const originalCols = xValueArray.length\n const truncated = originalRows > MAX_HEATMAP_ROWS || originalCols > MAX_HEATMAP_COLS\n\n // Truncate X values if needed\n const limitedXValues = xValueArray.slice(0, MAX_HEATMAP_COLS)\n\n // Build result with truncation\n const result: HeatMapSerie[] = []\n let rowCount = 0\n for (const [yValue, xMap] of groupedByY) {\n if (rowCount >= MAX_HEATMAP_ROWS) break\n result.push({\n id: yValue,\n data: limitedXValues.map((x) => ({\n x,\n y: xMap.get(x) ?? null,\n })),\n })\n rowCount++\n }\n\n return { data: result, truncated, originalRows, originalCols }\n}\n\n/**\n * HeatMapChart Component\n *\n * Renders a heatmap visualization from query results.\n * Shows intensity patterns across two categorical dimensions.\n */\nconst HeatMapChart = React.memo(function HeatMapChart({\n data,\n height = '100%',\n chartConfig,\n colorPalette,\n displayConfig,\n queryObject,\n}: ChartProps) {\n // Get display config options\n const displayConfigAny = displayConfig as Record<string, unknown> | undefined\n const showLabels = (displayConfigAny?.showLabels as boolean) ?? false\n const cellShape = (displayConfigAny?.cellShape as 'rect' | 'circle') ?? 'rect'\n const showLegend = (displayConfigAny?.showLegend as boolean) ?? true\n const xAxisFormat = displayConfigAny?.xAxisFormat as AxisFormatConfig | undefined\n const yAxisFormat = displayConfigAny?.yAxisFormat as AxisFormatConfig | undefined\n const valueFormat = displayConfigAny?.valueFormat as AxisFormatConfig | undefined\n\n // Extract field names from chartConfig (handle both array and string formats)\n const xAxisField = chartConfig?.xAxis\n ? (Array.isArray(chartConfig.xAxis) ? chartConfig.xAxis[0] : chartConfig.xAxis)\n : undefined\n const yAxisField = chartConfig?.yAxis\n ? (Array.isArray(chartConfig.yAxis) ? chartConfig.yAxis[0] : chartConfig.yAxis)\n : undefined\n const valueField = chartConfig?.valueField\n ? (Array.isArray(chartConfig.valueField) ? chartConfig.valueField[0] : chartConfig.valueField)\n : undefined\n\n // Get granularity for time dimensions (only if field is defined)\n const xGranularity = xAxisField ? getFieldGranularity(queryObject, xAxisField) : undefined\n const yGranularity = yAxisField ? getFieldGranularity(queryObject, yAxisField) : undefined\n\n // Transform data to nivo format\n const { data: heatmapData, truncated, originalRows, originalCols } = useMemo(() => {\n if (!data || data.length === 0) {\n return { data: [], truncated: false, originalRows: 0, originalCols: 0 }\n }\n return transformToHeatMapFormat(\n data as Record<string, unknown>[],\n xAxisField,\n yAxisField,\n valueField,\n xGranularity,\n yGranularity\n )\n }, [data, xAxisField, yAxisField, valueField, xGranularity, yGranularity])\n\n // Handle no data or missing config\n if (!data || data.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n Run a query to see heatmap visualization\n </div>\n </div>\n </div>\n )\n }\n\n if (!xAxisField || !yAxisField || !valueField) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">Configuration required</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n {!xAxisField && 'X-axis dimension required. '}\n {!yAxisField && 'Y-axis dimension required. '}\n {!valueField && 'Value measure required.'}\n </div>\n </div>\n </div>\n )\n }\n\n if (heatmapData.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">No data to display</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n The query returned no results for the heatmap\n </div>\n </div>\n </div>\n )\n }\n\n // Use gradient colors from palette, or default sequential blue gradient\n // Sequential single-hue gradients are ideal for heatmaps showing magnitude/intensity\n const colors = colorPalette?.gradient || [\n '#eff3ff', // lightest blue\n '#c6dbef',\n '#9ecae1',\n '#6baed6',\n '#3182bd',\n '#08519c', // darkest blue\n ]\n\n return (\n <div className=\"dc:relative dc:w-full dc:h-full\" style={{ height }}>\n {truncated && (\n <div className=\"dc:absolute dc:top-0 dc:left-0 dc:right-0 dc:z-10 dc:px-3 dc:py-1.5 dc:text-xs bg-dc-warning-bg text-dc-warning dc:border-b border-dc-border\">\n Data truncated to {MAX_HEATMAP_ROWS}x{MAX_HEATMAP_COLS} cells (original: {originalRows}x{originalCols}). Add filters to reduce dimensions.\n </div>\n )}\n <ResponsiveHeatMap\n data={heatmapData}\n margin={{ top: truncated ? 40 : 20, right: 20, bottom: 120, left: 120 }}\n valueFormat={valueFormat ? (v) => formatAxisValue(v, valueFormat) : '>-.2s'}\n axisTop={null}\n axisRight={null}\n axisBottom={{\n tickSize: 5,\n tickPadding: 5,\n tickRotation: -45,\n legend: xAxisFormat?.label || xAxisField?.split('.').pop() || 'X Axis',\n legendPosition: 'middle',\n legendOffset: 70,\n format: xAxisFormat\n ? (v) => {\n const num = parseFloat(String(v))\n return isNaN(num) ? String(v) : formatAxisValue(num, xAxisFormat)\n }\n : undefined,\n }}\n axisLeft={{\n tickSize: 5,\n tickPadding: 5,\n tickRotation: 0,\n legend: yAxisFormat?.label || yAxisField?.split('.').pop() || 'Y Axis',\n legendPosition: 'middle',\n legendOffset: -80,\n format: yAxisFormat\n ? (v) => {\n const num = parseFloat(String(v))\n return isNaN(num) ? String(v) : formatAxisValue(num, yAxisFormat)\n }\n : undefined,\n }}\n colors={\n colors.length >= 2\n // Sequential scale needs [minColor, maxColor] tuple\n // Use first color for low values, last color for high values\n ? { type: 'sequential', colors: [colors[0], colors[colors.length - 1]] as [string, string] }\n : { type: 'sequential', scheme: 'greens' } // Fallback to built-in scheme\n }\n emptyColor=\"var(--dc-surface-tertiary)\"\n cellComponent={cellShape === 'circle' ? 'circle' : 'rect'}\n enableLabels={showLabels}\n labelTextColor={({ color }) => getContrastingTextColor(color)}\n legends={\n showLegend\n ? [\n {\n anchor: 'bottom',\n translateX: 0,\n translateY: 95,\n length: 400,\n thickness: 8,\n direction: 'row',\n tickPosition: 'after',\n tickSize: 3,\n tickSpacing: 4,\n tickOverlap: false,\n title: valueField?.split('.').pop() || 'Value',\n titleAlign: 'start',\n titleOffset: 4,\n },\n ]\n : []\n }\n annotations={[]}\n theme={{\n text: {\n fill: 'var(--dc-text)',\n },\n axis: {\n legend: {\n text: {\n fill: 'var(--dc-text)',\n },\n },\n ticks: {\n text: {\n fill: 'var(--dc-text-secondary)',\n },\n },\n },\n legends: {\n text: {\n fill: 'var(--dc-text-secondary)',\n },\n title: {\n text: {\n fill: 'var(--dc-text)',\n },\n },\n },\n tooltip: {\n container: {\n background: 'var(--dc-surface)',\n color: 'var(--dc-text)',\n borderRadius: '4px',\n boxShadow: '0 1px 2px rgba(0, 0, 0, 0.25)',\n },\n },\n }}\n />\n </div>\n )\n})\n\nexport default HeatMapChart\n"],"mappings":"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"}
1
+ {"version":3,"file":"chart-heat-map-BAMVhLGG.js","names":[],"sources":["../../../src/client/components/charts/HeatMapChart.tsx"],"sourcesContent":["/**\n * HeatMapChart Component\n *\n * Visualizes intensity across two categorical dimensions using a color matrix.\n * Uses @nivo/heatmap for rendering.\n *\n * The chart displays:\n * - Rows: Y-axis dimension values\n * - Columns: X-axis dimension values\n * - Cell color: Intensity based on measure value\n */\n\nimport React, { useMemo } from 'react'\nimport { ResponsiveHeatMap } from '@nivo/heatmap'\nimport { formatTimeValue, getFieldGranularity, formatAxisValue } from '../../utils/chartUtils'\nimport type { AxisFormatConfig } from '../../types'\nimport type { ChartProps } from '../../types'\n\n/**\n * Parse color string (hex or rgb) to RGB values\n */\nfunction parseColor(color: string): { r: number; g: number; b: number } | null {\n // Handle hex colors\n if (color.startsWith('#')) {\n const hex = color.slice(1)\n return {\n r: parseInt(hex.substring(0, 2), 16),\n g: parseInt(hex.substring(2, 4), 16),\n b: parseInt(hex.substring(4, 6), 16),\n }\n }\n\n // Handle rgb/rgba colors\n const rgbMatch = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (rgbMatch) {\n return {\n r: parseInt(rgbMatch[1], 10),\n g: parseInt(rgbMatch[2], 10),\n b: parseInt(rgbMatch[3], 10),\n }\n }\n\n return null\n}\n\n/**\n * Calculate relative luminance of a color\n * Returns value between 0 (black) and 1 (white)\n */\nfunction getLuminance(color: string): number {\n const rgb = parseColor(color)\n if (!rgb) return 0.5 // Default to mid-gray if parsing fails\n\n const r = rgb.r / 255\n const g = rgb.g / 255\n const b = rgb.b / 255\n\n // Apply gamma correction\n const rLinear = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4)\n const gLinear = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4)\n const bLinear = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4)\n\n // Calculate luminance using WCAG formula\n return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear\n}\n\n/**\n * Get contrasting text color (white or dark) based on background color\n */\nfunction getContrastingTextColor(bgColor: string): string {\n const luminance = getLuminance(bgColor)\n // Use white text on dark backgrounds, dark text on light backgrounds\n return luminance < 0.4 ? '#ffffff' : '#1f2937'\n}\n\n/**\n * Maximum dimensions for heatmap to prevent browser lockup\n * 50x50 = 2500 cells max\n */\nconst MAX_HEATMAP_ROWS = 50\nconst MAX_HEATMAP_COLS = 50\n\n/**\n * Nivo heatmap data format\n */\ninterface HeatMapDatum {\n x: string\n y: number | null\n}\n\ninterface HeatMapSerie {\n id: string\n data: HeatMapDatum[]\n}\n\n/**\n * Result of heatmap transformation including truncation info\n */\ninterface HeatMapTransformResult {\n data: HeatMapSerie[]\n truncated: boolean\n originalRows: number\n originalCols: number\n}\n\n/**\n * Transform drizzle-cube flat query results to nivo heatmap format\n *\n * Input (drizzle-cube):\n * [\n * { \"Region.name\": \"East\", \"Product.category\": \"Electronics\", \"Sales.total\": 1500 },\n * { \"Region.name\": \"East\", \"Product.category\": \"Clothing\", \"Sales.total\": 800 },\n * ]\n *\n * Output (nivo format):\n * [\n * { id: \"East\", data: [{ x: \"Electronics\", y: 1500 }, { x: \"Clothing\", y: 800 }] }\n * ]\n *\n * Data is truncated to MAX_HEATMAP_ROWS x MAX_HEATMAP_COLS to prevent browser lockup\n */\nfunction transformToHeatMapFormat(\n data: Record<string, unknown>[],\n xAxisField: string | undefined,\n yAxisField: string | undefined,\n valueField: string | undefined,\n xGranularity?: string,\n yGranularity?: string\n): HeatMapTransformResult {\n if (!xAxisField || !yAxisField || !valueField) {\n return { data: [], truncated: false, originalRows: 0, originalCols: 0 }\n }\n\n // Group data by Y-axis dimension\n const groupedByY = new Map<string, Map<string, number>>()\n const allXValues = new Set<string>()\n // Keep original values for sorting timestamps correctly\n const xValueOriginals = new Map<string, unknown>()\n\n for (const row of data) {\n const rawYValue = row[yAxisField]\n const rawXValue = row[xAxisField]\n\n // Format time values based on granularity\n const yValue = formatTimeValue(rawYValue, yGranularity) || String(rawYValue ?? '(empty)')\n const xValue = formatTimeValue(rawXValue, xGranularity) || String(rawXValue ?? '(empty)')\n const value = Number(row[valueField]) || 0\n\n allXValues.add(xValue)\n // Store original for sorting\n if (!xValueOriginals.has(xValue)) {\n xValueOriginals.set(xValue, rawXValue)\n }\n\n if (!groupedByY.has(yValue)) {\n groupedByY.set(yValue, new Map())\n }\n groupedByY.get(yValue)!.set(xValue, value)\n }\n\n // Sort X values - try to sort by original timestamp if available\n const xValueArray = Array.from(allXValues).sort((a, b) => {\n const origA = xValueOriginals.get(a)\n const origB = xValueOriginals.get(b)\n // If both are date strings, sort chronologically\n if (typeof origA === 'string' && typeof origB === 'string' &&\n origA.match(/^\\d{4}-\\d{2}-\\d{2}/) && origB.match(/^\\d{4}-\\d{2}-\\d{2}/)) {\n return origA.localeCompare(origB)\n }\n // Otherwise sort alphabetically by formatted value\n return a.localeCompare(b)\n })\n\n // Track original dimensions for truncation warning\n const originalRows = groupedByY.size\n const originalCols = xValueArray.length\n const truncated = originalRows > MAX_HEATMAP_ROWS || originalCols > MAX_HEATMAP_COLS\n\n // Truncate X values if needed\n const limitedXValues = xValueArray.slice(0, MAX_HEATMAP_COLS)\n\n // Build result with truncation\n const result: HeatMapSerie[] = []\n let rowCount = 0\n for (const [yValue, xMap] of groupedByY) {\n if (rowCount >= MAX_HEATMAP_ROWS) break\n result.push({\n id: yValue,\n data: limitedXValues.map((x) => ({\n x,\n y: xMap.get(x) ?? null,\n })),\n })\n rowCount++\n }\n\n return { data: result, truncated, originalRows, originalCols }\n}\n\n/**\n * HeatMapChart Component\n *\n * Renders a heatmap visualization from query results.\n * Shows intensity patterns across two categorical dimensions.\n */\nconst HeatMapChart = React.memo(function HeatMapChart({\n data,\n height = '100%',\n chartConfig,\n colorPalette,\n displayConfig,\n queryObject,\n}: ChartProps) {\n // Get display config options\n const displayConfigAny = displayConfig as Record<string, unknown> | undefined\n const showLabels = (displayConfigAny?.showLabels as boolean) ?? false\n const cellShape = (displayConfigAny?.cellShape as 'rect' | 'circle') ?? 'rect'\n const showLegend = (displayConfigAny?.showLegend as boolean) ?? true\n const xAxisFormat = displayConfigAny?.xAxisFormat as AxisFormatConfig | undefined\n const yAxisFormat = displayConfigAny?.yAxisFormat as AxisFormatConfig | undefined\n const valueFormat = displayConfigAny?.valueFormat as AxisFormatConfig | undefined\n\n // Extract field names from chartConfig (handle both array and string formats)\n const xAxisField = chartConfig?.xAxis\n ? (Array.isArray(chartConfig.xAxis) ? chartConfig.xAxis[0] : chartConfig.xAxis)\n : undefined\n const yAxisField = chartConfig?.yAxis\n ? (Array.isArray(chartConfig.yAxis) ? chartConfig.yAxis[0] : chartConfig.yAxis)\n : undefined\n const valueField = chartConfig?.valueField\n ? (Array.isArray(chartConfig.valueField) ? chartConfig.valueField[0] : chartConfig.valueField)\n : undefined\n\n // Get granularity for time dimensions (only if field is defined)\n const xGranularity = xAxisField ? getFieldGranularity(queryObject, xAxisField) : undefined\n const yGranularity = yAxisField ? getFieldGranularity(queryObject, yAxisField) : undefined\n\n // Transform data to nivo format\n const { data: heatmapData, truncated, originalRows, originalCols } = useMemo(() => {\n if (!data || data.length === 0) {\n return { data: [], truncated: false, originalRows: 0, originalCols: 0 }\n }\n return transformToHeatMapFormat(\n data as Record<string, unknown>[],\n xAxisField,\n yAxisField,\n valueField,\n xGranularity,\n yGranularity\n )\n }, [data, xAxisField, yAxisField, valueField, xGranularity, yGranularity])\n\n // Handle no data or missing config\n if (!data || data.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n Run a query to see heatmap visualization\n </div>\n </div>\n </div>\n )\n }\n\n if (!xAxisField || !yAxisField || !valueField) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">Configuration required</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n {!xAxisField && 'X-axis dimension required. '}\n {!yAxisField && 'Y-axis dimension required. '}\n {!valueField && 'Value measure required.'}\n </div>\n </div>\n </div>\n )\n }\n\n if (heatmapData.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">No data to display</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n The query returned no results for the heatmap\n </div>\n </div>\n </div>\n )\n }\n\n // Use gradient colors from palette, or default sequential blue gradient\n // Sequential single-hue gradients are ideal for heatmaps showing magnitude/intensity\n const colors = colorPalette?.gradient || [\n '#eff3ff', // lightest blue\n '#c6dbef',\n '#9ecae1',\n '#6baed6',\n '#3182bd',\n '#08519c', // darkest blue\n ]\n\n return (\n <div className=\"dc:relative dc:w-full dc:h-full\" style={{ height }}>\n {truncated && (\n <div className=\"dc:absolute dc:top-0 dc:left-0 dc:right-0 dc:z-10 dc:px-3 dc:py-1.5 dc:text-xs bg-dc-warning-bg text-dc-warning dc:border-b border-dc-border\">\n Data truncated to {MAX_HEATMAP_ROWS}x{MAX_HEATMAP_COLS} cells (original: {originalRows}x{originalCols}). Add filters to reduce dimensions.\n </div>\n )}\n <ResponsiveHeatMap\n data={heatmapData}\n margin={{ top: truncated ? 40 : 20, right: 20, bottom: 120, left: 120 }}\n valueFormat={valueFormat ? (v) => formatAxisValue(v, valueFormat) : '>-.2s'}\n axisTop={null}\n axisRight={null}\n axisBottom={{\n tickSize: 5,\n tickPadding: 5,\n tickRotation: -45,\n legend: xAxisFormat?.label || xAxisField?.split('.').pop() || 'X Axis',\n legendPosition: 'middle',\n legendOffset: 70,\n format: xAxisFormat\n ? (v) => {\n const num = parseFloat(String(v))\n return isNaN(num) ? String(v) : formatAxisValue(num, xAxisFormat)\n }\n : undefined,\n }}\n axisLeft={{\n tickSize: 5,\n tickPadding: 5,\n tickRotation: 0,\n legend: yAxisFormat?.label || yAxisField?.split('.').pop() || 'Y Axis',\n legendPosition: 'middle',\n legendOffset: -80,\n format: yAxisFormat\n ? (v) => {\n const num = parseFloat(String(v))\n return isNaN(num) ? String(v) : formatAxisValue(num, yAxisFormat)\n }\n : undefined,\n }}\n colors={\n colors.length >= 2\n // Sequential scale needs [minColor, maxColor] tuple\n // Use first color for low values, last color for high values\n ? { type: 'sequential', colors: [colors[0], colors[colors.length - 1]] as [string, string] }\n : { type: 'sequential', scheme: 'greens' } // Fallback to built-in scheme\n }\n emptyColor=\"var(--dc-surface-tertiary)\"\n cellComponent={cellShape === 'circle' ? 'circle' : 'rect'}\n enableLabels={showLabels}\n labelTextColor={({ color }) => getContrastingTextColor(color)}\n legends={\n showLegend\n ? [\n {\n anchor: 'bottom',\n translateX: 0,\n translateY: 95,\n length: 400,\n thickness: 8,\n direction: 'row',\n tickPosition: 'after',\n tickSize: 3,\n tickSpacing: 4,\n tickOverlap: false,\n title: valueField?.split('.').pop() || 'Value',\n titleAlign: 'start',\n titleOffset: 4,\n },\n ]\n : []\n }\n annotations={[]}\n theme={{\n text: {\n fill: 'var(--dc-text)',\n },\n axis: {\n legend: {\n text: {\n fill: 'var(--dc-text)',\n },\n },\n ticks: {\n text: {\n fill: 'var(--dc-text-secondary)',\n },\n },\n },\n legends: {\n text: {\n fill: 'var(--dc-text-secondary)',\n },\n title: {\n text: {\n fill: 'var(--dc-text)',\n },\n },\n },\n tooltip: {\n container: {\n background: 'var(--dc-surface)',\n color: 'var(--dc-text)',\n borderRadius: '4px',\n boxShadow: '0 1px 2px rgba(0, 0, 0, 0.25)',\n },\n },\n }}\n />\n </div>\n )\n})\n\nexport default HeatMapChart\n"],"mappings":"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"}
@@ -1,10 +1,9 @@
1
- import { n as e } from "./rolldown-runtime-lc2dmIiU.js";
2
- import { B as t, z as n } from "./chart-data-table-D4s27-U3.js";
3
- import { _ as r } from "./chart-activity-grid-wR2Twpo7.js";
4
- import i, { useEffect as a, useRef as o, useState as s } from "react";
5
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
1
+ import { B as e, z as t } from "./chart-data-table-2iCsn0CF.js";
2
+ import { _ as n } from "./chart-activity-grid-DLktOINm.js";
3
+ import r, { useEffect as i, useRef as a, useState as o } from "react";
4
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
6
5
  //#region src/client/utils/periodUtils.ts
7
- function u(e, t) {
6
+ function l(e, t) {
8
7
  let n = new Date(e);
9
8
  switch (t.toLowerCase()) {
10
9
  case "day":
@@ -30,14 +29,14 @@ function u(e, t) {
30
29
  }
31
30
  return n;
32
31
  }
33
- function d(e, t, n) {
32
+ function u(e, t, n) {
34
33
  if (!e || !t || !n) return !0;
35
34
  let r = e[t];
36
35
  if (!r) return !0;
37
36
  let i = new Date(r);
38
- return isNaN(i.getTime()) ? !0 : u(i, n) <= /* @__PURE__ */ new Date();
37
+ return isNaN(i.getTime()) ? !0 : l(i, n) <= /* @__PURE__ */ new Date();
39
38
  }
40
- function f(e, t) {
39
+ function d(e, t) {
41
40
  if (!e?.timeDimensions || e.timeDimensions.length === 0) return null;
42
41
  if (t) {
43
42
  let n = e.timeDimensions.find((e) => e.dimension === t || e.dimension?.includes(t) || t?.includes(e.dimension));
@@ -45,7 +44,7 @@ function f(e, t) {
45
44
  }
46
45
  return e.timeDimensions[0]?.granularity || null;
47
46
  }
48
- function p(e, t, n, r, i = !1) {
47
+ function f(e, t, n, r, i = !1) {
49
48
  let a = {
50
49
  filteredData: e,
51
50
  excludedIncompletePeriod: !1,
@@ -53,7 +52,7 @@ function p(e, t, n, r, i = !1) {
53
52
  granularity: null
54
53
  };
55
54
  if (e.length < 2) return a;
56
- let o = f(n, t);
55
+ let o = d(n, t);
57
56
  if (i) return {
58
57
  filteredData: e.slice(0, -1),
59
58
  excludedIncompletePeriod: !1,
@@ -66,7 +65,7 @@ function p(e, t, n, r, i = !1) {
66
65
  };
67
66
  if (!o) return a;
68
67
  let s = e[e.length - 1];
69
- return d(s, t, o) ? {
68
+ return u(s, t, o) ? {
70
69
  ...a,
71
70
  granularity: o
72
71
  } : {
@@ -78,30 +77,29 @@ function p(e, t, n, r, i = !1) {
78
77
  }
79
78
  //#endregion
80
79
  //#region src/client/components/charts/KpiDelta.tsx
81
- var m = /* @__PURE__ */ e({ default: () => g });
82
- function h({ values: e, lastValue: t, positiveColor: n, negativeColor: r, formatValue: i, width: a, height: o }) {
83
- let s = Math.max(10, Math.floor(a / 10)), u = e.length > s ? e.slice(-s) : e, d = u.map((e) => e - t), f = Math.min(...d, 0), p = Math.max(...d, 0);
84
- if (Math.max(Math.abs(f), Math.abs(p)) === 0 || d.length === 0) return /* @__PURE__ */ c("div", {
80
+ function p({ values: e, lastValue: t, positiveColor: n, negativeColor: r, formatValue: i, width: a, height: o }) {
81
+ let l = Math.max(10, Math.floor(a / 10)), u = e.length > l ? e.slice(-l) : e, d = u.map((e) => e - t), f = Math.min(...d, 0), p = Math.max(...d, 0);
82
+ if (Math.max(Math.abs(f), Math.abs(p)) === 0 || d.length === 0) return /* @__PURE__ */ s("div", {
85
83
  className: "dc:flex dc:items-center dc:justify-center bg-dc-bg-secondary dc:rounded-sm dc:border border-dc-border",
86
84
  style: {
87
85
  width: `${a}px`,
88
86
  height: `${o}px`
89
87
  },
90
- children: /* @__PURE__ */ c("span", {
88
+ children: /* @__PURE__ */ s("span", {
91
89
  className: "dc:text-xs text-dc-text-muted",
92
90
  children: "No variance data"
93
91
  })
94
92
  });
95
93
  let m = a - (u.length - 1) * 2, h = Math.max(4, m / u.length), g = p - f, _ = g > 0 ? p / g * 100 : 50;
96
- return /* @__PURE__ */ l("div", {
94
+ return /* @__PURE__ */ c("div", {
97
95
  className: "dc:flex dc:items-center dc:space-x-2",
98
- children: [/* @__PURE__ */ l("div", {
96
+ children: [/* @__PURE__ */ c("div", {
99
97
  className: "dc:relative",
100
98
  style: {
101
99
  width: `${a}px`,
102
100
  height: `${o}px`
103
101
  },
104
- children: [/* @__PURE__ */ c("div", {
102
+ children: [/* @__PURE__ */ s("div", {
105
103
  className: "dc:absolute dc:left-0 dc:right-0",
106
104
  style: {
107
105
  height: "1px",
@@ -110,13 +108,13 @@ function h({ values: e, lastValue: t, positiveColor: n, negativeColor: r, format
110
108
  zIndex: 1
111
109
  }
112
110
  }), d.map((e, t) => {
113
- let a = Math.abs(e) / g, s = Math.max(2, a * (o - 4)), l = e >= 0, d = t === u.length - 1, f = l ? n : r;
114
- return /* @__PURE__ */ c("div", {
111
+ let a = Math.abs(e) / g, c = Math.max(2, a * (o - 4)), l = e >= 0, d = t === u.length - 1, f = l ? n : r;
112
+ return /* @__PURE__ */ s("div", {
115
113
  className: "dc:absolute rounded-xs",
116
114
  style: {
117
115
  left: `${t * (h + 2)}px`,
118
116
  width: `${h}px`,
119
- height: `${s}px`,
117
+ height: `${c}px`,
120
118
  backgroundColor: f,
121
119
  opacity: d ? 1 : .6,
122
120
  ...l ? { bottom: `${100 - _}%` } : { top: `${_}%` },
@@ -125,16 +123,16 @@ function h({ values: e, lastValue: t, positiveColor: n, negativeColor: r, format
125
123
  title: `${i(u[t])}: ${e >= 0 ? "+" : ""}${i(e)} vs current`
126
124
  }, t);
127
125
  })]
128
- }), /* @__PURE__ */ l("div", {
126
+ }), /* @__PURE__ */ c("div", {
129
127
  className: "dc:flex dc:flex-col dc:justify-between dc:text-xs text-dc-text-muted",
130
128
  style: { height: `${o}px` },
131
- children: [/* @__PURE__ */ l("span", { children: ["+", i(p)] }), /* @__PURE__ */ l("span", { children: ["", i(f)] })]
129
+ children: [/* @__PURE__ */ c("span", { children: ["+", i(p)] }), /* @__PURE__ */ c("span", { children: ["", i(f)] })]
132
130
  })]
133
131
  });
134
132
  }
135
- var g = i.memo(function({ data: e, chartConfig: i, displayConfig: u = {}, queryObject: d, height: f = "100%", colorPalette: m }) {
136
- let [g, _] = s(32), [v, y] = s(250), b = o(null), x = o(null), S = r();
137
- if (a(() => {
133
+ var m = r.memo(function({ data: r, chartConfig: l, displayConfig: u = {}, queryObject: d, height: m = "100%", colorPalette: h }) {
134
+ let [g, _] = o(32), [v, y] = o(250), b = a(null), x = a(null), S = n();
135
+ if (i(() => {
138
136
  let e = () => {
139
137
  if (b.current) {
140
138
  let e = b.current.getBoundingClientRect(), t = e.width, n = e.height;
@@ -154,71 +152,71 @@ var g = i.memo(function({ data: e, chartConfig: i, displayConfig: u = {}, queryO
154
152
  return b.current && n.observe(b.current), () => {
155
153
  clearTimeout(t), n.disconnect();
156
154
  };
157
- }, [e, i]), !e || e.length === 0) return /* @__PURE__ */ c("div", {
155
+ }, [r, l]), !r || r.length === 0) return /* @__PURE__ */ s("div", {
158
156
  className: "dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full",
159
157
  style: {
160
- height: f === "100%" ? "100%" : f,
161
- minHeight: f === "100%" ? "200px" : void 0
158
+ height: m === "100%" ? "100%" : m,
159
+ minHeight: m === "100%" ? "200px" : void 0
162
160
  },
163
- children: /* @__PURE__ */ l("div", {
161
+ children: /* @__PURE__ */ c("div", {
164
162
  className: "dc:text-center text-dc-text-muted",
165
- children: [/* @__PURE__ */ c("div", {
163
+ children: [/* @__PURE__ */ s("div", {
166
164
  className: "dc:text-sm dc:font-semibold dc:mb-1",
167
165
  children: "No data available"
168
- }), /* @__PURE__ */ c("div", {
166
+ }), /* @__PURE__ */ s("div", {
169
167
  className: "dc:text-xs text-dc-text-secondary",
170
168
  children: "No data points to display"
171
169
  })]
172
170
  })
173
171
  });
174
172
  let C = [], w = [];
175
- if (i?.yAxis && (C = Array.isArray(i.yAxis) ? i.yAxis : [i.yAxis]), i?.xAxis && (w = Array.isArray(i.xAxis) ? i.xAxis : [i.xAxis]), C.length === 0) return /* @__PURE__ */ c("div", {
173
+ if (l?.yAxis && (C = Array.isArray(l.yAxis) ? l.yAxis : [l.yAxis]), l?.xAxis && (w = Array.isArray(l.xAxis) ? l.xAxis : [l.xAxis]), C.length === 0) return /* @__PURE__ */ s("div", {
176
174
  className: "dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full",
177
175
  style: {
178
- height: f === "100%" ? "100%" : f,
179
- minHeight: f === "100%" ? "200px" : void 0,
176
+ height: m === "100%" ? "100%" : m,
177
+ minHeight: m === "100%" ? "200px" : void 0,
180
178
  backgroundColor: "var(--dc-danger-bg)",
181
179
  color: "var(--dc-danger)",
182
180
  borderColor: "var(--dc-danger-border)"
183
181
  },
184
- children: /* @__PURE__ */ l("div", {
182
+ children: /* @__PURE__ */ c("div", {
185
183
  className: "dc:text-center",
186
- children: [/* @__PURE__ */ c("div", {
184
+ children: [/* @__PURE__ */ s("div", {
187
185
  className: "dc:text-sm dc:font-semibold dc:mb-1",
188
186
  children: "Configuration Error"
189
- }), /* @__PURE__ */ c("div", {
187
+ }), /* @__PURE__ */ s("div", {
190
188
  className: "dc:text-xs",
191
189
  children: "No measure field configured"
192
190
  })]
193
191
  })
194
192
  });
195
- let T = C[0], E = w[0], D = [...e];
193
+ let T = C[0], E = w[0], D = [...r];
196
194
  E && (D = D.sort((e, t) => {
197
195
  let n = e[E], r = t[E];
198
196
  return n < r ? -1 : n > r ? 1 : 0;
199
197
  }));
200
- let { useLastCompletePeriod: O = !0, skipLastPeriod: k = !1 } = u, { filteredData: A, excludedIncompletePeriod: j, skippedLastPeriod: M, granularity: N } = p(D, E, d, O, k), P = A.map((e) => e[T]).filter((e) => e != null && !isNaN(Number(e))).map((e) => Number(e));
201
- if (P.length < 2) return /* @__PURE__ */ c("div", {
198
+ let { useLastCompletePeriod: O = !0, skipLastPeriod: k = !1 } = u, { filteredData: A, excludedIncompletePeriod: j, skippedLastPeriod: M, granularity: N } = f(D, E, d, O, k), P = A.map((e) => e[T]).filter((e) => e != null && !isNaN(Number(e))).map((e) => Number(e));
199
+ if (P.length < 2) return /* @__PURE__ */ s("div", {
202
200
  className: "dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full",
203
201
  style: {
204
- height: f === "100%" ? "100%" : f,
205
- minHeight: f === "100%" ? "200px" : void 0,
202
+ height: m === "100%" ? "100%" : m,
203
+ minHeight: m === "100%" ? "200px" : void 0,
206
204
  backgroundColor: "var(--dc-warning-bg)",
207
205
  color: "var(--dc-warning)",
208
206
  borderColor: "var(--dc-warning-border)"
209
207
  },
210
- children: /* @__PURE__ */ l("div", {
208
+ children: /* @__PURE__ */ c("div", {
211
209
  className: "dc:text-center",
212
210
  children: [
213
- /* @__PURE__ */ c("div", {
211
+ /* @__PURE__ */ s("div", {
214
212
  className: "dc:text-sm dc:font-semibold dc:mb-1",
215
213
  children: "Insufficient Data"
216
214
  }),
217
- /* @__PURE__ */ c("div", {
215
+ /* @__PURE__ */ s("div", {
218
216
  className: "dc:text-xs",
219
217
  children: "Delta calculation requires at least 2 data points"
220
218
  }),
221
- /* @__PURE__ */ l("div", {
219
+ /* @__PURE__ */ c("div", {
222
220
  className: "dc:text-xs",
223
221
  children: ["Current data points: ", P.length]
224
222
  })
@@ -231,47 +229,47 @@ var g = i.memo(function({ data: e, chartConfig: i, displayConfig: u = {}, queryO
231
229
  let t = u.decimals ?? 0, n = u.prefix ?? "", r;
232
230
  return r = Math.abs(e) >= 1e9 ? (e / 1e9).toFixed(t) + "B" : Math.abs(e) >= 1e6 ? (e / 1e6).toFixed(t) + "M" : Math.abs(e) >= 1e3 ? (e / 1e3).toFixed(t) + "K" : e.toFixed(t), n + r;
233
231
  }, V = () => {
234
- if (u.positiveColorIndex !== void 0 && m?.colors) {
232
+ if (u.positiveColorIndex !== void 0 && h?.colors) {
235
233
  let e = u.positiveColorIndex;
236
- if (e >= 0 && e < m.colors.length) return m.colors[e];
234
+ if (e >= 0 && e < h.colors.length) return h.colors[e];
237
235
  }
238
236
  return "#10b981";
239
237
  }, H = () => {
240
- if (u.negativeColorIndex !== void 0 && m?.colors) {
238
+ if (u.negativeColorIndex !== void 0 && h?.colors) {
241
239
  let e = u.negativeColorIndex;
242
- if (e >= 0 && e < m.colors.length) return m.colors[e];
240
+ if (e >= 0 && e < h.colors.length) return h.colors[e];
243
241
  }
244
242
  return "#ef4444";
245
243
  }, U = V(), W = H(), G = z ? U : W;
246
- return /* @__PURE__ */ l("div", {
244
+ return /* @__PURE__ */ c("div", {
247
245
  ref: b,
248
246
  className: "dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4",
249
247
  style: {
250
- height: f === "100%" ? "100%" : f,
251
- minHeight: f === "100%" ? "200px" : void 0
248
+ height: m === "100%" ? "100%" : m,
249
+ minHeight: m === "100%" ? "200px" : void 0
252
250
  },
253
251
  children: [
254
- /* @__PURE__ */ l("div", {
252
+ /* @__PURE__ */ c("div", {
255
253
  className: "text-dc-text-secondary dc:font-bold dc:text-center dc:mb-2 dc:flex dc:items-center dc:justify-center dc:gap-1",
256
254
  style: {
257
255
  fontSize: "14px",
258
256
  lineHeight: "1.2"
259
257
  },
260
- children: [/* @__PURE__ */ c("span", { children: (() => {
258
+ children: [/* @__PURE__ */ s("span", { children: (() => {
261
259
  let e = S(T);
262
260
  return e && e.length > 1 ? e : T;
263
- })() }), (j || M) && /* @__PURE__ */ c("span", {
261
+ })() }), (j || M) && /* @__PURE__ */ s("span", {
264
262
  title: M ? `Excludes last ${N || "period"}` : `Excludes current incomplete ${N}`,
265
263
  className: "dc:cursor-help",
266
- children: /* @__PURE__ */ c(t, {
267
- icon: n,
264
+ children: /* @__PURE__ */ s(e, {
265
+ icon: t,
268
266
  className: "dc:w-4 dc:h-4 text-dc-text-muted dc:opacity-70"
269
267
  })
270
268
  })]
271
269
  }),
272
- /* @__PURE__ */ l("div", {
270
+ /* @__PURE__ */ c("div", {
273
271
  className: "dc:flex dc:items-center dc:justify-center dc:space-x-4 dc:mb-2",
274
- children: [/* @__PURE__ */ c("div", {
272
+ children: [/* @__PURE__ */ s("div", {
275
273
  ref: x,
276
274
  className: "dc:font-bold dc:leading-none",
277
275
  style: {
@@ -279,25 +277,25 @@ var g = i.memo(function({ data: e, chartConfig: i, displayConfig: u = {}, queryO
279
277
  color: "var(--dc-text)"
280
278
  },
281
279
  children: B(F)
282
- }), /* @__PURE__ */ l("div", {
280
+ }), /* @__PURE__ */ c("div", {
283
281
  className: "dc:flex dc:items-center dc:space-x-1",
284
- children: [/* @__PURE__ */ c("div", {
282
+ children: [/* @__PURE__ */ s("div", {
285
283
  className: "dc:font-bold",
286
284
  style: {
287
285
  color: G,
288
286
  fontSize: `${g * .35}px`
289
287
  },
290
288
  children: z ? "▲" : "▼"
291
- }), /* @__PURE__ */ l("div", {
289
+ }), /* @__PURE__ */ c("div", {
292
290
  className: "dc:text-left",
293
- children: [/* @__PURE__ */ l("div", {
291
+ children: [/* @__PURE__ */ c("div", {
294
292
  className: "dc:font-bold dc:leading-tight",
295
293
  style: {
296
294
  fontSize: `${g * .35}px`,
297
295
  color: G
298
296
  },
299
297
  children: [z ? "+" : "", B(L)]
300
- }), /* @__PURE__ */ l("div", {
298
+ }), /* @__PURE__ */ c("div", {
301
299
  className: "dc:font-semibold dc:leading-tight",
302
300
  style: {
303
301
  fontSize: `${g * .28}px`,
@@ -313,7 +311,7 @@ var g = i.memo(function({ data: e, chartConfig: i, displayConfig: u = {}, queryO
313
311
  })]
314
312
  })]
315
313
  }),
316
- u.suffix && !u.formatValue && /* @__PURE__ */ c("div", {
314
+ u.suffix && !u.formatValue && /* @__PURE__ */ s("div", {
317
315
  className: "text-dc-text-muted dc:text-center dc:mb-3",
318
316
  style: {
319
317
  fontSize: "14px",
@@ -322,9 +320,9 @@ var g = i.memo(function({ data: e, chartConfig: i, displayConfig: u = {}, queryO
322
320
  },
323
321
  children: u.suffix
324
322
  }),
325
- u.showHistogram !== !1 && P.length > 2 && /* @__PURE__ */ c("div", {
323
+ u.showHistogram !== !1 && P.length > 2 && /* @__PURE__ */ s("div", {
326
324
  className: "dc:mt-2 dc:w-full dc:flex dc:justify-center dc:overflow-hidden",
327
- children: /* @__PURE__ */ c(h, {
325
+ children: /* @__PURE__ */ s(p, {
328
326
  values: P,
329
327
  lastValue: F,
330
328
  positiveColor: U,
@@ -338,6 +336,6 @@ var g = i.memo(function({ data: e, chartConfig: i, displayConfig: u = {}, queryO
338
336
  });
339
337
  });
340
338
  //#endregion
341
- export { d as a, f as i, p as n, u as r, m as t };
339
+ export { u as a, d as i, f as n, l as r, m as t };
342
340
 
343
- //# sourceMappingURL=chart-kpi-delta-DzGNnIcW.js.map
341
+ //# sourceMappingURL=chart-kpi-delta-KQjUIeal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chart-kpi-delta-DzGNnIcW.js","names":[],"sources":["../../../src/client/utils/periodUtils.ts","../../../src/client/components/charts/KpiDelta.tsx"],"sourcesContent":["/**\n * Utility functions for detecting incomplete time periods in KPI charts\n */\n\n/**\n * Get the end date of a period based on granularity\n * @param date - The date within the period\n * @param granularity - The time granularity (day, week, month, quarter, year)\n * @returns The end date of the period (end of day)\n */\nexport function getPeriodEndDate(date: Date, granularity: string): Date {\n const endDate = new Date(date)\n\n switch (granularity.toLowerCase()) {\n case 'day':\n // End of the same day\n endDate.setHours(23, 59, 59, 999)\n break\n\n case 'week': {\n // End of the week (Saturday, assuming week starts Sunday)\n const dayOfWeek = endDate.getDay()\n const daysUntilSaturday = 6 - dayOfWeek\n endDate.setDate(endDate.getDate() + daysUntilSaturday)\n endDate.setHours(23, 59, 59, 999)\n break\n }\n\n case 'month':\n // Last day of the month\n endDate.setMonth(endDate.getMonth() + 1, 0) // Set to day 0 of next month = last day of current month\n endDate.setHours(23, 59, 59, 999)\n break\n\n case 'quarter': {\n // Last day of the quarter\n const currentMonth = endDate.getMonth()\n const quarterEndMonth = Math.floor(currentMonth / 3) * 3 + 2 // 0->2, 3->5, 6->8, 9->11\n endDate.setMonth(quarterEndMonth + 1, 0) // Last day of quarter end month\n endDate.setHours(23, 59, 59, 999)\n break\n }\n\n case 'year':\n // December 31st\n endDate.setMonth(11, 31) // December 31\n endDate.setHours(23, 59, 59, 999)\n break\n\n default:\n // Unknown granularity - treat as complete\n endDate.setHours(23, 59, 59, 999)\n }\n\n return endDate\n}\n\n/**\n * Check if the last period in the data is complete based on granularity\n * @param lastDataPoint - The last data point in the sorted dataset\n * @param timeDimensionField - The field name containing the time value\n * @param granularity - The time granularity\n * @returns true if the period is complete, false if it's incomplete\n */\nexport function isLastPeriodComplete(\n lastDataPoint: any,\n timeDimensionField: string,\n granularity: string\n): boolean {\n if (!lastDataPoint || !timeDimensionField || !granularity) {\n return true // Assume complete if we can't determine\n }\n\n const timeValue = lastDataPoint[timeDimensionField]\n if (!timeValue) {\n return true // Assume complete if no time value\n }\n\n // Parse the time value\n const date = new Date(timeValue)\n if (isNaN(date.getTime())) {\n return true // Assume complete if invalid date\n }\n\n // Get the end of this period\n const periodEnd = getPeriodEndDate(date, granularity)\n\n // Compare with current time\n const now = new Date()\n\n // If the period end is in the future, the period is incomplete\n return periodEnd <= now\n}\n\n/**\n * Extract granularity from a query object\n * @param queryObject - The CubeQuery object\n * @param dimensionField - Optional specific dimension field to match\n * @returns The granularity string or null if not found\n */\nexport function getQueryGranularity(\n queryObject: any,\n dimensionField?: string\n): string | null {\n if (!queryObject?.timeDimensions || queryObject.timeDimensions.length === 0) {\n return null\n }\n\n // If a specific dimension field is provided, try to find its granularity\n if (dimensionField) {\n const matchingDim = queryObject.timeDimensions.find((td: any) =>\n td.dimension === dimensionField ||\n td.dimension?.includes(dimensionField) ||\n dimensionField?.includes(td.dimension)\n )\n if (matchingDim?.granularity) {\n return matchingDim.granularity\n }\n }\n\n // Fallback to first time dimension's granularity\n const firstTimeDim = queryObject.timeDimensions[0]\n return firstTimeDim?.granularity || null\n}\n\n/**\n * Filter data to exclude incomplete or last period\n * @param data - The data array sorted by time\n * @param timeDimensionField - The field containing time values\n * @param queryObject - The query object containing timeDimensions\n * @param useLastCompletePeriod - Whether to check for incomplete periods\n * @param skipLastPeriod - Whether to always skip the last period (overrides useLastCompletePeriod)\n * @returns Object with filtered data and whether filtering was applied\n */\nexport function filterIncompletePeriod(\n data: any[],\n timeDimensionField: string | undefined,\n queryObject: any,\n useLastCompletePeriod: boolean,\n skipLastPeriod: boolean = false\n): { filteredData: any[]; excludedIncompletePeriod: boolean; skippedLastPeriod: boolean; granularity: string | null } {\n // Default return - no filtering\n const noFilter = {\n filteredData: data,\n excludedIncompletePeriod: false,\n skippedLastPeriod: false,\n granularity: null\n }\n\n // Need at least 2 data points to filter\n if (data.length < 2) {\n return noFilter\n }\n\n const granularity = getQueryGranularity(queryObject, timeDimensionField)\n\n // If skipLastPeriod is enabled, always skip the last period\n if (skipLastPeriod) {\n return {\n filteredData: data.slice(0, -1),\n excludedIncompletePeriod: false,\n skippedLastPeriod: true,\n granularity\n }\n }\n\n // Skip incomplete period check if feature is disabled\n if (!useLastCompletePeriod) {\n return { ...noFilter, granularity }\n }\n\n // Skip if no time dimension field\n if (!timeDimensionField) {\n return { ...noFilter, granularity }\n }\n\n // Skip if no time dimensions in query\n if (!queryObject?.timeDimensions || queryObject.timeDimensions.length === 0) {\n return { ...noFilter, granularity }\n }\n\n if (!granularity) {\n return noFilter\n }\n\n // Check if last period is incomplete\n const lastRow = data[data.length - 1]\n if (!isLastPeriodComplete(lastRow, timeDimensionField, granularity)) {\n return {\n filteredData: data.slice(0, -1),\n excludedIncompletePeriod: true,\n skippedLastPeriod: false,\n granularity\n }\n }\n\n return { ...noFilter, granularity }\n}\n","import React, { useState, useRef, useEffect } from \"react\";\nimport { Icon } from \"@iconify/react\";\nimport infoCircleIcon from \"@iconify-icons/tabler/info-circle\";\nimport { useCubeFieldLabel } from \"../../hooks/useCubeFieldLabel\";\nimport { filterIncompletePeriod } from \"../../utils/periodUtils\";\nimport type { ChartProps } from \"../../types\";\n\ninterface VarianceHistogramProps {\n values: number[];\n lastValue: number;\n positiveColor: string;\n negativeColor: string;\n formatValue: (value: number) => string;\n width: number;\n height: number;\n}\n\nfunction VarianceHistogram({\n values,\n lastValue,\n positiveColor,\n negativeColor,\n formatValue,\n width,\n height,\n}: VarianceHistogramProps) {\n // Limit to most recent N values to fit in the histogram\n // Calculate max bars based on width (minimum 8px per bar including gap)\n const maxBars = Math.max(10, Math.floor(width / 10));\n const limitedValues =\n values.length > maxBars\n ? values.slice(-maxBars) // Take the most recent values\n : values;\n\n // Calculate variance (difference) from current/last value for each point\n const variances = limitedValues.map((value) => value - lastValue);\n\n // Find min/max variance for scaling (include 0 as baseline)\n const minVariance = Math.min(...variances, 0);\n const maxVariance = Math.max(...variances, 0);\n const range = Math.max(Math.abs(minVariance), Math.abs(maxVariance));\n\n if (range === 0 || variances.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center bg-dc-bg-secondary dc:rounded-sm dc:border border-dc-border\"\n style={{ width: `${width}px`, height: `${height}px` }}\n >\n <span className=\"dc:text-xs text-dc-text-muted\">No variance data</span>\n </div>\n );\n }\n\n // Calculate bar dimensions\n const barGap = 2;\n const availableWidth = width - (limitedValues.length - 1) * barGap;\n const barWidth = Math.max(4, availableWidth / limitedValues.length);\n\n // Calculate where zero line should be positioned (as percentage from top)\n // If maxVariance = 67 and minVariance = -24, total range = 91\n // Zero should be at 67/91 = 73.6% from top\n const totalRange = maxVariance - minVariance;\n const zeroLinePercent =\n totalRange > 0 ? (maxVariance / totalRange) * 100 : 50;\n\n return (\n <div className=\"dc:flex dc:items-center dc:space-x-2\">\n {/* Histogram bars */}\n <div\n className=\"dc:relative\"\n style={{\n width: `${width}px`,\n height: `${height}px`,\n }}\n >\n {/* Zero line (represents current value) */}\n <div\n className=\"dc:absolute dc:left-0 dc:right-0\"\n style={{\n height: \"1px\",\n top: `${zeroLinePercent}%`,\n backgroundColor: \"var(--dc-border)\",\n zIndex: 1,\n }}\n />\n\n {/* Variance bars - oldest to newest (left to right) */}\n {variances.map((variance, index) => {\n // Calculate bar height as proportion of total range\n const normalizedHeight = Math.abs(variance) / totalRange;\n const barHeight = Math.max(2, normalizedHeight * (height - 4));\n const isPositive = variance >= 0;\n const isLastValue = index === limitedValues.length - 1;\n const color = isPositive ? positiveColor : negativeColor;\n const xPosition = index * (barWidth + barGap);\n\n return (\n <div\n key={index}\n className=\"dc:absolute rounded-xs\"\n style={{\n left: `${xPosition}px`,\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: color,\n opacity: isLastValue ? 1 : 0.6,\n // Position bar relative to zero line\n ...(isPositive\n ? { bottom: `${100 - zeroLinePercent}%` }\n : { top: `${zeroLinePercent}%` }),\n zIndex: 2,\n }}\n title={`${formatValue(limitedValues[index])}: ${variance >= 0 ? \"+\" : \"\"}${formatValue(variance)} vs current`}\n />\n );\n })}\n </div>\n\n {/* Variance labels on the right - show actual value difference */}\n <div\n className=\"dc:flex dc:flex-col dc:justify-between dc:text-xs text-dc-text-muted\"\n style={{ height: `${height}px` }}\n >\n <span>+{formatValue(maxVariance)}</span>\n <span>\n {minVariance < 0 ? \"\" : \"\"}\n {formatValue(minVariance)}\n </span>\n </div>\n </div>\n );\n}\n\nconst KpiDelta = React.memo(function KpiDelta({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n height = \"100%\",\n colorPalette,\n}: ChartProps) {\n const [fontSize, setFontSize] = useState(32);\n const [textWidth, setTextWidth] = useState(250);\n const containerRef = useRef<HTMLDivElement>(null);\n const valueRef = useRef<HTMLDivElement>(null);\n // Use specialized hook to avoid re-renders from unrelated context changes\n const getFieldLabel = useCubeFieldLabel();\n\n // Calculate font size and text width based on container dimensions\n useEffect(() => {\n const updateDimensions = () => {\n if (containerRef.current) {\n const container = containerRef.current;\n const rect = container.getBoundingClientRect();\n const containerWidth = rect.width;\n const containerHeight = rect.height;\n\n if (containerWidth > 0 && containerHeight > 0) {\n const widthBasedSize = containerWidth / 4;\n const heightBasedSize = containerHeight / 4;\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize);\n const clampedFontSize = Math.max(28, Math.min(baseFontSize, 140));\n setFontSize(clampedFontSize);\n\n setTimeout(() => {\n if (valueRef.current) {\n const textRect = valueRef.current.getBoundingClientRect();\n const measuredWidth = textRect.width;\n // Scale histogram width with container, accounting for labels on the right (~60px)\n const maxHistogramWidth = containerWidth - 100; // Leave room for padding and labels\n const effectiveWidth = Math.max(\n measuredWidth,\n Math.min(maxHistogramWidth, containerWidth * 0.7),\n );\n setTextWidth(Math.max(100, effectiveWidth)); // Minimum 100px\n }\n }, 10);\n }\n }\n };\n\n const timer = setTimeout(updateDimensions, 50);\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDimensions, 10);\n });\n\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current);\n }\n\n return () => {\n clearTimeout(timer);\n resizeObserver.disconnect();\n };\n }, [data, chartConfig]);\n\n if (!data || data.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? \"200px\" : undefined,\n }}\n >\n <div className=\"dc:text-center text-dc-text-muted\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n No data points to display\n </div>\n </div>\n </div>\n );\n }\n\n // Extract value and dimension fields from chart config\n let valueFields: string[] = [];\n let dimensionFields: string[] = [];\n\n if (chartConfig?.yAxis) {\n valueFields = Array.isArray(chartConfig.yAxis)\n ? chartConfig.yAxis\n : [chartConfig.yAxis];\n }\n\n if (chartConfig?.xAxis) {\n dimensionFields = Array.isArray(chartConfig.xAxis)\n ? chartConfig.xAxis\n : [chartConfig.xAxis];\n }\n\n if (valueFields.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? \"200px\" : undefined,\n backgroundColor: \"var(--dc-danger-bg)\",\n color: \"var(--dc-danger)\",\n borderColor: \"var(--dc-danger-border)\",\n }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">Configuration Error</div>\n <div className=\"dc:text-xs\">No measure field configured</div>\n </div>\n </div>\n );\n }\n\n const valueField = valueFields[0];\n const dimensionField = dimensionFields[0]; // Optional\n\n // Sort data by dimension if available (for time series)\n let sortedData = [...data];\n if (dimensionField) {\n sortedData = sortedData.sort((a, b) => {\n const aVal = a[dimensionField];\n const bVal = b[dimensionField];\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n });\n }\n\n // Filter out incomplete or last period if enabled\n const { useLastCompletePeriod = true, skipLastPeriod = false } =\n displayConfig;\n const {\n filteredData,\n excludedIncompletePeriod,\n skippedLastPeriod,\n granularity,\n } = filterIncompletePeriod(\n sortedData,\n dimensionField,\n queryObject,\n useLastCompletePeriod,\n skipLastPeriod,\n );\n\n // Use filtered data for calculations\n const dataToUse = filteredData;\n\n // Extract values from filtered data\n const values = dataToUse\n .map((row) => row[valueField])\n .filter((val) => val !== null && val !== undefined && !isNaN(Number(val)))\n .map((val) => Number(val));\n\n if (values.length < 2) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? \"200px\" : undefined,\n backgroundColor: \"var(--dc-warning-bg)\",\n color: \"var(--dc-warning)\",\n borderColor: \"var(--dc-warning-border)\",\n }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">Insufficient Data</div>\n <div className=\"dc:text-xs\">\n Delta calculation requires at least 2 data points\n </div>\n <div className=\"dc:text-xs\">Current data points: {values.length}</div>\n </div>\n </div>\n );\n }\n\n // Calculate delta between last and second-last values\n const lastValue = values[values.length - 1];\n const secondLastValue = values[values.length - 2];\n const absoluteChange = lastValue - secondLastValue;\n const percentageChange =\n secondLastValue !== 0\n ? (absoluteChange / Math.abs(secondLastValue)) * 100\n : 0;\n\n const isPositiveChange = absoluteChange >= 0;\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string => {\n // If custom formatValue is provided, use it exclusively\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value);\n }\n\n // Null handling: Show placeholder for missing data\n if (value === null || value === undefined) {\n return \"—\";\n }\n\n const decimals = displayConfig.decimals ?? 0;\n const prefix = displayConfig.prefix ?? \"\";\n\n let formattedValue: string;\n\n if (Math.abs(value) >= 1e9) {\n formattedValue = (value / 1e9).toFixed(decimals) + \"B\";\n } else if (Math.abs(value) >= 1e6) {\n formattedValue = (value / 1e6).toFixed(decimals) + \"M\";\n } else if (Math.abs(value) >= 1e3) {\n formattedValue = (value / 1e3).toFixed(decimals) + \"K\";\n } else {\n formattedValue = value.toFixed(decimals);\n }\n\n return prefix + formattedValue;\n };\n\n // Get colors from palette\n const getPositiveColor = (): string => {\n if (\n displayConfig.positiveColorIndex !== undefined &&\n colorPalette?.colors\n ) {\n const colorIndex = displayConfig.positiveColorIndex;\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex];\n }\n }\n return \"#10b981\"; // Default green\n };\n\n const getNegativeColor = (): string => {\n if (\n displayConfig.negativeColorIndex !== undefined &&\n colorPalette?.colors\n ) {\n const colorIndex = displayConfig.negativeColorIndex;\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex];\n }\n }\n return \"#ef4444\"; // Default red\n };\n\n const positiveColor = getPositiveColor();\n const negativeColor = getNegativeColor();\n const currentColor = isPositiveChange ? positiveColor : negativeColor;\n\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? \"200px\" : undefined,\n }}\n >\n {/* Field Label */}\n <div\n className=\"text-dc-text-secondary dc:font-bold dc:text-center dc:mb-2 dc:flex dc:items-center dc:justify-center dc:gap-1\"\n style={{\n fontSize: \"14px\",\n lineHeight: \"1.2\",\n }}\n >\n <span>\n {(() => {\n const label = getFieldLabel(valueField);\n return label && label.length > 1 ? label : valueField;\n })()}\n </span>\n {(excludedIncompletePeriod || skippedLastPeriod) && (\n <span\n title={\n skippedLastPeriod\n ? `Excludes last ${granularity || \"period\"}`\n : `Excludes current incomplete ${granularity}`\n }\n className=\"dc:cursor-help\"\n >\n <Icon\n icon={infoCircleIcon}\n className=\"dc:w-4 dc:h-4 text-dc-text-muted dc:opacity-70\"\n />\n </span>\n )}\n </div>\n\n {/* Main KPI Value and Delta */}\n <div className=\"dc:flex dc:items-center dc:justify-center dc:space-x-4 dc:mb-2\">\n {/* Main KPI Value */}\n <div\n ref={valueRef}\n className=\"dc:font-bold dc:leading-none\"\n style={{\n fontSize: `${fontSize}px`,\n color: \"var(--dc-text)\", // Keep main value neutral\n }}\n >\n {formatNumber(lastValue)}\n </div>\n\n {/* Delta Information */}\n <div className=\"dc:flex dc:items-center dc:space-x-1\">\n {/* Arrow */}\n <div\n className=\"dc:font-bold\"\n style={{\n color: currentColor,\n fontSize: `${fontSize * 0.35}px`,\n }}\n >\n {isPositiveChange ? \"▲\" : \"▼\"}\n </div>\n\n {/* Delta Values */}\n <div className=\"dc:text-left\">\n <div\n className=\"dc:font-bold dc:leading-tight\"\n style={{\n fontSize: `${fontSize * 0.35}px`,\n color: currentColor,\n }}\n >\n {isPositiveChange ? \"+\" : \"\"}\n {formatNumber(absoluteChange)}\n </div>\n <div\n className=\"dc:font-semibold dc:leading-tight\"\n style={{\n fontSize: `${fontSize * 0.28}px`,\n color: currentColor,\n opacity: 0.8,\n }}\n >\n {isPositiveChange ? \"+\" : \"\"}\n {percentageChange.toFixed(1)}%\n </div>\n </div>\n </div>\n </div>\n\n {/* Unit/Suffix (hidden when formatValue is provided) */}\n {displayConfig.suffix && !displayConfig.formatValue && (\n <div\n className=\"text-dc-text-muted dc:text-center dc:mb-3\"\n style={{\n fontSize: \"14px\",\n lineHeight: \"1.2\",\n opacity: 0.8,\n }}\n >\n {displayConfig.suffix}\n </div>\n )}\n\n {/* Variance Histogram */}\n {displayConfig.showHistogram !== false && values.length > 2 && (\n <div className=\"dc:mt-2 dc:w-full dc:flex dc:justify-center dc:overflow-hidden\">\n <VarianceHistogram\n values={values}\n lastValue={lastValue}\n positiveColor={positiveColor}\n negativeColor={negativeColor}\n formatValue={formatNumber}\n width={textWidth}\n height={64}\n />\n </div>\n )}\n </div>\n );\n})\n\nexport default KpiDelta\n"],"mappings":"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"}
1
+ {"version":3,"file":"chart-kpi-delta-KQjUIeal.js","names":[],"sources":["../../../src/client/utils/periodUtils.ts","../../../src/client/components/charts/KpiDelta.tsx"],"sourcesContent":["/**\n * Utility functions for detecting incomplete time periods in KPI charts\n */\n\n/**\n * Get the end date of a period based on granularity\n * @param date - The date within the period\n * @param granularity - The time granularity (day, week, month, quarter, year)\n * @returns The end date of the period (end of day)\n */\nexport function getPeriodEndDate(date: Date, granularity: string): Date {\n const endDate = new Date(date)\n\n switch (granularity.toLowerCase()) {\n case 'day':\n // End of the same day\n endDate.setHours(23, 59, 59, 999)\n break\n\n case 'week': {\n // End of the week (Saturday, assuming week starts Sunday)\n const dayOfWeek = endDate.getDay()\n const daysUntilSaturday = 6 - dayOfWeek\n endDate.setDate(endDate.getDate() + daysUntilSaturday)\n endDate.setHours(23, 59, 59, 999)\n break\n }\n\n case 'month':\n // Last day of the month\n endDate.setMonth(endDate.getMonth() + 1, 0) // Set to day 0 of next month = last day of current month\n endDate.setHours(23, 59, 59, 999)\n break\n\n case 'quarter': {\n // Last day of the quarter\n const currentMonth = endDate.getMonth()\n const quarterEndMonth = Math.floor(currentMonth / 3) * 3 + 2 // 0->2, 3->5, 6->8, 9->11\n endDate.setMonth(quarterEndMonth + 1, 0) // Last day of quarter end month\n endDate.setHours(23, 59, 59, 999)\n break\n }\n\n case 'year':\n // December 31st\n endDate.setMonth(11, 31) // December 31\n endDate.setHours(23, 59, 59, 999)\n break\n\n default:\n // Unknown granularity - treat as complete\n endDate.setHours(23, 59, 59, 999)\n }\n\n return endDate\n}\n\n/**\n * Check if the last period in the data is complete based on granularity\n * @param lastDataPoint - The last data point in the sorted dataset\n * @param timeDimensionField - The field name containing the time value\n * @param granularity - The time granularity\n * @returns true if the period is complete, false if it's incomplete\n */\nexport function isLastPeriodComplete(\n lastDataPoint: any,\n timeDimensionField: string,\n granularity: string\n): boolean {\n if (!lastDataPoint || !timeDimensionField || !granularity) {\n return true // Assume complete if we can't determine\n }\n\n const timeValue = lastDataPoint[timeDimensionField]\n if (!timeValue) {\n return true // Assume complete if no time value\n }\n\n // Parse the time value\n const date = new Date(timeValue)\n if (isNaN(date.getTime())) {\n return true // Assume complete if invalid date\n }\n\n // Get the end of this period\n const periodEnd = getPeriodEndDate(date, granularity)\n\n // Compare with current time\n const now = new Date()\n\n // If the period end is in the future, the period is incomplete\n return periodEnd <= now\n}\n\n/**\n * Extract granularity from a query object\n * @param queryObject - The CubeQuery object\n * @param dimensionField - Optional specific dimension field to match\n * @returns The granularity string or null if not found\n */\nexport function getQueryGranularity(\n queryObject: any,\n dimensionField?: string\n): string | null {\n if (!queryObject?.timeDimensions || queryObject.timeDimensions.length === 0) {\n return null\n }\n\n // If a specific dimension field is provided, try to find its granularity\n if (dimensionField) {\n const matchingDim = queryObject.timeDimensions.find((td: any) =>\n td.dimension === dimensionField ||\n td.dimension?.includes(dimensionField) ||\n dimensionField?.includes(td.dimension)\n )\n if (matchingDim?.granularity) {\n return matchingDim.granularity\n }\n }\n\n // Fallback to first time dimension's granularity\n const firstTimeDim = queryObject.timeDimensions[0]\n return firstTimeDim?.granularity || null\n}\n\n/**\n * Filter data to exclude incomplete or last period\n * @param data - The data array sorted by time\n * @param timeDimensionField - The field containing time values\n * @param queryObject - The query object containing timeDimensions\n * @param useLastCompletePeriod - Whether to check for incomplete periods\n * @param skipLastPeriod - Whether to always skip the last period (overrides useLastCompletePeriod)\n * @returns Object with filtered data and whether filtering was applied\n */\nexport function filterIncompletePeriod(\n data: any[],\n timeDimensionField: string | undefined,\n queryObject: any,\n useLastCompletePeriod: boolean,\n skipLastPeriod: boolean = false\n): { filteredData: any[]; excludedIncompletePeriod: boolean; skippedLastPeriod: boolean; granularity: string | null } {\n // Default return - no filtering\n const noFilter = {\n filteredData: data,\n excludedIncompletePeriod: false,\n skippedLastPeriod: false,\n granularity: null\n }\n\n // Need at least 2 data points to filter\n if (data.length < 2) {\n return noFilter\n }\n\n const granularity = getQueryGranularity(queryObject, timeDimensionField)\n\n // If skipLastPeriod is enabled, always skip the last period\n if (skipLastPeriod) {\n return {\n filteredData: data.slice(0, -1),\n excludedIncompletePeriod: false,\n skippedLastPeriod: true,\n granularity\n }\n }\n\n // Skip incomplete period check if feature is disabled\n if (!useLastCompletePeriod) {\n return { ...noFilter, granularity }\n }\n\n // Skip if no time dimension field\n if (!timeDimensionField) {\n return { ...noFilter, granularity }\n }\n\n // Skip if no time dimensions in query\n if (!queryObject?.timeDimensions || queryObject.timeDimensions.length === 0) {\n return { ...noFilter, granularity }\n }\n\n if (!granularity) {\n return noFilter\n }\n\n // Check if last period is incomplete\n const lastRow = data[data.length - 1]\n if (!isLastPeriodComplete(lastRow, timeDimensionField, granularity)) {\n return {\n filteredData: data.slice(0, -1),\n excludedIncompletePeriod: true,\n skippedLastPeriod: false,\n granularity\n }\n }\n\n return { ...noFilter, granularity }\n}\n","import React, { useState, useRef, useEffect } from \"react\";\nimport { Icon } from \"@iconify/react\";\nimport infoCircleIcon from \"@iconify-icons/tabler/info-circle\";\nimport { useCubeFieldLabel } from \"../../hooks/useCubeFieldLabel\";\nimport { filterIncompletePeriod } from \"../../utils/periodUtils\";\nimport type { ChartProps } from \"../../types\";\n\ninterface VarianceHistogramProps {\n values: number[];\n lastValue: number;\n positiveColor: string;\n negativeColor: string;\n formatValue: (value: number) => string;\n width: number;\n height: number;\n}\n\nfunction VarianceHistogram({\n values,\n lastValue,\n positiveColor,\n negativeColor,\n formatValue,\n width,\n height,\n}: VarianceHistogramProps) {\n // Limit to most recent N values to fit in the histogram\n // Calculate max bars based on width (minimum 8px per bar including gap)\n const maxBars = Math.max(10, Math.floor(width / 10));\n const limitedValues =\n values.length > maxBars\n ? values.slice(-maxBars) // Take the most recent values\n : values;\n\n // Calculate variance (difference) from current/last value for each point\n const variances = limitedValues.map((value) => value - lastValue);\n\n // Find min/max variance for scaling (include 0 as baseline)\n const minVariance = Math.min(...variances, 0);\n const maxVariance = Math.max(...variances, 0);\n const range = Math.max(Math.abs(minVariance), Math.abs(maxVariance));\n\n if (range === 0 || variances.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center bg-dc-bg-secondary dc:rounded-sm dc:border border-dc-border\"\n style={{ width: `${width}px`, height: `${height}px` }}\n >\n <span className=\"dc:text-xs text-dc-text-muted\">No variance data</span>\n </div>\n );\n }\n\n // Calculate bar dimensions\n const barGap = 2;\n const availableWidth = width - (limitedValues.length - 1) * barGap;\n const barWidth = Math.max(4, availableWidth / limitedValues.length);\n\n // Calculate where zero line should be positioned (as percentage from top)\n // If maxVariance = 67 and minVariance = -24, total range = 91\n // Zero should be at 67/91 = 73.6% from top\n const totalRange = maxVariance - minVariance;\n const zeroLinePercent =\n totalRange > 0 ? (maxVariance / totalRange) * 100 : 50;\n\n return (\n <div className=\"dc:flex dc:items-center dc:space-x-2\">\n {/* Histogram bars */}\n <div\n className=\"dc:relative\"\n style={{\n width: `${width}px`,\n height: `${height}px`,\n }}\n >\n {/* Zero line (represents current value) */}\n <div\n className=\"dc:absolute dc:left-0 dc:right-0\"\n style={{\n height: \"1px\",\n top: `${zeroLinePercent}%`,\n backgroundColor: \"var(--dc-border)\",\n zIndex: 1,\n }}\n />\n\n {/* Variance bars - oldest to newest (left to right) */}\n {variances.map((variance, index) => {\n // Calculate bar height as proportion of total range\n const normalizedHeight = Math.abs(variance) / totalRange;\n const barHeight = Math.max(2, normalizedHeight * (height - 4));\n const isPositive = variance >= 0;\n const isLastValue = index === limitedValues.length - 1;\n const color = isPositive ? positiveColor : negativeColor;\n const xPosition = index * (barWidth + barGap);\n\n return (\n <div\n key={index}\n className=\"dc:absolute rounded-xs\"\n style={{\n left: `${xPosition}px`,\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: color,\n opacity: isLastValue ? 1 : 0.6,\n // Position bar relative to zero line\n ...(isPositive\n ? { bottom: `${100 - zeroLinePercent}%` }\n : { top: `${zeroLinePercent}%` }),\n zIndex: 2,\n }}\n title={`${formatValue(limitedValues[index])}: ${variance >= 0 ? \"+\" : \"\"}${formatValue(variance)} vs current`}\n />\n );\n })}\n </div>\n\n {/* Variance labels on the right - show actual value difference */}\n <div\n className=\"dc:flex dc:flex-col dc:justify-between dc:text-xs text-dc-text-muted\"\n style={{ height: `${height}px` }}\n >\n <span>+{formatValue(maxVariance)}</span>\n <span>\n {minVariance < 0 ? \"\" : \"\"}\n {formatValue(minVariance)}\n </span>\n </div>\n </div>\n );\n}\n\nconst KpiDelta = React.memo(function KpiDelta({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n height = \"100%\",\n colorPalette,\n}: ChartProps) {\n const [fontSize, setFontSize] = useState(32);\n const [textWidth, setTextWidth] = useState(250);\n const containerRef = useRef<HTMLDivElement>(null);\n const valueRef = useRef<HTMLDivElement>(null);\n // Use specialized hook to avoid re-renders from unrelated context changes\n const getFieldLabel = useCubeFieldLabel();\n\n // Calculate font size and text width based on container dimensions\n useEffect(() => {\n const updateDimensions = () => {\n if (containerRef.current) {\n const container = containerRef.current;\n const rect = container.getBoundingClientRect();\n const containerWidth = rect.width;\n const containerHeight = rect.height;\n\n if (containerWidth > 0 && containerHeight > 0) {\n const widthBasedSize = containerWidth / 4;\n const heightBasedSize = containerHeight / 4;\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize);\n const clampedFontSize = Math.max(28, Math.min(baseFontSize, 140));\n setFontSize(clampedFontSize);\n\n setTimeout(() => {\n if (valueRef.current) {\n const textRect = valueRef.current.getBoundingClientRect();\n const measuredWidth = textRect.width;\n // Scale histogram width with container, accounting for labels on the right (~60px)\n const maxHistogramWidth = containerWidth - 100; // Leave room for padding and labels\n const effectiveWidth = Math.max(\n measuredWidth,\n Math.min(maxHistogramWidth, containerWidth * 0.7),\n );\n setTextWidth(Math.max(100, effectiveWidth)); // Minimum 100px\n }\n }, 10);\n }\n }\n };\n\n const timer = setTimeout(updateDimensions, 50);\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDimensions, 10);\n });\n\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current);\n }\n\n return () => {\n clearTimeout(timer);\n resizeObserver.disconnect();\n };\n }, [data, chartConfig]);\n\n if (!data || data.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? \"200px\" : undefined,\n }}\n >\n <div className=\"dc:text-center text-dc-text-muted\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n No data points to display\n </div>\n </div>\n </div>\n );\n }\n\n // Extract value and dimension fields from chart config\n let valueFields: string[] = [];\n let dimensionFields: string[] = [];\n\n if (chartConfig?.yAxis) {\n valueFields = Array.isArray(chartConfig.yAxis)\n ? chartConfig.yAxis\n : [chartConfig.yAxis];\n }\n\n if (chartConfig?.xAxis) {\n dimensionFields = Array.isArray(chartConfig.xAxis)\n ? chartConfig.xAxis\n : [chartConfig.xAxis];\n }\n\n if (valueFields.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? \"200px\" : undefined,\n backgroundColor: \"var(--dc-danger-bg)\",\n color: \"var(--dc-danger)\",\n borderColor: \"var(--dc-danger-border)\",\n }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">Configuration Error</div>\n <div className=\"dc:text-xs\">No measure field configured</div>\n </div>\n </div>\n );\n }\n\n const valueField = valueFields[0];\n const dimensionField = dimensionFields[0]; // Optional\n\n // Sort data by dimension if available (for time series)\n let sortedData = [...data];\n if (dimensionField) {\n sortedData = sortedData.sort((a, b) => {\n const aVal = a[dimensionField];\n const bVal = b[dimensionField];\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n });\n }\n\n // Filter out incomplete or last period if enabled\n const { useLastCompletePeriod = true, skipLastPeriod = false } =\n displayConfig;\n const {\n filteredData,\n excludedIncompletePeriod,\n skippedLastPeriod,\n granularity,\n } = filterIncompletePeriod(\n sortedData,\n dimensionField,\n queryObject,\n useLastCompletePeriod,\n skipLastPeriod,\n );\n\n // Use filtered data for calculations\n const dataToUse = filteredData;\n\n // Extract values from filtered data\n const values = dataToUse\n .map((row) => row[valueField])\n .filter((val) => val !== null && val !== undefined && !isNaN(Number(val)))\n .map((val) => Number(val));\n\n if (values.length < 2) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? \"200px\" : undefined,\n backgroundColor: \"var(--dc-warning-bg)\",\n color: \"var(--dc-warning)\",\n borderColor: \"var(--dc-warning-border)\",\n }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">Insufficient Data</div>\n <div className=\"dc:text-xs\">\n Delta calculation requires at least 2 data points\n </div>\n <div className=\"dc:text-xs\">Current data points: {values.length}</div>\n </div>\n </div>\n );\n }\n\n // Calculate delta between last and second-last values\n const lastValue = values[values.length - 1];\n const secondLastValue = values[values.length - 2];\n const absoluteChange = lastValue - secondLastValue;\n const percentageChange =\n secondLastValue !== 0\n ? (absoluteChange / Math.abs(secondLastValue)) * 100\n : 0;\n\n const isPositiveChange = absoluteChange >= 0;\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string => {\n // If custom formatValue is provided, use it exclusively\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value);\n }\n\n // Null handling: Show placeholder for missing data\n if (value === null || value === undefined) {\n return \"—\";\n }\n\n const decimals = displayConfig.decimals ?? 0;\n const prefix = displayConfig.prefix ?? \"\";\n\n let formattedValue: string;\n\n if (Math.abs(value) >= 1e9) {\n formattedValue = (value / 1e9).toFixed(decimals) + \"B\";\n } else if (Math.abs(value) >= 1e6) {\n formattedValue = (value / 1e6).toFixed(decimals) + \"M\";\n } else if (Math.abs(value) >= 1e3) {\n formattedValue = (value / 1e3).toFixed(decimals) + \"K\";\n } else {\n formattedValue = value.toFixed(decimals);\n }\n\n return prefix + formattedValue;\n };\n\n // Get colors from palette\n const getPositiveColor = (): string => {\n if (\n displayConfig.positiveColorIndex !== undefined &&\n colorPalette?.colors\n ) {\n const colorIndex = displayConfig.positiveColorIndex;\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex];\n }\n }\n return \"#10b981\"; // Default green\n };\n\n const getNegativeColor = (): string => {\n if (\n displayConfig.negativeColorIndex !== undefined &&\n colorPalette?.colors\n ) {\n const colorIndex = displayConfig.negativeColorIndex;\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex];\n }\n }\n return \"#ef4444\"; // Default red\n };\n\n const positiveColor = getPositiveColor();\n const negativeColor = getNegativeColor();\n const currentColor = isPositiveChange ? positiveColor : negativeColor;\n\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? \"200px\" : undefined,\n }}\n >\n {/* Field Label */}\n <div\n className=\"text-dc-text-secondary dc:font-bold dc:text-center dc:mb-2 dc:flex dc:items-center dc:justify-center dc:gap-1\"\n style={{\n fontSize: \"14px\",\n lineHeight: \"1.2\",\n }}\n >\n <span>\n {(() => {\n const label = getFieldLabel(valueField);\n return label && label.length > 1 ? label : valueField;\n })()}\n </span>\n {(excludedIncompletePeriod || skippedLastPeriod) && (\n <span\n title={\n skippedLastPeriod\n ? `Excludes last ${granularity || \"period\"}`\n : `Excludes current incomplete ${granularity}`\n }\n className=\"dc:cursor-help\"\n >\n <Icon\n icon={infoCircleIcon}\n className=\"dc:w-4 dc:h-4 text-dc-text-muted dc:opacity-70\"\n />\n </span>\n )}\n </div>\n\n {/* Main KPI Value and Delta */}\n <div className=\"dc:flex dc:items-center dc:justify-center dc:space-x-4 dc:mb-2\">\n {/* Main KPI Value */}\n <div\n ref={valueRef}\n className=\"dc:font-bold dc:leading-none\"\n style={{\n fontSize: `${fontSize}px`,\n color: \"var(--dc-text)\", // Keep main value neutral\n }}\n >\n {formatNumber(lastValue)}\n </div>\n\n {/* Delta Information */}\n <div className=\"dc:flex dc:items-center dc:space-x-1\">\n {/* Arrow */}\n <div\n className=\"dc:font-bold\"\n style={{\n color: currentColor,\n fontSize: `${fontSize * 0.35}px`,\n }}\n >\n {isPositiveChange ? \"▲\" : \"▼\"}\n </div>\n\n {/* Delta Values */}\n <div className=\"dc:text-left\">\n <div\n className=\"dc:font-bold dc:leading-tight\"\n style={{\n fontSize: `${fontSize * 0.35}px`,\n color: currentColor,\n }}\n >\n {isPositiveChange ? \"+\" : \"\"}\n {formatNumber(absoluteChange)}\n </div>\n <div\n className=\"dc:font-semibold dc:leading-tight\"\n style={{\n fontSize: `${fontSize * 0.28}px`,\n color: currentColor,\n opacity: 0.8,\n }}\n >\n {isPositiveChange ? \"+\" : \"\"}\n {percentageChange.toFixed(1)}%\n </div>\n </div>\n </div>\n </div>\n\n {/* Unit/Suffix (hidden when formatValue is provided) */}\n {displayConfig.suffix && !displayConfig.formatValue && (\n <div\n className=\"text-dc-text-muted dc:text-center dc:mb-3\"\n style={{\n fontSize: \"14px\",\n lineHeight: \"1.2\",\n opacity: 0.8,\n }}\n >\n {displayConfig.suffix}\n </div>\n )}\n\n {/* Variance Histogram */}\n {displayConfig.showHistogram !== false && values.length > 2 && (\n <div className=\"dc:mt-2 dc:w-full dc:flex dc:justify-center dc:overflow-hidden\">\n <VarianceHistogram\n values={values}\n lastValue={lastValue}\n positiveColor={positiveColor}\n negativeColor={negativeColor}\n formatValue={formatNumber}\n width={textWidth}\n height={64}\n />\n </div>\n )}\n </div>\n );\n})\n\nexport default KpiDelta\n"],"mappings":"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"}