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,46 +1,68 @@
|
|
|
1
|
-
import {AxesAssignment,
|
|
1
|
+
import {type AxesAssignment, clipToArea} from "./plot";
|
|
2
|
+
import {useCanvasZoomPan} from "./useCanvasZoomPan";
|
|
2
3
|
import * as d3 from "d3";
|
|
3
|
-
import {ZoomTransform} from "d3";
|
|
4
4
|
import {noop} from "../utils";
|
|
5
5
|
import {useChart} from "../hooks/useChart";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import {useCallback, useEffect, useEffectEvent, useMemo, useRef} from "react";
|
|
7
|
+
import type {CanvasContext} from "../d3types";
|
|
8
|
+
import {seriesAt, canvasLocalPoint, type SeriesGeometry} from "./hitTesting";
|
|
9
9
|
import {
|
|
10
10
|
axesForSeriesGen,
|
|
11
|
-
BaseAxis,
|
|
12
|
-
ContinuousNumericAxis,
|
|
11
|
+
type BaseAxis,
|
|
12
|
+
type ContinuousNumericAxis,
|
|
13
|
+
AxisType,
|
|
14
|
+
checkAxisType,
|
|
13
15
|
ordinalAxisIntervals,
|
|
14
16
|
ordinalAxisRanges,
|
|
15
17
|
ordinalAxisZoomHandler,
|
|
16
18
|
ordinalPanHandler,
|
|
17
|
-
OrdinalStringAxis
|
|
19
|
+
type OrdinalStringAxis
|
|
18
20
|
} from "../axes/axes";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import type {Dimensions, Margin} from "../styling/margins";
|
|
22
|
+
import {ordinalViewDriverFor} from "../subscriptions/viewDrivers";
|
|
23
|
+
import {useDataSource, useDataSourceRunning, useInitialData, useStreamingSettings} from "../hooks/useDataSource";
|
|
24
|
+
import {initialOrdinalStats, type WindowedOrdinalStats} from "../datasources/ordinalDataSource";
|
|
23
25
|
import {usePlotDimensions} from "../hooks/usePlotDimensions";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
STROKE_COLOR,
|
|
32
|
-
STROKE_OPACITY,
|
|
33
|
-
STROKE_WIDTH,
|
|
34
|
-
SvgFillStyle,
|
|
35
|
-
SvgStrokeStyle
|
|
36
|
-
} from "../styling/svgStyle";
|
|
37
|
-
import {BarSeriesStyle, BarStyle, defaultBarSeriesStyle, LineStyle} from "../styling/barPlotStyle";
|
|
38
|
-
import {TooltipData} from "../hooks/useTooltip";
|
|
26
|
+
import {type OrdinalChartData} from "../observables/ordinals";
|
|
27
|
+
import type {BaseSeries} from "../series/baseSeries";
|
|
28
|
+
import {type OrdinalDatum} from "../series/ordinalSeries";
|
|
29
|
+
import {applyFillStyle, applyStrokeStyle} from "../styling/canvasStyle";
|
|
30
|
+
import type {SvgFillStyle, SvgStrokeStyle} from "../styling/svgStyle";
|
|
31
|
+
import {type BarSeriesStyle, type BarStyle, defaultBarSeriesStyle} from "../styling/barPlotStyle";
|
|
32
|
+
import type {TooltipData} from "../hooks/useTooltip";
|
|
39
33
|
import {OrdinalAxisRange} from "../axes/OrdinalAxisRange";
|
|
40
|
-
import {AxisInterval} from "../axes/AxisInterval";
|
|
41
34
|
import {Optional} from "result-fn";
|
|
35
|
+
import {BAR_CHART_TOOLTIP_PROVIDER_IDS} from "./constants";
|
|
36
|
+
import {AxisInterval} from "../axes/AxisInterval";
|
|
37
|
+
import {extentOf, extentWith} from "../axes/autoScale";
|
|
38
|
+
import {type AutoScaleAxes, growAutoScaledYAxes, useAutoScaleRestart} from "./autoScaleYAxes";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A snapshot of the bar plot's zoom/pan state, in a form that can be held outside the plot (e.g.
|
|
42
|
+
* in an application store) and handed back to a later instance of the plot (see the
|
|
43
|
+
* {@link Props.zoomState} and {@link Props.onZoomStateChange} props) -- so that zooming, navigating
|
|
44
|
+
* away, and navigating back doesn't reset the zoom.
|
|
45
|
+
*/
|
|
46
|
+
export interface BarPlotZoomState {
|
|
47
|
+
/**
|
|
48
|
+
* d3-zoom's cumulative scale factor (`transform.k`) at the time of the snapshot. The ordinal
|
|
49
|
+
* zoom math depends on this matching the axis ranges (see `OrdinalAxisRange.scaledCumulative`),
|
|
50
|
+
* so it must be restored onto the new canvas along with the ranges.
|
|
51
|
+
*/
|
|
52
|
+
k: number
|
|
53
|
+
/**
|
|
54
|
+
* Maps each ordinal x-axis ID to its current (zoomed/panned) pixel range, expressed as
|
|
55
|
+
* fractions of the axis' original (un-zoomed) pixel extent, so that the snapshot remains valid
|
|
56
|
+
* if the plot is a different width when it is restored.
|
|
57
|
+
*/
|
|
58
|
+
ranges: Record<string, [startFraction: number, endFraction: number]>
|
|
59
|
+
}
|
|
42
60
|
|
|
43
61
|
// typescript doesn't support enums with computed string values, even though they are all constants...
|
|
62
|
+
/**
|
|
63
|
+
* An ID (e.g. a tooltip provider ID) for each of the five kinds of element the bar plot draws for
|
|
64
|
+
* each series
|
|
65
|
+
*/
|
|
44
66
|
export type BarChartElementId = {
|
|
45
67
|
readonly currentValue: string
|
|
46
68
|
readonly meanValue: string
|
|
@@ -49,30 +71,9 @@ export type BarChartElementId = {
|
|
|
49
71
|
readonly windowedMinMax: string
|
|
50
72
|
}
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const BAR_CHART_CLASS_IDS: BarChartElementId = {
|
|
56
|
-
currentValue: STREAM_CHARTS_BAR_CHART_ID + '-value-lines',
|
|
57
|
-
meanValue: STREAM_CHARTS_BAR_CHART_ID + '-mean-value-lines',
|
|
58
|
-
minMax: STREAM_CHARTS_BAR_CHART_ID + '-min-max-bars',
|
|
59
|
-
windowedMeanValue: STREAM_CHARTS_BAR_CHART_ID + '-windowed-mean-value-lines',
|
|
60
|
-
windowedMinMax: STREAM_CHARTS_BAR_CHART_ID + '-windowed-mean-bars'
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const classIdFor = (id: string) => '.' + id
|
|
64
|
-
|
|
65
|
-
// constants identifying the bar-chart elements for which mouse-over/mouse-leave events are defined
|
|
66
|
-
const TOOLTIP_PROVIDER_ID = STREAM_CHARTS_BAR_CHART_ID + '-tooltip-provider'
|
|
67
|
-
|
|
68
|
-
export const BAR_CHART_TOOLTIP_PROVIDER_IDS: BarChartElementId = {
|
|
69
|
-
currentValue: TOOLTIP_PROVIDER_ID + '-current-value',
|
|
70
|
-
meanValue: TOOLTIP_PROVIDER_ID + '-mean-value',
|
|
71
|
-
minMax: TOOLTIP_PROVIDER_ID + '-min-max',
|
|
72
|
-
windowedMeanValue: TOOLTIP_PROVIDER_ID + '-windowed-min-max',
|
|
73
|
-
windowedMinMax: TOOLTIP_PROVIDER_ID + '-windowed-mean'
|
|
74
|
-
}
|
|
75
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The properties of the {@link BarPlot}
|
|
76
|
+
*/
|
|
76
77
|
export interface Props {
|
|
77
78
|
/**
|
|
78
79
|
* Holds the mapping between a series and the axis it uses (is assigned). The
|
|
@@ -80,16 +81,26 @@ export interface Props {
|
|
|
80
81
|
* object holding the ID of the assigned x-axis and y-axis.
|
|
81
82
|
*/
|
|
82
83
|
axisAssignments?: Map<string, AxesAssignment>
|
|
84
|
+
/**
|
|
85
|
+
* Whether to show each series' lifetime min/max bar (default is true)
|
|
86
|
+
*/
|
|
83
87
|
showMinMaxBars?: boolean
|
|
88
|
+
/**
|
|
89
|
+
* Whether to show each series' windowed min/max bar (default is true)
|
|
90
|
+
*/
|
|
84
91
|
showWindowedMinMaxBars?: boolean
|
|
92
|
+
/**
|
|
93
|
+
* Whether to show each series' current-value line (default is true)
|
|
94
|
+
*/
|
|
85
95
|
showValueLines?: boolean
|
|
96
|
+
/**
|
|
97
|
+
* Whether to show each series' lifetime mean-value line (default is true)
|
|
98
|
+
*/
|
|
86
99
|
showMeanValueLines?: boolean
|
|
87
|
-
showWindowedMeanValueLines?: boolean
|
|
88
100
|
/**
|
|
89
|
-
*
|
|
90
|
-
* infinity (i.e. no data is dropped)
|
|
101
|
+
* Whether to show each series' windowed mean-value line (default is true)
|
|
91
102
|
*/
|
|
92
|
-
|
|
103
|
+
showWindowedMeanValueLines?: boolean
|
|
93
104
|
/**
|
|
94
105
|
* Enables panning (default is false)
|
|
95
106
|
*/
|
|
@@ -104,35 +115,85 @@ export interface Props {
|
|
|
104
115
|
*/
|
|
105
116
|
zoomKeyModifiersRequired?: boolean
|
|
106
117
|
/**
|
|
107
|
-
* The (optional, default = 2 pixels)
|
|
108
|
-
* Margins on individual series can also be set through the
|
|
118
|
+
* The (optional, default = 2 pixels) left and right margin (in pixels) between each category's
|
|
119
|
+
* edges and its lines. Margins on individual series can also be set through the
|
|
120
|
+
* {@link ChartProps.seriesStyles} property.
|
|
109
121
|
*/
|
|
110
122
|
barMargin?: number
|
|
111
123
|
/**
|
|
112
124
|
* The (optional) default style for the bar series that are used if no other styles are specified
|
|
113
125
|
*/
|
|
114
126
|
barSeriesStyle?: BarSeriesStyle
|
|
127
|
+
/**
|
|
128
|
+
* When true, hovering over any element (bar, line) of a series also highlights the x- and
|
|
129
|
+
* y-axes it is plotted against, using the axis' own default highlight color/width (bar series
|
|
130
|
+
* styles don't carry a single flat highlight color/width the way line-series styles do, so
|
|
131
|
+
* this doesn't attempt to match a specific element's highlight style). Defaults to `false`.
|
|
132
|
+
*/
|
|
133
|
+
highlightAxesOnMouseOver?: boolean
|
|
134
|
+
/**
|
|
135
|
+
* An (optional) zoom/pan state to restore when this plot mounts -- commonly the last value
|
|
136
|
+
* reported through {@link onZoomStateChange} and held by the application (e.g. in a store)
|
|
137
|
+
* so that the zoom survives the plot being unmounted and remounted (e.g. navigating away
|
|
138
|
+
* and back). Only read once, when the plot's axes are first available; later changes to this
|
|
139
|
+
* prop are ignored.
|
|
140
|
+
*/
|
|
141
|
+
zoomState?: BarPlotZoomState
|
|
142
|
+
/**
|
|
143
|
+
* (Optional) callback that is handed the plot's zoom/pan state after each zoom event and at
|
|
144
|
+
* the end of each pan, so the application can hold on to it (see {@link BarPlotProps.zoomState}).
|
|
145
|
+
*/
|
|
146
|
+
onZoomStateChange?: (zoomState: BarPlotZoomState) => void
|
|
147
|
+
/**
|
|
148
|
+
* Called whenever this plot (re)creates its zoom behavior, handing
|
|
149
|
+
* the caller a `resetZoom` function that programmatically clears d3-zoom's own accumulated
|
|
150
|
+
* scale state back to identity and restores the ordinal x-axes to their original (un-zoomed,
|
|
151
|
+
* un-panned) ranges. See {@link ScatterPlot}'s identical prop for the full explanation of why
|
|
152
|
+
* this is needed. Only meaningful (called) while `zoomEnabled` is true.
|
|
153
|
+
*/
|
|
154
|
+
onZoomReset?: (resetZoom: () => void) => void
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Maps the short element-type keys used internally for hit-testing geometry (`minMax`,
|
|
159
|
+
* `windowedMinMax`, `meanValue`, `windowedMeanValue`, `currentValue`) to the actual provider IDs
|
|
160
|
+
* (`BAR_CHART_TOOLTIP_PROVIDER_IDS`) that `mouseOverHandlerFor`/`mouseLeaveHandlerFor` expect.
|
|
161
|
+
*/
|
|
162
|
+
const PROVIDER_ID_FOR_ELEMENT_TYPE: Record<string, string> = {
|
|
163
|
+
minMax: BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax,
|
|
164
|
+
windowedMinMax: BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax,
|
|
165
|
+
meanValue: BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue,
|
|
166
|
+
windowedMeanValue: BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue,
|
|
167
|
+
currentValue: BAR_CHART_TOOLTIP_PROVIDER_IDS.currentValue,
|
|
115
168
|
}
|
|
116
169
|
|
|
117
170
|
/**
|
|
118
|
-
* Renders a streaming bar plot for the series
|
|
119
|
-
*
|
|
171
|
+
* Renders a streaming bar plot for the series held by the {@link StreamingDataSource} handed to the
|
|
172
|
+
* {@link Chart} (its `dataSource` prop) -- along with each series' lifetime and windowed stats,
|
|
173
|
+
* which an {@link OrdinalDataSource} keeps. This component uses the {@link useChart}
|
|
120
174
|
* hook, and therefore must be a child of the {@link Chart} to be plugged in to the
|
|
121
175
|
* chart ecosystem (axes, tracker, tooltip).
|
|
122
176
|
*
|
|
177
|
+
* Internally, this no longer creates/updates SVG `<rect>`/`<line>` elements. Instead, it registers
|
|
178
|
+
* a single draw function with the chart's {@link CanvasContext} that redraws every series' bars
|
|
179
|
+
* and lines from scratch each time the canvas repaints. Mouse hover (for the tooltip and the
|
|
180
|
+
* value-line/windowed-mean-line highlight) is handled by hit-testing the mouse position against
|
|
181
|
+
* the last-drawn geometry, keyed per `${seriesName}::${elementType}` so the five overlaid visual
|
|
182
|
+
* elements (min-max bar, windowed min-max bar, mean line, windowed mean line, value line) can be
|
|
183
|
+
* hit-tested and highlighted independently, matching the old per-element SVG mouseover behavior.
|
|
184
|
+
*
|
|
123
185
|
* For a relatively complete example of how to use this plot component, see the
|
|
124
186
|
* <a href="https://github.com/robphilipp/stream-charts-examples">`StreamingBarChart` example</a>
|
|
125
187
|
*
|
|
126
188
|
* @param props The properties associated with the bar plot
|
|
189
|
+
* @return `null` (the plot draws onto the chart's canvas)
|
|
127
190
|
* @example
|
|
128
191
|
* ```typescript
|
|
129
192
|
* <BarPlot
|
|
130
193
|
* barMargin={1}
|
|
131
|
-
* dropDataAfter={5000}
|
|
132
194
|
* panEnabled={true}
|
|
133
195
|
* zoomEnabled={true}
|
|
134
196
|
* zoomKeyModifiersRequired={true}
|
|
135
|
-
* withCadenceOf={50}
|
|
136
197
|
*
|
|
137
198
|
* showMinMaxBars={true}
|
|
138
199
|
* showValueLines={true}
|
|
@@ -145,8 +206,7 @@ export interface Props {
|
|
|
145
206
|
export function BarPlot(props: Props): null {
|
|
146
207
|
const {
|
|
147
208
|
chartId,
|
|
148
|
-
|
|
149
|
-
mainG,
|
|
209
|
+
canvasContext,
|
|
150
210
|
axes,
|
|
151
211
|
seriesStyles,
|
|
152
212
|
seriesFilter,
|
|
@@ -159,28 +219,29 @@ export function BarPlot(props: Props): null {
|
|
|
159
219
|
setAxisAssignments,
|
|
160
220
|
setAxisIntervalFor,
|
|
161
221
|
setOriginalAxisIntervalFor,
|
|
162
|
-
axesRanges
|
|
222
|
+
axesRanges,
|
|
223
|
+
axisRangeFor,
|
|
224
|
+
updateAxisRanges,
|
|
163
225
|
} = axes
|
|
164
226
|
|
|
227
|
+
|
|
165
228
|
const {mouseOverHandlerFor, mouseLeaveHandlerFor} = mouse
|
|
166
229
|
|
|
167
230
|
const {plotDimensions, margin} = usePlotDimensions()
|
|
168
231
|
|
|
169
232
|
const {
|
|
170
|
-
seriesObservable,
|
|
171
233
|
windowingTime = 100,
|
|
172
|
-
shouldSubscribe,
|
|
173
|
-
|
|
174
|
-
onSubscribe = noop,
|
|
175
|
-
onUpdateData,
|
|
176
234
|
onUpdateChartTime = noop
|
|
177
|
-
} =
|
|
235
|
+
} = useStreamingSettings()
|
|
178
236
|
|
|
179
|
-
|
|
237
|
+
// the application-owned source of the data, and whether it's running (streaming)
|
|
238
|
+
const dataSource = useDataSource<OrdinalChartData, OrdinalDatum>()
|
|
239
|
+
const running = useDataSourceRunning(dataSource)
|
|
240
|
+
|
|
241
|
+
const {initialData} = useInitialData<OrdinalDatum>()
|
|
180
242
|
|
|
181
243
|
const {
|
|
182
244
|
axisAssignments = new Map<string, AxesAssignment>(),
|
|
183
|
-
dropDataAfter = Infinity,
|
|
184
245
|
panEnabled = false,
|
|
185
246
|
zoomEnabled = false,
|
|
186
247
|
zoomKeyModifiersRequired = true,
|
|
@@ -190,175 +251,77 @@ export function BarPlot(props: Props): null {
|
|
|
190
251
|
showMeanValueLines = true,
|
|
191
252
|
showWindowedMeanValueLines = true,
|
|
192
253
|
barMargin = 2,
|
|
193
|
-
barSeriesStyle = defaultBarSeriesStyle()
|
|
254
|
+
barSeriesStyle = defaultBarSeriesStyle(),
|
|
255
|
+
highlightAxesOnMouseOver = false,
|
|
256
|
+
zoomState = undefined,
|
|
257
|
+
onZoomStateChange = noop,
|
|
258
|
+
onZoomReset = noop,
|
|
194
259
|
} = props
|
|
195
260
|
|
|
196
261
|
// why do "dataRef" and "seriesRef" both hold on to the same underlying data? for performance.
|
|
197
262
|
//
|
|
198
263
|
// the "dataRef" and "seriesRef" both point to the same underlying data, a collection
|
|
199
|
-
// of series
|
|
200
|
-
// are the
|
|
264
|
+
// of series (held, and updated as new data is streamed in, by the data source). The series in
|
|
265
|
+
// "dataRef" are read by the canvas draw function, and the "seriesRef" map is for looking a
|
|
266
|
+
// series up by its name.
|
|
201
267
|
//
|
|
202
268
|
// the "dataRef" object holds on to a copy of the initial data (which is an array of
|
|
203
269
|
// time-series, e.i. an array of BaseSeries<OrdinalDatum> objects). The slice just creates a copy of
|
|
204
270
|
// the array, but the references to the BaseSeries objects are the same and still point to the same
|
|
205
271
|
// data as the "initialData" array.
|
|
206
272
|
//
|
|
207
|
-
// the "seriesRef" object is a reference to a map (series_name -> BaseSeries<OrdinalDatum>)
|
|
208
|
-
//
|
|
273
|
+
// the "seriesRef" object is a reference to a map (series_name -> BaseSeries<OrdinalDatum>), used
|
|
274
|
+
// to find the series under the mouse (for the tooltip).
|
|
209
275
|
//
|
|
210
|
-
// the series in the "dataRef" object are the ones
|
|
211
|
-
//
|
|
276
|
+
// the series in the "dataRef" object are the ones read by the next redraw, so as these are
|
|
277
|
+
// updated, the next canvas repaint picks up the new data.
|
|
212
278
|
const dataRef = useRef<Array<BaseSeries<OrdinalDatum>>>(initialData.slice())
|
|
213
279
|
const seriesRef = useRef<Map<string, BaseSeries<OrdinalDatum>>>(
|
|
214
280
|
new Map(initialData.map(series => [series.name, series]))
|
|
215
281
|
)
|
|
216
|
-
|
|
282
|
+
// the lifetime and windowed stats for each series -- kept by the data source (when it's an
|
|
283
|
+
// ordinal data source), so they keep accumulating while this plot is unmounted
|
|
284
|
+
const statsRef = useRef<WindowedOrdinalStats>(statsFrom(dataSource, initialData))
|
|
217
285
|
|
|
218
286
|
// map(axis_id -> current_time) -- maps the axis ID to the current time for that axis
|
|
219
287
|
const currentTimeRef = useRef<number>(0)
|
|
220
|
-
const subscriptionRef = useRef<Subscription>(undefined)
|
|
221
|
-
|
|
222
|
-
const isSubscriptionClosed = () => subscriptionRef.current === undefined || subscriptionRef.current.closed
|
|
223
|
-
|
|
224
|
-
const allowTooltipRef = useRef<boolean>(isSubscriptionClosed())
|
|
225
|
-
|
|
226
|
-
useEffect(
|
|
227
|
-
() => {
|
|
228
|
-
currentTimeRef.current = 0
|
|
229
|
-
},
|
|
230
|
-
[xAxesState]
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
// set the axis assignments needed if a tooltip is being used
|
|
234
|
-
useEffect(
|
|
235
|
-
() => {
|
|
236
|
-
setAxisAssignments(axisAssignments)
|
|
237
|
-
},
|
|
238
|
-
[axisAssignments, setAxisAssignments]
|
|
239
|
-
)
|
|
240
288
|
|
|
241
|
-
//
|
|
242
|
-
const
|
|
243
|
-
() => axesForSeriesGen<OrdinalDatum, OrdinalStringAxis>(initialData, axisAssignments, xAxesState),
|
|
244
|
-
[initialData, axisAssignments, xAxesState]
|
|
245
|
-
)
|
|
289
|
+
// tooltips are only shown while the data source is paused (and not while panning)
|
|
290
|
+
const allowTooltipRef = useRef<boolean>(!(dataSource?.running ?? false))
|
|
246
291
|
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
[mainG, onUpdateChartTime]
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
// todo find better way
|
|
262
|
-
// when the initial data changes, then reset the plot. note that the initial data doesn't change
|
|
263
|
-
// during the normal course of updates from the observable, only when the plot is restarted.
|
|
264
|
-
useEffect(
|
|
265
|
-
() => {
|
|
266
|
-
dataRef.current = initialData.slice()
|
|
267
|
-
seriesRef.current = new Map(initialData.map(series => [series.name, series]))
|
|
268
|
-
currentTimeRef.current = 0
|
|
269
|
-
statsRef.current = initialOrdinalStats(dataRef.current)
|
|
270
|
-
},
|
|
271
|
-
// ** not happy about this **
|
|
272
|
-
// only want this effect to run when the initial data is changed, which mean all the
|
|
273
|
-
// other dependencies are recalculated anyway.
|
|
274
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
275
|
-
[initialData]
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Adjusts the time-range and updates the plot when the plot is dragged to the left or right
|
|
280
|
-
* @param deltaX The amount that the plot is dragged
|
|
281
|
-
* @param plotDimensions The dimensions of the plot
|
|
282
|
-
* @param series An array of series names
|
|
283
|
-
* @param ranges A map holding the axis ID and its associated time range
|
|
284
|
-
*/
|
|
285
|
-
const onPan = useCallback(
|
|
286
|
-
(x: number,
|
|
287
|
-
plotDimensions: Dimensions,
|
|
288
|
-
series: Array<string>,
|
|
289
|
-
ranges: Map<string, OrdinalAxisRange>
|
|
290
|
-
) => ordinalPanHandler(axesForSeries, margin, setAxisIntervalFor, xAxesState)(x, plotDimensions, series, ranges),
|
|
291
|
-
[axesForSeries, margin, setAxisIntervalFor, xAxesState]
|
|
292
|
-
)
|
|
292
|
+
// the last-drawn geometry for each `${seriesName}::${elementType}`, in canvas coordinates,
|
|
293
|
+
// used for hit-testing mouse hover on `mousemove` (see the effect below that wires up the
|
|
294
|
+
// listener)
|
|
295
|
+
const geometryRef = useRef<Map<string, SeriesGeometry>>(new Map())
|
|
296
|
+
// which specific element (series + type) the mouse was over on the previous `mousemove`, so
|
|
297
|
+
// we know when to fire a "leave" before firing an "over" for a different element -- unlike
|
|
298
|
+
// Scatter/Raster, BarPlot doesn't use the shared `hoveredSeriesName` chart state, since here
|
|
299
|
+
// hover is scoped to one of five element *types* per series, not the series as a whole
|
|
300
|
+
const lastHoveredRef = useRef<{seriesName: string, elementType: string} | undefined>(undefined)
|
|
293
301
|
|
|
294
302
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
transform: ZoomTransform,
|
|
306
|
-
x: number,
|
|
307
|
-
plotDimensions: Dimensions,
|
|
308
|
-
ranges: Map<string, OrdinalAxisRange>,
|
|
309
|
-
) => ordinalAxisZoomHandler(axesForSeries, margin, setAxisIntervalFor, setOriginalAxisIntervalFor, xAxesState)(transform, x, plotDimensions, ranges),
|
|
310
|
-
[axesForSeries, margin, setAxisIntervalFor, setOriginalAxisIntervalFor, xAxesState]
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* @param ordinalRanges
|
|
315
|
-
* @param mainGElem
|
|
303
|
+
* (Re-)registers this plot's draw function with the canvas context and requests a redraw.
|
|
304
|
+
* Replaces the old version, which directly mutated SVG `<rect>`/`<line>` elements bound via
|
|
305
|
+
* d3's enter/update/exit join. Canvas has no persistent elements to join against, so the draw
|
|
306
|
+
* function just redraws every element from current data/scale state each time it's invoked.
|
|
307
|
+
*
|
|
308
|
+
* Pan/zoom behavior setup lives in a separate effect (see below), not here -- this function
|
|
309
|
+
* runs on every data tick (each `windowingTime` interval), and recreating/reattaching a
|
|
310
|
+
* `d3.drag()`/`d3.zoom()` behavior that often is pure overhead unrelated to drawing the new
|
|
311
|
+
* data.
|
|
312
|
+
* @param cc The canvas context to register the draw function with
|
|
316
313
|
*/
|
|
317
314
|
const updatePlot = useCallback(
|
|
318
|
-
(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const svg: SvgSelection = d3.select<SVGSVGElement, any>(container)
|
|
322
|
-
|
|
323
|
-
// set up panning
|
|
324
|
-
if (panEnabled) {
|
|
325
|
-
const drag = d3.drag<SVGSVGElement, Datum>()
|
|
326
|
-
.on("start", () => {
|
|
327
|
-
d3.select(container).style("cursor", "move")
|
|
328
|
-
allowTooltipRef.current = false
|
|
329
|
-
})
|
|
330
|
-
.on("drag", (event: any) => {
|
|
331
|
-
const names = dataRef.current.map(series => series.name)
|
|
332
|
-
onPan(event.dx, plotDimensions, names, ordinalRanges)
|
|
333
|
-
// need to update the plot with the new time-ranges
|
|
334
|
-
updatePlotRef.current(ordinalRanges, mainGElem)
|
|
335
|
-
})
|
|
336
|
-
.on("end", () => {
|
|
337
|
-
d3.select(container).style("cursor", "auto")
|
|
338
|
-
allowTooltipRef.current = isSubscriptionClosed()
|
|
339
|
-
})
|
|
315
|
+
(cc: CanvasContext) => {
|
|
316
|
+
const draw = (context: CanvasContext) => {
|
|
317
|
+
const {context2D} = context
|
|
340
318
|
|
|
341
|
-
|
|
342
|
-
}
|
|
319
|
+
context2D.save()
|
|
320
|
+
clipToArea(context, plotDimensions, {x: margin.left, y: margin.top})
|
|
321
|
+
context2D.translate(margin.left, margin.top)
|
|
343
322
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const zoom = d3.zoom<SVGSVGElement, Datum>()
|
|
347
|
-
.filter((event: any) => !zoomKeyModifiersRequired || event.shiftKey || event.ctrlKey)
|
|
348
|
-
.scaleExtent([1, 10])
|
|
349
|
-
.translateExtent([[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]])
|
|
350
|
-
.on("zoom", (event: any) => {
|
|
351
|
-
onZoom(
|
|
352
|
-
event.transform,
|
|
353
|
-
event.sourceEvent.offsetX - margin.left,
|
|
354
|
-
plotDimensions,
|
|
355
|
-
ordinalRanges,
|
|
356
|
-
)
|
|
357
|
-
updatePlotRef.current(ordinalRanges, mainGElem)
|
|
358
|
-
}
|
|
359
|
-
)
|
|
360
|
-
svg.call(zoom)
|
|
361
|
-
}
|
|
323
|
+
const newGeometry = new Map<string, SeriesGeometry>()
|
|
324
|
+
const hovered = lastHoveredRef.current
|
|
362
325
|
|
|
363
326
|
// enter, update, delete the bar data
|
|
364
327
|
dataRef.current.forEach(series => {
|
|
@@ -368,6 +331,7 @@ export function BarPlot(props: Props): null {
|
|
|
368
331
|
axisId => xAxesState.axisFor(axisId).getOrUndefined(),
|
|
369
332
|
axisId => yAxesState.axisFor(axisId).getOrUndefined()
|
|
370
333
|
)
|
|
334
|
+
if (xAxis === undefined || yAxis === undefined) return
|
|
371
335
|
|
|
372
336
|
// grab the series styles, or the defaults if none exist
|
|
373
337
|
const {
|
|
@@ -383,13 +347,13 @@ export function BarPlot(props: Props): null {
|
|
|
383
347
|
}
|
|
384
348
|
|
|
385
349
|
// only show the data for which the regex filter matches
|
|
386
|
-
const plotData = series.name.match(seriesFilter)
|
|
350
|
+
const plotData = series.name.match(seriesFilter) && series.data.length > 0 ?
|
|
351
|
+
[series.data[series.data.length - 1]] :
|
|
352
|
+
[]
|
|
353
|
+
if (plotData.length === 0) return
|
|
387
354
|
|
|
388
355
|
// grab the functions for determining the lower and upper bounds of the category
|
|
389
|
-
const {
|
|
390
|
-
lower,
|
|
391
|
-
upper
|
|
392
|
-
} = xAxisCategoryBoundsFn(xAxis.scale.bandwidth(), valueLineStyle.regular.width, categoryMargin)
|
|
356
|
+
const {lower, upper} = xAxisCategoryBoundsFn(xAxis.scale.bandwidth(), valueLineStyle.regular.width, categoryMargin)
|
|
393
357
|
|
|
394
358
|
// grab the value (index) associated with the series name (this is a category axis)
|
|
395
359
|
const x = xAxis.scale(series.name) || 0
|
|
@@ -403,55 +367,20 @@ export function BarPlot(props: Props): null {
|
|
|
403
367
|
statsRef.current.valueStatsForSeries.get(series.name)?.max.value || 0,
|
|
404
368
|
yAxis
|
|
405
369
|
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
update => barFor(
|
|
418
|
-
update,
|
|
419
|
-
totalBar,
|
|
420
|
-
barStyleFor(showMinMaxBars, minMaxBarStyle)
|
|
421
|
-
),
|
|
422
|
-
exit => exit.remove()
|
|
423
|
-
)
|
|
424
|
-
.on(
|
|
425
|
-
"mouseover",
|
|
426
|
-
(event,) =>
|
|
427
|
-
handleMouseOverBar(
|
|
428
|
-
container,
|
|
429
|
-
yAxis,
|
|
430
|
-
series,
|
|
431
|
-
statsRef.current,
|
|
432
|
-
event,
|
|
433
|
-
margin,
|
|
434
|
-
seriesStyles,
|
|
435
|
-
barSeriesStyle,
|
|
436
|
-
allowTooltipRef.current && showMinMaxBars,
|
|
437
|
-
mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax),
|
|
438
|
-
BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax
|
|
439
|
-
)
|
|
440
|
-
)
|
|
441
|
-
.on(
|
|
442
|
-
"mouseleave",
|
|
443
|
-
event => handleMouseLeaveSeries(
|
|
444
|
-
series.name,
|
|
445
|
-
event.currentTarget,
|
|
446
|
-
seriesStyles,
|
|
447
|
-
barSeriesStyle,
|
|
448
|
-
mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax),
|
|
449
|
-
BAR_CHART_TOOLTIP_PROVIDER_IDS.minMax
|
|
450
|
-
)
|
|
451
|
-
)
|
|
370
|
+
drawBar(context2D, totalBar, barStyleFor(showMinMaxBars, minMaxBarStyle))
|
|
371
|
+
newGeometry.set(`${series.name}::minMax`, {
|
|
372
|
+
points: [],
|
|
373
|
+
rects: [{
|
|
374
|
+
x: totalBar.upperX + margin.left,
|
|
375
|
+
y: totalBar.upperY + margin.top,
|
|
376
|
+
width: totalBar.width,
|
|
377
|
+
height: totalBar.height
|
|
378
|
+
}],
|
|
379
|
+
hitRadius: 0
|
|
380
|
+
})
|
|
452
381
|
|
|
453
382
|
//
|
|
454
|
-
// windowed-mean line when the windowed stats are defined for the series
|
|
383
|
+
// windowed-mean line and windowed min/max bar, when the windowed stats are defined for the series
|
|
455
384
|
const seriesWindowedStats = statsRef.current.windowedValueStatsForSeries.get(series.name)
|
|
456
385
|
if (seriesWindowedStats !== undefined) {
|
|
457
386
|
const windowedBar = barDimensions(
|
|
@@ -460,307 +389,377 @@ export function BarPlot(props: Props): null {
|
|
|
460
389
|
seriesWindowedStats.min.value, seriesWindowedStats.max.value,
|
|
461
390
|
yAxis
|
|
462
391
|
)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
update => barFor(
|
|
475
|
-
update,
|
|
476
|
-
windowedBar,
|
|
477
|
-
barStyleFor(showWindowedMinMaxBars, windowedBarStyle)),
|
|
478
|
-
exit => exit.remove()
|
|
479
|
-
)
|
|
480
|
-
.on(
|
|
481
|
-
"mouseover",
|
|
482
|
-
(event,) =>
|
|
483
|
-
handleMouseOverBar(
|
|
484
|
-
container,
|
|
485
|
-
yAxis,
|
|
486
|
-
series,
|
|
487
|
-
statsRef.current,
|
|
488
|
-
event,
|
|
489
|
-
margin,
|
|
490
|
-
seriesStyles,
|
|
491
|
-
barSeriesStyle,
|
|
492
|
-
allowTooltipRef.current && showWindowedMinMaxBars,
|
|
493
|
-
mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax),
|
|
494
|
-
BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax
|
|
495
|
-
)
|
|
496
|
-
)
|
|
497
|
-
.on(
|
|
498
|
-
"mouseleave",
|
|
499
|
-
event => handleMouseLeaveSeries(
|
|
500
|
-
series.name,
|
|
501
|
-
event.currentTarget,
|
|
502
|
-
seriesStyles,
|
|
503
|
-
barSeriesStyle,
|
|
504
|
-
mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax),
|
|
505
|
-
BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMinMax
|
|
506
|
-
)
|
|
507
|
-
)
|
|
392
|
+
drawBar(context2D, windowedBar, barStyleFor(showWindowedMinMaxBars, windowedBarStyle))
|
|
393
|
+
newGeometry.set(`${series.name}::windowedMinMax`, {
|
|
394
|
+
points: [],
|
|
395
|
+
rects: [{
|
|
396
|
+
x: windowedBar.upperX + margin.left,
|
|
397
|
+
y: windowedBar.upperY + margin.top,
|
|
398
|
+
width: windowedBar.width,
|
|
399
|
+
height: windowedBar.height
|
|
400
|
+
}],
|
|
401
|
+
hitRadius: 0
|
|
402
|
+
})
|
|
508
403
|
|
|
509
404
|
//
|
|
510
405
|
// mean line
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
meanValueLineStyle.regular
|
|
524
|
-
),
|
|
525
|
-
update => lineFor(
|
|
526
|
-
update,
|
|
527
|
-
{
|
|
528
|
-
x1: () => lower(x), y1: () => meanLineY,
|
|
529
|
-
x2: () => upper(x), y2: () => meanLineY
|
|
530
|
-
},
|
|
531
|
-
meanValueLineStyle.regular
|
|
532
|
-
),
|
|
533
|
-
exit => exit.remove()
|
|
534
|
-
)
|
|
535
|
-
.on(
|
|
536
|
-
"mouseover",
|
|
537
|
-
(event,) =>
|
|
538
|
-
handleMouseOverBar(
|
|
539
|
-
container,
|
|
540
|
-
yAxis,
|
|
541
|
-
series,
|
|
542
|
-
statsRef.current,
|
|
543
|
-
event,
|
|
544
|
-
margin,
|
|
545
|
-
seriesStyles,
|
|
546
|
-
barSeriesStyle,
|
|
547
|
-
allowTooltipRef.current && showMeanValueLines,
|
|
548
|
-
mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue),
|
|
549
|
-
BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue
|
|
550
|
-
)
|
|
551
|
-
)
|
|
552
|
-
.on(
|
|
553
|
-
"mouseleave",
|
|
554
|
-
event => handleMouseLeaveSeries(
|
|
555
|
-
series.name,
|
|
556
|
-
event.currentTarget,
|
|
557
|
-
seriesStyles,
|
|
558
|
-
barSeriesStyle,
|
|
559
|
-
mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue),
|
|
560
|
-
BAR_CHART_TOOLTIP_PROVIDER_IDS.meanValue
|
|
561
|
-
)
|
|
562
|
-
)
|
|
406
|
+
if (showMeanValueLines) {
|
|
407
|
+
const meanLineY = yAxis.scale(statsRef.current.valueStatsForSeries.get(series.name)?.mean || 0)
|
|
408
|
+
drawLine(context2D, lower(x), meanLineY, upper(x), meanLineY, meanValueLineStyle.regular)
|
|
409
|
+
newGeometry.set(`${series.name}::meanValue`, {
|
|
410
|
+
points: [],
|
|
411
|
+
segments: [[
|
|
412
|
+
[lower(x) + margin.left, meanLineY + margin.top],
|
|
413
|
+
[upper(x) + margin.left, meanLineY + margin.top]
|
|
414
|
+
]],
|
|
415
|
+
hitRadius: Math.max(4, meanValueLineStyle.regular.width ?? 1)
|
|
416
|
+
})
|
|
417
|
+
}
|
|
563
418
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
.
|
|
568
|
-
.
|
|
569
|
-
.
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
{
|
|
581
|
-
x1: () => lower(x), y1: () => windowedMeanLineY,
|
|
582
|
-
x2: () => upper(x), y2: () => windowedMeanLineY
|
|
583
|
-
},
|
|
584
|
-
windowedMeanLineStyle.regular
|
|
585
|
-
),
|
|
586
|
-
exit => exit.remove()
|
|
587
|
-
)
|
|
588
|
-
.on(
|
|
589
|
-
"mouseover",
|
|
590
|
-
(event,) =>
|
|
591
|
-
handleMouseOverBar(
|
|
592
|
-
container,
|
|
593
|
-
yAxis,
|
|
594
|
-
series,
|
|
595
|
-
statsRef.current,
|
|
596
|
-
event,
|
|
597
|
-
margin,
|
|
598
|
-
seriesStyles,
|
|
599
|
-
barSeriesStyle,
|
|
600
|
-
allowTooltipRef.current && showWindowedMeanValueLines,
|
|
601
|
-
mouseOverHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue),
|
|
602
|
-
BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue
|
|
603
|
-
)
|
|
604
|
-
)
|
|
605
|
-
.on(
|
|
606
|
-
"mouseleave",
|
|
607
|
-
event => handleMouseLeaveSeries(
|
|
608
|
-
series.name,
|
|
609
|
-
event.currentTarget,
|
|
610
|
-
seriesStyles,
|
|
611
|
-
barSeriesStyle,
|
|
612
|
-
mouseLeaveHandlerFor(`tooltip-${chartId}`, BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue),
|
|
613
|
-
BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue
|
|
614
|
-
)
|
|
615
|
-
)
|
|
419
|
+
//
|
|
420
|
+
// windowed-mean line
|
|
421
|
+
if (showWindowedMeanValueLines) {
|
|
422
|
+
const windowedMeanLineY = yAxis.scale(isNaN(seriesWindowedStats.mean) ? 0 : seriesWindowedStats.mean)
|
|
423
|
+
const isHovered = hovered?.seriesName === series.name && hovered.elementType === 'windowedMeanValue'
|
|
424
|
+
const style = isHovered ? windowedMeanLineStyle.highlight : windowedMeanLineStyle.regular
|
|
425
|
+
drawLine(context2D, lower(x), windowedMeanLineY, upper(x), windowedMeanLineY, style)
|
|
426
|
+
newGeometry.set(`${series.name}::windowedMeanValue`, {
|
|
427
|
+
points: [],
|
|
428
|
+
segments: [[
|
|
429
|
+
[lower(x) + margin.left, windowedMeanLineY + margin.top],
|
|
430
|
+
[upper(x) + margin.left, windowedMeanLineY + margin.top]
|
|
431
|
+
]],
|
|
432
|
+
hitRadius: Math.max(4, style.width ?? 1)
|
|
433
|
+
})
|
|
434
|
+
}
|
|
616
435
|
}
|
|
617
436
|
|
|
618
437
|
//
|
|
619
|
-
// value
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
.
|
|
623
|
-
.
|
|
624
|
-
.
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
)
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
438
|
+
// value line
|
|
439
|
+
if (showValueLines) {
|
|
440
|
+
const datum = plotData[0]
|
|
441
|
+
const valueLineY = yAxis.scale(datum.value)
|
|
442
|
+
const isHovered = hovered?.seriesName === series.name && hovered.elementType === 'currentValue'
|
|
443
|
+
const style = isHovered ? valueLineStyle.highlight : valueLineStyle.regular
|
|
444
|
+
drawLine(context2D, lower(x), valueLineY, upper(x), valueLineY, style)
|
|
445
|
+
newGeometry.set(`${series.name}::currentValue`, {
|
|
446
|
+
points: [],
|
|
447
|
+
segments: [[
|
|
448
|
+
[lower(x) + margin.left, valueLineY + margin.top],
|
|
449
|
+
[upper(x) + margin.left, valueLineY + margin.top]
|
|
450
|
+
]],
|
|
451
|
+
hitRadius: Math.max(4, style.width ?? 1)
|
|
452
|
+
})
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
geometryRef.current = newGeometry
|
|
457
|
+
|
|
458
|
+
context2D.restore()
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Grows the auto-scaling y-axes (see `ContinuousAxis`'s `autoScale` prop) to fit what's
|
|
463
|
+
* shown of the series whose categories are on screen (within the x-axis' current,
|
|
464
|
+
* possibly zoomed, window): the min/max bars, the windowed min/max bars, the mean and
|
|
465
|
+
* windowed-mean lines, and the value lines -- each only when it's shown. Registered to
|
|
466
|
+
* run before the axes draw (they're at z-index 0), so the axes and the bars are drawn
|
|
467
|
+
* with the grown domain in the same frame.
|
|
468
|
+
*/
|
|
469
|
+
const autoScaleYAxes = (): void => {
|
|
470
|
+
const extents = new Map<string, [min: number, max: number]>()
|
|
471
|
+
dataRef.current.forEach(series => {
|
|
472
|
+
if (!series.name.match(seriesFilter) || series.data.length === 0) return
|
|
473
|
+
const [xAxis, yAxis] = axesFor(
|
|
474
|
+
series.name,
|
|
475
|
+
axisAssignments,
|
|
476
|
+
axisId => xAxesState.axisFor(axisId).getOrUndefined(),
|
|
477
|
+
axisId => yAxesState.axisFor(axisId).getOrUndefined()
|
|
478
|
+
)
|
|
479
|
+
if (xAxis === undefined || yAxis?.autoScale === undefined) return
|
|
480
|
+
|
|
481
|
+
// only the categories (at least partly) on screen
|
|
482
|
+
const x = xAxis.scale(series.name)
|
|
483
|
+
if (x === undefined || x + xAxis.scale.bandwidth() <= 0 || x >= plotDimensions.width) return
|
|
484
|
+
|
|
485
|
+
// what's shown of the series (mirrors the draw function above)
|
|
486
|
+
const values: Array<number> = []
|
|
487
|
+
const stats = statsRef.current.valueStatsForSeries.get(series.name)
|
|
488
|
+
if (showMinMaxBars && stats !== undefined) values.push(stats.min.value, stats.max.value)
|
|
489
|
+
const windowedStats = statsRef.current.windowedValueStatsForSeries.get(series.name)
|
|
490
|
+
if (windowedStats !== undefined) {
|
|
491
|
+
if (showWindowedMinMaxBars) values.push(windowedStats.min.value, windowedStats.max.value)
|
|
492
|
+
if (showMeanValueLines && stats !== undefined) values.push(stats.mean)
|
|
493
|
+
if (showWindowedMeanValueLines) values.push(windowedStats.mean)
|
|
494
|
+
}
|
|
495
|
+
if (showValueLines) values.push(series.data[series.data.length - 1].value)
|
|
496
|
+
|
|
497
|
+
const seriesExtent = extentOf(values, yAxis.scale)
|
|
498
|
+
if (seriesExtent === undefined) return
|
|
499
|
+
const extent = extents.get(yAxis.axisId)
|
|
500
|
+
extents.set(
|
|
501
|
+
yAxis.axisId,
|
|
502
|
+
extent === undefined ? seriesExtent : extentWith(extentWith(extent, seriesExtent[0]), seriesExtent[1])
|
|
503
|
+
)
|
|
504
|
+
})
|
|
505
|
+
// (the y-axes are continuous axes, even though this plot's axes context is typed
|
|
506
|
+
// for its ordinal x-axes)
|
|
507
|
+
growAutoScaledYAxes(extents, {
|
|
508
|
+
yAxes: yAxesState.axes,
|
|
509
|
+
axisRangeFor,
|
|
510
|
+
updateAxisRanges: updateAxisRanges as unknown as AutoScaleAxes['updateAxisRanges'],
|
|
671
511
|
})
|
|
672
512
|
}
|
|
513
|
+
|
|
514
|
+
cc.register(`bar-plot-autoscale-${chartId}`, autoScaleYAxes, -10)
|
|
515
|
+
cc.register(`bar-plot-${chartId}`, draw, 10)
|
|
516
|
+
cc.requestRedraw()
|
|
673
517
|
},
|
|
674
518
|
[
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
axisAssignments, xAxesState, yAxesState,
|
|
680
|
-
barMargin, seriesStyles, barSeriesStyle,
|
|
681
|
-
seriesFilter,
|
|
682
|
-
chartId,
|
|
683
|
-
showValueLines,
|
|
684
|
-
showMinMaxBars,
|
|
685
|
-
mouseOverHandlerFor, mouseLeaveHandlerFor,
|
|
686
|
-
showMeanValueLines,
|
|
687
|
-
showWindowedMeanValueLines,
|
|
688
|
-
showWindowedMinMaxBars
|
|
519
|
+
chartId, plotDimensions, axisAssignments, xAxesState, yAxesState, barMargin, seriesStyles,
|
|
520
|
+
barSeriesStyle, seriesFilter, showValueLines, showMinMaxBars, showMeanValueLines,
|
|
521
|
+
showWindowedMeanValueLines, showWindowedMinMaxBars, axisRangeFor, updateAxisRanges,
|
|
522
|
+
margin.left, margin.top
|
|
689
523
|
]
|
|
690
524
|
)
|
|
691
525
|
|
|
526
|
+
// new data, new scale: restarts the auto-scaling y-axes when the data source is replaced
|
|
527
|
+
useAutoScaleRestart(dataSource, {
|
|
528
|
+
yAxes: yAxesState.axes,
|
|
529
|
+
axisRangeFor,
|
|
530
|
+
updateAxisRanges: updateAxisRanges as unknown as AutoScaleAxes['updateAxisRanges'],
|
|
531
|
+
})
|
|
532
|
+
|
|
692
533
|
// need to keep the function references for use by the subscription, which forms a closure
|
|
693
534
|
// on them. without the references, the closures become stale, and resizing during streaming
|
|
694
535
|
// doesn't work properly
|
|
695
|
-
const updatePlotRef = useRef<(
|
|
536
|
+
const updatePlotRef = useRef<(cc: CanvasContext) => void>(updatePlot)
|
|
696
537
|
useEffect(
|
|
697
538
|
() => {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
mainG
|
|
703
|
-
.selectAll<SVGGElement, BaseSeries<OrdinalDatum>>('g')
|
|
704
|
-
.attr("clip-path", `url(#${clipPathId})`)
|
|
705
|
-
.data<BaseSeries<OrdinalDatum>>(dataRef.current)
|
|
706
|
-
.enter()
|
|
707
|
-
.append('g')
|
|
708
|
-
.attr('class', 'spikes-series')
|
|
709
|
-
.attr('id', series => `${series.name}-${chartId}-bar`)
|
|
710
|
-
.attr('transform', `translate(${margin.left}, ${margin.top})`)
|
|
539
|
+
updatePlotRef.current = updatePlot
|
|
540
|
+
},
|
|
541
|
+
[updatePlot]
|
|
542
|
+
)
|
|
711
543
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
544
|
+
useEffect(
|
|
545
|
+
() => {
|
|
546
|
+
currentTimeRef.current = 0
|
|
547
|
+
},
|
|
548
|
+
[xAxesState]
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
// set the axis assignments needed if a tooltip is being used
|
|
552
|
+
useEffect(
|
|
553
|
+
() => {
|
|
554
|
+
setAxisAssignments(axisAssignments)
|
|
555
|
+
},
|
|
556
|
+
[axisAssignments, setAxisAssignments]
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
// calculates the distinct series IDs that cover all the series in the plot
|
|
560
|
+
const axesForSeries = useMemo(
|
|
561
|
+
() => axesForSeriesGen<OrdinalDatum, OrdinalStringAxis>(initialData, axisAssignments, xAxesState),
|
|
562
|
+
[initialData, axisAssignments, xAxesState]
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
// redraws the plot for newly arrived data and reports the chart time, using the updatePlot
|
|
566
|
+
// reference (to avoid a stale updatePlot). Deliberately leaves the axes' ranges
|
|
567
|
+
// (`ordinalRangesRef`) alone: the ordinal axes don't scroll as data arrives, so only this plot's
|
|
568
|
+
// own zoom, pan, and resize handling changes them -- replacing them here (as a previous version
|
|
569
|
+
// did, with the y-axes' ranges) left the x-axis zoom/pan handlers with nothing to act on until
|
|
570
|
+
// something else happened to rebuild the map.
|
|
571
|
+
const updateTimingAndPlot = useCallback(
|
|
572
|
+
(): void => {
|
|
573
|
+
if (canvasContext !== null) {
|
|
574
|
+
// (one redraw request is enough: `onUpdateChartTime` only reports the time to the
|
|
575
|
+
// application, and redraw requests are coalesced into one animation frame anyway)
|
|
576
|
+
updatePlotRef.current(canvasContext)
|
|
577
|
+
onUpdateChartTime(currentTimeRef.current)
|
|
718
578
|
}
|
|
719
579
|
},
|
|
720
|
-
[
|
|
580
|
+
[canvasContext, onUpdateChartTime]
|
|
721
581
|
)
|
|
722
582
|
|
|
723
|
-
//
|
|
724
|
-
|
|
583
|
+
// when the initial data changes, then reset the plot. note that the initial data doesn't change
|
|
584
|
+
// during the normal course of updates from the data source, only when the data source is replaced.
|
|
585
|
+
useEffect(
|
|
725
586
|
() => {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
587
|
+
dataRef.current = initialData.slice()
|
|
588
|
+
seriesRef.current = new Map(initialData.map(series => [series.name, series]))
|
|
589
|
+
currentTimeRef.current = 0
|
|
590
|
+
statsRef.current = statsFrom(dataSource, dataRef.current)
|
|
591
|
+
},
|
|
592
|
+
[initialData, dataSource]
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Adjusts the ordinal x-axes' ranges when the plot is dragged to the left or right (the caller
|
|
597
|
+
* redraws the plot)
|
|
598
|
+
* @param x The amount that the plot is dragged
|
|
599
|
+
* @param plotDimensions The dimensions of the plot
|
|
600
|
+
* @param series An array of series names
|
|
601
|
+
* @param ranges A map holding the axis ID and its associated range
|
|
602
|
+
*/
|
|
603
|
+
const onPan = useCallback(
|
|
604
|
+
(x: number,
|
|
605
|
+
plotDimensions: Dimensions,
|
|
606
|
+
series: Array<string>,
|
|
607
|
+
ranges: Map<string, OrdinalAxisRange>
|
|
608
|
+
) => ordinalPanHandler(axesForSeries, margin, setAxisIntervalFor, xAxesState)(x, plotDimensions, series, ranges),
|
|
609
|
+
[axesForSeries, margin, setAxisIntervalFor, xAxesState]
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
|
|
614
|
+
* at the location of the mouse when the scroll wheel or gesture was applied.
|
|
615
|
+
* @param zoomFactor The *cumulative* zoom scale factor for this event -- d3-zoom's own
|
|
616
|
+
* `event.transform.k`, passed through unmodified. See {@link OrdinalAxisRange.scaledCumulative}
|
|
617
|
+
* for why the ordinal x-axis needs this cumulative factor (from which it derives the
|
|
618
|
+
* incremental step itself, and which it snaps back to `.original` at exactly 1).
|
|
619
|
+
* @param x The x-position of the mouse when the scroll wheel or gesture is used
|
|
620
|
+
* @param plotDimensions The dimensions of the plot
|
|
621
|
+
* @param ranges A map holding the axis ID and its associated range
|
|
622
|
+
*/
|
|
623
|
+
const onZoom = useCallback(
|
|
624
|
+
(
|
|
625
|
+
zoomFactor: number,
|
|
626
|
+
x: number,
|
|
627
|
+
plotDimensions: Dimensions,
|
|
628
|
+
ranges: Map<string, OrdinalAxisRange>,
|
|
629
|
+
) => ordinalAxisZoomHandler(axesForSeries, margin, setAxisIntervalFor, setOriginalAxisIntervalFor, xAxesState)(zoomFactor, x, plotDimensions, ranges),
|
|
630
|
+
[axesForSeries, margin, setAxisIntervalFor, setOriginalAxisIntervalFor, xAxesState]
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
// holds the current per-axis ordinal ranges, kept in sync by the effect below. Replaces
|
|
634
|
+
// threading `ordinalRanges` through as an explicit `updatePlot` parameter, so the pan/zoom
|
|
635
|
+
// handlers (now set up once, in a separate effect below, rather than inside `updatePlot`
|
|
636
|
+
// itself) can always read the current ranges without needing `updatePlot` to be recreated
|
|
637
|
+
// every time the ranges change.
|
|
638
|
+
const ordinalRangesRef = useRef<Map<string, OrdinalAxisRange>>(new Map())
|
|
639
|
+
|
|
640
|
+
// hands the current zoom/pan state (the ordinal x-axes' ranges, as fractions of their
|
|
641
|
+
// original extent, and d3-zoom's cumulative `k`) to the `onZoomStateChange` callback
|
|
642
|
+
const reportZoomState = useCallback(
|
|
643
|
+
(cc: CanvasContext): void => {
|
|
644
|
+
const ranges: Record<string, [number, number]> = {}
|
|
645
|
+
ordinalRangesRef.current.forEach((range, axisId) => {
|
|
646
|
+
const extent = range.original.measure()
|
|
647
|
+
if (xAxesState.axes.has(axisId) && extent > 0) {
|
|
648
|
+
ranges[axisId] = [
|
|
649
|
+
(range.current.start - range.original.start) / extent,
|
|
650
|
+
(range.current.end - range.original.start) / extent
|
|
651
|
+
]
|
|
652
|
+
}
|
|
653
|
+
})
|
|
654
|
+
onZoomStateChange({k: d3.zoomTransform(cc.canvas).k, ranges})
|
|
655
|
+
},
|
|
656
|
+
[onZoomStateChange, xAxesState.axes]
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
// panning (dragging) and zooming (the mouse wheel) -- see `useCanvasZoomPan`. The ordinal zoom
|
|
660
|
+
// takes d3-zoom's cumulative scale (`k`), rather than the incremental factor, so that it can
|
|
661
|
+
// snap back to the original range exactly when fully zoomed out (see `OrdinalAxisRange.scale`)
|
|
662
|
+
const {setZoomScale} = useCanvasZoomPan(canvasContext, {
|
|
663
|
+
panEnabled,
|
|
664
|
+
zoomEnabled,
|
|
665
|
+
keyModifiersRequired: zoomKeyModifiersRequired,
|
|
666
|
+
scaleExtent: [1, 10],
|
|
667
|
+
// (an ordinal axis zooms in as d3-zoom's scale goes up, the opposite way to the other
|
|
668
|
+
// plots' continuous axes, so the wheel is inverted to zoom the same way as they do)
|
|
669
|
+
invertWheel: true,
|
|
670
|
+
translateExtent: [[margin.left, margin.top], [plotDimensions.width, plotDimensions.height]],
|
|
671
|
+
// (tooltips are suppressed while panning, so that one isn't left behind)
|
|
672
|
+
onPanStart: () => {
|
|
673
|
+
allowTooltipRef.current = false
|
|
674
|
+
},
|
|
675
|
+
onPan: dx => {
|
|
676
|
+
if (canvasContext === null) return
|
|
677
|
+
onPan(dx, plotDimensions, dataRef.current.map(series => series.name), ordinalRangesRef.current)
|
|
678
|
+
updatePlotRef.current(canvasContext)
|
|
679
|
+
},
|
|
680
|
+
onPanEnd: () => {
|
|
681
|
+
allowTooltipRef.current = !(dataSource?.running ?? false)
|
|
682
|
+
if (canvasContext !== null) reportZoomState(canvasContext)
|
|
683
|
+
},
|
|
684
|
+
onZoom: ({k, x}) => {
|
|
685
|
+
if (canvasContext === null) return
|
|
686
|
+
onZoom(k, x - margin.left, plotDimensions, ordinalRangesRef.current)
|
|
687
|
+
updatePlotRef.current(canvasContext)
|
|
688
|
+
reportZoomState(canvasContext)
|
|
689
|
+
},
|
|
690
|
+
onZoomReset,
|
|
691
|
+
// restores each axis's range to its original
|
|
692
|
+
resetView: () => {
|
|
693
|
+
ordinalRangesRef.current.forEach((range, axisId) => {
|
|
694
|
+
xAxesState.axisFor(axisId).ifPresent(axis => {
|
|
695
|
+
ordinalRangesRef.current.set(axisId, range.update(range.original.start, range.original.end))
|
|
696
|
+
setAxisIntervalFor(axisId, range.original)
|
|
697
|
+
axis.update(axis.scale.domain(), range.original, range.original, plotDimensions, margin)
|
|
698
|
+
})
|
|
699
|
+
})
|
|
700
|
+
if (canvasContext !== null) updatePlotRef.current(canvasContext)
|
|
701
|
+
},
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
// the zoom state to restore, captured once at mount (see the `zoomState` prop); cleared once
|
|
705
|
+
// it has been applied (see `restoreZoomState` below), so it's only ever applied once
|
|
706
|
+
const pendingZoomStateRef = useRef<BarPlotZoomState | undefined>(zoomState)
|
|
707
|
+
|
|
708
|
+
// applies the pending zoom state (if any) to the ordinal x-axes' ranges and to d3-zoom's
|
|
709
|
+
// transform on the canvas -- a remounted plot has a brand-new canvas, whose d3-zoom transform
|
|
710
|
+
// starts at identity (k = 1). Both must be restored together: the ordinal zoom math derives
|
|
711
|
+
// its incremental step from d3's cumulative `k` relative to the range's current/original width
|
|
712
|
+
// ratio, and treats `k === 1` as "snap back to fully zoomed out" (see
|
|
713
|
+
// `OrdinalAxisRange.scaledCumulative`), so restoring the ranges without `k` would make the
|
|
714
|
+
// very next wheel tick jump. Waits (leaving the state pending) until every axis in the
|
|
715
|
+
// snapshot exists, since the axes register themselves with the chart asynchronously.
|
|
716
|
+
const restoreZoomState = useCallback(
|
|
717
|
+
(): void => {
|
|
718
|
+
const pending = pendingZoomStateRef.current
|
|
719
|
+
if (pending === undefined) return
|
|
720
|
+
const axisIds = Object.keys(pending.ranges)
|
|
721
|
+
const ready = axisIds.every(axisId =>
|
|
722
|
+
ordinalRangesRef.current.has(axisId) && xAxesState.axisFor(axisId).getOrUndefined() !== undefined
|
|
742
723
|
)
|
|
724
|
+
if (!ready || plotDimensions.width <= 0) return
|
|
725
|
+
|
|
726
|
+
axisIds.forEach(axisId => {
|
|
727
|
+
const range = ordinalRangesRef.current.get(axisId)
|
|
728
|
+
const axis = xAxesState.axisFor(axisId).getOrUndefined()
|
|
729
|
+
if (range === undefined || axis === undefined) return
|
|
730
|
+
const [startFraction, endFraction] = pending.ranges[axisId]
|
|
731
|
+
const {start: originalStart} = range.original
|
|
732
|
+
const extent = range.original.measure()
|
|
733
|
+
const current = AxisInterval.from(originalStart + startFraction * extent, originalStart + endFraction * extent)
|
|
734
|
+
ordinalRangesRef.current.set(axisId, range.update(current.start, current.end))
|
|
735
|
+
setAxisIntervalFor(axisId, current)
|
|
736
|
+
axis.update(axis.scale.domain(), current, range.original, plotDimensions, margin)
|
|
737
|
+
})
|
|
738
|
+
// (set directly, without dispatching a "zoom" event -- see `setZoomScale`)
|
|
739
|
+
setZoomScale(pending.k)
|
|
740
|
+
pendingZoomStateRef.current = undefined
|
|
743
741
|
},
|
|
744
|
-
[
|
|
745
|
-
axisAssignments, dropDataAfter, mainG,
|
|
746
|
-
onSubscribe, onUpdateData,
|
|
747
|
-
seriesObservable, updateTimingAndPlot, windowingTime, yAxesState,
|
|
748
|
-
plotDimensions.width
|
|
749
|
-
]
|
|
742
|
+
[margin, plotDimensions, setAxisIntervalFor, xAxesState, setZoomScale]
|
|
750
743
|
)
|
|
751
744
|
|
|
745
|
+
|
|
746
|
+
|
|
752
747
|
useEffect(
|
|
753
748
|
() => {
|
|
754
|
-
if (
|
|
749
|
+
if (canvasContext) {
|
|
755
750
|
const ordinalAxesRanges = axesRanges()
|
|
756
751
|
// so this gets a bit complicated. the ordinal-ranges need to be updated whenever the ordinal-ranges
|
|
757
752
|
// change. for example, when the window is resized, and then we need to update the
|
|
758
753
|
// ordinal-range. however, we want to keep the ordinal-ranges to reflect their original scale so that
|
|
759
754
|
// we can zoom properly (so the updates can't fuck with the scale).
|
|
760
755
|
if (ordinalAxesRanges.size === 0) {
|
|
761
|
-
// when no time-ranges have yet been created, then create them and
|
|
762
|
-
//
|
|
763
|
-
|
|
756
|
+
// when no time-ranges have yet been created, then create them and populate the
|
|
757
|
+
// existing ref's map in place (rather than replacing it -- reassigning `.current`
|
|
758
|
+
// once it's already in play elsewhere, e.g. the zoom/pan handlers, isn't allowed
|
|
759
|
+
// by react-hooks/immutability)
|
|
760
|
+
ordinalRangesRef.current.clear()
|
|
761
|
+
ordinalAxisRanges(xAxesState.axes)
|
|
762
|
+
.forEach((range, id) => ordinalRangesRef.current.set(id, range))
|
|
764
763
|
} else {
|
|
765
764
|
// when the ordinal-ranges already exist, then we want to update the ordinal-ranges for each
|
|
766
765
|
// existing ordinal-range in a way that maintains the original scale.
|
|
@@ -777,29 +776,188 @@ export function BarPlot(props: Props): null {
|
|
|
777
776
|
rangesMap.set(id, range.update(start, end) as OrdinalAxisRange)
|
|
778
777
|
})
|
|
779
778
|
})
|
|
780
|
-
|
|
779
|
+
ordinalRangesRef.current.clear()
|
|
780
|
+
ordinalAxesRanges.forEach((range, id) => ordinalRangesRef.current.set(id, range))
|
|
781
|
+
}
|
|
782
|
+
// restore a zoom state handed in from a previous instance (only does anything the
|
|
783
|
+
// first time the axes are all available -- see `restoreZoomState`)
|
|
784
|
+
restoreZoomState()
|
|
785
|
+
updatePlot(canvasContext)
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
[axesRanges, canvasContext, plotDimensions.width, updatePlot, xAxesState.axes, restoreZoomState]
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
// wires up a single mousemove/mouseleave listener on the shared canvas to replace the old
|
|
792
|
+
// per-element SVG mouseover/mouseleave handlers. Hit-tests the mouse position against the
|
|
793
|
+
// last-drawn geometry (see `geometryRef`, populated by the draw function above), keyed per
|
|
794
|
+
// `${seriesName}::${elementType}` so the five overlaid elements resolve independently.
|
|
795
|
+
useEffect(
|
|
796
|
+
() => {
|
|
797
|
+
if (!canvasContext) return
|
|
798
|
+
|
|
799
|
+
const canvas = canvasContext.canvas
|
|
800
|
+
|
|
801
|
+
// highlights (or un-highlights) the x- and y-axes a series is plotted against --
|
|
802
|
+
// keyed on the series name alone (not the specific hovered element type), so moving
|
|
803
|
+
// the mouse between a series' own bar/lines doesn't flicker the highlight off and on.
|
|
804
|
+
// Unlike Scatter/Outlier, bar series styles don't carry a single flat highlight
|
|
805
|
+
// color/width (they're nested per element type), so this uses the axis' own default
|
|
806
|
+
// highlight styling rather than trying to match a specific element's highlight style.
|
|
807
|
+
const highlightAxesFor = (seriesName: string, isHighlighted: boolean): void => {
|
|
808
|
+
if (!highlightAxesOnMouseOver) return
|
|
809
|
+
const [xAxis, yAxis] = axesFor(
|
|
810
|
+
seriesName,
|
|
811
|
+
axisAssignments,
|
|
812
|
+
axisId => xAxesState.axisFor(axisId).getOrUndefined(),
|
|
813
|
+
axisId => yAxesState.axisFor(axisId).getOrUndefined()
|
|
814
|
+
)
|
|
815
|
+
xAxis?.setHighlighted(isHighlighted)
|
|
816
|
+
yAxis?.setHighlighted(isHighlighted)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const handleMove = (event: MouseEvent) => {
|
|
820
|
+
const [x, y] = canvasLocalPoint(event, canvas)
|
|
821
|
+
const hit = seriesAt(x, y, geometryRef.current)
|
|
822
|
+
// geometry keys are "${seriesName}::${elementType}"; split back apart
|
|
823
|
+
const [hitSeriesName, hitElementType] = hit !== undefined ? hit.name.split('::') : [undefined, undefined]
|
|
824
|
+
|
|
825
|
+
const previous = lastHoveredRef.current
|
|
826
|
+
const sameAsBefore = previous !== undefined && hitSeriesName === previous.seriesName && hitElementType === previous.elementType
|
|
827
|
+
|
|
828
|
+
if (previous?.seriesName !== hitSeriesName) {
|
|
829
|
+
if (previous !== undefined) highlightAxesFor(previous.seriesName, false)
|
|
830
|
+
if (hitSeriesName !== undefined) highlightAxesFor(hitSeriesName, true)
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (!sameAsBefore) {
|
|
834
|
+
if (previous !== undefined) {
|
|
835
|
+
handleMouseLeaveSeries(
|
|
836
|
+
previous.seriesName,
|
|
837
|
+
mouseLeaveHandlerFor(`tooltip-${chartId}`, PROVIDER_ID_FOR_ELEMENT_TYPE[previous.elementType])
|
|
838
|
+
)
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (hitSeriesName !== undefined && hitElementType !== undefined) {
|
|
842
|
+
const series = seriesRef.current.get(hitSeriesName)
|
|
843
|
+
const yAxis = axesFor(
|
|
844
|
+
hitSeriesName,
|
|
845
|
+
axisAssignments,
|
|
846
|
+
axisId => xAxesState.axisFor(axisId).getOrUndefined(),
|
|
847
|
+
axisId => yAxesState.axisFor(axisId).getOrUndefined()
|
|
848
|
+
)[1]
|
|
849
|
+
const showingByType: Record<string, boolean> = {
|
|
850
|
+
minMax: showMinMaxBars,
|
|
851
|
+
windowedMinMax: showWindowedMinMaxBars,
|
|
852
|
+
meanValue: showMeanValueLines,
|
|
853
|
+
windowedMeanValue: showWindowedMeanValueLines,
|
|
854
|
+
currentValue: showValueLines,
|
|
855
|
+
}
|
|
856
|
+
if (series !== undefined && yAxis !== undefined && allowTooltipRef.current && showingByType[hitElementType]) {
|
|
857
|
+
handleMouseOverBar(
|
|
858
|
+
yAxis,
|
|
859
|
+
series,
|
|
860
|
+
statsRef.current,
|
|
861
|
+
[x, y],
|
|
862
|
+
margin,
|
|
863
|
+
mouseOverHandlerFor(`tooltip-${chartId}`, PROVIDER_ID_FOR_ELEMENT_TYPE[hitElementType])
|
|
864
|
+
)
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
lastHoveredRef.current = hitSeriesName !== undefined && hitElementType !== undefined ?
|
|
869
|
+
{seriesName: hitSeriesName, elementType: hitElementType} :
|
|
870
|
+
undefined
|
|
871
|
+
|
|
872
|
+
// the value-line and windowed-mean-line highlight depends on hover state, so
|
|
873
|
+
// request a redraw when it changes
|
|
874
|
+
canvasContext.requestRedraw()
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const handleLeaveCanvas = () => {
|
|
879
|
+
const previous = lastHoveredRef.current
|
|
880
|
+
if (previous !== undefined) {
|
|
881
|
+
handleMouseLeaveSeries(previous.seriesName, mouseLeaveHandlerFor(`tooltip-${chartId}`, PROVIDER_ID_FOR_ELEMENT_TYPE[previous.elementType]))
|
|
882
|
+
highlightAxesFor(previous.seriesName, false)
|
|
883
|
+
lastHoveredRef.current = undefined
|
|
884
|
+
canvasContext.requestRedraw()
|
|
781
885
|
}
|
|
782
886
|
}
|
|
887
|
+
|
|
888
|
+
canvas.addEventListener('mousemove', handleMove)
|
|
889
|
+
canvas.addEventListener('mouseleave', handleLeaveCanvas)
|
|
890
|
+
return () => {
|
|
891
|
+
canvas.removeEventListener('mousemove', handleMove)
|
|
892
|
+
canvas.removeEventListener('mouseleave', handleLeaveCanvas)
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
[
|
|
896
|
+
canvasContext, chartId, margin, axisAssignments, xAxesState, yAxesState,
|
|
897
|
+
mouseOverHandlerFor, mouseLeaveHandlerFor,
|
|
898
|
+
showMinMaxBars, showWindowedMinMaxBars, showMeanValueLines, showWindowedMeanValueLines, showValueLines,
|
|
899
|
+
highlightAxesOnMouseOver
|
|
900
|
+
]
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
// the latest `updateTimingAndPlot`, for the view driver to call -- the driver is created once
|
|
904
|
+
// per run (see below), so it must not capture a stale one (e.g. bound to an older canvas)
|
|
905
|
+
const updateTimingAndPlotRef = useRef(updateTimingAndPlot)
|
|
906
|
+
useEffect(
|
|
907
|
+
() => {
|
|
908
|
+
updateTimingAndPlotRef.current = updateTimingAndPlot
|
|
783
909
|
},
|
|
784
|
-
[
|
|
910
|
+
[updateTimingAndPlot]
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
// creates the view driver that keeps this plot's view in step with the data source, using the
|
|
914
|
+
// current settings -- see `useStreamingTimeAxes`'s identical effect event
|
|
915
|
+
const createViewDriver = useEffectEvent(
|
|
916
|
+
(source: NonNullable<typeof dataSource>) => ordinalViewDriverFor(
|
|
917
|
+
source,
|
|
918
|
+
windowingTime,
|
|
919
|
+
() => updateTimingAndPlotRef.current(),
|
|
920
|
+
currentTime => currentTimeRef.current = currentTime,
|
|
921
|
+
)
|
|
785
922
|
)
|
|
786
923
|
|
|
787
|
-
//
|
|
788
|
-
//
|
|
789
|
-
//
|
|
790
|
-
// do nothing.
|
|
924
|
+
// while the data source is running (and this plot has a canvas), drive the view from the
|
|
925
|
+
// data source's updates -- see `useStreamingTimeAxes`'s identical effect. The driver belongs to this plot
|
|
926
|
+
// instance alone and is always torn down with it.
|
|
791
927
|
useEffect(
|
|
792
928
|
() => {
|
|
793
|
-
if (
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
subscriptionRef.current = undefined
|
|
929
|
+
if (dataSource === undefined || !running || canvasContext === null) return
|
|
930
|
+
allowTooltipRef.current = false
|
|
931
|
+
const driver = createViewDriver(dataSource)
|
|
932
|
+
return () => {
|
|
933
|
+
driver.unsubscribe()
|
|
799
934
|
allowTooltipRef.current = true
|
|
800
935
|
}
|
|
801
936
|
},
|
|
802
|
-
[
|
|
937
|
+
[dataSource, running, canvasContext]
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
// this plot needs a data source (see the `Chart`'s `dataSource` prop)
|
|
941
|
+
useEffect(
|
|
942
|
+
() => {
|
|
943
|
+
if (dataSource === undefined) {
|
|
944
|
+
console.error("BarPlot requires a data source; set the enclosing Chart's `dataSource` prop")
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
[dataSource]
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
// unregister this plot's draw function on unmount
|
|
951
|
+
useEffect(
|
|
952
|
+
() => {
|
|
953
|
+
return () => {
|
|
954
|
+
if (canvasContext) {
|
|
955
|
+
canvasContext.unregister(`bar-plot-autoscale-${chartId}`)
|
|
956
|
+
canvasContext.unregister(`bar-plot-${chartId}`)
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
[canvasContext, chartId]
|
|
803
961
|
)
|
|
804
962
|
|
|
805
963
|
return null
|
|
@@ -809,11 +967,6 @@ export function BarPlot(props: Props): null {
|
|
|
809
967
|
Helper functions and types
|
|
810
968
|
*/
|
|
811
969
|
|
|
812
|
-
type PlotData = {
|
|
813
|
-
data: OrdinalDatum,
|
|
814
|
-
stats: OrdinalStats,
|
|
815
|
-
}
|
|
816
|
-
|
|
817
970
|
type BarDimensions = {
|
|
818
971
|
upperX: number
|
|
819
972
|
upperY: number
|
|
@@ -821,6 +974,12 @@ type BarDimensions = {
|
|
|
821
974
|
height: number
|
|
822
975
|
}
|
|
823
976
|
|
|
977
|
+
/**
|
|
978
|
+
* The bar style, made fully transparent when the bar isn't shown
|
|
979
|
+
* @param isVisible Whether the bar is shown
|
|
980
|
+
* @param style The bar's style
|
|
981
|
+
* @return The style (with its fill and stroke opacities 0 when the bar isn't shown)
|
|
982
|
+
*/
|
|
824
983
|
function barStyleFor(isVisible: boolean, style: BarStyle): BarStyle {
|
|
825
984
|
return {
|
|
826
985
|
...style,
|
|
@@ -829,6 +988,12 @@ function barStyleFor(isVisible: boolean, style: BarStyle): BarStyle {
|
|
|
829
988
|
}
|
|
830
989
|
}
|
|
831
990
|
|
|
991
|
+
/**
|
|
992
|
+
* The fill or stroke style, made fully transparent when its element isn't shown
|
|
993
|
+
* @param isVisible Whether the element is shown
|
|
994
|
+
* @param style The fill or stroke style
|
|
995
|
+
* @return The style (with an opacity of 0 when the element isn't shown)
|
|
996
|
+
*/
|
|
832
997
|
function updateOpacityFor<S extends SvgFillStyle | SvgStrokeStyle>(isVisible: boolean, style: S): S {
|
|
833
998
|
return {
|
|
834
999
|
...style,
|
|
@@ -837,53 +1002,51 @@ function updateOpacityFor<S extends SvgFillStyle | SvgStrokeStyle>(isVisible: bo
|
|
|
837
1002
|
}
|
|
838
1003
|
|
|
839
1004
|
/**
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
* @param
|
|
1005
|
+
* Draws a bar (rectangle) onto the canvas context. Replaces the old `barFor`, which set SVG
|
|
1006
|
+
* `<rect>` attributes via a d3 selection.
|
|
1007
|
+
* @param context The canvas 2D context
|
|
1008
|
+
* @param dimensions The bar's dimensions
|
|
843
1009
|
* @param style The bar style holding the fill and stroke styles
|
|
844
|
-
* @return The updated SVG selection (rect SVG element)
|
|
845
1010
|
*/
|
|
846
|
-
function
|
|
847
|
-
|
|
1011
|
+
function drawBar(
|
|
1012
|
+
context: CanvasRenderingContext2D,
|
|
848
1013
|
dimensions: BarDimensions,
|
|
849
1014
|
style: BarStyle
|
|
850
|
-
):
|
|
851
|
-
|
|
852
|
-
.attr('x', () => dimensions.upperX)
|
|
853
|
-
.attr('y', () => dimensions.upperY)
|
|
854
|
-
.attr('width', () => dimensions.width)
|
|
855
|
-
.attr('height', () => dimensions.height)
|
|
856
|
-
|
|
857
|
-
return applyStrokeStylesTo(applyFillStylesTo(selection, style.fill), style.stroke)
|
|
858
|
-
}
|
|
1015
|
+
): void {
|
|
1016
|
+
if (dimensions.width <= 0 || dimensions.height <= 0) return
|
|
859
1017
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1018
|
+
applyFillStyle(context, style.fill)
|
|
1019
|
+
context.fillRect(dimensions.upperX, dimensions.upperY, dimensions.width, dimensions.height)
|
|
1020
|
+
|
|
1021
|
+
if ((style.stroke.width ?? 0) > 0) {
|
|
1022
|
+
applyStrokeStyle(context, style.stroke)
|
|
1023
|
+
context.strokeRect(dimensions.upperX, dimensions.upperY, dimensions.width, dimensions.height)
|
|
1024
|
+
}
|
|
865
1025
|
}
|
|
866
1026
|
|
|
867
1027
|
/**
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
* @param
|
|
871
|
-
*
|
|
1028
|
+
* Draws a horizontal line segment (used for the value/mean/windowed-mean lines) onto the canvas
|
|
1029
|
+
* context. Replaces the old `lineFor`, which set SVG `<line>` attributes via a d3 selection.
|
|
1030
|
+
* @param context The canvas 2D context
|
|
1031
|
+
* @param x1 The line's start x-coordinate
|
|
1032
|
+
* @param y1 The line's start y-coordinate
|
|
1033
|
+
* @param x2 The line's end x-coordinate
|
|
1034
|
+
* @param y2 The line's end y-coordinate
|
|
872
1035
|
* @param strokeStyle The stroke style
|
|
873
|
-
* @return The updated SVG selection (rect SVG element)
|
|
874
1036
|
*/
|
|
875
|
-
function
|
|
876
|
-
|
|
877
|
-
|
|
1037
|
+
function drawLine(
|
|
1038
|
+
context: CanvasRenderingContext2D,
|
|
1039
|
+
x1: number,
|
|
1040
|
+
y1: number,
|
|
1041
|
+
x2: number,
|
|
1042
|
+
y2: number,
|
|
878
1043
|
strokeStyle: Partial<SvgStrokeStyle>
|
|
879
|
-
) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return applyStrokeStylesTo(selection, strokeStyle)
|
|
1044
|
+
): void {
|
|
1045
|
+
applyStrokeStyle(context, strokeStyle)
|
|
1046
|
+
context.beginPath()
|
|
1047
|
+
context.moveTo(x1, y1)
|
|
1048
|
+
context.lineTo(x2, y2)
|
|
1049
|
+
context.stroke()
|
|
887
1050
|
}
|
|
888
1051
|
|
|
889
1052
|
/**
|
|
@@ -915,17 +1078,15 @@ function barDimensions(widthFraction: number, lowerX: number, upperX: number, mi
|
|
|
915
1078
|
}
|
|
916
1079
|
|
|
917
1080
|
/**
|
|
918
|
-
*
|
|
919
|
-
*
|
|
920
|
-
* @
|
|
921
|
-
*
|
|
1081
|
+
* @param dataSource The chart's data source
|
|
1082
|
+
* @param series The series to calculate the stats for when the data source doesn't keep them
|
|
1083
|
+
* @return The data source's (live) stats when it keeps them (i.e. an ordinal data source);
|
|
1084
|
+
* otherwise, the stats calculated from the specified series
|
|
922
1085
|
*/
|
|
923
|
-
function
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
windowedValueStatsForSeries: copyValueStatsForSeries(ordinalStats.valueStatsForSeries)
|
|
928
|
-
}
|
|
1086
|
+
function statsFrom(dataSource: unknown, series: Array<BaseSeries<OrdinalDatum>>): WindowedOrdinalStats {
|
|
1087
|
+
return dataSource !== null && typeof dataSource === 'object' && 'stats' in dataSource ?
|
|
1088
|
+
(dataSource as {stats: WindowedOrdinalStats}).stats :
|
|
1089
|
+
initialOrdinalStats(series)
|
|
929
1090
|
}
|
|
930
1091
|
|
|
931
1092
|
/**
|
|
@@ -940,16 +1101,17 @@ type CategoryBounds = {
|
|
|
940
1101
|
}
|
|
941
1102
|
|
|
942
1103
|
/**
|
|
943
|
-
* Attempts to locate the x- and y-axes for the specified series. If
|
|
944
|
-
*
|
|
1104
|
+
* Attempts to locate the x- and y-axes for the specified series. If the series has no axis
|
|
1105
|
+
* assignment, then uses the default (first) x- and y-axes.
|
|
945
1106
|
* @param seriesName Name of the series for which to retrieve the axis
|
|
946
1107
|
* @param axisAssignments A map holding the series name and the associated x- and y-axes assigned
|
|
947
1108
|
* to that series. Note that the series in the axis-assignment map is merely a subset of the set
|
|
948
1109
|
* of series names.
|
|
949
1110
|
* @param xAxisFor The function that accepts an axis ID and returns the corresponding x-axis
|
|
950
1111
|
* @param yAxisFor The function that accepts an axis ID and returns the corresponding y-axis
|
|
951
|
-
* @return A 2d tuple holding the
|
|
952
|
-
* the second element.
|
|
1112
|
+
* @return A 2d tuple holding the category x-axis as its first element and the linear y-axis as
|
|
1113
|
+
* the second element (either is actually `undefined` when it isn't found, despite the type).
|
|
1114
|
+
* @throws Error when the x-axis isn't a category axis, or the y-axis isn't a continuous numeric axis
|
|
953
1115
|
*/
|
|
954
1116
|
function axesFor(
|
|
955
1117
|
seriesName: string,
|
|
@@ -959,25 +1121,23 @@ function axesFor(
|
|
|
959
1121
|
): [xAxis: OrdinalStringAxis, yAxis: ContinuousNumericAxis] {
|
|
960
1122
|
const axes = axisAssignments.get(seriesName)
|
|
961
1123
|
const xAxis = xAxisFor(axes?.xAxis || "")
|
|
1124
|
+
checkAxisType("BarPlot", "x", xAxis, AxisType.OrdinalString)
|
|
962
1125
|
const xAxisCategory = xAxis as OrdinalStringAxis
|
|
963
|
-
if (xAxis && !xAxisCategory) {
|
|
964
|
-
throw Error("Bar plot requires that x-axis be of type CategoryAxis")
|
|
965
|
-
}
|
|
966
1126
|
const yAxis = yAxisFor(axes?.yAxis || "")
|
|
1127
|
+
checkAxisType("BarPlot", "y", yAxis, AxisType.ContinuousNumeric)
|
|
967
1128
|
const yAxisContinuous = yAxis as ContinuousNumericAxis
|
|
968
|
-
if (yAxis && !yAxisContinuous) {
|
|
969
|
-
throw Error("Bar plot requires that y-axis be of type ContinuousNumericAxis")
|
|
970
|
-
}
|
|
971
1129
|
return [xAxisCategory, yAxisContinuous]
|
|
972
1130
|
}
|
|
973
1131
|
|
|
974
1132
|
/**
|
|
975
|
-
* Calculates the
|
|
976
|
-
*
|
|
1133
|
+
* Calculates the lower (left) and upper (right) x-coordinates of a category's lines, inside its
|
|
1134
|
+
* margins
|
|
1135
|
+
* @param categorySize The size of the category (i.e. plot_width / num_series)
|
|
977
1136
|
* @param lineWidth The width of the series line
|
|
978
|
-
* @param margin The margin applied to the
|
|
979
|
-
* @return An object with two functions, that when handed
|
|
980
|
-
*
|
|
1137
|
+
* @param margin The margin applied to the left and right of the category's lines (horizontal spacing)
|
|
1138
|
+
* @return An object with two functions, that when handed the category's (left) x-coordinate,
|
|
1139
|
+
* return the location of the start (lower) or end (upper) of its lines (when the category is no
|
|
1140
|
+
* wider than the margin, the lines are just the line width long)
|
|
981
1141
|
*/
|
|
982
1142
|
function xAxisCategoryBoundsFn(categorySize: number, lineWidth: number, margin: number): CategoryBounds {
|
|
983
1143
|
if (categorySize <= margin) return {
|
|
@@ -991,97 +1151,55 @@ function xAxisCategoryBoundsFn(categorySize: number, lineWidth: number, margin:
|
|
|
991
1151
|
}
|
|
992
1152
|
|
|
993
1153
|
/**
|
|
994
|
-
*
|
|
995
|
-
*
|
|
1154
|
+
* Reports a tooltip for the hovered element of a series in the bar chart (see {@link BarPlotTooltipContent}).
|
|
1155
|
+
* Replaces the old version, which also mutated the hovered SVG element's stroke directly for the
|
|
1156
|
+
* two element types that support a highlight (`currentValue`, `windowedMeanValue`); that highlight
|
|
1157
|
+
* is now handled by the draw function checking the hover state (see `updatePlot`'s `draw`) rather
|
|
1158
|
+
* than by touching an element here.
|
|
996
1159
|
* @param yAxis The y-axis
|
|
997
1160
|
* @param selectedSeries The selected series
|
|
998
1161
|
* @param seriesStats The statistics about the current series
|
|
999
|
-
* @param
|
|
1162
|
+
* @param mouseCoords The `[x, y]` position of the mouse, in canvas coordinates
|
|
1000
1163
|
* @param margin The plot margin
|
|
1001
|
-
* @param barStyles The series style information (needed for (un)highlighting)
|
|
1002
|
-
* @param defaultBarSeriesStyle The default bar series style that is used if no style is found for the series
|
|
1003
|
-
* @param allowTooltip When set to `false` won't show tooltip, even if it is visible (used by pan)
|
|
1004
1164
|
* @param mouseOverHandlerFor The handler for the mouse over (registered by the <Tooltip/>)
|
|
1005
|
-
* @param tooltipProvider The ID of the tooltip provider
|
|
1006
1165
|
*/
|
|
1007
1166
|
function handleMouseOverBar(
|
|
1008
|
-
container: SVGSVGElement,
|
|
1009
1167
|
yAxis: ContinuousNumericAxis,
|
|
1010
1168
|
selectedSeries: BaseSeries<OrdinalDatum>,
|
|
1011
1169
|
seriesStats: WindowedOrdinalStats,
|
|
1012
|
-
|
|
1170
|
+
mouseCoords: [x: number, y: number],
|
|
1013
1171
|
margin: Margin,
|
|
1014
|
-
barStyles: Map<string, BarSeriesStyle>,
|
|
1015
|
-
defaultBarSeriesStyle: BarSeriesStyle,
|
|
1016
|
-
allowTooltip: boolean,
|
|
1017
1172
|
mouseOverHandlerFor: ((
|
|
1018
1173
|
seriesName: string,
|
|
1019
1174
|
value: number,
|
|
1020
1175
|
tooltipData: TooltipData<OrdinalDatum, WindowedOrdinalStats>,
|
|
1021
1176
|
mouseCoords: [x: number, y: number]
|
|
1022
1177
|
) => void) | undefined,
|
|
1023
|
-
tooltipProvider?: string
|
|
1024
1178
|
): void {
|
|
1025
|
-
|
|
1026
|
-
const [x, y] = d3.pointer(event, container)
|
|
1179
|
+
const [, y] = mouseCoords
|
|
1027
1180
|
const value = yAxis.scale.invert(y - margin.top)
|
|
1028
1181
|
|
|
1029
1182
|
const {name: categoryName, data: selectedData} = selectedSeries
|
|
1030
1183
|
|
|
1031
|
-
|
|
1032
|
-
const lineStyle = lineStyleFor(tooltipProvider, barSeriesStyle)
|
|
1033
|
-
if (lineStyle !== undefined) {
|
|
1034
|
-
d3.select<SVGPathElement, Datum>(event.currentTarget)
|
|
1035
|
-
.style(STROKE_COLOR, lineStyle.highlight.color)
|
|
1036
|
-
.style(STROKE_WIDTH, lineStyle.highlight.width)
|
|
1037
|
-
.style(STROKE_OPACITY, lineStyle.highlight.opacity)
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
if (mouseOverHandlerFor && allowTooltip) {
|
|
1184
|
+
if (mouseOverHandlerFor) {
|
|
1041
1185
|
// the contract for the mouse over handler is for a series
|
|
1042
|
-
mouseOverHandlerFor(categoryName, value, {series: selectedData, metadata: seriesStats},
|
|
1186
|
+
mouseOverHandlerFor(categoryName, value, {series: selectedData, metadata: seriesStats}, mouseCoords)
|
|
1043
1187
|
}
|
|
1044
1188
|
}
|
|
1045
1189
|
|
|
1046
1190
|
/**
|
|
1047
|
-
*
|
|
1191
|
+
* Calls the mouse-leave-series handler registered for this series. Replaces the old version, which
|
|
1192
|
+
* also reset the hovered SVG element's stroke color/width directly; that reset is now implicit --
|
|
1193
|
+
* once the hover state no longer matches this element, the next redraw simply paints it in its
|
|
1194
|
+
* normal (non-highlighted) style.
|
|
1048
1195
|
* @param seriesName The name of the series (i.e. the neuron ID)
|
|
1049
|
-
* @param segment The SVG line element representing the spike, over which the mouse is hovering.
|
|
1050
|
-
* @param seriesStyles The styles for the series (for (un)highlighting)
|
|
1051
|
-
* @param defaultBarSeriesStyle The default bar series style that is used if no style is found for the series
|
|
1052
1196
|
* @param mouseLeaverHandlerFor Registered handler for the series when the mouse leaves
|
|
1053
|
-
* @param tooltipProvider The tooltip provider ID for the mouse-over event
|
|
1054
1197
|
*/
|
|
1055
1198
|
function handleMouseLeaveSeries(
|
|
1056
1199
|
seriesName: string,
|
|
1057
|
-
segment: SVGLineElement,
|
|
1058
|
-
seriesStyles: Map<string, BarSeriesStyle>,
|
|
1059
|
-
defaultBarSeriesStyle: BarSeriesStyle,
|
|
1060
1200
|
mouseLeaverHandlerFor: ((seriesName: string) => void) | undefined,
|
|
1061
|
-
tooltipProvider?: string
|
|
1062
1201
|
): void {
|
|
1063
|
-
const barSeriesStyle = seriesStyles.get(seriesName) || defaultBarSeriesStyle
|
|
1064
|
-
const lineStyle = lineStyleFor(tooltipProvider, barSeriesStyle)
|
|
1065
|
-
if (lineStyle !== undefined) {
|
|
1066
|
-
d3.select<SVGPathElement, Datum>(segment)
|
|
1067
|
-
.style(STROKE_COLOR, lineStyle.regular.color)
|
|
1068
|
-
.style(STROKE_WIDTH, lineStyle.regular.width)
|
|
1069
|
-
.style(STROKE_OPACITY, lineStyle.regular.opacity)
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
1202
|
if (mouseLeaverHandlerFor) {
|
|
1073
1203
|
mouseLeaverHandlerFor(seriesName)
|
|
1074
1204
|
}
|
|
1075
1205
|
}
|
|
1076
|
-
|
|
1077
|
-
function lineStyleFor(tooltipProvider: string | undefined, barSeriesStyle: BarSeriesStyle): LineStyle | undefined {
|
|
1078
|
-
const {valueLine, windowedMeanValueLine} = barSeriesStyle
|
|
1079
|
-
switch (tooltipProvider) {
|
|
1080
|
-
case BAR_CHART_TOOLTIP_PROVIDER_IDS.currentValue:
|
|
1081
|
-
return valueLine
|
|
1082
|
-
case BAR_CHART_TOOLTIP_PROVIDER_IDS.windowedMeanValue:
|
|
1083
|
-
return windowedMeanValueLine
|
|
1084
|
-
default:
|
|
1085
|
-
return undefined
|
|
1086
|
-
}
|
|
1087
|
-
}
|