stream-charts 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +809 -1022
- package/changes.md +40 -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,40 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useCallback, useEffect, useEffectEvent, useMemo, useRef } from 'react';
|
|
2
|
+
import { useCanvasZoomPan } from "./useCanvasZoomPan";
|
|
3
3
|
import { useChart } from "../hooks/useChart";
|
|
4
4
|
import * as d3 from "d3";
|
|
5
|
-
import {
|
|
6
|
-
import { axesZoomHandler, defaultLineStyle, panHandler2D } from "../axes/axes";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import { clipToArea } from "./plot";
|
|
6
|
+
import { axesZoomHandler, AxisType, checkAxisType, defaultLineStyle, panHandler2D } from "../axes/axes";
|
|
7
|
+
import { canvasLocalPoint, seriesAt } from "./hitTesting";
|
|
8
|
+
import { fontStringFor, formatTime, noop, textDimensions } from "../utils";
|
|
9
|
+
import { iteratesViewDriverFor } from "../subscriptions/viewDrivers";
|
|
10
|
+
import { useDataSource, useDataSourceRunning, useInitialData, useStreamingSettings } from "../hooks/useDataSource";
|
|
10
11
|
import { usePlotDimensions } from "../hooks/usePlotDimensions";
|
|
11
|
-
import { useInitialData } from "../hooks/useInitialData";
|
|
12
12
|
import { useTooltip } from "../hooks/useTooltip";
|
|
13
13
|
import { ContinuousAxisRange } from "../axes/ContinuousAxisRange";
|
|
14
|
+
import { AxisInterval } from "../axes/AxisInterval";
|
|
15
|
+
import { warnAboutIgnoredAutoScale } from "../axes/autoScale";
|
|
16
|
+
import { FastShiftArray } from "fast-shift-array";
|
|
17
|
+
import { DEFAULT_LINE_COLOR_STEPS, DEFAULT_LINE_END_COLOR, DEFAULT_LINE_START_COLOR, pointColorScale, strokeColorizedLine } from "./colorizedLine";
|
|
18
|
+
/**
|
|
19
|
+
* Creates an axis range (with its original range the same as its current range) from each axis'
|
|
20
|
+
* current domain
|
|
21
|
+
* @param axes The axes (continuous numeric ones), by their IDs
|
|
22
|
+
* @return The axes' ranges, by their IDs
|
|
23
|
+
*/
|
|
14
24
|
function generateAxisRangeMap(axes) {
|
|
15
25
|
return new Map(Array.from(axes.entries()).map(([id, axis]) => {
|
|
16
26
|
const [start, end] = axis.scale.domain();
|
|
17
27
|
return [id, ContinuousAxisRange.from(start, end)];
|
|
18
28
|
}));
|
|
19
29
|
}
|
|
20
|
-
// A sentinel that represents that no curve factory is to be used, which means
|
|
21
|
-
// that no line will be drawn between the iterates (kinda gross)
|
|
22
|
-
export const NoCurveFactory = undefined;
|
|
23
30
|
/**
|
|
24
|
-
* Renders a streaming Poincare (iterates) plot for the series
|
|
25
|
-
*
|
|
31
|
+
* Renders a streaming Poincare (iterates) plot for the series held by the {@link StreamingDataSource}
|
|
32
|
+
* handed to the {@link Chart} (its `dataSource` prop). This component uses the {@link useChart}
|
|
26
33
|
* hook, and therefore must be a child of the {@link Chart} in order to be plugged in to the
|
|
27
34
|
* chart ecosystem (axes, tracker, tooltip).
|
|
28
35
|
*
|
|
29
|
-
*
|
|
36
|
+
* Internally, this no longer creates/updates SVG `<path>`/`<circle>`/`<text>` elements. Instead, it
|
|
37
|
+
* registers a single draw function with the chart's {@link CanvasContext} that redraws every
|
|
38
|
+
* series' line and points from scratch each time the canvas repaints. The old version's
|
|
39
|
+
* hover-a-point-and-annotate-its-neighbors behavior used direct `d3.select('#specific-id')` lookups
|
|
40
|
+
* into already-rendered SVG elements from a separate pass after the main render; since canvas has
|
|
41
|
+
* no persistent elements to reach back into, this is restructured so the single draw pass checks
|
|
42
|
+
* "is this point hovered, or a neighbor of the hovered point" and renders the highlight/label
|
|
43
|
+
* inline, driven by a `hoveredPointRef` updated on `mousemove`.
|
|
44
|
+
*
|
|
45
|
+
* @param props The properties associated with the Poincaré plot
|
|
46
|
+
* @return `null` (the plot draws onto the chart's canvas)
|
|
30
47
|
* @example
|
|
31
48
|
* ```typescript
|
|
32
|
-
* <
|
|
49
|
+
* <PoincarePlot
|
|
33
50
|
* interpolation={interpolation}
|
|
34
|
-
*
|
|
35
|
-
* ['test2', assignAxes("x-axis-2", "y-axis-2")],
|
|
36
|
-
* ])}
|
|
37
|
-
* dropDataAfter={10000}
|
|
51
|
+
* showPoints={true}
|
|
38
52
|
* panEnabled={true}
|
|
39
53
|
* zoomEnabled={true}
|
|
40
54
|
* zoomKeyModifiersRequired={true}
|
|
@@ -42,46 +56,315 @@ export const NoCurveFactory = undefined;
|
|
|
42
56
|
* ```
|
|
43
57
|
*/
|
|
44
58
|
export function PoincarePlot(props) {
|
|
45
|
-
|
|
59
|
+
var _a;
|
|
60
|
+
const { chartId, canvasContext, axes, backgroundColor, seriesStyles, seriesFilter, mouse } = useChart();
|
|
46
61
|
const { xAxesState, yAxesState, setAxisIntervalFor, updateAxisRanges = noop, axisRangeFor, addAxesRangesUpdateHandler, removeAxesRangesUpdateHandler, } = axes;
|
|
62
|
+
// this plot doesn't auto-scale its y-axes, so warn about any that ask to be (see
|
|
63
|
+
// `ContinuousAxis`'s `autoScale` prop)
|
|
64
|
+
useEffect(() => warnAboutIgnoredAutoScale("PoincarePlot", yAxesState.axes), [yAxesState]);
|
|
47
65
|
const { mouseOverHandlerFor, mouseLeaveHandlerFor } = mouse;
|
|
48
66
|
const { plotDimensions, margin, } = usePlotDimensions();
|
|
49
|
-
const {
|
|
67
|
+
const { windowingTime = 100, onUpdateChartTime = noop, } = useStreamingSettings();
|
|
68
|
+
// the application-owned source of the data, and whether it's running (streaming)
|
|
69
|
+
const dataSource = useDataSource();
|
|
70
|
+
const running = useDataSourceRunning(dataSource);
|
|
50
71
|
const { initialData } = useInitialData();
|
|
51
72
|
const { visibilityState: tooltipVisible } = useTooltip();
|
|
52
|
-
const { interpolation, showPoints = true,
|
|
73
|
+
const { interpolation, showPoints = true, panEnabled = false, zoomEnabled = false, zoomKeyModifiersRequired = true, zoomMinScaleFactor = 0, zoomMaxScaleFactor = 1, highlightAxesOnMouseOver = false, colorizeLines = false, lineStartColor = DEFAULT_LINE_START_COLOR, lineEndColor = DEFAULT_LINE_END_COLOR, lineColorSteps = DEFAULT_LINE_COLOR_STEPS, zoomState = undefined, onZoomStateChange = noop, onZoomReset = noop, } = props;
|
|
53
74
|
// why do "dataRef" and "seriesRef" both hold on to the same underlying data? for performance.
|
|
54
75
|
//
|
|
55
76
|
// the "dataRef" and "seriesRef" both point to the same underlying data, a collection
|
|
56
|
-
// of series
|
|
57
|
-
// are the
|
|
77
|
+
// of series (held, and updated as new data is streamed in, by the data source). The series in
|
|
78
|
+
// "dataRef" are read by the canvas draw function, and the "seriesRef" map is for looking a
|
|
79
|
+
// series up by its name.
|
|
58
80
|
//
|
|
59
81
|
// the "dataRef" object holds on to a copy of the initial data (which is an array of
|
|
60
|
-
//
|
|
82
|
+
// iterate series, e.i. an array of BaseSeries<IterateDatum> objects). The slice just creates a copy of
|
|
61
83
|
// the array, but the references to the BaseSeries objects are the same and still point to the same
|
|
62
84
|
// data as the "initialData" array.
|
|
63
85
|
//
|
|
64
|
-
// the "seriesRef" object is a reference to a map (series_name -> BaseSeries<
|
|
65
|
-
//
|
|
86
|
+
// the "seriesRef" object is a reference to a map (series_name -> BaseSeries<IterateDatum>), used
|
|
87
|
+
// to find the series under the mouse (for the tooltip).
|
|
66
88
|
//
|
|
67
|
-
// the series in the "dataRef" object are the ones
|
|
68
|
-
//
|
|
89
|
+
// the series in the "dataRef" object are the ones read by the next redraw, so as these are
|
|
90
|
+
// updated, the next canvas repaint picks up the new data.
|
|
69
91
|
const dataRef = useRef(initialData.slice());
|
|
70
92
|
const seriesRef = useRef(new Map(initialData.map(series => [series.name, series])));
|
|
71
93
|
// map(axis_id -> current_time) -- maps the axis ID to the current time for that axis
|
|
72
94
|
const currentTimeRef = useRef(0);
|
|
73
95
|
const xAxisRangesRef = useRef(new Map());
|
|
74
96
|
const yAxisRangesRef = useRef(new Map());
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
// tooltips are only shown while the data source is paused (and not while panning)
|
|
98
|
+
const allowTooltip = useRef(!((_a = dataSource === null || dataSource === void 0 ? void 0 : dataSource.running) !== null && _a !== void 0 ? _a : false));
|
|
99
|
+
// the last-drawn geometry for each series' points, in canvas coordinates, used for
|
|
100
|
+
// hit-testing mouse hover on `mousemove` (see the effect below that wires up the listener).
|
|
101
|
+
// Also keeps the corresponding `IteratePointSeries` so the hovered/neighbor points' data can
|
|
102
|
+
// be looked up by index.
|
|
103
|
+
const geometryRef = useRef(new Map());
|
|
104
|
+
const plotDataRef = useRef(new Map());
|
|
105
|
+
// which point (series + index) is currently hovered, so the draw pass can highlight it and
|
|
106
|
+
// its neighbors, and so we know when to fire a "leave"
|
|
107
|
+
const hoveredPointRef = useRef(undefined);
|
|
108
|
+
const updatePlot = useCallback(
|
|
109
|
+
/**
|
|
110
|
+
* (Re-)registers this plot's draw function with the canvas context and requests a redraw.
|
|
111
|
+
*
|
|
112
|
+
* Pan/zoom behavior setup lives in a separate effect (see below), not here -- this
|
|
113
|
+
* function runs on every data tick, and recreating/reattaching a `d3.drag()`/`d3.zoom()`
|
|
114
|
+
* behavior that often is pure overhead unrelated to drawing the new data.
|
|
115
|
+
* @param cc The canvas context to register the draw function with
|
|
116
|
+
*/
|
|
117
|
+
(cc) => {
|
|
118
|
+
onUpdateChartTime(currentTimeRef.current);
|
|
119
|
+
// the colorized lines' color at a point, by the point's overall sequence number in its
|
|
120
|
+
// series (see `colorizeLines`)
|
|
121
|
+
const lineColorOf = pointColorScale(lineStartColor, lineEndColor, lineColorSteps);
|
|
122
|
+
const draw = (context) => {
|
|
123
|
+
const { context2D } = context;
|
|
124
|
+
// create a map associating series-names to their time-series.
|
|
125
|
+
const boundedSeries = new Map(dataRef.current.map(series => {
|
|
126
|
+
return [
|
|
127
|
+
series.name,
|
|
128
|
+
series.data
|
|
129
|
+
.filter(datum => !isNaN(datum.iterateN))
|
|
130
|
+
.map((datum, index) => ({
|
|
131
|
+
n: datum.iterateN,
|
|
132
|
+
n_1: datum.iterateN_1,
|
|
133
|
+
time: datum.time,
|
|
134
|
+
index: index
|
|
135
|
+
}))
|
|
136
|
+
];
|
|
137
|
+
}));
|
|
138
|
+
context2D.save();
|
|
139
|
+
clipToArea(context, plotDimensions, { x: margin.left, y: margin.top });
|
|
140
|
+
context2D.translate(margin.left, margin.top);
|
|
141
|
+
// ---
|
|
142
|
+
// todo only want to do this once, on the first plot, and then leave it,
|
|
143
|
+
// unless the axes are updated, also needs to be removed/added when the
|
|
144
|
+
// plot size changes
|
|
145
|
+
// (the same, checked axes as the series' below: the default x- and y-axes)
|
|
146
|
+
const [xAxis, yAxis] = axesFor(axisId => xAxesState.axisFor(axisId).getOrUndefined(), axisId => yAxesState.axisFor(axisId).getOrUndefined());
|
|
147
|
+
if (xAxis === undefined || yAxis === undefined)
|
|
148
|
+
throw new Error('No default axis found');
|
|
149
|
+
// ---
|
|
150
|
+
const [xStart, xEnd] = xAxesState.axisDefaultId()
|
|
151
|
+
.map(id => { var _a; return ((_a = xAxisRangesRef.current.get(id)) === null || _a === void 0 ? void 0 : _a.original.asTuple()) || [0, 0]; })
|
|
152
|
+
.getOrElse([0, 0]);
|
|
153
|
+
const [yStart, yEnd] = yAxesState.axisDefaultId()
|
|
154
|
+
.map(id => { var _a; return ((_a = yAxisRangesRef.current.get(id)) === null || _a === void 0 ? void 0 : _a.original.asTuple()) || [0, 0]; })
|
|
155
|
+
.getOrElse([0, 0]);
|
|
156
|
+
// the fn = fn+1 diagonal reference line (no hover behavior, matching the old
|
|
157
|
+
// version's explicitly no-op mouseenter/mouseleave handlers)
|
|
158
|
+
context2D.strokeStyle = "grey";
|
|
159
|
+
context2D.lineWidth = 1;
|
|
160
|
+
context2D.beginPath();
|
|
161
|
+
context2D.moveTo(xAxis.scale(xStart), yAxis.scale(yStart));
|
|
162
|
+
context2D.lineTo(xAxis.scale(xEnd), yAxis.scale(yEnd));
|
|
163
|
+
context2D.stroke();
|
|
164
|
+
const newGeometry = new Map();
|
|
165
|
+
const newPlotDataBySeries = new Map();
|
|
166
|
+
const hovered = hoveredPointRef.current;
|
|
167
|
+
boundedSeries.forEach((data, name) => {
|
|
168
|
+
var _a;
|
|
169
|
+
// grab the x and y axes assigned to the series, and if either or both
|
|
170
|
+
// axes aren't found, then give up and return
|
|
171
|
+
const [xAxisLinear, yAxisLinear] = axesFor(axisId => xAxesState.axisFor(axisId).getOrUndefined(), axisId => yAxesState.axisFor(axisId).getOrUndefined());
|
|
172
|
+
if (xAxisLinear === undefined || yAxisLinear === undefined)
|
|
173
|
+
return;
|
|
174
|
+
// grab the style for the series
|
|
175
|
+
const seriesLineStyle = seriesStyles.get(name) || {
|
|
176
|
+
...defaultLineStyle(),
|
|
177
|
+
highlightColor: defaultLineStyle().color
|
|
178
|
+
};
|
|
179
|
+
// only show the data for which the filter matches
|
|
180
|
+
const plotData = (name.match(seriesFilter)) ? data : FastShiftArray.empty();
|
|
181
|
+
newPlotDataBySeries.set(name, plotData);
|
|
182
|
+
// create the time-series path (when no interpolation is given, don't draw a
|
|
183
|
+
// connecting line at all -- matches the old version's `mainGElem.selectAll(...).remove()`)
|
|
184
|
+
// when colorizing the lines, a point's overall sequence number in its series is
|
|
185
|
+
// the number of points dropped from the series so far (when the data source
|
|
186
|
+
// counts them) plus its index -- the line leaving a point, and the point itself,
|
|
187
|
+
// are colored by it
|
|
188
|
+
const dropped = colorizeLines ? droppedCountFor(dataSource, name) : 0;
|
|
189
|
+
if (interpolation !== undefined) {
|
|
190
|
+
const pathGenerator = d3.line()
|
|
191
|
+
.x(d => xAxis.scale(d.n || 0))
|
|
192
|
+
.y(d => yAxis.scale(d.n_1 || 0))
|
|
193
|
+
.curve(interpolation);
|
|
194
|
+
context2D.lineWidth = seriesLineStyle.lineWidth;
|
|
195
|
+
if (colorizeLines) {
|
|
196
|
+
// the line's color changes along it, one step per point
|
|
197
|
+
const linePoints = Array.from(plotData);
|
|
198
|
+
strokeColorizedLine(context2D, linePoints, d => xAxis.scale(d.n || 0), d => yAxis.scale(d.n_1 || 0), interpolation, index => lineColorOf(dropped + linePoints[index].index));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
context2D.strokeStyle = seriesLineStyle.color;
|
|
202
|
+
context2D.stroke(new Path2D((_a = pathGenerator(Array.from(plotData))) !== null && _a !== void 0 ? _a : ""));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// when specified, show a circle for the actual data point
|
|
206
|
+
const points = [];
|
|
207
|
+
if (showPoints) {
|
|
208
|
+
const isThisSeriesHovered = (hovered === null || hovered === void 0 ? void 0 : hovered.seriesName) === name;
|
|
209
|
+
plotData.forEach((d) => {
|
|
210
|
+
const x = xAxisLinear.scale(d.n) || 0;
|
|
211
|
+
const y = yAxisLinear.scale(d.n_1) || 0;
|
|
212
|
+
points.push([x + margin.left, y + margin.top]);
|
|
213
|
+
const isHoveredPoint = isThisSeriesHovered && hovered.index === d.index;
|
|
214
|
+
const isNeighborOfHovered = isThisSeriesHovered &&
|
|
215
|
+
(hovered.index === d.index - 1 || hovered.index === d.index + 1);
|
|
216
|
+
if (isHoveredPoint) {
|
|
217
|
+
// the hovered point itself: enlarged, highlight-colored, no label
|
|
218
|
+
// (matches the old version, which only labeled the *neighbors*)
|
|
219
|
+
context2D.fillStyle = seriesLineStyle.highlightColor;
|
|
220
|
+
context2D.beginPath();
|
|
221
|
+
context2D.arc(x, y, 5, 0, 2 * Math.PI);
|
|
222
|
+
context2D.fill();
|
|
223
|
+
}
|
|
224
|
+
else if (isNeighborOfHovered) {
|
|
225
|
+
// a neighbor of the hovered point: enlarged, brighter fill, stroked,
|
|
226
|
+
// with a floating "n = i; t = X ms" label above it
|
|
227
|
+
context2D.fillStyle = d3.rgb(seriesLineStyle.highlightColor).brighter(0.7).toString();
|
|
228
|
+
context2D.strokeStyle = seriesLineStyle.color;
|
|
229
|
+
context2D.lineWidth = seriesLineStyle.lineWidth;
|
|
230
|
+
context2D.beginPath();
|
|
231
|
+
context2D.arc(x, y, 5, 0, 2 * Math.PI);
|
|
232
|
+
context2D.fill();
|
|
233
|
+
context2D.stroke();
|
|
234
|
+
const label = `n = ${d.index}; t = ${formatTime(d.time)} ms`;
|
|
235
|
+
context2D.font = fontStringFor(11, 'sans-serif', 700);
|
|
236
|
+
const { width, height } = textDimensions(context2D, label);
|
|
237
|
+
const padding = 4;
|
|
238
|
+
const circleRadius = 5;
|
|
239
|
+
const circleStroke = seriesLineStyle.lineWidth;
|
|
240
|
+
context2D.fillStyle = backgroundColor;
|
|
241
|
+
context2D.fillRect(x - padding / 2 - 8, y - padding / 2 - circleRadius - circleStroke - height, width + padding, height + padding / 2);
|
|
242
|
+
context2D.fillStyle = seriesLineStyle.highlightColor;
|
|
243
|
+
context2D.textAlign = 'left';
|
|
244
|
+
context2D.textBaseline = 'alphabetic';
|
|
245
|
+
context2D.fillText(label, x - 8, y - circleRadius - circleStroke - padding);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// normal, unhovered point (in the color of the line leaving it, when
|
|
249
|
+
// the lines are colorized)
|
|
250
|
+
context2D.fillStyle = colorizeLines ?
|
|
251
|
+
lineColorOf(dropped + d.index) :
|
|
252
|
+
seriesLineStyle.color;
|
|
253
|
+
context2D.beginPath();
|
|
254
|
+
context2D.arc(x, y, 2, 0, 2 * Math.PI);
|
|
255
|
+
context2D.fill();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
newGeometry.set(`${name}::points`, { points, hitRadius: 6 });
|
|
260
|
+
});
|
|
261
|
+
geometryRef.current = newGeometry;
|
|
262
|
+
plotDataRef.current = newPlotDataBySeries;
|
|
263
|
+
context2D.restore();
|
|
264
|
+
};
|
|
265
|
+
cc.register(`poincare-plot-${chartId}`, draw, 10);
|
|
266
|
+
cc.requestRedraw();
|
|
267
|
+
}, [
|
|
268
|
+
chartId, onUpdateChartTime, plotDimensions, xAxesState, yAxesState, seriesStyles, seriesFilter,
|
|
269
|
+
showPoints, interpolation, backgroundColor, colorizeLines, lineStartColor, lineEndColor,
|
|
270
|
+
lineColorSteps, dataSource, margin.left, margin.top
|
|
271
|
+
]);
|
|
272
|
+
// need to keep the function references for use by the subscription, which forms a closure
|
|
273
|
+
// on them. without the references, the closures become stale, and resizing during streaming
|
|
274
|
+
// doesn't work properly
|
|
275
|
+
const updatePlotRef = useRef(updatePlot);
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
updatePlotRef.current = updatePlot;
|
|
278
|
+
}, [updatePlot]);
|
|
279
|
+
// hands the current zoom/pan state (d3-zoom's cumulative `k`, and every axis' current domain) to
|
|
280
|
+
// the `onZoomStateChange` callback
|
|
281
|
+
const onZoomStateChangeRef = useRef(onZoomStateChange);
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
onZoomStateChangeRef.current = onZoomStateChange;
|
|
284
|
+
}, [onZoomStateChange]);
|
|
285
|
+
const reportZoomState = useCallback(() => {
|
|
286
|
+
const ranges = {};
|
|
287
|
+
xAxisRangesRef.current.forEach((range, axisId) => ranges[axisId] = range.current.asTuple());
|
|
288
|
+
yAxisRangesRef.current.forEach((range, axisId) => ranges[axisId] = range.current.asTuple());
|
|
289
|
+
const k = canvasContext !== null ? d3.zoomTransform(canvasContext.canvas).k : 1;
|
|
290
|
+
onZoomStateChangeRef.current({ k, ranges });
|
|
291
|
+
}, [canvasContext]);
|
|
292
|
+
// panning (dragging) and zooming (the mouse wheel) -- see `useCanvasZoomPan`. Fully zooming out
|
|
293
|
+
// (back to a scale of 1) snaps the axes back to their original ranges.
|
|
294
|
+
const { resetZoomTransform, setZoomScale } = useCanvasZoomPan(canvasContext, {
|
|
295
|
+
panEnabled,
|
|
296
|
+
zoomEnabled,
|
|
297
|
+
keyModifiersRequired: zoomKeyModifiersRequired,
|
|
298
|
+
scaleExtent: [zoomMinScaleFactor, zoomMaxScaleFactor],
|
|
299
|
+
translateExtent: [[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]],
|
|
300
|
+
// (tooltips are suppressed while panning and zooming, so that one isn't left behind)
|
|
301
|
+
onPanStart: () => {
|
|
302
|
+
allowTooltip.current = false;
|
|
303
|
+
},
|
|
304
|
+
onPan: (dx, dy) => {
|
|
305
|
+
if (canvasContext === null)
|
|
306
|
+
return;
|
|
307
|
+
onPan(dx, dy, plotDimensions, dataRef.current.map(series => series.name), xAxisRangesRef.current, yAxisRangesRef.current);
|
|
308
|
+
updatePlotRef.current(canvasContext);
|
|
309
|
+
},
|
|
310
|
+
onPanEnd: () => {
|
|
311
|
+
var _a;
|
|
312
|
+
allowTooltip.current = !((_a = dataSource === null || dataSource === void 0 ? void 0 : dataSource.running) !== null && _a !== void 0 ? _a : false);
|
|
313
|
+
reportZoomState();
|
|
314
|
+
},
|
|
315
|
+
onZoom: ({ factor, k, x, y }) => {
|
|
316
|
+
if (canvasContext === null)
|
|
317
|
+
return;
|
|
318
|
+
allowTooltip.current = false;
|
|
319
|
+
if (k === 1) {
|
|
320
|
+
snapToOriginalRanges();
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
onZoom(factor, x - margin.left, y - margin.top, plotDimensions, xAxisRangesRef.current, yAxisRangesRef.current);
|
|
324
|
+
}
|
|
325
|
+
updatePlotRef.current(canvasContext);
|
|
326
|
+
reportZoomState();
|
|
327
|
+
allowTooltip.current = true;
|
|
328
|
+
},
|
|
329
|
+
onZoomReset,
|
|
330
|
+
resetView: () => {
|
|
331
|
+
snapToOriginalRanges();
|
|
332
|
+
if (canvasContext !== null)
|
|
333
|
+
updatePlotRef.current(canvasContext);
|
|
334
|
+
reportZoomState();
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
// the zoom state to restore, captured once at mount (see the `zoomState` prop); cleared once
|
|
338
|
+
// it has been applied (see `restoreZoomState` below), so it's only ever applied once
|
|
339
|
+
const pendingZoomStateRef = useRef(zoomState);
|
|
340
|
+
// applies the pending zoom state (if any) to the axes' ranges and to d3-zoom's transform on the
|
|
341
|
+
// canvas (a remounted plot has a brand-new canvas, whose transform starts at identity). Each
|
|
342
|
+
// range keeps its `.original` -- the axis' real, un-zoomed domain -- since the zoom handler's
|
|
343
|
+
// constraint is measured against it. Waits until the axes' ranges and the canvas exist.
|
|
344
|
+
const restoreZoomState = useEffectEvent(() => {
|
|
345
|
+
const pending = pendingZoomStateRef.current;
|
|
346
|
+
if (pending === undefined || canvasContext === null)
|
|
347
|
+
return;
|
|
348
|
+
if (xAxisRangesRef.current.size === 0 || yAxisRangesRef.current.size === 0)
|
|
349
|
+
return;
|
|
350
|
+
Object.entries(pending.ranges).forEach(([axisId, [start, end]]) => {
|
|
351
|
+
var _a;
|
|
352
|
+
const interval = AxisInterval.from(start, end);
|
|
353
|
+
const ranges = xAxisRangesRef.current.has(axisId) ? xAxisRangesRef.current : yAxisRangesRef.current;
|
|
354
|
+
const range = ranges.get(axisId);
|
|
355
|
+
const axis = (_a = xAxesState.axisFor(axisId).getOrUndefined()) !== null && _a !== void 0 ? _a : yAxesState.axisFor(axisId).getOrUndefined();
|
|
356
|
+
if (range === undefined || axis === undefined)
|
|
357
|
+
return;
|
|
358
|
+
ranges.set(axisId, range.update(start, end));
|
|
359
|
+
setAxisIntervalFor(axisId, interval);
|
|
360
|
+
axis.update(interval, plotDimensions, margin);
|
|
361
|
+
});
|
|
362
|
+
// `__zoom` is where d3-zoom keeps an element's transform; setting it directly (rather than
|
|
363
|
+
// through `zoom.transform`) avoids dispatching a synthetic "zoom" event
|
|
364
|
+
setZoomScale(pending.k);
|
|
365
|
+
pendingZoomStateRef.current = undefined;
|
|
366
|
+
updatePlotRef.current(canvasContext);
|
|
367
|
+
});
|
|
85
368
|
// when the axes are available, then set the reference, but only once
|
|
86
369
|
useEffect(() => {
|
|
87
370
|
if (xAxesState.axes.size > 0 && xAxisRangesRef.current.size === 0) {
|
|
@@ -90,16 +373,22 @@ export function PoincarePlot(props) {
|
|
|
90
373
|
if (yAxesState.axes.size > 0 && yAxisRangesRef.current.size === 0) {
|
|
91
374
|
yAxisRangesRef.current = generateAxisRangeMap(yAxesState.axes);
|
|
92
375
|
}
|
|
93
|
-
|
|
376
|
+
// restore a zoom state handed in from a previous instance (only does anything the first
|
|
377
|
+
// time the axes and canvas are all available -- see `restoreZoomState`)
|
|
378
|
+
restoreZoomState();
|
|
379
|
+
}, [xAxesState, yAxesState, canvasContext]);
|
|
380
|
+
// calculates the distinct axis IDs that cover all the series in the plot
|
|
381
|
+
const xAxesForSeries = useMemo(() => xAxesState.axisIds(), [xAxesState]);
|
|
382
|
+
const yAxesForSeries = useMemo(() => yAxesState.axisIds(), [yAxesState]);
|
|
94
383
|
// update the plot with the new axes bounds
|
|
95
384
|
const updateRangesAndPlot = useCallback(() => {
|
|
96
|
-
if (
|
|
97
|
-
updatePlotRef.current(
|
|
385
|
+
if (canvasContext !== null) {
|
|
386
|
+
updatePlotRef.current(canvasContext);
|
|
98
387
|
}
|
|
99
|
-
}, [
|
|
388
|
+
}, [canvasContext]);
|
|
100
389
|
// todo find better way
|
|
101
390
|
// when the initial data changes, then reset the plot. note that the initial data doesn't change
|
|
102
|
-
// during the normal course of updates from the
|
|
391
|
+
// during the normal course of updates from the data source, only when the data source is replaced.
|
|
103
392
|
useEffect(() => {
|
|
104
393
|
dataRef.current = initialData.slice();
|
|
105
394
|
seriesRef.current = new Map(initialData.map(series => [series.name, series]));
|
|
@@ -107,23 +396,33 @@ export function PoincarePlot(props) {
|
|
|
107
396
|
updateRangesAndPlot();
|
|
108
397
|
}, [initialData, updateRangesAndPlot]);
|
|
109
398
|
/**
|
|
110
|
-
* When the axes bounds have changed
|
|
111
|
-
* the new axis ranges are used
|
|
399
|
+
* When the axes bounds have changed (from outside, e.g. a different iterate function), resets
|
|
400
|
+
* the range references so that the new axis ranges are used -- as the axes' new original
|
|
401
|
+
* (un-zoomed) domains -- resets the zoom, and reports the (reset) zoom state
|
|
112
402
|
* @param updates The updates to the axes
|
|
113
403
|
*/
|
|
114
404
|
const updatedBoundsHandler = useCallback((updates) => {
|
|
405
|
+
// the new bounds become the axes' new un-zoomed (original) domains -- the zoom is reset
|
|
406
|
+
// to identity below, so they're the baseline the zoom constraint and the fully-zoomed-out
|
|
407
|
+
// snap (see `snapToOriginalRanges`) must measure against. The update itself can't be
|
|
408
|
+
// used as-is: `ContinuousAxis` carries the *previous* `.original` forward on a domain
|
|
409
|
+
// change, which would leave the zoom clamped to (and snapping back to) the old domain,
|
|
410
|
+
// e.g. [0, 1] after switching to an iterate function whose range is [-1, 1].
|
|
115
411
|
updates.forEach((update, axisId) => {
|
|
412
|
+
const [start, end] = update.current.asTuple();
|
|
116
413
|
if (xAxisRangesRef.current.has(axisId)) {
|
|
117
|
-
xAxisRangesRef.current.set(axisId,
|
|
414
|
+
xAxisRangesRef.current.set(axisId, ContinuousAxisRange.from(start, end));
|
|
118
415
|
}
|
|
119
416
|
if (yAxisRangesRef.current.has(axisId)) {
|
|
120
|
-
yAxisRangesRef.current.set(axisId,
|
|
417
|
+
yAxisRangesRef.current.set(axisId, ContinuousAxisRange.from(start, end));
|
|
121
418
|
}
|
|
122
419
|
});
|
|
123
|
-
if (zoomEnabled
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
420
|
+
if (zoomEnabled)
|
|
421
|
+
resetZoomTransform();
|
|
422
|
+
// the axes' bounds were changed from outside (e.g. a different iterate function), which
|
|
423
|
+
// resets the zoom, so a previously reported zoom state no longer applies
|
|
424
|
+
reportZoomState();
|
|
425
|
+
}, [zoomEnabled, resetZoomTransform, reportZoomState]);
|
|
127
426
|
// strange construct so that we only add the update handler when the chart ID
|
|
128
427
|
// changes, and not when the addAxesBoundsUpdateHandler or removeAxesBoundsUpdateHandler
|
|
129
428
|
// which they do, and that breaks the updates...someone, please teach me react
|
|
@@ -142,360 +441,220 @@ export function PoincarePlot(props) {
|
|
|
142
441
|
};
|
|
143
442
|
}, [chartId, updatedBoundsHandler]);
|
|
144
443
|
/**
|
|
145
|
-
* Adjusts the
|
|
146
|
-
* @param x The amount that the plot is dragged
|
|
444
|
+
* Adjusts the x- and y-axes' ranges when the plot is dragged (the caller redraws the plot)
|
|
445
|
+
* @param x The amount that the plot is dragged in x
|
|
446
|
+
* @param y The amount that the plot is dragged in y
|
|
147
447
|
* @param plotDimensions The dimensions of the plot
|
|
148
448
|
* @param series An array of series names
|
|
149
|
-
* @param
|
|
449
|
+
* @param xRanges A map holding the x-axis ID and its associated range
|
|
450
|
+
* @param yRanges A map holding the y-axis ID and its associated range
|
|
150
451
|
*/
|
|
151
452
|
const onPan = useCallback((x, y, plotDimensions, series, xRanges, yRanges) => panHandler2D(xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState)(x, y, plotDimensions, series, xRanges, yRanges), [xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState]);
|
|
453
|
+
/**
|
|
454
|
+
* Restores every axis to its original (un-zoomed) domain. Used when d3-zoom's cumulative `k`
|
|
455
|
+
* returns to exactly 1 (fully zoomed out): composing pivot-preserving zooms at *different*
|
|
456
|
+
* pivots isn't position-preserving (zooming in at one spot and back out at another nets a
|
|
457
|
+
* translation, as in any pinch-zoom UI), and the zoom constraint clamps that translated window
|
|
458
|
+
* to the original bounds -- so without this, zooming in at one spot and out at another stops
|
|
459
|
+
* short of the full view on both axes, with `k` already at its maximum. d3-zoom's `scaleExtent`
|
|
460
|
+
* clamps `k` to *exactly* 1 at that limit, so the equality check reliably detects it. (This is
|
|
461
|
+
* the continuous-axis counterpart of `OrdinalAxisRange.scaledCumulative`'s `factor === 1` case.)
|
|
462
|
+
*/
|
|
463
|
+
const snapToOriginalRanges = useCallback(() => {
|
|
464
|
+
const snap = (ranges, axesState) => ranges.forEach((range, axisId) => {
|
|
465
|
+
ranges.set(axisId, range.update(range.original.start, range.original.end));
|
|
466
|
+
setAxisIntervalFor(axisId, range.original);
|
|
467
|
+
axesState.axisFor(axisId).ifPresent(axis => axis.update(range.original, plotDimensions, margin));
|
|
468
|
+
});
|
|
469
|
+
snap(xAxisRangesRef.current, xAxesState);
|
|
470
|
+
snap(yAxisRangesRef.current, yAxesState);
|
|
471
|
+
}, [setAxisIntervalFor, xAxesState, yAxesState, plotDimensions, margin]);
|
|
152
472
|
/**
|
|
153
473
|
* Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
|
|
154
474
|
* at the location of the mouse when the scroll wheel or gesture was applied. Unlike time-series
|
|
155
475
|
* plots, the iterates plot zooms the x- and y-axis at the same rate.
|
|
156
|
-
* @param
|
|
476
|
+
* @param zoomFactor The *incremental* zoom scale factor for this event -- see
|
|
477
|
+
* `useCanvasZoomPan` for how d3-zoom's own cumulative `k` is converted to this incremental
|
|
478
|
+
* form before this is called.
|
|
157
479
|
* @param x The x-position of the mouse when the scroll wheel or gesture is used
|
|
158
480
|
* @param y The y-position of the mouse when the scroll wheel or gesture is used
|
|
159
481
|
* @param plotDimensions The dimensions of the plot
|
|
160
|
-
* @param
|
|
161
|
-
* @param
|
|
162
|
-
*/
|
|
163
|
-
const onZoom = useCallback((transform, x, y, plotDimensions, xRanges, yRanges) => axesZoomHandler(xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState, [zoomMinScaleFactor, zoomMaxScaleFactor])(transform, [x, y], plotDimensions, xRanges, yRanges), [xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState, zoomMinScaleFactor, zoomMaxScaleFactor]);
|
|
164
|
-
const updatePlot = useCallback(
|
|
165
|
-
/**
|
|
166
|
-
* Updates the plot data for the specified time-range, which may have changed due to zoom or pan
|
|
167
|
-
* @param mainGElem The main <g> element selection for that holds the plot
|
|
482
|
+
* @param xRanges A map holding the x-axis ID and its associated range
|
|
483
|
+
* @param yRanges A map holding the y-axis ID and its associated range
|
|
168
484
|
*/
|
|
169
|
-
(
|
|
170
|
-
if (container) {
|
|
171
|
-
onUpdateChartTime(currentTimeRef.current);
|
|
172
|
-
// select the svg element bind the data to them
|
|
173
|
-
const svg = d3.select(container);
|
|
174
|
-
// create a map associating series-names to their time-series.
|
|
175
|
-
//
|
|
176
|
-
// performance-related confusion: wondering where the dataRef is updated? well it isn't
|
|
177
|
-
// directly. The dataRef holds on to an array of references to the Series. And so does the
|
|
178
|
-
// seriesRef, though it uses a map(series_name -> series). The seriesRef is used to append
|
|
179
|
-
// data to the underlying Series, and the dataRef is used so that we can just use
|
|
180
|
-
// dataRef.current and don't have to do Array.from(seriesRef.current.values()) which
|
|
181
|
-
// creates a temporary array
|
|
182
|
-
// const offset = 1
|
|
183
|
-
const boundedSeries = new Map(dataRef.current.map(series => {
|
|
184
|
-
return [
|
|
185
|
-
series.name,
|
|
186
|
-
series.data
|
|
187
|
-
.filter(datum => !isNaN(datum.iterateN))
|
|
188
|
-
.map((datum, index) => ({
|
|
189
|
-
n: datum.iterateN,
|
|
190
|
-
n_1: datum.iterateN_1,
|
|
191
|
-
time: datum.time,
|
|
192
|
-
index: index
|
|
193
|
-
}))
|
|
194
|
-
];
|
|
195
|
-
}));
|
|
196
|
-
// set up panning
|
|
197
|
-
if (panEnabled) {
|
|
198
|
-
const drag = d3.drag()
|
|
199
|
-
.on("start", () => {
|
|
200
|
-
d3.select(container).style("cursor", "move");
|
|
201
|
-
// during panning, we need to disable viewing the tooltip to prevent
|
|
202
|
-
// tooltips from rendering but not getting removed
|
|
203
|
-
allowTooltip.current = false;
|
|
204
|
-
})
|
|
205
|
-
.on("drag", event => {
|
|
206
|
-
onPan(event.dx, event.dy, plotDimensions, Array.from(boundedSeries.keys()), xAxisRangesRef.current, yAxisRangesRef.current);
|
|
207
|
-
updatePlotRef.current(mainGElem);
|
|
208
|
-
})
|
|
209
|
-
.on("end", () => {
|
|
210
|
-
d3.select(container).style("cursor", "auto");
|
|
211
|
-
// during panning, we disabled viewing the tooltip to prevent
|
|
212
|
-
// tooltips from rendering but not getting removed, now that panning
|
|
213
|
-
// is over, allow tooltips to render again
|
|
214
|
-
allowTooltip.current = isSubscriptionClosed();
|
|
215
|
-
});
|
|
216
|
-
svg.call(drag);
|
|
217
|
-
}
|
|
218
|
-
// set up for zooming
|
|
219
|
-
if (zoomEnabled) {
|
|
220
|
-
zoomRef.current = d3.zoom()
|
|
221
|
-
.filter(event => !zoomKeyModifiersRequired || event.shiftKey || event.ctrlKey)
|
|
222
|
-
.scaleExtent([zoomMinScaleFactor, zoomMaxScaleFactor])
|
|
223
|
-
.translateExtent([[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]])
|
|
224
|
-
.on("zoom", event => {
|
|
225
|
-
allowTooltip.current = false;
|
|
226
|
-
if (event.sourceEvent !== null) {
|
|
227
|
-
onZoom(event.transform, event.sourceEvent.offsetX - margin.left, event.sourceEvent.offsetY - margin.top, plotDimensions, xAxisRangesRef.current, yAxisRangesRef.current);
|
|
228
|
-
updatePlotRef.current(mainGElem);
|
|
229
|
-
}
|
|
230
|
-
allowTooltip.current = true;
|
|
231
|
-
});
|
|
232
|
-
zoomSelectionRef.current = svg.call(zoomRef.current);
|
|
233
|
-
}
|
|
234
|
-
// define the clip-path so that the series lines don't go beyond the plot area
|
|
235
|
-
const clipPathId = setClipPathG(chartId, mainGElem, plotDimensions);
|
|
236
|
-
// ---
|
|
237
|
-
// todo only want to do this once, on the first plot, and then leave it,
|
|
238
|
-
// unless the axes are updated, also needs to be removed/added when the
|
|
239
|
-
// plot size changes
|
|
240
|
-
const xAxis = xAxesState.defaultAxis().getOrThrow(() => new Error('No default axis found'));
|
|
241
|
-
const yAxis = yAxesState.defaultAxis().getOrThrow(() => new Error('No default axis found'));
|
|
242
|
-
const lineGenerator = d3.line()
|
|
243
|
-
.x(d => xAxis.scale(d[0] || 0))
|
|
244
|
-
.y(d => yAxis.scale(d[1] || 0));
|
|
245
|
-
// ---
|
|
246
|
-
const [xStart, xEnd] = xAxesState.axisDefaultId()
|
|
247
|
-
.map(id => { var _a; return ((_a = xAxisRangesRef.current.get(id)) === null || _a === void 0 ? void 0 : _a.original.asTuple()) || [0, 0]; })
|
|
248
|
-
.getOrElse([0, 0]);
|
|
249
|
-
const [yStart, yEnd] = yAxesState.axisDefaultId()
|
|
250
|
-
.map(id => { var _a; return ((_a = yAxisRangesRef.current.get(id)) === null || _a === void 0 ? void 0 : _a.original.asTuple()) || [0, 0]; })
|
|
251
|
-
.getOrElse([0, 0]);
|
|
252
|
-
// const [xStart, xEnd] = xAxisRangesRef.current.get(xAxesState.axisDefaultId())?.original.asTuple() || [0, 0]
|
|
253
|
-
// const [yStart, yEnd] = yAxisRangesRef.current.get(yAxesState.axisDefaultId())?.original.asTuple() || [0, 0]
|
|
254
|
-
mainGElem
|
|
255
|
-
.selectAll(`#fn-equals-fn1-${chartId}-poincare`)
|
|
256
|
-
.data([[[xStart, yStart], [xEnd, yEnd]]])
|
|
257
|
-
.join(enter => enter
|
|
258
|
-
.select("path")
|
|
259
|
-
.style("stroke", "grey")
|
|
260
|
-
.style("fill", "none")
|
|
261
|
-
.style("stroke-width", "1px")
|
|
262
|
-
.attr("class", `fn-equals-fn1-poincare`)
|
|
263
|
-
.attr("id", `#fn-equals-fn1-${chartId}-poincare`)
|
|
264
|
-
.attr('transform', `translate(${margin.left}, ${margin.top})`)
|
|
265
|
-
.attr("d", lineGenerator), update => update.remove(), exit => exit.remove())
|
|
266
|
-
// clear out mouse-enter callbacks for the diagonal line
|
|
267
|
-
.on("mouseenter", () => {
|
|
268
|
-
})
|
|
269
|
-
.on("mouseleave", () => {
|
|
270
|
-
});
|
|
271
|
-
boundedSeries.forEach((data, name) => {
|
|
272
|
-
// grab the x and y axes assigned to the series, and if either or both
|
|
273
|
-
// axes aren't found, then give up and return
|
|
274
|
-
const [xAxisLinear, yAxisLinear] = axesFor(axisId => xAxesState.axisFor(axisId).getOrUndefined(), axisId => yAxesState.axisFor(axisId).getOrUndefined());
|
|
275
|
-
if (xAxisLinear === undefined || yAxisLinear === undefined)
|
|
276
|
-
return;
|
|
277
|
-
// grab the style for the series
|
|
278
|
-
const seriesLineStyle = seriesStyles.get(name) || Object.assign(Object.assign({}, defaultLineStyle()), { highlightColor: defaultLineStyle().color });
|
|
279
|
-
// only show the data for which the filter matches
|
|
280
|
-
const plotData = (name.match(seriesFilter)) ? data : [];
|
|
281
|
-
const pathGenerator = d3.line()
|
|
282
|
-
.x(d => xAxis.scale(d.n || 0))
|
|
283
|
-
.y(d => yAxis.scale(d.n_1 || 0));
|
|
284
|
-
if (interpolation === undefined) {
|
|
285
|
-
mainGElem
|
|
286
|
-
.selectAll(`#${name}-${chartId}-poincare`)
|
|
287
|
-
.remove();
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
// create the time-series paths
|
|
291
|
-
mainGElem
|
|
292
|
-
.selectAll(`#${name}-${chartId}-poincare`)
|
|
293
|
-
.data([[], plotData], () => `${name}`)
|
|
294
|
-
.join(enter => enter
|
|
295
|
-
.append("path")
|
|
296
|
-
.attr("class", 'iterate-series-lines')
|
|
297
|
-
.attr("id", `${name}-${chartId}-poincare`)
|
|
298
|
-
.attr("d", pathGenerator.curve(interpolation))
|
|
299
|
-
.style("fill", "none")
|
|
300
|
-
.style("stroke", seriesLineStyle.color)
|
|
301
|
-
.style("stroke-width", seriesLineStyle.lineWidth)
|
|
302
|
-
.attr('transform', `translate(${margin.left}, ${margin.top})`)
|
|
303
|
-
.attr("clip-path", `url(#${clipPathId})`), update => update, exit => exit.remove());
|
|
304
|
-
}
|
|
305
|
-
// when specified, show a circle for the actual data point
|
|
306
|
-
if (showPoints) {
|
|
307
|
-
mainGElem
|
|
308
|
-
.selectAll(`.${name}-${chartId}-poincare-points`)
|
|
309
|
-
.data(plotData, () => `${name}`)
|
|
310
|
-
.join(enter => enter
|
|
311
|
-
.append("circle")
|
|
312
|
-
.attr("class", `${name}-${chartId}-poincare-points`)
|
|
313
|
-
.attr("id", (_, index) => `${name}-${chartId}-poincare-point-${index}`)
|
|
314
|
-
.attr("fill", seriesLineStyle.color)
|
|
315
|
-
.attr("stroke", "none")
|
|
316
|
-
.attr("cx", (d) => xAxisLinear.scale(d.n) || 0)
|
|
317
|
-
.attr("cy", (d) => yAxisLinear.scale(d.n_1) || 0)
|
|
318
|
-
.attr("r", 2)
|
|
319
|
-
.attr('transform', `translate(${margin.left}, ${margin.top})`)
|
|
320
|
-
.attr("clip-path", `url(#${clipPathId})`), update => update
|
|
321
|
-
.attr("cx", (d) => xAxisLinear.scale(d.n) || 0)
|
|
322
|
-
.attr("cy", (d) => yAxisLinear.scale(d.n_1) || 0), exit => exit.remove())
|
|
323
|
-
.on("mouseenter", (event, datum) => {
|
|
324
|
-
if (allowTooltip.current && tooltipVisible) {
|
|
325
|
-
return handleMouseEnterPoint(chartId, name, container, event, datum, plotData, xAxisLinear, yAxisLinear, margin, seriesLineStyle, backgroundColor, allowTooltip.current, mouseOverHandlerFor(`tooltip-${chartId}`));
|
|
326
|
-
}
|
|
327
|
-
return _jsx(_Fragment, {});
|
|
328
|
-
})
|
|
329
|
-
.on("mouseleave", () => {
|
|
330
|
-
handleMouseLeavePoint(chartId, name, seriesLineStyle.color, mouseLeaveHandlerFor(`tooltip-${chartId}`));
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
}, [
|
|
336
|
-
container, onUpdateChartTime, panEnabled, zoomEnabled, chartId, plotDimensions, margin,
|
|
337
|
-
xAxesState, yAxesState,
|
|
338
|
-
onPan,
|
|
339
|
-
zoomMinScaleFactor, zoomMaxScaleFactor, zoomKeyModifiersRequired, onZoom,
|
|
340
|
-
seriesStyles, seriesFilter, showPoints,
|
|
341
|
-
interpolation, backgroundColor,
|
|
342
|
-
mouseOverHandlerFor, mouseLeaveHandlerFor,
|
|
343
|
-
tooltipVisible,
|
|
344
|
-
]);
|
|
345
|
-
// need to keep the function references for use by the subscription, which forms a closure
|
|
346
|
-
// on them. without the references, the closures become stale, and resizing during streaming
|
|
347
|
-
// doesn't work properly
|
|
348
|
-
const updatePlotRef = useRef(updatePlot);
|
|
349
|
-
useEffect(() => {
|
|
350
|
-
updatePlotRef.current = updatePlot;
|
|
351
|
-
}, [updatePlot]);
|
|
485
|
+
const onZoom = useCallback((zoomFactor, x, y, plotDimensions, xRanges, yRanges) => axesZoomHandler(xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState, [zoomMinScaleFactor, zoomMaxScaleFactor])(zoomFactor, [x, y], plotDimensions, xRanges, yRanges), [xAxesForSeries, yAxesForSeries, margin, setAxisIntervalFor, xAxesState, yAxesState, zoomMinScaleFactor, zoomMaxScaleFactor]);
|
|
352
486
|
const onUpdateAxesBoundsRef = useRef(updateAxisRanges);
|
|
353
487
|
useEffect(() => {
|
|
354
488
|
onUpdateAxesBoundsRef.current = updateAxisRanges;
|
|
355
489
|
}, [updateAxisRanges]);
|
|
356
|
-
// memoized function for subscribing to the chart-data observable
|
|
357
|
-
const subscribe = useCallback(() => {
|
|
358
|
-
if (seriesObservable === undefined || mainG === null)
|
|
359
|
-
return undefined;
|
|
360
|
-
return subscriptionIteratesFor(seriesObservable, onSubscribe, windowingTime, xAxesState, yAxesState, onUpdateData, dropDataAfter, updateRangesAndPlot,
|
|
361
|
-
// as new data flows into the subscription, the subscription
|
|
362
|
-
// updates this map directly (for performance)
|
|
363
|
-
seriesRef.current, end => currentTimeRef.current = end);
|
|
364
|
-
}, [
|
|
365
|
-
dropDataAfter, mainG,
|
|
366
|
-
onSubscribe, onUpdateData,
|
|
367
|
-
seriesObservable, updateRangesAndPlot, windowingTime,
|
|
368
|
-
xAxesState, yAxesState,
|
|
369
|
-
]);
|
|
370
490
|
// updates the plot when the interpolation and filter change, because the updatePlot
|
|
371
491
|
// callback has changed.
|
|
372
492
|
useEffect(() => {
|
|
373
|
-
if (
|
|
374
|
-
updatePlot(
|
|
493
|
+
if (canvasContext) {
|
|
494
|
+
updatePlot(canvasContext);
|
|
375
495
|
}
|
|
376
|
-
}, [axisRangeFor,
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
496
|
+
}, [axisRangeFor, canvasContext, updatePlot]);
|
|
497
|
+
// wires up a single mousemove/mouseleave listener on the shared canvas to replace the old
|
|
498
|
+
// per-element SVG mouseenter/mouseleave handlers on the point circles. Hit-tests the mouse
|
|
499
|
+
// position against the last-drawn point geometry (see `geometryRef`, populated by the draw
|
|
500
|
+
// function above).
|
|
381
501
|
useEffect(() => {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
502
|
+
if (!canvasContext)
|
|
503
|
+
return;
|
|
504
|
+
const canvas = canvasContext.canvas;
|
|
505
|
+
// highlights (or un-highlights) the shared x- and y-axes -- keyed on the series name
|
|
506
|
+
// alone (not the specific hovered point index), so moving the mouse between a
|
|
507
|
+
// series' own points doesn't flicker the highlight off and on
|
|
508
|
+
const highlightAxesFor = (seriesName, isHighlighted) => {
|
|
509
|
+
if (!highlightAxesOnMouseOver)
|
|
510
|
+
return;
|
|
511
|
+
const [xAxis, yAxis] = axesFor(axisId => xAxesState.axisFor(axisId).getOrUndefined(), axisId => yAxesState.axisFor(axisId).getOrUndefined());
|
|
512
|
+
const { highlightColor, highlightWidth } = seriesStyles.get(seriesName) || defaultLineStyle();
|
|
513
|
+
xAxis === null || xAxis === void 0 ? void 0 : xAxis.setHighlighted(isHighlighted, highlightColor, highlightWidth);
|
|
514
|
+
yAxis === null || yAxis === void 0 ? void 0 : yAxis.setHighlighted(isHighlighted, highlightColor, highlightWidth);
|
|
515
|
+
};
|
|
516
|
+
const handleMove = (event) => {
|
|
517
|
+
if (!allowTooltip.current || !tooltipVisible)
|
|
518
|
+
return;
|
|
519
|
+
const [x, y] = canvasLocalPoint(event, canvas);
|
|
520
|
+
const hit = seriesAt(x, y, geometryRef.current);
|
|
521
|
+
const hitSeriesName = hit === null || hit === void 0 ? void 0 : hit.name.replace(/::points$/, '');
|
|
522
|
+
const previous = hoveredPointRef.current;
|
|
523
|
+
if ((previous === null || previous === void 0 ? void 0 : previous.seriesName) !== hitSeriesName) {
|
|
524
|
+
if (previous !== undefined)
|
|
525
|
+
highlightAxesFor(previous.seriesName, false);
|
|
526
|
+
if (hitSeriesName !== undefined)
|
|
527
|
+
highlightAxesFor(hitSeriesName, true);
|
|
528
|
+
}
|
|
529
|
+
const sameAsBefore = previous !== undefined && hit !== undefined &&
|
|
530
|
+
previous.seriesName === hitSeriesName && previous.index === hit.index;
|
|
531
|
+
if (sameAsBefore)
|
|
532
|
+
return;
|
|
533
|
+
if (previous !== undefined) {
|
|
534
|
+
handleMouseLeavePoint(previous.seriesName, mouseLeaveHandlerFor(`tooltip-${chartId}`));
|
|
535
|
+
}
|
|
536
|
+
if (hit !== undefined && hitSeriesName !== undefined) {
|
|
537
|
+
const plotData = plotDataRef.current.get(hitSeriesName);
|
|
538
|
+
if (plotData !== undefined) {
|
|
539
|
+
handleMouseEnterPoint(hitSeriesName, plotData[hit.index], plotData, [x, y], mouseOverHandlerFor(`tooltip-${chartId}`));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
hoveredPointRef.current = hit !== undefined && hitSeriesName !== undefined ?
|
|
543
|
+
{ seriesName: hitSeriesName, index: hit.index } :
|
|
544
|
+
undefined;
|
|
545
|
+
// hover state affects the highlight/label drawing, so request a redraw
|
|
546
|
+
canvasContext.requestRedraw();
|
|
547
|
+
};
|
|
548
|
+
const handleLeaveCanvas = () => {
|
|
549
|
+
const previous = hoveredPointRef.current;
|
|
550
|
+
if (previous !== undefined) {
|
|
551
|
+
handleMouseLeavePoint(previous.seriesName, mouseLeaveHandlerFor(`tooltip-${chartId}`));
|
|
552
|
+
highlightAxesFor(previous.seriesName, false);
|
|
553
|
+
hoveredPointRef.current = undefined;
|
|
554
|
+
canvasContext.requestRedraw();
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
canvas.addEventListener('mousemove', handleMove);
|
|
558
|
+
canvas.addEventListener('mouseleave', handleLeaveCanvas);
|
|
559
|
+
return () => {
|
|
560
|
+
canvas.removeEventListener('mousemove', handleMove);
|
|
561
|
+
canvas.removeEventListener('mouseleave', handleLeaveCanvas);
|
|
562
|
+
};
|
|
563
|
+
}, [
|
|
564
|
+
canvasContext, chartId, mouseOverHandlerFor, mouseLeaveHandlerFor, tooltipVisible,
|
|
565
|
+
highlightAxesOnMouseOver, xAxesState, yAxesState, seriesStyles
|
|
566
|
+
]);
|
|
567
|
+
// the latest `updateRangesAndPlot`, for the view driver to call -- the driver is created once
|
|
568
|
+
// per run (see below), so it must not capture a stale one (e.g. bound to an older canvas)
|
|
569
|
+
const updateRangesAndPlotRef = useRef(updateRangesAndPlot);
|
|
570
|
+
useEffect(() => {
|
|
571
|
+
updateRangesAndPlotRef.current = updateRangesAndPlot;
|
|
572
|
+
}, [updateRangesAndPlot]);
|
|
573
|
+
// creates the view driver that keeps this plot's view in step with the data source, using the
|
|
574
|
+
// current settings -- see `useStreamingTimeAxes`'s identical effect event
|
|
575
|
+
const createViewDriver = useEffectEvent((source) => iteratesViewDriverFor(source, windowingTime, xAxesState, yAxesState, () => updateRangesAndPlotRef.current(), end => currentTimeRef.current = end));
|
|
576
|
+
// while the data source is running (and this plot has a canvas), drive the view from the
|
|
577
|
+
// data source's updates -- see `useStreamingTimeAxes`'s identical effect. The driver belongs to this plot
|
|
578
|
+
// instance alone and is always torn down with it.
|
|
579
|
+
useEffect(() => {
|
|
580
|
+
if (dataSource === undefined || !running || canvasContext === null)
|
|
581
|
+
return;
|
|
582
|
+
allowTooltip.current = false;
|
|
583
|
+
const driver = createViewDriver(dataSource);
|
|
584
|
+
return () => {
|
|
585
|
+
driver.unsubscribe();
|
|
390
586
|
allowTooltip.current = true;
|
|
587
|
+
};
|
|
588
|
+
}, [dataSource, running, canvasContext]);
|
|
589
|
+
// this plot needs a data source (see the `Chart`'s `dataSource` prop)
|
|
590
|
+
useEffect(() => {
|
|
591
|
+
if (dataSource === undefined) {
|
|
592
|
+
console.error("PoincarePlot requires a data source; set the enclosing Chart's `dataSource` prop");
|
|
391
593
|
}
|
|
392
|
-
}, [
|
|
594
|
+
}, [dataSource]);
|
|
595
|
+
// unregister this plot's draw function on unmount
|
|
596
|
+
useEffect(() => {
|
|
597
|
+
return () => {
|
|
598
|
+
if (canvasContext) {
|
|
599
|
+
canvasContext.unregister(`poincare-plot-${chartId}`);
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
}, [canvasContext, chartId]);
|
|
393
603
|
return null;
|
|
394
604
|
}
|
|
395
605
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
606
|
+
* @param dataSource The chart's data source
|
|
607
|
+
* @param seriesName The name of the series
|
|
608
|
+
* @return The number of points dropped from the series so far, when the data source counts them
|
|
609
|
+
* (an {@link IteratesDataSource} does); otherwise 0
|
|
610
|
+
*/
|
|
611
|
+
function droppedCountFor(dataSource, seriesName) {
|
|
612
|
+
return dataSource !== null && typeof dataSource === 'object' && 'droppedCountFor' in dataSource ?
|
|
613
|
+
dataSource.droppedCountFor(seriesName) :
|
|
614
|
+
0;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Locates the plot's x- and y-axes: always the default (first) ones, since every series of a
|
|
618
|
+
* Poincaré plot shares the same axes
|
|
398
619
|
* @param xAxisFor The function that accepts an axis ID and returns the corresponding x-axis
|
|
399
620
|
* @param yAxisFor The function that accepts an axis ID and returns the corresponding y-axis
|
|
621
|
+
* @return The x- and y-axes (either is actually `undefined` when it isn't found, despite the type)
|
|
622
|
+
* @throws Error when either axis isn't a continuous numeric axis
|
|
400
623
|
*/
|
|
401
624
|
function axesFor(xAxisFor, yAxisFor) {
|
|
402
625
|
const xAxis = xAxisFor("");
|
|
403
|
-
const xAxisLinear = xAxis;
|
|
404
626
|
const yAxis = yAxisFor("");
|
|
627
|
+
checkAxisType("PoincarePlot", "x", xAxis, AxisType.ContinuousNumeric);
|
|
628
|
+
checkAxisType("PoincarePlot", "y", yAxis, AxisType.ContinuousNumeric);
|
|
629
|
+
const xAxisLinear = xAxis;
|
|
405
630
|
const yAxisLinear = yAxis;
|
|
406
|
-
if (xAxis && !xAxisLinear) {
|
|
407
|
-
throw Error("Poincare plot requires that x-axis be of type LinearAxis");
|
|
408
|
-
}
|
|
409
|
-
if (yAxis && !yAxisLinear) {
|
|
410
|
-
throw Error("Poincare plot requires that y-axis be of type LinearAxis");
|
|
411
|
-
}
|
|
412
631
|
return [xAxisLinear, yAxisLinear];
|
|
413
632
|
}
|
|
414
633
|
/**
|
|
415
|
-
*
|
|
634
|
+
* Reports the tooltip for the hovered point. Replaces the old version, which also directly
|
|
635
|
+
* mutated the hovered SVG `<circle>`'s radius/fill (and its neighbors') as a side effect; that
|
|
636
|
+
* highlighting is now handled entirely by the draw function reading `hoveredPointRef` (see
|
|
637
|
+
* `updatePlot`'s `draw`) rather than by touching elements here.
|
|
416
638
|
* @param seriesName The name of the series (i.e. the neuron ID)
|
|
417
|
-
* @param
|
|
418
|
-
* @param event The mouse event that triggered this call
|
|
419
|
-
* @param datum The datum over which the mouse has entered
|
|
639
|
+
* @param datum The hovered point, or `undefined` if the index doesn't resolve to a point
|
|
420
640
|
* @param plotData The iterates series
|
|
421
|
-
* @param
|
|
422
|
-
* @param yAxisLinear The y-axis (f[n+1](x))
|
|
423
|
-
* @param margin The plot margin
|
|
424
|
-
* @param seriesStyle The series style information (needed for (un)highlighting)
|
|
425
|
-
* @param backgroundColor
|
|
426
|
-
* @param allowTooltip When set to `false` won't show tooltip, even if it is visible (used by pan)
|
|
641
|
+
* @param mouseCoords The `[x, y]` position of the mouse, in canvas coordinates
|
|
427
642
|
* @param mouseOverHandlerFor The handler for the mouse-over (registered by the <Tooltip/>)
|
|
428
643
|
*/
|
|
429
|
-
function handleMouseEnterPoint(
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const circle = event.currentTarget;
|
|
435
|
-
d3.select(circle)
|
|
436
|
-
.attr("r", circleRadius)
|
|
437
|
-
.style("fill", highlightColor);
|
|
438
|
-
const index = plotData
|
|
439
|
-
.findIndex(point => point.n === datum.n && point.n_1 === datum.n_1);
|
|
440
|
-
/**
|
|
441
|
-
* Displays basic information about an iterate, generally its neighbors
|
|
442
|
-
* @param index The index of the iterate
|
|
443
|
-
*/
|
|
444
|
-
function showInfo(index) {
|
|
445
|
-
d3.select(`#${seriesName}-${chartId}-poincare-point-${index}`)
|
|
446
|
-
.attr("r", circleRadius)
|
|
447
|
-
.style("fill", d3.rgb(highlightColor).brighter(0.7).toString())
|
|
448
|
-
.style("stroke-width", circleStroke)
|
|
449
|
-
.style("stroke", color);
|
|
450
|
-
// grab the (x, y)-coordinates for the plot
|
|
451
|
-
const iterateN = xAxisLinear.scale(plotData[index].n) + margin.left;
|
|
452
|
-
const iterateN_1 = yAxisLinear.scale(plotData[index].n_1) + margin.top;
|
|
453
|
-
const svg = d3.select(container);
|
|
454
|
-
// add a rectangle that serves as the background for the text (to make the
|
|
455
|
-
// text readable when the chart is busy)
|
|
456
|
-
const rect = svg
|
|
457
|
-
.append("rect")
|
|
458
|
-
.attr("class", `${seriesName}-${chartId}-poincare-point-text-background`);
|
|
459
|
-
// add the information about the iterate as a text element
|
|
460
|
-
const textElement = svg
|
|
461
|
-
.append("text")
|
|
462
|
-
.attr('class', `${seriesName}-${chartId}-poincare-point-text`)
|
|
463
|
-
.attr('fill', highlightColor)
|
|
464
|
-
.attr('font-family', 'sans-serif')
|
|
465
|
-
.attr('font-size', 11)
|
|
466
|
-
.attr('font-weight', 700)
|
|
467
|
-
.text(`n = ${index}; t = ${formatTime(plotData[index].time)} ms`);
|
|
468
|
-
// calculate the width and height of the text element
|
|
469
|
-
const { width, height } = textDimensions(textElement);
|
|
470
|
-
textElement
|
|
471
|
-
.attr("transform", `translate(${iterateN - 8}, ${iterateN_1 - circleRadius - circleStroke - padding})`);
|
|
472
|
-
rect
|
|
473
|
-
.attr("x", iterateN - padding / 2 - 8)
|
|
474
|
-
.attr("y", iterateN_1 - padding / 2 - circleRadius - circleStroke - height)
|
|
475
|
-
.attr("width", width + padding)
|
|
476
|
-
.attr("height", height + padding / 2)
|
|
477
|
-
.style("fill", backgroundColor);
|
|
478
|
-
}
|
|
479
|
-
if (index > 0) {
|
|
480
|
-
showInfo(index - 1);
|
|
481
|
-
}
|
|
482
|
-
if (index < plotData.length - 1) {
|
|
483
|
-
showInfo(index + 1);
|
|
484
|
-
}
|
|
485
|
-
const [x, y] = d3.pointer(event, container);
|
|
486
|
-
const currentDatum = (index >= 0) ? plotData[index] : { time: 0, n: 0, n_1: 0, index };
|
|
487
|
-
if (mouseOverHandlerFor && allowTooltip) {
|
|
488
|
-
mouseOverHandlerFor(seriesName, currentDatum.time, { series: plotData.map(ip => ({ iterateN: ip.n, iterateN_1: ip.n_1, time: ip.time })), metadata: {} }, [x, y]);
|
|
644
|
+
function handleMouseEnterPoint(seriesName, datum, plotData, mouseCoords, mouseOverHandlerFor) {
|
|
645
|
+
if (datum === undefined)
|
|
646
|
+
return;
|
|
647
|
+
if (mouseOverHandlerFor) {
|
|
648
|
+
mouseOverHandlerFor(seriesName, datum.time, { series: plotData.map(ip => ({ iterateN: ip.n, iterateN_1: ip.n_1, time: ip.time })), metadata: {} }, mouseCoords);
|
|
489
649
|
}
|
|
490
650
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
d3.selectAll(`.${seriesName}-${chartId}-poincare-point-text-background`).remove();
|
|
651
|
+
/**
|
|
652
|
+
* Calls the mouse-leave-series handler registered for this series (the highlight is cleared by
|
|
653
|
+
* the draw function, once the hovered point is cleared)
|
|
654
|
+
* @param seriesName The name of the series
|
|
655
|
+
* @param mouseLeaverHandlerFor Registered handler for the series when the mouse leaves
|
|
656
|
+
*/
|
|
657
|
+
function handleMouseLeavePoint(seriesName, mouseLeaverHandlerFor) {
|
|
499
658
|
if (mouseLeaverHandlerFor) {
|
|
500
659
|
mouseLeaverHandlerFor(seriesName);
|
|
501
660
|
}
|