stream-charts 1.2.0 → 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 +40 -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,38 +1,47 @@
1
- import React, {useCallback, useEffect, useMemo, useRef} from 'react'
2
- import {NoTooltipMetadata, useChart} from "../hooks/useChart";
1
+ import {useCallback, useEffect, useRef} from 'react'
2
+ import {useStreamingTimeAxes} from "./useStreamingTimeAxes";
3
+ import {useHoveredSeries} from "../hooks/useHoveredSeries";
4
+ import {useChart} from "../hooks/useChart";
5
+ import {
6
+ type ScatterTooltipMetadata,
7
+ type ScatterTooltipPoints,
8
+ scatterTooltipPointList,
9
+ scatterTooltipPoints
10
+ } from "../tooltips/tooltipUtils";
3
11
  import * as d3 from "d3";
4
- import {ZoomTransform} from "d3";
5
- import {AxesAssignment, Series, setClipPathG} from "./plot";
6
- import {Datum, TimeSeries} from "../series/timeSeries";
12
+ import {type AxesAssignment, clipToArea} from "./plot";
13
+ import type {Datum, TimeSeries} from "../series/timeSeries";
7
14
  import {
8
- axesForSeriesGen,
9
- BaseAxis,
10
- continuousAxisIntervals,
11
- continuousAxisRanges,
12
- continuousAxisZoomHandler,
13
- ContinuousNumericAxis,
15
+ type BaseAxis,
16
+ AxisType,
17
+ checkAxisType,
18
+ type ContinuousNumericAxis,
14
19
  defaultLineStyle,
15
- panHandler,
16
- SeriesLineStyle
20
+ type SeriesLineStyle
17
21
  } from "../axes/axes";
18
- import {GSelection} from "../d3types";
19
- import {Observable, Subscription} from "rxjs";
20
- import {noop} from "../utils";
21
- import {Dimensions, Margin} from "../styling/margins";
22
- import {
23
- subscriptionTimeSeriesFor,
24
- subscriptionTimeSeriesWithCadenceFor,
25
- TimeWindowBehavior
26
- } from "../subscriptions/subscriptions";
27
- import {useDataObservable} from "../hooks/useDataObservable";
28
- import {TimeSeriesChartData} from "../series/timeSeriesChartData";
29
- import {usePlotDimensions} from "../hooks/usePlotDimensions";
30
- import {useInitialData} from "../hooks/useInitialData";
31
- import {TooltipData} from "../hooks/useTooltip";
32
- import {AxisInterval} from "../axes/AxisInterval";
33
- import {Optional} from "result-fn";
22
+ import type {CanvasContext} from "../d3types";
23
+ import {seriesAt, canvasLocalPoint, type SeriesGeometry} from "./hitTesting";
24
+ import {firstIndexAtOrAfter, noop} from "../utils";
25
+ import {NoCurveFactory} from "./constants";
26
+ import {timeSeriesAxisTimes, TimeWindowBehavior} from "../subscriptions/viewDrivers";
27
+ import {useDataSource, useDataSourceRunning, useInitialData} from "../hooks/useDataSource";
28
+ import type {TimeSeriesChartData} from "../series/timeSeriesChartData";
34
29
  import {ContinuousAxisRange} from "../axes/ContinuousAxisRange";
30
+ import {extentWith, visibleExtent} from "../axes/autoScale";
31
+ import {growAutoScaledYAxes, useAutoScaleRestart} from "./autoScaleYAxes";
32
+ import {usePlotDimensions} from "../hooks/usePlotDimensions";
33
+ import type {FastShiftArray} from "fast-shift-array";
34
+
35
+ // how close (in pixels) the mouse must be to a data point to be over that point (rather than over a
36
+ // line segment) -- at least this, or a little more than the markers' radius when they're larger
37
+ const MIN_POINT_HOVER_RADIUS = 6
38
+ // the radii of the highlights on the points the tooltip shows: the hovered point, and the others
39
+ const TOOLTIP_POINT_RADIUS = 5
40
+ const TOOLTIP_NEIGHBOR_RADIUS = 3.5
35
41
 
42
+ /**
43
+ * The properties of the {@link ScatterPlot}
44
+ */
36
45
  export interface Props {
37
46
  /**
38
47
  * Holds the mapping between a series and the axis it uses (is assigned). The
@@ -45,11 +54,6 @@ export interface Props {
45
54
  * {@link https://github.com/d3/d3-shape#curves} for information on available interpolations
46
55
  */
47
56
  interpolation?: d3.CurveFactory
48
- /**
49
- * The number of milliseconds of data to hold in memory before dropping it. Defaults to
50
- * infinity (i.e. no data is dropped)
51
- */
52
- dropDataAfter?: number
53
57
  /**
54
58
  * Enables panning (default is false)
55
59
  */
@@ -72,290 +76,206 @@ export interface Props {
72
76
  * to the current time.
73
77
  */
74
78
  withCadenceOf?: number
79
+ /**
80
+ * The time window behavior for the plot. There are two options: `SCROLL` and `SQUEEZE`.
81
+ * <ol>
82
+ * <li>
83
+ * <b>SCROLL</b> will scroll the plot window as new data is added whose time
84
+ * exceeds the time window.
85
+ * </li>
86
+ * <li>
87
+ * <b>SQUEEZE</b> will compress the time-axes when new data is added whose time
88
+ * exceeds the time window.
89
+ * </li>
90
+ * </ol>
91
+ * The default behavior is to `SCROLL`.
92
+ */
75
93
  timeWindowBehavior?: TimeWindowBehavior
76
- // initialTimes?: Map<string, number>
94
+ /**
95
+ * Radius (px) of the circle marker drawn at each datum. When omitted or `undefined`,
96
+ * no markers are rendered.
97
+ */
98
+ markerRadius?: number
99
+ /**
100
+ * Whether to hide the markers while the chart is running (streaming), showing them again
101
+ * once paused. Defaults to `true` (the previous, hard-coded behavior). Set to `false` to
102
+ * keep markers visible while running.
103
+ */
104
+ hideMarkersWhileRunning?: boolean
105
+ /**
106
+ * When true, hovering over a series also highlights the x- and y-axes it is plotted
107
+ * against (in the series' own highlight color/width). Defaults to `false`.
108
+ */
109
+ highlightAxesOnMouseOver?: boolean
110
+
111
+ /**
112
+ * Called whenever this plot (re)creates its zoom behavior, handing
113
+ * the caller a `resetZoom` function that programmatically clears d3-zoom's own accumulated
114
+ * scale/pan state back to identity. This exists because d3-zoom keeps that state attached
115
+ * directly to the canvas element -- entirely separate from (and unaffected by) resetting the
116
+ * axes' own ranges, e.g. via a "Reset" button that restores the axes' domains to their
117
+ * defaults. Without calling the handed-back `resetZoom`, the *next* user zoom gesture after
118
+ * such a reset would compute its new scale against the stale, still-accumulated transform
119
+ * (from before the reset) rather than starting fresh, producing a sudden, unexpected jump.
120
+ * Only meaningful (called) while `zoomEnabled` is true.
121
+ */
122
+ onZoomReset?: (resetZoom: () => void) => void
77
123
  }
78
124
 
79
125
  /**
80
- * Renders a streaming scatter plot for the series in the initial data and those sourced by the
81
- * observable specified as a property in the {@link Chart}. This component uses the {@link useChart}
82
- * hook, and therefore must be a child of the {@link Chart} in order to be plugged in to the
83
- * chart ecosystem (axes, tracker, tooltip).
126
+ * Renders a streaming scatter plot for the series held by the {@link StreamingDataSource} handed
127
+ * to the {@link Chart} (its `dataSource` prop). This component uses the {@link useChart} hook, and
128
+ * therefore must be a child of the {@link Chart} in order to be plugged in to the chart ecosystem
129
+ * (axes, tracker, tooltip).
130
+ *
131
+ * The plot never subscribes to the data stream itself: the application owns the data source (and
132
+ * starts/stops it), and the data source keeps ingesting even while this plot is unmounted. While
133
+ * the data source is running, this plot listens to its updates through a view driver that only
134
+ * advances the plot's own view (auto-scroll, cadence) and redraws -- created when the plot mounts
135
+ * (or the data source starts) and always torn down when it unmounts (or the data source stops).
136
+ *
137
+ * Internally, this no longer creates/updates SVG `<path>`/`<circle>` elements. Instead, it
138
+ * registers a single draw function with the chart's {@link CanvasContext} that redraws every
139
+ * series from scratch each time the canvas repaints. Mouse hover (for the tooltip) is handled by
140
+ * hit-testing the mouse position against the last-drawn series geometry (see `hitTesting.ts`) on a
141
+ * `mousemove` listener attached directly to the canvas, since canvas has no per-shape events.
142
+ *
143
+ * When the data source is paused, hovering over a series shows the tooltip (when there's one),
144
+ * and highlights the points it shows; a y-axis with `autoScale` set grows to fit the data on screen.
84
145
  *
85
146
  * @param props The properties associated with the scatter plot
147
+ * @return `null` (the plot draws onto the chart's canvas)
86
148
  * @example
87
- <ScatterPlot
88
- interpolation={interpolation}
89
- axisAssignments={new Map([
90
- ['test2', assignAxes("x-axis-2", "y-axis-2")],
91
- ])}
92
- dropDataAfter={10000}
93
- panEnabled={true}
94
- zoomEnabled={true}
95
- zoomKeyModifiersRequired={true}
96
- />
149
+ * <ScatterPlot
150
+ * interpolation={interpolation}
151
+ * axisAssignments={new Map([
152
+ * ['test2', assignAxes("x-axis-2", "y-axis-2")],
153
+ * ])}
154
+ * panEnabled={true}
155
+ * zoomEnabled={true}
156
+ * zoomKeyModifiersRequired={true}
157
+ * />
97
158
  */
98
159
  export function ScatterPlot(props: Props): null {
99
160
  const {
100
161
  chartId,
101
- container,
102
- mainG,
162
+ canvasContext,
103
163
  axes,
104
164
  color,
105
165
  seriesStyles,
106
166
  seriesFilter,
107
167
  mouse,
108
- hoveredSeriesRef,
109
- } = useChart<Datum, SeriesLineStyle, NoTooltipMetadata, ContinuousAxisRange, ContinuousNumericAxis>()
168
+ backgroundColor,
169
+ } = useChart<Datum, SeriesLineStyle, ScatterTooltipMetadata, ContinuousAxisRange, ContinuousNumericAxis>()
170
+ const {hoveredSeriesName} = useHoveredSeries()
110
171
 
111
172
  const {
112
173
  initialData
113
- } = useInitialData<TimeSeriesChartData, Datum>()
174
+ } = useInitialData<Datum>()
114
175
 
115
176
  const {
116
177
  xAxesState,
117
178
  yAxesState,
118
- setAxisIntervalFor,
119
179
  updateAxisRanges = noop,
120
- onUpdateAxesInterval,
121
- axesRanges,
180
+ axisRangeFor,
122
181
  } = axes
123
182
 
124
183
  const {mouseOverHandlerFor, mouseLeaveHandlerFor} = mouse
125
184
 
126
185
  const {plotDimensions, margin} = usePlotDimensions()
127
186
 
128
- const {
129
- seriesObservable,
130
- windowingTime = 100,
131
- shouldSubscribe,
132
-
133
- onSubscribe = noop,
134
- onUpdateData,
135
- } = useDataObservable()
187
+ // the application-owned source of the data, and whether it's running (streaming)
188
+ const dataSource = useDataSource<TimeSeriesChartData, Datum, TimeSeries>()
189
+ const running = useDataSourceRunning(dataSource)
136
190
 
137
191
  const {
138
192
  axisAssignments = new Map<string, AxesAssignment>(),
139
193
  interpolation = d3.curveLinear,
140
- dropDataAfter = Infinity,
141
194
  panEnabled = false,
142
195
  zoomEnabled = false,
143
196
  zoomKeyModifiersRequired = true,
144
197
  withCadenceOf,
145
198
  timeWindowBehavior = TimeWindowBehavior.SCROLL,
199
+ markerRadius,
200
+ hideMarkersWhileRunning = true,
201
+ highlightAxesOnMouseOver = false,
202
+ onZoomReset = noop,
146
203
  } = props
147
204
 
148
- const initialTimes = useMemo(
149
- () => {
150
- return new Map<string, number>(
151
- Array.from<[string, ContinuousAxisRange]>(axesRanges().entries())
152
- .map(([axisId, range]) => ([axisId, range.original.start]))
153
- )
154
- },
155
- [axesRanges]
156
- )
157
205
 
158
206
  // why do "dataRef" and "seriesRef" both hold on to the same underlying data? for performance.
159
207
  //
160
208
  // the "dataRef" and "seriesRef" both point to the same underlying data, a collection
161
- // of series. The series in "dataRef" are bound to the DOM elements through d3. The "seriesRef" series
162
- // are the ones that are updated as new data is streamed in.
209
+ // of series (held, and updated as new data is streamed in, by the data source). The "dataRef"
210
+ // array is what the draw function iterates over, and the "seriesRef" map is for looking a
211
+ // series up by its name.
163
212
  //
164
213
  // the "dataRef" object holds on to a copy of the initial data (which is an array of
165
- // time-series, e.i. an array of BaseSeries<OrdinalDatum> objects). The slice just creates a copy of
214
+ // time-series, e.i. an array of BaseSeries<Datum> objects). The slice just creates a copy of
166
215
  // the array, but the references to the BaseSeries objects are the same and still point to the same
167
216
  // data as the "initialData" array.
168
217
  //
169
- // the "seriesRef" object is a reference to a map (series_name -> BaseSeries<OrdinalDatum>) which is
170
- // used to update the data in the series. When new data enters, it is appended to one or more series.
218
+ // the "seriesRef" object is a reference to a map (series_name -> BaseSeries<Datum>), used to find
219
+ // the series under the mouse (for the tooltip).
171
220
  //
172
- // the series in the "dataRef" object are the ones bound to the DOM elements in d3, and so as these
173
- // are updated, d3 will update the DOM elements (the elements in this plot).
221
+ // the series in the "dataRef" object are the ones read by the canvas draw function, and so as
222
+ // these are updated, the new data is picked up the next time the canvas redraws.
174
223
  const dataRef = useRef<Array<TimeSeries>>(initialData.slice() as Array<TimeSeries>)
175
224
  const seriesRef = useRef<Map<string, TimeSeries>>(new Map(initialData.map(series => [series.name, series as TimeSeries])))
176
- // map(axis_id -> current_time) -- maps the axis ID to the current time for that axis
177
- const currentTimeRef = useRef<Map<string, number>>(new Map())
178
-
179
- const subscriptionRef = useRef<Subscription>(undefined)
180
- const isSubscriptionClosed = () => subscriptionRef.current === undefined || subscriptionRef.current.closed
181
-
182
- const allowTooltip = useRef<boolean>(isSubscriptionClosed())
183
-
184
- useEffect(
185
- () => {
186
- currentTimeRef.current = new Map(Array.from<string>(xAxesState.axes.keys()).map(id => [id, 0]))
187
- },
188
- [xAxesState]
189
- )
190
-
191
- // calculates the distinct series IDs that cover all the series in the plot
192
- const axesForSeries = useMemo(
193
- (): Array<string> => axesForSeriesGen<Datum, ContinuousNumericAxis>(initialData, axisAssignments, xAxesState),
194
- [initialData, axisAssignments, xAxesState]
195
- )
196
-
197
- // updates the timing using the onUpdateTime and updatePlot references. This and the references
198
- // defined above allow the axes' times to be updated properly by avoid stale reference to these
199
- // functions.
200
- const updateTimingAndPlot = useCallback((ranges: Map<string, ContinuousAxisRange>): void => {
201
- if (mainG !== null) {
202
- onUpdateTimeRef.current(ranges)
203
- updatePlotRef.current(ranges, mainG)
204
- if (onUpdateAxesInterval) {
205
- setTimeout(() => {
206
- const times = new Map<string, AxisInterval>()
207
- ranges.forEach((range, name) => times.set(name, range.current))
208
- onUpdateAxesInterval(times)
209
- }, 0)
210
- }
211
- }
212
- },
213
- [mainG, onUpdateAxesInterval]
214
- )
215
-
216
- // todo find better way
217
- // when the initial data changes, then reset the plot. note that the initial data doesn't change
218
- // during the normal course of updates from the observable, only when the plot is restarted.
219
- useEffect(
220
- () => {
221
- dataRef.current = initialData.slice()
222
- seriesRef.current = new Map(initialData.map(series => [series.name, series as TimeSeries]))
223
- currentTimeRef.current = new Map(Array.from<string>(xAxesState.axes.keys()).map(id => [id, 0]))
224
- updateTimingAndPlot(new Map(Array.from(continuousAxisRanges(xAxesState.axes as Map<string, ContinuousNumericAxis>).entries())
225
- .map(([id, range]) => {
226
- // grab the current range, then calculate the minimum time from the initial data, and
227
- // set that as the start, and then add the range to it for the end time
228
- const [start, end] = range.original.asTuple()
229
- const minTime = initialData
230
- .filter(srs => axisAssignments.get(srs.name)?.xAxis === id)
231
- .reduce(
232
- (tMin, series) => Math.min(
233
- tMin,
234
- !series.isEmpty() ? series.data[0].time : tMin
235
- ),
236
- Infinity
237
- )
238
- const startTime = minTime === Infinity ? 0 : minTime
239
- return [id, ContinuousAxisRange.from(startTime, startTime + end - start)]
240
- })
241
- )
242
- )
243
- },
244
- // ** not happy about this **
245
- // only want this effect to run when the initial data is changed, which mean all the
246
- // other dependencies are recalculated anyway.
247
- // eslint-disable-next-line react-hooks/exhaustive-deps
248
- [initialData]
249
- )
250
225
 
251
- /**
252
- * Adjusts the time-range and updates the plot when the plot is dragged to the left or right
253
- * @param deltaX The amount that the plot is dragged
254
- * @param plotDimensions The dimensions of the plot
255
- * @param ranges A map holding the axis ID and its associated time range
256
- */
257
- const onPan = useCallback(
258
- (x: number,
259
- plotDimensions: Dimensions,
260
- ranges: Map<string, ContinuousAxisRange>,
261
- ) => panHandler(axesForSeries, margin, setAxisIntervalFor, xAxesState)(x, plotDimensions, ranges),
262
- [axesForSeries, margin, setAxisIntervalFor, xAxesState]
263
- )
226
+ // the last-drawn geometry for each series, in canvas coordinates, used for hit-testing mouse
227
+ // hover on `mousemove` (see the effect below that wires up the listener)
228
+ const geometryRef = useRef<Map<string, SeriesGeometry>>(new Map())
229
+ // the name of the series the mouse was over on the previous `mousemove`, so we know when to
230
+ // fire a "leave" for the old series before firing an "over" for the new one
231
+ const lastHoveredRef = useRef<string | undefined>(undefined)
232
+ // the points the tooltip is showing (for the hovered series), which the plot highlights -- set
233
+ // on mouse-over while a tooltip is shown, and cleared when the mouse leaves the series
234
+ const tooltipPointsRef = useRef<{ seriesName: string, points: ScatterTooltipPoints } | undefined>(undefined)
264
235
 
265
- /**
266
- * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
267
- * at the location of the mouse when the scroll wheel or gesture was applied.
268
- * @param transform The d3 zoom transformation information
269
- * @param x The x-position of the mouse when the scroll wheel or gesture is used
270
- * @param plotDimensions The dimensions of the plot
271
- * @param series An array of series names
272
- * @param ranges A map holding the axis ID and its associated time-range
273
- */
274
- const onZoom = useCallback(
275
- (
276
- transform: ZoomTransform,
277
- x: number,
278
- plotDimensions: Dimensions,
279
- ranges: Map<string, ContinuousAxisRange>,
280
- ) => continuousAxisZoomHandler(axesForSeries, margin, setAxisIntervalFor, xAxesState)(transform, x, plotDimensions, ranges),
281
- [axesForSeries, margin, setAxisIntervalFor, xAxesState]
282
- )
236
+ // tooltips are only shown while the data source is paused (and not while panning)
237
+ const allowTooltip = useRef<boolean>(!(dataSource?.running ?? false))
283
238
 
284
239
  const updatePlot = useCallback(
285
240
  /**
286
- * Updates the plot data for the specified time-range, which may have changed due to zoom or pan
287
- * @param timeRanges The current time range
288
- * @param mainGElem The main <g> element selection for that holds the plot
241
+ * (Re-)registers this plot's draw function with the canvas context and requests a redraw.
242
+ * Replaces the old version, which directly mutated SVG `<path>`/`<circle>` elements bound
243
+ * via d3's enter/update/exit join. Canvas has no persistent elements to join against, so
244
+ * the draw function just redraws every series from its current data/scale state each time
245
+ * it's invoked -- there's no separate "update" case to handle.
246
+ *
247
+ * Pan/zoom behavior setup lives in a separate effect (see below), not here -- this
248
+ * function runs on every data tick (each `windowingTime` interval), and recreating/
249
+ * reattaching a `d3.drag()`/`d3.zoom()` behavior that often is pure overhead with no
250
+ * benefit, since panEnabled/zoomEnabled/plotDimensions/margin change far less often than
251
+ * the data does.
252
+ * @param cc The canvas context to register the draw function with
289
253
  */
290
- (timeRanges: Map<string, ContinuousAxisRange>, mainGElem: GSelection) => {
291
- if (container) {
292
- // select the svg element bind the data to them
293
- const svg = d3.select<SVGSVGElement, any>(container)
294
-
295
- // create a map associating series-names to their time-series.
254
+ (cc: CanvasContext) => {
255
+ const draw = (context: CanvasContext) => {
256
+ // const __drawStart = performance.now()
257
+ // let __totalRetained = 0
258
+ // let __totalProcessed = 0
259
+ const {context2D} = context
260
+
261
+ // create a map associating series-names with their time-series.
296
262
  //
297
- // performance-related confusion: wondering where the dataRef is updated? well it isn't
298
- // directly. The dataRef holds on to an array of references to the Series. And so does the
299
- // seriesRef, though is uses a map(series_name -> series). The seriesRef is use to append
263
+ // performance-related confusion: wondering where the dataRef is updated? well, it isn't
264
+ // updated directly. The dataRef holds on to an array of references to the Series. And so does the
265
+ // seriesRef, though it uses a map(series_name -> series). The seriesRef is use to append
300
266
  // data to the underlying Series, and the dataRef is used so that we can just use
301
267
  // dataRef.current and don't have to do Array.from(seriesRef.current.values()) which
302
268
  // creates a temporary array
303
- const boundedSeries = new Map(dataRef.current.map(series => [
269
+ const boundedSeries = new Map<string, FastShiftArray<Datum>>(dataRef.current.map(series => [
304
270
  series.name,
305
271
  series.data
306
272
  ]))
307
273
 
308
- // set up panning
309
- if (panEnabled) {
310
- const drag = d3.drag<SVGSVGElement, Datum>()
311
- .on("start", () => {
312
- d3.select(container).style("cursor", "move")
313
- // during panning, we need to disable viewing the tooltip to prevent
314
- // tooltips from rendering but not getting removed
315
- allowTooltip.current = false;
316
- })
317
- .on("drag", (event) => {
318
- onPan(
319
- event.dx,
320
- plotDimensions,
321
- // Array.from(boundedSeries.keys()),
322
- timeRanges,
323
- )
324
- updatePlotRef.current(timeRanges, mainGElem)
325
- })
326
- .on("end", () => {
327
- d3.select(container).style("cursor", "auto")
328
- // during panning, we disabled viewing the tooltip to prevent
329
- // tooltips from rendering but not getting removed, now that panning
330
- // is over, allow tooltips to render again
331
- allowTooltip.current = isSubscriptionClosed();
332
- })
333
-
334
- svg.call(drag)
335
- }
336
-
337
- // set up for zooming
338
- if (zoomEnabled) {
339
- const zoom = d3.zoom<SVGSVGElement, Datum>()
340
- .filter(event => !zoomKeyModifiersRequired || event.shiftKey || event.ctrlKey)
341
- .scaleExtent([0, 10])
342
- .translateExtent([[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]])
343
- .on("zoom", event => {
344
- onZoom(
345
- event.transform,
346
- event.sourceEvent.offsetX - margin.left,
347
- plotDimensions,
348
- timeRanges,
349
- )
350
- updatePlotRef.current(timeRanges, mainGElem)
351
- }
352
- )
353
-
354
- svg.call(zoom)
355
- }
274
+ context2D.save()
275
+ clipToArea(context, plotDimensions, {x: margin.left, y: margin.top})
276
+ context2D.translate(margin.left, margin.top)
356
277
 
357
- // define the clip-path so that the series lines don't go beyond the plot area
358
- const clipPathId = setClipPathG(chartId, mainGElem, plotDimensions)
278
+ const newGeometry = new Map<string, SeriesGeometry>()
359
279
 
360
280
  boundedSeries.forEach((data, name) => {
361
281
  // grab the x and y axes assigned to the series, and if either or both
@@ -369,204 +289,350 @@ export function ScatterPlot(props: Props): null {
369
289
  if (xAxisLinear === undefined || yAxisLinear === undefined) return
370
290
 
371
291
  // grab the style for the series
372
- const {color, lineWidth, highlightColor, highlightWidth} = seriesStyles.get(name) || defaultLineStyle()
292
+ const {
293
+ color: seriesColor,
294
+ lineWidth,
295
+ highlightColor,
296
+ highlightWidth
297
+ } = seriesStyles.get(name) || defaultLineStyle()
373
298
 
374
299
  // only show the data for which the filter matches
375
300
  const plotData = (name.match(seriesFilter)) ? data : []
376
-
377
- // create the time-series paths
378
- mainGElem
379
- .selectAll(`#${name}-${chartId}-scatter`)
380
- .data([[], plotData], () => `${name}`)
381
- .join(
382
- enter => enter
383
- .append("path")
384
- .attr("class", 'time-series-lines')
385
- .attr("id", `${name}-${chartId}-scatter`)
386
- .attr("data-series-name", name)
387
- .attr(
388
- "d",
389
- d3.line<Datum>()
390
- .x((d: Datum) => xAxisLinear.scale(d.time) || 0)
391
- .y((d: Datum) => yAxisLinear.scale(d.value) || 0)
392
- .curve(interpolation)
393
- )
394
- .attr("fill", "none")
395
- .attr("stroke", hoveredSeriesRef.current === name ? highlightColor : color)
396
- .attr("stroke-width", hoveredSeriesRef.current === name ? highlightWidth : lineWidth)
397
- .attr('transform', `translate(${margin.left}, ${margin.top})`)
398
- .attr("clip-path", `url(#${clipPathId})`)
399
- .on(
400
- "mouseover",
401
- (event, datumArray) =>
402
- // recall that this handler is passed down via the "useChart" hook
403
- handleMouseOverSeries(
404
- container,
405
- xAxisLinear,
406
- name,
407
- datumArray,
408
- event,
409
- margin,
410
- seriesStyles,
411
- allowTooltip.current,
412
- mouseOverHandlerFor(`tooltip-${chartId}`)
413
- )
414
- )
415
- .on(
416
- "mouseleave",
417
- event => handleMouseLeaveSeries(
418
- name,
419
- event.currentTarget,
420
- seriesStyles,
421
- mouseLeaveHandlerFor(`tooltip-${chartId}`)
422
- )
423
- ),
424
- update => update,
425
- exit => exit.remove()
301
+ const isHovered = hoveredSeriesName === name
302
+
303
+ // build the on-screen points, dropping data in the x-axis that is out of the
304
+ // chart bounds (mirrors the old lineGenerator's `.defined(...)`)
305
+ type ScreenPoint = [x: number, y: number]
306
+ // skip the expensive per-point work below for retained-but-off-screen data:
307
+ // `plotData` can hold far more history than is ever visible (dropDataAfter is
308
+ // independent of, and often much larger than, the axis's current scrolling
309
+ // window), so iterating the *full* retained array every frame does a lot of
310
+ // work whose result is immediately thrown away as off-screen. Binary-search
311
+ // for where the visible domain begins, and only process from there on --
312
+ // backing up by one point so a line entering from off-screen still renders
313
+ // correctly across the boundary.
314
+ const [domainStart] = xAxisLinear.scale.domain()
315
+ const startIndex = Math.max(0, firstIndexAtOrAfter(plotData, domainStart, (d: Datum) => d.x) - 1)
316
+
317
+ const showMarkers = markerRadius != null && markerRadius >= 0 && (!running || !hideMarkersWhileRunning)
318
+ const markerRadiusResolved = isHovered ? markerRadius! + 2 : markerRadius!
319
+
320
+ const segments: Array<Array<ScreenPoint>> = []
321
+ let currentSegment: Array<ScreenPoint> = []
322
+ const screenPoints: Array<[number, number]> = []
323
+
324
+ context2D.fillStyle = isHovered ? highlightColor : seriesColor
325
+ for (let i = startIndex; i < plotData.length; i++) {
326
+ const d = plotData[i]
327
+ const x = xAxisLinear.scale(d.x)
328
+ const y = yAxisLinear.scale(d.y)
329
+
330
+ // record geometry (outer/canvas coordinate space, margin baked in) for
331
+ // mousemove hit-testing, regardless of on/off-screen -- a point just past
332
+ // the edge should still be hoverable at the boundary
333
+ screenPoints.push([x + margin.left, y + margin.top])
334
+
335
+ // the first point in this loop is the one deliberately backed up before
336
+ // the visible domain (see startIndex above); include it in the segment so
337
+ // the interpolated line entering from the left is smoothly clipped by the
338
+ // canvas's clip region as the plot scrolls, rather than the line abruptly
339
+ // popping into/out of existence right at the boundary
340
+ const isLeftEdgeContinuationPoint = i === startIndex && x < 0
341
+ if (!isLeftEdgeContinuationPoint && (x < 0 || x > plotDimensions.width)) {
342
+ if (currentSegment.length > 0) {
343
+ segments.push(currentSegment)
344
+ currentSegment = []
345
+ }
346
+ continue
347
+ }
348
+
349
+ currentSegment.push([x, y])
350
+
351
+ // point markers (one circle per datum) -- suppressed while streaming
352
+ // because redrawing many circles every frame is more work than a single
353
+ // line path
354
+ if (showMarkers) {
355
+ context2D.beginPath()
356
+ context2D.arc(x, y, markerRadiusResolved, 0, 2 * Math.PI)
357
+ context2D.fill()
358
+ }
359
+ }
360
+ if (currentSegment.length > 0) segments.push(currentSegment)
361
+
362
+ // draw the series line (interpolation is applied via a Path2D built through a
363
+ // d3 line generator so that custom curve factories -- e.g. curveBasis,
364
+ // curveStep -- keep working exactly as they did with the SVG path)
365
+ context2D.strokeStyle = isHovered ? highlightColor : seriesColor
366
+ context2D.lineWidth = isHovered ? highlightWidth : lineWidth
367
+ segments.forEach(segment => {
368
+ const path = new Path2D(
369
+ d3.line().curve(interpolation)(segment) ?? ""
426
370
  )
371
+ context2D.stroke(path)
372
+ })
373
+
374
+ newGeometry.set(name, {
375
+ points: screenPoints,
376
+ asLine: true,
377
+ hitRadius: Math.max(6, (isHovered ? highlightWidth : lineWidth) + 4)
378
+ })
379
+ if (showMarkers) {
380
+ // markers get their own, generously-sized hit target keyed under a
381
+ // derived name so a marker hover can be distinguished from a line hover
382
+ // if the caller ever wants to (currently treated the same on mousemove)
383
+ newGeometry.set(`${name}-markers`, {
384
+ points: screenPoints,
385
+ hitRadius: markerRadiusResolved + 3
386
+ })
387
+ }
388
+ })
389
+
390
+ // highlights the points the tooltip is showing (on top of the series): the
391
+ // hovered point is drawn larger than its neighbors (or than a segment's ends)
392
+ const tooltipPoints = tooltipPointsRef.current
393
+ if (tooltipPoints !== undefined && !running && tooltipPoints.seriesName.match(seriesFilter)) {
394
+ const [xAxisLinear, yAxisLinear] = axesFor(
395
+ tooltipPoints.seriesName,
396
+ axisAssignments,
397
+ axisId => xAxesState.axisFor(axisId).getOrUndefined(),
398
+ axisId => yAxesState.axisFor(axisId).getOrUndefined()
399
+ )
400
+ if (xAxisLinear !== undefined && yAxisLinear !== undefined) {
401
+ const {highlightColor = color} = seriesStyles.get(tooltipPoints.seriesName) ?? {}
402
+ const {points} = tooltipPoints
403
+ scatterTooltipPointList(points).forEach(datum => {
404
+ const isHoveredPoint = points.kind === 'point' && datum === points.point
405
+ context2D.beginPath()
406
+ context2D.arc(
407
+ xAxisLinear.scale(datum.x), yAxisLinear.scale(datum.y),
408
+ isHoveredPoint ? TOOLTIP_POINT_RADIUS : TOOLTIP_NEIGHBOR_RADIUS,
409
+ 0, 2 * Math.PI
410
+ )
411
+ context2D.fillStyle = highlightColor
412
+ context2D.fill()
413
+ // outlined in the background color, so it stands out from the line
414
+ context2D.lineWidth = 1.5
415
+ context2D.strokeStyle = backgroundColor
416
+ context2D.stroke()
417
+ })
418
+ }
419
+ }
420
+
421
+ geometryRef.current = newGeometry
422
+ context2D.restore()
423
+ }
424
+
425
+ /**
426
+ * Grows the auto-scaling y-axes (see `ContinuousAxis`'s `autoScale` prop) to fit the
427
+ * data that's on screen: the data of the shown series (those matching the filter)
428
+ * within their x-axis' current window, including where their lines cross the window's
429
+ * edges (see `visibleExtent`). Since an axis only ever grows, the data that has
430
+ * scrolled off screen was already accounted for while it was on screen, so only the
431
+ * visible data needs checking each frame. Registered to run before the axes draw
432
+ * (they're at z-index 0), so the axes and the series are drawn with the grown domain
433
+ * in the same frame.
434
+ */
435
+ const autoScaleYAxes = (): void => {
436
+ const withLine = interpolation !== NoCurveFactory
437
+ const extents = new Map<string, [min: number, max: number]>()
438
+ dataRef.current.forEach(series => {
439
+ if (!series.name.match(seriesFilter)) return
440
+ const [xAxisLinear, yAxisLinear] = axesFor(
441
+ series.name,
442
+ axisAssignments,
443
+ axisId => xAxesState.axisFor(axisId).getOrUndefined(),
444
+ axisId => yAxesState.axisFor(axisId).getOrUndefined()
445
+ )
446
+ if (xAxisLinear === undefined || yAxisLinear?.autoScale === undefined) return
447
+
448
+ const [start, end] = xAxisLinear.scale.domain()
449
+ const seriesExtent = visibleExtent(series.data, [start, end], yAxisLinear.scale, withLine)
450
+ if (seriesExtent === undefined) return
451
+ const extent = extents.get(yAxisLinear.axisId)
452
+ extents.set(
453
+ yAxisLinear.axisId,
454
+ extent === undefined ? seriesExtent : extentWith(extentWith(extent, seriesExtent[0]), seriesExtent[1])
455
+ )
427
456
  })
457
+
458
+ growAutoScaledYAxes(extents, {yAxes: yAxesState.axes, axisRangeFor, updateAxisRanges})
428
459
  }
460
+
461
+ cc.register(`scatter-plot-autoscale-${chartId}`, autoScaleYAxes, -10)
462
+ cc.register(`scatter-plot-${chartId}`, draw, 10)
463
+ cc.requestRedraw()
429
464
  },
430
465
  [
431
- container, panEnabled, zoomEnabled, chartId, plotDimensions, margin, onPan,
432
- zoomKeyModifiersRequired, onZoom, axisAssignments,
433
- xAxesState, yAxesState,
434
- seriesStyles, seriesFilter, interpolation,
435
- mouseOverHandlerFor, mouseLeaveHandlerFor,
436
- hoveredSeriesRef
466
+ chartId, plotDimensions, axisAssignments, xAxesState, yAxesState, seriesStyles, seriesFilter,
467
+ interpolation, hoveredSeriesName, markerRadius, running, hideMarkersWhileRunning, axisRangeFor,
468
+ updateAxisRanges, color, backgroundColor, margin.left, margin.top
437
469
  ]
438
470
  )
439
471
 
440
- // need to keep the function references for use by the subscription, which forms a closure
441
- // on them. without the references, the closures become stale, and resizing during streaming
442
- // doesn't work properly
443
- const updatePlotRef = useRef<(ordinalRange: Map<string, ContinuousAxisRange>, g: GSelection) => void>(noop)
444
- useEffect(
445
- () => {
446
- updatePlotRef.current = updatePlot
472
+ // the time axes: their ranges, the view driver, panning and zooming, highlighting, and
473
+ // reporting the axes' intervals -- see `useStreamingTimeAxes`
474
+ useStreamingTimeAxes<TimeSeriesChartData, Datum>({
475
+ canvasContext,
476
+ dataSource,
477
+ running,
478
+ initialData,
479
+ timeOf: datum => datum.x,
480
+ axisTimesFor: timeSeriesAxisTimes(axisAssignments, xAxesState),
481
+ axisAssignments,
482
+ updatePlot,
483
+ onDataReset: () => {
484
+ dataRef.current = initialData.slice() as Array<TimeSeries>
485
+ seriesRef.current = new Map(initialData.map(series => [series.name, series as TimeSeries]))
447
486
  },
448
- [updatePlot]
449
- )
450
- const onUpdateTimeRef = useRef(updateAxisRanges)
487
+ allowTooltipRef: allowTooltip,
488
+ panEnabled,
489
+ zoomEnabled,
490
+ zoomKeyModifiersRequired,
491
+ onZoomReset,
492
+ withCadenceOf,
493
+ timeWindowBehavior,
494
+ highlightAxesOnMouseOver,
495
+ })
496
+
497
+ // wires up a single mousemove/mouseleave listener on the shared canvas to replace the old
498
+ // per-element SVG mouseover/mouseleave handlers. Hit-tests the mouse position against the
499
+ // last-drawn geometry (see `geometryRef`, populated by the draw function above).
451
500
  useEffect(
452
501
  () => {
453
- onUpdateTimeRef.current = updateAxisRanges
454
- },
455
- [updateAxisRanges]
456
- )
502
+ if (!canvasContext) return
503
+
504
+ const canvas = canvasContext.canvas
505
+
506
+ const handleMove = (event: MouseEvent) => {
507
+ if (!allowTooltip.current) return
508
+
509
+ const [x, y] = canvasLocalPoint(event, canvas)
510
+ const hit = seriesAt(x, y, geometryRef.current)
511
+ // markers are recorded under a "<name>-markers" key; normalize back to the
512
+ // underlying series name so callers only ever see the real series name.
513
+ // NOTE: `hit?.name.endsWith(...)` here would be unsafe -- `?.` only protects the
514
+ // `.name` access, not the chained `.endsWith(...)` call, so it would throw a
515
+ // TypeError on every mousemove where `hit` is undefined (i.e. most of them).
516
+ const hitName = hit !== undefined
517
+ ? (hit.name.endsWith('-markers') ? hit.name.slice(0, -'-markers'.length) : hit.name)
518
+ : undefined
519
+
520
+ // only fire enter/leave transitions when the hovered series actually changes --
521
+ // but always recompute and re-report the tooltip content below for the *current*
522
+ // mouse position, even while still hovering the same series, so the tooltip keeps
523
+ // tracking the mouse as it moves across a series instead of freezing at wherever
524
+ // the hover was first detected.
525
+ if (hitName !== lastHoveredRef.current) {
526
+ if (lastHoveredRef.current !== undefined) {
527
+ handleMouseLeaveSeries(
528
+ lastHoveredRef.current,
529
+ mouseLeaveHandlerFor(`tooltip-${chartId}`)
530
+ )
531
+ }
532
+ lastHoveredRef.current = hitName
533
+ }
457
534
 
458
- // memoized function for subscribing to the chart-data observable
459
- const subscribe = useCallback(
460
- () => {
461
- if (seriesObservable === undefined || mainG === null) return undefined
462
- if (withCadenceOf !== undefined) {
463
- return subscriptionTimeSeriesWithCadenceFor(
464
- seriesObservable as Observable<TimeSeriesChartData>,
465
- onSubscribe,
466
- windowingTime,
467
- axisAssignments, xAxesState,
468
- onUpdateData,
469
- dropDataAfter,
470
- updateTimingAndPlot,
471
- // as new data flows into the subscription, the subscription
472
- // updates this map directly (for performance)
473
- seriesRef.current,
474
- (axisId, end) => currentTimeRef.current.set(axisId, end),
475
- withCadenceOf
476
- )
535
+ // the points the tooltip shows (and the plot highlights): the point under the mouse
536
+ // and its neighbors, or the two ends of the segment under the mouse
537
+ let tooltipPoints: { seriesName: string, points: ScatterTooltipPoints } | undefined = undefined
538
+ if (hitName !== undefined) {
539
+ const series = seriesRef.current.get(hitName)
540
+ const mouseOverHandler = mouseOverHandlerFor(`tooltip-${chartId}`)
541
+ const [xAxisLinear, yAxisLinear] = axesFor(
542
+ hitName,
543
+ axisAssignments,
544
+ axisId => xAxesState.axisFor(axisId).getOrUndefined(),
545
+ axisId => yAxesState.axisFor(axisId).getOrUndefined()
546
+ )
547
+ if (series !== undefined && xAxisLinear !== undefined && yAxisLinear !== undefined && mouseOverHandler !== undefined) {
548
+ const points = scatterTooltipPoints(
549
+ series.data,
550
+ [x - margin.left, y - margin.top],
551
+ xAxisLinear.scale,
552
+ yAxisLinear.scale,
553
+ Math.max(MIN_POINT_HOVER_RADIUS, (markerRadius ?? 0) + 3)
554
+ )
555
+ tooltipPoints = {seriesName: hitName, points}
556
+ const time = Math.round(xAxisLinear.scale.invert(x - margin.left))
557
+ mouseOverHandler(hitName, time, {series: series.data, metadata: {points}}, [x, y])
558
+ }
559
+ }
560
+ updateTooltipPoints(tooltipPoints)
561
+ }
562
+
563
+ // remembers the points the tooltip shows, and redraws (to highlight them) when they change
564
+ const updateTooltipPoints = (tooltipPoints: { seriesName: string, points: ScatterTooltipPoints } | undefined): void => {
565
+ const previous = tooltipPointsRef.current
566
+ if (previous === undefined && tooltipPoints === undefined) return
567
+ if (previous !== undefined && tooltipPoints !== undefined &&
568
+ previous.seriesName === tooltipPoints.seriesName &&
569
+ previous.points.kind === tooltipPoints.points.kind) {
570
+ const previousList = scatterTooltipPointList(previous.points)
571
+ const list = scatterTooltipPointList(tooltipPoints.points)
572
+ if (previousList.length === list.length && previousList.every((datum, index) => datum === list[index])) return
573
+ }
574
+ tooltipPointsRef.current = tooltipPoints
575
+ canvasContext.requestRedraw()
576
+ }
577
+
578
+ const handleLeaveCanvas = () => {
579
+ if (lastHoveredRef.current !== undefined) {
580
+ handleMouseLeaveSeries(lastHoveredRef.current, mouseLeaveHandlerFor(`tooltip-${chartId}`))
581
+ lastHoveredRef.current = undefined
582
+ }
583
+ updateTooltipPoints(undefined)
584
+ }
585
+
586
+ canvas.addEventListener('mousemove', handleMove)
587
+ canvas.addEventListener('mouseleave', handleLeaveCanvas)
588
+ return () => {
589
+ canvas.removeEventListener('mousemove', handleMove)
590
+ canvas.removeEventListener('mouseleave', handleLeaveCanvas)
477
591
  }
478
- return subscriptionTimeSeriesFor(
479
- seriesObservable as Observable<TimeSeriesChartData>,
480
- onSubscribe,
481
- windowingTime,
482
- axisAssignments, xAxesState,
483
- onUpdateData,
484
- dropDataAfter,
485
- updateTimingAndPlot,
486
- // as new data flows into the subscription, the subscription
487
- // updates this map directly (for performance)
488
- seriesRef.current,
489
- (axisId, end) => currentTimeRef.current.set(axisId, end),
490
- timeWindowBehavior,
491
- initialTimes,
492
- )
493
592
  },
494
- [
495
- axisAssignments, dropDataAfter, mainG,
496
- onSubscribe, onUpdateData,
497
- seriesObservable, updateTimingAndPlot, windowingTime, xAxesState,
498
- withCadenceOf,
499
- initialTimes, timeWindowBehavior
500
- ]
593
+ [canvasContext, chartId, margin, axisAssignments, xAxesState, yAxesState, mouseOverHandlerFor, mouseLeaveHandlerFor, markerRadius]
501
594
  )
502
595
 
503
- const timeRangesRef = useRef<Map<string, ContinuousAxisRange>>(new Map())
596
+ // this plot needs a data source (see the `Chart`'s `dataSource` prop)
504
597
  useEffect(
505
598
  () => {
506
- if (container && mainG) {
507
- // so this gets a bit complicated. the time-ranges need to be updated whenever the time-ranges
508
- // change. for example, as data is streamed in, the times change, and then we need to update the
509
- // time-range. however, we want to keep the time-ranges to reflect their original scale so that
510
- // we can zoom properly (so the updates can't fuck with the scale). At the same time, when the
511
- // interpolation changes, then the update plot changes, and the time-ranges must maintain their
512
- // original scale as well.
513
- if (timeRangesRef.current.size === 0) {
514
- // when no time-ranges have yet been created, then create them and hold on to a mutable
515
- // reference to them
516
- timeRangesRef.current = continuousAxisRanges(xAxesState.axes as Map<string, ContinuousNumericAxis>)
517
- } else {
518
- // when the time-ranges already exist, then we want to update the time-ranges for each
519
- // existing time-range in a way that maintains the original scale.
520
- const intervals = continuousAxisIntervals(xAxesState.axes)
521
- timeRangesRef.current
522
- .forEach((range, id, rangesMap) => {
523
- const [start, end] = Optional.ofNullable(intervals.get(id))
524
- .map(interval => interval.asTuple())
525
- .getOrThrow(() => new Error(`Unable to retrieve interval for axis; axis_id: ${id}`))
526
- // .getOrElse([NaN, NaN])
527
- if (!isNaN(start) && !isNaN(end)) {
528
- // update the reference map with the new (start, end) portion of the range,
529
- // while keeping the original scale intact
530
- rangesMap.set(id, range.update(start, end))
531
- }
532
- })
533
- }
534
- updatePlot(timeRangesRef.current, mainG)
599
+ if (dataSource === undefined) {
600
+ console.error("ScatterPlot requires a data source; set the enclosing Chart's `dataSource` prop")
535
601
  }
536
602
  },
537
- [chartId, color, container, mainG, plotDimensions, updatePlot, xAxesState]
603
+ [dataSource]
538
604
  )
539
605
 
540
- // subscribe/unsubscribe to the observable chart data. when the `shouldSubscribe`
541
- // is changed to `true` and we haven't subscribed yet, then subscribe. when the
542
- // `shouldSubscribe` is `false` and we had subscribed, then unsubscribe. otherwise,
543
- // do nothing.
606
+ // unregister this plot's draw function (and its auto-scaling step) on unmount
544
607
  useEffect(
545
608
  () => {
546
- if (shouldSubscribe && subscriptionRef.current === undefined) {
547
- subscriptionRef.current = subscribe()
548
- allowTooltip.current = false
549
- } else if (!shouldSubscribe && subscriptionRef.current !== undefined) {
550
- subscriptionRef.current?.unsubscribe()
551
- subscriptionRef.current = undefined
552
- allowTooltip.current = true
609
+ return () => {
610
+ if (canvasContext) {
611
+ canvasContext.unregister(`scatter-plot-autoscale-${chartId}`)
612
+ canvasContext.unregister(`scatter-plot-${chartId}`)
613
+ }
553
614
  }
554
615
  },
555
- [shouldSubscribe, subscribe]
616
+ [canvasContext, chartId]
556
617
  )
557
618
 
619
+ // new data, new scale: restarts the auto-scaling y-axes when the data source is replaced
620
+ useAutoScaleRestart(dataSource, {yAxes: yAxesState.axes, axisRangeFor, updateAxisRanges})
621
+
558
622
  return null
559
623
  }
560
624
 
561
625
  /**
562
- * Attempts to locate the x- and y-axes for the specified series. If no axis is found for the
563
- * series name, then uses the default returned by the useChart() hook
626
+ * Attempts to locate the x- and y-axes for the specified series. If the series has no axis
627
+ * assignment, then uses the default (first) x- and y-axes.
564
628
  * @param seriesName Name of the series for which to retrieve the axis
565
629
  * @param axisAssignments A map holding the series name and the associated x- and y-axes assigned
566
630
  * to that series. Note that the series in the axis-assignment map is merely a subset of the set
567
631
  * of series names.
568
632
  * @param xAxisFor The function that accepts an axis ID and returns the corresponding x-axis
569
633
  * @param yAxisFor The function that accepts an axis ID and returns the corresponding y-axis
634
+ * @return The x- and y-axes (either is actually `undefined` when it isn't found, despite the type)
635
+ * @throws Error when either axis isn't a continuous numeric axis
570
636
  */
571
637
  function axesFor(
572
638
  seriesName: string,
@@ -576,75 +642,26 @@ function axesFor(
576
642
  ): [xAxis: ContinuousNumericAxis, yAxis: ContinuousNumericAxis] {
577
643
  const axes = axisAssignments.get(seriesName)
578
644
  const xAxis = xAxisFor(axes?.xAxis || "")
579
- const xAxisLinear = xAxis as ContinuousNumericAxis
580
645
  const yAxis = yAxisFor(axes?.yAxis || "")
646
+ checkAxisType("ScatterPlot", "x", xAxis, AxisType.ContinuousNumeric)
647
+ checkAxisType("ScatterPlot", "y", yAxis, AxisType.ContinuousNumeric)
648
+ const xAxisLinear = xAxis as ContinuousNumericAxis
581
649
  const yAxisLinear = yAxis as ContinuousNumericAxis
582
- if (xAxis && !xAxisLinear) {
583
- throw Error("Scatter plot requires that x-axis be of type LinearAxis")
584
- }
585
- if (yAxis && !yAxisLinear) {
586
- throw Error("Scatter plot requires that y-axis be of type LinearAxis")
587
- }
588
650
  return [xAxisLinear, yAxisLinear]
589
651
  }
590
652
 
591
653
  /**
592
- * Renders a tooltip showing the neuron, spike time, and the spike strength when the mouse hovers over a spike.
593
- * @param container The chart container
594
- * @param xAxis The x-axis
595
- * @param seriesName The name of the series (i.e. the neuron ID)
596
- * @param series The time series
597
- * @param event The mouse-over series event
598
- * @param margin The plot margin
599
- * @param seriesStyles The series style information (needed for (un)highlighting)
600
- * @param allowTooltip When set to `false` won't show tooltip, even if it is visible (used by pan)
601
- * @param mouseOverHandlerFor The handler for the mouse over (registered by the <Tooltip/>)
602
- */
603
- function handleMouseOverSeries(
604
- container: SVGSVGElement,
605
- xAxis: ContinuousNumericAxis,
606
- seriesName: string,
607
- series: Series<Datum>,
608
- event: React.MouseEvent<SVGPathElement>,
609
- margin: Margin,
610
- seriesStyles: Map<string, SeriesLineStyle>,
611
- allowTooltip: boolean,
612
- mouseOverHandlerFor: ((seriesName: string, time: number, tooltipData: TooltipData<Datum, NoTooltipMetadata>, mouseCoords: [x: number, y: number]) => void) | undefined,
613
- ): void {
614
- // grab the time needed for the tooltip ID
615
- const [x, y] = d3.pointer(event, container)
616
- const time = Math.round(xAxis.scale.invert(x - margin.left))
617
-
618
- const {highlightColor, highlightWidth} = seriesStyles.get(seriesName) || defaultLineStyle()
619
-
620
- // Use d3 to select element, change color and size
621
- d3.select<SVGPathElement, Datum>(event.currentTarget)
622
- .attr('stroke', highlightColor)
623
- .attr('stroke-width', highlightWidth)
624
-
625
- if (mouseOverHandlerFor && allowTooltip) {
626
- mouseOverHandlerFor(seriesName, time, {series, metadata: {}}, [x, y])
627
- }
628
- }
629
-
630
- /**
631
- * Unselects the time series and calls the mouse-leave-series handler registered for this series.
654
+ * Calls the mouse-leave-series handler registered for this series. Replaces the old version, which
655
+ * also reset the hovered SVG element's stroke color/width directly; that reset is now implicit --
656
+ * once `hoveredSeriesName` is cleared, the next redraw simply paints the series in its normal
657
+ * (non-highlighted) style.
632
658
  * @param seriesName The name of the series (i.e. the neuron ID)
633
- * @param segment The SVG line element representing the spike, over which the mouse is hovering.
634
- * @param seriesStyles The styles for the series (for (un)highlighting)
635
659
  * @param mouseLeaverHandlerFor Registered handler for the series when the mouse leaves
636
660
  */
637
661
  function handleMouseLeaveSeries(
638
662
  seriesName: string,
639
- segment: SVGPathElement,
640
- seriesStyles: Map<string, SeriesLineStyle>,
641
663
  mouseLeaverHandlerFor: ((seriesName: string) => void) | undefined,
642
664
  ): void {
643
- const {color, lineWidth} = seriesStyles.get(seriesName) || defaultLineStyle()
644
- d3.select<SVGPathElement, Datum>(segment)
645
- .attr('stroke', color)
646
- .attr('stroke-width', lineWidth)
647
-
648
665
  if (mouseLeaverHandlerFor) {
649
666
  mouseLeaverHandlerFor(seriesName)
650
667
  }