stream-charts 1.2.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +809 -1022
- package/changes.md +36 -0
- package/dist/app/charts/Chart.d.ts +35 -139
- package/dist/app/charts/Chart.d.ts.map +1 -0
- package/dist/app/charts/Chart.js +74 -65
- package/dist/app/charts/Chart.js.map +1 -1
- package/dist/app/charts/axes/AxesState.d.ts +26 -18
- package/dist/app/charts/axes/AxesState.d.ts.map +1 -0
- package/dist/app/charts/axes/AxesState.js +26 -24
- package/dist/app/charts/axes/AxesState.js.map +1 -1
- package/dist/app/charts/axes/AxisInterval.d.ts +44 -26
- package/dist/app/charts/axes/AxisInterval.d.ts.map +1 -0
- package/dist/app/charts/axes/AxisInterval.js +48 -29
- package/dist/app/charts/axes/AxisInterval.js.map +1 -1
- package/dist/app/charts/axes/BaseAxisRange.d.ts +46 -22
- package/dist/app/charts/axes/BaseAxisRange.d.ts.map +1 -0
- package/dist/app/charts/axes/BaseAxisRange.js +17 -12
- package/dist/app/charts/axes/BaseAxisRange.js.map +1 -1
- package/dist/app/charts/axes/ContinuousAxis.d.ts +47 -6
- package/dist/app/charts/axes/ContinuousAxis.d.ts.map +1 -0
- package/dist/app/charts/axes/ContinuousAxis.js +160 -27
- package/dist/app/charts/axes/ContinuousAxis.js.map +1 -1
- package/dist/app/charts/axes/ContinuousAxisRange.d.ts +29 -10
- package/dist/app/charts/axes/ContinuousAxisRange.d.ts.map +1 -0
- package/dist/app/charts/axes/ContinuousAxisRange.js +29 -11
- package/dist/app/charts/axes/ContinuousAxisRange.js.map +1 -1
- package/dist/app/charts/axes/EmptyAxis.d.ts +5 -2
- package/dist/app/charts/axes/EmptyAxis.d.ts.map +1 -0
- package/dist/app/charts/axes/EmptyAxis.js +30 -9
- package/dist/app/charts/axes/EmptyAxis.js.map +1 -1
- package/dist/app/charts/axes/OrdinalAxis.d.ts +5 -3
- package/dist/app/charts/axes/OrdinalAxis.d.ts.map +1 -0
- package/dist/app/charts/axes/OrdinalAxis.js +39 -16
- package/dist/app/charts/axes/OrdinalAxis.js.map +1 -1
- package/dist/app/charts/axes/OrdinalAxisRange.d.ts +76 -13
- package/dist/app/charts/axes/OrdinalAxisRange.d.ts.map +1 -0
- package/dist/app/charts/axes/OrdinalAxisRange.js +95 -16
- package/dist/app/charts/axes/OrdinalAxisRange.js.map +1 -1
- package/dist/app/charts/axes/autoScale.d.ts +111 -0
- package/dist/app/charts/axes/autoScale.d.ts.map +1 -0
- package/dist/app/charts/axes/autoScale.js +188 -0
- package/dist/app/charts/axes/autoScale.js.map +1 -0
- package/dist/app/charts/axes/axes.d.ts +273 -150
- package/dist/app/charts/axes/axes.d.ts.map +1 -0
- package/dist/app/charts/axes/axes.js +808 -583
- package/dist/app/charts/axes/axes.js.map +1 -1
- package/dist/app/charts/d3types.d.ts +40 -11
- package/dist/app/charts/d3types.d.ts.map +1 -0
- package/dist/app/charts/d3types.js +3 -0
- package/dist/app/charts/d3types.js.map +1 -1
- package/dist/app/charts/datasources/StreamingDataSource.d.ts +149 -0
- package/dist/app/charts/datasources/StreamingDataSource.d.ts.map +1 -0
- package/dist/app/charts/datasources/StreamingDataSource.js +110 -0
- package/dist/app/charts/datasources/StreamingDataSource.js.map +1 -0
- package/dist/app/charts/datasources/iteratesDataSource.d.ts +73 -0
- package/dist/app/charts/datasources/iteratesDataSource.d.ts.map +1 -0
- package/dist/app/charts/datasources/iteratesDataSource.js +87 -0
- package/dist/app/charts/datasources/iteratesDataSource.js.map +1 -0
- package/dist/app/charts/datasources/ordinalDataSource.d.ts +75 -0
- package/dist/app/charts/datasources/ordinalDataSource.d.ts.map +1 -0
- package/dist/app/charts/datasources/ordinalDataSource.js +135 -0
- package/dist/app/charts/datasources/ordinalDataSource.js.map +1 -0
- package/dist/app/charts/datasources/outlierDataSource.d.ts +53 -0
- package/dist/app/charts/datasources/outlierDataSource.d.ts.map +1 -0
- package/dist/app/charts/datasources/outlierDataSource.js +57 -0
- package/dist/app/charts/datasources/outlierDataSource.js.map +1 -0
- package/dist/app/charts/datasources/timeSeriesDataSource.d.ts +77 -0
- package/dist/app/charts/datasources/timeSeriesDataSource.d.ts.map +1 -0
- package/dist/app/charts/datasources/timeSeriesDataSource.js +85 -0
- package/dist/app/charts/datasources/timeSeriesDataSource.js.map +1 -0
- package/dist/app/charts/filters/regexFilter.d.ts +31 -4
- package/dist/app/charts/filters/regexFilter.d.ts.map +1 -0
- package/dist/app/charts/filters/regexFilter.js +38 -4
- package/dist/app/charts/filters/regexFilter.js.map +1 -1
- package/dist/app/charts/hooks/AxesProvider.d.ts +21 -0
- package/dist/app/charts/hooks/AxesProvider.d.ts.map +1 -0
- package/dist/app/charts/hooks/AxesProvider.js +213 -0
- package/dist/app/charts/hooks/AxesProvider.js.map +1 -0
- package/dist/app/charts/hooks/CanvasSurfaceProvider.d.ts +25 -0
- package/dist/app/charts/hooks/CanvasSurfaceProvider.d.ts.map +1 -0
- package/dist/app/charts/hooks/CanvasSurfaceProvider.js +76 -0
- package/dist/app/charts/hooks/CanvasSurfaceProvider.js.map +1 -0
- package/dist/app/charts/hooks/ChartProvider.d.ts +40 -0
- package/dist/app/charts/hooks/ChartProvider.d.ts.map +1 -0
- package/dist/app/charts/hooks/ChartProvider.js +33 -0
- package/dist/app/charts/hooks/ChartProvider.js.map +1 -0
- package/dist/app/charts/hooks/InteractionProvider.d.ts +23 -0
- package/dist/app/charts/hooks/InteractionProvider.d.ts.map +1 -0
- package/dist/app/charts/hooks/InteractionProvider.js +60 -0
- package/dist/app/charts/hooks/InteractionProvider.js.map +1 -0
- package/dist/app/charts/hooks/PlotDimensionsProvider.d.ts +16 -0
- package/dist/app/charts/hooks/PlotDimensionsProvider.d.ts.map +1 -0
- package/dist/app/charts/hooks/PlotDimensionsProvider.js +76 -0
- package/dist/app/charts/hooks/PlotDimensionsProvider.js.map +1 -0
- package/dist/app/charts/hooks/defaultAxesValues.d.ts +11 -0
- package/dist/app/charts/hooks/defaultAxesValues.d.ts.map +1 -0
- package/dist/app/charts/hooks/defaultAxesValues.js +36 -0
- package/dist/app/charts/hooks/defaultAxesValues.js.map +1 -0
- package/dist/app/charts/hooks/defaultMouseValues.d.ts +9 -0
- package/dist/app/charts/hooks/defaultMouseValues.d.ts.map +1 -0
- package/dist/app/charts/hooks/defaultMouseValues.js +23 -0
- package/dist/app/charts/hooks/defaultMouseValues.js.map +1 -0
- package/dist/app/charts/hooks/defaultPlotDimensions.d.ts +13 -0
- package/dist/app/charts/hooks/defaultPlotDimensions.d.ts.map +1 -0
- package/dist/app/charts/hooks/defaultPlotDimensions.js +23 -0
- package/dist/app/charts/hooks/defaultPlotDimensions.js.map +1 -0
- package/dist/app/charts/hooks/defaultTooltipValues.d.ts +9 -0
- package/dist/app/charts/hooks/defaultTooltipValues.d.ts.map +1 -0
- package/dist/app/charts/hooks/defaultTooltipValues.js +21 -0
- package/dist/app/charts/hooks/defaultTooltipValues.js.map +1 -0
- package/dist/app/charts/hooks/defaultUseChartValues.d.ts +14 -0
- package/dist/app/charts/hooks/defaultUseChartValues.d.ts.map +1 -0
- package/dist/app/charts/hooks/defaultUseChartValues.js +30 -0
- package/dist/app/charts/hooks/defaultUseChartValues.js.map +1 -0
- package/dist/app/charts/hooks/useAxes.d.ts +49 -36
- package/dist/app/charts/hooks/useAxes.d.ts.map +1 -0
- package/dist/app/charts/hooks/useAxes.js +16 -176
- package/dist/app/charts/hooks/useAxes.js.map +1 -1
- package/dist/app/charts/hooks/useCanvasSurface.d.ts +25 -0
- package/dist/app/charts/hooks/useCanvasSurface.d.ts.map +1 -0
- package/dist/app/charts/hooks/useCanvasSurface.js +18 -0
- package/dist/app/charts/hooks/useCanvasSurface.js.map +1 -0
- package/dist/app/charts/hooks/useChart.d.ts +30 -44
- package/dist/app/charts/hooks/useChart.d.ts.map +1 -0
- package/dist/app/charts/hooks/useChart.js +26 -48
- package/dist/app/charts/hooks/useChart.js.map +1 -1
- package/dist/app/charts/hooks/useDataSource.d.ts +68 -0
- package/dist/app/charts/hooks/useDataSource.d.ts.map +1 -0
- package/dist/app/charts/hooks/useDataSource.js +63 -0
- package/dist/app/charts/hooks/useDataSource.js.map +1 -0
- package/dist/app/charts/hooks/useHoveredSeries.d.ts +25 -0
- package/dist/app/charts/hooks/useHoveredSeries.d.ts.map +1 -0
- package/dist/app/charts/hooks/useHoveredSeries.js +15 -0
- package/dist/app/charts/hooks/useHoveredSeries.js.map +1 -0
- package/dist/app/charts/hooks/useMouse.d.ts +46 -22
- package/dist/app/charts/hooks/useMouse.d.ts.map +1 -0
- package/dist/app/charts/hooks/useMouse.js +12 -56
- package/dist/app/charts/hooks/useMouse.js.map +1 -1
- package/dist/app/charts/hooks/usePlotDimensions.d.ts +25 -13
- package/dist/app/charts/hooks/usePlotDimensions.d.ts.map +1 -0
- package/dist/app/charts/hooks/usePlotDimensions.js +6 -62
- package/dist/app/charts/hooks/usePlotDimensions.js.map +1 -1
- package/dist/app/charts/hooks/useTooltip.d.ts +24 -26
- package/dist/app/charts/hooks/useTooltip.d.ts.map +1 -0
- package/dist/app/charts/hooks/useTooltip.js +12 -42
- package/dist/app/charts/hooks/useTooltip.js.map +1 -1
- package/dist/app/charts/index.d.ts +66 -0
- package/dist/app/charts/index.d.ts.map +1 -0
- package/dist/app/charts/index.js +87 -0
- package/dist/app/charts/index.js.map +1 -0
- package/dist/app/charts/legends/Legend.d.ts +33 -12
- package/dist/app/charts/legends/Legend.d.ts.map +1 -0
- package/dist/app/charts/legends/Legend.js +138 -379
- package/dist/app/charts/legends/Legend.js.map +1 -1
- package/dist/app/charts/legends/constants.d.ts +19 -0
- package/dist/app/charts/legends/constants.d.ts.map +1 -0
- package/dist/app/charts/legends/constants.js +34 -0
- package/dist/app/charts/legends/constants.js.map +1 -0
- package/dist/app/charts/observables/ChartData.d.ts +1 -0
- package/dist/app/charts/observables/ChartData.d.ts.map +1 -0
- package/dist/app/charts/observables/iterates.d.ts +13 -7
- package/dist/app/charts/observables/iterates.d.ts.map +1 -0
- package/dist/app/charts/observables/iterates.js +42 -9
- package/dist/app/charts/observables/iterates.js.map +1 -1
- package/dist/app/charts/observables/ordinals.d.ts +70 -12
- package/dist/app/charts/observables/ordinals.d.ts.map +1 -0
- package/dist/app/charts/observables/ordinals.js +127 -48
- package/dist/app/charts/observables/ordinals.js.map +1 -1
- package/dist/app/charts/observables/outliers.d.ts +18 -0
- package/dist/app/charts/observables/outliers.d.ts.map +1 -0
- package/dist/app/charts/observables/outliers.js +2 -0
- package/dist/app/charts/observables/outliers.js.map +1 -0
- package/dist/app/charts/plots/BarPlot.d.ts +87 -13
- package/dist/app/charts/plots/BarPlot.d.ts.map +1 -0
- package/dist/app/charts/plots/BarPlot.js +603 -356
- package/dist/app/charts/plots/BarPlot.js.map +1 -1
- package/dist/app/charts/plots/OutlierPlot.d.ts +123 -0
- package/dist/app/charts/plots/OutlierPlot.d.ts.map +1 -0
- package/dist/app/charts/plots/OutlierPlot.js +430 -0
- package/dist/app/charts/plots/OutlierPlot.js.map +1 -0
- package/dist/app/charts/plots/PoincarePlot.d.ts +92 -14
- package/dist/app/charts/plots/PoincarePlot.d.ts.map +1 -0
- package/dist/app/charts/plots/PoincarePlot.js +523 -364
- package/dist/app/charts/plots/PoincarePlot.js.map +1 -1
- package/dist/app/charts/plots/RasterPlot.d.ts +31 -10
- package/dist/app/charts/plots/RasterPlot.d.ts.map +1 -0
- package/dist/app/charts/plots/RasterPlot.js +198 -306
- package/dist/app/charts/plots/RasterPlot.js.map +1 -1
- package/dist/app/charts/plots/ScatterPlot.d.ts +77 -21
- package/dist/app/charts/plots/ScatterPlot.d.ts.map +1 -0
- package/dist/app/charts/plots/ScatterPlot.js +359 -302
- package/dist/app/charts/plots/ScatterPlot.js.map +1 -1
- package/dist/app/charts/plots/autoScaleYAxes.d.ts +43 -0
- package/dist/app/charts/plots/autoScaleYAxes.d.ts.map +1 -0
- package/dist/app/charts/plots/autoScaleYAxes.js +71 -0
- package/dist/app/charts/plots/autoScaleYAxes.js.map +1 -0
- package/dist/app/charts/plots/colorizedLine.d.ts +61 -0
- package/dist/app/charts/plots/colorizedLine.d.ts.map +1 -0
- package/dist/app/charts/plots/colorizedLine.js +157 -0
- package/dist/app/charts/plots/colorizedLine.js.map +1 -0
- package/dist/app/charts/plots/constants.d.ts +7 -0
- package/dist/app/charts/plots/constants.d.ts.map +1 -0
- package/dist/app/charts/plots/constants.js +11 -0
- package/dist/app/charts/plots/constants.js.map +1 -0
- package/dist/app/charts/plots/hitTesting.d.ts +88 -0
- package/dist/app/charts/plots/hitTesting.d.ts.map +1 -0
- package/dist/app/charts/plots/hitTesting.js +136 -0
- package/dist/app/charts/plots/hitTesting.js.map +1 -0
- package/dist/app/charts/plots/plot.d.ts +52 -16
- package/dist/app/charts/plots/plot.d.ts.map +1 -0
- package/dist/app/charts/plots/plot.js +108 -35
- package/dist/app/charts/plots/plot.js.map +1 -1
- package/dist/app/charts/plots/useCanvasZoomPan.d.ts +119 -0
- package/dist/app/charts/plots/useCanvasZoomPan.d.ts.map +1 -0
- package/dist/app/charts/plots/useCanvasZoomPan.js +113 -0
- package/dist/app/charts/plots/useCanvasZoomPan.js.map +1 -0
- package/dist/app/charts/plots/useStreamingTimeAxes.d.ts +103 -0
- package/dist/app/charts/plots/useStreamingTimeAxes.d.ts.map +1 -0
- package/dist/app/charts/plots/useStreamingTimeAxes.js +257 -0
- package/dist/app/charts/plots/useStreamingTimeAxes.js.map +1 -0
- package/dist/app/charts/series/baseSeries.d.ts +60 -8
- package/dist/app/charts/series/baseSeries.d.ts.map +1 -0
- package/dist/app/charts/series/baseSeries.js +53 -8
- package/dist/app/charts/series/baseSeries.js.map +1 -1
- package/dist/app/charts/series/iterateSeries.d.ts +9 -8
- package/dist/app/charts/series/iterateSeries.d.ts.map +1 -0
- package/dist/app/charts/series/iterateSeries.js +7 -7
- package/dist/app/charts/series/iterateSeries.js.map +1 -1
- package/dist/app/charts/series/ordinalSeries.d.ts +27 -13
- package/dist/app/charts/series/ordinalSeries.d.ts.map +1 -0
- package/dist/app/charts/series/ordinalSeries.js +29 -15
- package/dist/app/charts/series/ordinalSeries.js.map +1 -1
- package/dist/app/charts/series/outlierSeries.d.ts +142 -0
- package/dist/app/charts/series/outlierSeries.d.ts.map +1 -0
- package/dist/app/charts/series/outlierSeries.js +82 -0
- package/dist/app/charts/series/outlierSeries.js.map +1 -0
- package/dist/app/charts/series/timeSeries.d.ts +13 -9
- package/dist/app/charts/series/timeSeries.d.ts.map +1 -0
- package/dist/app/charts/series/timeSeries.js +6 -3
- package/dist/app/charts/series/timeSeries.js.map +1 -1
- package/dist/app/charts/series/timeSeriesChartData.d.ts +18 -10
- package/dist/app/charts/series/timeSeriesChartData.d.ts.map +1 -0
- package/dist/app/charts/series/timeSeriesChartData.js +28 -14
- package/dist/app/charts/series/timeSeriesChartData.js.map +1 -1
- package/dist/app/charts/styling/barPlotStyle.d.ts +46 -2
- package/dist/app/charts/styling/barPlotStyle.d.ts.map +1 -0
- package/dist/app/charts/styling/barPlotStyle.js +37 -3
- package/dist/app/charts/styling/barPlotStyle.js.map +1 -1
- package/dist/app/charts/styling/canvasStyle.d.ts +53 -0
- package/dist/app/charts/styling/canvasStyle.d.ts.map +1 -0
- package/dist/app/charts/styling/canvasStyle.js +71 -0
- package/dist/app/charts/styling/canvasStyle.js.map +1 -0
- package/dist/app/charts/styling/margins.d.ts +21 -2
- package/dist/app/charts/styling/margins.d.ts.map +1 -0
- package/dist/app/charts/styling/margins.js +25 -4
- package/dist/app/charts/styling/margins.js.map +1 -1
- package/dist/app/charts/styling/svgStyle.d.ts +10 -56
- package/dist/app/charts/styling/svgStyle.d.ts.map +1 -0
- package/dist/app/charts/styling/svgStyle.js +3 -78
- package/dist/app/charts/styling/svgStyle.js.map +1 -1
- package/dist/app/charts/subscriptions/viewDrivers.d.ts +124 -0
- package/dist/app/charts/subscriptions/viewDrivers.d.ts.map +1 -0
- package/dist/app/charts/subscriptions/viewDrivers.js +338 -0
- package/dist/app/charts/subscriptions/viewDrivers.js.map +1 -0
- package/dist/app/charts/tooltips/BarPlotTooltipContent.d.ts +12 -78
- package/dist/app/charts/tooltips/BarPlotTooltipContent.d.ts.map +1 -0
- package/dist/app/charts/tooltips/BarPlotTooltipContent.js +65 -257
- package/dist/app/charts/tooltips/BarPlotTooltipContent.js.map +1 -1
- package/dist/app/charts/tooltips/DefaultOutlierTooltipContent.d.ts +9 -0
- package/dist/app/charts/tooltips/DefaultOutlierTooltipContent.d.ts.map +1 -0
- package/dist/app/charts/tooltips/DefaultOutlierTooltipContent.js +16 -0
- package/dist/app/charts/tooltips/DefaultOutlierTooltipContent.js.map +1 -0
- package/dist/app/charts/tooltips/OutlierPlotTooltipContent.d.ts +30 -0
- package/dist/app/charts/tooltips/OutlierPlotTooltipContent.d.ts.map +1 -0
- package/dist/app/charts/tooltips/OutlierPlotTooltipContent.js +26 -0
- package/dist/app/charts/tooltips/OutlierPlotTooltipContent.js.map +1 -0
- package/dist/app/charts/tooltips/PoincarePlotTooltipContent.d.ts +22 -72
- package/dist/app/charts/tooltips/PoincarePlotTooltipContent.d.ts.map +1 -0
- package/dist/app/charts/tooltips/PoincarePlotTooltipContent.js +104 -171
- package/dist/app/charts/tooltips/PoincarePlotTooltipContent.js.map +1 -1
- package/dist/app/charts/tooltips/RasterPlotTooltipContent.d.ts +32 -8
- package/dist/app/charts/tooltips/RasterPlotTooltipContent.d.ts.map +1 -0
- package/dist/app/charts/tooltips/RasterPlotTooltipContent.js +62 -115
- package/dist/app/charts/tooltips/RasterPlotTooltipContent.js.map +1 -1
- package/dist/app/charts/tooltips/ScatterPlotTooltipContent.d.ts +39 -12
- package/dist/app/charts/tooltips/ScatterPlotTooltipContent.d.ts.map +1 -0
- package/dist/app/charts/tooltips/ScatterPlotTooltipContent.js +162 -134
- package/dist/app/charts/tooltips/ScatterPlotTooltipContent.js.map +1 -1
- package/dist/app/charts/tooltips/Tooltip.d.ts +27 -32
- package/dist/app/charts/tooltips/Tooltip.d.ts.map +1 -0
- package/dist/app/charts/tooltips/Tooltip.js +37 -81
- package/dist/app/charts/tooltips/Tooltip.js.map +1 -1
- package/dist/app/charts/tooltips/TooltipContent.d.ts +81 -0
- package/dist/app/charts/tooltips/TooltipContent.d.ts.map +1 -0
- package/dist/app/charts/tooltips/TooltipContent.js +99 -0
- package/dist/app/charts/tooltips/TooltipContent.js.map +1 -0
- package/dist/app/charts/tooltips/tooltipUtils.d.ts +186 -36
- package/dist/app/charts/tooltips/tooltipUtils.d.ts.map +1 -0
- package/dist/app/charts/tooltips/tooltipUtils.js +277 -48
- package/dist/app/charts/tooltips/tooltipUtils.js.map +1 -1
- package/dist/app/charts/tooltips/useOutlierTooltip.d.ts +59 -0
- package/dist/app/charts/tooltips/useOutlierTooltip.d.ts.map +1 -0
- package/dist/app/charts/tooltips/useOutlierTooltip.js +16 -0
- package/dist/app/charts/tooltips/useOutlierTooltip.js.map +1 -0
- package/dist/app/charts/tooltips/useTooltipPlacement.d.ts +51 -0
- package/dist/app/charts/tooltips/useTooltipPlacement.d.ts.map +1 -0
- package/dist/app/charts/tooltips/useTooltipPlacement.js +52 -0
- package/dist/app/charts/tooltips/useTooltipPlacement.js.map +1 -0
- package/dist/app/charts/tooltips/useTooltipStyle.d.ts +11 -0
- package/dist/app/charts/tooltips/useTooltipStyle.d.ts.map +1 -0
- package/dist/app/charts/tooltips/useTooltipStyle.js +14 -0
- package/dist/app/charts/tooltips/useTooltipStyle.js.map +1 -0
- package/dist/app/charts/trackers/Tracker.d.ts +22 -6
- package/dist/app/charts/trackers/Tracker.d.ts.map +1 -0
- package/dist/app/charts/trackers/Tracker.js +36 -22
- package/dist/app/charts/trackers/Tracker.js.map +1 -1
- package/dist/app/charts/trackers/trackerUtils.d.ts +40 -18
- package/dist/app/charts/trackers/trackerUtils.d.ts.map +1 -0
- package/dist/app/charts/trackers/trackerUtils.js +213 -300
- package/dist/app/charts/trackers/trackerUtils.js.map +1 -1
- package/dist/app/charts/utils.d.ts +72 -33
- package/dist/app/charts/utils.d.ts.map +1 -0
- package/dist/app/charts/utils.js +87 -42
- package/dist/app/charts/utils.js.map +1 -1
- package/dist/index.d.ts +2 -45
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -72
- package/dist/index.js.map +1 -1
- package/package.json +87 -46
- package/src/app/charts/Chart.tsx +132 -168
- package/src/app/charts/axes/AxesState.ts +27 -29
- package/src/app/charts/axes/AxisInterval.ts +57 -32
- package/src/app/charts/axes/BaseAxisRange.ts +47 -27
- package/src/app/charts/axes/ContinuousAxis.tsx +235 -39
- package/src/app/charts/axes/ContinuousAxisRange.ts +29 -11
- package/src/app/charts/axes/EmptyAxis.tsx +52 -17
- package/src/app/charts/axes/OrdinalAxis.tsx +54 -28
- package/src/app/charts/axes/OrdinalAxisRange.ts +96 -16
- package/src/app/charts/axes/autoScale.ts +244 -0
- package/src/app/charts/axes/axes.ts +954 -735
- package/src/app/charts/d3types.ts +42 -13
- package/src/app/charts/datasources/StreamingDataSource.ts +214 -0
- package/src/app/charts/datasources/iteratesDataSource.ts +122 -0
- package/src/app/charts/datasources/ordinalDataSource.ts +205 -0
- package/src/app/charts/datasources/outlierDataSource.ts +90 -0
- package/src/app/charts/datasources/timeSeriesDataSource.ts +133 -0
- package/src/app/charts/filters/regexFilter.ts +45 -5
- package/src/app/charts/hooks/AxesProvider.tsx +301 -0
- package/src/app/charts/hooks/CanvasSurfaceProvider.tsx +117 -0
- package/src/app/charts/hooks/ChartProvider.tsx +79 -0
- package/src/app/charts/hooks/InteractionProvider.tsx +88 -0
- package/src/app/charts/hooks/PlotDimensionsProvider.tsx +109 -0
- package/src/app/charts/hooks/defaultAxesValues.ts +40 -0
- package/src/app/charts/hooks/defaultMouseValues.ts +24 -0
- package/src/app/charts/hooks/defaultPlotDimensions.ts +27 -0
- package/src/app/charts/hooks/defaultTooltipValues.tsx +23 -0
- package/src/app/charts/hooks/defaultUseChartValues.tsx +36 -0
- package/src/app/charts/hooks/useAxes.tsx +60 -228
- package/src/app/charts/hooks/useCanvasSurface.tsx +35 -0
- package/src/app/charts/hooks/useChart.tsx +47 -106
- package/src/app/charts/hooks/useDataSource.tsx +106 -0
- package/src/app/charts/hooks/useHoveredSeries.tsx +33 -0
- package/src/app/charts/hooks/useMouse.tsx +58 -82
- package/src/app/charts/hooks/usePlotDimensions.tsx +25 -89
- package/src/app/charts/hooks/useTooltip.tsx +32 -63
- package/src/app/charts/index.ts +213 -0
- package/src/app/charts/legends/Legend.tsx +198 -479
- package/src/app/charts/legends/constants.ts +37 -0
- package/src/app/charts/observables/iterates.ts +32 -12
- package/src/app/charts/observables/ordinals.ts +141 -67
- package/src/app/charts/observables/outliers.ts +20 -0
- package/src/app/charts/plots/BarPlot.tsx +784 -666
- package/src/app/charts/plots/OutlierPlot.tsx +685 -0
- package/src/app/charts/plots/PoincarePlot.tsx +707 -489
- package/src/app/charts/plots/RasterPlot.tsx +253 -445
- package/src/app/charts/plots/ScatterPlot.tsx +480 -463
- package/src/app/charts/plots/autoScaleYAxes.ts +102 -0
- package/src/app/charts/plots/colorizedLine.ts +185 -0
- package/src/app/charts/plots/constants.ts +16 -0
- package/src/app/charts/plots/hitTesting.ts +198 -0
- package/src/app/charts/plots/plot.ts +138 -44
- package/src/app/charts/plots/useCanvasZoomPan.ts +240 -0
- package/src/app/charts/plots/useStreamingTimeAxes.ts +384 -0
- package/src/app/charts/series/baseSeries.ts +81 -14
- package/src/app/charts/series/iterateSeries.ts +8 -8
- package/src/app/charts/series/ordinalSeries.ts +35 -20
- package/src/app/charts/series/outlierSeries.ts +188 -0
- package/src/app/charts/series/timeSeries.ts +14 -12
- package/src/app/charts/series/timeSeriesChartData.ts +31 -17
- package/src/app/charts/styling/barPlotStyle.ts +48 -5
- package/src/app/charts/styling/canvasStyle.ts +86 -0
- package/src/app/charts/styling/margins.ts +28 -4
- package/src/app/charts/styling/svgStyle.ts +10 -91
- package/src/app/charts/subscriptions/viewDrivers.ts +447 -0
- package/src/app/charts/tooltips/BarPlotTooltipContent.tsx +156 -380
- package/src/app/charts/tooltips/DefaultOutlierTooltipContent.tsx +40 -0
- package/src/app/charts/tooltips/OutlierPlotTooltipContent.tsx +67 -0
- package/src/app/charts/tooltips/PoincarePlotTooltipContent.tsx +229 -292
- package/src/app/charts/tooltips/RasterPlotTooltipContent.tsx +164 -217
- package/src/app/charts/tooltips/ScatterPlotTooltipContent.tsx +373 -236
- package/src/app/charts/tooltips/Tooltip.tsx +58 -124
- package/src/app/charts/tooltips/TooltipContent.tsx +177 -0
- package/src/app/charts/tooltips/tooltipUtils.ts +359 -61
- package/src/app/charts/tooltips/useOutlierTooltip.tsx +68 -0
- package/src/app/charts/tooltips/useTooltipPlacement.ts +86 -0
- package/src/app/charts/tooltips/useTooltipStyle.ts +15 -0
- package/src/app/charts/trackers/Tracker.tsx +68 -33
- package/src/app/charts/trackers/trackerUtils.ts +263 -376
- package/src/app/charts/utils.ts +90 -48
- package/src/index.ts +2 -322
- package/dist/app/charts/axes/AxesState.test.d.ts +0 -1
- package/dist/app/charts/axes/AxesState.test.js +0 -30
- package/dist/app/charts/axes/AxesState.test.js.map +0 -1
- package/dist/app/charts/axes/AxisInterval.test.d.ts +0 -1
- package/dist/app/charts/axes/AxisInterval.test.js +0 -120
- package/dist/app/charts/axes/AxisInterval.test.js.map +0 -1
- package/dist/app/charts/axes/ContinuousAxisRange.test.d.ts +0 -1
- package/dist/app/charts/axes/ContinuousAxisRange.test.js +0 -47
- package/dist/app/charts/axes/ContinuousAxisRange.test.js.map +0 -1
- package/dist/app/charts/axes/OrdinalAxisRange.test.d.ts +0 -1
- package/dist/app/charts/axes/OrdinalAxisRange.test.js +0 -48
- package/dist/app/charts/axes/OrdinalAxisRange.test.js.map +0 -1
- package/dist/app/charts/filters/regexFilter.test.d.ts +0 -1
- package/dist/app/charts/filters/regexFilter.test.js +0 -11
- package/dist/app/charts/filters/regexFilter.test.js.map +0 -1
- package/dist/app/charts/hooks/useDataObservable.d.ts +0 -72
- package/dist/app/charts/hooks/useDataObservable.js +0 -74
- package/dist/app/charts/hooks/useDataObservable.js.map +0 -1
- package/dist/app/charts/hooks/useInitialData.d.ts +0 -36
- package/dist/app/charts/hooks/useInitialData.js +0 -28
- package/dist/app/charts/hooks/useInitialData.js.map +0 -1
- package/dist/app/charts/observables/iterates.test.d.ts +0 -1
- package/dist/app/charts/observables/iterates.test.js +0 -180
- package/dist/app/charts/observables/iterates.test.js.map +0 -1
- package/dist/app/charts/observables/ordinals.test.d.ts +0 -1
- package/dist/app/charts/observables/ordinals.test.js +0 -102
- package/dist/app/charts/observables/ordinals.test.js.map +0 -1
- package/dist/app/charts/series/datumSeries.test.d.ts +0 -1
- package/dist/app/charts/series/datumSeries.test.js +0 -50
- package/dist/app/charts/series/datumSeries.test.js.map +0 -1
- package/dist/app/charts/styling/margins.test.d.ts +0 -1
- package/dist/app/charts/styling/margins.test.js +0 -19
- package/dist/app/charts/styling/margins.test.js.map +0 -1
- package/dist/app/charts/subscriptions/subscription.test.d.ts +0 -1
- package/dist/app/charts/subscriptions/subscription.test.js +0 -395
- package/dist/app/charts/subscriptions/subscription.test.js.map +0 -1
- package/dist/app/charts/subscriptions/subscriptions.d.ts +0 -100
- package/dist/app/charts/subscriptions/subscriptions.js +0 -449
- package/dist/app/charts/subscriptions/subscriptions.js.map +0 -1
- package/src/app/charts/axes/AxesState.test.ts +0 -34
- package/src/app/charts/axes/AxisInterval.test.ts +0 -143
- package/src/app/charts/axes/ContinuousAxisRange.test.ts +0 -55
- package/src/app/charts/axes/OrdinalAxisRange.test.ts +0 -55
- package/src/app/charts/filters/regexFilter.test.ts +0 -13
- package/src/app/charts/hooks/useDataObservable.tsx +0 -170
- package/src/app/charts/hooks/useInitialData.tsx +0 -64
- package/src/app/charts/observables/iterates.test.ts +0 -201
- package/src/app/charts/observables/ordinals.test.ts +0 -135
- package/src/app/charts/series/datumSeries.test.ts +0 -60
- package/src/app/charts/styling/margins.test.ts +0 -22
- package/src/app/charts/subscriptions/subscription.test.ts +0 -499
- package/src/app/charts/subscriptions/subscriptions.ts +0 -635
|
@@ -1,38 +1,47 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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 {
|
|
5
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
continuousAxisZoomHandler,
|
|
13
|
-
ContinuousNumericAxis,
|
|
15
|
+
type BaseAxis,
|
|
16
|
+
AxisType,
|
|
17
|
+
checkAxisType,
|
|
18
|
+
type ContinuousNumericAxis,
|
|
14
19
|
defaultLineStyle,
|
|
15
|
-
|
|
16
|
-
SeriesLineStyle
|
|
20
|
+
type SeriesLineStyle
|
|
17
21
|
} from "../axes/axes";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {noop} from "../utils";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
102
|
-
mainG,
|
|
162
|
+
canvasContext,
|
|
103
163
|
axes,
|
|
104
164
|
color,
|
|
105
165
|
seriesStyles,
|
|
106
166
|
seriesFilter,
|
|
107
167
|
mouse,
|
|
108
|
-
|
|
109
|
-
} = useChart<Datum, SeriesLineStyle,
|
|
168
|
+
backgroundColor,
|
|
169
|
+
} = useChart<Datum, SeriesLineStyle, ScatterTooltipMetadata, ContinuousAxisRange, ContinuousNumericAxis>()
|
|
170
|
+
const {hoveredSeriesName} = useHoveredSeries()
|
|
110
171
|
|
|
111
172
|
const {
|
|
112
173
|
initialData
|
|
113
|
-
} = useInitialData<
|
|
174
|
+
} = useInitialData<Datum>()
|
|
114
175
|
|
|
115
176
|
const {
|
|
116
177
|
xAxesState,
|
|
117
178
|
yAxesState,
|
|
118
|
-
setAxisIntervalFor,
|
|
119
179
|
updateAxisRanges = noop,
|
|
120
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
162
|
-
//
|
|
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<
|
|
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<
|
|
170
|
-
//
|
|
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
|
|
173
|
-
// are updated,
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
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
|
-
(
|
|
291
|
-
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
//
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
596
|
+
// this plot needs a data source (see the `Chart`'s `dataSource` prop)
|
|
504
597
|
useEffect(
|
|
505
598
|
() => {
|
|
506
|
-
if (
|
|
507
|
-
|
|
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
|
-
[
|
|
603
|
+
[dataSource]
|
|
538
604
|
)
|
|
539
605
|
|
|
540
|
-
//
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
[
|
|
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
|
|
563
|
-
*
|
|
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
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
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
|
}
|