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