stream-charts 1.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +809 -1022
  3. package/changes.md +36 -0
  4. package/dist/app/charts/Chart.d.ts +35 -139
  5. package/dist/app/charts/Chart.d.ts.map +1 -0
  6. package/dist/app/charts/Chart.js +74 -65
  7. package/dist/app/charts/Chart.js.map +1 -1
  8. package/dist/app/charts/axes/AxesState.d.ts +26 -18
  9. package/dist/app/charts/axes/AxesState.d.ts.map +1 -0
  10. package/dist/app/charts/axes/AxesState.js +26 -24
  11. package/dist/app/charts/axes/AxesState.js.map +1 -1
  12. package/dist/app/charts/axes/AxisInterval.d.ts +44 -26
  13. package/dist/app/charts/axes/AxisInterval.d.ts.map +1 -0
  14. package/dist/app/charts/axes/AxisInterval.js +48 -29
  15. package/dist/app/charts/axes/AxisInterval.js.map +1 -1
  16. package/dist/app/charts/axes/BaseAxisRange.d.ts +46 -22
  17. package/dist/app/charts/axes/BaseAxisRange.d.ts.map +1 -0
  18. package/dist/app/charts/axes/BaseAxisRange.js +17 -12
  19. package/dist/app/charts/axes/BaseAxisRange.js.map +1 -1
  20. package/dist/app/charts/axes/ContinuousAxis.d.ts +47 -6
  21. package/dist/app/charts/axes/ContinuousAxis.d.ts.map +1 -0
  22. package/dist/app/charts/axes/ContinuousAxis.js +160 -27
  23. package/dist/app/charts/axes/ContinuousAxis.js.map +1 -1
  24. package/dist/app/charts/axes/ContinuousAxisRange.d.ts +29 -10
  25. package/dist/app/charts/axes/ContinuousAxisRange.d.ts.map +1 -0
  26. package/dist/app/charts/axes/ContinuousAxisRange.js +29 -11
  27. package/dist/app/charts/axes/ContinuousAxisRange.js.map +1 -1
  28. package/dist/app/charts/axes/EmptyAxis.d.ts +5 -2
  29. package/dist/app/charts/axes/EmptyAxis.d.ts.map +1 -0
  30. package/dist/app/charts/axes/EmptyAxis.js +30 -9
  31. package/dist/app/charts/axes/EmptyAxis.js.map +1 -1
  32. package/dist/app/charts/axes/OrdinalAxis.d.ts +5 -3
  33. package/dist/app/charts/axes/OrdinalAxis.d.ts.map +1 -0
  34. package/dist/app/charts/axes/OrdinalAxis.js +39 -16
  35. package/dist/app/charts/axes/OrdinalAxis.js.map +1 -1
  36. package/dist/app/charts/axes/OrdinalAxisRange.d.ts +76 -13
  37. package/dist/app/charts/axes/OrdinalAxisRange.d.ts.map +1 -0
  38. package/dist/app/charts/axes/OrdinalAxisRange.js +95 -16
  39. package/dist/app/charts/axes/OrdinalAxisRange.js.map +1 -1
  40. package/dist/app/charts/axes/autoScale.d.ts +111 -0
  41. package/dist/app/charts/axes/autoScale.d.ts.map +1 -0
  42. package/dist/app/charts/axes/autoScale.js +188 -0
  43. package/dist/app/charts/axes/autoScale.js.map +1 -0
  44. package/dist/app/charts/axes/axes.d.ts +273 -150
  45. package/dist/app/charts/axes/axes.d.ts.map +1 -0
  46. package/dist/app/charts/axes/axes.js +808 -583
  47. package/dist/app/charts/axes/axes.js.map +1 -1
  48. package/dist/app/charts/d3types.d.ts +40 -11
  49. package/dist/app/charts/d3types.d.ts.map +1 -0
  50. package/dist/app/charts/d3types.js +3 -0
  51. package/dist/app/charts/d3types.js.map +1 -1
  52. package/dist/app/charts/datasources/StreamingDataSource.d.ts +149 -0
  53. package/dist/app/charts/datasources/StreamingDataSource.d.ts.map +1 -0
  54. package/dist/app/charts/datasources/StreamingDataSource.js +110 -0
  55. package/dist/app/charts/datasources/StreamingDataSource.js.map +1 -0
  56. package/dist/app/charts/datasources/iteratesDataSource.d.ts +73 -0
  57. package/dist/app/charts/datasources/iteratesDataSource.d.ts.map +1 -0
  58. package/dist/app/charts/datasources/iteratesDataSource.js +87 -0
  59. package/dist/app/charts/datasources/iteratesDataSource.js.map +1 -0
  60. package/dist/app/charts/datasources/ordinalDataSource.d.ts +75 -0
  61. package/dist/app/charts/datasources/ordinalDataSource.d.ts.map +1 -0
  62. package/dist/app/charts/datasources/ordinalDataSource.js +135 -0
  63. package/dist/app/charts/datasources/ordinalDataSource.js.map +1 -0
  64. package/dist/app/charts/datasources/outlierDataSource.d.ts +53 -0
  65. package/dist/app/charts/datasources/outlierDataSource.d.ts.map +1 -0
  66. package/dist/app/charts/datasources/outlierDataSource.js +57 -0
  67. package/dist/app/charts/datasources/outlierDataSource.js.map +1 -0
  68. package/dist/app/charts/datasources/timeSeriesDataSource.d.ts +77 -0
  69. package/dist/app/charts/datasources/timeSeriesDataSource.d.ts.map +1 -0
  70. package/dist/app/charts/datasources/timeSeriesDataSource.js +85 -0
  71. package/dist/app/charts/datasources/timeSeriesDataSource.js.map +1 -0
  72. package/dist/app/charts/filters/regexFilter.d.ts +31 -4
  73. package/dist/app/charts/filters/regexFilter.d.ts.map +1 -0
  74. package/dist/app/charts/filters/regexFilter.js +38 -4
  75. package/dist/app/charts/filters/regexFilter.js.map +1 -1
  76. package/dist/app/charts/hooks/AxesProvider.d.ts +21 -0
  77. package/dist/app/charts/hooks/AxesProvider.d.ts.map +1 -0
  78. package/dist/app/charts/hooks/AxesProvider.js +213 -0
  79. package/dist/app/charts/hooks/AxesProvider.js.map +1 -0
  80. package/dist/app/charts/hooks/CanvasSurfaceProvider.d.ts +25 -0
  81. package/dist/app/charts/hooks/CanvasSurfaceProvider.d.ts.map +1 -0
  82. package/dist/app/charts/hooks/CanvasSurfaceProvider.js +76 -0
  83. package/dist/app/charts/hooks/CanvasSurfaceProvider.js.map +1 -0
  84. package/dist/app/charts/hooks/ChartProvider.d.ts +40 -0
  85. package/dist/app/charts/hooks/ChartProvider.d.ts.map +1 -0
  86. package/dist/app/charts/hooks/ChartProvider.js +33 -0
  87. package/dist/app/charts/hooks/ChartProvider.js.map +1 -0
  88. package/dist/app/charts/hooks/InteractionProvider.d.ts +23 -0
  89. package/dist/app/charts/hooks/InteractionProvider.d.ts.map +1 -0
  90. package/dist/app/charts/hooks/InteractionProvider.js +60 -0
  91. package/dist/app/charts/hooks/InteractionProvider.js.map +1 -0
  92. package/dist/app/charts/hooks/PlotDimensionsProvider.d.ts +16 -0
  93. package/dist/app/charts/hooks/PlotDimensionsProvider.d.ts.map +1 -0
  94. package/dist/app/charts/hooks/PlotDimensionsProvider.js +76 -0
  95. package/dist/app/charts/hooks/PlotDimensionsProvider.js.map +1 -0
  96. package/dist/app/charts/hooks/defaultAxesValues.d.ts +11 -0
  97. package/dist/app/charts/hooks/defaultAxesValues.d.ts.map +1 -0
  98. package/dist/app/charts/hooks/defaultAxesValues.js +36 -0
  99. package/dist/app/charts/hooks/defaultAxesValues.js.map +1 -0
  100. package/dist/app/charts/hooks/defaultMouseValues.d.ts +9 -0
  101. package/dist/app/charts/hooks/defaultMouseValues.d.ts.map +1 -0
  102. package/dist/app/charts/hooks/defaultMouseValues.js +23 -0
  103. package/dist/app/charts/hooks/defaultMouseValues.js.map +1 -0
  104. package/dist/app/charts/hooks/defaultPlotDimensions.d.ts +13 -0
  105. package/dist/app/charts/hooks/defaultPlotDimensions.d.ts.map +1 -0
  106. package/dist/app/charts/hooks/defaultPlotDimensions.js +23 -0
  107. package/dist/app/charts/hooks/defaultPlotDimensions.js.map +1 -0
  108. package/dist/app/charts/hooks/defaultTooltipValues.d.ts +9 -0
  109. package/dist/app/charts/hooks/defaultTooltipValues.d.ts.map +1 -0
  110. package/dist/app/charts/hooks/defaultTooltipValues.js +21 -0
  111. package/dist/app/charts/hooks/defaultTooltipValues.js.map +1 -0
  112. package/dist/app/charts/hooks/defaultUseChartValues.d.ts +14 -0
  113. package/dist/app/charts/hooks/defaultUseChartValues.d.ts.map +1 -0
  114. package/dist/app/charts/hooks/defaultUseChartValues.js +30 -0
  115. package/dist/app/charts/hooks/defaultUseChartValues.js.map +1 -0
  116. package/dist/app/charts/hooks/useAxes.d.ts +49 -36
  117. package/dist/app/charts/hooks/useAxes.d.ts.map +1 -0
  118. package/dist/app/charts/hooks/useAxes.js +16 -176
  119. package/dist/app/charts/hooks/useAxes.js.map +1 -1
  120. package/dist/app/charts/hooks/useCanvasSurface.d.ts +25 -0
  121. package/dist/app/charts/hooks/useCanvasSurface.d.ts.map +1 -0
  122. package/dist/app/charts/hooks/useCanvasSurface.js +18 -0
  123. package/dist/app/charts/hooks/useCanvasSurface.js.map +1 -0
  124. package/dist/app/charts/hooks/useChart.d.ts +30 -44
  125. package/dist/app/charts/hooks/useChart.d.ts.map +1 -0
  126. package/dist/app/charts/hooks/useChart.js +26 -48
  127. package/dist/app/charts/hooks/useChart.js.map +1 -1
  128. package/dist/app/charts/hooks/useDataSource.d.ts +68 -0
  129. package/dist/app/charts/hooks/useDataSource.d.ts.map +1 -0
  130. package/dist/app/charts/hooks/useDataSource.js +63 -0
  131. package/dist/app/charts/hooks/useDataSource.js.map +1 -0
  132. package/dist/app/charts/hooks/useHoveredSeries.d.ts +25 -0
  133. package/dist/app/charts/hooks/useHoveredSeries.d.ts.map +1 -0
  134. package/dist/app/charts/hooks/useHoveredSeries.js +15 -0
  135. package/dist/app/charts/hooks/useHoveredSeries.js.map +1 -0
  136. package/dist/app/charts/hooks/useMouse.d.ts +46 -22
  137. package/dist/app/charts/hooks/useMouse.d.ts.map +1 -0
  138. package/dist/app/charts/hooks/useMouse.js +12 -56
  139. package/dist/app/charts/hooks/useMouse.js.map +1 -1
  140. package/dist/app/charts/hooks/usePlotDimensions.d.ts +25 -13
  141. package/dist/app/charts/hooks/usePlotDimensions.d.ts.map +1 -0
  142. package/dist/app/charts/hooks/usePlotDimensions.js +6 -62
  143. package/dist/app/charts/hooks/usePlotDimensions.js.map +1 -1
  144. package/dist/app/charts/hooks/useTooltip.d.ts +24 -26
  145. package/dist/app/charts/hooks/useTooltip.d.ts.map +1 -0
  146. package/dist/app/charts/hooks/useTooltip.js +12 -42
  147. package/dist/app/charts/hooks/useTooltip.js.map +1 -1
  148. package/dist/app/charts/index.d.ts +66 -0
  149. package/dist/app/charts/index.d.ts.map +1 -0
  150. package/dist/app/charts/index.js +87 -0
  151. package/dist/app/charts/index.js.map +1 -0
  152. package/dist/app/charts/legends/Legend.d.ts +33 -12
  153. package/dist/app/charts/legends/Legend.d.ts.map +1 -0
  154. package/dist/app/charts/legends/Legend.js +138 -379
  155. package/dist/app/charts/legends/Legend.js.map +1 -1
  156. package/dist/app/charts/legends/constants.d.ts +19 -0
  157. package/dist/app/charts/legends/constants.d.ts.map +1 -0
  158. package/dist/app/charts/legends/constants.js +34 -0
  159. package/dist/app/charts/legends/constants.js.map +1 -0
  160. package/dist/app/charts/observables/ChartData.d.ts +1 -0
  161. package/dist/app/charts/observables/ChartData.d.ts.map +1 -0
  162. package/dist/app/charts/observables/iterates.d.ts +13 -7
  163. package/dist/app/charts/observables/iterates.d.ts.map +1 -0
  164. package/dist/app/charts/observables/iterates.js +42 -9
  165. package/dist/app/charts/observables/iterates.js.map +1 -1
  166. package/dist/app/charts/observables/ordinals.d.ts +70 -12
  167. package/dist/app/charts/observables/ordinals.d.ts.map +1 -0
  168. package/dist/app/charts/observables/ordinals.js +127 -48
  169. package/dist/app/charts/observables/ordinals.js.map +1 -1
  170. package/dist/app/charts/observables/outliers.d.ts +18 -0
  171. package/dist/app/charts/observables/outliers.d.ts.map +1 -0
  172. package/dist/app/charts/observables/outliers.js +2 -0
  173. package/dist/app/charts/observables/outliers.js.map +1 -0
  174. package/dist/app/charts/plots/BarPlot.d.ts +87 -13
  175. package/dist/app/charts/plots/BarPlot.d.ts.map +1 -0
  176. package/dist/app/charts/plots/BarPlot.js +603 -356
  177. package/dist/app/charts/plots/BarPlot.js.map +1 -1
  178. package/dist/app/charts/plots/OutlierPlot.d.ts +123 -0
  179. package/dist/app/charts/plots/OutlierPlot.d.ts.map +1 -0
  180. package/dist/app/charts/plots/OutlierPlot.js +430 -0
  181. package/dist/app/charts/plots/OutlierPlot.js.map +1 -0
  182. package/dist/app/charts/plots/PoincarePlot.d.ts +92 -14
  183. package/dist/app/charts/plots/PoincarePlot.d.ts.map +1 -0
  184. package/dist/app/charts/plots/PoincarePlot.js +523 -364
  185. package/dist/app/charts/plots/PoincarePlot.js.map +1 -1
  186. package/dist/app/charts/plots/RasterPlot.d.ts +31 -10
  187. package/dist/app/charts/plots/RasterPlot.d.ts.map +1 -0
  188. package/dist/app/charts/plots/RasterPlot.js +198 -306
  189. package/dist/app/charts/plots/RasterPlot.js.map +1 -1
  190. package/dist/app/charts/plots/ScatterPlot.d.ts +77 -21
  191. package/dist/app/charts/plots/ScatterPlot.d.ts.map +1 -0
  192. package/dist/app/charts/plots/ScatterPlot.js +359 -302
  193. package/dist/app/charts/plots/ScatterPlot.js.map +1 -1
  194. package/dist/app/charts/plots/autoScaleYAxes.d.ts +43 -0
  195. package/dist/app/charts/plots/autoScaleYAxes.d.ts.map +1 -0
  196. package/dist/app/charts/plots/autoScaleYAxes.js +71 -0
  197. package/dist/app/charts/plots/autoScaleYAxes.js.map +1 -0
  198. package/dist/app/charts/plots/colorizedLine.d.ts +61 -0
  199. package/dist/app/charts/plots/colorizedLine.d.ts.map +1 -0
  200. package/dist/app/charts/plots/colorizedLine.js +157 -0
  201. package/dist/app/charts/plots/colorizedLine.js.map +1 -0
  202. package/dist/app/charts/plots/constants.d.ts +7 -0
  203. package/dist/app/charts/plots/constants.d.ts.map +1 -0
  204. package/dist/app/charts/plots/constants.js +11 -0
  205. package/dist/app/charts/plots/constants.js.map +1 -0
  206. package/dist/app/charts/plots/hitTesting.d.ts +88 -0
  207. package/dist/app/charts/plots/hitTesting.d.ts.map +1 -0
  208. package/dist/app/charts/plots/hitTesting.js +136 -0
  209. package/dist/app/charts/plots/hitTesting.js.map +1 -0
  210. package/dist/app/charts/plots/plot.d.ts +52 -16
  211. package/dist/app/charts/plots/plot.d.ts.map +1 -0
  212. package/dist/app/charts/plots/plot.js +108 -35
  213. package/dist/app/charts/plots/plot.js.map +1 -1
  214. package/dist/app/charts/plots/useCanvasZoomPan.d.ts +119 -0
  215. package/dist/app/charts/plots/useCanvasZoomPan.d.ts.map +1 -0
  216. package/dist/app/charts/plots/useCanvasZoomPan.js +113 -0
  217. package/dist/app/charts/plots/useCanvasZoomPan.js.map +1 -0
  218. package/dist/app/charts/plots/useStreamingTimeAxes.d.ts +103 -0
  219. package/dist/app/charts/plots/useStreamingTimeAxes.d.ts.map +1 -0
  220. package/dist/app/charts/plots/useStreamingTimeAxes.js +257 -0
  221. package/dist/app/charts/plots/useStreamingTimeAxes.js.map +1 -0
  222. package/dist/app/charts/series/baseSeries.d.ts +60 -8
  223. package/dist/app/charts/series/baseSeries.d.ts.map +1 -0
  224. package/dist/app/charts/series/baseSeries.js +53 -8
  225. package/dist/app/charts/series/baseSeries.js.map +1 -1
  226. package/dist/app/charts/series/iterateSeries.d.ts +9 -8
  227. package/dist/app/charts/series/iterateSeries.d.ts.map +1 -0
  228. package/dist/app/charts/series/iterateSeries.js +7 -7
  229. package/dist/app/charts/series/iterateSeries.js.map +1 -1
  230. package/dist/app/charts/series/ordinalSeries.d.ts +27 -13
  231. package/dist/app/charts/series/ordinalSeries.d.ts.map +1 -0
  232. package/dist/app/charts/series/ordinalSeries.js +29 -15
  233. package/dist/app/charts/series/ordinalSeries.js.map +1 -1
  234. package/dist/app/charts/series/outlierSeries.d.ts +142 -0
  235. package/dist/app/charts/series/outlierSeries.d.ts.map +1 -0
  236. package/dist/app/charts/series/outlierSeries.js +82 -0
  237. package/dist/app/charts/series/outlierSeries.js.map +1 -0
  238. package/dist/app/charts/series/timeSeries.d.ts +13 -9
  239. package/dist/app/charts/series/timeSeries.d.ts.map +1 -0
  240. package/dist/app/charts/series/timeSeries.js +6 -3
  241. package/dist/app/charts/series/timeSeries.js.map +1 -1
  242. package/dist/app/charts/series/timeSeriesChartData.d.ts +18 -10
  243. package/dist/app/charts/series/timeSeriesChartData.d.ts.map +1 -0
  244. package/dist/app/charts/series/timeSeriesChartData.js +28 -14
  245. package/dist/app/charts/series/timeSeriesChartData.js.map +1 -1
  246. package/dist/app/charts/styling/barPlotStyle.d.ts +46 -2
  247. package/dist/app/charts/styling/barPlotStyle.d.ts.map +1 -0
  248. package/dist/app/charts/styling/barPlotStyle.js +37 -3
  249. package/dist/app/charts/styling/barPlotStyle.js.map +1 -1
  250. package/dist/app/charts/styling/canvasStyle.d.ts +53 -0
  251. package/dist/app/charts/styling/canvasStyle.d.ts.map +1 -0
  252. package/dist/app/charts/styling/canvasStyle.js +71 -0
  253. package/dist/app/charts/styling/canvasStyle.js.map +1 -0
  254. package/dist/app/charts/styling/margins.d.ts +21 -2
  255. package/dist/app/charts/styling/margins.d.ts.map +1 -0
  256. package/dist/app/charts/styling/margins.js +25 -4
  257. package/dist/app/charts/styling/margins.js.map +1 -1
  258. package/dist/app/charts/styling/svgStyle.d.ts +10 -56
  259. package/dist/app/charts/styling/svgStyle.d.ts.map +1 -0
  260. package/dist/app/charts/styling/svgStyle.js +3 -78
  261. package/dist/app/charts/styling/svgStyle.js.map +1 -1
  262. package/dist/app/charts/subscriptions/viewDrivers.d.ts +124 -0
  263. package/dist/app/charts/subscriptions/viewDrivers.d.ts.map +1 -0
  264. package/dist/app/charts/subscriptions/viewDrivers.js +338 -0
  265. package/dist/app/charts/subscriptions/viewDrivers.js.map +1 -0
  266. package/dist/app/charts/tooltips/BarPlotTooltipContent.d.ts +12 -78
  267. package/dist/app/charts/tooltips/BarPlotTooltipContent.d.ts.map +1 -0
  268. package/dist/app/charts/tooltips/BarPlotTooltipContent.js +65 -257
  269. package/dist/app/charts/tooltips/BarPlotTooltipContent.js.map +1 -1
  270. package/dist/app/charts/tooltips/DefaultOutlierTooltipContent.d.ts +9 -0
  271. package/dist/app/charts/tooltips/DefaultOutlierTooltipContent.d.ts.map +1 -0
  272. package/dist/app/charts/tooltips/DefaultOutlierTooltipContent.js +16 -0
  273. package/dist/app/charts/tooltips/DefaultOutlierTooltipContent.js.map +1 -0
  274. package/dist/app/charts/tooltips/OutlierPlotTooltipContent.d.ts +30 -0
  275. package/dist/app/charts/tooltips/OutlierPlotTooltipContent.d.ts.map +1 -0
  276. package/dist/app/charts/tooltips/OutlierPlotTooltipContent.js +26 -0
  277. package/dist/app/charts/tooltips/OutlierPlotTooltipContent.js.map +1 -0
  278. package/dist/app/charts/tooltips/PoincarePlotTooltipContent.d.ts +22 -72
  279. package/dist/app/charts/tooltips/PoincarePlotTooltipContent.d.ts.map +1 -0
  280. package/dist/app/charts/tooltips/PoincarePlotTooltipContent.js +104 -171
  281. package/dist/app/charts/tooltips/PoincarePlotTooltipContent.js.map +1 -1
  282. package/dist/app/charts/tooltips/RasterPlotTooltipContent.d.ts +32 -8
  283. package/dist/app/charts/tooltips/RasterPlotTooltipContent.d.ts.map +1 -0
  284. package/dist/app/charts/tooltips/RasterPlotTooltipContent.js +62 -115
  285. package/dist/app/charts/tooltips/RasterPlotTooltipContent.js.map +1 -1
  286. package/dist/app/charts/tooltips/ScatterPlotTooltipContent.d.ts +39 -12
  287. package/dist/app/charts/tooltips/ScatterPlotTooltipContent.d.ts.map +1 -0
  288. package/dist/app/charts/tooltips/ScatterPlotTooltipContent.js +162 -134
  289. package/dist/app/charts/tooltips/ScatterPlotTooltipContent.js.map +1 -1
  290. package/dist/app/charts/tooltips/Tooltip.d.ts +27 -32
  291. package/dist/app/charts/tooltips/Tooltip.d.ts.map +1 -0
  292. package/dist/app/charts/tooltips/Tooltip.js +37 -81
  293. package/dist/app/charts/tooltips/Tooltip.js.map +1 -1
  294. package/dist/app/charts/tooltips/TooltipContent.d.ts +81 -0
  295. package/dist/app/charts/tooltips/TooltipContent.d.ts.map +1 -0
  296. package/dist/app/charts/tooltips/TooltipContent.js +99 -0
  297. package/dist/app/charts/tooltips/TooltipContent.js.map +1 -0
  298. package/dist/app/charts/tooltips/tooltipUtils.d.ts +186 -36
  299. package/dist/app/charts/tooltips/tooltipUtils.d.ts.map +1 -0
  300. package/dist/app/charts/tooltips/tooltipUtils.js +277 -48
  301. package/dist/app/charts/tooltips/tooltipUtils.js.map +1 -1
  302. package/dist/app/charts/tooltips/useOutlierTooltip.d.ts +59 -0
  303. package/dist/app/charts/tooltips/useOutlierTooltip.d.ts.map +1 -0
  304. package/dist/app/charts/tooltips/useOutlierTooltip.js +16 -0
  305. package/dist/app/charts/tooltips/useOutlierTooltip.js.map +1 -0
  306. package/dist/app/charts/tooltips/useTooltipPlacement.d.ts +51 -0
  307. package/dist/app/charts/tooltips/useTooltipPlacement.d.ts.map +1 -0
  308. package/dist/app/charts/tooltips/useTooltipPlacement.js +52 -0
  309. package/dist/app/charts/tooltips/useTooltipPlacement.js.map +1 -0
  310. package/dist/app/charts/tooltips/useTooltipStyle.d.ts +11 -0
  311. package/dist/app/charts/tooltips/useTooltipStyle.d.ts.map +1 -0
  312. package/dist/app/charts/tooltips/useTooltipStyle.js +14 -0
  313. package/dist/app/charts/tooltips/useTooltipStyle.js.map +1 -0
  314. package/dist/app/charts/trackers/Tracker.d.ts +22 -6
  315. package/dist/app/charts/trackers/Tracker.d.ts.map +1 -0
  316. package/dist/app/charts/trackers/Tracker.js +36 -22
  317. package/dist/app/charts/trackers/Tracker.js.map +1 -1
  318. package/dist/app/charts/trackers/trackerUtils.d.ts +40 -18
  319. package/dist/app/charts/trackers/trackerUtils.d.ts.map +1 -0
  320. package/dist/app/charts/trackers/trackerUtils.js +213 -300
  321. package/dist/app/charts/trackers/trackerUtils.js.map +1 -1
  322. package/dist/app/charts/utils.d.ts +72 -33
  323. package/dist/app/charts/utils.d.ts.map +1 -0
  324. package/dist/app/charts/utils.js +87 -42
  325. package/dist/app/charts/utils.js.map +1 -1
  326. package/dist/index.d.ts +2 -45
  327. package/dist/index.d.ts.map +1 -0
  328. package/dist/index.js +2 -72
  329. package/dist/index.js.map +1 -1
  330. package/package.json +87 -46
  331. package/src/app/charts/Chart.tsx +132 -168
  332. package/src/app/charts/axes/AxesState.ts +27 -29
  333. package/src/app/charts/axes/AxisInterval.ts +57 -32
  334. package/src/app/charts/axes/BaseAxisRange.ts +47 -27
  335. package/src/app/charts/axes/ContinuousAxis.tsx +235 -39
  336. package/src/app/charts/axes/ContinuousAxisRange.ts +29 -11
  337. package/src/app/charts/axes/EmptyAxis.tsx +52 -17
  338. package/src/app/charts/axes/OrdinalAxis.tsx +54 -28
  339. package/src/app/charts/axes/OrdinalAxisRange.ts +96 -16
  340. package/src/app/charts/axes/autoScale.ts +244 -0
  341. package/src/app/charts/axes/axes.ts +954 -735
  342. package/src/app/charts/d3types.ts +42 -13
  343. package/src/app/charts/datasources/StreamingDataSource.ts +214 -0
  344. package/src/app/charts/datasources/iteratesDataSource.ts +122 -0
  345. package/src/app/charts/datasources/ordinalDataSource.ts +205 -0
  346. package/src/app/charts/datasources/outlierDataSource.ts +90 -0
  347. package/src/app/charts/datasources/timeSeriesDataSource.ts +133 -0
  348. package/src/app/charts/filters/regexFilter.ts +45 -5
  349. package/src/app/charts/hooks/AxesProvider.tsx +301 -0
  350. package/src/app/charts/hooks/CanvasSurfaceProvider.tsx +117 -0
  351. package/src/app/charts/hooks/ChartProvider.tsx +79 -0
  352. package/src/app/charts/hooks/InteractionProvider.tsx +88 -0
  353. package/src/app/charts/hooks/PlotDimensionsProvider.tsx +109 -0
  354. package/src/app/charts/hooks/defaultAxesValues.ts +40 -0
  355. package/src/app/charts/hooks/defaultMouseValues.ts +24 -0
  356. package/src/app/charts/hooks/defaultPlotDimensions.ts +27 -0
  357. package/src/app/charts/hooks/defaultTooltipValues.tsx +23 -0
  358. package/src/app/charts/hooks/defaultUseChartValues.tsx +36 -0
  359. package/src/app/charts/hooks/useAxes.tsx +60 -228
  360. package/src/app/charts/hooks/useCanvasSurface.tsx +35 -0
  361. package/src/app/charts/hooks/useChart.tsx +47 -106
  362. package/src/app/charts/hooks/useDataSource.tsx +106 -0
  363. package/src/app/charts/hooks/useHoveredSeries.tsx +33 -0
  364. package/src/app/charts/hooks/useMouse.tsx +58 -82
  365. package/src/app/charts/hooks/usePlotDimensions.tsx +25 -89
  366. package/src/app/charts/hooks/useTooltip.tsx +32 -63
  367. package/src/app/charts/index.ts +213 -0
  368. package/src/app/charts/legends/Legend.tsx +198 -479
  369. package/src/app/charts/legends/constants.ts +37 -0
  370. package/src/app/charts/observables/iterates.ts +32 -12
  371. package/src/app/charts/observables/ordinals.ts +141 -67
  372. package/src/app/charts/observables/outliers.ts +20 -0
  373. package/src/app/charts/plots/BarPlot.tsx +784 -666
  374. package/src/app/charts/plots/OutlierPlot.tsx +685 -0
  375. package/src/app/charts/plots/PoincarePlot.tsx +707 -489
  376. package/src/app/charts/plots/RasterPlot.tsx +253 -445
  377. package/src/app/charts/plots/ScatterPlot.tsx +480 -463
  378. package/src/app/charts/plots/autoScaleYAxes.ts +102 -0
  379. package/src/app/charts/plots/colorizedLine.ts +185 -0
  380. package/src/app/charts/plots/constants.ts +16 -0
  381. package/src/app/charts/plots/hitTesting.ts +198 -0
  382. package/src/app/charts/plots/plot.ts +138 -44
  383. package/src/app/charts/plots/useCanvasZoomPan.ts +240 -0
  384. package/src/app/charts/plots/useStreamingTimeAxes.ts +384 -0
  385. package/src/app/charts/series/baseSeries.ts +81 -14
  386. package/src/app/charts/series/iterateSeries.ts +8 -8
  387. package/src/app/charts/series/ordinalSeries.ts +35 -20
  388. package/src/app/charts/series/outlierSeries.ts +188 -0
  389. package/src/app/charts/series/timeSeries.ts +14 -12
  390. package/src/app/charts/series/timeSeriesChartData.ts +31 -17
  391. package/src/app/charts/styling/barPlotStyle.ts +48 -5
  392. package/src/app/charts/styling/canvasStyle.ts +86 -0
  393. package/src/app/charts/styling/margins.ts +28 -4
  394. package/src/app/charts/styling/svgStyle.ts +10 -91
  395. package/src/app/charts/subscriptions/viewDrivers.ts +447 -0
  396. package/src/app/charts/tooltips/BarPlotTooltipContent.tsx +156 -380
  397. package/src/app/charts/tooltips/DefaultOutlierTooltipContent.tsx +40 -0
  398. package/src/app/charts/tooltips/OutlierPlotTooltipContent.tsx +67 -0
  399. package/src/app/charts/tooltips/PoincarePlotTooltipContent.tsx +229 -292
  400. package/src/app/charts/tooltips/RasterPlotTooltipContent.tsx +164 -217
  401. package/src/app/charts/tooltips/ScatterPlotTooltipContent.tsx +373 -236
  402. package/src/app/charts/tooltips/Tooltip.tsx +58 -124
  403. package/src/app/charts/tooltips/TooltipContent.tsx +177 -0
  404. package/src/app/charts/tooltips/tooltipUtils.ts +359 -61
  405. package/src/app/charts/tooltips/useOutlierTooltip.tsx +68 -0
  406. package/src/app/charts/tooltips/useTooltipPlacement.ts +86 -0
  407. package/src/app/charts/tooltips/useTooltipStyle.ts +15 -0
  408. package/src/app/charts/trackers/Tracker.tsx +68 -33
  409. package/src/app/charts/trackers/trackerUtils.ts +263 -376
  410. package/src/app/charts/utils.ts +90 -48
  411. package/src/index.ts +2 -322
  412. package/dist/app/charts/axes/AxesState.test.d.ts +0 -1
  413. package/dist/app/charts/axes/AxesState.test.js +0 -30
  414. package/dist/app/charts/axes/AxesState.test.js.map +0 -1
  415. package/dist/app/charts/axes/AxisInterval.test.d.ts +0 -1
  416. package/dist/app/charts/axes/AxisInterval.test.js +0 -120
  417. package/dist/app/charts/axes/AxisInterval.test.js.map +0 -1
  418. package/dist/app/charts/axes/ContinuousAxisRange.test.d.ts +0 -1
  419. package/dist/app/charts/axes/ContinuousAxisRange.test.js +0 -47
  420. package/dist/app/charts/axes/ContinuousAxisRange.test.js.map +0 -1
  421. package/dist/app/charts/axes/OrdinalAxisRange.test.d.ts +0 -1
  422. package/dist/app/charts/axes/OrdinalAxisRange.test.js +0 -48
  423. package/dist/app/charts/axes/OrdinalAxisRange.test.js.map +0 -1
  424. package/dist/app/charts/filters/regexFilter.test.d.ts +0 -1
  425. package/dist/app/charts/filters/regexFilter.test.js +0 -11
  426. package/dist/app/charts/filters/regexFilter.test.js.map +0 -1
  427. package/dist/app/charts/hooks/useDataObservable.d.ts +0 -72
  428. package/dist/app/charts/hooks/useDataObservable.js +0 -74
  429. package/dist/app/charts/hooks/useDataObservable.js.map +0 -1
  430. package/dist/app/charts/hooks/useInitialData.d.ts +0 -36
  431. package/dist/app/charts/hooks/useInitialData.js +0 -28
  432. package/dist/app/charts/hooks/useInitialData.js.map +0 -1
  433. package/dist/app/charts/observables/iterates.test.d.ts +0 -1
  434. package/dist/app/charts/observables/iterates.test.js +0 -180
  435. package/dist/app/charts/observables/iterates.test.js.map +0 -1
  436. package/dist/app/charts/observables/ordinals.test.d.ts +0 -1
  437. package/dist/app/charts/observables/ordinals.test.js +0 -102
  438. package/dist/app/charts/observables/ordinals.test.js.map +0 -1
  439. package/dist/app/charts/series/datumSeries.test.d.ts +0 -1
  440. package/dist/app/charts/series/datumSeries.test.js +0 -50
  441. package/dist/app/charts/series/datumSeries.test.js.map +0 -1
  442. package/dist/app/charts/styling/margins.test.d.ts +0 -1
  443. package/dist/app/charts/styling/margins.test.js +0 -19
  444. package/dist/app/charts/styling/margins.test.js.map +0 -1
  445. package/dist/app/charts/subscriptions/subscription.test.d.ts +0 -1
  446. package/dist/app/charts/subscriptions/subscription.test.js +0 -395
  447. package/dist/app/charts/subscriptions/subscription.test.js.map +0 -1
  448. package/dist/app/charts/subscriptions/subscriptions.d.ts +0 -100
  449. package/dist/app/charts/subscriptions/subscriptions.js +0 -449
  450. package/dist/app/charts/subscriptions/subscriptions.js.map +0 -1
  451. package/src/app/charts/axes/AxesState.test.ts +0 -34
  452. package/src/app/charts/axes/AxisInterval.test.ts +0 -143
  453. package/src/app/charts/axes/ContinuousAxisRange.test.ts +0 -55
  454. package/src/app/charts/axes/OrdinalAxisRange.test.ts +0 -55
  455. package/src/app/charts/filters/regexFilter.test.ts +0 -13
  456. package/src/app/charts/hooks/useDataObservable.tsx +0 -170
  457. package/src/app/charts/hooks/useInitialData.tsx +0 -64
  458. package/src/app/charts/observables/iterates.test.ts +0 -201
  459. package/src/app/charts/observables/ordinals.test.ts +0 -135
  460. package/src/app/charts/series/datumSeries.test.ts +0 -60
  461. package/src/app/charts/styling/margins.test.ts +0 -22
  462. package/src/app/charts/subscriptions/subscription.test.ts +0 -499
  463. package/src/app/charts/subscriptions/subscriptions.ts +0 -635
@@ -1,11 +1,17 @@
1
1
  import { ContinuousAxisRange } from "./ContinuousAxisRange";
2
2
  import * as d3 from "d3";
3
- import { noop } from "../utils";
3
+ import { clipToArea } from "../plots/plot";
4
+ import { fontStringFor } from "../utils";
4
5
  import { OrdinalAxisRange } from "./OrdinalAxisRange";
5
6
  import { AxisInterval } from "./AxisInterval";
7
+ /** Default tick length, in pixels, matching d3-axis's default `tickSize`. */
8
+ const TICK_SIZE = 6;
9
+ /** Default gap between the end of a tick and its label, matching d3-axis's default `tickPadding`. */
10
+ const TICK_PADDING = 3;
6
11
  /**
7
12
  * Factory function to create a default AxisTickStyle with the default font,
8
- * no rotation, and no auto-rotation.
13
+ * no rotation, and no autorotation.
14
+ * @return The default axis tick style
9
15
  */
10
16
  export function defaultAxisTickStyle() {
11
17
  return {
@@ -17,6 +23,7 @@ export function defaultAxisTickStyle() {
17
23
  /**
18
24
  * Factory function to create a default AxesFont with the default font size, color,
19
25
  * family, and weight.
26
+ * @return The default axes font
20
27
  */
21
28
  export function defaultAxesFont() {
22
29
  return {
@@ -29,6 +36,7 @@ export function defaultAxesFont() {
29
36
  /**
30
37
  * Factory function to create a default SeriesLineStyle with the default color, line width,
31
38
  * highlight color, and highlight width.
39
+ * @return The default series line style
32
40
  */
33
41
  export function defaultLineStyle() {
34
42
  return {
@@ -39,296 +47,348 @@ export function defaultLineStyle() {
39
47
  };
40
48
  }
41
49
  /**
42
- * Enumerates the possible locations of an axis.
50
+ * The concrete kind of an axis, set by every axis-creator function to its own unique value.
51
+ * Lets code that only has a `BaseAxis` (e.g. a plot's `axesFor` validating an `axisAssignments`
52
+ * entry) discriminate the concrete axis shape at runtime -- `BaseAxis`/`ContinuousNumericAxis`/
53
+ * `OrdinalStringAxis` are plain structural interfaces, so a type-level cast (`as ContinuousNumericAxis`)
54
+ * cannot do that; this field is what actually can.
55
+ * Note: replaces enums to support `erasableSyntaxOnly`
56
+ * TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
43
57
  */
44
- export var AxisLocation;
45
- (function (AxisLocation) {
46
- // y-axes
47
- AxisLocation[AxisLocation["Left"] = 0] = "Left";
48
- AxisLocation[AxisLocation["Right"] = 1] = "Right";
49
- // x-axes
50
- AxisLocation[AxisLocation["Bottom"] = 2] = "Bottom";
51
- AxisLocation[AxisLocation["Top"] = 3] = "Top";
52
- })(AxisLocation || (AxisLocation = {}));
53
- /*
54
- axis clip path definition
58
+ export const AxisType = {
59
+ ContinuousNumeric: "ContinuousNumeric",
60
+ OrdinalString: "OrdinalString",
61
+ };
62
+ /**
63
+ * The axis component that creates each type of axis (with its article), for messages about axes
55
64
  */
65
+ const AXIS_COMPONENTS = {
66
+ [AxisType.ContinuousNumeric]: "a <ContinuousAxis> (or an <EmptyAxis>)",
67
+ [AxisType.OrdinalString]: "an <OrdinalAxis>",
68
+ };
56
69
  /**
57
- * Adds a clip area for the chart to the specified SVG element. The clip-area is given
58
- * an `id` of `clip-series-<chart_id>`, which because the chart ID should be unique, makes
59
- * this unique as well
60
- * @param chartId The ID of the chart to which the clip area is to be added
61
- * @param axisId The ID of the axis to which the clip area is to be added
62
- * @param svg The SVG element to which the clip area is to be added
63
- * @param location The location of the axis
64
- * @param plotDimensions The dimensions of the plot
65
- * @param margin The margins around the plot
66
- * @return The ID of the clip-path
70
+ * Checks that a plot's axis is of the type the plot needs (e.g. a bar plot needs an ordinal x-axis).
71
+ * A missing axis passes, since the plots already skip the series whose axes aren't found.
72
+ * @param plotName The name of the plot's component (e.g. "RasterPlot")
73
+ * @param dimension Whether the axis is the plot's x-axis or its y-axis
74
+ * @param axis The axis (`undefined` when it isn't found)
75
+ * @param expected The axis type the plot needs
76
+ * @throws Error when the axis isn't of the expected type, naming the axis component it needs to be
67
77
  */
68
- export function setClipPath(chartId, axisId, svg, location, plotDimensions, margin) {
69
- const clipPathId = `chart-clip-path-${chartId}-${axisId}`;
70
- // calculate the width and height of the clip-area for the axis
71
- const width = location === AxisLocation.Left ? margin.right :
72
- location === AxisLocation.Right ? margin.left :
73
- plotDimensions.width;
74
- const height = location === AxisLocation.Top ? margin.top + 1 :
75
- location === AxisLocation.Bottom ? margin.bottom - 1 :
76
- plotDimensions.height;
77
- // remove the old clipping region and add a new one with the updated plot dimensions
78
- svg.select(`#${clipPathId}-defs`).remove();
79
- svg
80
- .append('defs')
81
- .attr('id', `${clipPathId}-defs`)
82
- .append("clipPath")
83
- .attr("id", clipPathId)
84
- .append("rect")
85
- .attr("x", 0)
86
- .attr("y", location === AxisLocation.Bottom ? -1 : -margin.top)
87
- .attr("width", width)
88
- .attr("height", height);
89
- return clipPathId;
78
+ export function checkAxisType(plotName, dimension, axis, expected) {
79
+ if (axis !== undefined && axis.axisType !== expected) {
80
+ throw Error(`<${plotName}> needs its ${dimension}-axis to be ${AXIS_COMPONENTS[expected]}, but the ` +
81
+ `${dimension}-axis "${axis.axisId}" is ${AXIS_COMPONENTS[axis.axisType]}`);
82
+ }
90
83
  }
84
+ /**
85
+ * The possible locations of an axis.
86
+ * Note: replaces enums to support `erasableSyntaxOnly`
87
+ * TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
88
+ */
89
+ export const AxisLocation = {
90
+ // y-axes
91
+ Left: "Left",
92
+ Right: "Right",
93
+ // x-axes
94
+ Bottom: "Bottom",
95
+ Top: "Top"
96
+ };
91
97
  /*
92
- category axes
98
+ category (ordinal) axes
93
99
  */
94
100
  /**
95
- * Adds a category axis to the specified location. When the location is top or bottom,
96
- * the category axis represents the x-axis. When the location is left or right, then the
97
- * category axis represents the y-axis.
98
- * @param chartId The unique ID of the chart to which this axis belongs
101
+ * Adds a category axis to the specified location, registering its draw function with the canvas
102
+ * context. When the location is top or bottom, the category axis represents the x-axis; when the
103
+ * location is left or right, it represents the y-axis.
104
+ * @param cc The canvas context to register the axis' draw function with
99
105
  * @param axisId A unique ID for the axis
100
- * @param svg The SVG selection (d3)
101
106
  * @param location The location of the axis
102
- * @param categories An array holding the category names
107
+ * @param categories The category names (the axis' `update` function can replace them later)
103
108
  * @param axisLabel The axis label
104
109
  * @param axesLabelFont The font for the axis label
105
- * @param axisTickStyle Styling information for the ticks (e.g. font, rotation, etc)
110
+ * @param axisTickStyle Styling information for the ticks (font, rotation, etc.)
106
111
  * @param plotDimensions The dimensions of the plot
107
112
  * @param margin The plot margin
108
- * @param setAxisRangeFor Lambda that sets the axis range for the specified axis
109
- * @param setOriginalAxisRangeFor Lambda that sets the original axis range for the specified axis
110
- * @return A category axis
113
+ * @param setAxisRangeFor Callback that sets the axis range for the specified axis
114
+ * @param setOriginalAxisRangeFor Callback that sets the original axis range for the specified axis
115
+ * @return An {@link OrdinalStringAxis}
111
116
  */
112
- export function addOrdinalStringAxis(chartId, axisId, svg, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor) {
117
+ export function addOrdinalStringAxis(cc, axisId, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor) {
113
118
  switch (location) {
114
119
  case AxisLocation.Top:
115
120
  case AxisLocation.Bottom:
116
- return addOrdinalStringXAxis(chartId, axisId, svg, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor);
121
+ return addOrdinalStringXAxis(cc, axisId, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor);
117
122
  case AxisLocation.Left:
118
123
  case AxisLocation.Right:
119
- return addOrdinalStringYAxis(chartId, axisId, svg, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor);
124
+ return addOrdinalStringYAxis(cc, axisId, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor);
120
125
  }
121
126
  }
122
127
  /**
123
- * Adds a category axis representing the x-axis.
124
- * @param chartId The unique ID of the chart to which this axis belongs
128
+ * Adds a category x-axis (top or bottom), registering its draw function with the canvas context.
129
+ * See {@link addOrdinalStringAxis}.
130
+ *
131
+ * NOTE ON FIDELITY: the original SVG version rotated tick labels using each label's own
132
+ * `getBBox()` to compute a per-label rotation origin, which let long labels pivot around their own
133
+ * visual center/corner. Canvas `measureText` has no bbox-offset equivalent, so this version rotates
134
+ * every label about its anchor point at the tick instead. At `rotation: 0` (the common case) this
135
+ * produces the same centered layout as before; at non-zero rotations the labels will be close but
136
+ * not pixel-identical to the old SVG output -- worth a visual check if you use tick rotation.
137
+ * @param cc The canvas context to register the axis' draw function with
125
138
  * @param axisId A unique ID for the axis
126
- * @param svg The SVG selection (d3)
127
- * @param location The location of the axis
128
- * @param categories An array holding the category names
139
+ * @param location The location of the axis (top or bottom)
140
+ * @param categories The category names (the axis' `update` function can replace them later)
129
141
  * @param axisLabel The axis label
130
- * @param axesLabelFont The font for the axis label
131
- * @param axisTickStyle Styling information for the ticks (e.g. font, rotation, etc)
142
+ * @param axesLabelFont The font for the axis label (its color is also the color of the axis line,
143
+ * ticks, and tick labels)
144
+ * @param axisTickStyle Styling information for the ticks (font, rotation, etc.)
132
145
  * @param plotDimensions The dimensions of the plot
133
146
  * @param margin The plot margin
134
- * @param setAxisRangeFor Lambda that sets the axis range for the specified axis
135
- * @param setOriginalAxisRangeFor Lambda that sets the original axis range for the specified axis
136
- * @return A category axis
147
+ * @param setAxisRangeFor Callback that sets the axis range for the specified axis
148
+ * @param setOriginalAxisRangeFor Callback that sets the original axis range for the specified axis
149
+ * @return An {@link OrdinalStringAxis}
137
150
  */
138
- function addOrdinalStringXAxis(chartId, axisId, svg, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor) {
139
- const scale = d3.scaleBand()
140
- .domain(categories)
141
- .range([0, plotDimensions.width]);
142
- const categorySize = scale.bandwidth();
143
- // create and add the axes
144
- const generator = location === AxisLocation.Bottom ? d3.axisBottom(scale) : d3.axisTop(scale);
145
- const clipPathId = setClipPath(chartId, axisId, svg, location, plotDimensions, margin);
146
- const selection = svg
147
- .append('g')
148
- .attr('id', `x-axis-selection-${chartId}-${axisId}`)
149
- .attr("clip-path", `url(#${clipPathId})`)
150
- .classed('x-axis', true)
151
- .attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`)
152
- .call(generator);
153
- // rotate the tick-labels by the specified amount (in degrees)
154
- let maxTickLabelHeight = 0;
155
- const { font, rotation } = axisTickStyle;
156
- selection
157
- .selectAll("text")
158
- .style("text-anchor", "end")
159
- .each(function () {
160
- const degrees = location === AxisLocation.Bottom ? -rotation : rotation;
151
+ function addOrdinalStringXAxis(cc, axisId, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor) {
152
+ const scale = d3.scaleBand().domain(categories).range([0, plotDimensions.width]);
153
+ let currentDimensions = plotDimensions;
154
+ let currentMargin = margin;
155
+ let currentFont = axesLabelFont;
156
+ const currentTickStyle = axisTickStyle;
157
+ let highlighted = false;
158
+ let highlightColor = defaultLineStyle().highlightColor;
159
+ let highlightLineWidth = defaultLineStyle().highlightWidth;
160
+ const drawHandle = `x-axis-ordinal-${cc.chartId}-${axisId}`;
161
+ const draw = (context) => {
162
+ const { context2D } = context;
163
+ context2D.save();
164
+ context2D.translate(currentMargin.left, yTranslation(location, currentDimensions, currentMargin));
165
+ context2D.save();
166
+ clipToArea(context, { width: currentDimensions.width, height: currentMargin.bottom }, { x: 0, y: location === AxisLocation.Bottom ? -1 : -currentMargin.top });
167
+ const axisColor = highlighted ? highlightColor : currentFont.color;
168
+ context2D.strokeStyle = axisColor;
169
+ context2D.fillStyle = axisColor;
170
+ context2D.lineWidth = highlighted ? highlightLineWidth : 1;
171
+ context2D.font = fontStringFor(currentTickStyle.font.size, currentTickStyle.font.family, currentTickStyle.font.weight);
172
+ context2D.textBaseline = 'middle';
173
+ // domain line
174
+ context2D.beginPath();
175
+ context2D.moveTo(0, 0);
176
+ context2D.lineTo(currentDimensions.width, 0);
177
+ context2D.stroke();
178
+ const tickDirection = location === AxisLocation.Bottom ? 1 : -1;
179
+ const degrees = location === AxisLocation.Bottom ? -currentTickStyle.rotation : currentTickStyle.rotation;
161
180
  const radians = degrees * Math.PI / 180;
162
- const { width, height, x, y } = this.getBBox();
163
- const xOrigin = x + width;
164
- const yOrigin = location === AxisLocation.Bottom ? y + height / 2 : y + height / 2;
165
- d3.select(this)
166
- .attr("transform", () => `translate(${width * Math.cos(radians) / 2}, 0) rotate(${degrees}, ${xOrigin}, ${yOrigin})`);
167
- const newHeight = Math.abs(width * Math.sin(radians));
168
- if (newHeight > maxTickLabelHeight) {
169
- maxTickLabelHeight = newHeight;
170
- }
171
- });
172
- svg
173
- .append('text')
174
- .attr('id', labelIdFor(chartId, location))
175
- .style('text-anchor', 'middle')
176
- // when the axis is at the top, want the label to hang from the top (y=0) of the plot
177
- .attr('dominant-baseline', location === AxisLocation.Top ? "hanging" : "baseline")
178
- .attr('font-size', font.size)
179
- .attr('fill', font.color)
180
- .attr('font-family', font.family)
181
- .attr('font-weight', font.weight)
182
- .text(axisLabel);
183
- const axis = { axisId, selection, location, scale, generator, categorySize, update: () => categorySize };
184
- return Object.assign(Object.assign({}, axis), { update: (range, originalRange, plotDimensions, margin) => {
185
- const categorySize = updateOrdinalStringXAxis(chartId, axis, svg, location, categories, range, axesLabelFont, plotDimensions, margin);
181
+ categories.forEach(category => {
182
+ var _a;
183
+ const x = ((_a = scale(category)) !== null && _a !== void 0 ? _a : 0) + scale.bandwidth() / 2;
184
+ context2D.beginPath();
185
+ context2D.moveTo(x, 0);
186
+ context2D.lineTo(x, TICK_SIZE * tickDirection);
187
+ context2D.stroke();
188
+ context2D.save();
189
+ context2D.translate(x, (TICK_SIZE + TICK_PADDING) * tickDirection);
190
+ context2D.rotate(radians);
191
+ context2D.textAlign = radians === 0 ? 'center' : 'end';
192
+ context2D.fillText(category, 0, 0);
193
+ context2D.restore();
194
+ });
195
+ context2D.restore(); // pop the clip
196
+ // axis label (absolute position, not clipped, matches the old behavior)
197
+ context2D.fillStyle = axisColor;
198
+ context2D.font = fontStringFor(currentFont.size, currentFont.family, currentFont.weight);
199
+ context2D.textAlign = 'center';
200
+ // (a bottom label sits on the canvas' bottom edge, so it's drawn above it, descenders and all)
201
+ context2D.textBaseline = location === AxisLocation.Top ? 'hanging' : 'bottom';
202
+ const labelX = ordinalLabelXTranslation(location, currentDimensions, currentMargin, currentFont) - currentMargin.left;
203
+ const labelY = ordinalLabelYTranslation(location, currentDimensions, currentMargin) - yTranslation(location, currentDimensions, currentMargin);
204
+ context2D.fillText(axisLabel, labelX, labelY);
205
+ context2D.restore();
206
+ };
207
+ cc.register(drawHandle, draw, 0);
208
+ const categorySize = () => scale.bandwidth();
209
+ return {
210
+ axisId,
211
+ location,
212
+ axisType: AxisType.OrdinalString,
213
+ scale,
214
+ categorySize: categorySize(),
215
+ update: (updatedCategories, range, originalRange, plotDimensions, margin) => {
216
+ // reassign the closed-over `categories` (not just a local variable) so `draw`'s own
217
+ // `categories.forEach(...)` picks up the change on the next redraw too
218
+ categories = updatedCategories;
219
+ const updatedRange = AxisInterval.from(Math.min(range.start, 0), Math.max(range.end, plotDimensions.width));
220
+ scale.domain(categories).range(updatedRange.asTuple());
221
+ currentDimensions = plotDimensions;
222
+ currentMargin = margin;
186
223
  setAxisRangeFor(axisId, range);
187
224
  setOriginalAxisRangeFor(axisId, originalRange);
188
- return categorySize;
189
- } });
225
+ cc.requestRedraw();
226
+ return categorySize();
227
+ },
228
+ setHighlighted: (isHighlighted, color, lineWidth) => {
229
+ highlighted = isHighlighted;
230
+ if (color !== undefined)
231
+ highlightColor = color;
232
+ if (lineWidth !== undefined)
233
+ highlightLineWidth = lineWidth;
234
+ cc.requestRedraw();
235
+ },
236
+ updateFont: (font) => {
237
+ // the font's color is used for the label, the axis line, the ticks, and the tick
238
+ // labels; the tick labels' size, family, and weight stay at axisTickStyle.font's
239
+ currentFont = font;
240
+ cc.requestRedraw();
241
+ }
242
+ };
190
243
  }
191
244
  /**
192
- * Adds a category axis representing the y-axis.
193
- * @param chartId The unique ID of the chart to which this axis belongs
245
+ * Adds a category y-axis (left or right), registering its draw function with the canvas context.
246
+ * See {@link addOrdinalStringAxis}.
247
+ * @param cc The canvas context to register the axis' draw function with
194
248
  * @param axisId A unique ID for the axis
195
- * @param svg The SVG selection (d3)
196
- * @param location The location of the axis
197
- * @param categories An array holding the category names
249
+ * @param location The location of the axis (left or right)
250
+ * @param categories The category names (the axis' `update` function can replace them later)
198
251
  * @param axisLabel The axis label
199
- * @param axesLabelFont The font for the axis label
200
- * @param axisTickStyle Styling information for the ticks (e.g. font, rotation, etc)
252
+ * @param axesLabelFont The font for the axis label (its color is also the color of the axis line,
253
+ * ticks, and tick labels)
254
+ * @param axisTickStyle Styling information for the ticks (font, rotation, etc.)
201
255
  * @param plotDimensions The dimensions of the plot
202
256
  * @param margin The plot margin
203
- * @param setAxisRangeFor Lambda that sets the axis range for the specified axis
204
- * @param setOriginalAxisRangeFor Lambda that sets the original axis range for the specified axis
205
- * @return A category axis
257
+ * @param setAxisRangeFor Callback that sets the axis range for the specified axis
258
+ * @param setOriginalAxisRangeFor Callback that sets the original axis range for the specified axis
259
+ * @return An {@link OrdinalStringAxis}
206
260
  */
207
- function addOrdinalStringYAxis(chartId, axisId, svg, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor) {
208
- const scale = d3.scaleBand()
209
- .domain(categories)
210
- .range([0, plotDimensions.height]);
211
- const categorySize = scale.bandwidth();
212
- // create and add the axes
213
- const generator = location === AxisLocation.Left ? d3.axisLeft(scale) : d3.axisRight(scale);
214
- const selection = svg
215
- .append('g')
216
- .attr('id', `y-axis-selection-${chartId}`)
217
- .attr('class', 'y-axis')
218
- .attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`)
219
- .call(generator);
220
- // rotate the tick-labels by the specified amount (in degrees)
221
- const { font, rotation } = axisTickStyle;
222
- selection
223
- .selectAll("text")
224
- .style("text-anchor", "end")
225
- .each(function () {
226
- const degrees = location === AxisLocation.Left ? -rotation : rotation;
227
- const { width, height, x, y } = this.getBBox();
228
- const xTranslation = location === AxisLocation.Left ? 0 : width;
229
- const xOrigin = x + width;
230
- const yOrigin = location === AxisLocation.Left ? y + height / 2 : y + height / 2;
231
- d3.select(this)
232
- .attr("transform", () => `translate(${xTranslation}, 0) rotate(${degrees}, ${xOrigin}, ${yOrigin})`);
233
- });
234
- svg
235
- .append('text')
236
- .attr('id', labelIdFor(chartId, location))
237
- .attr('text-anchor', 'middle')
238
- .attr('font-size', font.size)
239
- .attr('fill', font.color)
240
- .attr('font-family', font.family)
241
- .attr('font-weight', font.weight)
242
- .attr('transform', `translate(${ordinalLabelXTranslation(location, plotDimensions, margin, axesLabelFont)}, ${ordinalLabelYTranslation(location, plotDimensions, margin)}) rotate(-90)`)
243
- .text(axisLabel);
244
- const axis = { axisId, selection, location, scale, generator, categorySize, update: () => categorySize };
245
- return Object.assign(Object.assign({}, axis), { update: (range, originalRange, plotDimensions, margin) => {
246
- const categorySize = updateOrdinalStringYAxis(chartId, axis, svg, location, categories, range, axesLabelFont, plotDimensions, margin);
261
+ function addOrdinalStringYAxis(cc, axisId, location, categories, axisLabel, axesLabelFont, axisTickStyle, plotDimensions, margin, setAxisRangeFor, setOriginalAxisRangeFor) {
262
+ const scale = d3.scaleBand().domain(categories).range([0, plotDimensions.height]);
263
+ let currentDimensions = plotDimensions;
264
+ let currentMargin = margin;
265
+ let currentFont = axesLabelFont;
266
+ const currentTickStyle = axisTickStyle;
267
+ let highlighted = false;
268
+ let highlightColor = defaultLineStyle().highlightColor;
269
+ let highlightLineWidth = defaultLineStyle().highlightWidth;
270
+ const drawHandle = `y-axis-ordinal-${cc.chartId}-${axisId}`;
271
+ const draw = (context) => {
272
+ const { context2D } = context;
273
+ context2D.save();
274
+ context2D.translate(xTranslation(location, currentDimensions, currentMargin), currentMargin.top);
275
+ context2D.save();
276
+ const clipWidth = location === AxisLocation.Left ? currentMargin.left : currentMargin.right;
277
+ // the boundary that abuts the domain line (x=0) is nudged 1px past it (into the plot side),
278
+ // so the line's 1px-wide stroke -- centered on x=0, spanning -0.5 to +0.5 -- isn't clipped
279
+ // in half; mirrors the same -1 fudge used for the bottom x-axis's domain line
280
+ clipToArea(context, { width: clipWidth, height: currentDimensions.height }, { x: location === AxisLocation.Left ? -clipWidth + 1 : -1, y: 0 });
281
+ const axisColor = highlighted ? highlightColor : currentFont.color;
282
+ context2D.strokeStyle = axisColor;
283
+ context2D.fillStyle = axisColor;
284
+ context2D.lineWidth = highlighted ? highlightLineWidth : 1;
285
+ context2D.font = fontStringFor(currentTickStyle.font.size, currentTickStyle.font.family, currentTickStyle.font.weight);
286
+ // matches the old SVG version, which forced text-anchor "end" for ordinal ticks
287
+ // regardless of Left/Right location
288
+ context2D.textAlign = 'end';
289
+ context2D.textBaseline = 'middle';
290
+ // domain line
291
+ context2D.beginPath();
292
+ context2D.moveTo(0, 0);
293
+ context2D.lineTo(0, currentDimensions.height);
294
+ context2D.stroke();
295
+ const degrees = location === AxisLocation.Left ? -currentTickStyle.rotation : currentTickStyle.rotation;
296
+ const radians = degrees * Math.PI / 180;
297
+ const tickDirection = location === AxisLocation.Left ? -1 : 1;
298
+ // offset the label past the tick mark plus a little padding, rather than flush against
299
+ // the axis line -- this was previously just `0` (Left) / `scale.bandwidth()` (Right),
300
+ // which put the label's text-anchor edge right on top of the axis line with no gap
301
+ const xTick = (TICK_SIZE + TICK_PADDING) * tickDirection;
302
+ categories.forEach(category => {
303
+ var _a;
304
+ const y = ((_a = scale(category)) !== null && _a !== void 0 ? _a : 0) + scale.bandwidth() / 2;
305
+ context2D.beginPath();
306
+ context2D.moveTo(0, y);
307
+ context2D.lineTo(TICK_SIZE * tickDirection, y);
308
+ context2D.stroke();
309
+ context2D.save();
310
+ context2D.translate(xTick, y);
311
+ context2D.rotate(radians);
312
+ context2D.fillText(category, 0, 0);
313
+ context2D.restore();
314
+ });
315
+ context2D.restore(); // pop the clip
316
+ // axis label, rotated -90deg, matching the old SVG version
317
+ context2D.save();
318
+ const labelX = ordinalLabelXTranslation(location, currentDimensions, currentMargin, currentFont) - xTranslation(location, currentDimensions, currentMargin);
319
+ const labelY = ordinalLabelYTranslation(location, currentDimensions, currentMargin) - currentMargin.top;
320
+ context2D.translate(labelX, labelY);
321
+ context2D.rotate(-Math.PI / 2);
322
+ context2D.fillStyle = axisColor;
323
+ context2D.font = fontStringFor(currentFont.size, currentFont.family, currentFont.weight);
324
+ context2D.textAlign = 'center';
325
+ context2D.textBaseline = 'alphabetic';
326
+ context2D.fillText(axisLabel, 0, 0);
327
+ context2D.restore();
328
+ context2D.restore();
329
+ };
330
+ cc.register(drawHandle, draw, 0);
331
+ const categorySize = () => scale.bandwidth();
332
+ return {
333
+ axisId,
334
+ location,
335
+ axisType: AxisType.OrdinalString,
336
+ scale,
337
+ categorySize: categorySize(),
338
+ update: (updatedCategories, range, originalRange, plotDimensions, margin) => {
339
+ // reassign the closed-over `categories` (not just a local variable) so `draw`'s own
340
+ // `categories.forEach(...)` picks up the change on the next redraw too
341
+ categories = updatedCategories;
342
+ const updatedRange = AxisInterval.from(Math.min(range.start, 0), Math.max(range.end, plotDimensions.height));
343
+ scale.domain(categories).range(updatedRange.asTuple());
344
+ currentDimensions = plotDimensions;
345
+ currentMargin = margin;
247
346
  setAxisRangeFor(axisId, range);
248
347
  setOriginalAxisRangeFor(axisId, originalRange);
249
- return categorySize;
250
- } });
348
+ cc.requestRedraw();
349
+ return categorySize();
350
+ },
351
+ setHighlighted: (isHighlighted, color, lineWidth) => {
352
+ highlighted = isHighlighted;
353
+ if (color !== undefined)
354
+ highlightColor = color;
355
+ if (lineWidth !== undefined)
356
+ highlightLineWidth = lineWidth;
357
+ cc.requestRedraw();
358
+ },
359
+ updateFont: (font) => {
360
+ // the font's color is used for the label, the axis line, the ticks, and the tick
361
+ // labels; the tick labels' size, family, and weight stay at axisTickStyle.font's
362
+ currentFont = font;
363
+ cc.requestRedraw();
364
+ }
365
+ };
251
366
  }
252
367
  /**
253
- * Updates the category axis representing the x-axis, calculates and returns the
254
- * number of pixels for each category on the x-axis.
255
- * @param chartId The unique ID of the chart to which this axis belongs
256
- * @param axis The category axis to be updated
257
- * @param svg The SVG selection (d3)
258
- * @param location The location of the axis
259
- * @param names An array holding the category names
260
- * @param axesLabelFont The font for the axis label
261
- * @param plotDimensions The dimensions of the plot
262
- * @param margin The plot margin
263
- * @param range The range of the axis (e.g., start and end) in pixels
264
- * @param axesLabelFont The font for the axis label
265
- * @param plotDimensions The dimensions of the plot
266
- * @param margin The plot margin
267
- * adjust the axis label location when needed
268
- * @return The number of pixels for each category
368
+ * Removes a category x-axis' draw function from the canvas context (e.g. when the axis unmounts)
369
+ * @param cc The canvas context the axis' draw function is registered with
370
+ * @param axisId The ID of the axis
269
371
  */
270
- function updateOrdinalStringXAxis(chartId, axis, svg, location, names, // domain (categories)
271
- range, // range (pixels)
272
- axesLabelFont, plotDimensions, margin) {
273
- const updatedRange = AxisInterval.from(Math.min(range.start, 0), Math.max(range.end, plotDimensions.width));
274
- axis.scale = axis.scale
275
- .domain(names)
276
- .range(updatedRange.asTuple());
277
- axis.categorySize = axis.scale.bandwidth();
278
- const clipPathId = setClipPath(chartId, axis.axisId, svg, location, plotDimensions, margin);
279
- axis.selection = axis.selection
280
- .attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`)
281
- .attr("clip-path", `url(#${clipPathId})`)
282
- .call(axis.generator);
283
- const xLabelTranslation = ordinalLabelXTranslation(location, plotDimensions, margin, axesLabelFont);
284
- const yLabelTranslation = ordinalLabelYTranslation(location, plotDimensions, margin);
285
- svg
286
- .select(`#${labelIdFor(chartId, location)}`)
287
- .attr('transform', `translate(${xLabelTranslation}, ${yLabelTranslation})`);
288
- return axis.categorySize;
372
+ export function removeOrdinalXAxis(cc, axisId) {
373
+ cc.unregister(`x-axis-ordinal-${cc.chartId}-${axisId}`);
289
374
  }
290
375
  /**
291
- * Updates the category axis representing the y-axis, calculates and returns the
292
- * number of pixels for each category on the y-axis.
293
- * @param chartId The unique ID of the chart to which this axis belongs
294
- * @param axis The category axis to be updated
295
- * @param svg The SVG selection (d3)
296
- * @param location The location of the axis
297
- * @param names An array holding the category names
298
- * @param range The range of the axis (e.g. start and end)
299
- * @param axesLabelFont The font for the axis label
300
- * @param plotDimensions The dimensions of the plot
301
- * @param margin The plot margin
302
- * @param axesLabelFont The font for the axis label
303
- * @param plotDimensions The dimensions of the plot
304
- * @param margin The plot margin
305
- * @return The number of pixels for each category
376
+ * Removes a category y-axis' draw function from the canvas context (e.g. when the axis unmounts)
377
+ * @param cc The canvas context the axis' draw function is registered with
378
+ * @param axisId The ID of the axis
306
379
  */
307
- function updateOrdinalStringYAxis(chartId, axis, svg, location, names, // domain
308
- range, // range
309
- axesLabelFont, plotDimensions, margin) {
310
- const updatedRange = AxisInterval.from(Math.min(range.start, 0), Math.max(range.end, plotDimensions.height));
311
- axis.scale = axis.scale
312
- .domain(names)
313
- .range(updatedRange.asTuple());
314
- axis.categorySize = axis.scale.bandwidth();
315
- axis.selection = axis.selection
316
- .attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`)
317
- .call(axis.generator);
318
- svg
319
- .select(`#${labelIdFor(chartId, location)}`)
320
- .attr('transform', `translate(${ordinalLabelXTranslation(location, plotDimensions, margin, axesLabelFont)}, ${ordinalLabelYTranslation(location, plotDimensions, margin)}) rotate(-90)`);
321
- return axis.categorySize;
380
+ export function removeOrdinalYAxis(cc, axisId) {
381
+ cc.unregister(`y-axis-ordinal-${cc.chartId}-${axisId}`);
322
382
  }
323
383
  /**
324
- * Calculates the number of pixels by which to translate the label in the x-direction for the axis.
325
- * The calculation uses the dimensions and margins of the plot, the location of the
326
- * axis, and the font for the label.
327
- * @param location The axis location (i.e. top, bottom, left, right)
384
+ * The x-coordinate (in canvas pixels, from the canvas' left edge) of a category axis' label: the
385
+ * label's font size in from the canvas' left or right edge for a y-axis, or the canvas' center for
386
+ * an x-axis
387
+ * @param location The location of the axis
328
388
  * @param plotDimensions The dimensions of the plot
329
- * @param margin The margins for the plot
389
+ * @param margin The plot margin
330
390
  * @param axesLabelFont The font for the axis label
331
- * @return The number of pixels to translate the label in the x-direction
391
+ * @return The x-coordinate of the axis label
332
392
  */
333
393
  function ordinalLabelXTranslation(location, plotDimensions, margin, axesLabelFont) {
334
394
  switch (location) {
@@ -343,13 +403,12 @@ function ordinalLabelXTranslation(location, plotDimensions, margin, axesLabelFon
343
403
  }
344
404
  }
345
405
  /**
346
- * Calculates the number of pixels by which to translate the label in the y-direction the axis.
347
- * The calculation uses the dimensions and margins of the plot, the location of the
348
- * axis, and the font for the label.
349
- * @param location The axis location (i.e. top, bottom, left, right)
406
+ * The y-coordinate (in canvas pixels, from the canvas' top edge) of a category axis' label: the
407
+ * canvas' middle for a y-axis, or the canvas' top or bottom edge for an x-axis
408
+ * @param location The location of the axis
350
409
  * @param plotDimensions The dimensions of the plot
351
- * @param margin The margins for the plot
352
- * @return The number of pixels to translate the label for the y-direction
410
+ * @param margin The plot margin
411
+ * @return The y-coordinate of the axis label
353
412
  */
354
413
  function ordinalLabelYTranslation(location, plotDimensions, margin) {
355
414
  switch (location) {
@@ -366,261 +425,368 @@ function ordinalLabelYTranslation(location, plotDimensions, margin) {
366
425
  continuous numeric axes
367
426
  */
368
427
  /**
369
- * Adds a new empty x-axis to the SVG element at the specified location. An empty axis
428
+ * Adds a new, empty x-axis (a line with no ticks or label) to the canvas context. An empty axis
370
429
  * is just a line where the axis would be, without any ticks or labels.
430
+ * @param canvasContext The canvas context to register the axis' draw function with
371
431
  * @param axisId The ID of the axis
372
- * @param svg The SVG selection to which to add the axis
373
432
  * @param plotDimensions The dimensions of the plot
374
433
  * @param location The location of the axis
375
- * @param scaleGenerator The higher-order function that returns the axis d3 "scale" function
376
- * @param domain The axis range (start, end)
377
- * @param margin The plot margins for the border of main SVG group
378
- * @param setAxisRangeFor A callback used to set the axis range
379
- * @return A {@link ContinuousNumericAxis} based on the arguments to this function
434
+ * @param scaleGenerator The d3 scale to use for the axis (its domain and range get overwritten)
435
+ * @param margin The plot margins
436
+ * @param setAxisRangeFor A callback used to report the axis' domain when it's updated
437
+ * @param color The color of the axis line (the axis' `updateFont` updates it with the font's color)
438
+ * @param [domain=[0, 1]] The axis' initial domain (min, max)
439
+ * @return A {@link ContinuousNumericAxis} (marked `isEmpty`) based on the arguments to this function
380
440
  */
381
- export function addEmptyXAxis(axisId, svg, plotDimensions, location, scaleGenerator, margin, setAxisRangeFor, domain = [0, 1]) {
441
+ export function addEmptyXAxis(canvasContext, axisId, plotDimensions, location, scaleGenerator, margin, setAxisRangeFor, color, domain = [0, 1]) {
382
442
  const scale = scaleGenerator.domain(domain).range([0, plotDimensions.width]);
383
- const selection = svg
384
- .append('g')
385
- .attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`);
386
- const line = selection.append('line')
387
- .attr('x1', 0)
388
- .attr('y1', 0)
389
- .attr('x2', plotDimensions.width)
390
- .attr('y2', 0)
391
- .attr('stroke', 'currentColor');
392
- const axis = {
443
+ let currentDimensions = plotDimensions;
444
+ let currentMargin = margin;
445
+ // NOTE: previously this read `ctx.fillStyle` directly, assuming it reflected "the chart's
446
+ // current color". But `ctx` is shared across every draw function registered on the canvas
447
+ // (all axes, all plots, the tracker). Whichever one last set `fillStyle` before this one
448
+ // ran left that value behind. It had nothing to do with this axis' actual color and never
449
+ // updated when the chart's `color`/theme changed, since nothing here ever assigned a new
450
+ // value in response to that. The `currentColor` is now explicit, passed in and refreshable via
451
+ // `updateFont` below.
452
+ let currentColor = color;
453
+ let highlighted = false;
454
+ let highlightColor = defaultLineStyle().highlightColor;
455
+ let highlightLineWidth = defaultLineStyle().highlightWidth;
456
+ const drawHandle = `x-axis-empty-${canvasContext.chartId}-${axisId}`;
457
+ const draw = (context) => {
458
+ const { context2D } = context;
459
+ context2D.save();
460
+ context2D.translate(currentMargin.left, yTranslation(location, currentDimensions, currentMargin));
461
+ context2D.strokeStyle = highlighted ? highlightColor : currentColor;
462
+ context2D.lineWidth = highlighted ? highlightLineWidth : 1;
463
+ context2D.beginPath();
464
+ context2D.moveTo(0, 0);
465
+ context2D.lineTo(currentDimensions.width, 0);
466
+ context2D.stroke();
467
+ context2D.restore();
468
+ };
469
+ canvasContext.register(drawHandle, draw, 0);
470
+ return {
393
471
  axisId,
394
472
  location,
395
- selection,
473
+ axisType: AxisType.ContinuousNumeric,
474
+ isEmpty: true,
396
475
  scale,
397
- generator: d3.axisBottom(scale).tickValues([]).tickSize(0),
398
- update: noop
399
- };
400
- return Object.assign(Object.assign({}, axis), { update: (domain, plotDimensions, margin) => {
401
- axis.scale.domain(domain.asTuple()).range([0, plotDimensions.width]);
402
- axis.selection
403
- .attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`);
404
- line.attr('x2', plotDimensions.width);
476
+ update: (domain, plotDimensions, margin) => {
477
+ scale.domain(domain.asTuple()).range([0, plotDimensions.width]);
478
+ currentDimensions = plotDimensions;
479
+ currentMargin = margin;
405
480
  setAxisRangeFor(axisId, domain);
406
- } });
481
+ canvasContext.requestRedraw();
482
+ },
483
+ // an empty axis has no ticks or label text but does have a colored line -- reuse
484
+ // updateFont's color field to keep that line in sync with the chart's color (e.g., on a
485
+ // theme change), matching the pattern used by the other axis types
486
+ updateFont: (font) => {
487
+ currentColor = font.color;
488
+ canvasContext.requestRedraw();
489
+ },
490
+ setHighlighted: (isHighlighted, color, lineWidth) => {
491
+ highlighted = isHighlighted;
492
+ if (color !== undefined)
493
+ highlightColor = color;
494
+ if (lineWidth !== undefined)
495
+ highlightLineWidth = lineWidth;
496
+ canvasContext.requestRedraw();
497
+ }
498
+ };
407
499
  }
408
500
  /**
409
- * Adds a new empty y-axis to the SVG element at the specified location. An empty axis
410
- * is just a line where the axis would be, without any ticks or labels.
501
+ * Adds a new, empty y-axis (a line with no ticks or label) to the canvas context.
502
+ * @param cc The canvas context to register the axis' draw function with
411
503
  * @param axisId The ID of the axis
412
- * @param svg The SVG selection to which to add the axis
413
504
  * @param plotDimensions The dimensions of the plot
414
505
  * @param location The location of the axis
415
- * @param scaleGenerator The higher-order function that returns the axis d3 "scale" function
416
- * @param domain The axis range (start, end)
417
- * @param margin The plot margins for the border of main SVG group
418
- * @param setAxisRangeFor A callback used to set the axis range
419
- * @return A {@link ContinuousNumericAxis} based on the arguments to this function
506
+ * @param scaleGenerator The d3 scale to use for the axis (its domain and range get overwritten)
507
+ * @param margin The plot margins
508
+ * @param setAxisRangeFor A callback used to report the axis' domain when it's updated
509
+ * @param color The color of the axis line (the axis' `updateFont` updates it with the font's color)
510
+ * @param [domain=[0, 1]] The axis' initial domain (min, max)
511
+ * @return A {@link ContinuousNumericAxis} (marked `isEmpty`) based on the arguments to this function
420
512
  */
421
- export function addEmptyYAxis(axisId, svg, plotDimensions, location, scaleGenerator, margin, setAxisRangeFor, domain = [0, 1]) {
513
+ export function addEmptyYAxis(cc, axisId, plotDimensions, location, scaleGenerator, margin, setAxisRangeFor, color, domain = [0, 1]) {
422
514
  const scale = scaleGenerator.domain(domain).range([plotDimensions.height, 0]);
423
- const selection = svg
424
- .append('g')
425
- .attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`);
426
- const line = selection.append('line')
427
- .attr('x1', 0)
428
- .attr('y1', 0)
429
- .attr('x2', 0)
430
- .attr('y2', plotDimensions.height)
431
- .attr('stroke', 'currentColor');
432
- const axis = {
515
+ let currentDimensions = plotDimensions;
516
+ let currentMargin = margin;
517
+ let currentColor = color;
518
+ let highlighted = false;
519
+ let highlightColor = defaultLineStyle().highlightColor;
520
+ let highlightLineWidth = defaultLineStyle().highlightWidth;
521
+ const drawHandle = `y-axis-empty-${cc.chartId}-${axisId}`;
522
+ const draw = (context) => {
523
+ const { context2D } = context;
524
+ context2D.save();
525
+ context2D.translate(xTranslation(location, currentDimensions, currentMargin), currentMargin.top);
526
+ context2D.strokeStyle = highlighted ? highlightColor : currentColor;
527
+ context2D.lineWidth = highlighted ? highlightLineWidth : 1;
528
+ context2D.beginPath();
529
+ context2D.moveTo(0, 0);
530
+ context2D.lineTo(0, currentDimensions.height);
531
+ context2D.stroke();
532
+ context2D.restore();
533
+ };
534
+ cc.register(drawHandle, draw, 0);
535
+ return {
433
536
  axisId,
434
537
  location,
435
- selection,
538
+ axisType: AxisType.ContinuousNumeric,
539
+ isEmpty: true,
436
540
  scale,
437
- generator: d3.axisLeft(scale).tickValues([]).tickSize(0),
438
- update: noop
439
- };
440
- return Object.assign(Object.assign({}, axis), { update: (domain, plotDimensions, margin) => {
441
- axis.scale.domain(domain.asTuple()).range([plotDimensions.height, 0]);
442
- axis.selection
443
- .attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`);
444
- line.attr('y2', plotDimensions.height);
541
+ update: (domain, plotDimensions, margin) => {
542
+ scale.domain(domain.asTuple()).range([plotDimensions.height, 0]);
543
+ currentDimensions = plotDimensions;
544
+ currentMargin = margin;
445
545
  setAxisRangeFor(axisId, domain);
446
- } });
546
+ cc.requestRedraw();
547
+ },
548
+ // an empty axis has no ticks or label text but does have a colored line -- reuse
549
+ // updateFont's color field to keep that line in sync with the chart's color (e.g., on a
550
+ // theme change), matching the pattern used by the other axis types
551
+ updateFont: (font) => {
552
+ currentColor = font.color;
553
+ cc.requestRedraw();
554
+ },
555
+ setHighlighted: (isHighlighted, color, lineWidth) => {
556
+ highlighted = isHighlighted;
557
+ if (color !== undefined)
558
+ highlightColor = color;
559
+ if (lineWidth !== undefined)
560
+ highlightLineWidth = lineWidth;
561
+ cc.requestRedraw();
562
+ }
563
+ };
447
564
  }
448
565
  /**
449
- * Adds a new x-axis to the SVG element at the specified location
450
- * @param chartId The ID of the chart
451
- * @param axisId The ID of the axis
452
- * @param svg The SVG selection to which to add the axis
566
+ * Adds a new x-axis, registering its draw function with the canvas context.
567
+ * @param cc The canvas context to register the axis' draw function with
568
+ * @param axisId A unique ID for the axis
453
569
  * @param plotDimensions The dimensions of the plot
454
- * @param location The location of the axis
455
- * @param scaleGenerator The higher-order function that returns the axis d3 "scale" function
456
- * @param domain The axis range (start, end)
457
- * @param axesLabelFont The font for the axis labels
458
- * @param margin The plot margins for the border of main SVG group
570
+ * @param location The location of the axis (top or bottom)
571
+ * @param scaleGenerator The d3 scale to use for the axis (its domain/range get overwritten)
572
+ * @param domain The axis' initial domain (min, max)
573
+ * @param axesLabelFont The font for the axis label and ticks
574
+ * @param margin The plot margins
459
575
  * @param axisLabel The label for the axis
460
- * @param setAxisRangeFor A callback used to set the axis range
461
- * @return A {@link ContinuousNumericAxis} based on the arguments to this function
576
+ * @param setAxisRangeFor Callback used to report the axis' domain when it's updated
577
+ * @return A {@link ContinuousNumericAxis}
462
578
  */
463
- export function addContinuousNumericXAxis(chartId, axisId, svg, plotDimensions, location, scaleGenerator, domain, axesLabelFont, margin, axisLabel, setAxisRangeFor) {
579
+ export function addContinuousNumericXAxis(cc, axisId, plotDimensions, location, scaleGenerator, domain, axesLabelFont, margin, axisLabel, setAxisRangeFor) {
464
580
  const scale = scaleGenerator.domain(domain).range([0, plotDimensions.width]);
465
- const selection = svg
466
- .append('g')
467
- .attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`);
468
- svg
469
- .append('text')
470
- .attr('id', labelIdFor(chartId, location))
471
- .attr('text-anchor', 'middle')
472
- // when the axis is at the top, want the label to hang from the top (y=0) of the plot
473
- .attr('dominant-baseline', location === AxisLocation.Top ? "hanging" : "baseline")
474
- .attr('font-size', axesLabelFont.size)
475
- .attr('fill', axesLabelFont.color)
476
- .attr('font-family', axesLabelFont.family)
477
- .attr('font-weight', axesLabelFont.weight)
478
- .attr('transform', `translate(${margin.left + plotDimensions.width / 2}, ${continuousLabelYTranslation(location, plotDimensions, margin)})`)
479
- .text(axisLabel);
480
- const axis = {
581
+ let currentDimensions = plotDimensions;
582
+ let currentMargin = margin;
583
+ const currentLabel = axisLabel;
584
+ let currentFont = axesLabelFont;
585
+ let highlighted = false;
586
+ let highlightColor = defaultLineStyle().highlightColor;
587
+ let highlightLineWidth = defaultLineStyle().highlightWidth;
588
+ const drawHandle = `x-axis-${cc.chartId}-${axisId}`;
589
+ const draw = (context) => {
590
+ const { context2D } = context;
591
+ context2D.save();
592
+ context2D.translate(currentMargin.left, yTranslation(location, currentDimensions, currentMargin));
593
+ // NOTE: the original SVG version never applied a clip-path to the continuous numeric
594
+ // axes (only the ordinal/category axes clip), which let the first/last tick labels
595
+ // overflow into the margin without being cut off. Clipping to exactly the plot width
596
+ // here would chop those end labels in half, so ticks/labels are intentionally unclipped.
597
+ const axisColor = highlighted ? highlightColor : currentFont.color;
598
+ context2D.strokeStyle = axisColor;
599
+ context2D.fillStyle = axisColor;
600
+ context2D.lineWidth = highlighted ? highlightLineWidth : 1;
601
+ context2D.font = fontStringFor(currentFont.size, currentFont.family, currentFont.weight);
602
+ context2D.textAlign = 'center';
603
+ context2D.textBaseline = location === AxisLocation.Bottom ? 'top' : 'bottom';
604
+ // domain line
605
+ context2D.beginPath();
606
+ context2D.moveTo(0, 0);
607
+ context2D.lineTo(currentDimensions.width, 0);
608
+ context2D.stroke();
609
+ // ticks
610
+ const tickDirection = location === AxisLocation.Bottom ? 1 : -1;
611
+ scale.ticks().forEach(tickValue => {
612
+ const x = scale(tickValue);
613
+ context2D.beginPath();
614
+ context2D.moveTo(x, 0);
615
+ context2D.lineTo(x, TICK_SIZE * tickDirection);
616
+ context2D.stroke();
617
+ context2D.fillText(scale.tickFormat()(tickValue), x, (TICK_SIZE + TICK_PADDING) * tickDirection);
618
+ });
619
+ // axis label (not clipped, matches the old behavior)
620
+ context2D.fillStyle = axisColor;
621
+ context2D.font = fontStringFor(currentFont.size, currentFont.family, currentFont.weight);
622
+ context2D.textAlign = 'center';
623
+ // (a bottom label sits on the canvas' bottom edge, so it's drawn above it, descenders and all)
624
+ context2D.textBaseline = location === AxisLocation.Top ? 'hanging' : 'bottom';
625
+ const labelY = continuousLabelYTranslation(location, currentDimensions, currentMargin) - yTranslation(location, currentDimensions, currentMargin);
626
+ context2D.fillText(currentLabel, currentDimensions.width / 2, labelY);
627
+ context2D.restore();
628
+ };
629
+ cc.register(drawHandle, draw, 0);
630
+ return {
481
631
  axisId,
482
632
  location,
483
- selection,
633
+ axisType: AxisType.ContinuousNumeric,
484
634
  scale,
485
- generator: location === AxisLocation.Bottom ? d3.axisBottom(scale) : d3.axisTop(scale),
486
- update: noop
487
- };
488
- return Object.assign(Object.assign({}, axis), { update: (domain, plotDimensions, margin) => {
489
- updateLinearXAxis(domain, axisLabel, chartId, svg, axis, plotDimensions, margin, location);
635
+ update: (domain, plotDimensions, margin) => {
636
+ scale.domain(domain.asTuple()).range([0, plotDimensions.width]);
637
+ currentDimensions = plotDimensions;
638
+ currentMargin = margin;
490
639
  setAxisRangeFor(axisId, domain);
491
- } });
492
- }
493
- /**
494
- * Updates the x-axis with the new domain and axis label
495
- * @param domain The new (start, end) range of the axis
496
- * @param label The new label for the axis
497
- * @param chartId The ID of the chart
498
- * @param svg The SVG selection of which the axis is a child node
499
- * @param axis The x-axis
500
- * @param plotDimensions The dimensions of the plot
501
- * @param margin The plot margins for the border of main SVG group
502
- * @param location The location of the axis (i.e. top, bottom, left, right)
503
- */
504
- function updateLinearXAxis(domain, label, chartId, svg, axis, plotDimensions, margin, location) {
505
- axis.scale.domain(domain.asTuple()).range([0, plotDimensions.width]);
506
- axis.selection
507
- .attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`)
508
- .call(axis.generator);
509
- svg
510
- .select(`#${labelIdFor(chartId, location)}`)
511
- .attr('transform', `translate(${margin.left + plotDimensions.width / 2}, ${continuousLabelYTranslation(location, plotDimensions, margin)})`)
512
- .text(label);
513
- }
514
- /**
515
- * The number of pixels to translate the x-axis label to the right
516
- * @param location The location of the x-axis
517
- * @param plotDimensions The dimensions of the plot
518
- * @param margin The plot margins for the border of main SVG group
519
- * @return The number of pixels to translate the x-axis label to the right
520
- */
521
- function continuousLabelYTranslation(location, plotDimensions, margin) {
522
- return location === AxisLocation.Bottom ? plotDimensions.height + margin.top + margin.bottom : 0;
640
+ cc.requestRedraw();
641
+ },
642
+ updateFont: (font) => {
643
+ currentFont = font;
644
+ cc.requestRedraw();
645
+ },
646
+ setHighlighted: (isHighlighted, color, lineWidth) => {
647
+ highlighted = isHighlighted;
648
+ if (color !== undefined)
649
+ highlightColor = color;
650
+ if (lineWidth !== undefined)
651
+ highlightLineWidth = lineWidth;
652
+ cc.requestRedraw();
653
+ }
654
+ };
523
655
  }
524
656
  /**
525
- * Adds a new y-axis to the SVG element at the specified location
526
- * @param chartId The ID of the chart
527
- * @param axisId The ID of the axis
528
- * @param svg The SVG selection to which to add the axis
657
+ * Adds a new y-axis, registering its draw function with the canvas context.
658
+ * @param cc The canvas context to register the axis' draw function with
659
+ * @param axisId A unique ID for the axis
529
660
  * @param plotDimensions The dimensions of the plot
530
- * @param location The location of the axis
531
- * @param scaleGenerator The higher-order function that returns the axis d3 "scale" function
532
- * @param domain The axis range (start, end)
533
- * @param axesLabelFont The font for the axis labels
534
- * @param margin The plot margins for the border of main SVG group
661
+ * @param location The location of the axis (left or right)
662
+ * @param scaleGenerator The d3 scale to use for the axis (its domain/range get overwritten)
663
+ * @param domain The axis' initial domain (min, max)
664
+ * @param axesLabelFont The font for the axis label and ticks
665
+ * @param margin The plot margins
535
666
  * @param axisLabel The label for the axis
536
- * @param setAxisRangeFor A callback used to set the axis range
537
- * @return A {@link ContinuousNumericAxis} based on the arguments to this function
667
+ * @param setAxisRangeFor Callback used to report the axis' domain when it's updated
668
+ * @return A {@link ContinuousNumericAxis}
538
669
  */
539
- export function addContinuousNumericYAxis(chartId, axisId, svg, plotDimensions, location, scaleGenerator, domain, axesLabelFont, margin, axisLabel, setAxisRangeFor) {
670
+ export function addContinuousNumericYAxis(cc, axisId, plotDimensions, location, scaleGenerator, domain, axesLabelFont, margin, axisLabel, setAxisRangeFor) {
540
671
  const scale = scaleGenerator
541
672
  .domain(domain)
542
673
  .range([Math.max(margin.bottom, plotDimensions.height), 0]);
543
- const selection = svg
544
- .append('g')
545
- .attr('class', 'y-axis')
546
- .attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`);
547
- svg
548
- .append('text')
549
- .attr('id', labelIdFor(chartId, location))
550
- .attr('text-anchor', 'start')
551
- .attr('font-size', axesLabelFont.size)
552
- .attr('fill', axesLabelFont.color)
553
- .attr('font-family', axesLabelFont.family)
554
- .attr('font-weight', axesLabelFont.weight)
555
- .attr('transform', `translate(${continuousLabelXTranslation(location, plotDimensions, margin, axesLabelFont)}, ${margin.top + plotDimensions.height / 2}) rotate(-90)`)
556
- .text(axisLabel);
557
- const axis = {
674
+ let currentDimensions = plotDimensions;
675
+ let currentMargin = margin;
676
+ const currentLabel = axisLabel;
677
+ let currentFont = axesLabelFont;
678
+ let highlighted = false;
679
+ let highlightColor = defaultLineStyle().highlightColor;
680
+ let highlightLineWidth = defaultLineStyle().highlightWidth;
681
+ const drawHandle = `y-axis-${cc.chartId}-${axisId}`;
682
+ const draw = (context) => {
683
+ const { context2D } = context;
684
+ context2D.save();
685
+ context2D.translate(xTranslation(location, currentDimensions, currentMargin), currentMargin.top);
686
+ // NOTE: unclipped, matching the old SVG version's continuous numeric axes (only the
687
+ // ordinal/category axes clipped there) -- clipping to exactly the plot height would cut
688
+ // off the top- and bottom-most tick labels, which straddle y=0 and y=height.
689
+ const axisColor = highlighted ? highlightColor : currentFont.color;
690
+ context2D.strokeStyle = axisColor;
691
+ context2D.fillStyle = axisColor;
692
+ context2D.lineWidth = highlighted ? highlightLineWidth : 1;
693
+ context2D.font = fontStringFor(currentFont.size, currentFont.family, currentFont.weight);
694
+ context2D.textBaseline = 'middle';
695
+ context2D.textAlign = location === AxisLocation.Left ? 'right' : 'left';
696
+ // domain line
697
+ context2D.beginPath();
698
+ context2D.moveTo(0, 0);
699
+ context2D.lineTo(0, currentDimensions.height);
700
+ context2D.stroke();
701
+ // ticks
702
+ const tickDirection = location === AxisLocation.Left ? -1 : 1;
703
+ scale.ticks().forEach(tickValue => {
704
+ const y = scale(tickValue);
705
+ context2D.beginPath();
706
+ context2D.moveTo(0, y);
707
+ context2D.lineTo(TICK_SIZE * tickDirection, y);
708
+ context2D.stroke();
709
+ context2D.fillText(scale.tickFormat()(tickValue), (TICK_SIZE + TICK_PADDING) * tickDirection, y);
710
+ });
711
+ // axis label, rotated -90deg, matching the old SVG version
712
+ context2D.save();
713
+ const labelX = continuousLabelXTranslation(location, currentDimensions, currentMargin, currentFont) - xTranslation(location, currentDimensions, currentMargin);
714
+ context2D.translate(labelX, currentDimensions.height / 2);
715
+ context2D.rotate(-Math.PI / 2);
716
+ context2D.fillStyle = axisColor;
717
+ context2D.font = fontStringFor(currentFont.size, currentFont.family, currentFont.weight);
718
+ context2D.textAlign = 'center';
719
+ context2D.textBaseline = 'alphabetic';
720
+ context2D.fillText(currentLabel, 0, 0);
721
+ context2D.restore();
722
+ context2D.restore();
723
+ };
724
+ cc.register(drawHandle, draw, 0);
725
+ return {
558
726
  axisId,
559
727
  location,
560
- selection,
728
+ axisType: AxisType.ContinuousNumeric,
561
729
  scale,
562
- generator: location === AxisLocation.Left ? d3.axisLeft(scale) : d3.axisRight(scale),
563
- update: noop
564
- };
565
- return Object.assign(Object.assign({}, axis), { update: (domain, plotDimensions, margin) => {
566
- updateLinearYAxis(domain, axisLabel, chartId, svg, axis, plotDimensions, margin, axesLabelFont, location);
730
+ update: (domain, plotDimensions, margin) => {
731
+ scale.domain(domain.asTuple()).range([Math.max(margin.bottom, plotDimensions.height), 0]);
732
+ currentDimensions = plotDimensions;
733
+ currentMargin = margin;
567
734
  setAxisRangeFor(axisId, domain);
568
- } });
735
+ cc.requestRedraw();
736
+ },
737
+ updateFont: (font) => {
738
+ currentFont = font;
739
+ cc.requestRedraw();
740
+ },
741
+ setHighlighted: (isHighlighted, color, lineWidth) => {
742
+ highlighted = isHighlighted;
743
+ if (color !== undefined)
744
+ highlightColor = color;
745
+ if (lineWidth !== undefined)
746
+ highlightLineWidth = lineWidth;
747
+ cc.requestRedraw();
748
+ }
749
+ };
569
750
  }
570
751
  /**
571
- * Updates the y-axis with the new domain and axis label
572
- * @param domain The new (start, end) range of the axis
573
- * @param label The new label for the axis
574
- * @param chartId The ID of the chart
575
- * @param svg The SVG selection of which the axis is a child node
576
- * @param axis The y-axis
577
- * @param plotDimensions The dimensions of the plot
578
- * @param margin The plot margins for the border of main SVG group
579
- * @param axesLabelFont The font for the axis label
580
- * @param location The location of the axis (i.e. top, bottom, left, right)
752
+ * Removes a continuous x-axis' draw function from the canvas context (called on "unmount" to avoid
753
+ * leaking a draw registration for an axis that's no longer part of the chart).
754
+ * @param cc The canvas context the axis' draw function is registered with
755
+ * @param axisId The ID of the axis
581
756
  */
582
- function updateLinearYAxis(domain, label, chartId, svg, axis, plotDimensions, margin, axesLabelFont, location) {
583
- axis.scale.domain(domain.asTuple()).range([Math.max(margin.bottom, plotDimensions.height), 0]);
584
- axis.selection
585
- .attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`)
586
- .call(axis.generator);
587
- svg
588
- .select(`#${labelIdFor(chartId, location)}`)
589
- .attr('transform', `translate(${continuousLabelXTranslation(location, plotDimensions, margin, axesLabelFont)}, ${margin.top + plotDimensions.height / 2}) rotate(-90)`)
590
- .text(label);
757
+ export function removeContinuousXAxis(cc, axisId) {
758
+ cc.unregister(`x-axis-${cc.chartId}-${axisId}`);
591
759
  }
592
760
  /**
593
- * The number of pixels to translate the y-axis label down
594
- * @param location The location of the y-axis
595
- * @param plotDimensions The dimensions of the plot
596
- * @param margin The plot margins for the border of main SVG group
597
- * @param axesLabelFont The font for the axis label
598
- * @return The number of pixels to translate the y-axis label down
761
+ * Removes a continuous y-axis' draw function from the canvas context (called on "unmount" to avoid
762
+ * leaking a draw registration for an axis that's no longer part of the chart).
763
+ * @param cc The canvas context the axis' draw function is registered with
764
+ * @param axisId The ID of the axis
599
765
  */
600
- function continuousLabelXTranslation(location, plotDimensions, margin, axesLabelFont) {
601
- return location === AxisLocation.Left ?
602
- axesLabelFont.size :
603
- margin.left + plotDimensions.width + margin.right - axesLabelFont.size;
766
+ export function removeContinuousYAxis(cc, axisId) {
767
+ cc.unregister(`y-axis-${cc.chartId}-${axisId}`);
604
768
  }
605
769
  /*
606
770
  common axis functions
607
771
  */
608
772
  /**
609
- * Calculates the number of pixels by which to translate the x-axis.
610
- * @param location The axis location (i.e. top, bottom, left, right)
773
+ * The x-coordinate (in canvas pixels) of a y-axis' line: the plot's left edge for a left axis, or
774
+ * its right edge for a right axis
775
+ * @param location The location of the axis
611
776
  * @param plotDimensions The dimensions of the plot
612
- * @param margin The margins for the plot
613
- * @return The number of pixels to translate the x-axis
777
+ * @param margin The plot margin
778
+ * @return The x-coordinate of the axis line
614
779
  */
615
780
  function xTranslation(location, plotDimensions, margin) {
616
781
  return location === AxisLocation.Left ? margin.left : margin.left + plotDimensions.width;
617
782
  }
618
783
  /**
619
- * The number of pixels to translate the x-axis to the right
620
- * @param location The location of the x-axis
784
+ * The y-coordinate (in canvas pixels) of an x-axis' line: the plot's bottom edge for a bottom
785
+ * axis, or its top edge for a top axis
786
+ * @param location The location of the axis
621
787
  * @param plotDimensions The dimensions of the plot
622
- * @param margin The plot margins for the border of main SVG group
623
- * @return The number of pixels to translate the y-axis to the down
788
+ * @param margin The plot margin
789
+ * @return The y-coordinate of the axis line
624
790
  */
625
791
  function yTranslation(location, plotDimensions, margin) {
626
792
  return location === AxisLocation.Bottom ?
@@ -628,92 +794,88 @@ function yTranslation(location, plotDimensions, margin) {
628
794
  margin.top;
629
795
  }
630
796
  /**
631
- * Calculates a unique ID for the axis based on the location and chart ID
632
- * @param chartId The unique ID of the chart
633
- * @param location The axis location (i.e. top, bottom, left, right)
634
- * @return A unique ID for the axis
797
+ * The y-coordinate (in canvas pixels) of a continuous x-axis' label: the canvas' bottom edge for
798
+ * a bottom axis, or its top edge for a top axis
799
+ * @param location The location of the axis
800
+ * @param plotDimensions The dimensions of the plot
801
+ * @param margin The plot margin
802
+ * @return The y-coordinate of the axis label
635
803
  */
636
- export function labelIdFor(chartId, location) {
637
- switch (location) {
638
- case AxisLocation.Left:
639
- case AxisLocation.Right:
640
- return `stream-chart-y-axis-${location}-label-${chartId}`;
641
- case AxisLocation.Top:
642
- case AxisLocation.Bottom:
643
- return `stream-chart-x-axis-${location}-label-${chartId}`;
644
- }
804
+ function continuousLabelYTranslation(location, plotDimensions, margin) {
805
+ return location === AxisLocation.Bottom ? plotDimensions.height + margin.top + margin.bottom : 0;
806
+ }
807
+ /**
808
+ * The x-coordinate (in canvas pixels) of a continuous y-axis' label: the label's font size in from
809
+ * the canvas' left edge for a left axis, or from its right edge for a right axis
810
+ * @param location The location of the axis
811
+ * @param plotDimensions The dimensions of the plot
812
+ * @param margin The plot margin
813
+ * @param axesLabelFont The font for the axis label
814
+ * @return The x-coordinate of the axis label
815
+ */
816
+ function continuousLabelXTranslation(location, plotDimensions, margin, axesLabelFont) {
817
+ return location === AxisLocation.Left ?
818
+ axesLabelFont.size :
819
+ margin.left + plotDimensions.width + margin.right - axesLabelFont.size;
645
820
  }
646
- // /**
647
- // * todo this can be generalized to any continuous numeric axis, replace x with value
648
- // * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
649
- // * at the location of the mouse when the scroll wheel or gesture was applied.
650
- // * @param transform The d3 zoom transformation information
651
- // * @param x The x-position of the mouse when the scroll wheel or gesture is used
652
- // * @param axis The axis being zoomed
653
- // * @param range The current range for the axis being zoomed
654
- // * @return The updated range and the new zoom factor
655
- // */
656
- // // export function calculateZoomFor<AR extends BaseAxisRange>(
657
- // export function calculateZoomFor(
658
- // transform: ZoomTransform,
659
- // x: number,
660
- // axis: ContinuousNumericAxis,
661
- // range: ContinuousAxisRange,
662
- // ): ZoomResult<ContinuousAxisRange> {
663
- // const time = axis.generator.scale<ScaleLinear<number, number>>().invert(x);
664
- // return {
665
- // range: range.scale(transform.k, time),
666
- // zoomFactor: transform.k
667
- // } as ZoomResult<ContinuousAxisRange>
668
- // }
669
821
  /**
670
822
  * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
671
- * at the location of the mouse when the scroll wheel or gesture was applied, while ensure that
672
- * the range (start, end) is contained within the constraint (min, max).
673
- * @param transform The d3 zoom transformation information
674
- * @param x The x-position of the mouse when the scroll wheel or gesture is used
675
- * @param axis The axis being zoomed
823
+ * around the specified domain value (the pivot), while ensuring that the range (start, end) is
824
+ * contained within the constraint (min, max). The pivot is a domain value rather than a pixel
825
+ * position -- while streaming, callers pin it to the axis's current "now" rather than to wherever
826
+ * the mouse happens to be (see `calcZoomAndUpdate`'s `pivotDomainValueFor`), which is what keeps
827
+ * "now" fixed on screen as the zoom widens/narrows the window around it.
828
+ *
829
+ * `zoomFactor` is *incremental* (relative to the range's `.current`, not its `.original`) -- see
830
+ * {@link BaseAxisRange.scaledRange}. d3-zoom's own `transform.k` is cumulative from wherever
831
+ * `__zoom` was last reset to identity, so callers must convert it to an incremental factor (this
832
+ * event's `k` divided by the previously-applied `k`) before calling this -- see
833
+ * `useCanvasZoomPan`.
834
+ * @param zoomFactor The incremental zoom scale factor for this event
835
+ * @param domainValue The domain value to zoom around (the pivot)
676
836
  * @param range The current range for the axis being zoomed
677
837
  * @param constraint The minimum and maximum value the scaled range can have
678
- * @return The updated range and the new zoom factor
838
+ * @return The updated range
679
839
  */
680
- export function calculateConstrainedZoomFor(transform, x, axis, range, constraint) {
681
- const domainValue = axis.generator.scale().invert(x);
840
+ export function calculateConstrainedZoomFor(zoomFactor, domainValue, range, constraint) {
682
841
  return {
683
- range: range.constrainedScale(transform.k, domainValue, constraint),
684
- zoomFactor: transform.k
842
+ range: range.constrainedScale(zoomFactor, domainValue, constraint),
685
843
  };
686
844
  }
687
845
  /**
688
- * Calculates the zoom for an ordinal axis.
689
- * @param transform The d3 zoom transformation information
846
+ * Calculates the zoom for an ordinal axis. Unlike {@link calculateConstrainedZoomFor}'s continuous
847
+ * axis, `zoomFactor` here is *cumulative* (d3-zoom's own `event.transform.k`, passed through
848
+ * unmodified) -- see {@link OrdinalAxisRange.scaledCumulative} for why: the range is scaled from
849
+ * `.current` by the incremental step the cumulative factor implies, except that a factor of
850
+ * exactly 1 snaps it back to `.original`, so that zooming all the way out always shows every
851
+ * category, whatever pivots were used along the way.
852
+ * @param zoomFactor The cumulative zoom scale factor for this event
690
853
  * @param x The x-position of the mouse when the scroll wheel or gesture is used
691
854
  * @param range The current range for the axis being zoomed
692
855
  * @param constraint The minimum and maximum value the scaled range can have
693
- * @return A ZoomResult holding the updated range and the new zoom factor
856
+ * @return A ZoomResult holding the updated range
694
857
  */
695
- export function calculateOrdinalConstrainedZoomFor(transform, x, range, constraint) {
696
- const updatedRange = range.constrainedScale(transform.k, x, constraint);
697
- const k = range.original.equals(updatedRange.original) ? 1 : transform.k;
858
+ export function calculateOrdinalConstrainedZoomFor(zoomFactor, x, range, constraint) {
698
859
  return {
699
- range: updatedRange,
700
- zoomFactor: k
860
+ range: range.constrainedScale(zoomFactor, x, constraint),
701
861
  };
702
862
  }
703
863
  /*
704
864
  panning
705
865
  */
706
866
  /**
707
- * Adjusts the range and updates the plot when the plot is dragged to the left or right
708
- * @param delta The amount that the plot is dragged
709
- * @param axis The axis being zoomed
710
- * @param range The current range for the axis being zoomed
867
+ * Calculates the axis range after the plot is dragged to the left or right (x-axis) or up or down
868
+ * (y-axis).
869
+ * @param delta The amount (in pixels) that the plot is dragged
870
+ * @param axis The axis being panned
871
+ * @param range The current range for the axis being panned
711
872
  * @param [constrainToOriginalRange=false] When set to `true` then the pan requires that the axis
712
- * range remains a subset of the origin axis range; when `false` the pan allows and range
873
+ * range remains a subset of the original axis range (a pan that would leave it isn't made); when
874
+ * `false` the pan allows any range
713
875
  * @return The updated range
714
876
  */
715
877
  export function calculatePanFor(delta, axis, range, constrainToOriginalRange = false) {
716
- const scale = axis.generator.scale();
878
+ const scale = axis.scale;
717
879
  const value = scale(range.current.start);
718
880
  if (value !== undefined) {
719
881
  const deltaValue = scale.invert(value + delta) - range.current.start;
@@ -724,32 +886,45 @@ export function calculatePanFor(delta, axis, range, constrainToOriginalRange = f
724
886
  }
725
887
  return range;
726
888
  }
727
- export function calculateOrdinalPanFor(delta, range, plotDimensions, constrainToOriginalRange = false) {
889
+ /**
890
+ * Calculates the ordinal axis range after the plot is dragged to the left or right (x-axis) or up
891
+ * or down (y-axis). A pan that would leave part of the plot without categories (i.e. take the
892
+ * range's start past 0, or its end short of the plot's width or height) isn't made.
893
+ * @param delta The amount (in pixels) to pan the axis by.
894
+ * @param range The current axis range.
895
+ * @param plotDimensions The dimensions of the plot.
896
+ * @param location The location of the axis.
897
+ * @param constrainToOriginalRange Whether to constrain the pan to the original range.
898
+ * @returns The new axis range.
899
+ */
900
+ export function calculateOrdinalPanFor(delta, range, plotDimensions, location, constrainToOriginalRange = false) {
728
901
  const constraint = constrainToOriginalRange ?
729
902
  range.original.asTuple() :
730
903
  [-Infinity, Infinity];
731
- // only allow panning if the plot dimensions (i.e. [0, width]) is in the current axis range
732
- // todo deal with the fact that this could be a pan of the y-axis
733
- if (range.current.start + delta > 0 || range.current.end + delta < plotDimensions.width) {
904
+ // the axis's pixel extent depends on its location: a top/bottom (x) axis spans the plot's
905
+ // width, while a left/right (y) axis spans the plot's height
906
+ const extent = location === AxisLocation.Top || location === AxisLocation.Bottom ?
907
+ plotDimensions.width :
908
+ plotDimensions.height;
909
+ // only allow panning if the plot dimensions (i.e. [0, extent]) is in the current axis range
910
+ if (range.current.start + delta > 0 || range.current.end + delta < extent) {
734
911
  return range;
735
912
  }
736
913
  return range.translate(delta, constraint);
737
914
  }
738
- /*
739
-
740
- */
741
915
  /**
742
916
  * Accepts the series, the assignment of the series to axes, and the current x-axes state, and
743
- * returns an array of the distinct axis IDs that cover all the series in the plot.
917
+ * returns an array of the distinct x-axis IDs that cover all the series in the plot (a series
918
+ * without an x-axis assignment uses the default x-axis).
744
919
  *
745
920
  * @param series The array of series
746
921
  * @param axisAssignments A map association a series name with its axis assignments
747
- * @param axesState The current axis state
748
- * @return an array of the distinct axes that cover all the series in the plot
922
+ * @param axesState The current x-axes state
923
+ * @return an array of the distinct x-axis IDs that cover all the series in the plot
749
924
  */
750
925
  export function axesForSeriesGen(series, axisAssignments, axesState) {
751
926
  return series.map(srs => srs.name)
752
- // grab the x-axis assigned to the series, or use the default x-axis if not
927
+ // grab the x-axis assigned to the series or use the default x-axis if not
753
928
  // assignment has been made
754
929
  .map(name => { var _a; return ((_a = axisAssignments.get(name)) === null || _a === void 0 ? void 0 : _a.xAxis) || axesState.axisDefaultId().getOrElse(""); })
755
930
  // de-dup the array of axis IDs so that we don't end up applying the pan or zoom
@@ -807,12 +982,13 @@ function ordinalPanAxes(delta, axesForSeries, axesState, ranges, setAxisRange, p
807
982
  const currentRange = ranges.get(axisId);
808
983
  if (currentRange && axis) {
809
984
  // calculate the change in the axis-range based on the pixel change from the drag event
810
- const range = calculateOrdinalPanFor(delta, currentRange, plotDimensions, constrainToOriginalRange);
985
+ const range = calculateOrdinalPanFor(delta, currentRange, plotDimensions, axis.location, constrainToOriginalRange);
811
986
  // update the time-range for the axis
812
987
  ranges.set(axisId, range);
813
988
  setAxisRange(axisId, range.current);
814
- // update the axis' time-range
815
- axis.update(range.current, range.original, plotDimensions, margin);
989
+ // update the axis' time-range -- panning never changes the set of categories, so
990
+ // pass the axis's own current domain back unchanged
991
+ axis.update(axis.scale.domain(), range.current, range.original, plotDimensions, margin);
816
992
  }
817
993
  });
818
994
  });
@@ -824,7 +1000,7 @@ function ordinalPanAxes(delta, axesForSeries, axesState, ranges, setAxisRange, p
824
1000
  * to the left or right. After calling the handler function, the plot needs to be updated as well, and this is
825
1001
  * left for the caller.
826
1002
  *
827
- * Please note that the function generated by this function has side effects -- it updates the axes ranges.
1003
+ * Please note that the function generated by this function has side effects -- it updates the axis ranges.
828
1004
  *
829
1005
  * @param axesForSeries The distinct axes that cover all the series
830
1006
  * @param margin The plot margin
@@ -837,13 +1013,11 @@ function ordinalPanAxes(delta, axesForSeries, axesState, ranges, setAxisRange, p
837
1013
  export function panHandler(axesForSeries, margin, setAxisRangeFor, axesState, constrainToOriginalRange = false) {
838
1014
  /**
839
1015
  * Adjusts the time-range and updates the plot when the plot is dragged to the left or right
840
- * @param deltaX The amount that the plot is dragged
1016
+ * @param delta The amount that the plot is dragged
841
1017
  * @param plotDimensions The dimensions of the plot
842
- * @param series An array of series names
843
1018
  * @param ranges A map holding the axis ID and its associated time range
844
1019
  */
845
1020
  return (delta, plotDimensions, ranges) => {
846
- // return (delta: number, plotDimensions: Dimensions, series: Array<string>, ranges: Map<string, ContinuousAxisRange>) => {
847
1021
  // run through the axis IDs, adjust their domain, and update the time-range set for that axis
848
1022
  panAxes(delta, axesForSeries, axesState, ranges, setAxisRangeFor, plotDimensions, margin, constrainToOriginalRange);
849
1023
  // hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
@@ -856,7 +1030,7 @@ export function panHandler(axesForSeries, margin, setAxisRangeFor, axesState, co
856
1030
  * to the left or right. After calling the handler function, the plot needs to be updated as well, and this is
857
1031
  * left for the caller.
858
1032
  *
859
- * Please note that the function generated by this function has side effects -- it updates the axes ranges.
1033
+ * Please note that the function generated by this function has side effects -- it updates the axes' ranges.
860
1034
  *
861
1035
  * @param axesForSeries The distinct axes that cover all the series
862
1036
  * @param margin The plot margin
@@ -869,12 +1043,12 @@ export function panHandler(axesForSeries, margin, setAxisRangeFor, axesState, co
869
1043
  export function ordinalPanHandler(axesForSeries, margin, setAxisRangeFor, axesState, constrainToOriginalRange = false) {
870
1044
  /**
871
1045
  * Adjusts the time-range and updates the plot when the plot is dragged to the left or right
872
- * @param deltaX The amount that the plot is dragged
1046
+ * @param delta The amount that the plot is dragged
873
1047
  * @param plotDimensions The dimensions of the plot
874
- * @param series An array of series names
1048
+ * @param _series An array of series names
875
1049
  * @param ranges A map holding the axis ID and its associated time range
876
1050
  */
877
- return (delta, plotDimensions, series, ranges) => {
1051
+ return (delta, plotDimensions, _series, ranges) => {
878
1052
  // run through the axis IDs, adjust their domain, and update the time-range set for that axis
879
1053
  ordinalPanAxes(delta, axesForSeries, axesState, ranges, setAxisRangeFor, plotDimensions, margin, constrainToOriginalRange);
880
1054
  // hey, don't forget to update the plot with the new ranges in the code calling this... :)
@@ -887,7 +1061,7 @@ export function ordinalPanHandler(axesForSeries, margin, setAxisRangeFor, axesSt
887
1061
  * to the left or right. After calling the handler function, the plot needs to be updated as well, and this is
888
1062
  * left for the caller.
889
1063
  *
890
- * Please note that the function generated by this function has side effects -- it updates the axes ranges.
1064
+ * Please note that the function generated by this function has side effects -- it updates the axes' ranges.
891
1065
  *
892
1066
  * @param xAxesForSeries The distinct x-axes that cover all the series
893
1067
  * @param yAxesForSeries The distinct y-axes that cover all the series
@@ -902,12 +1076,14 @@ export function ordinalPanHandler(axesForSeries, margin, setAxisRangeFor, axesSt
902
1076
  export function panHandler2D(xAxesForSeries, yAxesForSeries, margin, setAxisRange, xAxesState, yAxesState, constrainToOriginalRange = true) {
903
1077
  /**
904
1078
  * Adjusts the time-range and updates the plot when the plot is dragged to the left or right
905
- * @param deltaX The amount that the plot is dragged
1079
+ * @param deltaX The amount that the plot is dragged in x
1080
+ * @param deltaY The amount that the plot is dragged in y
906
1081
  * @param plotDimensions The dimensions of the plot
907
- * @param series An array of series names
908
- * @param ranges A map holding the axis ID and its associated time range
1082
+ * @param _series An array of series names
1083
+ * @param xRanges A map holding the x-axis ID and its associated time range
1084
+ * @param yRanges A map holding the y-axis ID and its associated time range
909
1085
  */
910
- return (deltaX, deltaY, plotDimensions, series, xRanges, yRanges) => {
1086
+ return (deltaX, deltaY, plotDimensions, _series, xRanges, yRanges) => {
911
1087
  // run through the x- and y-axes and update them by delta, within the original bounds
912
1088
  panAxes(deltaX, xAxesForSeries, xAxesState, xRanges, setAxisRange, plotDimensions, margin, constrainToOriginalRange);
913
1089
  panAxes(deltaY, yAxesForSeries, yAxesState, yRanges, setAxisRange, plotDimensions, margin, constrainToOriginalRange);
@@ -916,17 +1092,22 @@ export function panHandler2D(xAxesForSeries, yAxesForSeries, margin, setAxisRang
916
1092
  }
917
1093
  /**
918
1094
  * Calculates the zoom for the specified axis and updates the axis and the axis ranges
919
- * @param value The x- or y-coordinate of the mouse
920
- * @param axisId
1095
+ * @param pivotDomainValueFor Given the axis being zoomed, returns the domain value to pivot the
1096
+ * zoom around. Callers pass a function (rather than a fixed value) because the right pivot
1097
+ * depends on the axis's *own* domain/scale (e.g. inverting a shared mouse pixel position through
1098
+ * each axis's own scale, or reading each axis's own "now" tracking) -- there's no single value
1099
+ * that's correct for every axis when a plot has more than one (e.g. Scatter's dual x-axes).
1100
+ * @param axisId The id of the axis to zoom
921
1101
  * @param margin The plot margin
922
- * @param setRangeFor Function for setting the new time-range for a specific axis * @param scaleExtent The smallest and largest scale factors allowed
923
- * @param axesState
1102
+ * @param setRangeFor Function for setting the new time-range for a specific axis
1103
+ * @param axesState The current state of the x- or y-axes
924
1104
  * @param ranges A map associating axis IDs with axis ranges
925
1105
  * @param scaleExtent The smallest and largest scale factors allowed
926
- * @param transform The d3 zoom transformation information
1106
+ * @param zoomFactor The incremental zoom scale factor for this event -- see
1107
+ * {@link calculateConstrainedZoomFor} for what "incremental" means here
927
1108
  * @param plotDimensions The dimensions of the plot
928
1109
  */
929
- function calcZoomAndUpdate(value, axisId, margin, setRangeFor, axesState, ranges, scaleExtent, transform, plotDimensions) {
1110
+ function calcZoomAndUpdate(pivotDomainValueFor, axisId, margin, setRangeFor, axesState, ranges, scaleExtent, zoomFactor, plotDimensions) {
930
1111
  const [, zoomMax] = scaleExtent;
931
1112
  axesState.axisFor(axisId).ifPresent(axis => {
932
1113
  const range = ranges.get(axisId);
@@ -935,7 +1116,7 @@ function calcZoomAndUpdate(value, axisId, margin, setRangeFor, axesState, ranges
935
1116
  const constraint = isFinite(zoomMax) ?
936
1117
  [range.original.start * zoomMax, range.original.end * zoomMax] :
937
1118
  [0, Infinity];
938
- const zoom = calculateConstrainedZoomFor(transform, value, axis, range, constraint);
1119
+ const zoom = calculateConstrainedZoomFor(zoomFactor, pivotDomainValueFor(axis), range, constraint);
939
1120
  // update the axis range
940
1121
  ranges.set(axisId, zoom.range);
941
1122
  setRangeFor(axisId, zoom.range.current);
@@ -944,24 +1125,44 @@ function calcZoomAndUpdate(value, axisId, margin, setRangeFor, axesState, ranges
944
1125
  }
945
1126
  });
946
1127
  }
947
- function calcOrdinalZoomAndUpdate(value, axisId, margin, setRangeFor, setOriginalRangeFor, axesState, ranges, scaleExtent, transform, plotDimensions) {
1128
+ /**
1129
+ * Calculates the zoom for the specified ordinal axis and updates the axis and the axis ranges (the
1130
+ * original range is reset to the plot's width or height)
1131
+ * @param value The pixel position to pivot the zoom around (e.g. the mouse position)
1132
+ * @param axisId The id of the axis to zoom
1133
+ * @param margin The plot margin
1134
+ * @param setRangeFor Function for setting the new range for a specific axis
1135
+ * @param setOriginalRangeFor Function for setting the new original range for a specific axis
1136
+ * @param axesState The current state of the x- or y-axes
1137
+ * @param ranges A map associating axis IDs with axis ranges (updated with the zoomed range)
1138
+ * @param scaleExtent The smallest and largest scale factors allowed
1139
+ * @param zoomFactor The *cumulative* zoom scale factor for this event -- see
1140
+ * {@link calculateOrdinalConstrainedZoomFor}
1141
+ * @param plotDimensions The dimensions of the plot
1142
+ */
1143
+ function calcOrdinalZoomAndUpdate(value, axisId, margin, setRangeFor, setOriginalRangeFor, axesState, ranges, scaleExtent, zoomFactor, plotDimensions) {
948
1144
  const [, zoomMax] = scaleExtent;
949
1145
  axesState.axisFor(axisId).ifPresent(axis => {
950
1146
  const range = ranges.get(axisId);
951
1147
  if (range && axis) {
952
1148
  // calculate the constraint for the zoom
953
- // const [originalStart, originalEnd] = range.original
954
1149
  const constraint = isFinite(zoomMax) ?
955
1150
  [range.original.start * zoomMax, range.original.end * zoomMax] :
956
1151
  [range.original.start, range.original.end];
957
- const zoom = calculateOrdinalConstrainedZoomFor(transform, value, range, constraint);
1152
+ const zoom = calculateOrdinalConstrainedZoomFor(zoomFactor, value, range, constraint);
958
1153
  // update the axis range
959
1154
  ranges.set(axisId, zoom.range);
960
1155
  setRangeFor(axisId, zoom.range.current);
961
- const origRange = AxisInterval.from(0, plotDimensions.width);
1156
+ // the axis's pixel extent depends on its location: a top/bottom (x) axis spans the
1157
+ // plot's width, while a left/right (y) axis spans the plot's height
1158
+ const extent = axis.location === AxisLocation.Top || axis.location === AxisLocation.Bottom ?
1159
+ plotDimensions.width :
1160
+ plotDimensions.height;
1161
+ const origRange = AxisInterval.from(0, extent);
962
1162
  setOriginalRangeFor(axisId, origRange);
963
- // update the axis' range
964
- axis.update(zoom.range.current, origRange, plotDimensions, margin);
1163
+ // update the axis' range -- zooming never changes the set of categories, so pass the
1164
+ // axis's own current domain back unchanged
1165
+ axis.update(axis.scale.domain(), zoom.range.current, origRange, plotDimensions, margin);
965
1166
  }
966
1167
  });
967
1168
  }
@@ -971,27 +1172,32 @@ function calcOrdinalZoomAndUpdate(value, axisId, margin, setRangeFor, setOrigina
971
1172
  * function returns a handler function. And this handler function adjusts the range when the plot is zoomed.
972
1173
  * After calling the handler function, the plot needs to be updated as well, and this is left for the caller.
973
1174
  *
974
- * Please note that the function generated by this function has side effects -- it updates the axes ranges.
1175
+ * Please note that the function generated by this function has side effects -- it updates the axes' ranges.
975
1176
  *
976
1177
  * @param axesForSeries The distinct axes that cover all the series
977
1178
  * @param margin The plot margin
978
1179
  * @param setRangeFor Function for setting the new time-range for a specific axis
979
1180
  * @param axesState The current state of the x- or y-axes
980
- * @param scaleExtent The minimum and maximum allowed scale factors
981
- * @return A handler function for pan events
1181
+ * @param [scaleExtent=[0, Infinity]] The minimum and maximum allowed scale factors
1182
+ * @return A handler function for zoom events
982
1183
  */
983
1184
  export function continuousAxisZoomHandler(axesForSeries, margin, setRangeFor, axesState, scaleExtent = [0, Infinity]) {
984
1185
  /**
985
- * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
986
- * at the location of the mouse when the scroll wheel or gesture was applied.
987
- * @param transform The d3 zoom transformation information
988
- * @param x The x-position of the mouse when the scroll wheel or gesture is used
1186
+ * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out.
1187
+ * @param zoomFactor The *incremental* zoom scale factor for this event -- relative to each
1188
+ * axis's `.current`, not its `.original` (see {@link calculateConstrainedZoomFor}). Callers
1189
+ * must convert d3-zoom's cumulative `event.transform.k` to this incremental form themselves
1190
+ * (this event's `k` divided by the previously-applied `k`) before calling this handler.
1191
+ * @param pivotDomainValueFor Given an axis ID and its axis, returns the domain value to pivot
1192
+ * that axis's zoom around -- e.g. the mouse position inverted through that axis's own scale,
1193
+ * or (while streaming) that axis's own "now" tracking, so "now" stays fixed on screen as the
1194
+ * zoom widens/narrows the window around it instead of drifting with wherever the mouse is.
989
1195
  * @param plotDimensions The dimensions of the plot
990
1196
  * @param ranges A map holding the axis ID and its associated time-range
991
1197
  */
992
- return (transform, x, plotDimensions, ranges) => {
1198
+ return (zoomFactor, pivotDomainValueFor, plotDimensions, ranges) => {
993
1199
  // run through the axis IDs, adjust their domain, and update the time-range set for that axis
994
- axesForSeries.forEach(axisId => calcZoomAndUpdate(x, axisId, margin, setRangeFor, axesState, ranges, scaleExtent, transform, plotDimensions));
1200
+ axesForSeries.forEach(axisId => calcZoomAndUpdate(axis => pivotDomainValueFor(axisId, axis), axisId, margin, setRangeFor, axesState, ranges, scaleExtent, zoomFactor, plotDimensions));
995
1201
  // hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
996
1202
  };
997
1203
  }
@@ -1001,28 +1207,32 @@ export function continuousAxisZoomHandler(axesForSeries, margin, setRangeFor, ax
1001
1207
  * function returns a handler function. And this handler function adjusts the range when the plot is zoomed.
1002
1208
  * After calling the handler function, the plot needs to be updated as well, and this is left for the caller.
1003
1209
  *
1004
- * Please note that the function generated by this function has side effects -- it updates the axes ranges.
1210
+ * Please note that the function generated by this function has side effects -- it updates the axes' ranges.
1005
1211
  *
1006
1212
  * @param axesForSeries The distinct axes that cover all the series
1007
1213
  * @param margin The plot margin
1008
1214
  * @param setRangeFor Function for setting the new time-range for a specific axis
1009
1215
  * @param setOriginalRangeFor Function for setting the new original range for a specific axis
1010
1216
  * @param axesState The current state of the x- or y-axes
1011
- * @param scaleExtent The minimum and maximum allowed scale factors
1012
- * @return A handler function for pan events
1217
+ * @param [scaleExtent=[0, Infinity]] The minimum and maximum allowed scale factors
1218
+ * @return A handler function for zoom events
1013
1219
  */
1014
1220
  export function ordinalAxisZoomHandler(axesForSeries, margin, setRangeFor, setOriginalRangeFor, axesState, scaleExtent = [0, Infinity]) {
1015
1221
  /**
1016
1222
  * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
1017
1223
  * at the location of the mouse when the scroll wheel or gesture was applied.
1018
- * @param transform The d3 zoom transformation information
1224
+ * @param zoomFactor The *cumulative* zoom scale factor for this event -- d3-zoom's own
1225
+ * `event.transform.k`, passed through unmodified. Unlike {@link continuousAxisZoomHandler},
1226
+ * this must NOT be converted to an incremental factor -- see
1227
+ * {@link OrdinalAxisRange.scaledCumulative} for why an ordinal axis's bounded domain needs
1228
+ * the raw cumulative factor instead.
1019
1229
  * @param x The x-position of the mouse when the scroll wheel or gesture is used
1020
1230
  * @param plotDimensions The dimensions of the plot
1021
1231
  * @param ranges A map holding the axis ID and its associated time-range
1022
1232
  */
1023
- return (transform, x, plotDimensions, ranges) => {
1233
+ return (zoomFactor, x, plotDimensions, ranges) => {
1024
1234
  // run through the axis IDs, adjust their domain, and update the time-range set for that axis
1025
- axesForSeries.forEach(axisId => calcOrdinalZoomAndUpdate(x, axisId, margin, setRangeFor, setOriginalRangeFor, axesState, ranges, scaleExtent, transform, plotDimensions));
1235
+ axesForSeries.forEach(axisId => calcOrdinalZoomAndUpdate(x, axisId, margin, setRangeFor, setOriginalRangeFor, axesState, ranges, scaleExtent, zoomFactor, plotDimensions));
1026
1236
  // hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
1027
1237
  };
1028
1238
  }
@@ -1032,7 +1242,7 @@ export function ordinalAxisZoomHandler(axesForSeries, margin, setRangeFor, setOr
1032
1242
  * function returns a handler function. And this handler function adjusts the time-range when the plot is zoomed.
1033
1243
  * After calling the handler function, the plot needs to be updated as well, and this is left for the caller.
1034
1244
  *
1035
- * Please note that the function generated by this function has side effects -- it updates the axes ranges.
1245
+ * Please note that the function generated by this function has side effects -- it updates the axes' ranges.
1036
1246
  *
1037
1247
  * @param xAxesForSeries The distinct x-axes that cover all the series
1038
1248
  * @param yAxesForSeries The distinct y-axes that cover all the series
@@ -1041,46 +1251,50 @@ export function ordinalAxisZoomHandler(axesForSeries, margin, setRangeFor, setOr
1041
1251
  * @param xAxesState The current state of the x-axes
1042
1252
  * @param yAxesState The current state of the y-axes
1043
1253
  * @param scaleExtent The smallest and largest scale factors allowed
1044
- * @return A handler function for pan events
1254
+ * @return A handler function for zoom events
1045
1255
  */
1046
1256
  export function axesZoomHandler(xAxesForSeries, yAxesForSeries, margin, setRangeFor, xAxesState, yAxesState, scaleExtent) {
1047
1257
  /**
1048
1258
  * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
1049
1259
  * at the location of the mouse when the scroll wheel or gesture was applied.
1050
- * @param transform The d3 zoom transformation information
1051
- * @param mousePosistion The position of the mouse when the scroll wheel or gesture is used
1260
+ * @param zoomFactor The *incremental* zoom scale factor for this event -- see
1261
+ * {@link continuousAxisZoomHandler} for the full explanation of why this must already be
1262
+ * incremental (not d3-zoom's raw, cumulative `event.transform.k`)
1263
+ * @param mousePosition The position of the mouse when the scroll wheel or gesture is used
1052
1264
  * @param plotDimensions The dimensions of the plot
1053
- * @param ranges A map holding the axis ID and its associated time-range
1265
+ * @param xRanges A map holding the x-axis ID and its associated time-range
1266
+ * @param yRanges A map holding the y-axis ID and its associated time-range
1054
1267
  */
1055
- return (transform, mousePosition, plotDimensions, xRanges, yRanges) => {
1268
+ return (zoomFactor, mousePosition, plotDimensions, xRanges, yRanges) => {
1056
1269
  // run through the axis IDs, adjust their domain, and update the time-range set for that axis
1057
1270
  const [x, y] = mousePosition;
1058
- xAxesForSeries.forEach(id => calcZoomAndUpdate(x, id, margin, setRangeFor, xAxesState, xRanges, scaleExtent, transform, plotDimensions));
1059
- yAxesForSeries.forEach(id => calcZoomAndUpdate(y, id, margin, setRangeFor, yAxesState, yRanges, scaleExtent, transform, plotDimensions));
1271
+ xAxesForSeries.forEach(id => calcZoomAndUpdate(axis => axis.scale.invert(x), id, margin, setRangeFor, xAxesState, xRanges, scaleExtent, zoomFactor, plotDimensions));
1272
+ yAxesForSeries.forEach(id => calcZoomAndUpdate(axis => axis.scale.invert(y), id, margin, setRangeFor, yAxesState, yRanges, scaleExtent, zoomFactor, plotDimensions));
1060
1273
  // hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
1061
1274
  };
1062
1275
  }
1063
1276
  /**
1064
- * Calculates the axis-ranges for each of the continuous numeric axes in the map
1277
+ * Calculates the axis-ranges for each of the continuous numeric axes in the map, from each axis'
1278
+ * current domain (see {@link continuousRange})
1065
1279
  * @param axes The map containing the axes and their associated IDs
1066
- * @return a map associating the axis IDs to their continuous axis-range
1280
+ * @return a map associating the axis IDs with their continuous axis-range
1067
1281
  */
1068
1282
  export function continuousAxisRanges(axes) {
1069
1283
  return continuousRange(axes);
1070
1284
  }
1071
1285
  /**
1072
- * Calculates the axis-ranges for each of the ordinal axes in the map
1286
+ * Calculates the axis-ranges for each of the ordinal axes in the map, read from each axis's own
1287
+ * scale range.
1073
1288
  * @param axes The map containing the axes and their associated IDs
1074
- * @param originalRange The original range of the axis
1075
- * @return a map associating the axis IDs to their ordinal axis-range
1289
+ * @return a map associating the axis IDs with their ordinal axis-range
1076
1290
  */
1077
- export function ordinalAxisRanges(axes, originalRange) {
1078
- return ordinalRange(axes, originalRange);
1291
+ export function ordinalAxisRanges(axes) {
1292
+ return ordinalRange(axes);
1079
1293
  }
1080
1294
  /**
1081
- * Calculates the axis interval (start, end) for each of the axis
1082
- * @param axes The axes representing the time
1083
- * @return A map associating each axis with a (start, end) interval
1295
+ * Calculates the axis interval (start, end) of each axis' current domain
1296
+ * @param axes The axes, by their IDs
1297
+ * @return A map associating each axis ID with its domain's (start, end) interval
1084
1298
  */
1085
1299
  export function continuousAxisIntervals(axes) {
1086
1300
  return new Map(Array.from(axes.entries())
@@ -1090,9 +1304,10 @@ export function continuousAxisIntervals(axes) {
1090
1304
  }));
1091
1305
  }
1092
1306
  /**
1093
- * Calculates the axis interval (start, end) for each of the axis
1094
- * @param axes The axes representing the time
1095
- * @return A map associating each axis with a (start, end) interval
1307
+ * Calculates the pixel interval (start, end) of each ordinal axis' current range, along with its
1308
+ * categories
1309
+ * @param axes The axes, by their IDs
1310
+ * @return A map associating each axis ID with its range's (start, end) interval and its categories
1096
1311
  */
1097
1312
  export function ordinalAxisIntervals(axes) {
1098
1313
  return new Map(Array.from(axes.entries())
@@ -1102,7 +1317,8 @@ export function ordinalAxisIntervals(axes) {
1102
1317
  }));
1103
1318
  }
1104
1319
  /**
1105
- * Returns the bounds on the specified continuous numeric axes
1320
+ * Returns the bounds on the specified continuous numeric axes: a new axis range (with its original
1321
+ * range the same as its current range) from each axis' current domain
1106
1322
  * @param axes A map associating an axis ID with a {@link ContinuousNumericAxis}
1107
1323
  * @return A map associating each specified axis ID with the interval covered (bounds) by the axis
1108
1324
  */
@@ -1114,13 +1330,22 @@ export function continuousRange(axes) {
1114
1330
  }));
1115
1331
  }
1116
1332
  /**
1117
- * Returns the bounds on the specified continuous numeric axes
1333
+ * Returns the bounds on the specified ordinal axes, read from each axis's own scale range.
1334
+ *
1335
+ * Previously mapped every axis ID to one shared, caller-supplied `originalRange`, discarding the
1336
+ * axis object entirely (`[id, ]`) -- harmless today only because there's no per-axis margin/offset
1337
+ * mechanism anywhere in this codebase, so every ordinal axis in a chart is architecturally
1338
+ * guaranteed to span the same pixel range already. Reading each axis's own `.scale.range()`
1339
+ * instead (mirroring {@link ordinalAxisIntervals}, which already does this correctly) removes that
1340
+ * latent footgun for a chart with multiple category axes whose ranges genuinely differ.
1118
1341
  * @param axes A map associating an axis ID with a {@link OrdinalStringAxis}
1119
- * @param originalRange The original range of the axis
1120
1342
  * @return A map associating each specified axis ID with the interval covered (bounds) by the axis
1121
1343
  */
1122
- export function ordinalRange(axes, originalRange) {
1344
+ export function ordinalRange(axes) {
1123
1345
  return new Map(Array.from(axes.entries())
1124
- .map(([id, _]) => [id, OrdinalAxisRange.from(originalRange.start, originalRange.end)]));
1346
+ .map(([id, axis]) => {
1347
+ const [start, end] = axis.scale.range();
1348
+ return [id, OrdinalAxisRange.from(start, end)];
1349
+ }));
1125
1350
  }
1126
1351
  //# sourceMappingURL=axes.js.map