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,34 +1,50 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import {useCallback, useEffect, useEffectEvent, useMemo, useRef} from 'react'
|
|
2
|
+
import {useCanvasZoomPan} from "./useCanvasZoomPan";
|
|
3
|
+
import {type NoTooltipMetadata, useChart} from "../hooks/useChart";
|
|
3
4
|
import * as d3 from "d3";
|
|
4
|
-
import {
|
|
5
|
-
import {setClipPathG} from "./plot";
|
|
6
|
-
import {Datum} from "../series/timeSeries";
|
|
5
|
+
import {clipToArea} from "./plot";
|
|
7
6
|
import {
|
|
8
7
|
axesZoomHandler,
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
AxisType,
|
|
9
|
+
type BaseAxis,
|
|
10
|
+
checkAxisType,
|
|
11
|
+
type ContinuousNumericAxis,
|
|
11
12
|
defaultLineStyle,
|
|
12
13
|
panHandler2D,
|
|
13
|
-
SeriesLineStyle
|
|
14
|
+
type SeriesLineStyle
|
|
14
15
|
} from "../axes/axes";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {formatTime, noop, textDimensions} from "../utils";
|
|
18
|
-
import {Dimensions
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {IterateChartData} from "../observables/iterates";
|
|
22
|
-
import {IterateDatum, IterateSeries} from "../series/iterateSeries";
|
|
16
|
+
import type {CanvasContext} from "../d3types";
|
|
17
|
+
import {canvasLocalPoint, seriesAt, type SeriesGeometry} from "./hitTesting";
|
|
18
|
+
import {fontStringFor, formatTime, noop, textDimensions} from "../utils";
|
|
19
|
+
import type {Dimensions} from "../styling/margins";
|
|
20
|
+
import {iteratesViewDriverFor} from "../subscriptions/viewDrivers";
|
|
21
|
+
import {useDataSource, useDataSourceRunning, useInitialData, useStreamingSettings} from "../hooks/useDataSource";
|
|
22
|
+
import type {IterateChartData} from "../observables/iterates";
|
|
23
|
+
import type {IterateDatum, IterateSeries} from "../series/iterateSeries";
|
|
23
24
|
import {usePlotDimensions} from "../hooks/usePlotDimensions";
|
|
24
|
-
import {
|
|
25
|
-
import {TooltipData, useTooltip} from "../hooks/useTooltip";
|
|
26
|
-
import {TimeSeriesChartData} from "../series/timeSeriesChartData";
|
|
25
|
+
import {type TooltipData, useTooltip} from "../hooks/useTooltip";
|
|
27
26
|
import {ContinuousAxisRange} from "../axes/ContinuousAxisRange";
|
|
28
|
-
|
|
27
|
+
import {AxisInterval} from "../axes/AxisInterval";
|
|
28
|
+
import {warnAboutIgnoredAutoScale} from "../axes/autoScale";
|
|
29
|
+
import {FastShiftArray} from "fast-shift-array";
|
|
30
|
+
import {
|
|
31
|
+
DEFAULT_LINE_COLOR_STEPS,
|
|
32
|
+
DEFAULT_LINE_END_COLOR,
|
|
33
|
+
DEFAULT_LINE_START_COLOR,
|
|
34
|
+
pointColorScale,
|
|
35
|
+
strokeColorizedLine
|
|
36
|
+
} from "./colorizedLine";
|
|
37
|
+
|
|
38
|
+
// an iterate as the plot draws it: f[n](x), f[n+1](x), the iterate's time, and its index in its series
|
|
29
39
|
type IteratePoint = { n: number, n_1: number, time: number, index: number }
|
|
30
|
-
type IteratePointSeries =
|
|
40
|
+
type IteratePointSeries = FastShiftArray<IteratePoint>
|
|
31
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Creates an axis range (with its original range the same as its current range) from each axis'
|
|
44
|
+
* current domain
|
|
45
|
+
* @param axes The axes (continuous numeric ones), by their IDs
|
|
46
|
+
* @return The axes' ranges, by their IDs
|
|
47
|
+
*/
|
|
32
48
|
function generateAxisRangeMap(axes: Map<string, BaseAxis>): Map<string, ContinuousAxisRange> {
|
|
33
49
|
return new Map(
|
|
34
50
|
Array.from(axes.entries()).map(([id, axis]) => {
|
|
@@ -38,10 +54,29 @@ function generateAxisRangeMap(axes: Map<string, BaseAxis>): Map<string, Continuo
|
|
|
38
54
|
)
|
|
39
55
|
}
|
|
40
56
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
/**
|
|
58
|
+
* A snapshot of the Poincaré plot's zoom/pan state, in a form that can be held outside the plot
|
|
59
|
+
* (e.g. in an application store) and handed back to a later instance of the plot (see the
|
|
60
|
+
* {@link Props.zoomState} and {@link Props.onZoomStateChange} props) -- so that zooming, navigating
|
|
61
|
+
* away, and navigating back doesn't reset the zoom.
|
|
62
|
+
*/
|
|
63
|
+
export interface PoincarePlotZoomState {
|
|
64
|
+
/**
|
|
65
|
+
* d3-zoom's cumulative scale factor (`transform.k`) at the time of the snapshot. It must be
|
|
66
|
+
* restored onto the new canvas along with the ranges: d3-zoom's `scaleExtent` (see
|
|
67
|
+
* {@link Props.zoomMaxScaleFactor}) limits `k`, so a remounted canvas starting back at `k = 1`
|
|
68
|
+
* would otherwise mis-measure how much further the user may zoom in or out.
|
|
69
|
+
*/
|
|
70
|
+
k: number
|
|
71
|
+
/**
|
|
72
|
+
* Maps each axis ID (x and y) to its current (zoomed, panned) domain
|
|
73
|
+
*/
|
|
74
|
+
ranges: Record<string, [start: number, end: number]>
|
|
75
|
+
}
|
|
44
76
|
|
|
77
|
+
/**
|
|
78
|
+
* The properties of the {@link PoincarePlot}
|
|
79
|
+
*/
|
|
45
80
|
export interface Props {
|
|
46
81
|
/**
|
|
47
82
|
* The line interpolation curve factory. See the d3 documentation for curves at
|
|
@@ -49,14 +84,38 @@ export interface Props {
|
|
|
49
84
|
*/
|
|
50
85
|
interpolation?: d3.CurveFactory
|
|
51
86
|
/**
|
|
52
|
-
* When set to `true` plots the data points as well as the line.
|
|
87
|
+
* When set to `true` (the default) plots the data points as well as the line.
|
|
53
88
|
*/
|
|
54
89
|
showPoints?: boolean
|
|
55
90
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
91
|
+
* When `true`, the lines connecting the points change color along their length, one step per
|
|
92
|
+
* point: from `lineStartColor` at each series' first point to `lineEndColor` after
|
|
93
|
+
* `lineColorSteps` points, then back to `lineStartColor` over the next `lineColorSteps` points,
|
|
94
|
+
* and so on, sweeping the colors of the spectrum in between (the long way around the hue
|
|
95
|
+
* circle -- e.g. from blue, through cyan, green, and yellow, to red). A point keeps its color
|
|
96
|
+
* as old points are dropped (with an {@link IteratesDataSource}, which counts them), so that
|
|
97
|
+
* the path's direction, and its age, can be seen. The points are filled with the color of the
|
|
98
|
+
* line leaving them (except while hovered, when they're highlighted as usual). When `false`
|
|
99
|
+
* (the default), the lines and points are drawn in the series' own color.
|
|
100
|
+
*/
|
|
101
|
+
colorizeLines?: boolean
|
|
102
|
+
/**
|
|
103
|
+
* The number of points (steps) it takes the colorized lines' color to sweep from
|
|
104
|
+
* `lineStartColor` to `lineEndColor` (and as many to sweep back) -- the larger, the slower the
|
|
105
|
+
* color changes. Defaults to {@link DEFAULT_LINE_COLOR_STEPS} (1,000). Only used when
|
|
106
|
+
* `colorizeLines` is `true`.
|
|
107
|
+
*/
|
|
108
|
+
lineColorSteps?: number
|
|
109
|
+
/**
|
|
110
|
+
* The color the colorized lines' sweep starts at (each series' first point). Defaults to a blue
|
|
111
|
+
* ({@link DEFAULT_LINE_START_COLOR}). Only used when `colorizeLines` is `true`.
|
|
112
|
+
*/
|
|
113
|
+
lineStartColor?: string
|
|
114
|
+
/**
|
|
115
|
+
* The color the colorized lines' sweep ends at (after `lineColorSteps` points). Defaults to a red
|
|
116
|
+
* ({@link DEFAULT_LINE_END_COLOR}). Only used when `colorizeLines` is `true`.
|
|
58
117
|
*/
|
|
59
|
-
|
|
118
|
+
lineEndColor?: string
|
|
60
119
|
/**
|
|
61
120
|
* Enables panning (default is false)
|
|
62
121
|
*/
|
|
@@ -83,6 +142,25 @@ export interface Props {
|
|
|
83
142
|
* the larger this factor, the more the user can "zoom out". Default value is 1.0.
|
|
84
143
|
*/
|
|
85
144
|
zoomMaxScaleFactor?: number
|
|
145
|
+
/**
|
|
146
|
+
* An (optional) zoom/pan state to restore when this plot mounts -- commonly the last value
|
|
147
|
+
* reported through {@link onZoomStateChange} and held by the application (e.g. in a store) so
|
|
148
|
+
* that the zoom survives the plot being unmounted and remounted (e.g. navigating away and
|
|
149
|
+
* back). Only read once, when the plot's axes are first available.
|
|
150
|
+
*/
|
|
151
|
+
zoomState?: PoincarePlotZoomState
|
|
152
|
+
/**
|
|
153
|
+
* (Optional) callback that is handed the plot's zoom/pan state after each zoom event, at the
|
|
154
|
+
* end of each pan, and when the axes' bounds are changed from outside (which resets the zoom)
|
|
155
|
+
*/
|
|
156
|
+
onZoomStateChange?: (zoomState: PoincarePlotZoomState) => void
|
|
157
|
+
/**
|
|
158
|
+
* Called whenever this plot (re)creates its zoom behavior, handing the caller a `resetZoom`
|
|
159
|
+
* function that clears d3-zoom's own accumulated scale back to identity and restores every axis
|
|
160
|
+
* to its original (un-zoomed, un-panned) domain -- e.g. for a "Clear" action. Only meaningful
|
|
161
|
+
* (called) while `zoomEnabled` is true.
|
|
162
|
+
*/
|
|
163
|
+
onZoomReset?: (resetZoom: () => void) => void
|
|
86
164
|
/**
|
|
87
165
|
* When set, uses a cadence with the specified refresh period (in milliseconds). For plots
|
|
88
166
|
* where the updates are slow (> 100 ms) using a cadence of 10 to 25 ms smooths out the
|
|
@@ -90,25 +168,39 @@ export interface Props {
|
|
|
90
168
|
* then setting the cadence period too small will result in poor update performance. Generally
|
|
91
169
|
* at high update speeds, the cadence is unnecessary. Finally, using cadence, sets the max time
|
|
92
170
|
* to the current time.
|
|
171
|
+
*
|
|
172
|
+
* Note: the Poincaré plot accepts this, but doesn't (yet) use it -- its axes don't follow time.
|
|
93
173
|
*/
|
|
94
174
|
withCadenceOf?: number
|
|
175
|
+
/**
|
|
176
|
+
* When true, hovering over a point also highlights the (single, shared) x- and y-axes it is
|
|
177
|
+
* plotted against, in the series' own highlight color/width. Defaults to `false`.
|
|
178
|
+
*/
|
|
179
|
+
highlightAxesOnMouseOver?: boolean
|
|
95
180
|
}
|
|
96
181
|
|
|
97
182
|
/**
|
|
98
|
-
* Renders a streaming Poincare (iterates) plot for the series
|
|
99
|
-
*
|
|
183
|
+
* Renders a streaming Poincare (iterates) plot for the series held by the {@link StreamingDataSource}
|
|
184
|
+
* handed to the {@link Chart} (its `dataSource` prop). This component uses the {@link useChart}
|
|
100
185
|
* hook, and therefore must be a child of the {@link Chart} in order to be plugged in to the
|
|
101
186
|
* chart ecosystem (axes, tracker, tooltip).
|
|
102
187
|
*
|
|
103
|
-
*
|
|
188
|
+
* Internally, this no longer creates/updates SVG `<path>`/`<circle>`/`<text>` elements. Instead, it
|
|
189
|
+
* registers a single draw function with the chart's {@link CanvasContext} that redraws every
|
|
190
|
+
* series' line and points from scratch each time the canvas repaints. The old version's
|
|
191
|
+
* hover-a-point-and-annotate-its-neighbors behavior used direct `d3.select('#specific-id')` lookups
|
|
192
|
+
* into already-rendered SVG elements from a separate pass after the main render; since canvas has
|
|
193
|
+
* no persistent elements to reach back into, this is restructured so the single draw pass checks
|
|
194
|
+
* "is this point hovered, or a neighbor of the hovered point" and renders the highlight/label
|
|
195
|
+
* inline, driven by a `hoveredPointRef` updated on `mousemove`.
|
|
196
|
+
*
|
|
197
|
+
* @param props The properties associated with the Poincaré plot
|
|
198
|
+
* @return `null` (the plot draws onto the chart's canvas)
|
|
104
199
|
* @example
|
|
105
200
|
* ```typescript
|
|
106
|
-
* <
|
|
201
|
+
* <PoincarePlot
|
|
107
202
|
* interpolation={interpolation}
|
|
108
|
-
*
|
|
109
|
-
* ['test2', assignAxes("x-axis-2", "y-axis-2")],
|
|
110
|
-
* ])}
|
|
111
|
-
* dropDataAfter={10000}
|
|
203
|
+
* showPoints={true}
|
|
112
204
|
* panEnabled={true}
|
|
113
205
|
* zoomEnabled={true}
|
|
114
206
|
* zoomKeyModifiersRequired={true}
|
|
@@ -118,8 +210,7 @@ export interface Props {
|
|
|
118
210
|
export function PoincarePlot(props: Props): null {
|
|
119
211
|
const {
|
|
120
212
|
chartId,
|
|
121
|
-
|
|
122
|
-
mainG,
|
|
213
|
+
canvasContext,
|
|
123
214
|
axes,
|
|
124
215
|
backgroundColor,
|
|
125
216
|
seriesStyles,
|
|
@@ -138,6 +229,13 @@ export function PoincarePlot(props: Props): null {
|
|
|
138
229
|
removeAxesRangesUpdateHandler,
|
|
139
230
|
} = axes
|
|
140
231
|
|
|
232
|
+
// this plot doesn't auto-scale its y-axes, so warn about any that ask to be (see
|
|
233
|
+
// `ContinuousAxis`'s `autoScale` prop)
|
|
234
|
+
useEffect(
|
|
235
|
+
() => warnAboutIgnoredAutoScale("PoincarePlot", yAxesState.axes),
|
|
236
|
+
[yAxesState]
|
|
237
|
+
)
|
|
238
|
+
|
|
141
239
|
const {
|
|
142
240
|
mouseOverHandlerFor,
|
|
143
241
|
mouseLeaveHandlerFor
|
|
@@ -149,46 +247,53 @@ export function PoincarePlot(props: Props): null {
|
|
|
149
247
|
} = usePlotDimensions()
|
|
150
248
|
|
|
151
249
|
const {
|
|
152
|
-
seriesObservable,
|
|
153
250
|
windowingTime = 100,
|
|
154
|
-
shouldSubscribe,
|
|
155
|
-
|
|
156
|
-
onSubscribe = noop,
|
|
157
|
-
onUpdateData,
|
|
158
251
|
onUpdateChartTime = noop,
|
|
159
|
-
} =
|
|
252
|
+
} = useStreamingSettings()
|
|
160
253
|
|
|
161
|
-
|
|
254
|
+
// the application-owned source of the data, and whether it's running (streaming)
|
|
255
|
+
const dataSource = useDataSource<IterateChartData, IterateDatum, IterateSeries>()
|
|
256
|
+
const running = useDataSourceRunning(dataSource)
|
|
257
|
+
|
|
258
|
+
const {initialData} = useInitialData<IterateDatum>()
|
|
162
259
|
|
|
163
260
|
const {visibilityState: tooltipVisible} = useTooltip()
|
|
164
261
|
|
|
165
262
|
const {
|
|
166
263
|
interpolation,
|
|
167
264
|
showPoints = true,
|
|
168
|
-
dropDataAfter = 1000,
|
|
169
265
|
panEnabled = false,
|
|
170
266
|
zoomEnabled = false,
|
|
171
267
|
zoomKeyModifiersRequired = true,
|
|
172
268
|
zoomMinScaleFactor = 0,
|
|
173
269
|
zoomMaxScaleFactor = 1,
|
|
270
|
+
highlightAxesOnMouseOver = false,
|
|
271
|
+
colorizeLines = false,
|
|
272
|
+
lineStartColor = DEFAULT_LINE_START_COLOR,
|
|
273
|
+
lineEndColor = DEFAULT_LINE_END_COLOR,
|
|
274
|
+
lineColorSteps = DEFAULT_LINE_COLOR_STEPS,
|
|
275
|
+
zoomState = undefined,
|
|
276
|
+
onZoomStateChange = noop,
|
|
277
|
+
onZoomReset = noop,
|
|
174
278
|
} = props
|
|
175
279
|
|
|
176
280
|
// why do "dataRef" and "seriesRef" both hold on to the same underlying data? for performance.
|
|
177
281
|
//
|
|
178
282
|
// the "dataRef" and "seriesRef" both point to the same underlying data, a collection
|
|
179
|
-
// of series
|
|
180
|
-
// are the
|
|
283
|
+
// of series (held, and updated as new data is streamed in, by the data source). The series in
|
|
284
|
+
// "dataRef" are read by the canvas draw function, and the "seriesRef" map is for looking a
|
|
285
|
+
// series up by its name.
|
|
181
286
|
//
|
|
182
287
|
// the "dataRef" object holds on to a copy of the initial data (which is an array of
|
|
183
|
-
//
|
|
288
|
+
// iterate series, e.i. an array of BaseSeries<IterateDatum> objects). The slice just creates a copy of
|
|
184
289
|
// the array, but the references to the BaseSeries objects are the same and still point to the same
|
|
185
290
|
// data as the "initialData" array.
|
|
186
291
|
//
|
|
187
|
-
// the "seriesRef" object is a reference to a map (series_name -> BaseSeries<
|
|
188
|
-
//
|
|
292
|
+
// the "seriesRef" object is a reference to a map (series_name -> BaseSeries<IterateDatum>), used
|
|
293
|
+
// to find the series under the mouse (for the tooltip).
|
|
189
294
|
//
|
|
190
|
-
// the series in the "dataRef" object are the ones
|
|
191
|
-
//
|
|
295
|
+
// the series in the "dataRef" object are the ones read by the next redraw, so as these are
|
|
296
|
+
// updated, the next canvas repaint picks up the new data.
|
|
192
297
|
const dataRef = useRef<Array<IterateSeries>>(initialData.slice() as Array<IterateSeries>)
|
|
193
298
|
const seriesRef = useRef<Map<string, IterateSeries>>(new Map(initialData.map(series => [series.name, series as IterateSeries])))
|
|
194
299
|
// map(axis_id -> current_time) -- maps the axis ID to the current time for that axis
|
|
@@ -196,26 +301,323 @@ export function PoincarePlot(props: Props): null {
|
|
|
196
301
|
const xAxisRangesRef = useRef<Map<string, ContinuousAxisRange>>(new Map());
|
|
197
302
|
const yAxisRangesRef = useRef<Map<string, ContinuousAxisRange>>(new Map());
|
|
198
303
|
|
|
199
|
-
|
|
200
|
-
const
|
|
304
|
+
// tooltips are only shown while the data source is paused (and not while panning)
|
|
305
|
+
const allowTooltip = useRef<boolean>(!(dataSource?.running ?? false))
|
|
201
306
|
|
|
202
|
-
const allowTooltip = useRef<boolean>(isSubscriptionClosed())
|
|
203
307
|
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
308
|
+
// the last-drawn geometry for each series' points, in canvas coordinates, used for
|
|
309
|
+
// hit-testing mouse hover on `mousemove` (see the effect below that wires up the listener).
|
|
310
|
+
// Also keeps the corresponding `IteratePointSeries` so the hovered/neighbor points' data can
|
|
311
|
+
// be looked up by index.
|
|
312
|
+
const geometryRef = useRef<Map<string, SeriesGeometry>>(new Map())
|
|
313
|
+
const plotDataRef = useRef<Map<string, IteratePointSeries>>(new Map())
|
|
314
|
+
// which point (series + index) is currently hovered, so the draw pass can highlight it and
|
|
315
|
+
// its neighbors, and so we know when to fire a "leave"
|
|
316
|
+
const hoveredPointRef = useRef<{seriesName: string, index: number} | undefined>(undefined)
|
|
208
317
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
318
|
+
const updatePlot = useCallback(
|
|
319
|
+
/**
|
|
320
|
+
* (Re-)registers this plot's draw function with the canvas context and requests a redraw.
|
|
321
|
+
*
|
|
322
|
+
* Pan/zoom behavior setup lives in a separate effect (see below), not here -- this
|
|
323
|
+
* function runs on every data tick, and recreating/reattaching a `d3.drag()`/`d3.zoom()`
|
|
324
|
+
* behavior that often is pure overhead unrelated to drawing the new data.
|
|
325
|
+
* @param cc The canvas context to register the draw function with
|
|
326
|
+
*/
|
|
327
|
+
(cc: CanvasContext) => {
|
|
328
|
+
onUpdateChartTime(currentTimeRef.current)
|
|
329
|
+
|
|
330
|
+
// the colorized lines' color at a point, by the point's overall sequence number in its
|
|
331
|
+
// series (see `colorizeLines`)
|
|
332
|
+
const lineColorOf = pointColorScale(lineStartColor, lineEndColor, lineColorSteps)
|
|
333
|
+
|
|
334
|
+
const draw = (context: CanvasContext) => {
|
|
335
|
+
const {context2D} = context
|
|
336
|
+
|
|
337
|
+
// create a map associating series-names to their time-series.
|
|
338
|
+
const boundedSeries = new Map<string, IteratePointSeries>(dataRef.current.map(series => {
|
|
339
|
+
return [
|
|
340
|
+
series.name,
|
|
341
|
+
series.data
|
|
342
|
+
.filter(datum => !isNaN(datum.iterateN))
|
|
343
|
+
.map((datum, index) => ({
|
|
344
|
+
n: datum.iterateN,
|
|
345
|
+
n_1: datum.iterateN_1,
|
|
346
|
+
time: datum.time,
|
|
347
|
+
index: index
|
|
348
|
+
})
|
|
349
|
+
) as IteratePointSeries
|
|
350
|
+
]
|
|
351
|
+
}))
|
|
352
|
+
|
|
353
|
+
context2D.save()
|
|
354
|
+
clipToArea(context, plotDimensions, {x: margin.left, y: margin.top})
|
|
355
|
+
context2D.translate(margin.left, margin.top)
|
|
356
|
+
|
|
357
|
+
// ---
|
|
358
|
+
// todo only want to do this once, on the first plot, and then leave it,
|
|
359
|
+
// unless the axes are updated, also needs to be removed/added when the
|
|
360
|
+
// plot size changes
|
|
361
|
+
// (the same, checked axes as the series' below: the default x- and y-axes)
|
|
362
|
+
const [xAxis, yAxis] = axesFor(
|
|
363
|
+
axisId => xAxesState.axisFor(axisId).getOrUndefined(),
|
|
364
|
+
axisId => yAxesState.axisFor(axisId).getOrUndefined(),
|
|
365
|
+
)
|
|
366
|
+
if (xAxis === undefined || yAxis === undefined) throw new Error('No default axis found')
|
|
367
|
+
// ---
|
|
368
|
+
|
|
369
|
+
const [xStart, xEnd] = xAxesState.axisDefaultId()
|
|
370
|
+
.map(id => xAxisRangesRef.current.get(id)?.original.asTuple() || [0, 0])
|
|
371
|
+
.getOrElse([0, 0])
|
|
372
|
+
const [yStart, yEnd] = yAxesState.axisDefaultId()
|
|
373
|
+
.map(id => yAxisRangesRef.current.get(id)?.original.asTuple() || [0, 0])
|
|
374
|
+
.getOrElse([0, 0])
|
|
375
|
+
|
|
376
|
+
// the fn = fn+1 diagonal reference line (no hover behavior, matching the old
|
|
377
|
+
// version's explicitly no-op mouseenter/mouseleave handlers)
|
|
378
|
+
context2D.strokeStyle = "grey"
|
|
379
|
+
context2D.lineWidth = 1
|
|
380
|
+
context2D.beginPath()
|
|
381
|
+
context2D.moveTo(xAxis.scale(xStart), yAxis.scale(yStart))
|
|
382
|
+
context2D.lineTo(xAxis.scale(xEnd), yAxis.scale(yEnd))
|
|
383
|
+
context2D.stroke()
|
|
384
|
+
|
|
385
|
+
const newGeometry = new Map<string, SeriesGeometry>()
|
|
386
|
+
const newPlotDataBySeries = new Map<string, IteratePointSeries>()
|
|
387
|
+
const hovered = hoveredPointRef.current
|
|
388
|
+
|
|
389
|
+
boundedSeries.forEach((data, name) => {
|
|
390
|
+
// grab the x and y axes assigned to the series, and if either or both
|
|
391
|
+
// axes aren't found, then give up and return
|
|
392
|
+
const [xAxisLinear, yAxisLinear] = axesFor(
|
|
393
|
+
axisId => xAxesState.axisFor(axisId).getOrUndefined(),
|
|
394
|
+
axisId => yAxesState.axisFor(axisId).getOrUndefined(),
|
|
395
|
+
)
|
|
396
|
+
if (xAxisLinear === undefined || yAxisLinear === undefined) return
|
|
397
|
+
|
|
398
|
+
// grab the style for the series
|
|
399
|
+
const seriesLineStyle: SeriesLineStyle = seriesStyles.get(name) || {
|
|
400
|
+
...defaultLineStyle(),
|
|
401
|
+
highlightColor: defaultLineStyle().color
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// only show the data for which the filter matches
|
|
405
|
+
const plotData = (name.match(seriesFilter)) ? data : FastShiftArray.empty<IteratePoint>()
|
|
406
|
+
newPlotDataBySeries.set(name, plotData)
|
|
407
|
+
|
|
408
|
+
// create the time-series path (when no interpolation is given, don't draw a
|
|
409
|
+
// connecting line at all -- matches the old version's `mainGElem.selectAll(...).remove()`)
|
|
410
|
+
// when colorizing the lines, a point's overall sequence number in its series is
|
|
411
|
+
// the number of points dropped from the series so far (when the data source
|
|
412
|
+
// counts them) plus its index -- the line leaving a point, and the point itself,
|
|
413
|
+
// are colored by it
|
|
414
|
+
const dropped = colorizeLines ? droppedCountFor(dataSource, name) : 0
|
|
415
|
+
|
|
416
|
+
if (interpolation !== undefined) {
|
|
417
|
+
const pathGenerator = d3.line<IteratePoint>()
|
|
418
|
+
.x(d => xAxis.scale(d.n || 0))
|
|
419
|
+
.y(d => yAxis.scale(d.n_1 || 0))
|
|
420
|
+
.curve(interpolation)
|
|
421
|
+
|
|
422
|
+
context2D.lineWidth = seriesLineStyle.lineWidth
|
|
423
|
+
if (colorizeLines) {
|
|
424
|
+
// the line's color changes along it, one step per point
|
|
425
|
+
const linePoints = Array.from(plotData)
|
|
426
|
+
strokeColorizedLine(
|
|
427
|
+
context2D, linePoints,
|
|
428
|
+
d => xAxis.scale(d.n || 0), d => yAxis.scale(d.n_1 || 0),
|
|
429
|
+
interpolation, index => lineColorOf(dropped + linePoints[index].index)
|
|
430
|
+
)
|
|
431
|
+
} else {
|
|
432
|
+
context2D.strokeStyle = seriesLineStyle.color
|
|
433
|
+
context2D.stroke(new Path2D(pathGenerator(Array.from(plotData)) ?? ""))
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// when specified, show a circle for the actual data point
|
|
438
|
+
const points: Array<[number, number]> = []
|
|
439
|
+
if (showPoints) {
|
|
440
|
+
const isThisSeriesHovered = hovered?.seriesName === name
|
|
441
|
+
|
|
442
|
+
plotData.forEach((d: IteratePoint) => {
|
|
443
|
+
const x = xAxisLinear.scale(d.n) || 0
|
|
444
|
+
const y = yAxisLinear.scale(d.n_1) || 0
|
|
445
|
+
points.push([x + margin.left, y + margin.top])
|
|
446
|
+
|
|
447
|
+
const isHoveredPoint = isThisSeriesHovered && hovered!.index === d.index
|
|
448
|
+
const isNeighborOfHovered = isThisSeriesHovered &&
|
|
449
|
+
(hovered!.index === d.index - 1 || hovered!.index === d.index + 1)
|
|
450
|
+
|
|
451
|
+
if (isHoveredPoint) {
|
|
452
|
+
// the hovered point itself: enlarged, highlight-colored, no label
|
|
453
|
+
// (matches the old version, which only labeled the *neighbors*)
|
|
454
|
+
context2D.fillStyle = seriesLineStyle.highlightColor
|
|
455
|
+
context2D.beginPath()
|
|
456
|
+
context2D.arc(x, y, 5, 0, 2 * Math.PI)
|
|
457
|
+
context2D.fill()
|
|
458
|
+
} else if (isNeighborOfHovered) {
|
|
459
|
+
// a neighbor of the hovered point: enlarged, brighter fill, stroked,
|
|
460
|
+
// with a floating "n = i; t = X ms" label above it
|
|
461
|
+
context2D.fillStyle = d3.rgb(seriesLineStyle.highlightColor).brighter(0.7).toString()
|
|
462
|
+
context2D.strokeStyle = seriesLineStyle.color
|
|
463
|
+
context2D.lineWidth = seriesLineStyle.lineWidth
|
|
464
|
+
context2D.beginPath()
|
|
465
|
+
context2D.arc(x, y, 5, 0, 2 * Math.PI)
|
|
466
|
+
context2D.fill()
|
|
467
|
+
context2D.stroke()
|
|
468
|
+
|
|
469
|
+
const label = `n = ${d.index}; t = ${formatTime(d.time)} ms`
|
|
470
|
+
context2D.font = fontStringFor(11, 'sans-serif', 700)
|
|
471
|
+
const {width, height} = textDimensions(context2D, label)
|
|
472
|
+
const padding = 4
|
|
473
|
+
const circleRadius = 5
|
|
474
|
+
const circleStroke = seriesLineStyle.lineWidth
|
|
475
|
+
|
|
476
|
+
context2D.fillStyle = backgroundColor
|
|
477
|
+
context2D.fillRect(
|
|
478
|
+
x - padding / 2 - 8,
|
|
479
|
+
y - padding / 2 - circleRadius - circleStroke - height,
|
|
480
|
+
width + padding,
|
|
481
|
+
height + padding / 2
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
context2D.fillStyle = seriesLineStyle.highlightColor
|
|
485
|
+
context2D.textAlign = 'left'
|
|
486
|
+
context2D.textBaseline = 'alphabetic'
|
|
487
|
+
context2D.fillText(label, x - 8, y - circleRadius - circleStroke - padding)
|
|
488
|
+
} else {
|
|
489
|
+
// normal, unhovered point (in the color of the line leaving it, when
|
|
490
|
+
// the lines are colorized)
|
|
491
|
+
context2D.fillStyle = colorizeLines ?
|
|
492
|
+
lineColorOf(dropped + d.index) :
|
|
493
|
+
seriesLineStyle.color
|
|
494
|
+
context2D.beginPath()
|
|
495
|
+
context2D.arc(x, y, 2, 0, 2 * Math.PI)
|
|
496
|
+
context2D.fill()
|
|
497
|
+
}
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
newGeometry.set(`${name}::points`, {points, hitRadius: 6})
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
geometryRef.current = newGeometry
|
|
504
|
+
plotDataRef.current = newPlotDataBySeries
|
|
505
|
+
|
|
506
|
+
context2D.restore()
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
cc.register(`poincare-plot-${chartId}`, draw, 10)
|
|
510
|
+
cc.requestRedraw()
|
|
511
|
+
},
|
|
512
|
+
[
|
|
513
|
+
chartId, onUpdateChartTime, plotDimensions, xAxesState, yAxesState, seriesStyles, seriesFilter,
|
|
514
|
+
showPoints, interpolation, backgroundColor, colorizeLines, lineStartColor, lineEndColor,
|
|
515
|
+
lineColorSteps, dataSource, margin.left, margin.top
|
|
516
|
+
]
|
|
213
517
|
)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
518
|
+
|
|
519
|
+
// need to keep the function references for use by the subscription, which forms a closure
|
|
520
|
+
// on them. without the references, the closures become stale, and resizing during streaming
|
|
521
|
+
// doesn't work properly
|
|
522
|
+
const updatePlotRef = useRef<(cc: CanvasContext) => void>(updatePlot)
|
|
523
|
+
useEffect(
|
|
524
|
+
() => {
|
|
525
|
+
updatePlotRef.current = updatePlot
|
|
526
|
+
},
|
|
527
|
+
[updatePlot]
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
// hands the current zoom/pan state (d3-zoom's cumulative `k`, and every axis' current domain) to
|
|
531
|
+
// the `onZoomStateChange` callback
|
|
532
|
+
const onZoomStateChangeRef = useRef(onZoomStateChange)
|
|
533
|
+
useEffect(
|
|
534
|
+
() => {
|
|
535
|
+
onZoomStateChangeRef.current = onZoomStateChange
|
|
536
|
+
},
|
|
537
|
+
[onZoomStateChange]
|
|
538
|
+
)
|
|
539
|
+
const reportZoomState = useCallback(
|
|
540
|
+
(): void => {
|
|
541
|
+
const ranges: Record<string, [number, number]> = {}
|
|
542
|
+
xAxisRangesRef.current.forEach((range, axisId) => ranges[axisId] = range.current.asTuple())
|
|
543
|
+
yAxisRangesRef.current.forEach((range, axisId) => ranges[axisId] = range.current.asTuple())
|
|
544
|
+
const k = canvasContext !== null ? d3.zoomTransform(canvasContext.canvas).k : 1
|
|
545
|
+
onZoomStateChangeRef.current({k, ranges})
|
|
546
|
+
},
|
|
547
|
+
[canvasContext]
|
|
217
548
|
)
|
|
218
549
|
|
|
550
|
+
// panning (dragging) and zooming (the mouse wheel) -- see `useCanvasZoomPan`. Fully zooming out
|
|
551
|
+
// (back to a scale of 1) snaps the axes back to their original ranges.
|
|
552
|
+
const {resetZoomTransform, setZoomScale} = useCanvasZoomPan(canvasContext, {
|
|
553
|
+
panEnabled,
|
|
554
|
+
zoomEnabled,
|
|
555
|
+
keyModifiersRequired: zoomKeyModifiersRequired,
|
|
556
|
+
scaleExtent: [zoomMinScaleFactor, zoomMaxScaleFactor],
|
|
557
|
+
translateExtent: [[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]],
|
|
558
|
+
// (tooltips are suppressed while panning and zooming, so that one isn't left behind)
|
|
559
|
+
onPanStart: () => {
|
|
560
|
+
allowTooltip.current = false
|
|
561
|
+
},
|
|
562
|
+
onPan: (dx, dy) => {
|
|
563
|
+
if (canvasContext === null) return
|
|
564
|
+
onPan(dx, dy, plotDimensions, dataRef.current.map(series => series.name), xAxisRangesRef.current, yAxisRangesRef.current)
|
|
565
|
+
updatePlotRef.current(canvasContext)
|
|
566
|
+
},
|
|
567
|
+
onPanEnd: () => {
|
|
568
|
+
allowTooltip.current = !(dataSource?.running ?? false)
|
|
569
|
+
reportZoomState()
|
|
570
|
+
},
|
|
571
|
+
onZoom: ({factor, k, x, y}) => {
|
|
572
|
+
if (canvasContext === null) return
|
|
573
|
+
allowTooltip.current = false
|
|
574
|
+
if (k === 1) {
|
|
575
|
+
snapToOriginalRanges()
|
|
576
|
+
} else {
|
|
577
|
+
onZoom(factor, x - margin.left, y - margin.top, plotDimensions, xAxisRangesRef.current, yAxisRangesRef.current)
|
|
578
|
+
}
|
|
579
|
+
updatePlotRef.current(canvasContext)
|
|
580
|
+
reportZoomState()
|
|
581
|
+
allowTooltip.current = true
|
|
582
|
+
},
|
|
583
|
+
onZoomReset,
|
|
584
|
+
resetView: () => {
|
|
585
|
+
snapToOriginalRanges()
|
|
586
|
+
if (canvasContext !== null) updatePlotRef.current(canvasContext)
|
|
587
|
+
reportZoomState()
|
|
588
|
+
},
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
// the zoom state to restore, captured once at mount (see the `zoomState` prop); cleared once
|
|
592
|
+
// it has been applied (see `restoreZoomState` below), so it's only ever applied once
|
|
593
|
+
const pendingZoomStateRef = useRef<PoincarePlotZoomState | undefined>(zoomState)
|
|
594
|
+
|
|
595
|
+
// applies the pending zoom state (if any) to the axes' ranges and to d3-zoom's transform on the
|
|
596
|
+
// canvas (a remounted plot has a brand-new canvas, whose transform starts at identity). Each
|
|
597
|
+
// range keeps its `.original` -- the axis' real, un-zoomed domain -- since the zoom handler's
|
|
598
|
+
// constraint is measured against it. Waits until the axes' ranges and the canvas exist.
|
|
599
|
+
const restoreZoomState = useEffectEvent((): void => {
|
|
600
|
+
const pending = pendingZoomStateRef.current
|
|
601
|
+
if (pending === undefined || canvasContext === null) return
|
|
602
|
+
if (xAxisRangesRef.current.size === 0 || yAxisRangesRef.current.size === 0) return
|
|
603
|
+
|
|
604
|
+
Object.entries(pending.ranges).forEach(([axisId, [start, end]]) => {
|
|
605
|
+
const interval = AxisInterval.from(start, end)
|
|
606
|
+
const ranges = xAxisRangesRef.current.has(axisId) ? xAxisRangesRef.current : yAxisRangesRef.current
|
|
607
|
+
const range = ranges.get(axisId)
|
|
608
|
+
const axis = xAxesState.axisFor(axisId).getOrUndefined() ?? yAxesState.axisFor(axisId).getOrUndefined()
|
|
609
|
+
if (range === undefined || axis === undefined) return
|
|
610
|
+
ranges.set(axisId, range.update(start, end))
|
|
611
|
+
setAxisIntervalFor(axisId, interval)
|
|
612
|
+
axis.update(interval, plotDimensions, margin)
|
|
613
|
+
})
|
|
614
|
+
// `__zoom` is where d3-zoom keeps an element's transform; setting it directly (rather than
|
|
615
|
+
// through `zoom.transform`) avoids dispatching a synthetic "zoom" event
|
|
616
|
+
setZoomScale(pending.k)
|
|
617
|
+
pendingZoomStateRef.current = undefined
|
|
618
|
+
updatePlotRef.current(canvasContext)
|
|
619
|
+
})
|
|
620
|
+
|
|
219
621
|
// when the axes are available, then set the reference, but only once
|
|
220
622
|
useEffect(() => {
|
|
221
623
|
if (xAxesState.axes.size > 0 && xAxisRangesRef.current.size === 0) {
|
|
@@ -224,21 +626,35 @@ export function PoincarePlot(props: Props): null {
|
|
|
224
626
|
if (yAxesState.axes.size > 0 && yAxisRangesRef.current.size === 0) {
|
|
225
627
|
yAxisRangesRef.current = generateAxisRangeMap(yAxesState.axes)
|
|
226
628
|
}
|
|
227
|
-
|
|
629
|
+
// restore a zoom state handed in from a previous instance (only does anything the first
|
|
630
|
+
// time the axes and canvas are all available -- see `restoreZoomState`)
|
|
631
|
+
restoreZoomState()
|
|
632
|
+
}, [xAxesState, yAxesState, canvasContext]);
|
|
633
|
+
|
|
634
|
+
// calculates the distinct axis IDs that cover all the series in the plot
|
|
635
|
+
const xAxesForSeries = useMemo(
|
|
636
|
+
(): Array<string> => xAxesState.axisIds(),
|
|
637
|
+
[xAxesState]
|
|
638
|
+
)
|
|
639
|
+
const yAxesForSeries = useMemo(
|
|
640
|
+
(): Array<string> => yAxesState.axisIds(),
|
|
641
|
+
[yAxesState]
|
|
642
|
+
)
|
|
643
|
+
|
|
228
644
|
|
|
229
645
|
// update the plot with the new axes bounds
|
|
230
646
|
const updateRangesAndPlot = useCallback(
|
|
231
647
|
(): void => {
|
|
232
|
-
if (
|
|
233
|
-
updatePlotRef.current(
|
|
648
|
+
if (canvasContext !== null) {
|
|
649
|
+
updatePlotRef.current(canvasContext)
|
|
234
650
|
}
|
|
235
651
|
},
|
|
236
|
-
[
|
|
652
|
+
[canvasContext]
|
|
237
653
|
)
|
|
238
654
|
|
|
239
655
|
// todo find better way
|
|
240
656
|
// when the initial data changes, then reset the plot. note that the initial data doesn't change
|
|
241
|
-
// during the normal course of updates from the
|
|
657
|
+
// during the normal course of updates from the data source, only when the data source is replaced.
|
|
242
658
|
useEffect(
|
|
243
659
|
() => {
|
|
244
660
|
dataRef.current = initialData.slice()
|
|
@@ -251,25 +667,34 @@ export function PoincarePlot(props: Props): null {
|
|
|
251
667
|
)
|
|
252
668
|
|
|
253
669
|
/**
|
|
254
|
-
* When the axes bounds have changed
|
|
255
|
-
* the new axis ranges are used
|
|
670
|
+
* When the axes bounds have changed (from outside, e.g. a different iterate function), resets
|
|
671
|
+
* the range references so that the new axis ranges are used -- as the axes' new original
|
|
672
|
+
* (un-zoomed) domains -- resets the zoom, and reports the (reset) zoom state
|
|
256
673
|
* @param updates The updates to the axes
|
|
257
674
|
*/
|
|
258
675
|
const updatedBoundsHandler = useCallback(
|
|
259
676
|
(updates: Map<string, ContinuousAxisRange>): void => {
|
|
677
|
+
// the new bounds become the axes' new un-zoomed (original) domains -- the zoom is reset
|
|
678
|
+
// to identity below, so they're the baseline the zoom constraint and the fully-zoomed-out
|
|
679
|
+
// snap (see `snapToOriginalRanges`) must measure against. The update itself can't be
|
|
680
|
+
// used as-is: `ContinuousAxis` carries the *previous* `.original` forward on a domain
|
|
681
|
+
// change, which would leave the zoom clamped to (and snapping back to) the old domain,
|
|
682
|
+
// e.g. [0, 1] after switching to an iterate function whose range is [-1, 1].
|
|
260
683
|
updates.forEach((update, axisId) => {
|
|
684
|
+
const [start, end] = update.current.asTuple()
|
|
261
685
|
if (xAxisRangesRef.current.has(axisId)) {
|
|
262
|
-
xAxisRangesRef.current.set(axisId,
|
|
686
|
+
xAxisRangesRef.current.set(axisId, ContinuousAxisRange.from(start, end))
|
|
263
687
|
}
|
|
264
688
|
if (yAxisRangesRef.current.has(axisId)) {
|
|
265
|
-
yAxisRangesRef.current.set(axisId,
|
|
689
|
+
yAxisRangesRef.current.set(axisId, ContinuousAxisRange.from(start, end))
|
|
266
690
|
}
|
|
267
691
|
})
|
|
268
|
-
if (zoomEnabled
|
|
269
|
-
|
|
270
|
-
|
|
692
|
+
if (zoomEnabled) resetZoomTransform()
|
|
693
|
+
// the axes' bounds were changed from outside (e.g. a different iterate function), which
|
|
694
|
+
// resets the zoom, so a previously reported zoom state no longer applies
|
|
695
|
+
reportZoomState()
|
|
271
696
|
},
|
|
272
|
-
[zoomEnabled]
|
|
697
|
+
[zoomEnabled, resetZoomTransform, reportZoomState]
|
|
273
698
|
)
|
|
274
699
|
|
|
275
700
|
// strange construct so that we only add the update handler when the chart ID
|
|
@@ -294,11 +719,13 @@ export function PoincarePlot(props: Props): null {
|
|
|
294
719
|
);
|
|
295
720
|
|
|
296
721
|
/**
|
|
297
|
-
* Adjusts the
|
|
298
|
-
* @param x The amount that the plot is dragged
|
|
722
|
+
* Adjusts the x- and y-axes' ranges when the plot is dragged (the caller redraws the plot)
|
|
723
|
+
* @param x The amount that the plot is dragged in x
|
|
724
|
+
* @param y The amount that the plot is dragged in y
|
|
299
725
|
* @param plotDimensions The dimensions of the plot
|
|
300
726
|
* @param series An array of series names
|
|
301
|
-
* @param
|
|
727
|
+
* @param xRanges A map holding the x-axis ID and its associated range
|
|
728
|
+
* @param yRanges A map holding the y-axis ID and its associated range
|
|
302
729
|
*/
|
|
303
730
|
const onPan = useCallback(
|
|
304
731
|
(
|
|
@@ -317,20 +744,46 @@ export function PoincarePlot(props: Props): null {
|
|
|
317
744
|
[xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState]
|
|
318
745
|
)
|
|
319
746
|
|
|
747
|
+
/**
|
|
748
|
+
* Restores every axis to its original (un-zoomed) domain. Used when d3-zoom's cumulative `k`
|
|
749
|
+
* returns to exactly 1 (fully zoomed out): composing pivot-preserving zooms at *different*
|
|
750
|
+
* pivots isn't position-preserving (zooming in at one spot and back out at another nets a
|
|
751
|
+
* translation, as in any pinch-zoom UI), and the zoom constraint clamps that translated window
|
|
752
|
+
* to the original bounds -- so without this, zooming in at one spot and out at another stops
|
|
753
|
+
* short of the full view on both axes, with `k` already at its maximum. d3-zoom's `scaleExtent`
|
|
754
|
+
* clamps `k` to *exactly* 1 at that limit, so the equality check reliably detects it. (This is
|
|
755
|
+
* the continuous-axis counterpart of `OrdinalAxisRange.scaledCumulative`'s `factor === 1` case.)
|
|
756
|
+
*/
|
|
757
|
+
const snapToOriginalRanges = useCallback(
|
|
758
|
+
(): void => {
|
|
759
|
+
const snap = (ranges: Map<string, ContinuousAxisRange>, axesState: typeof xAxesState) =>
|
|
760
|
+
ranges.forEach((range, axisId) => {
|
|
761
|
+
ranges.set(axisId, range.update(range.original.start, range.original.end))
|
|
762
|
+
setAxisIntervalFor(axisId, range.original)
|
|
763
|
+
axesState.axisFor(axisId).ifPresent(axis => axis.update(range.original, plotDimensions, margin))
|
|
764
|
+
})
|
|
765
|
+
snap(xAxisRangesRef.current, xAxesState)
|
|
766
|
+
snap(yAxisRangesRef.current, yAxesState)
|
|
767
|
+
},
|
|
768
|
+
[setAxisIntervalFor, xAxesState, yAxesState, plotDimensions, margin]
|
|
769
|
+
)
|
|
770
|
+
|
|
320
771
|
/**
|
|
321
772
|
* Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
|
|
322
773
|
* at the location of the mouse when the scroll wheel or gesture was applied. Unlike time-series
|
|
323
774
|
* plots, the iterates plot zooms the x- and y-axis at the same rate.
|
|
324
|
-
* @param
|
|
775
|
+
* @param zoomFactor The *incremental* zoom scale factor for this event -- see
|
|
776
|
+
* `useCanvasZoomPan` for how d3-zoom's own cumulative `k` is converted to this incremental
|
|
777
|
+
* form before this is called.
|
|
325
778
|
* @param x The x-position of the mouse when the scroll wheel or gesture is used
|
|
326
779
|
* @param y The y-position of the mouse when the scroll wheel or gesture is used
|
|
327
780
|
* @param plotDimensions The dimensions of the plot
|
|
328
|
-
* @param
|
|
329
|
-
* @param
|
|
781
|
+
* @param xRanges A map holding the x-axis ID and its associated range
|
|
782
|
+
* @param yRanges A map holding the y-axis ID and its associated range
|
|
330
783
|
*/
|
|
331
784
|
const onZoom = useCallback(
|
|
332
785
|
(
|
|
333
|
-
|
|
786
|
+
zoomFactor: number,
|
|
334
787
|
x: number,
|
|
335
788
|
y: number,
|
|
336
789
|
plotDimensions: Dimensions,
|
|
@@ -338,494 +791,259 @@ export function PoincarePlot(props: Props): null {
|
|
|
338
791
|
yRanges: Map<string, ContinuousAxisRange>
|
|
339
792
|
) => axesZoomHandler(
|
|
340
793
|
xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState, [zoomMinScaleFactor, zoomMaxScaleFactor]
|
|
341
|
-
)(
|
|
794
|
+
)(zoomFactor, [x, y], plotDimensions, xRanges, yRanges),
|
|
342
795
|
[xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState, zoomMinScaleFactor, zoomMaxScaleFactor]
|
|
343
796
|
)
|
|
344
797
|
|
|
345
|
-
const updatePlot = useCallback(
|
|
346
|
-
/**
|
|
347
|
-
* Updates the plot data for the specified time-range, which may have changed due to zoom or pan
|
|
348
|
-
* @param mainGElem The main <g> element selection for that holds the plot
|
|
349
|
-
*/
|
|
350
|
-
(mainGElem: GSelection) => {
|
|
351
|
-
if (container) {
|
|
352
|
-
onUpdateChartTime(currentTimeRef.current)
|
|
353
798
|
|
|
354
|
-
|
|
355
|
-
|
|
799
|
+
const onUpdateAxesBoundsRef = useRef(updateAxisRanges)
|
|
800
|
+
useEffect(
|
|
801
|
+
() => {
|
|
802
|
+
onUpdateAxesBoundsRef.current = updateAxisRanges
|
|
803
|
+
},
|
|
804
|
+
[updateAxisRanges]
|
|
805
|
+
)
|
|
356
806
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
return [
|
|
368
|
-
series.name,
|
|
369
|
-
series.data
|
|
370
|
-
.filter(datum => !isNaN(datum.iterateN))
|
|
371
|
-
.map((datum, index) => ({
|
|
372
|
-
n: datum.iterateN,
|
|
373
|
-
n_1: datum.iterateN_1,
|
|
374
|
-
time: datum.time,
|
|
375
|
-
index: index
|
|
376
|
-
})
|
|
377
|
-
) as IteratePointSeries
|
|
378
|
-
]
|
|
379
|
-
}))
|
|
807
|
+
// updates the plot when the interpolation and filter change, because the updatePlot
|
|
808
|
+
// callback has changed.
|
|
809
|
+
useEffect(
|
|
810
|
+
() => {
|
|
811
|
+
if (canvasContext) {
|
|
812
|
+
updatePlot(canvasContext)
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
[axisRangeFor, canvasContext, updatePlot]
|
|
816
|
+
)
|
|
380
817
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// tooltips from rendering but not getting removed, now that panning
|
|
405
|
-
// is over, allow tooltips to render again
|
|
406
|
-
allowTooltip.current = isSubscriptionClosed();
|
|
407
|
-
})
|
|
818
|
+
// wires up a single mousemove/mouseleave listener on the shared canvas to replace the old
|
|
819
|
+
// per-element SVG mouseenter/mouseleave handlers on the point circles. Hit-tests the mouse
|
|
820
|
+
// position against the last-drawn point geometry (see `geometryRef`, populated by the draw
|
|
821
|
+
// function above).
|
|
822
|
+
useEffect(
|
|
823
|
+
() => {
|
|
824
|
+
if (!canvasContext) return
|
|
825
|
+
|
|
826
|
+
const canvas = canvasContext.canvas
|
|
827
|
+
|
|
828
|
+
// highlights (or un-highlights) the shared x- and y-axes -- keyed on the series name
|
|
829
|
+
// alone (not the specific hovered point index), so moving the mouse between a
|
|
830
|
+
// series' own points doesn't flicker the highlight off and on
|
|
831
|
+
const highlightAxesFor = (seriesName: string, isHighlighted: boolean): void => {
|
|
832
|
+
if (!highlightAxesOnMouseOver) return
|
|
833
|
+
const [xAxis, yAxis] = axesFor(
|
|
834
|
+
axisId => xAxesState.axisFor(axisId).getOrUndefined(),
|
|
835
|
+
axisId => yAxesState.axisFor(axisId).getOrUndefined(),
|
|
836
|
+
)
|
|
837
|
+
const {highlightColor, highlightWidth} = seriesStyles.get(seriesName) || defaultLineStyle()
|
|
838
|
+
xAxis?.setHighlighted(isHighlighted, highlightColor, highlightWidth)
|
|
839
|
+
yAxis?.setHighlighted(isHighlighted, highlightColor, highlightWidth)
|
|
840
|
+
}
|
|
408
841
|
|
|
409
|
-
|
|
410
|
-
|
|
842
|
+
const handleMove = (event: MouseEvent) => {
|
|
843
|
+
if (!allowTooltip.current || !tooltipVisible) return
|
|
411
844
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
.translateExtent([[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]])
|
|
418
|
-
.on("zoom", event => {
|
|
419
|
-
allowTooltip.current = false
|
|
420
|
-
if (event.sourceEvent !== null) {
|
|
421
|
-
onZoom(
|
|
422
|
-
event.transform,
|
|
423
|
-
event.sourceEvent.offsetX - margin.left,
|
|
424
|
-
event.sourceEvent.offsetY - margin.top,
|
|
425
|
-
plotDimensions,
|
|
426
|
-
xAxisRangesRef.current,
|
|
427
|
-
yAxisRangesRef.current
|
|
428
|
-
)
|
|
429
|
-
updatePlotRef.current(mainGElem)
|
|
430
|
-
}
|
|
431
|
-
allowTooltip.current = true
|
|
432
|
-
}
|
|
433
|
-
)
|
|
845
|
+
const [x, y] = canvasLocalPoint(event, canvas)
|
|
846
|
+
const hit = seriesAt(x, y, geometryRef.current)
|
|
847
|
+
const hitSeriesName = hit?.name.replace(/::points$/, '')
|
|
848
|
+
|
|
849
|
+
const previous = hoveredPointRef.current
|
|
434
850
|
|
|
435
|
-
|
|
851
|
+
if (previous?.seriesName !== hitSeriesName) {
|
|
852
|
+
if (previous !== undefined) highlightAxesFor(previous.seriesName, false)
|
|
853
|
+
if (hitSeriesName !== undefined) highlightAxesFor(hitSeriesName, true)
|
|
436
854
|
}
|
|
437
855
|
|
|
438
|
-
|
|
439
|
-
|
|
856
|
+
const sameAsBefore = previous !== undefined && hit !== undefined &&
|
|
857
|
+
previous.seriesName === hitSeriesName && previous.index === hit.index
|
|
440
858
|
|
|
441
|
-
|
|
442
|
-
// todo only want to do this once, on the first plot, and then leave it,
|
|
443
|
-
// unless the axes are updated, also needs to be removed/added when the
|
|
444
|
-
// plot size changes
|
|
445
|
-
const xAxis = xAxesState.defaultAxis().getOrThrow(() => new Error('No default axis found')) as ContinuousNumericAxis
|
|
446
|
-
const yAxis = yAxesState.defaultAxis().getOrThrow(() => new Error('No default axis found')) as ContinuousNumericAxis
|
|
859
|
+
if (sameAsBefore) return
|
|
447
860
|
|
|
448
|
-
|
|
449
|
-
.
|
|
450
|
-
|
|
451
|
-
// ---
|
|
861
|
+
if (previous !== undefined) {
|
|
862
|
+
handleMouseLeavePoint(previous.seriesName, mouseLeaveHandlerFor(`tooltip-${chartId}`))
|
|
863
|
+
}
|
|
452
864
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
.join(enter => enter
|
|
466
|
-
.select("path")
|
|
467
|
-
.style("stroke", "grey")
|
|
468
|
-
.style("fill", "none")
|
|
469
|
-
.style("stroke-width", "1px")
|
|
470
|
-
.attr("class", `fn-equals-fn1-poincare`)
|
|
471
|
-
.attr("id", `#fn-equals-fn1-${chartId}-poincare`)
|
|
472
|
-
.attr('transform', `translate(${margin.left}, ${margin.top})`)
|
|
473
|
-
.attr("d", lineGenerator),
|
|
474
|
-
update => update.remove(),
|
|
475
|
-
exit => exit.remove()
|
|
476
|
-
)
|
|
477
|
-
// clear out mouse-enter callbacks for the diagonal line
|
|
478
|
-
.on("mouseenter", () => {
|
|
479
|
-
})
|
|
480
|
-
.on("mouseleave", () => {
|
|
481
|
-
})
|
|
865
|
+
if (hit !== undefined && hitSeriesName !== undefined) {
|
|
866
|
+
const plotData = plotDataRef.current.get(hitSeriesName)
|
|
867
|
+
if (plotData !== undefined) {
|
|
868
|
+
handleMouseEnterPoint(
|
|
869
|
+
hitSeriesName,
|
|
870
|
+
plotData[hit.index],
|
|
871
|
+
plotData,
|
|
872
|
+
[x, y],
|
|
873
|
+
mouseOverHandlerFor(`tooltip-${chartId}`)
|
|
874
|
+
)
|
|
875
|
+
}
|
|
876
|
+
}
|
|
482
877
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const [xAxisLinear, yAxisLinear] = axesFor(
|
|
487
|
-
axisId => xAxesState.axisFor(axisId).getOrUndefined(),
|
|
488
|
-
axisId => yAxesState.axisFor(axisId).getOrUndefined(),
|
|
489
|
-
)
|
|
490
|
-
if (xAxisLinear === undefined || yAxisLinear === undefined) return
|
|
878
|
+
hoveredPointRef.current = hit !== undefined && hitSeriesName !== undefined ?
|
|
879
|
+
{seriesName: hitSeriesName, index: hit.index} :
|
|
880
|
+
undefined
|
|
491
881
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
highlightColor: defaultLineStyle().color
|
|
496
|
-
}
|
|
882
|
+
// hover state affects the highlight/label drawing, so request a redraw
|
|
883
|
+
canvasContext.requestRedraw()
|
|
884
|
+
}
|
|
497
885
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
.selectAll(`#${name}-${chartId}-poincare`)
|
|
508
|
-
.remove()
|
|
509
|
-
} else {
|
|
510
|
-
// create the time-series paths
|
|
511
|
-
mainGElem
|
|
512
|
-
.selectAll(`#${name}-${chartId}-poincare`)
|
|
513
|
-
.data([[], plotData], () => `${name}`)
|
|
514
|
-
.join(
|
|
515
|
-
enter => enter
|
|
516
|
-
.append("path")
|
|
517
|
-
.attr("class", 'iterate-series-lines')
|
|
518
|
-
.attr("id", `${name}-${chartId}-poincare`)
|
|
519
|
-
.attr("d", pathGenerator.curve(interpolation))
|
|
520
|
-
.style("fill", "none")
|
|
521
|
-
.style("stroke", seriesLineStyle.color)
|
|
522
|
-
.style("stroke-width", seriesLineStyle.lineWidth)
|
|
523
|
-
.attr('transform', `translate(${margin.left}, ${margin.top})`)
|
|
524
|
-
.attr("clip-path", `url(#${clipPathId})`)
|
|
525
|
-
,
|
|
526
|
-
update => update,
|
|
527
|
-
exit => exit.remove()
|
|
528
|
-
)
|
|
529
|
-
}
|
|
886
|
+
const handleLeaveCanvas = () => {
|
|
887
|
+
const previous = hoveredPointRef.current
|
|
888
|
+
if (previous !== undefined) {
|
|
889
|
+
handleMouseLeavePoint(previous.seriesName, mouseLeaveHandlerFor(`tooltip-${chartId}`))
|
|
890
|
+
highlightAxesFor(previous.seriesName, false)
|
|
891
|
+
hoveredPointRef.current = undefined
|
|
892
|
+
canvasContext.requestRedraw()
|
|
893
|
+
}
|
|
894
|
+
}
|
|
530
895
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
.join(
|
|
537
|
-
enter => enter
|
|
538
|
-
.append("circle")
|
|
539
|
-
.attr("class", `${name}-${chartId}-poincare-points`)
|
|
540
|
-
.attr("id", (_, index) => `${name}-${chartId}-poincare-point-${index}`)
|
|
541
|
-
.attr("fill", seriesLineStyle.color)
|
|
542
|
-
.attr("stroke", "none")
|
|
543
|
-
.attr("cx", (d: IteratePoint) => xAxisLinear.scale(d.n) || 0)
|
|
544
|
-
.attr("cy", (d: IteratePoint) => yAxisLinear.scale(d.n_1) || 0)
|
|
545
|
-
.attr("r", 2)
|
|
546
|
-
.attr('transform', `translate(${margin.left}, ${margin.top})`)
|
|
547
|
-
.attr("clip-path", `url(#${clipPathId})`)
|
|
548
|
-
,
|
|
549
|
-
update => update
|
|
550
|
-
.attr("cx", (d: IteratePoint) => xAxisLinear.scale(d.n) || 0)
|
|
551
|
-
.attr("cy", (d: IteratePoint) => yAxisLinear.scale(d.n_1) || 0)
|
|
552
|
-
,
|
|
553
|
-
exit => exit.remove()
|
|
554
|
-
)
|
|
555
|
-
.on("mouseenter",
|
|
556
|
-
(event: React.MouseEvent<SVGCircleElement>, datum: IteratePoint) => {
|
|
557
|
-
if (allowTooltip.current && tooltipVisible) {
|
|
558
|
-
return handleMouseEnterPoint(
|
|
559
|
-
chartId,
|
|
560
|
-
name,
|
|
561
|
-
container,
|
|
562
|
-
event,
|
|
563
|
-
datum,
|
|
564
|
-
plotData,
|
|
565
|
-
xAxisLinear,
|
|
566
|
-
yAxisLinear,
|
|
567
|
-
margin,
|
|
568
|
-
seriesLineStyle,
|
|
569
|
-
backgroundColor,
|
|
570
|
-
allowTooltip.current,
|
|
571
|
-
mouseOverHandlerFor(`tooltip-${chartId}`)
|
|
572
|
-
)
|
|
573
|
-
}
|
|
574
|
-
return <></>
|
|
575
|
-
}
|
|
576
|
-
)
|
|
577
|
-
.on("mouseleave", () => {
|
|
578
|
-
handleMouseLeavePoint(
|
|
579
|
-
chartId,
|
|
580
|
-
name,
|
|
581
|
-
seriesLineStyle.color,
|
|
582
|
-
mouseLeaveHandlerFor(`tooltip-${chartId}`)
|
|
583
|
-
)
|
|
584
|
-
})
|
|
585
|
-
}
|
|
586
|
-
})
|
|
896
|
+
canvas.addEventListener('mousemove', handleMove)
|
|
897
|
+
canvas.addEventListener('mouseleave', handleLeaveCanvas)
|
|
898
|
+
return () => {
|
|
899
|
+
canvas.removeEventListener('mousemove', handleMove)
|
|
900
|
+
canvas.removeEventListener('mouseleave', handleLeaveCanvas)
|
|
587
901
|
}
|
|
588
902
|
},
|
|
589
903
|
[
|
|
590
|
-
|
|
591
|
-
xAxesState, yAxesState,
|
|
592
|
-
onPan,
|
|
593
|
-
zoomMinScaleFactor, zoomMaxScaleFactor, zoomKeyModifiersRequired, onZoom,
|
|
594
|
-
seriesStyles, seriesFilter, showPoints,
|
|
595
|
-
interpolation, backgroundColor,
|
|
596
|
-
mouseOverHandlerFor, mouseLeaveHandlerFor,
|
|
597
|
-
tooltipVisible,
|
|
904
|
+
canvasContext, chartId, mouseOverHandlerFor, mouseLeaveHandlerFor, tooltipVisible,
|
|
905
|
+
highlightAxesOnMouseOver, xAxesState, yAxesState, seriesStyles
|
|
598
906
|
]
|
|
599
907
|
)
|
|
600
908
|
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
const updatePlotRef = useRef(updatePlot)
|
|
909
|
+
// the latest `updateRangesAndPlot`, for the view driver to call -- the driver is created once
|
|
910
|
+
// per run (see below), so it must not capture a stale one (e.g. bound to an older canvas)
|
|
911
|
+
const updateRangesAndPlotRef = useRef(updateRangesAndPlot)
|
|
605
912
|
useEffect(
|
|
606
913
|
() => {
|
|
607
|
-
|
|
914
|
+
updateRangesAndPlotRef.current = updateRangesAndPlot
|
|
608
915
|
},
|
|
609
|
-
[
|
|
916
|
+
[updateRangesAndPlot]
|
|
610
917
|
)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
918
|
+
|
|
919
|
+
// creates the view driver that keeps this plot's view in step with the data source, using the
|
|
920
|
+
// current settings -- see `useStreamingTimeAxes`'s identical effect event
|
|
921
|
+
const createViewDriver = useEffectEvent(
|
|
922
|
+
(source: NonNullable<typeof dataSource>) => iteratesViewDriverFor(
|
|
923
|
+
source,
|
|
924
|
+
windowingTime,
|
|
925
|
+
xAxesState,
|
|
926
|
+
yAxesState,
|
|
927
|
+
() => updateRangesAndPlotRef.current(),
|
|
928
|
+
end => currentTimeRef.current = end
|
|
929
|
+
)
|
|
617
930
|
)
|
|
618
931
|
|
|
619
|
-
//
|
|
620
|
-
|
|
932
|
+
// while the data source is running (and this plot has a canvas), drive the view from the
|
|
933
|
+
// data source's updates -- see `useStreamingTimeAxes`'s identical effect. The driver belongs to this plot
|
|
934
|
+
// instance alone and is always torn down with it.
|
|
935
|
+
useEffect(
|
|
621
936
|
() => {
|
|
622
|
-
if (
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
onUpdateData,
|
|
630
|
-
dropDataAfter,
|
|
631
|
-
updateRangesAndPlot,
|
|
632
|
-
// as new data flows into the subscription, the subscription
|
|
633
|
-
// updates this map directly (for performance)
|
|
634
|
-
seriesRef.current,
|
|
635
|
-
end => currentTimeRef.current = end
|
|
636
|
-
)
|
|
937
|
+
if (dataSource === undefined || !running || canvasContext === null) return
|
|
938
|
+
allowTooltip.current = false
|
|
939
|
+
const driver = createViewDriver(dataSource)
|
|
940
|
+
return () => {
|
|
941
|
+
driver.unsubscribe()
|
|
942
|
+
allowTooltip.current = true
|
|
943
|
+
}
|
|
637
944
|
},
|
|
638
|
-
[
|
|
639
|
-
dropDataAfter, mainG,
|
|
640
|
-
onSubscribe, onUpdateData,
|
|
641
|
-
seriesObservable, updateRangesAndPlot, windowingTime,
|
|
642
|
-
xAxesState, yAxesState,
|
|
643
|
-
]
|
|
945
|
+
[dataSource, running, canvasContext]
|
|
644
946
|
)
|
|
645
947
|
|
|
646
|
-
//
|
|
647
|
-
// callback has changed.
|
|
948
|
+
// this plot needs a data source (see the `Chart`'s `dataSource` prop)
|
|
648
949
|
useEffect(
|
|
649
950
|
() => {
|
|
650
|
-
if (
|
|
651
|
-
|
|
951
|
+
if (dataSource === undefined) {
|
|
952
|
+
console.error("PoincarePlot requires a data source; set the enclosing Chart's `dataSource` prop")
|
|
652
953
|
}
|
|
653
954
|
},
|
|
654
|
-
[
|
|
955
|
+
[dataSource]
|
|
655
956
|
)
|
|
656
957
|
|
|
657
|
-
//
|
|
658
|
-
// is changed to `true` and we haven't subscribed yet, then subscribe. when the
|
|
659
|
-
// `shouldSubscribe` is `false` and we had subscribed, then unsubscribe. otherwise,
|
|
660
|
-
// do nothing.
|
|
958
|
+
// unregister this plot's draw function on unmount
|
|
661
959
|
useEffect(
|
|
662
960
|
() => {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
subscriptionRef.current?.unsubscribe()
|
|
668
|
-
subscriptionRef.current = undefined
|
|
669
|
-
allowTooltip.current = true
|
|
961
|
+
return () => {
|
|
962
|
+
if (canvasContext) {
|
|
963
|
+
canvasContext.unregister(`poincare-plot-${chartId}`)
|
|
964
|
+
}
|
|
670
965
|
}
|
|
671
966
|
},
|
|
672
|
-
[
|
|
967
|
+
[canvasContext, chartId]
|
|
673
968
|
)
|
|
674
969
|
|
|
675
970
|
return null
|
|
676
971
|
}
|
|
677
972
|
|
|
678
973
|
/**
|
|
679
|
-
*
|
|
680
|
-
*
|
|
974
|
+
* @param dataSource The chart's data source
|
|
975
|
+
* @param seriesName The name of the series
|
|
976
|
+
* @return The number of points dropped from the series so far, when the data source counts them
|
|
977
|
+
* (an {@link IteratesDataSource} does); otherwise 0
|
|
978
|
+
*/
|
|
979
|
+
function droppedCountFor(dataSource: unknown, seriesName: string): number {
|
|
980
|
+
return dataSource !== null && typeof dataSource === 'object' && 'droppedCountFor' in dataSource ?
|
|
981
|
+
(dataSource as {droppedCountFor: (name: string) => number}).droppedCountFor(seriesName) :
|
|
982
|
+
0
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Locates the plot's x- and y-axes: always the default (first) ones, since every series of a
|
|
987
|
+
* Poincaré plot shares the same axes
|
|
681
988
|
* @param xAxisFor The function that accepts an axis ID and returns the corresponding x-axis
|
|
682
989
|
* @param yAxisFor The function that accepts an axis ID and returns the corresponding y-axis
|
|
990
|
+
* @return The x- and y-axes (either is actually `undefined` when it isn't found, despite the type)
|
|
991
|
+
* @throws Error when either axis isn't a continuous numeric axis
|
|
683
992
|
*/
|
|
684
993
|
function axesFor(
|
|
685
994
|
xAxisFor: (id: string) => BaseAxis | undefined,
|
|
686
995
|
yAxisFor: (id: string) => BaseAxis | undefined,
|
|
687
996
|
): [xAxis: ContinuousNumericAxis, yAxis: ContinuousNumericAxis] {
|
|
688
997
|
const xAxis = xAxisFor("")
|
|
689
|
-
const xAxisLinear = xAxis as ContinuousNumericAxis
|
|
690
998
|
const yAxis = yAxisFor("")
|
|
999
|
+
checkAxisType("PoincarePlot", "x", xAxis, AxisType.ContinuousNumeric)
|
|
1000
|
+
checkAxisType("PoincarePlot", "y", yAxis, AxisType.ContinuousNumeric)
|
|
1001
|
+
const xAxisLinear = xAxis as ContinuousNumericAxis
|
|
691
1002
|
const yAxisLinear = yAxis as ContinuousNumericAxis
|
|
692
|
-
if (xAxis && !xAxisLinear) {
|
|
693
|
-
throw Error("Poincare plot requires that x-axis be of type LinearAxis")
|
|
694
|
-
}
|
|
695
|
-
if (yAxis && !yAxisLinear) {
|
|
696
|
-
throw Error("Poincare plot requires that y-axis be of type LinearAxis")
|
|
697
|
-
}
|
|
698
1003
|
return [xAxisLinear, yAxisLinear]
|
|
699
1004
|
}
|
|
700
1005
|
|
|
701
1006
|
/**
|
|
702
|
-
*
|
|
1007
|
+
* Reports the tooltip for the hovered point. Replaces the old version, which also directly
|
|
1008
|
+
* mutated the hovered SVG `<circle>`'s radius/fill (and its neighbors') as a side effect; that
|
|
1009
|
+
* highlighting is now handled entirely by the draw function reading `hoveredPointRef` (see
|
|
1010
|
+
* `updatePlot`'s `draw`) rather than by touching elements here.
|
|
703
1011
|
* @param seriesName The name of the series (i.e. the neuron ID)
|
|
704
|
-
* @param
|
|
705
|
-
* @param event The mouse event that triggered this call
|
|
706
|
-
* @param datum The datum over which the mouse has entered
|
|
1012
|
+
* @param datum The hovered point, or `undefined` if the index doesn't resolve to a point
|
|
707
1013
|
* @param plotData The iterates series
|
|
708
|
-
* @param
|
|
709
|
-
* @param yAxisLinear The y-axis (f[n+1](x))
|
|
710
|
-
* @param margin The plot margin
|
|
711
|
-
* @param seriesStyle The series style information (needed for (un)highlighting)
|
|
712
|
-
* @param backgroundColor
|
|
713
|
-
* @param allowTooltip When set to `false` won't show tooltip, even if it is visible (used by pan)
|
|
1014
|
+
* @param mouseCoords The `[x, y]` position of the mouse, in canvas coordinates
|
|
714
1015
|
* @param mouseOverHandlerFor The handler for the mouse-over (registered by the <Tooltip/>)
|
|
715
1016
|
*/
|
|
716
1017
|
function handleMouseEnterPoint(
|
|
717
|
-
chartId: number,
|
|
718
1018
|
seriesName: string,
|
|
719
|
-
|
|
720
|
-
event: React.MouseEvent<SVGPathElement>,
|
|
721
|
-
datum: IteratePoint,
|
|
1019
|
+
datum: IteratePoint | undefined,
|
|
722
1020
|
plotData: IteratePointSeries,
|
|
723
|
-
|
|
724
|
-
yAxisLinear: ContinuousNumericAxis,
|
|
725
|
-
margin: Margin,
|
|
726
|
-
seriesStyle: SeriesLineStyle,
|
|
727
|
-
backgroundColor: string,
|
|
728
|
-
allowTooltip: boolean,
|
|
1021
|
+
mouseCoords: [x: number, y: number],
|
|
729
1022
|
mouseOverHandlerFor: ((seriesName: string, time: number, tooltipData: TooltipData<IterateDatum, NoTooltipMetadata>, mouseCoords: [x: number, y: number]) => void) | undefined,
|
|
730
1023
|
): void {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const padding = 4
|
|
734
|
-
const circleRadius = 5
|
|
735
|
-
const circleStroke = lineWidth
|
|
736
|
-
|
|
737
|
-
const circle = event.currentTarget as SVGCircleElement
|
|
738
|
-
|
|
739
|
-
d3.select<SVGPathElement, Datum>(circle)
|
|
740
|
-
.attr("r", circleRadius)
|
|
741
|
-
.style("fill", highlightColor)
|
|
742
|
-
|
|
743
|
-
const index = plotData
|
|
744
|
-
.findIndex(point => point.n === datum.n && point.n_1 === datum.n_1)
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* Displays basic information about an iterate, generally its neighbors
|
|
748
|
-
* @param index The index of the iterate
|
|
749
|
-
*/
|
|
750
|
-
function showInfo(index: number): void {
|
|
751
|
-
d3.select(`#${seriesName}-${chartId}-poincare-point-${index}`)
|
|
752
|
-
.attr("r", circleRadius)
|
|
753
|
-
.style("fill", d3.rgb(highlightColor).brighter(0.7).toString())
|
|
754
|
-
.style("stroke-width", circleStroke)
|
|
755
|
-
.style("stroke", color)
|
|
756
|
-
|
|
757
|
-
// grab the (x, y)-coordinates for the plot
|
|
758
|
-
const iterateN = xAxisLinear.scale(plotData[index].n) + margin.left
|
|
759
|
-
const iterateN_1 = yAxisLinear.scale(plotData[index].n_1) + margin.top
|
|
760
|
-
|
|
761
|
-
const svg = d3.select<SVGSVGElement, any>(container)
|
|
762
|
-
|
|
763
|
-
// add a rectangle that serves as the background for the text (to make the
|
|
764
|
-
// text readable when the chart is busy)
|
|
765
|
-
const rect = svg
|
|
766
|
-
.append("rect")
|
|
767
|
-
.attr("class", `${seriesName}-${chartId}-poincare-point-text-background`)
|
|
768
|
-
|
|
769
|
-
// add the information about the iterate as a text element
|
|
770
|
-
const textElement = svg
|
|
771
|
-
.append("text")
|
|
772
|
-
.attr('class', `${seriesName}-${chartId}-poincare-point-text`)
|
|
773
|
-
.attr('fill', highlightColor)
|
|
774
|
-
.attr('font-family', 'sans-serif')
|
|
775
|
-
.attr('font-size', 11)
|
|
776
|
-
.attr('font-weight', 700)
|
|
777
|
-
.text(`n = ${index}; t = ${formatTime(plotData[index].time)} ms`)
|
|
778
|
-
|
|
779
|
-
// calculate the width and height of the text element
|
|
780
|
-
const {width, height} = textDimensions(textElement)
|
|
781
|
-
|
|
782
|
-
textElement
|
|
783
|
-
.attr("transform", `translate(${iterateN - 8}, ${iterateN_1 - circleRadius - circleStroke - padding})`)
|
|
784
|
-
|
|
785
|
-
rect
|
|
786
|
-
.attr("x", iterateN - padding / 2 - 8)
|
|
787
|
-
.attr("y", iterateN_1 - padding / 2 - circleRadius - circleStroke - height)
|
|
788
|
-
.attr("width", width + padding)
|
|
789
|
-
.attr("height", height + padding / 2)
|
|
790
|
-
.style("fill", backgroundColor)
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
if (index > 0) {
|
|
794
|
-
showInfo(index - 1)
|
|
795
|
-
}
|
|
796
|
-
if (index < plotData.length - 1) {
|
|
797
|
-
showInfo(index + 1)
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
const [x, y] = d3.pointer(event, container)
|
|
801
|
-
const currentDatum: IteratePoint = (index >= 0) ? plotData[index] : {time: 0, n: 0, n_1: 0, index}
|
|
1024
|
+
if (datum === undefined) return
|
|
802
1025
|
|
|
803
|
-
if (mouseOverHandlerFor
|
|
1026
|
+
if (mouseOverHandlerFor) {
|
|
804
1027
|
mouseOverHandlerFor(
|
|
805
1028
|
seriesName,
|
|
806
|
-
|
|
1029
|
+
datum.time,
|
|
807
1030
|
{series: plotData.map(ip => ({iterateN: ip.n, iterateN_1: ip.n_1, time: ip.time})), metadata: {}},
|
|
808
|
-
|
|
1031
|
+
mouseCoords
|
|
809
1032
|
)
|
|
810
1033
|
}
|
|
811
1034
|
}
|
|
812
1035
|
|
|
1036
|
+
/**
|
|
1037
|
+
* Calls the mouse-leave-series handler registered for this series (the highlight is cleared by
|
|
1038
|
+
* the draw function, once the hovered point is cleared)
|
|
1039
|
+
* @param seriesName The name of the series
|
|
1040
|
+
* @param mouseLeaverHandlerFor Registered handler for the series when the mouse leaves
|
|
1041
|
+
*/
|
|
813
1042
|
function handleMouseLeavePoint(
|
|
814
|
-
chartId: number,
|
|
815
1043
|
seriesName: string,
|
|
816
|
-
color: string,
|
|
817
1044
|
mouseLeaverHandlerFor: ((seriesName: string) => void) | undefined,
|
|
818
1045
|
): void {
|
|
819
|
-
d3.selectAll<SVGPathElement, Datum>(`.${seriesName}-${chartId}-poincare-points`)
|
|
820
|
-
.attr("r", 2)
|
|
821
|
-
.style("fill", color)
|
|
822
|
-
.style("stroke", "none")
|
|
823
|
-
d3.selectAll(`.${seriesName}-${chartId}-poincare-point-arrows`).remove()
|
|
824
|
-
d3.selectAll(`.${seriesName}-${chartId}-poincare-point-text`).remove()
|
|
825
|
-
d3.selectAll(`.${seriesName}-${chartId}-poincare-point-text-background`).remove()
|
|
826
|
-
|
|
827
1046
|
if (mouseLeaverHandlerFor) {
|
|
828
1047
|
mouseLeaverHandlerFor(seriesName)
|
|
829
1048
|
}
|
|
830
|
-
|
|
831
1049
|
}
|