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,46 +1,68 @@
1
- import {AxesAssignment, setClipPathG} from "./plot";
1
+ import {type AxesAssignment, clipToArea} from "./plot";
2
+ import {useCanvasZoomPan} from "./useCanvasZoomPan";
2
3
  import * as d3 from "d3";
3
- import {ZoomTransform} from "d3";
4
4
  import {noop} from "../utils";
5
5
  import {useChart} from "../hooks/useChart";
6
- import React, {useCallback, useEffect, useMemo, useRef} from "react";
7
- import {Datum, TimeSeries} from "../series/timeSeries";
8
- import {GSelection, SvgSelection} from "../d3types";
6
+ import {useCallback, useEffect, useEffectEvent, useMemo, useRef} from "react";
7
+ import type {CanvasContext} from "../d3types";
8
+ import {seriesAt, canvasLocalPoint, type SeriesGeometry} from "./hitTesting";
9
9
  import {
10
10
  axesForSeriesGen,
11
- BaseAxis,
12
- ContinuousNumericAxis,
11
+ type BaseAxis,
12
+ type ContinuousNumericAxis,
13
+ AxisType,
14
+ checkAxisType,
13
15
  ordinalAxisIntervals,
14
16
  ordinalAxisRanges,
15
17
  ordinalAxisZoomHandler,
16
18
  ordinalPanHandler,
17
- OrdinalStringAxis
19
+ type OrdinalStringAxis
18
20
  } from "../axes/axes";
19
- import {Subscription} from "rxjs";
20
- import {Dimensions, Margin} from "../styling/margins";
21
- import {subscriptionOrdinalXFor, WindowedOrdinalStats} from "../subscriptions/subscriptions";
22
- import {useDataObservable} from "../hooks/useDataObservable";
21
+ import type {Dimensions, Margin} from "../styling/margins";
22
+ import {ordinalViewDriverFor} from "../subscriptions/viewDrivers";
23
+ import {useDataSource, useDataSourceRunning, useInitialData, useStreamingSettings} from "../hooks/useDataSource";
24
+ import {initialOrdinalStats, type WindowedOrdinalStats} from "../datasources/ordinalDataSource";
23
25
  import {usePlotDimensions} from "../hooks/usePlotDimensions";
24
- import {useInitialData} from "../hooks/useInitialData";
25
- import {copyValueStatsForSeries, OrdinalChartData, OrdinalStats} from "../observables/ordinals";
26
- import {BaseSeries} from "../series/baseSeries";
27
- import {calculateOrdinalStats, OrdinalDatum, OrdinalSeries} from "../series/ordinalSeries";
28
- import {
29
- applyFillStylesTo,
30
- applyStrokeStylesTo,
31
- STROKE_COLOR,
32
- STROKE_OPACITY,
33
- STROKE_WIDTH,
34
- SvgFillStyle,
35
- SvgStrokeStyle
36
- } from "../styling/svgStyle";
37
- import {BarSeriesStyle, BarStyle, defaultBarSeriesStyle, LineStyle} from "../styling/barPlotStyle";
38
- import {TooltipData} from "../hooks/useTooltip";
26
+ import {type OrdinalChartData} from "../observables/ordinals";
27
+ import type {BaseSeries} from "../series/baseSeries";
28
+ import {type OrdinalDatum} from "../series/ordinalSeries";
29
+ import {applyFillStyle, applyStrokeStyle} from "../styling/canvasStyle";
30
+ import type {SvgFillStyle, SvgStrokeStyle} from "../styling/svgStyle";
31
+ import {type BarSeriesStyle, type BarStyle, defaultBarSeriesStyle} from "../styling/barPlotStyle";
32
+ import type {TooltipData} from "../hooks/useTooltip";
39
33
  import {OrdinalAxisRange} from "../axes/OrdinalAxisRange";
40
- import {AxisInterval} from "../axes/AxisInterval";
41
34
  import {Optional} from "result-fn";
35
+ import {BAR_CHART_TOOLTIP_PROVIDER_IDS} from "./constants";
36
+ import {AxisInterval} from "../axes/AxisInterval";
37
+ import {extentOf, extentWith} from "../axes/autoScale";
38
+ import {type AutoScaleAxes, growAutoScaledYAxes, useAutoScaleRestart} from "./autoScaleYAxes";
39
+
40
+ /**
41
+ * A snapshot of the bar plot's zoom/pan state, in a form that can be held outside the plot (e.g.
42
+ * in an application store) and handed back to a later instance of the plot (see the
43
+ * {@link Props.zoomState} and {@link Props.onZoomStateChange} props) -- so that zooming, navigating
44
+ * away, and navigating back doesn't reset the zoom.
45
+ */
46
+ export interface BarPlotZoomState {
47
+ /**
48
+ * d3-zoom's cumulative scale factor (`transform.k`) at the time of the snapshot. The ordinal
49
+ * zoom math depends on this matching the axis ranges (see `OrdinalAxisRange.scaledCumulative`),
50
+ * so it must be restored onto the new canvas along with the ranges.
51
+ */
52
+ k: number
53
+ /**
54
+ * Maps each ordinal x-axis ID to its current (zoomed/panned) pixel range, expressed as
55
+ * fractions of the axis' original (un-zoomed) pixel extent, so that the snapshot remains valid
56
+ * if the plot is a different width when it is restored.
57
+ */
58
+ ranges: Record<string, [startFraction: number, endFraction: number]>
59
+ }
42
60
 
43
61
  // typescript doesn't support enums with computed string values, even though they are all constants...
62
+ /**
63
+ * An ID (e.g. a tooltip provider ID) for each of the five kinds of element the bar plot draws for
64
+ * each series
65
+ */
44
66
  export type BarChartElementId = {
45
67
  readonly currentValue: string
46
68
  readonly meanValue: string
@@ -49,30 +71,9 @@ export type BarChartElementId = {
49
71
  readonly windowedMinMax: string
50
72
  }
51
73
 
52
- const STREAM_CHARTS_BAR_CHART_ID = 'stream-charts-bar-chart'
53
-
54
- // elements of the bar-chart
55
- const BAR_CHART_CLASS_IDS: BarChartElementId = {
56
- currentValue: STREAM_CHARTS_BAR_CHART_ID + '-value-lines',
57
- meanValue: STREAM_CHARTS_BAR_CHART_ID + '-mean-value-lines',
58
- minMax: STREAM_CHARTS_BAR_CHART_ID + '-min-max-bars',
59
- windowedMeanValue: STREAM_CHARTS_BAR_CHART_ID + '-windowed-mean-value-lines',
60
- windowedMinMax: STREAM_CHARTS_BAR_CHART_ID + '-windowed-mean-bars'
61
- }
62
-
63
- const classIdFor = (id: string) => '.' + id
64
-
65
- // constants identifying the bar-chart elements for which mouse-over/mouse-leave events are defined
66
- const TOOLTIP_PROVIDER_ID = STREAM_CHARTS_BAR_CHART_ID + '-tooltip-provider'
67
-
68
- export const BAR_CHART_TOOLTIP_PROVIDER_IDS: BarChartElementId = {
69
- currentValue: TOOLTIP_PROVIDER_ID + '-current-value',
70
- meanValue: TOOLTIP_PROVIDER_ID + '-mean-value',
71
- minMax: TOOLTIP_PROVIDER_ID + '-min-max',
72
- windowedMeanValue: TOOLTIP_PROVIDER_ID + '-windowed-min-max',
73
- windowedMinMax: TOOLTIP_PROVIDER_ID + '-windowed-mean'
74
- }
75
-
74
+ /**
75
+ * The properties of the {@link BarPlot}
76
+ */
76
77
  export interface Props {
77
78
  /**
78
79
  * Holds the mapping between a series and the axis it uses (is assigned). The
@@ -80,16 +81,26 @@ export interface Props {
80
81
  * object holding the ID of the assigned x-axis and y-axis.
81
82
  */
82
83
  axisAssignments?: Map<string, AxesAssignment>
84
+ /**
85
+ * Whether to show each series' lifetime min/max bar (default is true)
86
+ */
83
87
  showMinMaxBars?: boolean
88
+ /**
89
+ * Whether to show each series' windowed min/max bar (default is true)
90
+ */
84
91
  showWindowedMinMaxBars?: boolean
92
+ /**
93
+ * Whether to show each series' current-value line (default is true)
94
+ */
85
95
  showValueLines?: boolean
96
+ /**
97
+ * Whether to show each series' lifetime mean-value line (default is true)
98
+ */
86
99
  showMeanValueLines?: boolean
87
- showWindowedMeanValueLines?: boolean
88
100
  /**
89
- * The number of milliseconds worth of data to hold in memory before dropping it. Defaults to
90
- * infinity (i.e. no data is dropped)
101
+ * Whether to show each series' windowed mean-value line (default is true)
91
102
  */
92
- dropDataAfter?: number
103
+ showWindowedMeanValueLines?: boolean
93
104
  /**
94
105
  * Enables panning (default is false)
95
106
  */
@@ -104,35 +115,85 @@ export interface Props {
104
115
  */
105
116
  zoomKeyModifiersRequired?: boolean
106
117
  /**
107
- * The (optional, default = 2 pixels) top and bottom margin (in pixels) for the spike lines in the plot.
108
- * Margins on individual series can also be set through the {@link Chart.seriesStyles} property.
118
+ * The (optional, default = 2 pixels) left and right margin (in pixels) between each category's
119
+ * edges and its lines. Margins on individual series can also be set through the
120
+ * {@link ChartProps.seriesStyles} property.
109
121
  */
110
122
  barMargin?: number
111
123
  /**
112
124
  * The (optional) default style for the bar series that are used if no other styles are specified
113
125
  */
114
126
  barSeriesStyle?: BarSeriesStyle
127
+ /**
128
+ * When true, hovering over any element (bar, line) of a series also highlights the x- and
129
+ * y-axes it is plotted against, using the axis' own default highlight color/width (bar series
130
+ * styles don't carry a single flat highlight color/width the way line-series styles do, so
131
+ * this doesn't attempt to match a specific element's highlight style). Defaults to `false`.
132
+ */
133
+ highlightAxesOnMouseOver?: boolean
134
+ /**
135
+ * An (optional) zoom/pan state to restore when this plot mounts -- commonly the last value
136
+ * reported through {@link onZoomStateChange} and held by the application (e.g. in a store)
137
+ * so that the zoom survives the plot being unmounted and remounted (e.g. navigating away
138
+ * and back). Only read once, when the plot's axes are first available; later changes to this
139
+ * prop are ignored.
140
+ */
141
+ zoomState?: BarPlotZoomState
142
+ /**
143
+ * (Optional) callback that is handed the plot's zoom/pan state after each zoom event and at
144
+ * the end of each pan, so the application can hold on to it (see {@link BarPlotProps.zoomState}).
145
+ */
146
+ onZoomStateChange?: (zoomState: BarPlotZoomState) => void
147
+ /**
148
+ * Called whenever this plot (re)creates its zoom behavior, handing
149
+ * the caller a `resetZoom` function that programmatically clears d3-zoom's own accumulated
150
+ * scale state back to identity and restores the ordinal x-axes to their original (un-zoomed,
151
+ * un-panned) ranges. See {@link ScatterPlot}'s identical prop for the full explanation of why
152
+ * this is needed. Only meaningful (called) while `zoomEnabled` is true.
153
+ */
154
+ onZoomReset?: (resetZoom: () => void) => void
155
+ }
156
+
157
+ /**
158
+ * Maps the short element-type keys used internally for hit-testing geometry (`minMax`,
159
+ * `windowedMinMax`, `meanValue`, `windowedMeanValue`, `currentValue`) to the actual provider IDs
160
+ * (`BAR_CHART_TOOLTIP_PROVIDER_IDS`) that `mouseOverHandlerFor`/`mouseLeaveHandlerFor` expect.
161
+ */
162
+ const PROVIDER_ID_FOR_ELEMENT_TYPE: Record<string, string> = {
163
+ minMax: BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax,
164
+ windowedMinMax: BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax,
165
+ meanValue: BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue,
166
+ windowedMeanValue: BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue,
167
+ currentValue: BAR_CHART_TOOLTIP_PROVIDER_IDS.currentValue,
115
168
  }
116
169
 
117
170
  /**
118
- * Renders a streaming bar plot for the series in the initial data and those sourced by the
119
- * observable specified as a property in the {@link Chart}. This component uses the {@link useChart}
171
+ * Renders a streaming bar plot for the series held by the {@link StreamingDataSource} handed to the
172
+ * {@link Chart} (its `dataSource` prop) -- along with each series' lifetime and windowed stats,
173
+ * which an {@link OrdinalDataSource} keeps. This component uses the {@link useChart}
120
174
  * hook, and therefore must be a child of the {@link Chart} to be plugged in to the
121
175
  * chart ecosystem (axes, tracker, tooltip).
122
176
  *
177
+ * Internally, this no longer creates/updates SVG `<rect>`/`<line>` elements. Instead, it registers
178
+ * a single draw function with the chart's {@link CanvasContext} that redraws every series' bars
179
+ * and lines from scratch each time the canvas repaints. Mouse hover (for the tooltip and the
180
+ * value-line/windowed-mean-line highlight) is handled by hit-testing the mouse position against
181
+ * the last-drawn geometry, keyed per `${seriesName}::${elementType}` so the five overlaid visual
182
+ * elements (min-max bar, windowed min-max bar, mean line, windowed mean line, value line) can be
183
+ * hit-tested and highlighted independently, matching the old per-element SVG mouseover behavior.
184
+ *
123
185
  * For a relatively complete example of how to use this plot component, see the
124
186
  * <a href="https://github.com/robphilipp/stream-charts-examples">`StreamingBarChart` example</a>
125
187
  *
126
188
  * @param props The properties associated with the bar plot
189
+ * @return `null` (the plot draws onto the chart's canvas)
127
190
  * @example
128
191
  * ```typescript
129
192
  * <BarPlot
130
193
  * barMargin={1}
131
- * dropDataAfter={5000}
132
194
  * panEnabled={true}
133
195
  * zoomEnabled={true}
134
196
  * zoomKeyModifiersRequired={true}
135
- * withCadenceOf={50}
136
197
  *
137
198
  * showMinMaxBars={true}
138
199
  * showValueLines={true}
@@ -145,8 +206,7 @@ export interface Props {
145
206
  export function BarPlot(props: Props): null {
146
207
  const {
147
208
  chartId,
148
- container,
149
- mainG,
209
+ canvasContext,
150
210
  axes,
151
211
  seriesStyles,
152
212
  seriesFilter,
@@ -159,28 +219,29 @@ export function BarPlot(props: Props): null {
159
219
  setAxisAssignments,
160
220
  setAxisIntervalFor,
161
221
  setOriginalAxisIntervalFor,
162
- axesRanges
222
+ axesRanges,
223
+ axisRangeFor,
224
+ updateAxisRanges,
163
225
  } = axes
164
226
 
227
+
165
228
  const {mouseOverHandlerFor, mouseLeaveHandlerFor} = mouse
166
229
 
167
230
  const {plotDimensions, margin} = usePlotDimensions()
168
231
 
169
232
  const {
170
- seriesObservable,
171
233
  windowingTime = 100,
172
- shouldSubscribe,
173
-
174
- onSubscribe = noop,
175
- onUpdateData,
176
234
  onUpdateChartTime = noop
177
- } = useDataObservable<OrdinalChartData, OrdinalDatum>()
235
+ } = useStreamingSettings()
178
236
 
179
- const {initialData} = useInitialData<OrdinalChartData, OrdinalDatum>()
237
+ // the application-owned source of the data, and whether it's running (streaming)
238
+ const dataSource = useDataSource<OrdinalChartData, OrdinalDatum>()
239
+ const running = useDataSourceRunning(dataSource)
240
+
241
+ const {initialData} = useInitialData<OrdinalDatum>()
180
242
 
181
243
  const {
182
244
  axisAssignments = new Map<string, AxesAssignment>(),
183
- dropDataAfter = Infinity,
184
245
  panEnabled = false,
185
246
  zoomEnabled = false,
186
247
  zoomKeyModifiersRequired = true,
@@ -190,175 +251,77 @@ export function BarPlot(props: Props): null {
190
251
  showMeanValueLines = true,
191
252
  showWindowedMeanValueLines = true,
192
253
  barMargin = 2,
193
- barSeriesStyle = defaultBarSeriesStyle()
254
+ barSeriesStyle = defaultBarSeriesStyle(),
255
+ highlightAxesOnMouseOver = false,
256
+ zoomState = undefined,
257
+ onZoomStateChange = noop,
258
+ onZoomReset = noop,
194
259
  } = props
195
260
 
196
261
  // why do "dataRef" and "seriesRef" both hold on to the same underlying data? for performance.
197
262
  //
198
263
  // the "dataRef" and "seriesRef" both point to the same underlying data, a collection
199
- // of series. The series in "dataRef" are bound to the DOM elements through d3. The "seriesRef" series
200
- // are the ones that are updated as new data is streamed in.
264
+ // of series (held, and updated as new data is streamed in, by the data source). The series in
265
+ // "dataRef" are read by the canvas draw function, and the "seriesRef" map is for looking a
266
+ // series up by its name.
201
267
  //
202
268
  // the "dataRef" object holds on to a copy of the initial data (which is an array of
203
269
  // time-series, e.i. an array of BaseSeries<OrdinalDatum> objects). The slice just creates a copy of
204
270
  // the array, but the references to the BaseSeries objects are the same and still point to the same
205
271
  // data as the "initialData" array.
206
272
  //
207
- // the "seriesRef" object is a reference to a map (series_name -> BaseSeries<OrdinalDatum>) which is
208
- // used to update the data in the series. When new data enters, it is appended to one or more series.
273
+ // the "seriesRef" object is a reference to a map (series_name -> BaseSeries<OrdinalDatum>), used
274
+ // to find the series under the mouse (for the tooltip).
209
275
  //
210
- // the series in the "dataRef" object are the ones bound to the DOM elements in d3, and so as these
211
- // are updated, d3 will update the DOM elements (the elements in this plot).
276
+ // the series in the "dataRef" object are the ones read by the next redraw, so as these are
277
+ // updated, the next canvas repaint picks up the new data.
212
278
  const dataRef = useRef<Array<BaseSeries<OrdinalDatum>>>(initialData.slice())
213
279
  const seriesRef = useRef<Map<string, BaseSeries<OrdinalDatum>>>(
214
280
  new Map(initialData.map(series => [series.name, series]))
215
281
  )
216
- const statsRef = useRef<WindowedOrdinalStats>(initialOrdinalStats(dataRef.current))
282
+ // the lifetime and windowed stats for each series -- kept by the data source (when it's an
283
+ // ordinal data source), so they keep accumulating while this plot is unmounted
284
+ const statsRef = useRef<WindowedOrdinalStats>(statsFrom(dataSource, initialData))
217
285
 
218
286
  // map(axis_id -> current_time) -- maps the axis ID to the current time for that axis
219
287
  const currentTimeRef = useRef<number>(0)
220
- const subscriptionRef = useRef<Subscription>(undefined)
221
-
222
- const isSubscriptionClosed = () => subscriptionRef.current === undefined || subscriptionRef.current.closed
223
-
224
- const allowTooltipRef = useRef<boolean>(isSubscriptionClosed())
225
-
226
- useEffect(
227
- () => {
228
- currentTimeRef.current = 0
229
- },
230
- [xAxesState]
231
- )
232
-
233
- // set the axis assignments needed if a tooltip is being used
234
- useEffect(
235
- () => {
236
- setAxisAssignments(axisAssignments)
237
- },
238
- [axisAssignments, setAxisAssignments]
239
- )
240
288
 
241
- // calculates the distinct series IDs that cover all the series in the plot
242
- const axesForSeries = useMemo(
243
- () => axesForSeriesGen<OrdinalDatum, OrdinalStringAxis>(initialData, axisAssignments, xAxesState),
244
- [initialData, axisAssignments, xAxesState]
245
- )
289
+ // tooltips are only shown while the data source is paused (and not while panning)
290
+ const allowTooltipRef = useRef<boolean>(!(dataSource?.running ?? false))
246
291
 
247
- // updates the timing using the onUpdateTime and updatePlot references. This and the references
248
- // defined above allow the axes' times to be updated properly by avoid stale reference to these
249
- // functions.
250
- const updateTimingAndPlot = useCallback(
251
- (ranges: Map<string, OrdinalAxisRange>): void => {
252
- if (mainG !== null) {
253
- updatePlotRef.current(ranges, mainG)
254
- onUpdateChartTime(currentTimeRef.current)
255
- updatePlotRef.current(ranges, mainG)
256
- }
257
- },
258
- [mainG, onUpdateChartTime]
259
- )
260
-
261
- // todo find better way
262
- // when the initial data changes, then reset the plot. note that the initial data doesn't change
263
- // during the normal course of updates from the observable, only when the plot is restarted.
264
- useEffect(
265
- () => {
266
- dataRef.current = initialData.slice()
267
- seriesRef.current = new Map(initialData.map(series => [series.name, series]))
268
- currentTimeRef.current = 0
269
- statsRef.current = initialOrdinalStats(dataRef.current)
270
- },
271
- // ** not happy about this **
272
- // only want this effect to run when the initial data is changed, which mean all the
273
- // other dependencies are recalculated anyway.
274
- // eslint-disable-next-line react-hooks/exhaustive-deps
275
- [initialData]
276
- )
277
-
278
- /**
279
- * Adjusts the time-range and updates the plot when the plot is dragged to the left or right
280
- * @param deltaX The amount that the plot is dragged
281
- * @param plotDimensions The dimensions of the plot
282
- * @param series An array of series names
283
- * @param ranges A map holding the axis ID and its associated time range
284
- */
285
- const onPan = useCallback(
286
- (x: number,
287
- plotDimensions: Dimensions,
288
- series: Array<string>,
289
- ranges: Map<string, OrdinalAxisRange>
290
- ) => ordinalPanHandler(axesForSeries, margin, setAxisIntervalFor, xAxesState)(x, plotDimensions, series, ranges),
291
- [axesForSeries, margin, setAxisIntervalFor, xAxesState]
292
- )
292
+ // the last-drawn geometry for each `${seriesName}::${elementType}`, in canvas coordinates,
293
+ // used for hit-testing mouse hover on `mousemove` (see the effect below that wires up the
294
+ // listener)
295
+ const geometryRef = useRef<Map<string, SeriesGeometry>>(new Map())
296
+ // which specific element (series + type) the mouse was over on the previous `mousemove`, so
297
+ // we know when to fire a "leave" before firing an "over" for a different element -- unlike
298
+ // Scatter/Raster, BarPlot doesn't use the shared `hoveredSeriesName` chart state, since here
299
+ // hover is scoped to one of five element *types* per series, not the series as a whole
300
+ const lastHoveredRef = useRef<{seriesName: string, elementType: string} | undefined>(undefined)
293
301
 
294
302
  /**
295
- * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
296
- * at the location of the mouse when the scroll wheel or gesture was applied.
297
- * @param transform The d3 zoom transformation information
298
- * @param x The x-position of the mouse when the scroll wheel or gesture is used
299
- * @param plotDimensions The dimensions of the plot
300
- * @param series An array of series names
301
- * @param ranges A map holding the axis ID and its associated time-range
302
- */
303
- const onZoom = useCallback(
304
- (
305
- transform: ZoomTransform,
306
- x: number,
307
- plotDimensions: Dimensions,
308
- ranges: Map<string, OrdinalAxisRange>,
309
- ) => ordinalAxisZoomHandler(axesForSeries, margin, setAxisIntervalFor, setOriginalAxisIntervalFor, xAxesState)(transform, x, plotDimensions, ranges),
310
- [axesForSeries, margin, setAxisIntervalFor, setOriginalAxisIntervalFor, xAxesState]
311
- )
312
-
313
- /**
314
- * @param ordinalRanges
315
- * @param mainGElem
303
+ * (Re-)registers this plot's draw function with the canvas context and requests a redraw.
304
+ * Replaces the old version, which directly mutated SVG `<rect>`/`<line>` elements bound via
305
+ * d3's enter/update/exit join. Canvas has no persistent elements to join against, so the draw
306
+ * function just redraws every element from current data/scale state each time it's invoked.
307
+ *
308
+ * Pan/zoom behavior setup lives in a separate effect (see below), not here -- this function
309
+ * runs on every data tick (each `windowingTime` interval), and recreating/reattaching a
310
+ * `d3.drag()`/`d3.zoom()` behavior that often is pure overhead unrelated to drawing the new
311
+ * data.
312
+ * @param cc The canvas context to register the draw function with
316
313
  */
317
314
  const updatePlot = useCallback(
318
- (ordinalRanges: Map<string, OrdinalAxisRange>, mainGElem: GSelection) => {
319
- if (container) {
320
- // select the svg element bind the data to them
321
- const svg: SvgSelection = d3.select<SVGSVGElement, any>(container)
322
-
323
- // set up panning
324
- if (panEnabled) {
325
- const drag = d3.drag<SVGSVGElement, Datum>()
326
- .on("start", () => {
327
- d3.select(container).style("cursor", "move")
328
- allowTooltipRef.current = false
329
- })
330
- .on("drag", (event: any) => {
331
- const names = dataRef.current.map(series => series.name)
332
- onPan(event.dx, plotDimensions, names, ordinalRanges)
333
- // need to update the plot with the new time-ranges
334
- updatePlotRef.current(ordinalRanges, mainGElem)
335
- })
336
- .on("end", () => {
337
- d3.select(container).style("cursor", "auto")
338
- allowTooltipRef.current = isSubscriptionClosed()
339
- })
315
+ (cc: CanvasContext) => {
316
+ const draw = (context: CanvasContext) => {
317
+ const {context2D} = context
340
318
 
341
- svg.call(drag)
342
- }
319
+ context2D.save()
320
+ clipToArea(context, plotDimensions, {x: margin.left, y: margin.top})
321
+ context2D.translate(margin.left, margin.top)
343
322
 
344
- // set up for zooming
345
- if (zoomEnabled) {
346
- const zoom = d3.zoom<SVGSVGElement, Datum>()
347
- .filter((event: any) => !zoomKeyModifiersRequired || event.shiftKey || event.ctrlKey)
348
- .scaleExtent([1, 10])
349
- .translateExtent([[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]])
350
- .on("zoom", (event: any) => {
351
- onZoom(
352
- event.transform,
353
- event.sourceEvent.offsetX - margin.left,
354
- plotDimensions,
355
- ordinalRanges,
356
- )
357
- updatePlotRef.current(ordinalRanges, mainGElem)
358
- }
359
- )
360
- svg.call(zoom)
361
- }
323
+ const newGeometry = new Map<string, SeriesGeometry>()
324
+ const hovered = lastHoveredRef.current
362
325
 
363
326
  // enter, update, delete the bar data
364
327
  dataRef.current.forEach(series => {
@@ -368,6 +331,7 @@ export function BarPlot(props: Props): null {
368
331
  axisId => xAxesState.axisFor(axisId).getOrUndefined(),
369
332
  axisId => yAxesState.axisFor(axisId).getOrUndefined()
370
333
  )
334
+ if (xAxis === undefined || yAxis === undefined) return
371
335
 
372
336
  // grab the series styles, or the defaults if none exist
373
337
  const {
@@ -383,13 +347,13 @@ export function BarPlot(props: Props): null {
383
347
  }
384
348
 
385
349
  // only show the data for which the regex filter matches
386
- const plotData = series.name.match(seriesFilter) ? [series.data[series.data.length - 1]] : []
350
+ const plotData = series.name.match(seriesFilter) && series.data.length > 0 ?
351
+ [series.data[series.data.length - 1]] :
352
+ []
353
+ if (plotData.length === 0) return
387
354
 
388
355
  // grab the functions for determining the lower and upper bounds of the category
389
- const {
390
- lower,
391
- upper
392
- } = xAxisCategoryBoundsFn(xAxis.scale.bandwidth(), valueLineStyle.regular.width, categoryMargin)
356
+ const {lower, upper} = xAxisCategoryBoundsFn(xAxis.scale.bandwidth(), valueLineStyle.regular.width, categoryMargin)
393
357
 
394
358
  // grab the value (index) associated with the series name (this is a category axis)
395
359
  const x = xAxis.scale(series.name) || 0
@@ -403,55 +367,20 @@ export function BarPlot(props: Props): null {
403
367
  statsRef.current.valueStatsForSeries.get(series.name)?.max.value || 0,
404
368
  yAxis
405
369
  )
406
-
407
- svg
408
- .select<SVGGElement>(`#${series.name}-${chartId}-bar`)
409
- .selectAll<SVGRectElement, PlotData>(classIdFor(BAR_CHART_CLASS_IDS.minMax))
410
- .data(plotData)
411
- .join(
412
- enter => barFor(
413
- enter.append<SVGRectElement>('rect').attr('class', BAR_CHART_CLASS_IDS.minMax),
414
- totalBar,
415
- barStyleFor(showMinMaxBars, minMaxBarStyle)
416
- ),
417
- update => barFor(
418
- update,
419
- totalBar,
420
- barStyleFor(showMinMaxBars, minMaxBarStyle)
421
- ),
422
- exit => exit.remove()
423
- )
424
- .on(
425
- "mouseover",
426
- (event,) =>
427
- handleMouseOverBar(
428
- container,
429
- yAxis,
430
- series,
431
- statsRef.current,
432
- event,
433
- margin,
434
- seriesStyles,
435
- barSeriesStyle,
436
- allowTooltipRef.current && showMinMaxBars,
437
- mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax),
438
- BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax
439
- )
440
- )
441
- .on(
442
- "mouseleave",
443
- event => handleMouseLeaveSeries(
444
- series.name,
445
- event.currentTarget,
446
- seriesStyles,
447
- barSeriesStyle,
448
- mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax),
449
- BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax
450
- )
451
- )
370
+ drawBar(context2D, totalBar, barStyleFor(showMinMaxBars, minMaxBarStyle))
371
+ newGeometry.set(`${series.name}::minMax`, {
372
+ points: [],
373
+ rects: [{
374
+ x: totalBar.upperX + margin.left,
375
+ y: totalBar.upperY + margin.top,
376
+ width: totalBar.width,
377
+ height: totalBar.height
378
+ }],
379
+ hitRadius: 0
380
+ })
452
381
 
453
382
  //
454
- // windowed-mean line when the windowed stats are defined for the series
383
+ // windowed-mean line and windowed min/max bar, when the windowed stats are defined for the series
455
384
  const seriesWindowedStats = statsRef.current.windowedValueStatsForSeries.get(series.name)
456
385
  if (seriesWindowedStats !== undefined) {
457
386
  const windowedBar = barDimensions(
@@ -460,307 +389,377 @@ export function BarPlot(props: Props): null {
460
389
  seriesWindowedStats.min.value, seriesWindowedStats.max.value,
461
390
  yAxis
462
391
  )
463
-
464
- svg
465
- .select<SVGGElement>(`#${series.name}-${chartId}-bar`)
466
- .selectAll<SVGRectElement, PlotData>(classIdFor(BAR_CHART_CLASS_IDS.windowedMinMax))
467
- .data(plotData)
468
- .join(
469
- enter => barFor(
470
- enter.append<SVGRectElement>('rect').attr('class', BAR_CHART_CLASS_IDS.windowedMinMax),
471
- windowedBar,
472
- barStyleFor(showWindowedMinMaxBars, windowedBarStyle)
473
- ),
474
- update => barFor(
475
- update,
476
- windowedBar,
477
- barStyleFor(showWindowedMinMaxBars, windowedBarStyle)),
478
- exit => exit.remove()
479
- )
480
- .on(
481
- "mouseover",
482
- (event,) =>
483
- handleMouseOverBar(
484
- container,
485
- yAxis,
486
- series,
487
- statsRef.current,
488
- event,
489
- margin,
490
- seriesStyles,
491
- barSeriesStyle,
492
- allowTooltipRef.current && showWindowedMinMaxBars,
493
- mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax),
494
- BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax
495
- )
496
- )
497
- .on(
498
- "mouseleave",
499
- event => handleMouseLeaveSeries(
500
- series.name,
501
- event.currentTarget,
502
- seriesStyles,
503
- barSeriesStyle,
504
- mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax),
505
- BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax
506
- )
507
- )
392
+ drawBar(context2D, windowedBar, barStyleFor(showWindowedMinMaxBars, windowedBarStyle))
393
+ newGeometry.set(`${series.name}::windowedMinMax`, {
394
+ points: [],
395
+ rects: [{
396
+ x: windowedBar.upperX + margin.left,
397
+ y: windowedBar.upperY + margin.top,
398
+ width: windowedBar.width,
399
+ height: windowedBar.height
400
+ }],
401
+ hitRadius: 0
402
+ })
508
403
 
509
404
  //
510
405
  // mean line
511
- const meanLineY = yAxis.scale(statsRef.current.valueStatsForSeries.get(series.name)?.mean || 0)
512
- svg
513
- .select<SVGGElement>(`#${series.name}-${chartId}-bar`)
514
- .selectAll<SVGLineElement, PlotData>(classIdFor(BAR_CHART_CLASS_IDS.meanValue))
515
- .data(showMeanValueLines ? plotData : [])
516
- .join(
517
- enter => lineFor(
518
- enter.append<SVGLineElement>('line').attr('class', BAR_CHART_CLASS_IDS.meanValue),
519
- {
520
- x1: () => lower(x), y1: () => meanLineY,
521
- x2: () => upper(x), y2: () => meanLineY
522
- },
523
- meanValueLineStyle.regular
524
- ),
525
- update => lineFor(
526
- update,
527
- {
528
- x1: () => lower(x), y1: () => meanLineY,
529
- x2: () => upper(x), y2: () => meanLineY
530
- },
531
- meanValueLineStyle.regular
532
- ),
533
- exit => exit.remove()
534
- )
535
- .on(
536
- "mouseover",
537
- (event,) =>
538
- handleMouseOverBar(
539
- container,
540
- yAxis,
541
- series,
542
- statsRef.current,
543
- event,
544
- margin,
545
- seriesStyles,
546
- barSeriesStyle,
547
- allowTooltipRef.current && showMeanValueLines,
548
- mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue),
549
- BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue
550
- )
551
- )
552
- .on(
553
- "mouseleave",
554
- event => handleMouseLeaveSeries(
555
- series.name,
556
- event.currentTarget,
557
- seriesStyles,
558
- barSeriesStyle,
559
- mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue),
560
- BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue
561
- )
562
- )
406
+ if (showMeanValueLines) {
407
+ const meanLineY = yAxis.scale(statsRef.current.valueStatsForSeries.get(series.name)?.mean || 0)
408
+ drawLine(context2D, lower(x), meanLineY, upper(x), meanLineY, meanValueLineStyle.regular)
409
+ newGeometry.set(`${series.name}::meanValue`, {
410
+ points: [],
411
+ segments: [[
412
+ [lower(x) + margin.left, meanLineY + margin.top],
413
+ [upper(x) + margin.left, meanLineY + margin.top]
414
+ ]],
415
+ hitRadius: Math.max(4, meanValueLineStyle.regular.width ?? 1)
416
+ })
417
+ }
563
418
 
564
- const windowedMeanLineY = yAxis.scale(isNaN(seriesWindowedStats.mean) ? 0 : seriesWindowedStats.mean)
565
- svg
566
- .select<SVGGElement>(`#${series.name}-${chartId}-bar`)
567
- .selectAll<SVGLineElement, PlotData>(classIdFor(BAR_CHART_CLASS_IDS.windowedMeanValue))
568
- .data(showWindowedMeanValueLines ? plotData : [])
569
- .join(
570
- enter => lineFor(
571
- enter.append<SVGLineElement>('line').attr('class', BAR_CHART_CLASS_IDS.windowedMeanValue),
572
- {
573
- x1: () => lower(x), y1: () => windowedMeanLineY,
574
- x2: () => upper(x), y2: () => windowedMeanLineY
575
- },
576
- windowedMeanLineStyle.regular
577
- ),
578
- update => lineFor(
579
- update,
580
- {
581
- x1: () => lower(x), y1: () => windowedMeanLineY,
582
- x2: () => upper(x), y2: () => windowedMeanLineY
583
- },
584
- windowedMeanLineStyle.regular
585
- ),
586
- exit => exit.remove()
587
- )
588
- .on(
589
- "mouseover",
590
- (event,) =>
591
- handleMouseOverBar(
592
- container,
593
- yAxis,
594
- series,
595
- statsRef.current,
596
- event,
597
- margin,
598
- seriesStyles,
599
- barSeriesStyle,
600
- allowTooltipRef.current && showWindowedMeanValueLines,
601
- mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue),
602
- BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue
603
- )
604
- )
605
- .on(
606
- "mouseleave",
607
- event => handleMouseLeaveSeries(
608
- series.name,
609
- event.currentTarget,
610
- seriesStyles,
611
- barSeriesStyle,
612
- mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue),
613
- BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue
614
- )
615
- )
419
+ //
420
+ // windowed-mean line
421
+ if (showWindowedMeanValueLines) {
422
+ const windowedMeanLineY = yAxis.scale(isNaN(seriesWindowedStats.mean) ? 0 : seriesWindowedStats.mean)
423
+ const isHovered = hovered?.seriesName === series.name && hovered.elementType === 'windowedMeanValue'
424
+ const style = isHovered ? windowedMeanLineStyle.highlight : windowedMeanLineStyle.regular
425
+ drawLine(context2D, lower(x), windowedMeanLineY, upper(x), windowedMeanLineY, style)
426
+ newGeometry.set(`${series.name}::windowedMeanValue`, {
427
+ points: [],
428
+ segments: [[
429
+ [lower(x) + margin.left, windowedMeanLineY + margin.top],
430
+ [upper(x) + margin.left, windowedMeanLineY + margin.top]
431
+ ]],
432
+ hitRadius: Math.max(4, style.width ?? 1)
433
+ })
434
+ }
616
435
  }
617
436
 
618
437
  //
619
- // value lines
620
- svg
621
- .select<SVGGElement>(`#${series.name}-${chartId}-bar`)
622
- .selectAll<SVGLineElement, PlotData>(classIdFor(BAR_CHART_CLASS_IDS.currentValue))
623
- .data(showValueLines ? plotData : [])
624
- .join(
625
- enter => lineFor(
626
- enter.append<SVGLineElement>('line').attr('class', BAR_CHART_CLASS_IDS.currentValue),
627
- {
628
- x1: () => lower(x), y1: datum => yAxis.scale(datum.value),
629
- x2: () => upper(x), y2: datum => yAxis.scale(datum.value)
630
- },
631
- valueLineStyle.regular
632
- ),
633
- update => lineFor(
634
- update,
635
- {
636
- x1: () => lower(x), y1: datum => yAxis.scale(datum.value),
637
- x2: () => upper(x), y2: datum => yAxis.scale(datum.value)
638
- },
639
- valueLineStyle.regular
640
- ),
641
- exit => exit.remove()
642
- )
643
- .on(
644
- "mouseover",
645
- (event,) =>
646
- handleMouseOverBar(
647
- container,
648
- yAxis,
649
- series,
650
- statsRef.current,
651
- event,
652
- margin,
653
- seriesStyles,
654
- barSeriesStyle,
655
- allowTooltipRef.current && showValueLines,
656
- mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.currentValue),
657
- BAR_CHART_TOOLTIP_PROVIDER_IDS.currentValue
658
- )
659
- )
660
- .on(
661
- "mouseleave",
662
- event => handleMouseLeaveSeries(
663
- series.name,
664
- event.currentTarget,
665
- seriesStyles,
666
- barSeriesStyle,
667
- mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.currentValue),
668
- BAR_CHART_TOOLTIP_PROVIDER_IDS.currentValue
669
- )
670
- )
438
+ // value line
439
+ if (showValueLines) {
440
+ const datum = plotData[0]
441
+ const valueLineY = yAxis.scale(datum.value)
442
+ const isHovered = hovered?.seriesName === series.name && hovered.elementType === 'currentValue'
443
+ const style = isHovered ? valueLineStyle.highlight : valueLineStyle.regular
444
+ drawLine(context2D, lower(x), valueLineY, upper(x), valueLineY, style)
445
+ newGeometry.set(`${series.name}::currentValue`, {
446
+ points: [],
447
+ segments: [[
448
+ [lower(x) + margin.left, valueLineY + margin.top],
449
+ [upper(x) + margin.left, valueLineY + margin.top]
450
+ ]],
451
+ hitRadius: Math.max(4, style.width ?? 1)
452
+ })
453
+ }
454
+ })
455
+
456
+ geometryRef.current = newGeometry
457
+
458
+ context2D.restore()
459
+ }
460
+
461
+ /**
462
+ * Grows the auto-scaling y-axes (see `ContinuousAxis`'s `autoScale` prop) to fit what's
463
+ * shown of the series whose categories are on screen (within the x-axis' current,
464
+ * possibly zoomed, window): the min/max bars, the windowed min/max bars, the mean and
465
+ * windowed-mean lines, and the value lines -- each only when it's shown. Registered to
466
+ * run before the axes draw (they're at z-index 0), so the axes and the bars are drawn
467
+ * with the grown domain in the same frame.
468
+ */
469
+ const autoScaleYAxes = (): void => {
470
+ const extents = new Map<string, [min: number, max: number]>()
471
+ dataRef.current.forEach(series => {
472
+ if (!series.name.match(seriesFilter) || series.data.length === 0) return
473
+ const [xAxis, yAxis] = axesFor(
474
+ series.name,
475
+ axisAssignments,
476
+ axisId => xAxesState.axisFor(axisId).getOrUndefined(),
477
+ axisId => yAxesState.axisFor(axisId).getOrUndefined()
478
+ )
479
+ if (xAxis === undefined || yAxis?.autoScale === undefined) return
480
+
481
+ // only the categories (at least partly) on screen
482
+ const x = xAxis.scale(series.name)
483
+ if (x === undefined || x + xAxis.scale.bandwidth() <= 0 || x >= plotDimensions.width) return
484
+
485
+ // what's shown of the series (mirrors the draw function above)
486
+ const values: Array<number> = []
487
+ const stats = statsRef.current.valueStatsForSeries.get(series.name)
488
+ if (showMinMaxBars && stats !== undefined) values.push(stats.min.value, stats.max.value)
489
+ const windowedStats = statsRef.current.windowedValueStatsForSeries.get(series.name)
490
+ if (windowedStats !== undefined) {
491
+ if (showWindowedMinMaxBars) values.push(windowedStats.min.value, windowedStats.max.value)
492
+ if (showMeanValueLines && stats !== undefined) values.push(stats.mean)
493
+ if (showWindowedMeanValueLines) values.push(windowedStats.mean)
494
+ }
495
+ if (showValueLines) values.push(series.data[series.data.length - 1].value)
496
+
497
+ const seriesExtent = extentOf(values, yAxis.scale)
498
+ if (seriesExtent === undefined) return
499
+ const extent = extents.get(yAxis.axisId)
500
+ extents.set(
501
+ yAxis.axisId,
502
+ extent === undefined ? seriesExtent : extentWith(extentWith(extent, seriesExtent[0]), seriesExtent[1])
503
+ )
504
+ })
505
+ // (the y-axes are continuous axes, even though this plot's axes context is typed
506
+ // for its ordinal x-axes)
507
+ growAutoScaledYAxes(extents, {
508
+ yAxes: yAxesState.axes,
509
+ axisRangeFor,
510
+ updateAxisRanges: updateAxisRanges as unknown as AutoScaleAxes['updateAxisRanges'],
671
511
  })
672
512
  }
513
+
514
+ cc.register(`bar-plot-autoscale-${chartId}`, autoScaleYAxes, -10)
515
+ cc.register(`bar-plot-${chartId}`, draw, 10)
516
+ cc.requestRedraw()
673
517
  },
674
518
  [
675
- container,
676
- panEnabled, zoomEnabled,
677
- onPan, onZoom,
678
- plotDimensions, margin, zoomKeyModifiersRequired,
679
- axisAssignments, xAxesState, yAxesState,
680
- barMargin, seriesStyles, barSeriesStyle,
681
- seriesFilter,
682
- chartId,
683
- showValueLines,
684
- showMinMaxBars,
685
- mouseOverHandlerFor, mouseLeaveHandlerFor,
686
- showMeanValueLines,
687
- showWindowedMeanValueLines,
688
- showWindowedMinMaxBars
519
+ chartId, plotDimensions, axisAssignments, xAxesState, yAxesState, barMargin, seriesStyles,
520
+ barSeriesStyle, seriesFilter, showValueLines, showMinMaxBars, showMeanValueLines,
521
+ showWindowedMeanValueLines, showWindowedMinMaxBars, axisRangeFor, updateAxisRanges,
522
+ margin.left, margin.top
689
523
  ]
690
524
  )
691
525
 
526
+ // new data, new scale: restarts the auto-scaling y-axes when the data source is replaced
527
+ useAutoScaleRestart(dataSource, {
528
+ yAxes: yAxesState.axes,
529
+ axisRangeFor,
530
+ updateAxisRanges: updateAxisRanges as unknown as AutoScaleAxes['updateAxisRanges'],
531
+ })
532
+
692
533
  // need to keep the function references for use by the subscription, which forms a closure
693
534
  // on them. without the references, the closures become stale, and resizing during streaming
694
535
  // doesn't work properly
695
- const updatePlotRef = useRef<(ordinalRange: Map<string, OrdinalAxisRange>, g: GSelection) => void>(noop)
536
+ const updatePlotRef = useRef<(cc: CanvasContext) => void>(updatePlot)
696
537
  useEffect(
697
538
  () => {
698
- if (mainG != null && container != null) {
699
- // when the update plot function doesn't yet exist, then create the container holding the plot
700
- const clipPathId = setClipPathG(chartId, mainG, plotDimensions)
701
- if (updatePlotRef.current === noop) {
702
- mainG
703
- .selectAll<SVGGElement, BaseSeries<OrdinalDatum>>('g')
704
- .attr("clip-path", `url(#${clipPathId})`)
705
- .data<BaseSeries<OrdinalDatum>>(dataRef.current)
706
- .enter()
707
- .append('g')
708
- .attr('class', 'spikes-series')
709
- .attr('id', series => `${series.name}-${chartId}-bar`)
710
- .attr('transform', `translate(${margin.left}, ${margin.top})`)
539
+ updatePlotRef.current = updatePlot
540
+ },
541
+ [updatePlot]
542
+ )
711
543
 
712
- } else {
713
- mainG
714
- .selectAll<SVGGElement, TimeSeries>('g')
715
- .attr("clip-path", `url(#${clipPathId})`)
716
- }
717
- updatePlotRef.current = updatePlot
544
+ useEffect(
545
+ () => {
546
+ currentTimeRef.current = 0
547
+ },
548
+ [xAxesState]
549
+ )
550
+
551
+ // set the axis assignments needed if a tooltip is being used
552
+ useEffect(
553
+ () => {
554
+ setAxisAssignments(axisAssignments)
555
+ },
556
+ [axisAssignments, setAxisAssignments]
557
+ )
558
+
559
+ // calculates the distinct series IDs that cover all the series in the plot
560
+ const axesForSeries = useMemo(
561
+ () => axesForSeriesGen<OrdinalDatum, OrdinalStringAxis>(initialData, axisAssignments, xAxesState),
562
+ [initialData, axisAssignments, xAxesState]
563
+ )
564
+
565
+ // redraws the plot for newly arrived data and reports the chart time, using the updatePlot
566
+ // reference (to avoid a stale updatePlot). Deliberately leaves the axes' ranges
567
+ // (`ordinalRangesRef`) alone: the ordinal axes don't scroll as data arrives, so only this plot's
568
+ // own zoom, pan, and resize handling changes them -- replacing them here (as a previous version
569
+ // did, with the y-axes' ranges) left the x-axis zoom/pan handlers with nothing to act on until
570
+ // something else happened to rebuild the map.
571
+ const updateTimingAndPlot = useCallback(
572
+ (): void => {
573
+ if (canvasContext !== null) {
574
+ // (one redraw request is enough: `onUpdateChartTime` only reports the time to the
575
+ // application, and redraw requests are coalesced into one animation frame anyway)
576
+ updatePlotRef.current(canvasContext)
577
+ onUpdateChartTime(currentTimeRef.current)
718
578
  }
719
579
  },
720
- [chartId, container, mainG, margin, plotDimensions, updatePlot]
580
+ [canvasContext, onUpdateChartTime]
721
581
  )
722
582
 
723
- // memoized function for subscribing to the chart-data observable
724
- const subscribe = useCallback(
583
+ // when the initial data changes, then reset the plot. note that the initial data doesn't change
584
+ // during the normal course of updates from the data source, only when the data source is replaced.
585
+ useEffect(
725
586
  () => {
726
- if (seriesObservable === undefined || mainG === null) return undefined
727
- return subscriptionOrdinalXFor(
728
- seriesObservable,
729
- onSubscribe,
730
- windowingTime,
731
- axisAssignments,
732
- yAxesState,
733
- onUpdateData,
734
- dropDataAfter,
735
- updateTimingAndPlot,
736
- // as new data flows into the subscription, the subscription
737
- // updates this map directly (for performance)
738
- seriesRef.current,
739
- statsRef,
740
- (currentTime: number) => currentTimeRef.current = currentTime,
741
- AxisInterval.from(0, plotDimensions.width)
587
+ dataRef.current = initialData.slice()
588
+ seriesRef.current = new Map(initialData.map(series => [series.name, series]))
589
+ currentTimeRef.current = 0
590
+ statsRef.current = statsFrom(dataSource, dataRef.current)
591
+ },
592
+ [initialData, dataSource]
593
+ )
594
+
595
+ /**
596
+ * Adjusts the ordinal x-axes' ranges when the plot is dragged to the left or right (the caller
597
+ * redraws the plot)
598
+ * @param x The amount that the plot is dragged
599
+ * @param plotDimensions The dimensions of the plot
600
+ * @param series An array of series names
601
+ * @param ranges A map holding the axis ID and its associated range
602
+ */
603
+ const onPan = useCallback(
604
+ (x: number,
605
+ plotDimensions: Dimensions,
606
+ series: Array<string>,
607
+ ranges: Map<string, OrdinalAxisRange>
608
+ ) => ordinalPanHandler(axesForSeries, margin, setAxisIntervalFor, xAxesState)(x, plotDimensions, series, ranges),
609
+ [axesForSeries, margin, setAxisIntervalFor, xAxesState]
610
+ )
611
+
612
+ /**
613
+ * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
614
+ * at the location of the mouse when the scroll wheel or gesture was applied.
615
+ * @param zoomFactor The *cumulative* zoom scale factor for this event -- d3-zoom's own
616
+ * `event.transform.k`, passed through unmodified. See {@link OrdinalAxisRange.scaledCumulative}
617
+ * for why the ordinal x-axis needs this cumulative factor (from which it derives the
618
+ * incremental step itself, and which it snaps back to `.original` at exactly 1).
619
+ * @param x The x-position of the mouse when the scroll wheel or gesture is used
620
+ * @param plotDimensions The dimensions of the plot
621
+ * @param ranges A map holding the axis ID and its associated range
622
+ */
623
+ const onZoom = useCallback(
624
+ (
625
+ zoomFactor: number,
626
+ x: number,
627
+ plotDimensions: Dimensions,
628
+ ranges: Map<string, OrdinalAxisRange>,
629
+ ) => ordinalAxisZoomHandler(axesForSeries, margin, setAxisIntervalFor, setOriginalAxisIntervalFor, xAxesState)(zoomFactor, x, plotDimensions, ranges),
630
+ [axesForSeries, margin, setAxisIntervalFor, setOriginalAxisIntervalFor, xAxesState]
631
+ )
632
+
633
+ // holds the current per-axis ordinal ranges, kept in sync by the effect below. Replaces
634
+ // threading `ordinalRanges` through as an explicit `updatePlot` parameter, so the pan/zoom
635
+ // handlers (now set up once, in a separate effect below, rather than inside `updatePlot`
636
+ // itself) can always read the current ranges without needing `updatePlot` to be recreated
637
+ // every time the ranges change.
638
+ const ordinalRangesRef = useRef<Map<string, OrdinalAxisRange>>(new Map())
639
+
640
+ // hands the current zoom/pan state (the ordinal x-axes' ranges, as fractions of their
641
+ // original extent, and d3-zoom's cumulative `k`) to the `onZoomStateChange` callback
642
+ const reportZoomState = useCallback(
643
+ (cc: CanvasContext): void => {
644
+ const ranges: Record<string, [number, number]> = {}
645
+ ordinalRangesRef.current.forEach((range, axisId) => {
646
+ const extent = range.original.measure()
647
+ if (xAxesState.axes.has(axisId) && extent > 0) {
648
+ ranges[axisId] = [
649
+ (range.current.start - range.original.start) / extent,
650
+ (range.current.end - range.original.start) / extent
651
+ ]
652
+ }
653
+ })
654
+ onZoomStateChange({k: d3.zoomTransform(cc.canvas).k, ranges})
655
+ },
656
+ [onZoomStateChange, xAxesState.axes]
657
+ )
658
+
659
+ // panning (dragging) and zooming (the mouse wheel) -- see `useCanvasZoomPan`. The ordinal zoom
660
+ // takes d3-zoom's cumulative scale (`k`), rather than the incremental factor, so that it can
661
+ // snap back to the original range exactly when fully zoomed out (see `OrdinalAxisRange.scale`)
662
+ const {setZoomScale} = useCanvasZoomPan(canvasContext, {
663
+ panEnabled,
664
+ zoomEnabled,
665
+ keyModifiersRequired: zoomKeyModifiersRequired,
666
+ scaleExtent: [1, 10],
667
+ // (an ordinal axis zooms in as d3-zoom's scale goes up, the opposite way to the other
668
+ // plots' continuous axes, so the wheel is inverted to zoom the same way as they do)
669
+ invertWheel: true,
670
+ translateExtent: [[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]],
671
+ // (tooltips are suppressed while panning, so that one isn't left behind)
672
+ onPanStart: () => {
673
+ allowTooltipRef.current = false
674
+ },
675
+ onPan: dx => {
676
+ if (canvasContext === null) return
677
+ onPan(dx, plotDimensions, dataRef.current.map(series => series.name), ordinalRangesRef.current)
678
+ updatePlotRef.current(canvasContext)
679
+ },
680
+ onPanEnd: () => {
681
+ allowTooltipRef.current = !(dataSource?.running ?? false)
682
+ if (canvasContext !== null) reportZoomState(canvasContext)
683
+ },
684
+ onZoom: ({k, x}) => {
685
+ if (canvasContext === null) return
686
+ onZoom(k, x - margin.left, plotDimensions, ordinalRangesRef.current)
687
+ updatePlotRef.current(canvasContext)
688
+ reportZoomState(canvasContext)
689
+ },
690
+ onZoomReset,
691
+ // restores each axis's range to its original
692
+ resetView: () => {
693
+ ordinalRangesRef.current.forEach((range, axisId) => {
694
+ xAxesState.axisFor(axisId).ifPresent(axis => {
695
+ ordinalRangesRef.current.set(axisId, range.update(range.original.start, range.original.end))
696
+ setAxisIntervalFor(axisId, range.original)
697
+ axis.update(axis.scale.domain(), range.original, range.original, plotDimensions, margin)
698
+ })
699
+ })
700
+ if (canvasContext !== null) updatePlotRef.current(canvasContext)
701
+ },
702
+ })
703
+
704
+ // the zoom state to restore, captured once at mount (see the `zoomState` prop); cleared once
705
+ // it has been applied (see `restoreZoomState` below), so it's only ever applied once
706
+ const pendingZoomStateRef = useRef<BarPlotZoomState | undefined>(zoomState)
707
+
708
+ // applies the pending zoom state (if any) to the ordinal x-axes' ranges and to d3-zoom's
709
+ // transform on the canvas -- a remounted plot has a brand-new canvas, whose d3-zoom transform
710
+ // starts at identity (k = 1). Both must be restored together: the ordinal zoom math derives
711
+ // its incremental step from d3's cumulative `k` relative to the range's current/original width
712
+ // ratio, and treats `k === 1` as "snap back to fully zoomed out" (see
713
+ // `OrdinalAxisRange.scaledCumulative`), so restoring the ranges without `k` would make the
714
+ // very next wheel tick jump. Waits (leaving the state pending) until every axis in the
715
+ // snapshot exists, since the axes register themselves with the chart asynchronously.
716
+ const restoreZoomState = useCallback(
717
+ (): void => {
718
+ const pending = pendingZoomStateRef.current
719
+ if (pending === undefined) return
720
+ const axisIds = Object.keys(pending.ranges)
721
+ const ready = axisIds.every(axisId =>
722
+ ordinalRangesRef.current.has(axisId) && xAxesState.axisFor(axisId).getOrUndefined() !== undefined
742
723
  )
724
+ if (!ready || plotDimensions.width <= 0) return
725
+
726
+ axisIds.forEach(axisId => {
727
+ const range = ordinalRangesRef.current.get(axisId)
728
+ const axis = xAxesState.axisFor(axisId).getOrUndefined()
729
+ if (range === undefined || axis === undefined) return
730
+ const [startFraction, endFraction] = pending.ranges[axisId]
731
+ const {start: originalStart} = range.original
732
+ const extent = range.original.measure()
733
+ const current = AxisInterval.from(originalStart + startFraction * extent, originalStart + endFraction * extent)
734
+ ordinalRangesRef.current.set(axisId, range.update(current.start, current.end))
735
+ setAxisIntervalFor(axisId, current)
736
+ axis.update(axis.scale.domain(), current, range.original, plotDimensions, margin)
737
+ })
738
+ // (set directly, without dispatching a "zoom" event -- see `setZoomScale`)
739
+ setZoomScale(pending.k)
740
+ pendingZoomStateRef.current = undefined
743
741
  },
744
- [
745
- axisAssignments, dropDataAfter, mainG,
746
- onSubscribe, onUpdateData,
747
- seriesObservable, updateTimingAndPlot, windowingTime, yAxesState,
748
- plotDimensions.width
749
- ]
742
+ [margin, plotDimensions, setAxisIntervalFor, xAxesState, setZoomScale]
750
743
  )
751
744
 
745
+
746
+
752
747
  useEffect(
753
748
  () => {
754
- if (container && mainG) {
749
+ if (canvasContext) {
755
750
  const ordinalAxesRanges = axesRanges()
756
751
  // so this gets a bit complicated. the ordinal-ranges need to be updated whenever the ordinal-ranges
757
752
  // change. for example, when the window is resized, and then we need to update the
758
753
  // ordinal-range. however, we want to keep the ordinal-ranges to reflect their original scale so that
759
754
  // we can zoom properly (so the updates can't fuck with the scale).
760
755
  if (ordinalAxesRanges.size === 0) {
761
- // when no time-ranges have yet been created, then create them and hold on to a mutable
762
- // reference to them
763
- updatePlot(ordinalAxisRanges(xAxesState.axes, AxisInterval.from(0, plotDimensions.width)), mainG)
756
+ // when no time-ranges have yet been created, then create them and populate the
757
+ // existing ref's map in place (rather than replacing it -- reassigning `.current`
758
+ // once it's already in play elsewhere, e.g. the zoom/pan handlers, isn't allowed
759
+ // by react-hooks/immutability)
760
+ ordinalRangesRef.current.clear()
761
+ ordinalAxisRanges(xAxesState.axes)
762
+ .forEach((range, id) => ordinalRangesRef.current.set(id, range))
764
763
  } else {
765
764
  // when the ordinal-ranges already exist, then we want to update the ordinal-ranges for each
766
765
  // existing ordinal-range in a way that maintains the original scale.
@@ -777,29 +776,188 @@ export function BarPlot(props: Props): null {
777
776
  rangesMap.set(id, range.update(start, end) as OrdinalAxisRange)
778
777
  })
779
778
  })
780
- updatePlot(ordinalAxesRanges, mainG)
779
+ ordinalRangesRef.current.clear()
780
+ ordinalAxesRanges.forEach((range, id) => ordinalRangesRef.current.set(id, range))
781
+ }
782
+ // restore a zoom state handed in from a previous instance (only does anything the
783
+ // first time the axes are all available -- see `restoreZoomState`)
784
+ restoreZoomState()
785
+ updatePlot(canvasContext)
786
+ }
787
+ },
788
+ [axesRanges, canvasContext, plotDimensions.width, updatePlot, xAxesState.axes, restoreZoomState]
789
+ )
790
+
791
+ // wires up a single mousemove/mouseleave listener on the shared canvas to replace the old
792
+ // per-element SVG mouseover/mouseleave handlers. Hit-tests the mouse position against the
793
+ // last-drawn geometry (see `geometryRef`, populated by the draw function above), keyed per
794
+ // `${seriesName}::${elementType}` so the five overlaid elements resolve independently.
795
+ useEffect(
796
+ () => {
797
+ if (!canvasContext) return
798
+
799
+ const canvas = canvasContext.canvas
800
+
801
+ // highlights (or un-highlights) the x- and y-axes a series is plotted against --
802
+ // keyed on the series name alone (not the specific hovered element type), so moving
803
+ // the mouse between a series' own bar/lines doesn't flicker the highlight off and on.
804
+ // Unlike Scatter/Outlier, bar series styles don't carry a single flat highlight
805
+ // color/width (they're nested per element type), so this uses the axis' own default
806
+ // highlight styling rather than trying to match a specific element's highlight style.
807
+ const highlightAxesFor = (seriesName: string, isHighlighted: boolean): void => {
808
+ if (!highlightAxesOnMouseOver) return
809
+ const [xAxis, yAxis] = axesFor(
810
+ seriesName,
811
+ axisAssignments,
812
+ axisId => xAxesState.axisFor(axisId).getOrUndefined(),
813
+ axisId => yAxesState.axisFor(axisId).getOrUndefined()
814
+ )
815
+ xAxis?.setHighlighted(isHighlighted)
816
+ yAxis?.setHighlighted(isHighlighted)
817
+ }
818
+
819
+ const handleMove = (event: MouseEvent) => {
820
+ const [x, y] = canvasLocalPoint(event, canvas)
821
+ const hit = seriesAt(x, y, geometryRef.current)
822
+ // geometry keys are "${seriesName}::${elementType}"; split back apart
823
+ const [hitSeriesName, hitElementType] = hit !== undefined ? hit.name.split('::') : [undefined, undefined]
824
+
825
+ const previous = lastHoveredRef.current
826
+ const sameAsBefore = previous !== undefined && hitSeriesName === previous.seriesName && hitElementType === previous.elementType
827
+
828
+ if (previous?.seriesName !== hitSeriesName) {
829
+ if (previous !== undefined) highlightAxesFor(previous.seriesName, false)
830
+ if (hitSeriesName !== undefined) highlightAxesFor(hitSeriesName, true)
831
+ }
832
+
833
+ if (!sameAsBefore) {
834
+ if (previous !== undefined) {
835
+ handleMouseLeaveSeries(
836
+ previous.seriesName,
837
+ mouseLeaveHandlerFor(`tooltip-${chartId}`, PROVIDER_ID_FOR_ELEMENT_TYPE[previous.elementType])
838
+ )
839
+ }
840
+
841
+ if (hitSeriesName !== undefined && hitElementType !== undefined) {
842
+ const series = seriesRef.current.get(hitSeriesName)
843
+ const yAxis = axesFor(
844
+ hitSeriesName,
845
+ axisAssignments,
846
+ axisId => xAxesState.axisFor(axisId).getOrUndefined(),
847
+ axisId => yAxesState.axisFor(axisId).getOrUndefined()
848
+ )[1]
849
+ const showingByType: Record<string, boolean> = {
850
+ minMax: showMinMaxBars,
851
+ windowedMinMax: showWindowedMinMaxBars,
852
+ meanValue: showMeanValueLines,
853
+ windowedMeanValue: showWindowedMeanValueLines,
854
+ currentValue: showValueLines,
855
+ }
856
+ if (series !== undefined && yAxis !== undefined && allowTooltipRef.current && showingByType[hitElementType]) {
857
+ handleMouseOverBar(
858
+ yAxis,
859
+ series,
860
+ statsRef.current,
861
+ [x, y],
862
+ margin,
863
+ mouseOverHandlerFor(`tooltip-${chartId}`, PROVIDER_ID_FOR_ELEMENT_TYPE[hitElementType])
864
+ )
865
+ }
866
+ }
867
+
868
+ lastHoveredRef.current = hitSeriesName !== undefined && hitElementType !== undefined ?
869
+ {seriesName: hitSeriesName, elementType: hitElementType} :
870
+ undefined
871
+
872
+ // the value-line and windowed-mean-line highlight depends on hover state, so
873
+ // request a redraw when it changes
874
+ canvasContext.requestRedraw()
875
+ }
876
+ }
877
+
878
+ const handleLeaveCanvas = () => {
879
+ const previous = lastHoveredRef.current
880
+ if (previous !== undefined) {
881
+ handleMouseLeaveSeries(previous.seriesName, mouseLeaveHandlerFor(`tooltip-${chartId}`, PROVIDER_ID_FOR_ELEMENT_TYPE[previous.elementType]))
882
+ highlightAxesFor(previous.seriesName, false)
883
+ lastHoveredRef.current = undefined
884
+ canvasContext.requestRedraw()
781
885
  }
782
886
  }
887
+
888
+ canvas.addEventListener('mousemove', handleMove)
889
+ canvas.addEventListener('mouseleave', handleLeaveCanvas)
890
+ return () => {
891
+ canvas.removeEventListener('mousemove', handleMove)
892
+ canvas.removeEventListener('mouseleave', handleLeaveCanvas)
893
+ }
894
+ },
895
+ [
896
+ canvasContext, chartId, margin, axisAssignments, xAxesState, yAxesState,
897
+ mouseOverHandlerFor, mouseLeaveHandlerFor,
898
+ showMinMaxBars, showWindowedMinMaxBars, showMeanValueLines, showWindowedMeanValueLines, showValueLines,
899
+ highlightAxesOnMouseOver
900
+ ]
901
+ )
902
+
903
+ // the latest `updateTimingAndPlot`, for the view driver to call -- the driver is created once
904
+ // per run (see below), so it must not capture a stale one (e.g. bound to an older canvas)
905
+ const updateTimingAndPlotRef = useRef(updateTimingAndPlot)
906
+ useEffect(
907
+ () => {
908
+ updateTimingAndPlotRef.current = updateTimingAndPlot
783
909
  },
784
- [axesRanges, container, mainG, plotDimensions.width, updatePlot, xAxesState.axes]
910
+ [updateTimingAndPlot]
911
+ )
912
+
913
+ // creates the view driver that keeps this plot's view in step with the data source, using the
914
+ // current settings -- see `useStreamingTimeAxes`'s identical effect event
915
+ const createViewDriver = useEffectEvent(
916
+ (source: NonNullable<typeof dataSource>) => ordinalViewDriverFor(
917
+ source,
918
+ windowingTime,
919
+ () => updateTimingAndPlotRef.current(),
920
+ currentTime => currentTimeRef.current = currentTime,
921
+ )
785
922
  )
786
923
 
787
- // subscribe/unsubscribe to the observable chart data. when the `shouldSubscribe`
788
- // is changed to `true` and we haven't subscribed yet, then subscribe. when the
789
- // `shouldSubscribe` is `false` and we had subscribed, then unsubscribe. otherwise,
790
- // do nothing.
924
+ // while the data source is running (and this plot has a canvas), drive the view from the
925
+ // data source's updates -- see `useStreamingTimeAxes`'s identical effect. The driver belongs to this plot
926
+ // instance alone and is always torn down with it.
791
927
  useEffect(
792
928
  () => {
793
- if (shouldSubscribe && subscriptionRef.current === undefined) {
794
- subscriptionRef.current = subscribe()
795
- allowTooltipRef.current = false
796
- } else if (!shouldSubscribe && subscriptionRef.current !== undefined) {
797
- subscriptionRef.current?.unsubscribe()
798
- subscriptionRef.current = undefined
929
+ if (dataSource === undefined || !running || canvasContext === null) return
930
+ allowTooltipRef.current = false
931
+ const driver = createViewDriver(dataSource)
932
+ return () => {
933
+ driver.unsubscribe()
799
934
  allowTooltipRef.current = true
800
935
  }
801
936
  },
802
- [shouldSubscribe, subscribe]
937
+ [dataSource, running, canvasContext]
938
+ )
939
+
940
+ // this plot needs a data source (see the `Chart`'s `dataSource` prop)
941
+ useEffect(
942
+ () => {
943
+ if (dataSource === undefined) {
944
+ console.error("BarPlot requires a data source; set the enclosing Chart's `dataSource` prop")
945
+ }
946
+ },
947
+ [dataSource]
948
+ )
949
+
950
+ // unregister this plot's draw function on unmount
951
+ useEffect(
952
+ () => {
953
+ return () => {
954
+ if (canvasContext) {
955
+ canvasContext.unregister(`bar-plot-autoscale-${chartId}`)
956
+ canvasContext.unregister(`bar-plot-${chartId}`)
957
+ }
958
+ }
959
+ },
960
+ [canvasContext, chartId]
803
961
  )
804
962
 
805
963
  return null
@@ -809,11 +967,6 @@ export function BarPlot(props: Props): null {
809
967
  Helper functions and types
810
968
  */
811
969
 
812
- type PlotData = {
813
- data: OrdinalDatum,
814
- stats: OrdinalStats,
815
- }
816
-
817
970
  type BarDimensions = {
818
971
  upperX: number
819
972
  upperY: number
@@ -821,6 +974,12 @@ type BarDimensions = {
821
974
  height: number
822
975
  }
823
976
 
977
+ /**
978
+ * The bar style, made fully transparent when the bar isn't shown
979
+ * @param isVisible Whether the bar is shown
980
+ * @param style The bar's style
981
+ * @return The style (with its fill and stroke opacities 0 when the bar isn't shown)
982
+ */
824
983
  function barStyleFor(isVisible: boolean, style: BarStyle): BarStyle {
825
984
  return {
826
985
  ...style,
@@ -829,6 +988,12 @@ function barStyleFor(isVisible: boolean, style: BarStyle): BarStyle {
829
988
  }
830
989
  }
831
990
 
991
+ /**
992
+ * The fill or stroke style, made fully transparent when its element isn't shown
993
+ * @param isVisible Whether the element is shown
994
+ * @param style The fill or stroke style
995
+ * @return The style (with an opacity of 0 when the element isn't shown)
996
+ */
832
997
  function updateOpacityFor<S extends SvgFillStyle | SvgStrokeStyle>(isVisible: boolean, style: S): S {
833
998
  return {
834
999
  ...style,
@@ -837,53 +1002,51 @@ function updateOpacityFor<S extends SvgFillStyle | SvgStrokeStyle>(isVisible: bo
837
1002
  }
838
1003
 
839
1004
  /**
840
- * Sets the attributes for the bar based on the d3 selection
841
- * @param selection The d3 selection (rect SVG element)
842
- * @param dimensions The bar dimensions
1005
+ * Draws a bar (rectangle) onto the canvas context. Replaces the old `barFor`, which set SVG
1006
+ * `<rect>` attributes via a d3 selection.
1007
+ * @param context The canvas 2D context
1008
+ * @param dimensions The bar's dimensions
843
1009
  * @param style The bar style holding the fill and stroke styles
844
- * @return The updated SVG selection (rect SVG element)
845
1010
  */
846
- function barFor(
847
- selection: d3.Selection<SVGRectElement, OrdinalDatum, SVGGElement, any>,
1011
+ function drawBar(
1012
+ context: CanvasRenderingContext2D,
848
1013
  dimensions: BarDimensions,
849
1014
  style: BarStyle
850
- ): d3.Selection<SVGRectElement, OrdinalDatum, SVGGElement, any> {
851
- selection
852
- .attr('x', () => dimensions.upperX)
853
- .attr('y', () => dimensions.upperY)
854
- .attr('width', () => dimensions.width)
855
- .attr('height', () => dimensions.height)
856
-
857
- return applyStrokeStylesTo(applyFillStylesTo(selection, style.fill), style.stroke)
858
- }
1015
+ ): void {
1016
+ if (dimensions.width <= 0 || dimensions.height <= 0) return
859
1017
 
860
- type PathSegment = {
861
- x1: (datum: OrdinalDatum) => number,
862
- y1: (datum: OrdinalDatum) => number,
863
- x2: (datum: OrdinalDatum) => number,
864
- y2: (datum: OrdinalDatum) => number,
1018
+ applyFillStyle(context, style.fill)
1019
+ context.fillRect(dimensions.upperX, dimensions.upperY, dimensions.width, dimensions.height)
1020
+
1021
+ if ((style.stroke.width ?? 0) > 0) {
1022
+ applyStrokeStyle(context, style.stroke)
1023
+ context.strokeRect(dimensions.upperX, dimensions.upperY, dimensions.width, dimensions.height)
1024
+ }
865
1025
  }
866
1026
 
867
1027
  /**
868
- * Creates the line segment for values and means
869
- * @param selection The d3 selection (rect SVG element)
870
- * @param pathSegment The object containing functions for extracting the (x1, y1) and (x2, y2)
871
- * points for the path segment
1028
+ * Draws a horizontal line segment (used for the value/mean/windowed-mean lines) onto the canvas
1029
+ * context. Replaces the old `lineFor`, which set SVG `<line>` attributes via a d3 selection.
1030
+ * @param context The canvas 2D context
1031
+ * @param x1 The line's start x-coordinate
1032
+ * @param y1 The line's start y-coordinate
1033
+ * @param x2 The line's end x-coordinate
1034
+ * @param y2 The line's end y-coordinate
872
1035
  * @param strokeStyle The stroke style
873
- * @return The updated SVG selection (rect SVG element)
874
1036
  */
875
- function lineFor(
876
- selection: d3.Selection<SVGLineElement, OrdinalDatum, SVGGElement, any>,
877
- pathSegment: PathSegment,
1037
+ function drawLine(
1038
+ context: CanvasRenderingContext2D,
1039
+ x1: number,
1040
+ y1: number,
1041
+ x2: number,
1042
+ y2: number,
878
1043
  strokeStyle: Partial<SvgStrokeStyle>
879
- ) {
880
- selection
881
- .attr('x1', datum => pathSegment.x1(datum))
882
- .attr('y1', datum => pathSegment.y1(datum))
883
- .attr('x2', datum => pathSegment.x2(datum))
884
- .attr('y2', datum => pathSegment.y2(datum))
885
-
886
- return applyStrokeStylesTo(selection, strokeStyle)
1044
+ ): void {
1045
+ applyStrokeStyle(context, strokeStyle)
1046
+ context.beginPath()
1047
+ context.moveTo(x1, y1)
1048
+ context.lineTo(x2, y2)
1049
+ context.stroke()
887
1050
  }
888
1051
 
889
1052
  /**
@@ -915,17 +1078,15 @@ function barDimensions(widthFraction: number, lowerX: number, upperX: number, mi
915
1078
  }
916
1079
 
917
1080
  /**
918
- * Calculates the ordinal stats for each of the ordinal series (generally, initial data) and
919
- * returns a {@link WindowedOrdinalStats} object
920
- * @param series The array of ordinal series
921
- * @return A {@link WindowedOrdinalStats} object with the stats for each of the series
1081
+ * @param dataSource The chart's data source
1082
+ * @param series The series to calculate the stats for when the data source doesn't keep them
1083
+ * @return The data source's (live) stats when it keeps them (i.e. an ordinal data source);
1084
+ * otherwise, the stats calculated from the specified series
922
1085
  */
923
- function initialOrdinalStats(series: Array<OrdinalSeries>): WindowedOrdinalStats {
924
- const ordinalStats = calculateOrdinalStats(series)
925
- return {
926
- ...ordinalStats,
927
- windowedValueStatsForSeries: copyValueStatsForSeries(ordinalStats.valueStatsForSeries)
928
- }
1086
+ function statsFrom(dataSource: unknown, series: Array<BaseSeries<OrdinalDatum>>): WindowedOrdinalStats {
1087
+ return dataSource !== null && typeof dataSource === 'object' && 'stats' in dataSource ?
1088
+ (dataSource as {stats: WindowedOrdinalStats}).stats :
1089
+ initialOrdinalStats(series)
929
1090
  }
930
1091
 
931
1092
  /**
@@ -940,16 +1101,17 @@ type CategoryBounds = {
940
1101
  }
941
1102
 
942
1103
  /**
943
- * Attempts to locate the x- and y-axes for the specified series. If no axis is found for the
944
- * series name, then uses the default returned by the useChart() hook.
1104
+ * Attempts to locate the x- and y-axes for the specified series. If the series has no axis
1105
+ * assignment, then uses the default (first) x- and y-axes.
945
1106
  * @param seriesName Name of the series for which to retrieve the axis
946
1107
  * @param axisAssignments A map holding the series name and the associated x- and y-axes assigned
947
1108
  * to that series. Note that the series in the axis-assignment map is merely a subset of the set
948
1109
  * of series names.
949
1110
  * @param xAxisFor The function that accepts an axis ID and returns the corresponding x-axis
950
1111
  * @param yAxisFor The function that accepts an axis ID and returns the corresponding y-axis
951
- * @return A 2d tuple holding the linear x-axis as its first element and the category y-axis as
952
- * the second element.
1112
+ * @return A 2d tuple holding the category x-axis as its first element and the linear y-axis as
1113
+ * the second element (either is actually `undefined` when it isn't found, despite the type).
1114
+ * @throws Error when the x-axis isn't a category axis, or the y-axis isn't a continuous numeric axis
953
1115
  */
954
1116
  function axesFor(
955
1117
  seriesName: string,
@@ -959,25 +1121,23 @@ function axesFor(
959
1121
  ): [xAxis: OrdinalStringAxis, yAxis: ContinuousNumericAxis] {
960
1122
  const axes = axisAssignments.get(seriesName)
961
1123
  const xAxis = xAxisFor(axes?.xAxis || "")
1124
+ checkAxisType("BarPlot", "x", xAxis, AxisType.OrdinalString)
962
1125
  const xAxisCategory = xAxis as OrdinalStringAxis
963
- if (xAxis && !xAxisCategory) {
964
- throw Error("Bar plot requires that x-axis be of type CategoryAxis")
965
- }
966
1126
  const yAxis = yAxisFor(axes?.yAxis || "")
1127
+ checkAxisType("BarPlot", "y", yAxis, AxisType.ContinuousNumeric)
967
1128
  const yAxisContinuous = yAxis as ContinuousNumericAxis
968
- if (yAxis && !yAxisContinuous) {
969
- throw Error("Bar plot requires that y-axis be of type ContinuousNumericAxis")
970
- }
971
1129
  return [xAxisCategory, yAxisContinuous]
972
1130
  }
973
1131
 
974
1132
  /**
975
- * Calculates the upper and lower coordinate for the category
976
- * @param categorySize The size of the category (i.e. plot_height / num_series)
1133
+ * Calculates the lower (left) and upper (right) x-coordinates of a category's lines, inside its
1134
+ * margins
1135
+ * @param categorySize The size of the category (i.e. plot_width / num_series)
977
1136
  * @param lineWidth The width of the series line
978
- * @param margin The margin applied to the top and bottom of the spike line (vertical spacing)
979
- * @return An object with two functions, that when handed a y-coordinate, return the location
980
- * for the start (yUpper) or end (yLower) of the spikes line.
1137
+ * @param margin The margin applied to the left and right of the category's lines (horizontal spacing)
1138
+ * @return An object with two functions, that when handed the category's (left) x-coordinate,
1139
+ * return the location of the start (lower) or end (upper) of its lines (when the category is no
1140
+ * wider than the margin, the lines are just the line width long)
981
1141
  */
982
1142
  function xAxisCategoryBoundsFn(categorySize: number, lineWidth: number, margin: number): CategoryBounds {
983
1143
  if (categorySize <= margin) return {
@@ -991,97 +1151,55 @@ function xAxisCategoryBoundsFn(categorySize: number, lineWidth: number, margin:
991
1151
  }
992
1152
 
993
1153
  /**
994
- * Renders a tooltip showing for the bar in the bar chart (see {@link BarPlotTooltipContent})
995
- * @param container The chart container
1154
+ * Reports a tooltip for the hovered element of a series in the bar chart (see {@link BarPlotTooltipContent}).
1155
+ * Replaces the old version, which also mutated the hovered SVG element's stroke directly for the
1156
+ * two element types that support a highlight (`currentValue`, `windowedMeanValue`); that highlight
1157
+ * is now handled by the draw function checking the hover state (see `updatePlot`'s `draw`) rather
1158
+ * than by touching an element here.
996
1159
  * @param yAxis The y-axis
997
1160
  * @param selectedSeries The selected series
998
1161
  * @param seriesStats The statistics about the current series
999
- * @param event The mouse-over series event holding the line element
1162
+ * @param mouseCoords The `[x, y]` position of the mouse, in canvas coordinates
1000
1163
  * @param margin The plot margin
1001
- * @param barStyles The series style information (needed for (un)highlighting)
1002
- * @param defaultBarSeriesStyle The default bar series style that is used if no style is found for the series
1003
- * @param allowTooltip When set to `false` won't show tooltip, even if it is visible (used by pan)
1004
1164
  * @param mouseOverHandlerFor The handler for the mouse over (registered by the <Tooltip/>)
1005
- * @param tooltipProvider The ID of the tooltip provider
1006
1165
  */
1007
1166
  function handleMouseOverBar(
1008
- container: SVGSVGElement,
1009
1167
  yAxis: ContinuousNumericAxis,
1010
1168
  selectedSeries: BaseSeries<OrdinalDatum>,
1011
1169
  seriesStats: WindowedOrdinalStats,
1012
- event: React.MouseEvent<SVGLineElement>,
1170
+ mouseCoords: [x: number, y: number],
1013
1171
  margin: Margin,
1014
- barStyles: Map<string, BarSeriesStyle>,
1015
- defaultBarSeriesStyle: BarSeriesStyle,
1016
- allowTooltip: boolean,
1017
1172
  mouseOverHandlerFor: ((
1018
1173
  seriesName: string,
1019
1174
  value: number,
1020
1175
  tooltipData: TooltipData<OrdinalDatum, WindowedOrdinalStats>,
1021
1176
  mouseCoords: [x: number, y: number]
1022
1177
  ) => void) | undefined,
1023
- tooltipProvider?: string
1024
1178
  ): void {
1025
- // grab the time needed for the tooltip ID
1026
- const [x, y] = d3.pointer(event, container)
1179
+ const [, y] = mouseCoords
1027
1180
  const value = yAxis.scale.invert(y - margin.top)
1028
1181
 
1029
1182
  const {name: categoryName, data: selectedData} = selectedSeries
1030
1183
 
1031
- const barSeriesStyle = barStyles.get(categoryName) || defaultBarSeriesStyle
1032
- const lineStyle = lineStyleFor(tooltipProvider, barSeriesStyle)
1033
- if (lineStyle !== undefined) {
1034
- d3.select<SVGPathElement, Datum>(event.currentTarget)
1035
- .style(STROKE_COLOR, lineStyle.highlight.color)
1036
- .style(STROKE_WIDTH, lineStyle.highlight.width)
1037
- .style(STROKE_OPACITY, lineStyle.highlight.opacity)
1038
- }
1039
-
1040
- if (mouseOverHandlerFor && allowTooltip) {
1184
+ if (mouseOverHandlerFor) {
1041
1185
  // the contract for the mouse over handler is for a series
1042
- mouseOverHandlerFor(categoryName, value, {series: selectedData, metadata: seriesStats}, [x, y])
1186
+ mouseOverHandlerFor(categoryName, value, {series: selectedData, metadata: seriesStats}, mouseCoords)
1043
1187
  }
1044
1188
  }
1045
1189
 
1046
1190
  /**
1047
- * Unselects the time series and calls the mouse-leave-series handler registered for this series.
1191
+ * Calls the mouse-leave-series handler registered for this series. Replaces the old version, which
1192
+ * also reset the hovered SVG element's stroke color/width directly; that reset is now implicit --
1193
+ * once the hover state no longer matches this element, the next redraw simply paints it in its
1194
+ * normal (non-highlighted) style.
1048
1195
  * @param seriesName The name of the series (i.e. the neuron ID)
1049
- * @param segment The SVG line element representing the spike, over which the mouse is hovering.
1050
- * @param seriesStyles The styles for the series (for (un)highlighting)
1051
- * @param defaultBarSeriesStyle The default bar series style that is used if no style is found for the series
1052
1196
  * @param mouseLeaverHandlerFor Registered handler for the series when the mouse leaves
1053
- * @param tooltipProvider The tooltip provider ID for the mouse-over event
1054
1197
  */
1055
1198
  function handleMouseLeaveSeries(
1056
1199
  seriesName: string,
1057
- segment: SVGLineElement,
1058
- seriesStyles: Map<string, BarSeriesStyle>,
1059
- defaultBarSeriesStyle: BarSeriesStyle,
1060
1200
  mouseLeaverHandlerFor: ((seriesName: string) => void) | undefined,
1061
- tooltipProvider?: string
1062
1201
  ): void {
1063
- const barSeriesStyle = seriesStyles.get(seriesName) || defaultBarSeriesStyle
1064
- const lineStyle = lineStyleFor(tooltipProvider, barSeriesStyle)
1065
- if (lineStyle !== undefined) {
1066
- d3.select<SVGPathElement, Datum>(segment)
1067
- .style(STROKE_COLOR, lineStyle.regular.color)
1068
- .style(STROKE_WIDTH, lineStyle.regular.width)
1069
- .style(STROKE_OPACITY, lineStyle.regular.opacity)
1070
- }
1071
-
1072
1202
  if (mouseLeaverHandlerFor) {
1073
1203
  mouseLeaverHandlerFor(seriesName)
1074
1204
  }
1075
1205
  }
1076
-
1077
- function lineStyleFor(tooltipProvider: string | undefined, barSeriesStyle: BarSeriesStyle): LineStyle | undefined {
1078
- const {valueLine, windowedMeanValueLine} = barSeriesStyle
1079
- switch (tooltipProvider) {
1080
- case BAR_CHART_TOOLTIP_PROVIDER_IDS.currentValue:
1081
- return valueLine
1082
- case BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue:
1083
- return windowedMeanValueLine
1084
- default:
1085
- return undefined
1086
- }
1087
- }