stream-charts 1.0.1 → 1.1.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 (271) hide show
  1. package/README.md +756 -343
  2. package/changes.md +7 -0
  3. package/dist/src/app/charts/Chart.d.ts +116 -87
  4. package/dist/src/app/charts/Chart.js +96 -101
  5. package/dist/src/app/charts/Chart.js.map +1 -1
  6. package/dist/src/app/charts/axes/AxesState.d.ts +64 -0
  7. package/dist/src/app/charts/axes/AxesState.js +97 -0
  8. package/dist/src/app/charts/axes/AxesState.js.map +1 -0
  9. package/dist/src/app/charts/axes/AxesState.test.js +30 -0
  10. package/dist/src/app/charts/axes/AxesState.test.js.map +1 -0
  11. package/dist/src/app/charts/axes/AxisInterval.d.ts +92 -0
  12. package/dist/src/app/charts/axes/AxisInterval.js +126 -0
  13. package/dist/src/app/charts/axes/AxisInterval.js.map +1 -0
  14. package/dist/src/app/charts/axes/AxisInterval.test.js +120 -0
  15. package/dist/src/app/charts/axes/AxisInterval.test.js.map +1 -0
  16. package/dist/src/app/charts/axes/BaseAxisRange.d.ts +83 -0
  17. package/dist/src/app/charts/axes/BaseAxisRange.js +58 -0
  18. package/dist/src/app/charts/axes/BaseAxisRange.js.map +1 -0
  19. package/dist/src/app/charts/axes/ContinuousAxis.d.ts +22 -0
  20. package/dist/src/app/charts/axes/ContinuousAxis.js +96 -0
  21. package/dist/src/app/charts/axes/ContinuousAxis.js.map +1 -0
  22. package/dist/src/app/charts/axes/ContinuousAxisRange.d.ts +59 -0
  23. package/dist/src/app/charts/axes/ContinuousAxisRange.js +84 -0
  24. package/dist/src/app/charts/axes/ContinuousAxisRange.js.map +1 -0
  25. package/dist/src/app/charts/axes/ContinuousAxisRange.test.js +47 -0
  26. package/dist/src/app/charts/axes/ContinuousAxisRange.test.js.map +1 -0
  27. package/dist/src/app/charts/axes/OrdinalAxis.d.ts +23 -0
  28. package/dist/src/app/charts/axes/OrdinalAxis.js +124 -0
  29. package/dist/src/app/charts/axes/OrdinalAxis.js.map +1 -0
  30. package/dist/src/app/charts/axes/OrdinalAxisRange.d.ts +69 -0
  31. package/dist/src/app/charts/axes/OrdinalAxisRange.js +100 -0
  32. package/dist/src/app/charts/axes/OrdinalAxisRange.js.map +1 -0
  33. package/dist/src/app/charts/axes/OrdinalAxisRange.test.js +48 -0
  34. package/dist/src/app/charts/axes/OrdinalAxisRange.test.js.map +1 -0
  35. package/dist/src/app/charts/axes/axes.d.ts +391 -0
  36. package/dist/src/app/charts/axes/axes.js +1046 -0
  37. package/dist/src/app/charts/axes/axes.js.map +1 -0
  38. package/dist/src/app/charts/d3types.d.ts +10 -10
  39. package/dist/src/app/charts/filters/regexFilter.d.ts +9 -0
  40. package/dist/src/app/charts/filters/regexFilter.js +18 -0
  41. package/dist/src/app/charts/filters/regexFilter.js.map +1 -0
  42. package/dist/src/app/charts/filters/regexFilter.test.d.ts +1 -0
  43. package/dist/src/app/charts/filters/regexFilter.test.js +11 -0
  44. package/dist/src/app/charts/filters/regexFilter.test.js.map +1 -0
  45. package/dist/src/app/charts/hooks/useAxes.d.ts +144 -0
  46. package/dist/src/app/charts/hooks/useAxes.js +187 -0
  47. package/dist/src/app/charts/hooks/useAxes.js.map +1 -0
  48. package/dist/src/app/charts/hooks/useChart.d.ts +30 -201
  49. package/dist/src/app/charts/hooks/useChart.js +33 -134
  50. package/dist/src/app/charts/hooks/useChart.js.map +1 -1
  51. package/dist/src/app/charts/hooks/useDataObservable.d.ts +74 -0
  52. package/dist/src/app/charts/hooks/useDataObservable.js +76 -0
  53. package/dist/src/app/charts/hooks/useDataObservable.js.map +1 -0
  54. package/dist/src/app/charts/hooks/useInitialData.d.ts +38 -0
  55. package/dist/src/app/charts/hooks/useInitialData.js +29 -0
  56. package/dist/src/app/charts/hooks/useInitialData.js.map +1 -0
  57. package/dist/src/app/charts/hooks/useMouse.d.ts +53 -0
  58. package/dist/src/app/charts/hooks/useMouse.js +65 -0
  59. package/dist/src/app/charts/hooks/useMouse.js.map +1 -0
  60. package/dist/src/app/charts/hooks/usePlotDimensions.d.ts +34 -0
  61. package/dist/src/app/charts/hooks/usePlotDimensions.js +73 -0
  62. package/dist/src/app/charts/hooks/usePlotDimensions.js.map +1 -0
  63. package/dist/src/app/charts/hooks/useTooltip.d.ts +64 -0
  64. package/dist/src/app/charts/hooks/useTooltip.js +52 -0
  65. package/dist/src/app/charts/hooks/useTooltip.js.map +1 -0
  66. package/dist/src/app/charts/observables/ChartData.d.ts +23 -0
  67. package/dist/src/app/charts/observables/ChartData.js +20 -0
  68. package/dist/src/app/charts/observables/ChartData.js.map +1 -0
  69. package/dist/src/app/charts/observables/iterates.d.ts +52 -0
  70. package/dist/src/app/charts/observables/iterates.js +121 -0
  71. package/dist/src/app/charts/observables/iterates.js.map +1 -0
  72. package/dist/src/app/charts/observables/iterates.test.d.ts +1 -0
  73. package/dist/src/app/charts/observables/iterates.test.js +180 -0
  74. package/dist/src/app/charts/observables/iterates.test.js.map +1 -0
  75. package/dist/src/app/charts/observables/ordinals.d.ts +75 -0
  76. package/dist/src/app/charts/observables/ordinals.js +141 -0
  77. package/dist/src/app/charts/observables/ordinals.js.map +1 -0
  78. package/dist/src/app/charts/observables/ordinals.test.d.ts +1 -0
  79. package/dist/src/app/charts/observables/ordinals.test.js +102 -0
  80. package/dist/src/app/charts/observables/ordinals.test.js.map +1 -0
  81. package/dist/src/app/charts/plots/BarPlot.d.ts +81 -0
  82. package/dist/src/app/charts/plots/BarPlot.js +557 -0
  83. package/dist/src/app/charts/plots/BarPlot.js.map +1 -0
  84. package/dist/src/app/charts/plots/PoincarePlot.d.ts +78 -0
  85. package/dist/src/app/charts/plots/PoincarePlot.js +504 -0
  86. package/dist/src/app/charts/plots/PoincarePlot.js.map +1 -0
  87. package/dist/src/app/charts/{RasterPlot.d.ts → plots/RasterPlot.d.ts} +1 -1
  88. package/dist/src/app/charts/{RasterPlot.js → plots/RasterPlot.js} +152 -133
  89. package/dist/src/app/charts/plots/RasterPlot.js.map +1 -0
  90. package/dist/src/app/charts/{ScatterPlot.d.ts → plots/ScatterPlot.d.ts} +10 -8
  91. package/dist/src/app/charts/plots/ScatterPlot.js +379 -0
  92. package/dist/src/app/charts/plots/ScatterPlot.js.map +1 -0
  93. package/dist/src/app/charts/{plot.d.ts → plots/plot.d.ts} +12 -14
  94. package/dist/src/app/charts/{plot.js → plots/plot.js} +19 -17
  95. package/dist/src/app/charts/plots/plot.js.map +1 -0
  96. package/dist/src/app/charts/series/baseSeries.d.ts +38 -0
  97. package/dist/src/app/charts/series/baseSeries.js +36 -0
  98. package/dist/src/app/charts/series/baseSeries.js.map +1 -0
  99. package/dist/src/app/charts/series/datumSeries.test.d.ts +1 -0
  100. package/dist/src/app/charts/series/datumSeries.test.js +50 -0
  101. package/dist/src/app/charts/series/datumSeries.test.js.map +1 -0
  102. package/dist/src/app/charts/series/iterateSeries.d.ts +42 -0
  103. package/dist/src/app/charts/series/iterateSeries.js +34 -0
  104. package/dist/src/app/charts/series/iterateSeries.js.map +1 -0
  105. package/dist/src/app/charts/series/ordinalSeries.d.ts +54 -0
  106. package/dist/src/app/charts/series/ordinalSeries.js +98 -0
  107. package/dist/src/app/charts/series/ordinalSeries.js.map +1 -0
  108. package/dist/src/app/charts/series/timeSeries.d.ts +30 -0
  109. package/dist/src/app/charts/series/timeSeries.js +19 -0
  110. package/dist/src/app/charts/series/timeSeries.js.map +1 -0
  111. package/dist/src/app/charts/{chartData.d.ts → series/timeSeriesChartData.d.ts} +11 -6
  112. package/dist/src/app/charts/series/timeSeriesChartData.js +32 -0
  113. package/dist/src/app/charts/series/timeSeriesChartData.js.map +1 -0
  114. package/dist/src/app/charts/styling/barPlotStyle.d.ts +41 -0
  115. package/dist/src/app/charts/styling/barPlotStyle.js +84 -0
  116. package/dist/src/app/charts/styling/barPlotStyle.js.map +1 -0
  117. package/dist/src/app/charts/{margins.d.ts → styling/margins.d.ts} +7 -5
  118. package/dist/src/app/charts/{margins.js → styling/margins.js} +19 -9
  119. package/dist/src/app/charts/styling/margins.js.map +1 -0
  120. package/dist/src/app/charts/styling/margins.test.d.ts +1 -0
  121. package/dist/src/app/charts/styling/margins.test.js +19 -0
  122. package/dist/src/app/charts/styling/margins.test.js.map +1 -0
  123. package/dist/src/app/charts/styling/svgStyle.d.ts +76 -0
  124. package/dist/src/app/charts/styling/svgStyle.js +84 -0
  125. package/dist/src/app/charts/styling/svgStyle.js.map +1 -0
  126. package/dist/src/app/charts/subscriptions/subscription.test.d.ts +1 -0
  127. package/dist/src/app/charts/subscriptions/subscription.test.js +395 -0
  128. package/dist/src/app/charts/subscriptions/subscription.test.js.map +1 -0
  129. package/dist/src/app/charts/subscriptions/subscriptions.d.ts +100 -0
  130. package/dist/src/app/charts/subscriptions/subscriptions.js +449 -0
  131. package/dist/src/app/charts/subscriptions/subscriptions.js.map +1 -0
  132. package/dist/src/app/charts/tooltips/BarPlotTooltipContent.d.ts +90 -0
  133. package/dist/src/app/charts/tooltips/BarPlotTooltipContent.js +302 -0
  134. package/dist/src/app/charts/tooltips/BarPlotTooltipContent.js.map +1 -0
  135. package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.d.ts +100 -0
  136. package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.js +190 -0
  137. package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.js.map +1 -0
  138. package/dist/src/app/charts/{RasterPlotTooltipContent.js → tooltips/RasterPlotTooltipContent.js} +40 -44
  139. package/dist/src/app/charts/tooltips/RasterPlotTooltipContent.js.map +1 -0
  140. package/dist/src/app/charts/tooltips/ScatterPlotTooltipContent.js +154 -0
  141. package/dist/src/app/charts/tooltips/ScatterPlotTooltipContent.js.map +1 -0
  142. package/dist/src/app/charts/{Tooltip.d.ts → tooltips/Tooltip.d.ts} +4 -2
  143. package/dist/src/app/charts/{Tooltip.js → tooltips/Tooltip.js} +23 -28
  144. package/dist/src/app/charts/tooltips/Tooltip.js.map +1 -0
  145. package/dist/src/app/charts/{tooltipUtils.d.ts → tooltips/tooltipUtils.d.ts} +11 -6
  146. package/dist/src/app/charts/{tooltipUtils.js → tooltips/tooltipUtils.js} +33 -17
  147. package/dist/src/app/charts/tooltips/tooltipUtils.js.map +1 -0
  148. package/dist/src/app/charts/{Tracker.d.ts → trackers/Tracker.d.ts} +6 -5
  149. package/dist/src/app/charts/trackers/Tracker.js +60 -0
  150. package/dist/src/app/charts/trackers/Tracker.js.map +1 -0
  151. package/dist/src/app/charts/{trackerUtils.d.ts → trackers/trackerUtils.d.ts} +13 -8
  152. package/dist/src/app/charts/trackers/trackerUtils.js +359 -0
  153. package/dist/src/app/charts/trackers/trackerUtils.js.map +1 -0
  154. package/dist/src/app/charts/utils.d.ts +22 -32
  155. package/dist/src/app/charts/utils.js +36 -37
  156. package/dist/src/app/charts/utils.js.map +1 -1
  157. package/dist/src/index.d.ts +48 -15
  158. package/dist/src/index.js +71 -15
  159. package/dist/src/index.js.map +1 -1
  160. package/package.json +30 -20
  161. package/src/app/charts/Chart.tsx +334 -0
  162. package/src/app/charts/axes/AxesState.test.ts +34 -0
  163. package/src/app/charts/axes/AxesState.ts +112 -0
  164. package/src/app/charts/axes/AxisInterval.test.ts +143 -0
  165. package/src/app/charts/axes/AxisInterval.ts +139 -0
  166. package/src/app/charts/axes/BaseAxisRange.ts +113 -0
  167. package/src/app/charts/axes/ContinuousAxis.tsx +192 -0
  168. package/src/app/charts/axes/ContinuousAxisRange.test.ts +55 -0
  169. package/src/app/charts/axes/ContinuousAxisRange.ts +96 -0
  170. package/src/app/charts/axes/OrdinalAxis.tsx +223 -0
  171. package/src/app/charts/axes/OrdinalAxisRange.test.ts +55 -0
  172. package/src/app/charts/axes/OrdinalAxisRange.ts +112 -0
  173. package/src/app/charts/axes/axes.ts +1569 -0
  174. package/src/app/charts/d3types.ts +16 -0
  175. package/src/app/charts/filters/regexFilter.test.ts +13 -0
  176. package/src/app/charts/filters/regexFilter.ts +17 -0
  177. package/src/app/charts/hooks/useAxes.tsx +341 -0
  178. package/src/app/charts/hooks/useChart.tsx +159 -0
  179. package/src/app/charts/hooks/useDataObservable.tsx +175 -0
  180. package/src/app/charts/hooks/useInitialData.tsx +65 -0
  181. package/src/app/charts/hooks/useMouse.tsx +125 -0
  182. package/src/app/charts/hooks/usePlotDimensions.tsx +120 -0
  183. package/src/app/charts/hooks/useTooltip.tsx +112 -0
  184. package/src/app/charts/observables/ChartData.ts +31 -0
  185. package/src/app/charts/observables/iterates.test.ts +201 -0
  186. package/src/app/charts/observables/iterates.ts +203 -0
  187. package/src/app/charts/observables/ordinals.test.ts +135 -0
  188. package/src/app/charts/observables/ordinals.ts +248 -0
  189. package/src/app/charts/plots/BarPlot.tsx +1088 -0
  190. package/src/app/charts/plots/PoincarePlot.tsx +832 -0
  191. package/src/app/charts/plots/RasterPlot.tsx +676 -0
  192. package/src/app/charts/plots/ScatterPlot.tsx +653 -0
  193. package/src/app/charts/plots/plot.ts +75 -0
  194. package/src/app/charts/series/baseSeries.ts +52 -0
  195. package/src/app/charts/series/datumSeries.test.ts +60 -0
  196. package/src/app/charts/series/iterateSeries.ts +56 -0
  197. package/src/app/charts/series/ordinalSeries.ts +164 -0
  198. package/src/app/charts/series/timeSeries.ts +40 -0
  199. package/src/app/charts/series/timeSeriesChartData.ts +68 -0
  200. package/src/app/charts/styling/barPlotStyle.ts +126 -0
  201. package/src/app/charts/styling/margins.test.ts +22 -0
  202. package/src/app/charts/styling/margins.ts +58 -0
  203. package/src/app/charts/styling/svgStyle.ts +118 -0
  204. package/src/app/charts/subscriptions/subscription.test.ts +499 -0
  205. package/src/app/charts/subscriptions/subscriptions.ts +635 -0
  206. package/src/app/charts/tooltips/BarPlotTooltipContent.tsx +440 -0
  207. package/src/app/charts/tooltips/PoincarePlotTooltipContent.tsx +352 -0
  208. package/src/app/charts/tooltips/RasterPlotTooltipContent.tsx +274 -0
  209. package/src/app/charts/tooltips/ScatterPlotTooltipContent.tsx +319 -0
  210. package/src/app/charts/tooltips/Tooltip.tsx +148 -0
  211. package/src/app/charts/tooltips/tooltipUtils.ts +216 -0
  212. package/src/app/charts/trackers/Tracker.tsx +129 -0
  213. package/src/app/charts/trackers/trackerUtils.ts +494 -0
  214. package/src/app/charts/utils.ts +109 -0
  215. package/dist/src/app/charts/CategoryAxis.d.ts +0 -20
  216. package/dist/src/app/charts/CategoryAxis.js +0 -112
  217. package/dist/src/app/charts/CategoryAxis.js.map +0 -1
  218. package/dist/src/app/charts/ContinuousAxis.d.ts +0 -16
  219. package/dist/src/app/charts/ContinuousAxis.js +0 -186
  220. package/dist/src/app/charts/ContinuousAxis.js.map +0 -1
  221. package/dist/src/app/charts/RasterPlot.js.map +0 -1
  222. package/dist/src/app/charts/RasterPlotTooltipContent.js.map +0 -1
  223. package/dist/src/app/charts/ScatterPlot.js +0 -351
  224. package/dist/src/app/charts/ScatterPlot.js.map +0 -1
  225. package/dist/src/app/charts/ScatterPlotTooltipContent.js +0 -163
  226. package/dist/src/app/charts/ScatterPlotTooltipContent.js.map +0 -1
  227. package/dist/src/app/charts/Tooltip.js.map +0 -1
  228. package/dist/src/app/charts/Tracker.js +0 -65
  229. package/dist/src/app/charts/Tracker.js.map +0 -1
  230. package/dist/src/app/charts/axes.d.ts +0 -132
  231. package/dist/src/app/charts/axes.js +0 -265
  232. package/dist/src/app/charts/axes.js.map +0 -1
  233. package/dist/src/app/charts/chartData.js +0 -32
  234. package/dist/src/app/charts/chartData.js.map +0 -1
  235. package/dist/src/app/charts/continuousAxisRangeFor.d.ts +0 -20
  236. package/dist/src/app/charts/continuousAxisRangeFor.js +0 -68
  237. package/dist/src/app/charts/continuousAxisRangeFor.js.map +0 -1
  238. package/dist/src/app/charts/datumSeries.d.ts +0 -59
  239. package/dist/src/app/charts/datumSeries.js +0 -55
  240. package/dist/src/app/charts/datumSeries.js.map +0 -1
  241. package/dist/src/app/charts/datumSeries.test.js +0 -50
  242. package/dist/src/app/charts/datumSeries.test.js.map +0 -1
  243. package/dist/src/app/charts/hooks/AxesState.d.ts +0 -37
  244. package/dist/src/app/charts/hooks/AxesState.js +0 -44
  245. package/dist/src/app/charts/hooks/AxesState.js.map +0 -1
  246. package/dist/src/app/charts/margins.js.map +0 -1
  247. package/dist/src/app/charts/margins.test.js +0 -14
  248. package/dist/src/app/charts/margins.test.js.map +0 -1
  249. package/dist/src/app/charts/plot.js.map +0 -1
  250. package/dist/src/app/charts/regexFilter.d.ts +0 -9
  251. package/dist/src/app/charts/regexFilter.js +0 -17
  252. package/dist/src/app/charts/regexFilter.js.map +0 -1
  253. package/dist/src/app/charts/regexFilter.test.js +0 -11
  254. package/dist/src/app/charts/regexFilter.test.js.map +0 -1
  255. package/dist/src/app/charts/subscriptions.d.ts +0 -43
  256. package/dist/src/app/charts/subscriptions.js +0 -213
  257. package/dist/src/app/charts/subscriptions.js.map +0 -1
  258. package/dist/src/app/charts/svgStyle.d.ts +0 -27
  259. package/dist/src/app/charts/svgStyle.js +0 -32
  260. package/dist/src/app/charts/svgStyle.js.map +0 -1
  261. package/dist/src/app/charts/timeRange.test.js +0 -54
  262. package/dist/src/app/charts/timeRange.test.js.map +0 -1
  263. package/dist/src/app/charts/tooltipUtils.js.map +0 -1
  264. package/dist/src/app/charts/trackerUtils.js +0 -130
  265. package/dist/src/app/charts/trackerUtils.js.map +0 -1
  266. /package/dist/src/app/charts/{datumSeries.test.d.ts → axes/AxesState.test.d.ts} +0 -0
  267. /package/dist/src/app/charts/{margins.test.d.ts → axes/AxisInterval.test.d.ts} +0 -0
  268. /package/dist/src/app/charts/{regexFilter.test.d.ts → axes/ContinuousAxisRange.test.d.ts} +0 -0
  269. /package/dist/src/app/charts/{timeRange.test.d.ts → axes/OrdinalAxisRange.test.d.ts} +0 -0
  270. /package/dist/src/app/charts/{RasterPlotTooltipContent.d.ts → tooltips/RasterPlotTooltipContent.d.ts} +0 -0
  271. /package/dist/src/app/charts/{ScatterPlotTooltipContent.d.ts → tooltips/ScatterPlotTooltipContent.d.ts} +0 -0
@@ -0,0 +1,676 @@
1
+ import {AxesAssignment, setClipPathG} from "./plot";
2
+ import * as d3 from "d3";
3
+ import {ZoomTransform} from "d3";
4
+ import {noop} from "../utils";
5
+ import {NoTooltipMetadata, useChart} from "../hooks/useChart";
6
+ import React, {useCallback, useEffect, useMemo, useRef} from "react";
7
+ import {Datum, PixelDatum, TimeSeries} from "../series/timeSeries";
8
+ import {GSelection} from "../d3types";
9
+ import {
10
+ axesForSeriesGen,
11
+ BaseAxis,
12
+ continuousAxisIntervals,
13
+ continuousAxisRanges,
14
+ continuousAxisZoomHandler,
15
+ ContinuousNumericAxis,
16
+ defaultLineStyle,
17
+ OrdinalStringAxis,
18
+ panHandler,
19
+ SeriesLineStyle
20
+ } from "../axes/axes";
21
+ import {Observable, Subscription} from "rxjs";
22
+ import {Dimensions, Margin} from "../styling/margins";
23
+ import {subscriptionTimeSeriesFor, subscriptionTimeSeriesWithCadenceFor} from "../subscriptions/subscriptions";
24
+ import {useDataObservable} from "../hooks/useDataObservable";
25
+ import {TimeSeriesChartData} from "../series/timeSeriesChartData";
26
+ import {usePlotDimensions} from "../hooks/usePlotDimensions";
27
+ import {useInitialData} from "../hooks/useInitialData";
28
+ import {TooltipData} from "../hooks/useTooltip";
29
+ import {AxisInterval} from "../axes/AxisInterval";
30
+ import {Optional} from "result-fn";
31
+ import {ContinuousAxisRange} from "../axes/ContinuousAxisRange";
32
+
33
+ interface Props {
34
+ /**
35
+ * Holds the mapping between a series and the axis it uses (is assigned). The
36
+ * map's key holds the series name, and the value is an {@link AxesAssignment}
37
+ * object holding the ID of the assigned x-axis and y-axis.
38
+ */
39
+ axisAssignments?: Map<string, AxesAssignment>
40
+ /**
41
+ * The number of milliseconds of data to hold in memory before dropping it. Defaults to
42
+ * infinity (i.e. no data is dropped)
43
+ */
44
+ dropDataAfter?: number
45
+ /**
46
+ * Enables panning (default is false)
47
+ */
48
+ panEnabled?: boolean
49
+ /**
50
+ * Enables zooming (default is false)
51
+ */
52
+ zoomEnabled?: boolean
53
+ /**
54
+ * When true, requires that the shift or control key be pressed while scrolling
55
+ * to activate the zoom
56
+ */
57
+ zoomKeyModifiersRequired?: boolean
58
+ /**
59
+ * When set, uses a cadence with the specified refresh period (in milliseconds). For plots
60
+ * where the updates are slow (> 100 ms) using a cadence of 10 to 25 ms smooths out the
61
+ * updates and makes the plot updates look cleaner. When updates are around 25 ms or less,
62
+ * then setting the cadence period too small will result in poor update performance. Generally
63
+ * at high update speeds, the cadence is unnecessary. Finally, using cadence, sets the max time
64
+ * to the current time.
65
+ */
66
+ withCadenceOf?: number
67
+ /**
68
+ * The (optional, default = 2 pixels) top and bottom margin (in pixels) for the spike lines in the plot.
69
+ * Margins on individual series can also be set through the {@link Chart.seriesStyles} property.
70
+ */
71
+ spikeMargin?: number
72
+ }
73
+
74
+ /**
75
+ * Renders a streaming neuron raster plot for the series in the initial data and those sourced by the
76
+ * observable specified as a property in the {@link Chart}. This component uses the {@link useChart}
77
+ * hook, and therefore must be a child of the {@link Chart} in order to be plugged in to the
78
+ * chart ecosystem (axes, tracker, tooltip).
79
+ *
80
+ * @param props The properties associated with the raster plot
81
+ * @constructor
82
+ * @example
83
+ <RasterPlot
84
+ axisAssignments={new Map([
85
+ ['neuron1', assignAxes("x-axis-2", "y-axis-2")],
86
+ ['neuron2', assignAxes("x-axis-2", "y-axis-2")],
87
+ ])}
88
+ spikeMargin={1}
89
+ dropDataAfter={5000}
90
+ panEnabled={true}
91
+ zoomEnabled={true}
92
+ zoomKeyModifiersRequired={true}
93
+ />
94
+ */
95
+ export function RasterPlot(props: Props): null {
96
+ const {
97
+ chartId,
98
+ container,
99
+ mainG,
100
+ axes,
101
+ color,
102
+ seriesStyles,
103
+ seriesFilter,
104
+ mouse
105
+ } = useChart<Datum, SeriesLineStyle, NoTooltipMetadata, ContinuousAxisRange, ContinuousNumericAxis>()
106
+
107
+ const {
108
+ xAxesState,
109
+ yAxesState,
110
+ setAxisAssignments,
111
+ setAxisIntervalFor,
112
+ updateAxisRanges = noop,
113
+ onUpdateAxesInterval,
114
+ } = axes
115
+
116
+ const {mouseOverHandlerFor, mouseLeaveHandlerFor} = mouse
117
+
118
+ const {plotDimensions, margin} = usePlotDimensions()
119
+
120
+ const {
121
+ seriesObservable,
122
+ windowingTime = 100,
123
+ shouldSubscribe,
124
+
125
+ onSubscribe = noop,
126
+ onUpdateData,
127
+ } = useDataObservable()
128
+
129
+ const {initialData} = useInitialData<TimeSeriesChartData, Datum>()
130
+
131
+ const {
132
+ axisAssignments = new Map<string, AxesAssignment>(),
133
+ dropDataAfter = Infinity,
134
+ panEnabled = false,
135
+ zoomEnabled = false,
136
+ zoomKeyModifiersRequired = true,
137
+ withCadenceOf,
138
+ spikeMargin = 2,
139
+ } = props
140
+
141
+ // why do "dataRef" and "seriesRef" both hold on to the same underlying data? for performance.
142
+ //
143
+ // the "dataRef" and "seriesRef" both point to the same underlying data, a collection
144
+ // of series. The series in "dataRef" are bound to the DOM elements through d3. The "seriesRef" series
145
+ // are the ones that are updated as new data is streamed in.
146
+ //
147
+ // the "dataRef" object holds on to a copy of the initial data (which is an array of
148
+ // time-series, e.i. an array of BaseSeries<OrdinalDatum> objects). The slice just creates a copy of
149
+ // the array, but the references to the BaseSeries objects are the same and still point to the same
150
+ // data as the "initialData" array.
151
+ //
152
+ // the "seriesRef" object is a reference to a map (series_name -> BaseSeries<OrdinalDatum>) which is
153
+ // used to update the data in the series. When new data enters, it is appended to one or more series.
154
+ //
155
+ // the series in the "dataRef" object are the ones bound to the DOM elements in d3, and so as these
156
+ // are updated, d3 will update the DOM elements (the elements in this plot).
157
+ const dataRef = useRef<Array<TimeSeries>>(initialData.slice() as Array<TimeSeries>)
158
+ const seriesRef = useRef<Map<string, TimeSeries>>(new Map(initialData.map(series => [series.name, series as TimeSeries])))
159
+ // map(axis_id -> current_time) -- maps the axis ID to the current time for that axis
160
+ const currentTimeRef = useRef<Map<string, number>>(new Map())
161
+
162
+ const subscriptionRef = useRef<Subscription>(undefined)
163
+
164
+ const isSubscriptionClosed = () => subscriptionRef.current === undefined || subscriptionRef.current.closed
165
+
166
+ const allowTooltipRef = useRef<boolean>(isSubscriptionClosed())
167
+
168
+ useEffect(
169
+ () => {
170
+ currentTimeRef.current = new Map(Array.from<string>(xAxesState.axes.keys()).map(id => [id, 0]))
171
+ },
172
+ [xAxesState]
173
+ )
174
+
175
+ // set the axis assignments needed if a tooltip is being used
176
+ useEffect(
177
+ () => {
178
+ setAxisAssignments(axisAssignments)
179
+ },
180
+ [axisAssignments, setAxisAssignments]
181
+ )
182
+
183
+ // calculates the distinct series IDs that cover all the series in the plot
184
+ const axesForSeries = useMemo(
185
+ () => axesForSeriesGen<Datum, ContinuousNumericAxis>(initialData, axisAssignments, xAxesState),
186
+ [initialData, axisAssignments, xAxesState]
187
+ )
188
+
189
+ // updates the timing using the onUpdateTime and updatePlot references. This and the references
190
+ // defined above allow the axes' times to be update properly by avoid stale reference to these
191
+ // functions.
192
+ const updateTimingAndPlot = useCallback((ranges: Map<string, ContinuousAxisRange>): void => {
193
+ if (mainG !== null) {
194
+ onUpdateTimeRef.current(ranges)
195
+ updatePlotRef.current(ranges, mainG)
196
+ if (onUpdateAxesInterval) {
197
+ setTimeout(() => {
198
+ const times = new Map<string, AxisInterval>()
199
+ ranges.forEach((range, name) => times.set(name, range.current))
200
+ onUpdateAxesInterval(times)
201
+ }, 0)
202
+ }
203
+ }
204
+ },
205
+ [mainG, onUpdateAxesInterval]
206
+ )
207
+
208
+ // todo find better way
209
+ // when the initial data changes, then reset the plot. note that the initial data doesn't change
210
+ // during the normal course of updates from the observable, only when the plot is restarted.
211
+ useEffect(
212
+ () => {
213
+ dataRef.current = initialData.slice() as Array<TimeSeries>
214
+ seriesRef.current = new Map(initialData.map(series => [series.name, series as TimeSeries]))
215
+ currentTimeRef.current = new Map(Array.from<string>(xAxesState.axes.keys()).map(id => [id, 0]))
216
+ updateTimingAndPlot(new Map(Array.from(continuousAxisRanges(xAxesState.axes as Map<string, ContinuousNumericAxis>).entries())
217
+ .map(([id, range]) => {
218
+ // grab the current range, then calculate the minimum time from the initial data, and
219
+ // set that as the start, and then add the range to it for the end time
220
+ const [start, end] = range.original.asTuple()
221
+ const minTime = (initialData as Array<TimeSeries>)
222
+ .filter(srs => axisAssignments.get(srs.name)?.xAxis === id)
223
+ .reduce(
224
+ (tMin: number, series: TimeSeries) => Math.min(
225
+ tMin,
226
+ !series.isEmpty() ? series.data[0].time : tMin
227
+ ),
228
+ Infinity
229
+ )
230
+ const startTime = minTime === Infinity ? 0 : minTime
231
+ return [id, ContinuousAxisRange.from(startTime, startTime + end - start)]
232
+ })
233
+ )
234
+ )
235
+ },
236
+ // ** not happy about this **
237
+ // only want this effect to run when the initial data is changed, which mean all the
238
+ // other dependencies are recalculated anyway.
239
+ // eslint-disable-next-line react-hooks/exhaustive-deps
240
+ [initialData]
241
+ )
242
+
243
+ /**
244
+ * Calculates the upper and lower y-coordinate for the spike line
245
+ * @param categorySize The size of the category (i.e. plot_height / num_series)
246
+ * @param lineWidth The width of the series line
247
+ * @param margin The margin applied to the top and bottom of the spike line (vertical spacing)
248
+ * @return An object with two functions, that when handed a y-coordinate, return the location
249
+ * for the start (yUpper) or end (yLower) of the spikes line.
250
+ */
251
+ function yCoordsFn(categorySize: number, lineWidth: number, margin: number):
252
+ { yUpper: (y: number) => number, yLower: (y: number) => number } {
253
+ if (categorySize <= margin) return {
254
+ yUpper: y => y,
255
+ yLower: y => y + lineWidth
256
+ }
257
+ return {
258
+ yUpper: y => y + margin,
259
+ yLower: y => y + categorySize - margin
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Adjusts the time-range and updates the plot when the plot is dragged to the left or right
265
+ * @param deltaX The amount that the plot is dragged
266
+ * @param plotDimensions The dimensions of the plot
267
+ * @param series An array of series names
268
+ * @param ranges A map holding the axis ID and its associated time range
269
+ */
270
+ const onPan = useCallback(
271
+ (x: number,
272
+ plotDimensions: Dimensions,
273
+ ranges: Map<string, ContinuousAxisRange>
274
+ ) => panHandler(axesForSeries, margin, setAxisIntervalFor, xAxesState)(x, plotDimensions, ranges),
275
+ [axesForSeries, margin, setAxisIntervalFor, xAxesState]
276
+ )
277
+
278
+ /**
279
+ * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
280
+ * at the location of the mouse when the scroll wheel or gesture was applied.
281
+ * @param transform The d3 zoom transformation information
282
+ * @param x The x-position of the mouse when the scroll wheel or gesture is used
283
+ * @param plotDimensions The dimensions of the plot
284
+ * @param series An array of series names
285
+ * @param ranges A map holding the axis ID and its associated time-range
286
+ */
287
+ const onZoom = useCallback(
288
+ (
289
+ transform: ZoomTransform,
290
+ x: number,
291
+ plotDimensions: Dimensions,
292
+ ranges: Map<string, ContinuousAxisRange>,
293
+ ) => continuousAxisZoomHandler(axesForSeries, margin, setAxisIntervalFor, xAxesState)(transform, x, plotDimensions, ranges),
294
+ [axesForSeries, margin, setAxisIntervalFor, xAxesState]
295
+ )
296
+
297
+ /**
298
+ * @param timeRanges
299
+ * @param mainGElem
300
+ */
301
+ const updatePlot = useCallback(
302
+ (timeRanges: Map<string, ContinuousAxisRange>, mainGElem: GSelection) => {
303
+ if (container) {
304
+ // select the svg element bind the data to them
305
+ const svg = d3.select<SVGSVGElement, any>(container)
306
+
307
+ // set up panning
308
+ if (panEnabled) {
309
+ const drag = d3.drag<SVGSVGElement, Datum>()
310
+ .on("start", () => {
311
+ d3.select(container).style("cursor", "move")
312
+ allowTooltipRef.current = false
313
+ })
314
+ .on("drag", (event: any) => {
315
+ onPan(event.dx, plotDimensions, timeRanges)
316
+ // need to update the plot with the new time-ranges
317
+ updatePlotRef.current(timeRanges, mainGElem)
318
+ })
319
+ .on("end", () => {
320
+ d3.select(container).style("cursor", "auto")
321
+ allowTooltipRef.current = isSubscriptionClosed()
322
+ })
323
+
324
+ svg.call(drag)
325
+ }
326
+
327
+ // set up for zooming
328
+ if (zoomEnabled) {
329
+ const zoom = d3.zoom<SVGSVGElement, Datum>()
330
+ .filter((event: any) => !zoomKeyModifiersRequired || event.shiftKey || event.ctrlKey)
331
+ .scaleExtent([0, 10])
332
+ .translateExtent([[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]])
333
+ .on("zoom", (event: any) => {
334
+ onZoom(
335
+ event.transform,
336
+ event.sourceEvent.offsetX - margin.left,
337
+ plotDimensions,
338
+ timeRanges,
339
+ )
340
+ updatePlotRef.current(timeRanges, mainGElem)
341
+ }
342
+ )
343
+
344
+ svg.call(zoom)
345
+ }
346
+
347
+ // enter, update, delete the raster data
348
+ dataRef.current.forEach(series => {
349
+ const [xAxis, yAxis] = axesFor(
350
+ series.name,
351
+ axisAssignments,
352
+ axisId => xAxesState.axisFor(axisId).getOrUndefined(),
353
+ axisId => yAxesState.axisFor(axisId).getOrUndefined()
354
+ )
355
+
356
+ // grab the series styles, or the defaults if none exist
357
+ const {color, lineWidth, margin: spikeLineMargin = spikeMargin} = seriesStyles.get(series.name) || {
358
+ ...defaultLineStyle(),
359
+ highlightColor: defaultLineStyle().color
360
+ }
361
+
362
+ // only show the data for which the regex filter matches
363
+ const plotData = (series.name.match(seriesFilter)) ? series.data : []
364
+
365
+ const seriesContainer = svg
366
+ .select<SVGGElement>(`#${series.name}-${chartId}-raster`)
367
+ .selectAll<SVGLineElement, PixelDatum>('line')
368
+ .data(plotData as PixelDatum[])
369
+
370
+ //
371
+ // enter new elements
372
+ const {yUpper, yLower} = yCoordsFn(yAxis.scale.bandwidth(), lineWidth, spikeLineMargin)
373
+
374
+ // grab the value (index) associated with the series name (this is a category axis)
375
+ const y = yAxis.scale(series.name) || 0
376
+ // enter
377
+ seriesContainer
378
+ .enter()
379
+ .append<SVGLineElement>('line')
380
+ .attr('x1', datum => datum.x)
381
+ .attr('x2', datum => datum.x)
382
+ .attr('y1', _ => yUpper(y))
383
+ .attr('y2', _ => yLower(y))
384
+ .attr('stroke', color)
385
+ .attr('stroke-width', lineWidth)
386
+ .attr('class', 'spikes-lines')
387
+ .on(
388
+ "mouseover",
389
+ (event, datumArray) =>
390
+ handleMouseOverSeries(
391
+ container,
392
+ xAxis,
393
+ series.name,
394
+ datumArray,
395
+ event,
396
+ margin,
397
+ seriesStyles,
398
+ allowTooltipRef.current,
399
+ mouseOverHandlerFor(`tooltip-${chartId}`)
400
+ )
401
+ )
402
+ .on(
403
+ "mouseleave",
404
+ event => handleMouseLeaveSeries(
405
+ series.name,
406
+ event.currentTarget,
407
+ seriesStyles,
408
+ mouseLeaveHandlerFor(`tooltip-${chartId}`)
409
+ )
410
+ )
411
+ .each(datum => datum.x = xAxis.scale(datum.time))
412
+
413
+ // update
414
+ seriesContainer
415
+ .each(datum => datum.x = xAxis.scale(datum.time))
416
+ .attr('x1', datum => datum.x)
417
+ .attr('x2', datum => datum.x)
418
+ .attr('y1', _ => yUpper(y))
419
+ .attr('y2', _ => yLower(y))
420
+ .attr('stroke', color)
421
+
422
+ // exit old elements
423
+ seriesContainer.exit().remove()
424
+ })
425
+ }
426
+ },
427
+ [
428
+ axisAssignments, chartId, container, margin,
429
+ mouseLeaveHandlerFor, mouseOverHandlerFor, onPan, onZoom,
430
+ panEnabled,
431
+ plotDimensions,
432
+ seriesFilter, seriesStyles,
433
+ xAxesState, yAxesState,
434
+ zoomEnabled, zoomKeyModifiersRequired,
435
+ spikeMargin
436
+ ]
437
+ )
438
+
439
+ // need to keep the function references for use by the subscription, which forms a closure
440
+ // on them. without the references, the closures become stale, and resizing during streaming
441
+ // doesn't work properly
442
+ const updatePlotRef = useRef<(r: Map<string, ContinuousAxisRange>, g: GSelection) => void>(noop)
443
+ useEffect(
444
+ () => {
445
+ if (mainG !== null && container !== null) {
446
+ // when the update plot function doesn't yet exist, then create the container holding the plot
447
+ const clipPathId = setClipPathG(chartId, mainG, plotDimensions)
448
+ if (updatePlotRef.current === noop) {
449
+ mainG
450
+ .selectAll<SVGGElement, TimeSeries>('g')
451
+ .attr("clip-path", `url(#${clipPathId})`)
452
+ .data<TimeSeries>(dataRef.current)
453
+ .enter()
454
+ .append('g')
455
+ .attr('class', 'spikes-series')
456
+ .attr('id', series => `${series.name}-${chartId}-raster`)
457
+ .attr('transform', `translate(${margin.left}, ${margin.top})`)
458
+
459
+ } else {
460
+ mainG
461
+ .selectAll<SVGGElement, TimeSeries>('g')
462
+ .attr("clip-path", `url(#${clipPathId})`)
463
+ }
464
+ updatePlotRef.current = updatePlot
465
+ }
466
+ },
467
+ [chartId, container, mainG, margin, plotDimensions, updatePlot]
468
+ )
469
+
470
+ // grab a reference to the function used to update the time ranges and update that reference
471
+ // if the function changes (solve for stale closures)
472
+ const onUpdateTimeRef = useRef(updateAxisRanges)
473
+ useEffect(
474
+ () => {
475
+ onUpdateTimeRef.current = updateAxisRanges
476
+ },
477
+ [updateAxisRanges]
478
+ )
479
+
480
+ // memoized function for subscribing to the chart-data observable
481
+ const subscribe = useCallback(
482
+ () => {
483
+ if (seriesObservable === undefined || mainG === null) return undefined
484
+ if (withCadenceOf !== undefined) {
485
+ return subscriptionTimeSeriesWithCadenceFor(
486
+ seriesObservable as Observable<TimeSeriesChartData>,
487
+ onSubscribe,
488
+ windowingTime,
489
+ axisAssignments, xAxesState,
490
+ onUpdateData,
491
+ dropDataAfter,
492
+ updateTimingAndPlot,
493
+ // as new data flows into the subscription, the subscription
494
+ // updates this map directly (for performance)
495
+ seriesRef.current,
496
+ (axisId, end) => currentTimeRef.current.set(axisId, end),
497
+ withCadenceOf
498
+ )
499
+ }
500
+ return subscriptionTimeSeriesFor(
501
+ seriesObservable as Observable<TimeSeriesChartData>,
502
+ onSubscribe,
503
+ windowingTime,
504
+ axisAssignments, xAxesState,
505
+ onUpdateData,
506
+ dropDataAfter,
507
+ updateTimingAndPlot,
508
+ // as new data flows into the subscription, the subscription
509
+ // updates this map directly (for performance)
510
+ seriesRef.current,
511
+ (axisId, end) => currentTimeRef.current.set(axisId, end)
512
+ )
513
+ },
514
+ [
515
+ axisAssignments, dropDataAfter, mainG,
516
+ onSubscribe, onUpdateData,
517
+ seriesObservable, updateTimingAndPlot, windowingTime, xAxesState,
518
+ withCadenceOf
519
+ ]
520
+ )
521
+
522
+ const timeRangesRef = useRef<Map<string, ContinuousAxisRange>>(new Map())
523
+ useEffect(
524
+ () => {
525
+ if (container && mainG) {
526
+ // so this gets a bit complicated. the time-ranges need to be updated whenever the time-ranges
527
+ // change. for example, as data is streamed in, the times change, and then we need to update the
528
+ // time-range. however, we want to keep the time-ranges to reflect their original scale so that
529
+ // we can zoom properly (so the updates can't fuck with the scale). At the same time, when the
530
+ // interpolation changes, then the update plot changes, and the time-ranges must maintain their
531
+ // original scale as well.
532
+ if (timeRangesRef.current.size === 0) {
533
+ // when no time-ranges have yet been created, then create them and hold on to a mutable
534
+ // reference to them
535
+ timeRangesRef.current = continuousAxisRanges(xAxesState.axes as Map<string, ContinuousNumericAxis>)
536
+ } else {
537
+ // when the time-ranges already exist, then we want to update the time-ranges for each
538
+ // existing time-range in a way that maintains the original scale.
539
+ const intervals = continuousAxisIntervals(xAxesState.axes)
540
+ timeRangesRef.current
541
+ .forEach((range, id, rangesMap) => {
542
+ const [start, end] = Optional.ofNullable(intervals.get(id))
543
+ .map(interval => interval.asTuple())
544
+ .getOrThrow(() => new Error(`Unable to retrieve interval for axis; axis_id: ${id}`))
545
+ // .getOrElse([NaN, NaN])
546
+ if (!isNaN(start) && !isNaN(end)) {
547
+ // update the reference map with the new (start, end) portion of the range,
548
+ // while keeping the original scale intact
549
+ rangesMap.set(id, range.update(start, end))
550
+ }
551
+ })
552
+ }
553
+ updatePlot(timeRangesRef.current, mainG)
554
+ }
555
+ },
556
+ [chartId, color, container, mainG, plotDimensions, updatePlot, xAxesState]
557
+ )
558
+
559
+ // subscribe/unsubscribe to the observable chart data. when the `shouldSubscribe`
560
+ // is changed to `true` and we haven't subscribed yet, then subscribe. when the
561
+ // `shouldSubscribe` is `false` and we had subscribed, then unsubscribe. otherwise,
562
+ // do nothing.
563
+ useEffect(
564
+ () => {
565
+ if (shouldSubscribe && subscriptionRef.current === undefined) {
566
+ subscriptionRef.current = subscribe()
567
+ allowTooltipRef.current = false
568
+ } else if (!shouldSubscribe && subscriptionRef.current !== undefined) {
569
+ subscriptionRef.current?.unsubscribe()
570
+ subscriptionRef.current = undefined
571
+ allowTooltipRef.current = true
572
+ }
573
+ },
574
+ [shouldSubscribe, subscribe]
575
+ )
576
+
577
+ return null
578
+ }
579
+
580
+ /**
581
+ * Attempts to locate the x- and y-axes for the specified series. If no axis is found for the
582
+ * series name, then uses the default returned by the useChart() hook.
583
+ * @param seriesName Name of the series for which to retrieve the axis
584
+ * @param axisAssignments A map holding the series name and the associated x- and y-axes assigned
585
+ * to that series. Note that the series in the axis-assignment map is merely a subset of the set
586
+ * of series names.
587
+ * @param xAxisFor The function that accepts an axis ID and returns the corresponding x-axis
588
+ * @param yAxisFor The function that accepts an axis ID and returns the corresponding y-axis
589
+ * @return A 2d tuple holding the linear x-axis as its first element and the category y-axis as
590
+ * the second element.
591
+ */
592
+ function axesFor(
593
+ seriesName: string,
594
+ axisAssignments: Map<string, AxesAssignment>,
595
+ xAxisFor: (id: string) => BaseAxis | undefined,
596
+ yAxisFor: (id: string) => BaseAxis | undefined,
597
+ ): [xAxis: ContinuousNumericAxis, yAxis: OrdinalStringAxis] {
598
+ const axes = axisAssignments.get(seriesName)
599
+ const xAxis = xAxisFor(axes?.xAxis || "")
600
+ const xAxisLinear = xAxis as ContinuousNumericAxis
601
+ if (xAxis && !xAxisLinear) {
602
+ throw Error("Raster plot requires that x-axis be of type LinearAxis")
603
+ }
604
+ const yAxis = yAxisFor(axes?.yAxis || "")
605
+ const yAxisCategory = yAxis as OrdinalStringAxis
606
+ if (yAxis && !yAxisCategory) {
607
+ throw Error("Raster plot requires that y-axis be of type CategoryAxis")
608
+ }
609
+ return [xAxisLinear, yAxisCategory]
610
+ }
611
+
612
+ /**
613
+ * Renders a tooltip showing the neuron, spike time, and the spike strength when the mouse hovers over a spike.
614
+ * @param container The chart container
615
+ * @param xAxis The x-axis
616
+ * @param seriesName The name of the series (i.e. the neuron ID)
617
+ * @param selectedDatum The selected datum
618
+ * @param event The mouse-over series event
619
+ * @param margin The plot margin
620
+ * @param seriesStyles The series style information (needed for (un)highlighting)
621
+ * @param allowTooltip When set to `false` won't show tooltip, even if it is visible (used by pan)
622
+ * @param mouseOverHandlerFor The handler for the mouse over (registered by the <Tooltip/>)
623
+ */
624
+ function handleMouseOverSeries(
625
+ container: SVGSVGElement,
626
+ xAxis: ContinuousNumericAxis,
627
+ seriesName: string,
628
+ selectedDatum: Datum,
629
+ event: React.MouseEvent<SVGPathElement>,
630
+ margin: Margin,
631
+ seriesStyles: Map<string, SeriesLineStyle>,
632
+ allowTooltip: boolean,
633
+ mouseOverHandlerFor: ((seriesName: string, time: number, tooltipData: TooltipData<Datum, NoTooltipMetadata>, mouseCoords: [x: number, y: number]) => void) | undefined,
634
+ ): void {
635
+ // grab the time needed for the tooltip ID
636
+ const [x, y] = d3.pointer(event, container)
637
+ const time = Math.round(xAxis.scale.invert(x - margin.left))
638
+
639
+ const {highlightColor, highlightWidth} = seriesStyles.get(seriesName) || defaultLineStyle()
640
+
641
+ // Use d3 to select element, change color and size
642
+ d3.select<SVGPathElement, Datum>(event.currentTarget)
643
+ .attr('stroke', highlightColor)
644
+ .attr('stroke-width', highlightWidth)
645
+
646
+ if (mouseOverHandlerFor && allowTooltip) {
647
+ // the contract for the mouse over handler is for a time-series, but here we only
648
+ // need one point, the selected datum, and so we convert it into an array of point
649
+ // (i.e. a time-series). The category tooltip is (and custom ones, must) be
650
+ // written to expect only the selected point
651
+ mouseOverHandlerFor(seriesName, time, {series: [selectedDatum], metadata: {}}, [x, y])
652
+ }
653
+ }
654
+
655
+ /**
656
+ * Unselects the time series and calls the mouse-leave-series handler registered for this series.
657
+ * @param seriesName The name of the series (i.e. the neuron ID)
658
+ * @param segment The SVG line element representing the spike, over which the mouse is hovering.
659
+ * @param seriesStyles The styles for the series (for (un)highlighting)
660
+ * @param mouseLeaverHandlerFor Registered handler for the series when the mouse leaves
661
+ */
662
+ function handleMouseLeaveSeries(
663
+ seriesName: string,
664
+ segment: SVGPathElement,
665
+ seriesStyles: Map<string, SeriesLineStyle>,
666
+ mouseLeaverHandlerFor: ((seriesName: string) => void) | undefined,
667
+ ): void {
668
+ const {color, lineWidth} = seriesStyles.get(seriesName) || defaultLineStyle()
669
+ d3.select<SVGPathElement, Datum>(segment)
670
+ .attr('stroke', color)
671
+ .attr('stroke-width', lineWidth)
672
+
673
+ if (mouseLeaverHandlerFor) {
674
+ mouseLeaverHandlerFor(seriesName)
675
+ }
676
+ }