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
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import {useCallback, useEffect, useRef} from 'react'
|
|
2
|
+
import {useStreamingTimeAxes} from "./useStreamingTimeAxes";
|
|
3
|
+
import {useHoveredSeries} from "../hooks/useHoveredSeries";
|
|
4
|
+
import * as d3 from "d3"
|
|
5
|
+
|
|
6
|
+
import {useChart} from "../hooks/useChart"
|
|
7
|
+
import {useDataSource, useDataSourceRunning, useInitialData} from "../hooks/useDataSource";
|
|
8
|
+
import {usePlotDimensions} from "../hooks/usePlotDimensions"
|
|
9
|
+
import {type AxesAssignment, clipToArea} from "./plot"
|
|
10
|
+
import type {CanvasContext} from "../d3types"
|
|
11
|
+
import {seriesAt, canvasLocalPoint, type SeriesGeometry} from "./hitTesting"
|
|
12
|
+
import {noop} from "../utils"
|
|
13
|
+
import {ContinuousAxisRange} from "../axes/ContinuousAxisRange"
|
|
14
|
+
import {extentWith, visibleExtent} from "../axes/autoScale"
|
|
15
|
+
import {growAutoScaledYAxes, useAutoScaleRestart} from "./autoScaleYAxes"
|
|
16
|
+
import {
|
|
17
|
+
type BaseAxis,
|
|
18
|
+
AxisType,
|
|
19
|
+
checkAxisType,
|
|
20
|
+
type ContinuousNumericAxis,
|
|
21
|
+
defaultLineStyle,
|
|
22
|
+
type SeriesLineStyle
|
|
23
|
+
} from "../axes/axes"
|
|
24
|
+
import {outlierAxisTimes, TimeWindowBehavior} from "../subscriptions/viewDrivers";
|
|
25
|
+
import type {OutlierChartData} from "../observables/outliers"
|
|
26
|
+
import type {OutlierDatum, OutlierSeries} from "../series/outlierSeries"
|
|
27
|
+
import {FastShiftArray} from "fast-shift-array";
|
|
28
|
+
|
|
29
|
+
type OutlierDatumColor<M extends readonly number[]> = { datum: OutlierDatum<M>, color: string }
|
|
30
|
+
|
|
31
|
+
export interface Props {
|
|
32
|
+
/**
|
|
33
|
+
* Holds the mapping between a series and the axis it uses (is assigned). The
|
|
34
|
+
* map's key holds the series name, and the value is an {@link AxesAssignment}
|
|
35
|
+
* object holding the ID of the assigned x-axis and y-axis.
|
|
36
|
+
*/
|
|
37
|
+
axisAssignments?: Map<string, AxesAssignment>
|
|
38
|
+
/**
|
|
39
|
+
* The line interpolation curve factory. See the d3 documentation for curves at
|
|
40
|
+
* {@link https://github.com/d3/d3-shape#curves} for information on available interpolations.
|
|
41
|
+
*/
|
|
42
|
+
interpolation?: d3.CurveFactory
|
|
43
|
+
/**
|
|
44
|
+
* Whether to enable panning. Defaults to false.
|
|
45
|
+
*/
|
|
46
|
+
panEnabled?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* Whether to enable zooming. Defaults to false.
|
|
49
|
+
*/
|
|
50
|
+
zoomEnabled?: boolean
|
|
51
|
+
/**
|
|
52
|
+
* Whether to require the shift or ctrl key to be held down when zooming. Defaults to true.
|
|
53
|
+
*/
|
|
54
|
+
zoomKeyModifiersRequired?: boolean
|
|
55
|
+
/**
|
|
56
|
+
* Behavior for time windowing (see {@link TimeWindowBehavior}). Defaults to `SCROLL`. Ignored
|
|
57
|
+
* when `withCadenceOf` is set.
|
|
58
|
+
*/
|
|
59
|
+
timeWindowBehavior?: TimeWindowBehavior
|
|
60
|
+
/**
|
|
61
|
+
* When set, uses a cadence with the specified refresh period (in milliseconds). For plots
|
|
62
|
+
* where the updates are slow (> 100 ms) using a cadence of 10 to 25 ms smooths out the
|
|
63
|
+
* updates and makes the plot updates look cleaner. When updates are around 25 ms or less,
|
|
64
|
+
* then setting the cadence period too small will result in poor update performance. Generally
|
|
65
|
+
* at high update speeds, the cadence is unnecessary. Finally, using cadence, sets the max time
|
|
66
|
+
* to the current time -- this is what causes the x-axis to keep scrolling once the data
|
|
67
|
+
* reaches the right-hand edge, even if new data hasn't arrived yet.
|
|
68
|
+
*/
|
|
69
|
+
withCadenceOf?: number
|
|
70
|
+
/**
|
|
71
|
+
* Base fill-opacity for the outermost (widest) outlier band. Inner (narrower / more
|
|
72
|
+
* confident) bands are rendered with progressively higher opacity, capped at 1. Defaults to 0.15.
|
|
73
|
+
*/
|
|
74
|
+
bandOpacity?: number
|
|
75
|
+
/**
|
|
76
|
+
* Optional per-band opacity step added for each more-confident (lower-index) band. Defaults to 0.12.
|
|
77
|
+
*/
|
|
78
|
+
bandOpacityStep?: number
|
|
79
|
+
/**
|
|
80
|
+
* Radius (px) of the circle marker drawn at each datum on the central line, while the data
|
|
81
|
+
* source is paused. When omitted, no markers are drawn.
|
|
82
|
+
*/
|
|
83
|
+
markerRadius?: number
|
|
84
|
+
/**
|
|
85
|
+
* Colors for the outlier marker drawn on any point that falls outside one or more bounds.
|
|
86
|
+
* The array is indexed by bound (i.e. by measure), so `outlierMarkerColors[i]` is used when
|
|
87
|
+
* the point is outside `bounds[i]`. When a point is outside multiple bounds, the color
|
|
88
|
+
* associated with the largest exceeded bound wins. When omitted, outlier markers fall back
|
|
89
|
+
* to red.
|
|
90
|
+
*/
|
|
91
|
+
outlierMarkerColors?: ReadonlyArray<string>
|
|
92
|
+
/**
|
|
93
|
+
* When true, hovering over a series also highlights the x- and y-axes it is plotted
|
|
94
|
+
* against (in the series' own highlight color/width). Defaults to `false`.
|
|
95
|
+
*/
|
|
96
|
+
highlightAxesOnMouseOver?: boolean
|
|
97
|
+
/**
|
|
98
|
+
* Called whenever this plot (re)creates its zoom behavior, handing
|
|
99
|
+
* the caller a `resetZoom` function that programmatically clears d3-zoom's own accumulated
|
|
100
|
+
* scale/pan state back to identity. See {@link ScatterPlot}'s identical prop for the full
|
|
101
|
+
* explanation of why this is needed. Only meaningful (called) while `zoomEnabled` is true.
|
|
102
|
+
*/
|
|
103
|
+
onZoomReset?: (resetZoom: () => void) => void
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Metadata carried through the tooltip system when the user hovers over an outlier band.
|
|
108
|
+
*/
|
|
109
|
+
export interface OutlierBandTooltipMetadata<M extends readonly number[] = readonly number[]> {
|
|
110
|
+
/**
|
|
111
|
+
* The outlier datum
|
|
112
|
+
*/
|
|
113
|
+
datum?: OutlierDatum<M>
|
|
114
|
+
/**
|
|
115
|
+
* The measure (confidence level) associated with the hovered band.
|
|
116
|
+
*/
|
|
117
|
+
upperMeasure?: number
|
|
118
|
+
/**
|
|
119
|
+
* The measure (confidence level) associated with the band below the hovered band.
|
|
120
|
+
*/
|
|
121
|
+
lowerMeasure?: number
|
|
122
|
+
/**
|
|
123
|
+
* The index of the hovered band (0 = tightest / most confident).
|
|
124
|
+
*/
|
|
125
|
+
bandIndex: number
|
|
126
|
+
/**
|
|
127
|
+
* Number of visible points whose y-value falls within the band bounds.
|
|
128
|
+
*/
|
|
129
|
+
pointsInBand: number
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A band's fill region, captured at draw time for `isPointInPath`-based hover hit-testing.
|
|
134
|
+
* Canvas has no per-shape events, and a filled, possibly-curved area isn't well modeled by
|
|
135
|
+
* `hitTesting.ts`'s line/segment/rect primitives, so bands use `ctx.isPointInPath` directly
|
|
136
|
+
* instead -- this is what canvas is built for.
|
|
137
|
+
*/
|
|
138
|
+
interface BandHitRegion<M extends readonly number[]> {
|
|
139
|
+
path: Path2D
|
|
140
|
+
seriesName: string
|
|
141
|
+
bandIndex: number
|
|
142
|
+
upperMeasure: number | undefined
|
|
143
|
+
lowerMeasure: number | undefined
|
|
144
|
+
plotData: FastShiftArray<OutlierDatum<M>>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Renders a streaming outlier plot. Each series consists of {@link OutlierDatum} points, where every
|
|
149
|
+
* datum carries an (x, y) value plus a set of (lower, upper) bounds — one per measure. The plot
|
|
150
|
+
* draws the central y-line for the series and a translucent band per measure, filled between
|
|
151
|
+
* the corresponding lower and upper bounds. The x-axis scrolls as new points arrive past the
|
|
152
|
+
* end of the visible window, in the same fashion as {@link ScatterPlot}.
|
|
153
|
+
*
|
|
154
|
+
* Internally, this no longer creates/updates SVG `<path>`/`<circle>` elements. Instead, it
|
|
155
|
+
* registers a single draw function with the chart's {@link CanvasContext} that redraws every
|
|
156
|
+
* series' bands, line, and markers from scratch each time the canvas repaints. Band hover uses
|
|
157
|
+
* `ctx.isPointInPath` against the band's actual filled path (captured each draw); outlier-marker
|
|
158
|
+
* hover uses the shared point-hit-testing in `hitTesting.ts`, same as {@link ScatterPlot}'s markers.
|
|
159
|
+
* When the data source is paused, hovering over a band shows the tooltip (when there's one); a
|
|
160
|
+
* y-axis with `autoScale` set grows to fit the data on screen (bands included).
|
|
161
|
+
* @param props The properties associated with the outlier plot
|
|
162
|
+
* @return `null` (the plot draws onto the chart's canvas)
|
|
163
|
+
* @template M The type of the measures (a tuple of numbers)
|
|
164
|
+
*/
|
|
165
|
+
export function OutlierPlot<M extends readonly number[] = readonly number[]>(props: Props): null {
|
|
166
|
+
const {
|
|
167
|
+
chartId,
|
|
168
|
+
canvasContext,
|
|
169
|
+
axes,
|
|
170
|
+
seriesStyles,
|
|
171
|
+
seriesFilter,
|
|
172
|
+
mouse,
|
|
173
|
+
} = useChart<OutlierDatum<M>, SeriesLineStyle, OutlierBandTooltipMetadata<M>, ContinuousAxisRange, ContinuousNumericAxis>()
|
|
174
|
+
const {hoveredSeriesName} = useHoveredSeries()
|
|
175
|
+
|
|
176
|
+
const {mouseOverHandlerFor, mouseLeaveHandlerFor} = mouse
|
|
177
|
+
|
|
178
|
+
const {initialData} = useInitialData<OutlierDatum<M>>()
|
|
179
|
+
|
|
180
|
+
const {
|
|
181
|
+
xAxesState,
|
|
182
|
+
yAxesState,
|
|
183
|
+
updateAxisRanges = noop,
|
|
184
|
+
axisRangeFor,
|
|
185
|
+
} = axes
|
|
186
|
+
|
|
187
|
+
const {plotDimensions, margin} = usePlotDimensions()
|
|
188
|
+
|
|
189
|
+
// the application-owned source of the data, and whether it's running (streaming)
|
|
190
|
+
const dataSource = useDataSource<OutlierChartData<M>, OutlierDatum<M>, OutlierSeries<M>>()
|
|
191
|
+
const running = useDataSourceRunning(dataSource)
|
|
192
|
+
|
|
193
|
+
const {
|
|
194
|
+
axisAssignments = new Map<string, AxesAssignment>(),
|
|
195
|
+
interpolation = d3.curveLinear,
|
|
196
|
+
panEnabled = false,
|
|
197
|
+
zoomEnabled = false,
|
|
198
|
+
zoomKeyModifiersRequired = true,
|
|
199
|
+
timeWindowBehavior = TimeWindowBehavior.SCROLL,
|
|
200
|
+
withCadenceOf,
|
|
201
|
+
bandOpacity = 0.15,
|
|
202
|
+
bandOpacityStep = 0.12,
|
|
203
|
+
markerRadius,
|
|
204
|
+
outlierMarkerColors,
|
|
205
|
+
highlightAxesOnMouseOver = false,
|
|
206
|
+
onZoomReset = noop,
|
|
207
|
+
} = props
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
// seriesRef is the single source of truth for what to render: the data source's own (live)
|
|
211
|
+
// series map, which grows as series first appear in the stream. the draw function iterates this
|
|
212
|
+
// directly (rather than a parallel dataRef array) so dynamically-arriving series get rendered.
|
|
213
|
+
const seriesRef = useRef<ReadonlyMap<string, OutlierSeries<M>>>(
|
|
214
|
+
dataSource?.series ?? new Map(initialData.map(series => [series.name, series as OutlierSeries<M>]))
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
// captured band fill-regions and outlier-marker geometry, in canvas coordinates, used for
|
|
218
|
+
// hit-testing mouse hover on `mousemove` (see the effect below that wires up the listener)
|
|
219
|
+
const bandsRef = useRef<Array<BandHitRegion<M>>>([])
|
|
220
|
+
const outlierGeometryRef = useRef<Map<string, SeriesGeometry>>(new Map())
|
|
221
|
+
const outlierDatumsRef = useRef<Map<string, Array<OutlierDatumColor<M>>>>(new Map())
|
|
222
|
+
// what's currently hovered: a band (series + band index) or an outlier point (series + point
|
|
223
|
+
// index) -- mutually exclusive, so we know when to fire a "leave" before an "over"
|
|
224
|
+
const lastHoveredRef = useRef<{kind: 'band', seriesName: string, bandIndex: number} | {kind: 'outlier', seriesName: string, index: number} | undefined>(undefined)
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* (Re-)registers this plot's draw function with the canvas context and requests a redraw.
|
|
228
|
+
* Replaces the old version, which directly mutated SVG `<path>`/`<circle>` elements bound via
|
|
229
|
+
* d3's enter/update/exit join. Canvas has no persistent elements to join against, so the draw
|
|
230
|
+
* function just redraws every band/line/marker from current data/scale state each time it's
|
|
231
|
+
* invoked.
|
|
232
|
+
*
|
|
233
|
+
* Pan/zoom behavior setup lives in a separate effect (see below), not here -- this function
|
|
234
|
+
* runs on every data tick (each `windowingTime` interval), and recreating/reattaching a
|
|
235
|
+
* `d3.drag()`/`d3.zoom()` behavior that often is pure overhead unrelated to drawing the new
|
|
236
|
+
* data.
|
|
237
|
+
* @param cc The canvas context to register the draw function with
|
|
238
|
+
*/
|
|
239
|
+
const updatePlot = useCallback(
|
|
240
|
+
(cc: CanvasContext) => {
|
|
241
|
+
const draw = (context: CanvasContext) => {
|
|
242
|
+
const {context2D} = context
|
|
243
|
+
|
|
244
|
+
context2D.save()
|
|
245
|
+
clipToArea(context, plotDimensions, {x: margin.left, y: margin.top})
|
|
246
|
+
context2D.translate(margin.left, margin.top)
|
|
247
|
+
|
|
248
|
+
const newBands: Array<BandHitRegion<M>> = []
|
|
249
|
+
const newOutlierGeometry = new Map<string, SeriesGeometry>()
|
|
250
|
+
const newOutlierDatums = new Map<string, Array<OutlierDatumColor<M>>>()
|
|
251
|
+
|
|
252
|
+
seriesRef.current.forEach(series => {
|
|
253
|
+
const [xAxis, yAxis] = axesFor(
|
|
254
|
+
series.name,
|
|
255
|
+
axisAssignments,
|
|
256
|
+
id => xAxesState.axisFor(id).getOrUndefined(),
|
|
257
|
+
id => yAxesState.axisFor(id).getOrUndefined()
|
|
258
|
+
)
|
|
259
|
+
if (xAxis === undefined || yAxis === undefined) return
|
|
260
|
+
|
|
261
|
+
const style = seriesStyles.get(series.name) ?? defaultLineStyle()
|
|
262
|
+
const plotData = series.name.match(seriesFilter) ? series.data : FastShiftArray.empty<OutlierDatum<M>>()
|
|
263
|
+
const numBands = plotData.length > 0 ? plotData[0].bounds.length : 0
|
|
264
|
+
|
|
265
|
+
// render the widest (highest-index) band first so the narrower, more-confident
|
|
266
|
+
// bands stack on top with darker opacity
|
|
267
|
+
for (let bandIndex = numBands - 1; bandIndex >= 0; bandIndex--) {
|
|
268
|
+
const opacity = Math.min(1, bandOpacity + (numBands - 1 - bandIndex) * bandOpacityStep)
|
|
269
|
+
const areaGen = d3.area<OutlierDatum<M>>()
|
|
270
|
+
.x(d => xAxis.scale(d.datum.x) || 0)
|
|
271
|
+
.y0(d => yAxis.scale(d.bounds[bandIndex].lower) || 0)
|
|
272
|
+
.y1(d => yAxis.scale(d.bounds[bandIndex].upper) || 0)
|
|
273
|
+
.curve(interpolation)
|
|
274
|
+
const upperMeasure = series.measures[bandIndex]
|
|
275
|
+
const lowerMeasure = bandIndex > 0 ? series.measures[bandIndex - 1] : undefined
|
|
276
|
+
|
|
277
|
+
const path = new Path2D(areaGen(Array.from(plotData)) ?? "")
|
|
278
|
+
context2D.fillStyle = style.color
|
|
279
|
+
context2D.globalAlpha = opacity
|
|
280
|
+
context2D.fill(path)
|
|
281
|
+
context2D.globalAlpha = 1
|
|
282
|
+
|
|
283
|
+
// pushed in draw order (widest first, narrowest/topmost last); hit-testing
|
|
284
|
+
// iterates this backwards so the topmost band wins, matching visual stacking
|
|
285
|
+
newBands.push({path, seriesName: series.name, bandIndex, upperMeasure, lowerMeasure, plotData})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// central line for the series y-value
|
|
289
|
+
const isHovered = hoveredSeriesName === series.name
|
|
290
|
+
const stroke = isHovered ? style.highlightColor : style.color
|
|
291
|
+
const strokeWidth = isHovered ? style.highlightWidth : style.lineWidth
|
|
292
|
+
const lineGen = d3.line<OutlierDatum<M>>()
|
|
293
|
+
.x(d => xAxis.scale(d.datum.x) || 0)
|
|
294
|
+
.y(d => yAxis.scale(d.datum.y) || 0)
|
|
295
|
+
.curve(interpolation)
|
|
296
|
+
|
|
297
|
+
context2D.strokeStyle = stroke
|
|
298
|
+
context2D.lineWidth = strokeWidth
|
|
299
|
+
context2D.stroke(new Path2D(lineGen(Array.from(plotData)) ?? ""))
|
|
300
|
+
|
|
301
|
+
// for the markers, we split the data into two categories: regular and outlier
|
|
302
|
+
const {regular, outlier} = categorizePoints(plotData, outlierMarkerColors)
|
|
303
|
+
|
|
304
|
+
// point markers (one circle per datum) -- decorative only, no hover/tooltip
|
|
305
|
+
if (markerRadius != null && markerRadius >= 0 && !running) {
|
|
306
|
+
context2D.fillStyle = stroke
|
|
307
|
+
regular.forEach(d => {
|
|
308
|
+
const x = xAxis.scale(d.datum.x) || 0
|
|
309
|
+
const y = yAxis.scale(d.datum.y) || 0
|
|
310
|
+
context2D.beginPath()
|
|
311
|
+
context2D.arc(x, y, markerRadius, 0, 2 * Math.PI)
|
|
312
|
+
context2D.fill()
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// outlier markers -- these DO get hover/tooltip, so we record their screen
|
|
317
|
+
// positions (and the underlying datum, for the tooltip) for hit-testing
|
|
318
|
+
const outlierPoints: Array<[number, number]> = []
|
|
319
|
+
outlier.forEach(o => {
|
|
320
|
+
const x = xAxis.scale(o.datum.datum.x) || 0
|
|
321
|
+
const y = yAxis.scale(o.datum.datum.y) || 0
|
|
322
|
+
context2D.fillStyle = o.color
|
|
323
|
+
context2D.beginPath()
|
|
324
|
+
context2D.arc(x, y, 4, 0, 2 * Math.PI)
|
|
325
|
+
context2D.fill()
|
|
326
|
+
outlierPoints.push([x + margin.left, y + margin.top])
|
|
327
|
+
})
|
|
328
|
+
newOutlierGeometry.set(`${series.name}::outlier`, {
|
|
329
|
+
points: outlierPoints,
|
|
330
|
+
hitRadius: 6
|
|
331
|
+
})
|
|
332
|
+
newOutlierDatums.set(series.name, outlier)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
bandsRef.current = newBands
|
|
336
|
+
outlierGeometryRef.current = newOutlierGeometry
|
|
337
|
+
outlierDatumsRef.current = newOutlierDatums
|
|
338
|
+
|
|
339
|
+
context2D.restore()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Grows the auto-scaling y-axes (see `ContinuousAxis`'s `autoScale` prop) to fit the
|
|
344
|
+
* data that's on screen: for each shown series (those matching the filter), the values
|
|
345
|
+
* and the bands' lower and upper bounds of its points within the x-axis' current
|
|
346
|
+
* window, along with where their lines cross the window's edges (see `visibleExtent`).
|
|
347
|
+
* Since an axis only ever grows, only the visible data needs checking each frame.
|
|
348
|
+
* Registered to run before the axes draw (they're at z-index 0), so the axes and the
|
|
349
|
+
* series are drawn with the grown domain in the same frame.
|
|
350
|
+
*/
|
|
351
|
+
const autoScaleYAxes = (): void => {
|
|
352
|
+
const extents = new Map<string, [min: number, max: number]>()
|
|
353
|
+
seriesRef.current.forEach(series => {
|
|
354
|
+
if (!series.name.match(seriesFilter)) return
|
|
355
|
+
const [xAxis, yAxis] = axesFor(
|
|
356
|
+
series.name,
|
|
357
|
+
axisAssignments,
|
|
358
|
+
id => xAxesState.axisFor(id).getOrUndefined(),
|
|
359
|
+
id => yAxesState.axisFor(id).getOrUndefined()
|
|
360
|
+
)
|
|
361
|
+
if (xAxis === undefined || yAxis?.autoScale === undefined) return
|
|
362
|
+
|
|
363
|
+
const [start, end] = xAxis.scale.domain()
|
|
364
|
+
// (the series' line and its bands' edges are always drawn as lines)
|
|
365
|
+
const seriesExtent = visibleExtent(
|
|
366
|
+
series.data, [start, end], yAxis.scale, true,
|
|
367
|
+
point => point.datum.x,
|
|
368
|
+
point => [point.datum.y, ...point.bounds.flatMap(({lower, upper}) => [lower, upper])]
|
|
369
|
+
)
|
|
370
|
+
if (seriesExtent === undefined) return
|
|
371
|
+
const extent = extents.get(yAxis.axisId)
|
|
372
|
+
extents.set(
|
|
373
|
+
yAxis.axisId,
|
|
374
|
+
extent === undefined ? seriesExtent : extentWith(extentWith(extent, seriesExtent[0]), seriesExtent[1])
|
|
375
|
+
)
|
|
376
|
+
})
|
|
377
|
+
growAutoScaledYAxes(extents, {yAxes: yAxesState.axes, axisRangeFor, updateAxisRanges})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
cc.register(`outlier-plot-autoscale-${chartId}`, autoScaleYAxes, -10)
|
|
381
|
+
cc.register(`outlier-plot-${chartId}`, draw, 10)
|
|
382
|
+
cc.requestRedraw()
|
|
383
|
+
},
|
|
384
|
+
// note: my WebStorm claims that bandOpacityStep is not a dependency, but it is and the
|
|
385
|
+
// linter flags is as missing when it is removed
|
|
386
|
+
[
|
|
387
|
+
chartId, plotDimensions, axisAssignments, xAxesState, yAxesState, seriesStyles, seriesFilter,
|
|
388
|
+
interpolation, bandOpacity, bandOpacityStep, markerRadius, outlierMarkerColors, hoveredSeriesName, running,
|
|
389
|
+
axisRangeFor, updateAxisRanges, margin.left, margin.top
|
|
390
|
+
]
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
// new data, new scale: restarts the auto-scaling y-axes when the data source is replaced
|
|
394
|
+
useAutoScaleRestart(dataSource, {yAxes: yAxesState.axes, axisRangeFor, updateAxisRanges})
|
|
395
|
+
|
|
396
|
+
// the time axes: their ranges, the view driver, panning and zooming, highlighting, and
|
|
397
|
+
// reporting the axes' intervals -- see `useStreamingTimeAxes`
|
|
398
|
+
useStreamingTimeAxes<OutlierChartData<M>, OutlierDatum<M>>({
|
|
399
|
+
canvasContext,
|
|
400
|
+
dataSource,
|
|
401
|
+
running,
|
|
402
|
+
initialData,
|
|
403
|
+
timeOf: datum => datum.datum.x,
|
|
404
|
+
axisTimesFor: outlierAxisTimes<M>(axisAssignments, xAxesState),
|
|
405
|
+
axisAssignments,
|
|
406
|
+
updatePlot,
|
|
407
|
+
onDataReset: () => {
|
|
408
|
+
seriesRef.current = dataSource?.series ?? new Map(initialData.map(series => [series.name, series as OutlierSeries<M>]))
|
|
409
|
+
},
|
|
410
|
+
panEnabled,
|
|
411
|
+
zoomEnabled,
|
|
412
|
+
zoomKeyModifiersRequired,
|
|
413
|
+
onZoomReset,
|
|
414
|
+
withCadenceOf,
|
|
415
|
+
timeWindowBehavior,
|
|
416
|
+
highlightAxesOnMouseOver,
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
// wires up a single mousemove/mouseleave listener on the shared canvas to replace the old
|
|
420
|
+
// per-element SVG mouseover/mouseleave handlers on the band paths and outlier-marker circles.
|
|
421
|
+
// Bands are hit-tested via `ctx.isPointInPath` against their captured fill path (checked
|
|
422
|
+
// topmost/narrowest-first, matching visual stacking); outlier markers use the shared
|
|
423
|
+
// point-hit-testing in `hitTesting.ts`. Outlier markers are checked first since they're drawn
|
|
424
|
+
// on top of the bands.
|
|
425
|
+
useEffect(
|
|
426
|
+
() => {
|
|
427
|
+
if (!canvasContext) return
|
|
428
|
+
|
|
429
|
+
const canvas = canvasContext.canvas
|
|
430
|
+
const {context2D, dpr} = canvasContext
|
|
431
|
+
|
|
432
|
+
const handleMove = (event: MouseEvent) => {
|
|
433
|
+
const [x, y] = canvasLocalPoint(event, canvas)
|
|
434
|
+
|
|
435
|
+
// check outlier markers first (drawn on top of the bands)
|
|
436
|
+
const outlierHit = seriesAt(x, y, outlierGeometryRef.current)
|
|
437
|
+
// The band's `Path2D` is built from raw (pre-margin) local coordinates -- the same
|
|
438
|
+
// space `x - margin.left, y - margin.top` (the "local" point) is in -- but
|
|
439
|
+
// `ctx.isPointInPath` only reported correct hits once that local point was ALSO
|
|
440
|
+
// scaled by `dpr`. (An earlier version of this instead dropped the margin
|
|
441
|
+
// subtraction and scaled the raw canvas-relative point by `dpr` -- that's wrong:
|
|
442
|
+
// verified against the actual rendered band, rigorously, by isolating a single
|
|
443
|
+
// band layer's on-screen pixels via `getImageData` and testing candidate query
|
|
444
|
+
// points against the real `Path2D` object captured from a live hover. Dropping the
|
|
445
|
+
// margin subtraction was close enough to pass a single, less careful spot-check,
|
|
446
|
+
// but consistently mislocated the hit region by `margin.left`/`margin.top`, which
|
|
447
|
+
// is what produced "hover ~margin-worth of pixels to the side of the band" in
|
|
448
|
+
// practice.) The exact reason `dpr` needs to be reapplied here, on top of a
|
|
449
|
+
// transform that already includes it, isn't fully pinned down -- this is the
|
|
450
|
+
// empirically-verified formula, not a re-derivation from the Canvas 2D spec.
|
|
451
|
+
const localX = (x - margin.left) * dpr
|
|
452
|
+
const localY = (y - margin.top) * dpr
|
|
453
|
+
const current = outlierHit !== undefined ?
|
|
454
|
+
{kind: 'outlier' as const, seriesName: outlierHit.name.replace(/::outlier$/, ''), index: outlierHit.index} :
|
|
455
|
+
findHoveredBand(bandsRef.current, context2D, localX, localY)
|
|
456
|
+
|
|
457
|
+
const previous = lastHoveredRef.current
|
|
458
|
+
const sameAsBefore = previous !== undefined && current !== undefined &&
|
|
459
|
+
previous.kind === current.kind && previous.seriesName === current.seriesName &&
|
|
460
|
+
((previous.kind === 'band' && current.kind === 'band' && previous.bandIndex === current.bandIndex) ||
|
|
461
|
+
(previous.kind === 'outlier' && current.kind === 'outlier' && previous.index === current.index))
|
|
462
|
+
|
|
463
|
+
if (sameAsBefore) return
|
|
464
|
+
|
|
465
|
+
if (previous !== undefined) {
|
|
466
|
+
mouseLeaveHandlerFor(`tooltip-${chartId}`)?.(previous.seriesName)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (current !== undefined) {
|
|
470
|
+
if (current.kind === 'band') {
|
|
471
|
+
const band = bandsRef.current.find(b => b.seriesName === current.seriesName && b.bandIndex === current.bandIndex)
|
|
472
|
+
if (band !== undefined && band.upperMeasure != null) {
|
|
473
|
+
const pointsInBand = calcPointsInBand(band.plotData, band.bandIndex)
|
|
474
|
+
mouseOverHandlerFor(`tooltip-${chartId}`)?.(
|
|
475
|
+
current.seriesName,
|
|
476
|
+
current.bandIndex,
|
|
477
|
+
{
|
|
478
|
+
series: band.plotData,
|
|
479
|
+
metadata: {
|
|
480
|
+
upperMeasure: band.upperMeasure,
|
|
481
|
+
lowerMeasure: band.lowerMeasure,
|
|
482
|
+
bandIndex: current.bandIndex,
|
|
483
|
+
pointsInBand,
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
[x, y]
|
|
487
|
+
)
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
const series = seriesRef.current.get(current.seriesName)
|
|
491
|
+
const outlierDatum = outlierDatumsRef.current.get(current.seriesName)?.[current.index]
|
|
492
|
+
if (series !== undefined && outlierDatum !== undefined) {
|
|
493
|
+
const plotData = series.name.match(seriesFilter) ? series.data : FastShiftArray.empty<OutlierDatum<M>>()
|
|
494
|
+
const datum = outlierDatum.datum
|
|
495
|
+
const bandIndex = largestExceededBoundIndex(datum) + 1
|
|
496
|
+
const upperMeasure = bandIndex < series.measures.length ? series.measures[bandIndex] : undefined
|
|
497
|
+
const lowerMeasure = bandIndex > 0 ? series.measures[bandIndex - 1] : undefined
|
|
498
|
+
const pointsInBand = calcPointsInBand(plotData, bandIndex)
|
|
499
|
+
mouseOverHandlerFor(`tooltip-${chartId}`)?.(
|
|
500
|
+
current.seriesName,
|
|
501
|
+
datum.datum.x,
|
|
502
|
+
{
|
|
503
|
+
series: series.data,
|
|
504
|
+
metadata: {datum, upperMeasure, lowerMeasure, bandIndex, pointsInBand}
|
|
505
|
+
},
|
|
506
|
+
[x, y]
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
lastHoveredRef.current = current
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const handleLeaveCanvas = () => {
|
|
516
|
+
const previous = lastHoveredRef.current
|
|
517
|
+
if (previous !== undefined) {
|
|
518
|
+
mouseLeaveHandlerFor(`tooltip-${chartId}`)?.(previous.seriesName)
|
|
519
|
+
lastHoveredRef.current = undefined
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
canvas.addEventListener('mousemove', handleMove)
|
|
524
|
+
canvas.addEventListener('mouseleave', handleLeaveCanvas)
|
|
525
|
+
return () => {
|
|
526
|
+
canvas.removeEventListener('mousemove', handleMove)
|
|
527
|
+
canvas.removeEventListener('mouseleave', handleLeaveCanvas)
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
[canvasContext, chartId, margin, seriesFilter, mouseOverHandlerFor, mouseLeaveHandlerFor]
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
// this plot needs a data source (see the `Chart`'s `dataSource` prop)
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
if (dataSource === undefined) {
|
|
536
|
+
console.error("OutlierPlot requires a data source; set the enclosing Chart's `dataSource` prop")
|
|
537
|
+
}
|
|
538
|
+
}, [dataSource])
|
|
539
|
+
|
|
540
|
+
// unregister this plot's draw function on unmount
|
|
541
|
+
useEffect(
|
|
542
|
+
() => {
|
|
543
|
+
return () => {
|
|
544
|
+
if (canvasContext) {
|
|
545
|
+
canvasContext.unregister(`outlier-plot-autoscale-${chartId}`)
|
|
546
|
+
canvasContext.unregister(`outlier-plot-${chartId}`)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
[canvasContext, chartId]
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
return null
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Finds the topmost (narrowest, most-confident) band whose fill path contains `(localX, localY)`.
|
|
558
|
+
* `bands` is in draw order (widest first, narrowest/topmost last), so this iterates backwards to
|
|
559
|
+
* check the topmost band first, matching visual stacking.
|
|
560
|
+
* @param bands The bands' fill regions, as last drawn, in draw order
|
|
561
|
+
* @param context The canvas 2D context (for `isPointInPath`)
|
|
562
|
+
* @param localX The mouse's x-coordinate, in the coordinates `isPointInPath` tests against (see
|
|
563
|
+
* the caller for why that's the plot-area position scaled by the device-pixel ratio)
|
|
564
|
+
* @param localY The mouse's y-coordinate, in the same coordinates
|
|
565
|
+
* @return The hovered band's series and index, or `undefined` when the mouse isn't over a band
|
|
566
|
+
*/
|
|
567
|
+
function findHoveredBand<M extends readonly number[]>(
|
|
568
|
+
bands: Array<BandHitRegion<M>>,
|
|
569
|
+
context: CanvasRenderingContext2D,
|
|
570
|
+
localX: number,
|
|
571
|
+
localY: number
|
|
572
|
+
): {kind: 'band', seriesName: string, bandIndex: number} | undefined {
|
|
573
|
+
for (let i = bands.length - 1; i >= 0; i--) {
|
|
574
|
+
const band = bands[i]
|
|
575
|
+
if (context.isPointInPath(band.path, localX, localY)) {
|
|
576
|
+
return {kind: 'band', seriesName: band.seriesName, bandIndex: band.bandIndex}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return undefined
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Returns the index of the largest bound the datum's y-value is outside of, or -1 if the value
|
|
584
|
+
* is inside every bound. Bounds are expected to be ordered from tightest (index 0) to widest
|
|
585
|
+
* (highest index), so the highest-index match identifies the most severe outlier tier.
|
|
586
|
+
* @param datum The datum
|
|
587
|
+
* @return The index of the largest bound the datum is outside of, or -1
|
|
588
|
+
*/
|
|
589
|
+
function largestExceededBoundIndex<M extends readonly number[]>(datum: OutlierDatum<M>): number {
|
|
590
|
+
for (let i = datum.bounds.length - 1; i >= 0; i--) {
|
|
591
|
+
const {lower, upper} = datum.bounds[i]
|
|
592
|
+
if (datum.datum.y < lower || datum.datum.y > upper) return i
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return -1
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Attempts to locate the x- and y-axes for the specified series. If the series has no axis
|
|
600
|
+
* assignment, then uses the default (first) x- and y-axes.
|
|
601
|
+
* @param seriesName Name of the series for which to retrieve the axis
|
|
602
|
+
* @param axisAssignments A map holding the series name and the associated x- and y-axes assigned
|
|
603
|
+
* to that series
|
|
604
|
+
* @param xAxisFor The function that accepts an axis ID and returns the corresponding x-axis
|
|
605
|
+
* @param yAxisFor The function that accepts an axis ID and returns the corresponding y-axis
|
|
606
|
+
* @return The x- and y-axes (`undefined` when not found)
|
|
607
|
+
* @throws Error when either axis isn't a continuous numeric axis
|
|
608
|
+
*/
|
|
609
|
+
function axesFor(
|
|
610
|
+
seriesName: string,
|
|
611
|
+
axisAssignments: Map<string, AxesAssignment>,
|
|
612
|
+
xAxisFor: (id: string) => BaseAxis | undefined,
|
|
613
|
+
yAxisFor: (id: string) => BaseAxis | undefined,
|
|
614
|
+
): [xAxis: ContinuousNumericAxis | undefined, yAxis: ContinuousNumericAxis | undefined] {
|
|
615
|
+
const assigned = axisAssignments.get(seriesName)
|
|
616
|
+
const xAxisRaw = xAxisFor(assigned?.xAxis || "")
|
|
617
|
+
const yAxisRaw = yAxisFor(assigned?.yAxis || "")
|
|
618
|
+
checkAxisType("OutlierPlot", "x", xAxisRaw, AxisType.ContinuousNumeric)
|
|
619
|
+
checkAxisType("OutlierPlot", "y", yAxisRaw, AxisType.ContinuousNumeric)
|
|
620
|
+
const xAxis = xAxisRaw as ContinuousNumericAxis | undefined
|
|
621
|
+
const yAxis = yAxisRaw as ContinuousNumericAxis | undefined
|
|
622
|
+
return [xAxis, yAxis]
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Calculate the number of points within a specific outlier band.
|
|
627
|
+
* @param plotData The array of outlier data points.
|
|
628
|
+
* @param bandIndex The index of the outlier band to calculate points for.
|
|
629
|
+
* @param [subtractLowerBandCount=true] Whether to subtract the count of points in the lower band. Defaults to true.
|
|
630
|
+
* @returns The number of points within the specified outlier band.
|
|
631
|
+
*/
|
|
632
|
+
function calcPointsInBand<M extends readonly number[]>(
|
|
633
|
+
plotData: Array<OutlierDatum<M>> | FastShiftArray<OutlierDatum<M>>,
|
|
634
|
+
bandIndex: number,
|
|
635
|
+
subtractLowerBandCount: boolean = true
|
|
636
|
+
): number {
|
|
637
|
+
// function to count the points in a specific outlier band
|
|
638
|
+
const countPointsInBand = (bandIndex: number) =>
|
|
639
|
+
plotData
|
|
640
|
+
.filter(datum => {
|
|
641
|
+
if (bandIndex < datum.bounds.length) {
|
|
642
|
+
return datum.datum.y >= datum.bounds[bandIndex].lower && datum.datum.y <= datum.bounds[bandIndex].upper
|
|
643
|
+
}
|
|
644
|
+
return true
|
|
645
|
+
}
|
|
646
|
+
).length
|
|
647
|
+
|
|
648
|
+
const pointsInBand = countPointsInBand(bandIndex)
|
|
649
|
+
// we need to subtract the points in the lower band. recall that each band has ALL the points
|
|
650
|
+
// in the band's range, not what we may think of visually
|
|
651
|
+
if (bandIndex > 0 && subtractLowerBandCount) {
|
|
652
|
+
return pointsInBand - countPointsInBand(bandIndex - 1)
|
|
653
|
+
}
|
|
654
|
+
return pointsInBand
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
type CategorizedData<M extends readonly number[]> = {
|
|
658
|
+
regular: Array<OutlierDatum<M>>,
|
|
659
|
+
outlier: Array<OutlierDatumColor<M>>
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Categorizes outlier data into regular and outlier points based on provided marker colors.
|
|
664
|
+
* @param data Array of outlier data to categorize
|
|
665
|
+
* @param outlierMarkerColors Array of colors to use for outlier markers
|
|
666
|
+
* @returns Object with regular and outlier data arrays
|
|
667
|
+
* @template M Type of the measure
|
|
668
|
+
*/
|
|
669
|
+
function categorizePoints<M extends readonly number[]>(
|
|
670
|
+
data: FastShiftArray<OutlierDatum<M>>,
|
|
671
|
+
outlierMarkerColors: ReadonlyArray<string> = []
|
|
672
|
+
): CategorizedData<M> {
|
|
673
|
+
return data.reduce<CategorizedData<M>>(
|
|
674
|
+
(accum, datum) => {
|
|
675
|
+
const bandIndex = largestExceededBoundIndex(datum)
|
|
676
|
+
if (bandIndex < 0) {
|
|
677
|
+
accum.regular.push(datum)
|
|
678
|
+
} else {
|
|
679
|
+
accum.outlier.push({datum, color: outlierMarkerColors[bandIndex] ?? "red"})
|
|
680
|
+
}
|
|
681
|
+
return accum
|
|
682
|
+
},
|
|
683
|
+
{regular: [], outlier: []} as CategorizedData<M>
|
|
684
|
+
)
|
|
685
|
+
}
|