stream-charts 1.2.1 → 2.0.1-snapshot
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
package/README.md
CHANGED
|
@@ -1,1225 +1,1012 @@
|
|
|
1
|
-
|
|
2
|
-
[Homepage](https://robphilipp.github.io/stream-charts/) •
|
|
3
|
-
[Code Docs](docs/index.html) •
|
|
4
|
-
[Change History](changes.md) •
|
|
5
|
-
[Example Project](https://github.com/robphilipp/stream-charts-examples)
|
|
6
|
-
|
|
7
|
-
## [⤒](#top) <span id="intro">intro</span>
|
|
8
|
-
`stream-charts` is a [react](https://reactjs.org)-based library for building real-time time-series charts for viewing high frequency data. Ingesting and plotting new data for about 100 time-series every 25 ms isn't generally a problem.
|
|
9
|
-
|
|
10
|
-
The following plot types are currently available:
|
|
11
|
-
|
|
12
|
-
1. **Scatter Plot**: for plotting time-series data.
|
|
13
|
-
2. **Raster Plot**: for plotting event-timing data.
|
|
14
|
-
3. **Bar Plot**: for plotting time-series as bars that show the current value and its minimum, maximum, and mean values for all times and time-windows.
|
|
15
|
-
4. **Poincare Plot**: for plotting function iterates (`f[n](t)` vs `f[n-m](t)`).
|
|
16
|
-
|
|
17
|
-
> Try it on codesandbox [stream-charts](https://codesandbox.io/p/sandbox/n7c8jm)
|
|
18
|
-
|
|
19
|
-
`Charts` are composable using react components. So you can add different axis types, multiple axes, trackers, tooltips, and plots. `stream-charts` provides the following react components from which to build your own charts:
|
|
20
|
-
1. The [Chart](#chart-usage) serves as the container, holding the other components as children, and providing the data to the plot.
|
|
21
|
-
2. The [ContinuousAxis](#continuous-axes-usage) and [OrdinalAxis](#ordinal-axes-usage) components define the x- and y-axes, and provide the scales for the axes. Charts are required to have at least one x-axis and one y-axis.
|
|
22
|
-
3. The optional [Tracker](#tracker-usage) component shows the value associated with the current mouse location of the axes to which it is assigned.
|
|
23
|
-
4. The optional [Tooltip](#tooltip-usage) component shows the `TooltipContent` for the datum under the mouse cursor. Each plot type has a default tooltip content component ([ScatterPlotTooltipContent](#scatterplot-tooltip-usage), [RasterPlotTooltipContent](#rasterplot-tooltip-usage), [BarPlotTooltipContent](#barplot-tooltip-usage), [PoincarePlotTooltipContent](#poincareplot-tooltip-usage)).
|
|
24
|
-
5. The `Plot` component displays the data. Currently, there are four plot components: [ScatterPlot](#scatter-plot-usage), [RasterPlot](#raster-plot-usage), [BarPlot](#bar-plot-usage), and [PoincarePlot](#poincare-plot-usage).
|
|
25
|
-
|
|
26
|
-
### [⤒](#content) <span id="axes">capabilities</span>
|
|
27
|
-
Charts can have multiple x-axes and y-axes, and these axes can have different scales. Time series can be assigned to an axis or use the default one. Currently, there are continuous and ordinal (category) axes. Continuous axes are available with all the scales supported by [d3](https://d3js.org) (for example, linear scales, logarithmic scales, and power scales).
|
|
28
|
-
|
|
29
|
-
Each plot type comes with a default tooltip and tooltip content. However, you can customize the tooltip and tooltip content to suit your needs.
|
|
30
|
-
|
|
31
|
-
The charts are designed to be responsive to changes in size, for example, to support window resizing.
|
|
32
|
-
|
|
33
|
-
With `stream-charts` you can display initial (static) data as well as live data from an [Observable](https://rxjs-dev.firebaseapp.com/api/index/class/Observable).
|
|
34
|
-
|
|
35
|
-
All aspects of the `Charts` style are exposed allowing you to use your favorite frameworks theme provider, or none at all.
|
|
36
|
-
|
|
37
|
-
`Charts` provide zooming and panning that can be enabled or disabled. And zooming is set to use a modifier key by default so that it doesn't interfere with normal scrolling.
|
|
38
|
-
|
|
39
|
-
### [⤒](#top) <span id="content">rendering performance</span>
|
|
40
|
-
Charts can easily ingest and plot new data for about 100 time-series every 25 ms.
|
|
41
|
-
|
|
42
|
-
When using lower update frequencies, such as < 250 ms, and data updates are sparse, you can set an update cadence so that the plot's time smoothly scrolls, rather than updating only when new data arrives.
|
|
43
|
-
|
|
44
|
-
`Charts` can have a data TTL (time-to-live) to drop older data to allow long-running streams of data. With the data-update callback you can capture that data and store it in local storage, memory, or a location of your choice.
|
|
45
|
-
|
|
46
|
-
To achieve this type of performance, the charts:
|
|
47
|
-
1. accept an [rxjs](https://rxjs-dev.firebaseapp.com) [Observable](https://rxjs-dev.firebaseapp.com/api/index/class/Observable) as the source of the time-series data, and
|
|
48
|
-
2. are implemented using [d3](https://d3js.org) SVG elements wrapped in [react](https://reactjs.org) functional components, and
|
|
49
|
-
3. keep chart updates outside [react](https://reactjs.org)'s render cycle.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
### [⤒](#top) <span id="content">project status</span>
|
|
53
|
-
The following plots are currently available:
|
|
54
|
-
|
|
55
|
-
1. **Scatter Plot**: for plotting time-series data.
|
|
56
|
-
2. **Raster Plot**: for plotting event-timing data.
|
|
57
|
-
3. **Bar Plot**: for plotting windowed stats (min, max, mean).
|
|
58
|
-
4. **Poincare Plot**: for plotting iterates (n vs n-1).
|
|
59
|
-
|
|
60
|
-
Over time, I'll add additional chart types. In the meantime, I welcome any contributions to create new chart types (gauges, etc).
|
|
61
|
-
|
|
62
|
-
## [⤒](#top) <span id="content">content</span>
|
|
63
|
-
|
|
64
|
-
**[quick start](#quick-start)**
|
|
65
|
-
- [example raster chart code](#example-raster-chart-code)
|
|
66
|
-
- [example scatter chart code](#example-scatter-chart-code)
|
|
67
|
-
- [example bar chart code](#example-bar-chart-code)
|
|
68
|
-
- [example poincare chart code](#example-poincare-chart-code)
|
|
69
|
-
|
|
70
|
-
**[intro](#intro)**
|
|
71
|
-
- [terminology](#terminology)
|
|
72
|
-
- [data ttl, time-windows, performance](#data-ttl)
|
|
73
|
-
|
|
74
|
-
**[chart](#chart)**
|
|
75
|
-
- [<Chart/>](#chart-usage)
|
|
76
|
-
- [dimensions](#chart-usage-dimensions)
|
|
77
|
-
- [styling](#chart-usage-styling)
|
|
78
|
-
- [initial data](#chart-usage-initial-data)
|
|
79
|
-
- [streaming data](#chart-usage-streaming-data)
|
|
80
|
-
|
|
81
|
-
**[axes](#axes)**
|
|
82
|
-
- [<ContinuousAxis/>](#continuous-axes-usage)
|
|
83
|
-
- [base properties](#continuous-axes-usage-base)
|
|
84
|
-
- [styling](#continuous-axes-usage-styling)
|
|
85
|
-
|
|
86
|
-
- [<OrdinalAxis/>](#ordinal-axes-usage)
|
|
87
|
-
- [base properties](#ordinal-axes-usage-base)
|
|
88
|
-
- [styling](#ordinal-axes-usage-styling)
|
|
89
|
-
|
|
90
|
-
**[plots](#plots)**
|
|
91
|
-
- [<ScatterPlot/>](#scatter-plot-usage)
|
|
92
|
-
- [base properties](#scatter-plot-usage-base)
|
|
93
|
-
- [view-modifying interactions](#scatter-plot-usage-view)
|
|
1
|
+
[](https://www.npmjs.com/package/stream-charts) [](https://github.com/robphilipp/stream-charts/actions/workflows/main.yml) [](LICENSE)
|
|
94
2
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- [
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
3
|
+
[Homepage](https://robphilipp.github.io/stream-charts/) •
|
|
4
|
+
[API Docs](https://robphilipp.github.io/stream-charts/docs/modules.html) •
|
|
5
|
+
[Change History](changes.md)
|
|
6
|
+
|
|
7
|
+
**`stream-charts`** is a [React](https://react.dev) library for real-time charts of high-frequency streaming data. This repository holds the library, and a **demo app** that shows each of its charts at work and lets you explore them.
|
|
8
|
+
|
|
9
|
+
<picture>
|
|
10
|
+
<source media="(prefers-color-scheme: dark)" srcset="packages/examples/public/images/charts-collage-dark.gif">
|
|
11
|
+
<img src="packages/examples/public/images/charts-collage-light.gif" alt="the five stream-charts plots: a scatter chart streaming live, scrolling smoothly with a 25 ms cadence, with two x-axes, a linear and a logarithmic y-axis, and a legend whose hovered entry highlights its series; a raster chart of neuron spikes with a legend and a tooltip; a bar chart with windowed statistics and a tooltip; a Poincaré plot of an iterated map with a tooltip; and an outlier chart with confidence bands, flagged outliers, and a tooltip">
|
|
12
|
+
</picture>
|
|
13
|
+
|
|
14
|
+
*Scatter, streaming (two x-axes, linear and log y-axes, hovering the legend highlights a series, 25 ms cadence) · raster (legend, tooltip) · bar (running and windowed statistics, tooltip) · Poincaré (tooltip) · outlier (confidence bands, flagged outliers, tooltip)*
|
|
15
|
+
|
|
16
|
+
**The library** charts data that never stops arriving. You hand it an [RxJS](https://rxjs.dev) observable of new data, and it buffers the updates, scrolls the time axes, and redraws – on a single canvas, so plotting new data for about 100 series every 25 ms isn't generally a problem. Along the way:
|
|
17
|
+
|
|
18
|
+
- **The data lives in a data source that your application owns**, not in the chart, so it keeps streaming while the chart is off screen, and the chart picks up where it left off when it comes back.
|
|
19
|
+
- **The charts stay usable while the data streams**: zoom and pan without losing "now", filter the series with a regular expression, and hover for tooltips (with statistics and sparklines), trackers, and legends that highlight the hovered series.
|
|
20
|
+
- **The charts are built from React components** – axes, a plot, and the extras you want – with up to two x-axes and two y-axes per chart, linear or logarithmic scales, and y-axes that can grow to fit the data.
|
|
21
|
+
|
|
22
|
+
**The demo app** (in [`packages/examples`](#run-demo)) has a tab for each chart type, each with a control bar for trying out the chart's options while the data streams – buffering, update rates, markers, interpolation, legends, and so on. Its intro walks through the charts and the library's design, with tutorials on adding your own data source, writing your own tooltips, and building a scatter chart from sensor data.
|
|
23
|
+
|
|
24
|
+
Five plot types are available:
|
|
25
|
+
|
|
26
|
+
| plot | for | example |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| [<ScatterPlot/>](#scatter-plot) | values over time (lines, with optional markers) | sensor readings, weights |
|
|
29
|
+
| [<RasterPlot/>](#raster-plot) | events over time, one row per category | neuron spikes |
|
|
30
|
+
| [<BarPlot/>](#bar-plot) | the current value and running statistics of each category | min, max, mean, and windowed min/max/mean |
|
|
31
|
+
| [<PoincarePlot/>](#poincare-plot) | a sequence plotted against itself (`f[n+m](x)` vs `f[n](x)`) | iterated maps, chaotic systems |
|
|
32
|
+
| [<OutlierPlot/>](#outlier-plot) | values over time, with confidence bands that flag outliers | a price index with 1σ, 2σ, 3σ bands |
|
|
33
|
+
|
|
34
|
+
> Try the demo app on CodeSandbox: [stream-charts demo](https://codesandbox.io/p/github/robphilipp/stream-charts/master). It opens this repository, installs it, and runs the demo app (it takes a minute or so to start).
|
|
35
|
+
|
|
36
|
+
## <span id="content">content</span>
|
|
37
|
+
|
|
38
|
+
**[getting started](#getting-started)**
|
|
39
|
+
- [using the library in your application](#installation)
|
|
40
|
+
- [quick start](#quick-start)
|
|
41
|
+
- [how it works](#how-it-works)
|
|
42
|
+
- [migrating from 1.x](#migrating)
|
|
43
|
+
- [this repository: the library and the demo app](#repository)
|
|
44
|
+
- [running the demo app](#run-demo)
|
|
45
|
+
- [working on the library](#contributing)
|
|
46
|
+
|
|
47
|
+
**[data](#data)**
|
|
48
|
+
- [data sources](#data-sources)
|
|
49
|
+
- [generators](#generators)
|
|
50
|
+
- [chart data, one shape for each plot](#chart-data)
|
|
51
|
+
- [series and datum helpers](#series-helpers)
|
|
52
|
+
- [performance: data TTL, windowing, and cadence](#performance)
|
|
53
|
+
|
|
54
|
+
**[components](#components)**
|
|
55
|
+
- [<Chart/>](#chart)
|
|
56
|
+
- [axes: <ContinuousAxis/>, <OrdinalAxis/>, <EmptyAxis/>](#axes)
|
|
57
|
+
- [plots](#plots)
|
|
58
|
+
- [properties common to the plots](#plot-common)
|
|
59
|
+
- [<ScatterPlot/>](#scatter-plot)
|
|
60
|
+
- [<RasterPlot/>](#raster-plot)
|
|
61
|
+
- [<BarPlot/>](#bar-plot)
|
|
62
|
+
- [<PoincarePlot/>](#poincare-plot)
|
|
63
|
+
- [<OutlierPlot/>](#outlier-plot)
|
|
64
|
+
- [<Tracker/>](#tracker)
|
|
65
|
+
- [<Legend/>](#legend)
|
|
66
|
+
- [<Tooltip/> and tooltip content](#tooltip)
|
|
67
|
+
- [writing your own tooltip content](#custom-tooltip)
|
|
68
|
+
|
|
69
|
+
**[more](#more)**
|
|
70
|
+
- [theming](#theming)
|
|
71
|
+
- [filtering series](#filtering)
|
|
119
72
|
- [hooks](#hooks)
|
|
73
|
+
- [further reading](#further-reading)
|
|
120
74
|
|
|
75
|
+
---
|
|
121
76
|
|
|
122
|
-
##
|
|
77
|
+
## <span id="getting-started">getting started</span> [↑](#content "back to contents")
|
|
123
78
|
|
|
124
|
-
|
|
125
|
-
npm install stream-charts
|
|
126
|
-
```
|
|
79
|
+
What to install depends on what you'd like to do:
|
|
127
80
|
|
|
128
|
-
|
|
129
|
-
|
|
81
|
+
| you'd like to... | install | then see |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| **use the charts in your own application** | the library, from npm | [using the library in your application](#installation), then the [quick start](#quick-start) |
|
|
84
|
+
| **explore the charts**, see how each one is put together, and try out ideas – a chart of your own, say – in a working app | the demo app: clone this repository | [running the demo app](#run-demo) |
|
|
85
|
+
| **add charts or features to the library**, and contribute them back with a pull request | the library's source, which comes with the demo app: fork this repository | [working on the library](#contributing) |
|
|
130
86
|
|
|
131
|
-
|
|
87
|
+
### <span id="installation">using the library in your application</span> [↑](#content "back to contents")
|
|
132
88
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
<Chart
|
|
136
|
-
chartId={chartId.current}
|
|
137
|
-
width={useGridCellWidth()}
|
|
138
|
-
height={useGridCellHeight()}
|
|
139
|
-
margin={{...defaultMargin, top: 40, right: 75, left: 70, bottom: 40}}
|
|
140
|
-
// svgStyle={{'background-color': 'pink'}}
|
|
141
|
-
color={theme.color}
|
|
142
|
-
backgroundColor={theme.backgroundColor}
|
|
143
|
-
seriesStyles={new Map([
|
|
144
|
-
['neuron1', {
|
|
145
|
-
...defaultLineStyle(),
|
|
146
|
-
color: 'orange',
|
|
147
|
-
lineWidth: 2,
|
|
148
|
-
highlightColor: 'orange'
|
|
149
|
-
}],
|
|
150
|
-
['neuron2', {
|
|
151
|
-
...defaultLineStyle(),
|
|
152
|
-
color: 'orange',
|
|
153
|
-
lineWidth: 2,
|
|
154
|
-
highlightColor: 'orange'
|
|
155
|
-
}],
|
|
156
|
-
['neuron3', {
|
|
157
|
-
...defaultLineStyle(),
|
|
158
|
-
color: 'orange',
|
|
159
|
-
lineWidth: 2,
|
|
160
|
-
highlightColor: 'orange'
|
|
161
|
-
}],
|
|
162
|
-
['neuron4', {
|
|
163
|
-
...defaultLineStyle(),
|
|
164
|
-
color: 'orange',
|
|
165
|
-
lineWidth: 2,
|
|
166
|
-
highlightColor: 'orange'
|
|
167
|
-
}],
|
|
168
|
-
['neuron5', {
|
|
169
|
-
...defaultLineStyle(),
|
|
170
|
-
color: 'orange',
|
|
171
|
-
lineWidth: 2,
|
|
172
|
-
highlightColor: 'orange'
|
|
173
|
-
}],
|
|
174
|
-
['neuron6', {
|
|
175
|
-
...defaultLineStyle(),
|
|
176
|
-
color: theme.name === 'light' ? 'blue' : 'gray',
|
|
177
|
-
lineWidth: 3,
|
|
178
|
-
highlightColor: theme.name === 'light' ? 'blue' : 'gray',
|
|
179
|
-
highlightWidth: 5
|
|
180
|
-
}],
|
|
181
|
-
// ['test3', {...defaultLineStyle, color: 'dodgerblue', lineWidth: 1, highlightColor: 'dodgerblue', highlightWidth: 3}],
|
|
182
|
-
])}
|
|
183
|
-
initialData={initialDataRef.current}
|
|
184
|
-
seriesFilter={filter}
|
|
185
|
-
seriesObservable={observableRef.current}
|
|
186
|
-
shouldSubscribe={running}
|
|
187
|
-
onUpdateAxesBounds={handleChartTimeUpdate}
|
|
188
|
-
windowingTime={25}
|
|
189
|
-
// onSubscribe={subscription => console.log("subscribed raster")}
|
|
190
|
-
>
|
|
191
|
-
<ContinuousAxis
|
|
192
|
-
axisId="x-axis-1"
|
|
193
|
-
location={AxisLocation.Bottom}
|
|
194
|
-
domain={[0, 5000]}
|
|
195
|
-
label="t (ms)"
|
|
196
|
-
// font={{color: theme.color}}
|
|
197
|
-
/>
|
|
198
|
-
<ContinuousAxis
|
|
199
|
-
axisId="x-axis-2"
|
|
200
|
-
location={AxisLocation.Top}
|
|
201
|
-
domain={[0, 5000]}
|
|
202
|
-
label="t (ms)"
|
|
203
|
-
// font={{color: theme.color}}
|
|
204
|
-
/>
|
|
205
|
-
<OrdinalAxis
|
|
206
|
-
axisId="y-axis-1"
|
|
207
|
-
location={AxisLocation.Left}
|
|
208
|
-
categories={initialDataRef.current.map(series => series.name)}
|
|
209
|
-
label="neuron"
|
|
210
|
-
axisTickStyle={{rotation: 25}}
|
|
211
|
-
/>
|
|
212
|
-
<OrdinalAxis
|
|
213
|
-
axisId="y-axis-2"
|
|
214
|
-
location={AxisLocation.Right}
|
|
215
|
-
categories={initialDataRef.current.map(series => series.name)}
|
|
216
|
-
label="neuron"
|
|
217
|
-
/>
|
|
218
|
-
<Tracker
|
|
219
|
-
visible={visibility.tracker}
|
|
220
|
-
labelLocation={TrackerLabelLocation.ByAxis}
|
|
221
|
-
labelFormatter={x => `${d3.format(",.0f")(x)} ms`}
|
|
222
|
-
style={{color: theme.color}}
|
|
223
|
-
font={{color: theme.color}}
|
|
224
|
-
// onTrackerUpdate={update => console.dir(update)}
|
|
225
|
-
/>
|
|
226
|
-
<Tooltip
|
|
227
|
-
visible={visibility.tooltip}
|
|
228
|
-
style={{
|
|
229
|
-
fontColor: theme.color,
|
|
230
|
-
backgroundColor: theme.backgroundColor,
|
|
231
|
-
borderColor: theme.color,
|
|
232
|
-
backgroundOpacity: 0.9,
|
|
233
|
-
}}
|
|
234
|
-
>
|
|
235
|
-
<RasterPlotTooltipContent
|
|
236
|
-
xFormatter={value => formatNumber(value, " ,.0f") + ' ms'}
|
|
237
|
-
yFormatter={value => formatNumber(value, " ,.1f") + ' mV'}
|
|
238
|
-
/>
|
|
239
|
-
</Tooltip>
|
|
240
|
-
<RasterPlot
|
|
241
|
-
axisAssignments={new Map([
|
|
242
|
-
// ['test', assignAxes("x-axis-1", "y-axis-1")],
|
|
243
|
-
['neuron1', assignAxes("x-axis-2", "y-axis-2")],
|
|
244
|
-
['neuron2', assignAxes("x-axis-2", "y-axis-2")],
|
|
245
|
-
['neuron3', assignAxes("x-axis-2", "y-axis-2")],
|
|
246
|
-
['neuron4', assignAxes("x-axis-2", "y-axis-2")],
|
|
247
|
-
['neuron5', assignAxes("x-axis-2", "y-axis-2")],
|
|
248
|
-
['neuron6', assignAxes("x-axis-2", "y-axis-2")],
|
|
249
|
-
// ['test3', assignAxes("x-axis-1", "y-axis-1")],
|
|
250
|
-
])}
|
|
251
|
-
spikeMargin={1}
|
|
252
|
-
dropDataAfter={5000}
|
|
253
|
-
panEnabled={true}
|
|
254
|
-
zoomEnabled={true}
|
|
255
|
-
zoomKeyModifiersRequired={true}
|
|
256
|
-
withCadenceOf={50}
|
|
257
|
-
/>
|
|
258
|
-
</Chart>
|
|
89
|
+
```shell
|
|
90
|
+
npm install stream-charts react react-dom rxjs d3
|
|
259
91
|
```
|
|
260
92
|
|
|
261
|
-
|
|
93
|
+
> This README describes version 2, whose API is different from version 1's. To upgrade from 1.x, see [migrating from 1.x](#migrating).
|
|
262
94
|
|
|
263
|
-
|
|
95
|
+
`stream-charts` has four peer dependencies: React 19 (`react`, `react-dom`), [RxJS](https://rxjs.dev) 7, and [d3](https://d3js.org) 7. Your application uses them directly – to render the components, create the observables that feed the charts, and pass d3 scales and curves to the axes and plots – so the library shares your copies rather than bringing its own. TypeScript users get d3's type definitions (`@types/d3`) with the library.
|
|
264
96
|
|
|
265
|
-
|
|
97
|
+
### <span id="quick-start">quick start</span> [↑](#content "back to contents")
|
|
266
98
|
|
|
99
|
+
This complete example streams three made-up temperature sensors, each reporting every 100 ms, into a scatter chart with a **Run**/**Pause** button. There are four steps: an [RxJS](https://rxjs.dev) observable that emits the data, a generator that creates that observable, a data source that holds the data, and a chart that shows it.
|
|
267
100
|
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
onUpdateAxesBounds={handleChartTimeUpdate}
|
|
304
|
-
windowingTime={25}
|
|
305
|
-
>
|
|
306
|
-
<ContinuousAxis
|
|
307
|
-
axisId="x-axis-1"
|
|
308
|
-
location={AxisLocation.Bottom}
|
|
309
|
-
domain={[10, 10000]}
|
|
310
|
-
label="x-axis"
|
|
311
|
-
/>
|
|
312
|
-
<ContinuousAxis
|
|
313
|
-
axisId="y-axis-1"
|
|
314
|
-
location={AxisLocation.Left}
|
|
315
|
-
domain={[0, 1000]}
|
|
316
|
-
label="y-axis"
|
|
317
|
-
/>
|
|
318
|
-
<ContinuousAxis
|
|
319
|
-
axisId="x-axis-2"
|
|
320
|
-
location={AxisLocation.Top}
|
|
321
|
-
domain={[100, 5000]}
|
|
322
|
-
label="x-axis (2)"
|
|
323
|
-
/>
|
|
324
|
-
<ContinuousAxis
|
|
325
|
-
axisId="y-axis-2"
|
|
326
|
-
location={AxisLocation.Right}
|
|
327
|
-
scale={d3.scaleLog()}
|
|
328
|
-
domain={[100, 1200]}
|
|
329
|
-
label="y-axis (2)"
|
|
330
|
-
/>
|
|
331
|
-
<Tracker
|
|
332
|
-
visible={visibility.tracker}
|
|
333
|
-
labelLocation={TrackerLabelLocation.ByAxis}
|
|
334
|
-
labelFormatter={x => `${d3.format(",.0f")(x)} ms`}
|
|
335
|
-
style={{color: theme.color}}
|
|
336
|
-
font={{color: theme.color}}
|
|
337
|
-
// onTrackerUpdate={update => console.dir(update)}
|
|
338
|
-
/>
|
|
339
|
-
<Tooltip
|
|
340
|
-
visible={visibility.tooltip}
|
|
341
|
-
style={{
|
|
342
|
-
fontColor: theme.color,
|
|
343
|
-
backgroundColor: theme.backgroundColor,
|
|
344
|
-
borderColor: theme.color,
|
|
345
|
-
backgroundOpacity: 0.9,
|
|
346
|
-
}}
|
|
347
|
-
>
|
|
348
|
-
<ScatterPlotTooltipContent
|
|
349
|
-
xLabel="t (ms)"
|
|
350
|
-
yLabel="count"
|
|
351
|
-
yValueFormatter={value => formatNumber(value, " ,.0f")}
|
|
352
|
-
yChangeFormatter={(y1, y2) => formatNumber(y2 - y1, " ,.0f")}
|
|
353
|
-
/>
|
|
354
|
-
</Tooltip>
|
|
355
|
-
<ScatterPlot
|
|
356
|
-
interpolation={interpolation}
|
|
357
|
-
axisAssignments={new Map([
|
|
358
|
-
// ['test1', assignAxes("x-axis-1", "y-axis-1")],
|
|
359
|
-
['test2', assignAxes("x-axis-2", "y-axis-2")],
|
|
360
|
-
['test3', assignAxes("x-axis-2", "y-axis-1")],
|
|
361
|
-
])}
|
|
362
|
-
dropDataAfter={20000}
|
|
363
|
-
panEnabled={true}
|
|
364
|
-
zoomEnabled={true}
|
|
365
|
-
zoomKeyModifiersRequired={true}
|
|
366
|
-
// withCadenceOf={30}
|
|
367
|
-
// timeWindowBehavior={TimeWindowBehavior.SQUEEZE}
|
|
368
|
-
/>
|
|
369
|
-
</Chart>
|
|
101
|
+
```tsx
|
|
102
|
+
import {type JSX} from "react"
|
|
103
|
+
import {interval, type Observable} from "rxjs"
|
|
104
|
+
import {map} from "rxjs/operators"
|
|
105
|
+
import {
|
|
106
|
+
AxisLocation,
|
|
107
|
+
Chart,
|
|
108
|
+
ContinuousAxis,
|
|
109
|
+
type DataGenerator,
|
|
110
|
+
type Datum,
|
|
111
|
+
emptySeries,
|
|
112
|
+
ScatterPlot,
|
|
113
|
+
type TimeSeries,
|
|
114
|
+
type TimeSeriesChartData,
|
|
115
|
+
TimeSeriesDataSource,
|
|
116
|
+
useDataSourceRunning,
|
|
117
|
+
} from "stream-charts"
|
|
118
|
+
|
|
119
|
+
// 1. an observable that emits each sensor's *new* readings, every `updatePeriod` ms
|
|
120
|
+
function sensorReadings(names: Array<string>, startTime: number, updatePeriod: number): Observable<TimeSeriesChartData> {
|
|
121
|
+
const clockStart = performance.now()
|
|
122
|
+
return interval(updatePeriod).pipe(
|
|
123
|
+
// use the wall clock (not a tick count), so the time stays right when the browser throttles timers
|
|
124
|
+
map(() => startTime + (performance.now() - clockStart)),
|
|
125
|
+
map(time => ({
|
|
126
|
+
seriesNames: new Set(names),
|
|
127
|
+
maxTime: time,
|
|
128
|
+
maxTimes: new Map(names.map(name => [name, time])),
|
|
129
|
+
newPoints: new Map<string, Array<Datum>>(names.map(name => [
|
|
130
|
+
name,
|
|
131
|
+
[{x: time, y: 20 + 5 * Math.sin(time / 1000) + Math.random()}]
|
|
132
|
+
])),
|
|
133
|
+
}))
|
|
134
|
+
)
|
|
135
|
+
}
|
|
370
136
|
|
|
137
|
+
// 2. a generator: the data source calls it each time it starts, handing it the series it holds
|
|
138
|
+
// right now, so that Run after Pause carries on from where the data left off
|
|
139
|
+
const sensorGenerator = (updatePeriod: number): DataGenerator<TimeSeriesChartData, TimeSeries> =>
|
|
140
|
+
currentSeries => {
|
|
141
|
+
const latestTime = currentSeries.reduce(
|
|
142
|
+
(latest, series) => Math.max(latest, series.last().map(datum => datum.x).getOrElse(0)),
|
|
143
|
+
0
|
|
144
|
+
)
|
|
145
|
+
return sensorReadings(currentSeries.map(series => series.name), latestTime, updatePeriod)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 3. the data source holds the data. It lives outside the component (here, at module level),
|
|
149
|
+
// so it keeps its data, and keeps streaming, even when the chart is unmounted
|
|
150
|
+
const dataSource = new TimeSeriesDataSource({
|
|
151
|
+
initialData: ['kitchen', 'garage', 'attic'].map(name => emptySeries<Datum>(name)),
|
|
152
|
+
generator: sensorGenerator(100),
|
|
153
|
+
dropDataAfter: 30_000, // keep the last 30 seconds of data
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// 4. the chart reads from the data source
|
|
157
|
+
export function SensorChart(): JSX.Element {
|
|
158
|
+
const running = useDataSourceRunning(dataSource)
|
|
159
|
+
return (
|
|
160
|
+
<div>
|
|
161
|
+
<button onClick={() => running ? dataSource.stop() : dataSource.start()}>
|
|
162
|
+
{running ? 'Pause' : 'Run'}
|
|
163
|
+
</button>
|
|
164
|
+
<Chart chartId={1} width={800} height={400} dataSource={dataSource} windowingTime={100} dataUpdatePeriod={100}>
|
|
165
|
+
<ContinuousAxis axisId="x-axis" location={AxisLocation.Bottom} domain={[0, 10_000]} label="time (ms)"/>
|
|
166
|
+
<ContinuousAxis axisId="y-axis" location={AxisLocation.Left} domain={[10, 30]} label="temperature (°C)"/>
|
|
167
|
+
<ScatterPlot/>
|
|
168
|
+
</Chart>
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
371
172
|
```
|
|
372
173
|
|
|
373
|
-
|
|
174
|
+
Click **Run** and the three sensors start drawing. When the data reaches the end of the x-axis, the axis scrolls to keep up. To show your real data, replace `sensorReadings` with an observable of your own: a web socket (`rxjs/webSocket`), server-sent events, polling (`interval` + `switchMap` to a `fetch`), or a simulation. Nothing else needs to change.
|
|
374
175
|
|
|
375
|
-
|
|
176
|
+
From here you can add [colors](#chart-styling), a [tooltip](#tooltip), a [tracker](#tracker), a [legend](#legend), and [zooming and panning](#plot-common). The [demo app](#run-demo), in this repository's `packages/examples`, has a complete, working example of each plot type. Its tutorial, [adding your own data source](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/adding-a-data-source.md), builds this quick start one step at a time.
|
|
376
177
|
|
|
377
|
-
|
|
178
|
+
### <span id="how-it-works">how it works</span> [↑](#content "back to contents")
|
|
378
179
|
|
|
379
|
-
|
|
180
|
+
<picture>
|
|
181
|
+
<source media="(prefers-color-scheme: dark)" srcset="packages/examples/public/images/data-flow-dark.png">
|
|
182
|
+
<img src="packages/examples/public/images/data-flow-light.png" alt="how the data flows: the generator feeds the data source, which feeds a view driver for each mounted plot, which feeds the plot">
|
|
183
|
+
</picture>
|
|
380
184
|
|
|
381
|
-
|
|
382
|
-
chartId={chartId.current}
|
|
383
|
-
width={useGridCellWidth()}
|
|
384
|
-
height={useGridCellHeight()}
|
|
385
|
-
margin={{...defaultMargin, top: 60, bottom: 80, right: 75, left: 70}}
|
|
386
|
-
// svgStyle={{'background-color': 'pink'}}
|
|
387
|
-
color={theme.color}
|
|
388
|
-
backgroundColor={theme.backgroundColor}
|
|
389
|
-
seriesStyles={new Map<string, BarSeriesStyle>([
|
|
390
|
-
['neuron1', {
|
|
391
|
-
...defaultBarSeriesStyle('orange'),
|
|
392
|
-
lineWidth: 2,
|
|
393
|
-
minMaxBar: {
|
|
394
|
-
...defaultBarSeriesStyle('orange').minMaxBar,
|
|
395
|
-
stroke: {
|
|
396
|
-
...defaultBarSeriesStyle('orange').minMaxBar.stroke,
|
|
397
|
-
width: 0
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
} as BarSeriesStyle],
|
|
401
|
-
['neuron14', {
|
|
402
|
-
...defaultBarSeriesStyle(theme.name === 'light' ? 'blue' : 'gray'),
|
|
403
|
-
lineWidth: 3,
|
|
404
|
-
highlightWidth: 5,
|
|
405
|
-
minMaxBar: {
|
|
406
|
-
...defaultBarSeriesStyle(theme.name === 'light' ? 'blue' : 'gray').minMaxBar,
|
|
407
|
-
widthFraction: 1
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
} as BarSeriesStyle],
|
|
411
|
-
['neuron31', {
|
|
412
|
-
...defaultBarSeriesStyle('green'),
|
|
413
|
-
lineWidth: 2,
|
|
414
|
-
} as BarSeriesStyle],
|
|
415
|
-
])}
|
|
416
|
-
initialData={initialDataRef.current}
|
|
417
|
-
seriesObservable={observableRef.current}
|
|
418
|
-
seriesFilter={filter}
|
|
419
|
-
shouldSubscribe={running}
|
|
420
|
-
onUpdateChartTime={handleChartTimeUpdate}
|
|
421
|
-
onUpdateAxesBounds={handleChartRangeUpdate}
|
|
422
|
-
windowingTime={25}
|
|
423
|
-
>
|
|
424
|
-
<OrdinalAxis
|
|
425
|
-
axisId="x-axis-1"
|
|
426
|
-
location={AxisLocation.Bottom}
|
|
427
|
-
categories={initialDataRef.current.map(series => series.name)}
|
|
428
|
-
label="neuron"
|
|
429
|
-
axisTickStyle={{rotation: 90}}
|
|
430
|
-
/>
|
|
431
|
-
<OrdinalAxis
|
|
432
|
-
axisId="x-axis-2"
|
|
433
|
-
location={AxisLocation.Top}
|
|
434
|
-
categories={initialDataRef.current.map(series => series.name)}
|
|
435
|
-
label="neuron"
|
|
436
|
-
axisTickStyle={{rotation: 40}}
|
|
437
|
-
/>
|
|
438
|
-
<ContinuousAxis
|
|
439
|
-
axisId="y-axis-1"
|
|
440
|
-
location={AxisLocation.Left}
|
|
441
|
-
domain={[-1.1, 1.1]}
|
|
442
|
-
label="ρ (mV)"
|
|
443
|
-
/>
|
|
444
|
-
<ContinuousAxis
|
|
445
|
-
axisId="y-axis-2"
|
|
446
|
-
location={AxisLocation.Right}
|
|
447
|
-
domain={[-1.1, 1.1]}
|
|
448
|
-
label="ρ (mV)"
|
|
449
|
-
/>
|
|
450
|
-
<Tracker
|
|
451
|
-
visible={visibility.tracker}
|
|
452
|
-
trackerAxis={AxisLocation.Left}
|
|
453
|
-
labelLocation={TrackerLabelLocation.ByAxis}
|
|
454
|
-
labelFormatter={x => `${d3.format(".2f")(x)} mV`}
|
|
455
|
-
style={{color: theme.color}}
|
|
456
|
-
font={{color: theme.color}}
|
|
457
|
-
// onTrackerUpdate={update => console.dir(update)}
|
|
458
|
-
/>
|
|
459
|
-
<Tooltip
|
|
460
|
-
visible={visibility.tooltip}
|
|
461
|
-
style={{
|
|
462
|
-
fontColor: theme.color,
|
|
463
|
-
backgroundColor: theme.backgroundColor,
|
|
464
|
-
borderColor: theme.color,
|
|
465
|
-
backgroundOpacity: 0.9,
|
|
466
|
-
}}
|
|
467
|
-
>
|
|
468
|
-
<BarPlotTooltipContent ordinalUnits="mV"/>
|
|
469
|
-
</Tooltip>
|
|
470
|
-
<BarPlot
|
|
471
|
-
barMargin={1}
|
|
472
|
-
dropDataAfter={5000}
|
|
473
|
-
panEnabled={true}
|
|
474
|
-
zoomEnabled={true}
|
|
475
|
-
zoomKeyModifiersRequired={true}
|
|
476
|
-
// withCadenceOf={50}
|
|
477
|
-
|
|
478
|
-
// to have the upper axis zoom when the series zoom, we must have at
|
|
479
|
-
// least one series assigned to this axis.
|
|
480
|
-
axisAssignments={new Map([
|
|
481
|
-
['neuron1', assignAxes("x-axis-2", "y-axis-2")],
|
|
482
|
-
])}
|
|
483
|
-
|
|
484
|
-
showMinMaxBars={showMinMax}
|
|
485
|
-
showValueLines={showValue}
|
|
486
|
-
showMeanValueLines={showMean}
|
|
487
|
-
showWindowedMinMaxBars={showWinMinMax}
|
|
488
|
-
showWindowedMeanValueLines={showWinMean}
|
|
489
|
-
/>
|
|
490
|
-
</Chart>
|
|
491
|
-
```
|
|
185
|
+
(The diagram is from the demo app, whose data generators stand in for your application's.)
|
|
492
186
|
|
|
493
|
-
|
|
187
|
+
1. **The generator** is a function that creates an RxJS observable of chart data. Each emission holds only the *new* points since the last one.
|
|
188
|
+
2. **The data source** subscribes to the generator's observable when you call `start()`, adds each chunk of new points to its series, and drops the points that are older than its `dropDataAfter` setting. `stop()` unsubscribes, and nothing keeps running in the background.
|
|
189
|
+
3. **The `<Chart>`** is handed the data source. The chart and its plots only *read* from the data source; they never subscribe to the stream themselves. While a plot is mounted, its *view driver* listens for the data source's updates, batches them (see `windowingTime`), scrolls the time axes, and has the plot redraw.
|
|
190
|
+
4. **The plot** draws the data onto a single `<canvas>`, outside of React's render cycle.
|
|
494
191
|
|
|
495
|
-
|
|
192
|
+
Because the data source isn't tied to any component, *you* decide how long it lives. Keep it in a store, a context, or at module level, and it outlives the chart. The user can start a chart, navigate to another page, and come back to find that it kept streaming the whole time. (The examples keep theirs in [zustand](https://www.npmjs.com/package/zustand) stores, but any of these works.) For a longer walk through this, see [how the data flows](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/how-the-data-flows.md).
|
|
496
193
|
|
|
497
|
-
|
|
194
|
+
The chart is composed from React components, so you pick the pieces you need: up to two x-axes and two y-axes, a plot, and optionally a tracker, a tooltip, and a legend.
|
|
498
195
|
|
|
499
|
-
```
|
|
500
|
-
<Chart
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
backgroundColor={theme.backgroundColor}
|
|
508
|
-
seriesStyles={new Map([
|
|
509
|
-
['test1', {
|
|
510
|
-
...defaultLineStyle(),
|
|
511
|
-
color: 'orange',
|
|
512
|
-
lineWidth: 1,
|
|
513
|
-
highlightColor: 'orange'
|
|
514
|
-
}],
|
|
515
|
-
['test2', {
|
|
516
|
-
...defaultLineStyle(),
|
|
517
|
-
color: theme.name === 'light' ? 'blue' : 'gray',
|
|
518
|
-
lineWidth: 1,
|
|
519
|
-
highlightColor: theme.name === 'light' ? 'blue' : 'gray',
|
|
520
|
-
highlightWidth: 5
|
|
521
|
-
}],
|
|
522
|
-
['test3', {
|
|
523
|
-
...defaultLineStyle(),
|
|
524
|
-
color: theme.name === 'light' ? 'red' : 'gray',
|
|
525
|
-
lineWidth: 1,
|
|
526
|
-
highlightColor: theme.name === 'light' ? 'dodgerblue' : 'gray',
|
|
527
|
-
highlightWidth: 5
|
|
528
|
-
}],
|
|
529
|
-
])}
|
|
530
|
-
initialData={initialDataRef.current}
|
|
531
|
-
// seriesFilter={filter}
|
|
532
|
-
seriesObservable={observableRef.current}
|
|
533
|
-
shouldSubscribe={running}
|
|
534
|
-
onUpdateChartTime={handleChartTimeUpdate}
|
|
535
|
-
windowingTime={25}
|
|
536
|
-
>
|
|
537
|
-
<ContinuousAxis
|
|
538
|
-
axisId="x-axis-1"
|
|
539
|
-
location={AxisLocation.Bottom}
|
|
540
|
-
domain={axesRange}
|
|
541
|
-
label="f[n](x)"
|
|
542
|
-
updateAxisBasedOnDomainValues={false}
|
|
543
|
-
/>
|
|
544
|
-
<ContinuousAxis
|
|
545
|
-
axisId="y-axis-1"
|
|
546
|
-
location={AxisLocation.Left}
|
|
547
|
-
domain={axesRange}
|
|
548
|
-
label={`f[n+${lagN}](x)`}
|
|
549
|
-
updateAxisBasedOnDomainValues={false}
|
|
550
|
-
/>
|
|
551
|
-
<ContinuousAxis
|
|
552
|
-
axisId="x-axis-2"
|
|
553
|
-
location={AxisLocation.Top}
|
|
554
|
-
domain={axesRange}
|
|
555
|
-
label="f[n](x)"
|
|
556
|
-
updateAxisBasedOnDomainValues={false}
|
|
557
|
-
/>
|
|
558
|
-
<ContinuousAxis
|
|
559
|
-
axisId="y-axis-2"
|
|
560
|
-
location={AxisLocation.Right}
|
|
561
|
-
domain={axesRange}
|
|
562
|
-
label={`f[n+${lagN}](x)`}
|
|
563
|
-
updateAxisBasedOnDomainValues={false}
|
|
564
|
-
/>
|
|
565
|
-
<Tracker
|
|
566
|
-
key="tracker-x-axis"
|
|
567
|
-
visible={visibility.tracker}
|
|
568
|
-
// labelLocation={TrackerLabelLocation.Nowhere}
|
|
569
|
-
labelLocation={TrackerLabelLocation.ByAxis}
|
|
570
|
-
trackerAxis={AxisLocation.Bottom}
|
|
571
|
-
labelFormatter={x => `${d3.format(",.3f")(x)}`}
|
|
572
|
-
style={{color: theme.color}}
|
|
573
|
-
font={{color: theme.color}}
|
|
574
|
-
// onTrackerUpdate={update => console.dir("bottom", update)}
|
|
575
|
-
/>
|
|
576
|
-
<Tracker
|
|
577
|
-
key="tracker-y-axis"
|
|
578
|
-
visible={visibility.tracker}
|
|
579
|
-
// labelLocation={TrackerLabelLocation.Nowhere}
|
|
580
|
-
labelLocation={TrackerLabelLocation.ByAxis}
|
|
581
|
-
trackerAxis={AxisLocation.Left}
|
|
582
|
-
labelFormatter={x => `${d3.format(",.3f")(x)}`}
|
|
583
|
-
style={{color: theme.color}}
|
|
584
|
-
font={{color: theme.color}}
|
|
585
|
-
// onTrackerUpdate={update => console.dir("left", update)}
|
|
586
|
-
/>
|
|
587
|
-
<Tooltip
|
|
588
|
-
visible={visibility.tooltip}
|
|
589
|
-
style={{
|
|
590
|
-
fontColor: theme.color,
|
|
591
|
-
backgroundColor: theme.backgroundColor,
|
|
592
|
-
borderColor: theme.color,
|
|
593
|
-
backgroundOpacity: 0.9,
|
|
594
|
-
}}
|
|
595
|
-
>
|
|
596
|
-
<PoincarePlotTooltipContent
|
|
597
|
-
xLabel="t (ms)"
|
|
598
|
-
yLabel="f(x)"
|
|
599
|
-
yValueFormatter={value => formatNumber(value, " ,.4f")}
|
|
600
|
-
style={{
|
|
601
|
-
...defaultTooltipStyle,
|
|
602
|
-
fontColor: 'black',
|
|
603
|
-
// fontColor: theme.color,
|
|
604
|
-
fontWeight: 650
|
|
605
|
-
}}
|
|
606
|
-
/>
|
|
607
|
-
</Tooltip>
|
|
608
|
-
<PoincarePlot
|
|
609
|
-
interpolation={interpolation}
|
|
610
|
-
dropDataAfter={dropAfterMs}
|
|
611
|
-
panEnabled={true}
|
|
612
|
-
zoomEnabled={true}
|
|
613
|
-
zoomKeyModifiersRequired={true}
|
|
614
|
-
// withCadenceOf={30}
|
|
615
|
-
/>
|
|
196
|
+
```tsx
|
|
197
|
+
<Chart dataSource={dataSource} ...>
|
|
198
|
+
<ContinuousAxis axisId="x-axis-1" location={AxisLocation.Bottom} .../>
|
|
199
|
+
<ContinuousAxis axisId="y-axis-1" location={AxisLocation.Left} .../>
|
|
200
|
+
<Tracker .../>
|
|
201
|
+
<Tooltip ...><ScatterPlotTooltipContent .../></Tooltip>
|
|
202
|
+
<Legend .../>
|
|
203
|
+
<ScatterPlot .../>
|
|
616
204
|
</Chart>
|
|
617
205
|
```
|
|
618
206
|
|
|
619
|
-
|
|
207
|
+
### <span id="migrating">migrating from 1.x</span> [↑](#content "back to contents")
|
|
620
208
|
|
|
621
|
-
|
|
209
|
+
This version is a substantial redesign, and code written for 1.x needs updating.
|
|
622
210
|
|
|
623
|
-
|
|
211
|
+
| 1.x | now |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `<Chart initialData={...} seriesObservable={...} shouldSubscribe={running}>` | Create a [data source](#data-sources) with your initial data and a [generator](#generators), and pass it as `<Chart dataSource={...}>`. Start and stop it with `dataSource.start()` and `dataSource.stop()`. |
|
|
214
|
+
| `onSubscribe`, `onUpdateData` on the `<Chart>` | `onUpdateData` is now an option of the data source. To watch the stream, subscribe to `dataSource.chartDataUpdatesObservable`, or to `dataSource.isRunningObservable` to follow start/stop. |
|
|
215
|
+
| `dropDataAfter` on the plot | `dropDataAfter` is an option of the data source (change it later with `setDropDataAfter`). |
|
|
216
|
+
| `Datum` was `{time, value}` | `Datum` is `{x, y}`. Create one with `datumOf(time, value)`. |
|
|
217
|
+
| `seriesFromTuples(name, tuples)` | `timeSeriesFromTuples(name, tuples)` (and `iterateSeriesFromTuples`, `ordinateSeriesFromTuples` for the other shapes). |
|
|
218
|
+
| charts drawn with SVG elements | charts drawn on a single `<canvas>`. The `svgStyle` prop is still accepted, and styles the chart's container. |
|
|
219
|
+
| `TrackerLabelLocation.WithMouse` | removed. Use `TrackerLabelLocation.ByAxis` (the default) or `TrackerLabelLocation.Nowhere`. |
|
|
220
|
+
| `zoomKeyModifiersRequired` defaulted to `false` | it now defaults to `true`: hold shift (or ctrl) while scrolling to zoom. |
|
|
221
|
+
| `useDataObservable()`, `useInitialData()` hooks | `useDataSource()`, and `useDataSourceRunning(dataSource)`. |
|
|
222
|
+
| deep imports, such as `stream-charts/dist/app/charts/...` | import everything from `stream-charts`. Anything not exported from the package root is internal and may change. |
|
|
624
223
|
|
|
625
|
-
|
|
224
|
+
New in this version: the [<OutlierPlot/>](#outlier-plot), [<EmptyAxis/>](#empty-axis), [auto-scaling y-axes](#continuous-axis-autoscale), markers on the scatter plot, axis highlighting on hover, colorized lines in the Poincaré plot, zoom state that survives remounting (bar and Poincaré plots), [custom tooltip content](#custom-tooltip) built on `<TooltipContent>`, and data that keeps streaming while the chart is unmounted.
|
|
626
225
|
|
|
627
|
-
|
|
226
|
+
### <span id="repository">this repository: the library and the demo app</span> [↑](#content "back to contents")
|
|
628
227
|
|
|
629
|
-
|
|
630
|
-
Generic container which holds the `Axes`, `Plot`, `Tracker`, `Tooltip`.
|
|
228
|
+
This repository is a monorepo (npm workspaces) holding two packages: the library, and a demo app that uses it.
|
|
631
229
|
|
|
632
|
-
|
|
633
|
-
|
|
230
|
+
| where | what |
|
|
231
|
+
|---|---|
|
|
232
|
+
| `packages/stream-charts` | **The library**, published to npm as `stream-charts`. Its code is in `src/app/charts`, and its public API – everything you can import from `"stream-charts"` – is `src/index.ts`. |
|
|
233
|
+
| `packages/examples` | **The demo app** (not published). The examples are in `src/app/examples`, and the guide pages shown in its intro are the markdown files in `src/app/examples/docs`. |
|
|
234
|
+
| `docs/` | The API docs, generated from the library's source, and served at [robphilipp.github.io/stream-charts/docs](https://robphilipp.github.io/stream-charts/docs/modules.html). |
|
|
235
|
+
| `README.md`, `changes.md` | This page and the change history – the library's, copied into its package when it's published. |
|
|
634
236
|
|
|
635
|
-
|
|
636
|
-
The plot is a container for the data that uses the `axes` for scale, domain, and range information. Plots provide panning and zooming of the x-axis (time), interacting with the `axes` to update the time-range. The plot is the visual representation of the data. Currently, four types of plots are available: [<RasterPlot/>](#raster-plot-usage), [<ScatterPlot/>](#scatter-plot-usage), [<BarPlot/>](#bar-plot-usage), and [<PoincarePlot/>](#poincare-plot-usage).
|
|
237
|
+
While you're working in this repository, the demo app's `"stream-charts"` import resolves to the library's TypeScript *source* (through a `source` condition in the library's `exports`), so a change to the library shows up in the running app right away, with no build step. Published copies of the library don't use that condition, and get the compiled `dist/`.
|
|
637
238
|
|
|
638
|
-
|
|
639
|
-
The tracker displays the current plot time of the mouse. When multiple x-axes are used in the chart, then the tracker displays both times (i.e. from the upper and lower x-axis).
|
|
239
|
+
### <span id="run-demo">running the demo app</span> [↑](#content "back to contents")
|
|
640
240
|
|
|
641
|
-
|
|
642
|
-
The tooltip is a generic component for rendering information about the data when the user mouses over a series or datum. The `<Tooltip/>` expects a child component that understands the data and renders the information show in the tooltip. For example, when using a [<RasterPlot/>](#raster-plot-usage), the [<RasterPlotTooltipContent/>](#rasterplot-tooltip-usage) renders the data for the raster plot tooltip. And when using a [<ScatterPlot/>](#scatter-plot-usage), the [<ScatterPlotTooltipContent/>](#scatterplot-tooltip-usage) renders the data for the scatter plot tooltip. The tooltip content can be extended.
|
|
241
|
+
The demo app, in `packages/examples`, has one tab for each plot type, with a control bar for trying out the plots' options, and an intro made of guide pages and tutorials. You'll need [Node.js](https://nodejs.org) 22.12+ (or 20.19+) and npm 7+. Clone the repository and start the app:
|
|
643
242
|
|
|
644
|
-
|
|
243
|
+
```shell
|
|
244
|
+
git clone https://github.com/robphilipp/stream-charts.git
|
|
245
|
+
cd stream-charts
|
|
246
|
+
npm install # installs both packages (one node_modules, one lockfile, at the root)
|
|
247
|
+
npm run dev # starts the demo app (Vite), at http://localhost:5173
|
|
248
|
+
```
|
|
645
249
|
|
|
646
|
-
|
|
250
|
+
<picture>
|
|
251
|
+
<source media="(prefers-color-scheme: dark)" srcset="packages/examples/public/images/intro-example-parts-dark.png">
|
|
252
|
+
<img src="packages/examples/public/images/intro-example-parts-light.png" alt="the anatomy of an example chart: the tabs and control bar are the demo app's, and the chart and its legend are the library's">
|
|
253
|
+
</picture>
|
|
647
254
|
|
|
648
|
-
|
|
255
|
+
Each tab runs one chart: click **Run**, and open its control bar to try the options while the data streams. The **Intro** tab walks through the charts and how they work. The tabs and control bars are the demo app's; the chart and its legend are the library's.
|
|
649
256
|
|
|
650
|
-
|
|
257
|
+
To try out an idea, change an example in `packages/examples/src/app/examples` (for example, `StreamingScatterChart.tsx`), and the app reloads as you save. The tutorial [your own scatter chart, from sensor data](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/scatter-chart-from-sensor-data.md) builds a new chart from scratch. To give a new example its own tab:
|
|
651
258
|
|
|
652
|
-
1.
|
|
653
|
-
2.
|
|
654
|
-
3.
|
|
259
|
+
1. Put the example component in `packages/examples/src/app/examples`.
|
|
260
|
+
2. Add a route component for it in `packages/examples/src/app/routeComponents.tsx`.
|
|
261
|
+
3. Add a route for it to the route tree in `packages/examples/src/app/router.tsx`.
|
|
262
|
+
4. Add `{name, path}` for the tab to `TABS` in `packages/examples/src/app/routeData.ts`.
|
|
655
263
|
|
|
656
|
-
|
|
264
|
+
The demo app imports everything from `"stream-charts"`, just as your application would, so the chart code carries over to your application. The app's stores (zustand), control bars, and routing are its own.
|
|
657
265
|
|
|
658
|
-
|
|
266
|
+
### <span id="contributing">working on the library</span> [↑](#content "back to contents")
|
|
659
267
|
|
|
660
|
-
|
|
268
|
+
To add a chart or a feature to the library (see [this repository](#repository) for where everything is):
|
|
661
269
|
|
|
662
|
-
|
|
270
|
+
1. **Fork the repository** on GitHub, clone your fork (instead of this repository), and make a branch from `develop` for your change. You'll need the same Node.js and npm as for [running the demo app](#run-demo).
|
|
271
|
+
2. **Run the demo app** (`npm run dev`), and change the library in `packages/stream-charts/src/app/charts`. The demo app runs on the library's source, so your change shows up as you save. Add or change an example in the demo app to show it off.
|
|
272
|
+
3. **Add tests** next to the code you changed: the library's tests are the `*.test.ts` files in `packages/stream-charts/src/app/charts`, and the demo app's are the `*.test.ts` files in `packages/examples/src`. Check that everything passes: `npm test`, `npm run lint`, and `npm run build`.
|
|
273
|
+
4. **Update the docs**: this README for anything that changes the library's API, and `changes.md` for the change history. Leave the `version` in `packages/stream-charts/package.json` alone: a merge to `master` with a new version stages the library on npm, for a maintainer to approve and release.
|
|
274
|
+
5. **Open a pull request** against `develop`. The repository's CI runs only for `master` (where the releases are), not for pull requests to `develop`, so run the checks in step 3 yourself before you open it.
|
|
663
275
|
|
|
664
|
-
The
|
|
276
|
+
The commands, all from the repository's root:
|
|
665
277
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
278
|
+
```shell
|
|
279
|
+
npm run dev # runs the demo app, on the library's source
|
|
280
|
+
npm run build # compiles the library to packages/stream-charts/dist, then builds the demo app
|
|
281
|
+
npm test # runs both packages' tests
|
|
282
|
+
npm run lint # lints both packages
|
|
283
|
+
npm run docs # regenerates the API docs into docs/
|
|
284
|
+
```
|
|
671
285
|
|
|
672
|
-
|
|
673
|
-
**width (pixels)**<br>
|
|
674
|
-
The width (in pixels) of the container that holds the chart. The actual plot will be smaller based on the margins.
|
|
286
|
+
To work on one package, add `-w` and its name: for example, `npm test -w stream-charts` runs only the library's tests, and `npm run preview -w stream-charts-examples` serves the demo app's production build (after `npm run build`). To see what would be published, `npm pack -w stream-charts --dry-run`.
|
|
675
287
|
|
|
676
|
-
|
|
677
|
-
The height (in pixels) of the container that holds the chart. The actual plot will be smaller based on the margins.
|
|
288
|
+
---
|
|
678
289
|
|
|
679
|
-
|
|
680
|
-
**margin ([Margin](https://github.com/robphilipp/stream-charts/blob/master/src/app/charts/margins.ts), optional)**<br>
|
|
681
|
-
The margin (in pixels) around plot. For example, if the container has a (h, w) = (300, 600) and a margin of 10 pixels for the top, left, right, bottom, then the actual plot will have a (h, w) = (290, 590), leaving only 10 pixels around the plot for axis titles, ticks, and axis labels.
|
|
290
|
+
## <span id="data">data</span> [↑](#content "back to contents")
|
|
682
291
|
|
|
683
|
-
|
|
684
|
-
>```typescript
|
|
685
|
-
>interface Margin {
|
|
686
|
-
top: number
|
|
687
|
-
bottom: number
|
|
688
|
-
left: number
|
|
689
|
-
right: number
|
|
690
|
-
>}
|
|
691
|
-
>```
|
|
292
|
+
### <span id="data-sources">data sources</span> [↑](#content "back to contents")
|
|
692
293
|
|
|
294
|
+
A data source owns the stream. It holds the series, subscribes to the generator while it's running, and applies the data-retention rules. Each plot type has its own kind of data, and so its own data source.
|
|
693
295
|
|
|
694
|
-
|
|
695
|
-
|
|
296
|
+
| your data is... | data source | chart data | datum | plot |
|
|
297
|
+
|---|---|---|---|---|
|
|
298
|
+
| values over time | `TimeSeriesDataSource` | `TimeSeriesChartData` | `Datum` (`{x, y}`) | `<ScatterPlot>` |
|
|
299
|
+
| events over time | `TimeSeriesDataSource` | `TimeSeriesChartData` | `Datum` (`{x, y}`) | `<RasterPlot>` |
|
|
300
|
+
| a sequence plotted against itself | `IteratesDataSource` | `IterateChartData` | `IterateDatum` (`{time, iterateN, iterateN_1}`) | `<PoincarePlot>` |
|
|
301
|
+
| the latest value of each category | `OrdinalDataSource` | `OrdinalChartData` | `OrdinalDatum` (`{time, ordinal, value}`) | `<BarPlot>` |
|
|
302
|
+
| values over time, with confidence bounds | `OutlierDataSource` | `OutlierChartData` | `OutlierDatum` (`{datum, bounds}`) | `<OutlierPlot>` |
|
|
696
303
|
|
|
697
|
-
|
|
698
|
-
The color of the chart background (the whole chart, not just the plot).
|
|
304
|
+
Every data source is created with an options object:
|
|
699
305
|
|
|
700
|
-
|
|
701
|
-
|
|
306
|
+
```ts
|
|
307
|
+
const dataSource = new TimeSeriesDataSource({
|
|
308
|
+
initialData, // the series to start with
|
|
309
|
+
generator, // creates the observable each time the source starts
|
|
310
|
+
dropDataAfter: 20_000, // optional: drop data older than 20 s (default: never drop)
|
|
311
|
+
onUpdateData: (seriesName, newData) => {/* e.g. archive the data */}, // optional
|
|
312
|
+
})
|
|
313
|
+
```
|
|
702
314
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
315
|
+
| option | description |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `initialData` | The series to start with. The data source takes ownership of these series, and changes them in place as data arrives. For the time-series, iterates, and ordinal data sources, **the initial data decides the series**: points for a series that isn't in the initial data are ignored. So start with one (possibly empty) series for each series you expect. The outlier data source is the exception: it adds a series the first time it appears in the stream. |
|
|
318
|
+
| `generator` | A [generator](#generators): the function that creates the observable of chart data each time the data source starts. |
|
|
319
|
+
| `dropDataAfter` | Optional time-to-live (ms, in data time, not wall-clock time) for the data. As new data arrives, points older than this, relative to the latest time, are dropped. Defaults to `Infinity` (keep everything). See [performance](#performance). |
|
|
320
|
+
| `onUpdateData` | Optional callback that is handed each series' new points as they arrive, for example to store the data elsewhere before it's dropped. |
|
|
321
|
+
| `timeGroupFor` | `TimeSeriesDataSource` only, optional. Assigns each series to a "time group" whose latest time `dropDataAfter` is measured against. Use it when series on different x-axes run on different clocks, for example `name => axisAssignments.get(name)?.xAxis ?? 'x-axis-1'`. By default, all series share one clock. |
|
|
322
|
+
|
|
323
|
+
Every data source has the same lifecycle and the same read-only view of its data:
|
|
324
|
+
|
|
325
|
+
| member | description |
|
|
326
|
+
|---|---|
|
|
327
|
+
| `start()` | Subscribes to the generator, handing it the current series. Does nothing when already running. A data source starts out stopped. |
|
|
328
|
+
| `stop()` | Unsubscribes from the generator. Nothing keeps generating in the background. The data stays. |
|
|
329
|
+
| `running` | Whether the data source is running. |
|
|
330
|
+
| `isRunningObservable` | Emits whether the data source is running, starting with the current value. Useful for a Run/Pause button (or use the [`useDataSourceRunning`](#hooks) hook). The data source also stops on its own when the generator's observable completes or fails. |
|
|
331
|
+
| `chartDataUpdatesObservable` | Emits each chunk of chart data *after* it has been added to the series. |
|
|
332
|
+
| `series`, `seriesList()` | The live series (a map by name, and an array, in insertion order). They change in place as data arrives, so copy what you need. |
|
|
333
|
+
| `latestTime()` | The latest time of any datum in any series (`-Infinity` when there's no data). |
|
|
334
|
+
| `setDropDataAfter(ms)` | Changes the data TTL. It takes effect as the next data arrives. |
|
|
335
|
+
| `setGenerator(generator)` | Replaces the generator, for example when the update period changes. Only allowed while the data source is stopped; the next `start()` uses it. |
|
|
336
|
+
| `dispose()` | Stops the data source and completes its observables. A disposed data source can't be started again. To start over with new data (a "Reset"), dispose of the old data source and create a new one. |
|
|
337
|
+
|
|
338
|
+
Two data sources keep extra state that their plots use:
|
|
339
|
+
- `OrdinalDataSource.stats` holds the lifetime and windowed statistics (min, max, mean) of each category, which keep accumulating while no plot is mounted.
|
|
340
|
+
- `IteratesDataSource.droppedCountFor(seriesName)` counts the points dropped from a series, so that the Poincaré plot's colorized lines keep their colors as old points are dropped.
|
|
341
|
+
|
|
342
|
+
To build a data source for some other shape of data, extend `BaseStreamingDataSource` and implement `ingest(data)` and `latestTime()`.
|
|
343
|
+
|
|
344
|
+
### <span id="generators">generators</span> [↑](#content "back to contents")
|
|
345
|
+
|
|
346
|
+
A data source doesn't take an observable. It takes a `DataGenerator`, a function that *creates* the observable:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
type DataGenerator<CD extends ChartData, S> = (currentSeries: Array<S>) => Observable<CD>
|
|
350
|
+
```
|
|
713
351
|
|
|
714
|
-
**
|
|
715
|
-
A map holding the data series name with an associated SeriesLineStyle. Any series listed in this map will use the associated styles for that series. Any series not in the map will use the default series styles.
|
|
352
|
+
The data source calls the generator each time it starts, and hands it the series as they are *right now*. That's what lets **Run** after **Pause** carry on from the latest data, rather than starting over from the initial data. A typical generator reads the latest time from the current series and starts the new stream from there (see the [quick start](#quick-start)).
|
|
716
353
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
color: string
|
|
721
|
-
lineWidth: number
|
|
722
|
-
// the color of the series when the user mouses over the series
|
|
723
|
-
highlightColor: string
|
|
724
|
-
// the line width of the series when the user mouses over the series
|
|
725
|
-
highlightWidth: number
|
|
726
|
-
// the line margin used for raster charts
|
|
727
|
-
margin?: number
|
|
728
|
-
}
|
|
729
|
-
>```
|
|
354
|
+
Two things to get right in your observable:
|
|
355
|
+
- **Emit only what's new.** Each emission's `newPoints` holds the points since the last emission. The data source keeps everything that came before.
|
|
356
|
+
- **Make each emission a new object.** Give every emission its own `newPoints` map and its own arrays. The chart can buffer several emissions before it draws them, and if they share (and change) the same map, the buffered emissions all end up holding the latest values.
|
|
730
357
|
|
|
731
|
-
|
|
358
|
+
### <span id="chart-data">chart data, one shape for each plot</span> [↑](#content "back to contents")
|
|
732
359
|
|
|
733
|
-
|
|
360
|
+
Every chart data object holds `seriesNames: Set<string>`, and a `newPoints` map from each series' name to its new points. Each plot's chart data adds a little more.
|
|
734
361
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
// the array of time-value pairs
|
|
744
|
-
data: Array<Datum>
|
|
745
|
-
// ... accessor functions
|
|
746
|
-
// .
|
|
747
|
-
// .
|
|
748
|
-
// .
|
|
362
|
+
**`TimeSeriesChartData`** (scatter and raster plots)
|
|
363
|
+
```ts
|
|
364
|
+
interface TimeSeriesChartData {
|
|
365
|
+
seriesNames: Set<string>
|
|
366
|
+
newPoints: Map<string, Array<Datum>> // each series' new {x: time, y: value} points
|
|
367
|
+
maxTimes: Map<string, number> // each series' latest time
|
|
368
|
+
maxTime: number // the latest time of all of them
|
|
369
|
+
currentTime?: number
|
|
749
370
|
}
|
|
750
371
|
```
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
): Observable<ChartData> {
|
|
776
|
-
const seriesNames = series.map(series => series.name)
|
|
777
|
-
const initialData = initialChartData(series)
|
|
778
|
-
return interval(updatePeriod).pipe(
|
|
779
|
-
// convert the number sequence to a time
|
|
780
|
-
map(sequence => (sequence + 1) * updatePeriod),
|
|
781
|
-
// create a random spike for each series
|
|
782
|
-
map((time) => randomSpikeData(time, seriesNames, initialData.maxTimes, updatePeriod, spikeProbability))
|
|
783
|
-
)
|
|
784
|
-
}
|
|
372
|
+
For a raster plot, each datum is an event (for example, a neuron's spike) at time `x`. Its `y` is shown in the tooltip.
|
|
373
|
+
|
|
374
|
+
**`IterateChartData`** (Poincaré plot)
|
|
375
|
+
|
|
376
|
+
You rarely build iterate data by hand. Write an observable of `TimeSeriesChartData` holding the sequence's values over time, and convert it with `iteratesObservable(observable, n)`, which pairs each value `f[m](x)` with the value `n` steps later, `f[m+n](x)`:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
import {interval, map, scan, type Observable} from "rxjs"
|
|
380
|
+
import {iteratesObservable, type IterateChartData, type IterateSeries, type DataGenerator, type TimeSeriesChartData} from "stream-charts"
|
|
381
|
+
|
|
382
|
+
// the logistic map, x[n+1] = r x[n] (1 - x[n]), one value every 25 ms
|
|
383
|
+
const logisticMap = (r: number, x0: number): Observable<TimeSeriesChartData> =>
|
|
384
|
+
interval(25).pipe(
|
|
385
|
+
scan(({x}, tick) => ({time: (tick + 1) * 25, x: r * x * (1 - x)}), {time: 0, x: x0}),
|
|
386
|
+
map(({time, x}) => ({
|
|
387
|
+
seriesNames: new Set(['logistic']),
|
|
388
|
+
maxTime: time,
|
|
389
|
+
maxTimes: new Map([['logistic', time]]),
|
|
390
|
+
newPoints: new Map([['logistic', [{x: time, y: x}]]]),
|
|
391
|
+
}))
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
const generator: DataGenerator<IterateChartData, IterateSeries> =
|
|
395
|
+
() => iteratesObservable(logisticMap(3.8, 0.2), 1) // 1-iterates: (f[m](x), f[m+1](x))
|
|
785
396
|
```
|
|
786
397
|
|
|
787
|
-
|
|
788
|
-
Optional property, that when set from `false` to `true`, causes the <Chart/> to subscribe to the chart-data observable. When set to `false` after a subscription, causes the <Chart/> to unsubscribe from the chart-data observable.
|
|
789
|
-
|
|
790
|
-
**windowingTime (number, milliseconds, optional, default = 100 ms)**<br>
|
|
791
|
-
Optional property that defines a time-window during which incoming events are buffered, and then handed to plot, causing the plot to update. The `windowingTime` defines the maximum plot update rate, though not the maximum data update rate. The larger the windowing time, the fewer updates per unit time, and the more choppy the updates. Large amounts of data with high update rates can cause rendering delays. The windowing time provides a lever to manage the plot update rates to get the smoothest plot updates that keep up with real-time.
|
|
792
|
-
|
|
793
|
-
**shouldSubscribe (number, optional, default = false)**<br>
|
|
794
|
-
Optional property that default to `false`. When changed to `true`, from `false`, signals the <Chart/> to subscribe to the `seriesObservable`, streaming in the `ChartData` and updating the <Chart/> in real-time.
|
|
795
|
-
|
|
796
|
-
**onSubscribe (callback function, (subscription: Subscription) => void, optional, default = noop**<br>
|
|
797
|
-
Optional callback function that is called when the <Chart/> subscribes to the `ChartData` observable.
|
|
798
|
-
|
|
799
|
-
**onUpdateData (callback function, (seriesName: string, data: Array<Datum>)) => void, optional, default = noop)**<br>
|
|
800
|
-
Optional callback function that is called when the data updates. This callback can be used if you would like to respond to data updates. For example, use this callback if you would like to have the plot drop data after 10 seconds, but would like to store that data in an in-browser database. Though, a more efficient way to store the data would be to subscribe to the series-observable separately, and then use that observer to stream the data to the storage.
|
|
801
|
-
|
|
802
|
-
**onUpdateTime (callback function, (times: Map<string, [start: number, end: number]>) => void, optional, default = noop)**<br>
|
|
803
|
-
Optional callback this is called whenever the time-ranges change. Use this to track the current time of the plot.
|
|
804
|
-
|
|
805
|
-
## [⤒](#content) <span id="axes">axes</span>
|
|
806
|
-
|
|
807
|
-
### [⤒](#content) <span id="continuous-axes-usage"><ContinuousAxis/></span>
|
|
808
|
-
|
|
809
|
-
The <ContinuousAxis/> must be a child of the <Chart/>. Each <Chart/> can one or two continuous axes for the x-axes and for the y-axes. When a <Chart/> has multiple x-axes or y-axes then you must assign series to the axes in one of the `Plot` components. Any series that are not explicitly assigned an axis will be assigned to the default x-axis or y-axis. The default x-axis is the bottom axis in the <Chart/>, and the default y-axis is the left-hand side axis in the <Chart/>.
|
|
810
|
-
|
|
811
|
-
When creating a <ContinuousAxis/>, you must specify its location using the `AxisLocation` enum defined in the [axes.ts](https://github.com/robphilipp/stream-charts/blob/master/src/app/charts/axes.ts) file. Each axis must have a unique axis ID. By default a <ContinuousAxis/> will have a linear scale (d3.scaleLinear). The `scale` property can by used to set the scale to a `log` (d3.scaleLog) or `power` (d3.scalePow) scale or any other continuous numeric scale available in `d3`. The `domain` property defines the initial min and max values for the axis, and when data streams into the plot, that defines the time-window displayed for the axis (unless changed by a zoom event).
|
|
812
|
-
|
|
813
|
-
#### [⤒](#content) <span id="continuous-axes-usage-base"><ContinuousAxis/> base properties</span>
|
|
814
|
-
|
|
815
|
-
The base properties defining the axis.
|
|
816
|
-
|
|
817
|
-
**axisId (string)**<br>
|
|
818
|
-
The unique ID of the axis. The axis can then be referred to by this ID. For example, when assigning axes to series, the assignment is made by associating the axis ID to the series name.
|
|
819
|
-
|
|
820
|
-
**location ([AxisLocation](https://github.com/robphilipp/stream-charts/blob/master/src/app/charts/axes.ts) )**<br>
|
|
821
|
-
The location of the axis. As defined by the `AxisLocation` in the the [axes.ts](https://github.com/robphilipp/stream-charts/blob/master/src/app/charts/axes.ts) file, x-axes can be placed on the `bottom` or the `top`, and y-axes can be placed on the `left` or the `right`.
|
|
822
|
-
|
|
823
|
-
**scale (ScaleContinuousNumeric<number, number>, optional, default = d3.scaleLinear)**<br>
|
|
824
|
-
The optional scale (factory) of the continuous axis. The scale of the axis is like the axis ruler and determines how the points are placed on the screen. For example, a linear scale is like an evenly spaced ruler, and the mapping between screen location and data value are linear. As another example, the log scale has a logarithmic mapping between the screen location and the data. The scale can be a linear scale (default scale, d3.scaleLinear), a logarithmic scale (d3.scaleLog), a power scale (d3.scalePower), or any other d3 continouos numeric scale that works. Not that if a chart, for example, has two x-axes, that the x-axes are **not** required to have the same scale.
|
|
825
|
-
|
|
826
|
-
**domain ([min: number, max: number])**<br>
|
|
827
|
-
The domain of the axis (in d3 terminology) is effectively the minimum value of the axis and the maximum value of the axis when the initial data is displayed. The domain defines the time-window of the displayed data. For example, if the `domain` for an x-axis is specified as `[1000, 6000]`, then the axis starts at `1000` and ends at `6000`, and the time-window is `5000`. Once data starts to stream past the axis end, the plot starts to scroll, maintaining the calculated time-window (in out example, 5000). Of course, a zooming event will change the domain, and also the time-window.
|
|
828
|
-
|
|
829
|
-
**label (string)**<br>
|
|
830
|
-
The axis label.
|
|
831
|
-
|
|
832
|
-
#### [⤒](#content) <span id="continuous-axes-usage-styling"><ContinuousAxis/> styling</span>
|
|
398
|
+
**`OrdinalChartData`** (bar plot)
|
|
833
399
|
|
|
834
|
-
|
|
400
|
+
Like the iterate data, ordinal data is usually derived: `ordinalsObservable(timeSeriesObservable, compactingSize?, initialStats?)` turns a `TimeSeriesChartData` observable (one series per category) into ordinal data, accumulating each category's statistics. Pass `calculateOrdinalStats(currentSeries)` as the `initialStats`, so that a restarted stream's statistics include the data the data source already holds:
|
|
835
401
|
|
|
836
|
-
|
|
837
|
-
|
|
402
|
+
```ts
|
|
403
|
+
const generator: DataGenerator<OrdinalChartData, OrdinalSeries> =
|
|
404
|
+
currentSeries => ordinalsObservable(myCategoryValues(currentSeries), undefined, calculateOrdinalStats(currentSeries))
|
|
405
|
+
```
|
|
838
406
|
|
|
407
|
+
**`OutlierChartData<M>`** (outlier plot)
|
|
408
|
+
```ts
|
|
409
|
+
interface OutlierChartData<M extends readonly number[]> {
|
|
410
|
+
seriesNames: Set<string>
|
|
411
|
+
newPoints: Map<string, Array<OutlierDatum<M>>>
|
|
412
|
+
currentTime?: number
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
`M` is a tuple of *measures*, the confidence level of each band, for example `[0.68, 0.95, 0.997] as const` for 1σ, 2σ, and 3σ. Each `OutlierDatum<M>` holds the point (`datum: {x, y}`) and one `{lower, upper}` bound for each measure (`bounds`). Create them with `outlierDatumFor(datum, measures, bounds)` and `outlierBoundsFor(lower, upper)`. Both check that there's one bound for each measure, and return a `Result`.
|
|
839
416
|
|
|
840
|
-
###
|
|
417
|
+
### <span id="series-helpers">series and datum helpers</span> [↑](#content "back to contents")
|
|
841
418
|
|
|
842
|
-
|
|
419
|
+
A series is a named array of data. Its data is held in a `FastShiftArray`, so that dropping the oldest points is cheap.
|
|
843
420
|
|
|
844
|
-
|
|
421
|
+
| function | creates |
|
|
422
|
+
|---|---|
|
|
423
|
+
| `emptySeries<D>(name)`, `emptySeriesFor<D>(names)` | empty series (a good start for streaming data) |
|
|
424
|
+
| `seriesFrom<D>(name, data)` | a series holding the data |
|
|
425
|
+
| `timeSeriesFromTuples(name, [[time, value], ...])` | a time series (scatter, raster) |
|
|
426
|
+
| `iterateSeriesFromTuples(name, [[time, iterateN, iterateN_1], ...])` | an iterate series (Poincaré) |
|
|
427
|
+
| `ordinateSeriesFromTuples(name, [[time, ordinal, value], ...])` | an ordinal series (bar) |
|
|
428
|
+
| `outlierSeriesFor(name, measures, [[time, value, bounds], ...])`, `outlierSeriesFrom(name, data, measures)` | an outlier series |
|
|
429
|
+
| `datumOf(time, value)`, `iterateDatumOf(...)`, `ordinalDatumOf(...)` | a single datum |
|
|
430
|
+
| `initialTimeSeriesChartData(series)`, `initialOrdinalChartData(series)` | chart data to start a stream from |
|
|
845
431
|
|
|
846
|
-
|
|
432
|
+
> **Ownership:** `seriesFrom` wraps the array you hand it *by reference*. As data streams in and ages out, the library changes that array in place. If you keep your own reference to it, pass a copy (`data.slice()`) instead.
|
|
847
433
|
|
|
848
|
-
|
|
434
|
+
`series.last()` returns a `Result` (from [result-fn](https://github.com/robphilipp/result-fn)), which is a failure when the series is empty: `series.last().map(datum => datum.x).getOrElse(0)`.
|
|
849
435
|
|
|
850
|
-
|
|
851
|
-
The unique ID of the axis. The axis can then be referred to by this ID. For example, when assigning axes to series, the assignment is made by associating the axis ID to the series name.
|
|
436
|
+
### <span id="performance">performance: data TTL, windowing, and cadence</span> [↑](#content "back to contents")
|
|
852
437
|
|
|
853
|
-
|
|
854
|
-
|
|
438
|
+
`stream-charts` is built for large amounts of high-frequency data. When the data's time passes the end of the x-axis, the chart scrolls to keep up. The data that scrolls off the chart is still held, so you can pan or zoom back to it. To get there, the charts:
|
|
439
|
+
1. take their data from an [RxJS](https://rxjs.dev) observable, through a data source,
|
|
440
|
+
2. draw onto a single `<canvas>` (with [d3](https://d3js.org) scales and shapes), and
|
|
441
|
+
3. keep the drawing outside React's render cycle.
|
|
855
442
|
|
|
856
|
-
|
|
857
|
-
The required `categories` property holds the names of the categories, in the order that they will be displayed on the axis. The first element in the array will be shown at the top of the axis. The second element will be lower, and the last element will be at the bottom of the axis.
|
|
443
|
+
There are limits, of course. When a chart starts to stutter, you have these levers. Each stream is different, so expect to tune them.
|
|
858
444
|
|
|
859
|
-
**
|
|
860
|
-
|
|
445
|
+
1. **Drop old data (`dropDataAfter`, on the data source).** Without it, the data that scrolls off the chart is kept, and processed, forever. With it, a scrolling chart can run indefinitely. The TTL is measured in data time, not wall-clock time. To keep the dropped data, store it with the data source's `onUpdateData` callback (or a separate subscription to your observable).
|
|
446
|
+
2. **Batch the updates (`windowingTime`, on the chart).** The chart collects incoming data for this long (ms) before redrawing. Longer windows mean fewer redraws, and so better performance, at the cost of choppier scrolling.
|
|
447
|
+
3. **Tell the chart your update period (`dataUpdatePeriod`, on the chart).** When your data arrives at a known, regular period, the chart buffers a fixed number of updates rather than a fixed amount of time, which makes scrolling smoother. Leave it out when your data arrives at irregular times.
|
|
448
|
+
4. **Use a cadence for slow data (`withCadenceOf`, on the plot).** When updates are slow (more than about 100 ms apart), a cadence of 10–25 ms advances the time axis smoothly between updates. At high update rates it's unnecessary, and a cadence that's too short hurts performance.
|
|
449
|
+
5. **Shorten the time window (the x-axis `domain`).** A shorter window holds less data, though the data streams by faster.
|
|
861
450
|
|
|
862
|
-
|
|
863
|
-
The axis label.
|
|
451
|
+
---
|
|
864
452
|
|
|
865
|
-
|
|
453
|
+
## <span id="components">components</span> [↑](#content "back to contents")
|
|
866
454
|
|
|
867
|
-
|
|
455
|
+
### <span id="chart"><Chart/></span> [↑](#content "back to contents")
|
|
868
456
|
|
|
869
|
-
|
|
870
|
-
An optional CSS properties specifying the font for the axis and tick labels.
|
|
457
|
+
The `<Chart>` is the container for everything else: the axes, the plot, and the tracker, tooltip, and legend. It creates the canvas, reads from the data source, and provides the shared state that its children use (see [hooks](#hooks)).
|
|
871
458
|
|
|
459
|
+
```tsx
|
|
460
|
+
<Chart
|
|
461
|
+
chartId={1}
|
|
462
|
+
width={800}
|
|
463
|
+
height={400}
|
|
464
|
+
margin={{...defaultMargin, top: 40, right: 60}}
|
|
465
|
+
color={theme.color}
|
|
466
|
+
backgroundColor={theme.backgroundColor}
|
|
467
|
+
seriesStyles={new Map([['kitchen', {...defaultLineStyle(), color: 'orange', highlightColor: 'orange'}]])}
|
|
468
|
+
seriesFilter={filter}
|
|
469
|
+
dataSource={dataSource}
|
|
470
|
+
windowingTime={50}
|
|
471
|
+
dataUpdatePeriod={50}
|
|
472
|
+
onUpdateAxesBounds={ranges => setXAxisRange(ranges.get('x-axis-1'))}
|
|
473
|
+
>
|
|
474
|
+
{/* axes, plot, tracker, tooltip, legend */}
|
|
475
|
+
</Chart>
|
|
476
|
+
```
|
|
872
477
|
|
|
873
|
-
|
|
478
|
+
The chart is generic in its data: `Chart<CD, D, S, TM, AR, A>`, where `CD` is the chart data, `D` the datum, `S` the series style, `TM` the tooltip metadata, `AR` the axis range, and `A` the axis type. TypeScript infers these for most charts. For a bar chart, spell them out:
|
|
874
479
|
|
|
875
|
-
|
|
480
|
+
```tsx
|
|
481
|
+
<Chart<OrdinalChartData, OrdinalDatum, BarSeriesStyle, WindowedOrdinalStats, OrdinalAxisRange, OrdinalStringAxis> ...>
|
|
482
|
+
```
|
|
876
483
|
|
|
877
|
-
|
|
484
|
+
#### <span id="chart-dimensions">dimensions</span>
|
|
878
485
|
|
|
879
|
-
|
|
486
|
+
| prop | type | description |
|
|
487
|
+
|---|---|---|
|
|
488
|
+
| `chartId` | `number` | **Required.** A unique ID for the chart. |
|
|
489
|
+
| `width` | `number` | **Required.** The width (px) of the chart's container. The plot area is this, less the margins. |
|
|
490
|
+
| `height` | `number` | **Required.** The height (px) of the chart's container. The plot area is this, less the margins. |
|
|
491
|
+
| `margin` | `Partial<Margin>` | The space (px) between the container's edges and the plot area, which holds the axes, their ticks, and labels. Any side you leave out uses `defaultMargin`. `Margin` is `{top, bottom, left, right}`. |
|
|
880
492
|
|
|
881
|
-
The
|
|
493
|
+
The chart draws onto a canvas, which must know its exact size in pixels, so the chart doesn't size itself to its container: you hand it `width` and `height`. It redraws when they change. To follow a resizing window or layout, measure the chart's container, for example with a `ResizeObserver`, and pass the size along. (The examples get their sizes from the grid cells of [react-resizable-grid-layout](https://www.npmjs.com/package/react-resizable-grid-layout), with `useGridCellWidth()` and `useGridCellHeight()`.)
|
|
882
494
|
|
|
883
|
-
####
|
|
495
|
+
#### <span id="chart-styling">styling</span>
|
|
884
496
|
|
|
885
|
-
|
|
886
|
-
|
|
497
|
+
| prop | type | description |
|
|
498
|
+
|---|---|---|
|
|
499
|
+
| `color` | `string` | The default color of the axes and text (default `'#d2933f'`). The axes' own styles can override it. |
|
|
500
|
+
| `backgroundColor` | `string` | The background color of the whole chart (default `'#202020'`). |
|
|
501
|
+
| `seriesStyles` | `Map<string, S>` | Maps a series' name to its style. A series that isn't in the map uses the default style. For the scatter, raster, Poincaré, and outlier plots, the style is a `SeriesLineStyle` (start from `defaultLineStyle()`). For the bar plot, it's a `BarSeriesStyle` (start from `defaultBarSeriesStyle(color)`). |
|
|
502
|
+
| `svgStyle` | `Partial<SvgStyle>` | CSS overrides for the chart's container element. Rarely needed. |
|
|
887
503
|
|
|
888
|
-
|
|
889
|
-
|
|
504
|
+
```ts
|
|
505
|
+
interface SeriesLineStyle {
|
|
506
|
+
color: string
|
|
507
|
+
lineWidth: number
|
|
508
|
+
highlightColor: string // the color of the series when the mouse is over it
|
|
509
|
+
highlightWidth: number // the line width of the series when the mouse is over it
|
|
510
|
+
margin?: number // the raster plot's spike margin, for this series
|
|
511
|
+
}
|
|
512
|
+
```
|
|
890
513
|
|
|
891
|
-
|
|
892
|
-
Optional property that sets when to drop data (effectively a TTL). This only drops data while streaming. Don't worry, your data won't disappear after the streaming has stopped. By default, none of the data is dropped. However, when large amounts of data are being streamed and plotted over long periods of time, memory and performance may become an issue. Setting this value allows a scrolling chart to run forever without running into resource issues.
|
|
514
|
+
All styles are exposed as props, so you can drive them from your theme provider, or not use one at all (see [theming](#theming)).
|
|
893
515
|
|
|
894
|
-
####
|
|
516
|
+
#### <span id="chart-data-props">data</span>
|
|
895
517
|
|
|
896
|
-
|
|
518
|
+
| prop | type | description |
|
|
519
|
+
|---|---|---|
|
|
520
|
+
| `dataSource` | `StreamingDataSource` | **Required.** The [data source](#data-sources) that holds the chart's data. The chart only reads from it; you start and stop it. Handing the chart a *new* data source resets the plots to that data source's data. |
|
|
521
|
+
| `windowingTime` | `number` (ms) | How long the chart collects incoming data before it redraws (default 100 ms). See [performance](#performance). |
|
|
522
|
+
| `dataUpdatePeriod` | `number` (ms) | The period at which your generator emits, when you know it. The chart then buffers a fixed number of updates (`windowingTime / dataUpdatePeriod`), which makes scrolling smoother. |
|
|
523
|
+
| `seriesFilter` | `RegExp` | Only the series whose names match are shown. It can change while streaming. Patterns that risk catastrophic backtracking are ignored, with a warning (see [filtering series](#filtering)). |
|
|
524
|
+
| `onUpdateAxesBounds` | `(ranges: Map<string, AxisInterval>) => void` | Called when the axes' ranges change, for example as the x-axis scrolls, or when the user zooms or pans. The map is keyed by axis ID. Use it to keep the current time range in your state (and to hand it back as the axis `domain` when the chart remounts). |
|
|
525
|
+
| `onUpdateChartTime` | `(time: number) => void` | Called with the current chart time. Mainly for plots whose axes don't show time, like the Poincaré and bar plots. |
|
|
526
|
+
| `children` | | The axes, the plot, and optionally the tracker, tooltip, and legend. |
|
|
897
527
|
|
|
898
|
-
|
|
899
|
-
Optional property that defaults to `false`. When set to `true` then enables "panning" which allows the user to drag the plot to the left and right.
|
|
528
|
+
### <span id="axes">axes</span> [↑](#content "back to contents")
|
|
900
529
|
|
|
901
|
-
|
|
902
|
-
Optional property that defaults for `false`. When set to `true` then enables "zooming" which allows the user to increase or decrease the displayed time range. By default, scrolling will cause the zoom effect. See the `zoomKeyModifiersRequired` property which requires the `shift` key to be pressed in order for the zoom action to apply.
|
|
530
|
+
A chart has one or two x-axes (`AxisLocation.Bottom`, `AxisLocation.Top`) and one or two y-axes (`AxisLocation.Left`, `AxisLocation.Right`). Each axis has a unique `axisId`. When a chart has more than one x-axis or y-axis, you assign series to axes with the plot's [`axisAssignments`](#plot-common). A series without an assignment uses the default axes: the first x-axis and the first y-axis in the chart's children, whatever their location.
|
|
903
531
|
|
|
904
|
-
|
|
905
|
-
An optional property that defaults to `false`. When set to `true`, and the `zoomEnabled` property is also set to `true`, then requires that the `shift` key be pressed when scrolling in order to activate the zoom. This is nice when a user can scroll through your page containing the plot, so that zooming doesn't interfere with scrolling.
|
|
532
|
+
#### <span id="plot-axes">which axes each plot takes</span>
|
|
906
533
|
|
|
907
|
-
|
|
908
|
-
An optional property that defaults to `undefined`. When set, uses a cadence with the specified refresh period (in milliseconds). For plots with slow data updates (> 100 ms) using a cadence of 10 to 25 ms smooths out the updates so the time scrolling doesn't appear choppy. When updates are around 25 ms or less, then setting the cadence period too small will result in poor update performance. Generally at high update speeds, the cadence is unnecessary. Finally, using cadence, sets the max time to the current time. See also the related <Chart/>'s `windowingTime` property.
|
|
534
|
+
Each plot needs particular kinds of axes:
|
|
909
535
|
|
|
536
|
+
| plot | x-axis | y-axis |
|
|
537
|
+
|---|---|---|
|
|
538
|
+
| `<ScatterPlot>` | `<ContinuousAxis>` | `<ContinuousAxis>` |
|
|
539
|
+
| `<RasterPlot>` | `<ContinuousAxis>` | `<OrdinalAxis>` |
|
|
540
|
+
| `<PoincarePlot>` | `<ContinuousAxis>` | `<ContinuousAxis>` |
|
|
541
|
+
| `<BarPlot>` | `<OrdinalAxis>` | `<ContinuousAxis>` |
|
|
542
|
+
| `<OutlierPlot>` | `<ContinuousAxis>` | `<ContinuousAxis>` |
|
|
910
543
|
|
|
911
|
-
|
|
544
|
+
- **An `<EmptyAxis>` counts as a continuous axis.** It's fine as a raster chart's top x-axis, but a raster chart's series can't use one as their y-axis.
|
|
545
|
+
- **The order of the axes matters.** Unassigned series use the *first* x-axis and the *first* y-axis, so put the axes your series use before any `<EmptyAxis>`. In a raster chart with an `<EmptyAxis>` on the right, list the `<OrdinalAxis>` first; otherwise the empty (continuous) axis becomes the default y-axis.
|
|
546
|
+
- **The Poincaré plot only uses the first x-axis and the first y-axis** (it has no `axisAssignments`). Any others just mirror them.
|
|
547
|
+
- **A series assigned to an axis ID that doesn't exist isn't drawn.** It doesn't fall back to the default axes.
|
|
548
|
+
- **The wrong kind of axis is reported when the plot draws.** The chart logs the error to the console, along the lines of `<RasterPlot> needs its y-axis to be an <OrdinalAxis>, but the y-axis "y-axis-2" is a <ContinuousAxis> (or an <EmptyAxis>)`, and skips that drawing. The axes are child components, so TypeScript can't catch the mismatch.
|
|
912
549
|
|
|
913
|
-
|
|
550
|
+
#### <span id="continuous-axis"><ContinuousAxis/></span>
|
|
914
551
|
|
|
915
|
-
|
|
552
|
+
A numeric axis, for time on the x-axis, or for values on either axis.
|
|
916
553
|
|
|
917
|
-
|
|
554
|
+
```tsx
|
|
555
|
+
<ContinuousAxis axisId="y-axis-2" location={AxisLocation.Right} scale={d3.scaleLog()} domain={[100, 1200]} label="distance (µm)"/>
|
|
556
|
+
```
|
|
918
557
|
|
|
919
|
-
|
|
558
|
+
| prop | type | description |
|
|
559
|
+
|---|---|---|
|
|
560
|
+
| `axisId` | `string` | **Required.** The axis' unique ID, used to assign series to it. |
|
|
561
|
+
| `location` | `AxisLocation` | **Required.** `Bottom` or `Top` for an x-axis; `Left` or `Right` for a y-axis. |
|
|
562
|
+
| `domain` | `[min, max]` | **Required.** The axis' starting range. For a time axis, this is also the time window: a domain of `[0, 5000]` shows 5 seconds, and once data passes 5000 ms, the axis scrolls, keeping that window (until a zoom changes it). |
|
|
563
|
+
| `label` | `string` | **Required.** The axis label. |
|
|
564
|
+
| `scale` | `ScaleContinuousNumeric` | The d3 scale (default `d3.scaleLinear()`). Any continuous numeric d3 scale works, such as `d3.scaleLog()` or `d3.scalePow()`. Two x-axes (or y-axes) can have different scales. |
|
|
565
|
+
| `font` | `Partial<AxesFont>` | The font of the ticks and label. |
|
|
566
|
+
| `updateAxisBasedOnDomainValues` | `boolean` | When `true` (the default), the axis resets when the *values* of `domain` change, so you can pass a new tuple literal on each render. For charts that don't scroll, such as the Poincaré plot, set it to `false` to reset when the `domain` *object* changes instead, for example when the user picks a new function. |
|
|
920
567
|
|
|
921
|
-
|
|
922
|
-
An optional property that assigns data series to (x, y)-axes. Any series not assigned to an axis will use the default axis. The default x-axis is the bottom axis, and the default y-axis is the axis on the left-hand side of the plot. The `Map` associates the series name with an [AxesAssignment](https://github.com/robphilipp/stream-charts/blob/master/src/app/charts/plot.ts), which is a simple object (`{xAxis: string, yAxis: string}`) that holds the axis ID for the x-axis and for the y-axis assigned to the series.
|
|
568
|
+
<span id="continuous-axis-autoscale">**Auto-scaling y-axes**</span> (scatter, bar, and outlier plots). With `autoScale`, a y-axis grows to fit the data on screen. It only ever grows, and goes back to `domain` when the plot's data is replaced, or when `domain` or the limits change.
|
|
923
569
|
|
|
924
|
-
|
|
925
|
-
|
|
570
|
+
| prop | type | description |
|
|
571
|
+
|---|---|---|
|
|
572
|
+
| `autoScale` | `boolean` | Turns auto-scaling on (default `false`). Ignored, with a warning, on x-axes and in plots that don't support it. |
|
|
573
|
+
| `autoScaleMin`, `autoScaleMax` | `number` | The limits the domain may grow to (default `-Infinity` and `Infinity`). |
|
|
574
|
+
| `autoScalePadding` | `number` | The fraction of the axis left beyond the data's extreme (0 to 0.45, default 0.05). |
|
|
575
|
+
| `autoScaledDomain`, `onAutoScale` | `[min, max]`, `(domain) => void` | Keep the grown domain across remounts: store what `onAutoScale` hands you, and pass it back as `autoScaledDomain`. |
|
|
926
576
|
|
|
927
|
-
|
|
928
|
-
Optional property that adds a margin to the top and bottom of the raster (event) lines to give vertical spacing to the events in the plot. Margins on individual series can also be set through the [Chart.seriesStyles](https://github.com/robphilipp/stream-charts/blob/master/src/app/charts/hooks/useChart.tsx) property.
|
|
577
|
+
#### <span id="ordinal-axis"><OrdinalAxis/></span>
|
|
929
578
|
|
|
930
|
-
|
|
579
|
+
A category axis (a d3 band scale). The raster plot uses one for its y-axis (one row per series), and the bar plot uses one for its x-axis (one bar per series).
|
|
931
580
|
|
|
932
|
-
|
|
581
|
+
```tsx
|
|
582
|
+
<OrdinalAxis axisId="y-axis-1" location={AxisLocation.Left} categories={dataSource.seriesList().map(s => s.name)} label="neuron"/>
|
|
583
|
+
```
|
|
933
584
|
|
|
934
|
-
|
|
935
|
-
|
|
585
|
+
| prop | type | description |
|
|
586
|
+
|---|---|---|
|
|
587
|
+
| `axisId` | `string` | **Required.** The axis' unique ID. |
|
|
588
|
+
| `location` | `AxisLocation` | **Required.** Where the axis goes. |
|
|
589
|
+
| `categories` | `Array<string>` | **Required.** The category names, in display order. On a y-axis, the first category is at the top. |
|
|
590
|
+
| `label` | `string` | **Required.** The axis label. |
|
|
591
|
+
| `axisTickStyle` | `Partial<AxisTickStyle>` | The style of the tick labels, for example `{rotation: 45}` for long category names. |
|
|
592
|
+
| `font` | `Partial<AxesFont>` | The font of the ticks and label. |
|
|
593
|
+
| `scale` | `ScaleBand<string>` | The d3 band scale (rarely needed). |
|
|
936
594
|
|
|
937
|
-
|
|
938
|
-
Optional property that defaults for `false`. When set to `true` then enables "zooming" which allows the user to increase or decrease the displayed time range. By default, scrolling will cause the zoom effect. See the `zoomKeyModifiersRequired` property which requires the `shift` key to be pressed in order for the zoom action to apply.
|
|
595
|
+
#### <span id="empty-axis"><EmptyAxis/></span>
|
|
939
596
|
|
|
940
|
-
|
|
941
|
-
An optional property that defaults to `false`. When set to `true`, and the `zoomEnabled` property is also set to `true`, then requires that the `shift` key be pressed when scrolling in order to activate the zoom. This is nice when a user can scroll through your page containing the plot, so that zooming doesn't interfere with scrolling.
|
|
597
|
+
A plain line where an axis would be, without ticks or a label. Use it to frame the plot on a side that has no axis of its own. It counts as a continuous axis, so list it after the axes your series use (see [which axes each plot takes](#plot-axes)).
|
|
942
598
|
|
|
943
|
-
|
|
944
|
-
|
|
599
|
+
```tsx
|
|
600
|
+
<EmptyAxis axisId="x-axis-2" location={AxisLocation.Top}/>
|
|
601
|
+
```
|
|
945
602
|
|
|
946
|
-
###
|
|
603
|
+
### <span id="plots">plots</span> [↑](#content "back to contents")
|
|
947
604
|
|
|
948
|
-
The
|
|
605
|
+
The plot draws the data. It reads the series from the chart's data source and the scales from its sibling axes. A chart holds one plot.
|
|
949
606
|
|
|
950
|
-
####
|
|
607
|
+
#### <span id="plot-common">properties common to the plots</span>
|
|
951
608
|
|
|
952
|
-
|
|
953
|
-
|
|
609
|
+
| prop | type | plots | description |
|
|
610
|
+
|---|---|---|---|
|
|
611
|
+
| `axisAssignments` | `Map<string, AxesAssignment>` | all but Poincaré | Assigns series to axes: `new Map([['kitchen', assignAxes('x-axis-2', 'y-axis-2')]])`. Unassigned series use the first x-axis and the first y-axis (see [which axes each plot takes](#plot-axes)). |
|
|
612
|
+
| `panEnabled` | `boolean` | all | Lets the user drag the plot to pan (default `false`). |
|
|
613
|
+
| `zoomEnabled` | `boolean` | all | Lets the user scroll to zoom (default `false`). |
|
|
614
|
+
| `zoomKeyModifiersRequired` | `boolean` | all | When `true` (the default), zooming requires holding shift (or ctrl) while scrolling, so that zooming doesn't get in the way of scrolling the page. |
|
|
615
|
+
| `withCadenceOf` | `number` (ms) | all | Advances the time axis on this cadence, between data updates, for smooth scrolling of slow data. See [performance](#performance). (The Poincaré plot accepts it, but doesn't use it, since its axes don't show time.) |
|
|
616
|
+
| `highlightAxesOnMouseOver` | `boolean` | all | When `true`, hovering over a series also highlights the axes it's plotted against (default `false`). |
|
|
617
|
+
| `onZoomReset` | `(resetZoom: () => void) => void` | all | Hands you a `resetZoom` function, to call when you reset the axes yourself (for example, from a **Reset** button). The zoom keeps its own state, separate from the axes' ranges. Without `resetZoom`, the next zoom gesture after such a reset jumps back to the old zoom. |
|
|
954
618
|
|
|
955
|
-
|
|
956
|
-
When `true`, displays bars representing the minimum and maximum values in the data.
|
|
619
|
+
#### <span id="scatter-plot"><ScatterPlot/></span>
|
|
957
620
|
|
|
958
|
-
|
|
959
|
-
When `true`, displays bars representing the windowed minimum and maximum values.
|
|
621
|
+
Values over time, drawn as lines, with optional markers. Both axes are `<ContinuousAxis>`. Uses a `TimeSeriesDataSource`. ([full example](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/StreamingScatterChart.tsx), [walkthrough](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/scatter-chart.md))
|
|
960
622
|
|
|
961
|
-
|
|
962
|
-
|
|
623
|
+
<picture>
|
|
624
|
+
<source media="(prefers-color-scheme: dark)" srcset="packages/examples/public/images/scatter-chart-dark.png">
|
|
625
|
+
<img src="packages/examples/public/images/scatter-chart-light.png" alt="the scatter chart example: ten random walks on two x-axes and two y-axes, with an external legend">
|
|
626
|
+
</picture>
|
|
963
627
|
|
|
964
|
-
|
|
965
|
-
When `true`, displays lines representing the mean values of the data series.
|
|
628
|
+
[▶ watch it stream](packages/examples/public/images/scatter-chart-streaming.gif) (animated)
|
|
966
629
|
|
|
967
|
-
|
|
968
|
-
|
|
630
|
+
| prop | type | description |
|
|
631
|
+
|---|---|---|
|
|
632
|
+
| `interpolation` | `d3.CurveFactory` | How the line is drawn between points (default `d3.curveLinear`). Any [d3 curve](https://d3js.org/d3-shape/curve) works, such as `d3.curveMonotoneX` or `d3.curveStep`. |
|
|
633
|
+
| `timeWindowBehavior` | `TimeWindowBehavior` | `SCROLL` (the default) scrolls the time axis as data passes its end. `SQUEEZE` compresses the time axis to fit all the data instead. |
|
|
634
|
+
| `markerRadius` | `number` (px) | Draws a circle of this radius at each point. No markers when omitted. |
|
|
635
|
+
| `hideMarkersWhileRunning` | `boolean` | Hides the markers while the data source is running, showing them when paused (default `true`). |
|
|
969
636
|
|
|
970
|
-
|
|
971
|
-
|
|
637
|
+
```tsx
|
|
638
|
+
<Chart chartId={1} width={width} height={height} dataSource={dataSource} seriesStyles={seriesStyles}
|
|
639
|
+
windowingTime={50} dataUpdatePeriod={50} onUpdateAxesBounds={handleAxesBounds}>
|
|
640
|
+
<ContinuousAxis axisId="x-axis-1" location={AxisLocation.Bottom} domain={[0, 10_000]} label="time (ms)"/>
|
|
641
|
+
<ContinuousAxis axisId="y-axis-1" location={AxisLocation.Left} domain={[0, 1000]} label="distance (µm)"/>
|
|
642
|
+
<ContinuousAxis axisId="x-axis-2" location={AxisLocation.Top} domain={[0, 5000]} label="expanded time (ms)"/>
|
|
643
|
+
<ContinuousAxis axisId="y-axis-2" location={AxisLocation.Right} scale={d3.scaleLog()} domain={[100, 1200]} label="distance (µm)"/>
|
|
644
|
+
<Tracker visible={true} labelFormatter={x => `${d3.format(",.0f")(x)} ms`}/>
|
|
645
|
+
<Tooltip visible={true}>
|
|
646
|
+
<ScatterPlotTooltipContent xLabel="t (ms)" yLabel="distance" yValueFormatter={value => formatNumber(value, " ,.0f")}/>
|
|
647
|
+
</Tooltip>
|
|
648
|
+
<ScatterPlot
|
|
649
|
+
interpolation={d3.curveMonotoneX}
|
|
650
|
+
axisAssignments={new Map([['test2', assignAxes('x-axis-2', 'y-axis-2')]])}
|
|
651
|
+
panEnabled={true}
|
|
652
|
+
zoomEnabled={true}
|
|
653
|
+
markerRadius={2}
|
|
654
|
+
/>
|
|
655
|
+
</Chart>
|
|
656
|
+
```
|
|
972
657
|
|
|
973
|
-
####
|
|
658
|
+
#### <span id="raster-plot"><RasterPlot/></span>
|
|
974
659
|
|
|
975
|
-
|
|
976
|
-
These properties behave the same as in [<ScatterPlot/>](#scatter-plot-usage-view).
|
|
660
|
+
Events over time: each series is a row, and each datum is a short vertical line (a "spike") at its time. The x-axis is a `<ContinuousAxis>`, and the y-axis is an `<OrdinalAxis>` whose categories are the series names. Uses a `TimeSeriesDataSource`. ([full example](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/StreamingRasterChart.tsx), [walkthrough](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/raster-chart.md))
|
|
977
661
|
|
|
978
|
-
|
|
662
|
+
<picture>
|
|
663
|
+
<source media="(prefers-color-scheme: dark)" srcset="packages/examples/public/images/raster-chart-dark.png">
|
|
664
|
+
<img src="packages/examples/public/images/raster-chart-light.png" alt="the raster chart example: fifty neurons, one row each, with a spike for each event">
|
|
665
|
+
</picture>
|
|
979
666
|
|
|
980
|
-
|
|
667
|
+
[▶ watch it stream](packages/examples/public/images/raster-chart-streaming.gif) (animated)
|
|
981
668
|
|
|
982
|
-
|
|
669
|
+
| prop | type | description |
|
|
670
|
+
|---|---|---|
|
|
671
|
+
| `spikeMargin` | `number` (px) | Space above and below each spike, within its row (default 2). Set it per series with the series style's `margin`. |
|
|
983
672
|
|
|
984
|
-
|
|
985
|
-
|
|
673
|
+
```tsx
|
|
674
|
+
<Chart chartId={2} width={width} height={height} dataSource={dataSource} seriesStyles={seriesStyles} windowingTime={50}>
|
|
675
|
+
<ContinuousAxis axisId="x-axis-1" location={AxisLocation.Bottom} domain={[0, 5000]} label="t (ms)"/>
|
|
676
|
+
<EmptyAxis axisId="x-axis-2" location={AxisLocation.Top}/>
|
|
677
|
+
<OrdinalAxis axisId="y-axis-1" location={AxisLocation.Left} categories={dataSource.seriesList().map(s => s.name)} label="neuron"/>
|
|
678
|
+
<EmptyAxis axisId="y-axis-2" location={AxisLocation.Right}/>
|
|
679
|
+
<Tracker visible={true} labelFormatter={x => `${d3.format(",.0f")(x)} ms`}/>
|
|
680
|
+
<Tooltip visible={true}>
|
|
681
|
+
<RasterPlotTooltipContent xFormatter={value => formatNumber(value, " ,.0f") + ' ms'}/>
|
|
682
|
+
</Tooltip>
|
|
683
|
+
<RasterPlot spikeMargin={1} panEnabled={true} zoomEnabled={true}/>
|
|
684
|
+
</Chart>
|
|
685
|
+
```
|
|
986
686
|
|
|
987
|
-
|
|
988
|
-
When `true`, displays individual data points on the plot.
|
|
687
|
+
#### <span id="bar-plot"><BarPlot/></span>
|
|
989
688
|
|
|
990
|
-
|
|
689
|
+
One bar per category, showing the category's latest value and its statistics, both over all time ("lifetime") and over the data currently held ("windowed", which depends on the data source's `dropDataAfter`). The x-axis is an `<OrdinalAxis>`, and the y-axis is a `<ContinuousAxis>`. Uses an `OrdinalDataSource`. ([full example](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/StreamingBarChart.tsx), [walkthrough](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/bar-chart.md))
|
|
991
690
|
|
|
992
|
-
|
|
993
|
-
|
|
691
|
+
<picture>
|
|
692
|
+
<source media="(prefers-color-scheme: dark)" srcset="packages/examples/public/images/bar-chart-dark.png">
|
|
693
|
+
<img src="packages/examples/public/images/bar-chart-light.png" alt="the bar chart example: fifty categories, each with its value, min/max, and mean">
|
|
694
|
+
</picture>
|
|
994
695
|
|
|
995
|
-
|
|
696
|
+
[▶ watch it stream](packages/examples/public/images/bar-chart-streaming.gif) (animated)
|
|
996
697
|
|
|
997
|
-
|
|
698
|
+
| prop | type | description |
|
|
699
|
+
|---|---|---|
|
|
700
|
+
| `showValueLines` | `boolean` | Shows each category's latest value (default `true`). |
|
|
701
|
+
| `showMinMaxBars` | `boolean` | Shows each category's lifetime min-to-max bar (default `true`). |
|
|
702
|
+
| `showMeanValueLines` | `boolean` | Shows each category's lifetime mean (default `true`). |
|
|
703
|
+
| `showWindowedMinMaxBars` | `boolean` | Shows each category's windowed min-to-max bar (default `true`). |
|
|
704
|
+
| `showWindowedMeanValueLines` | `boolean` | Shows each category's windowed mean (default `true`). |
|
|
705
|
+
| `barMargin` | `number` (px) | Space between a category's edges and its bars (default 2). |
|
|
706
|
+
| `barSeriesStyle` | `BarSeriesStyle` | The default style for categories without one in the chart's `seriesStyles` (default `defaultBarSeriesStyle()`). |
|
|
707
|
+
| `zoomState`, `onZoomStateChange` | `BarPlotZoomState`, `(state) => void` | Keep the zoom across remounts: store what `onZoomStateChange` hands you, and pass it back as `zoomState`. It's read once, when the plot mounts. |
|
|
998
708
|
|
|
999
|
-
|
|
709
|
+
Style the bars with `defaultBarSeriesStyle(color)`, and override the parts you want:
|
|
1000
710
|
|
|
1001
|
-
|
|
711
|
+
```tsx
|
|
712
|
+
seriesStyles={new Map<string, BarSeriesStyle>([
|
|
713
|
+
['HC 1', {...defaultBarSeriesStyle('orange'), lineWidth: 2}],
|
|
714
|
+
['HC 14', {
|
|
715
|
+
...defaultBarSeriesStyle('blue'),
|
|
716
|
+
minMaxBar: {...defaultBarSeriesStyle('blue').minMaxBar, widthFraction: 1}
|
|
717
|
+
}],
|
|
718
|
+
])}
|
|
719
|
+
```
|
|
1002
720
|
|
|
1003
|
-
|
|
1004
|
-
|
|
721
|
+
```tsx
|
|
722
|
+
<Chart<OrdinalChartData, OrdinalDatum, BarSeriesStyle, WindowedOrdinalStats, OrdinalAxisRange, OrdinalStringAxis>
|
|
723
|
+
chartId={3} width={width} height={height} dataSource={dataSource} seriesStyles={seriesStyles}
|
|
724
|
+
windowingTime={50} onUpdateChartTime={setChartTime}
|
|
725
|
+
>
|
|
726
|
+
<OrdinalAxis axisId="x-axis-1" location={AxisLocation.Bottom} categories={categories} label="neuron" axisTickStyle={{rotation: 90}}/>
|
|
727
|
+
<ContinuousAxis axisId="y-axis-1" location={AxisLocation.Left} domain={[-1.1, 1.1]} label="ρ (mV)"/>
|
|
728
|
+
<Tracker visible={true} trackerAxis={AxisLocation.Left} labelFormatter={y => `${d3.format(".2f")(y)} mV`}/>
|
|
729
|
+
<Tooltip visible={true}>
|
|
730
|
+
<BarPlotTooltipContent ordinalUnits="mV"/>
|
|
731
|
+
</Tooltip>
|
|
732
|
+
<BarPlot barMargin={1} panEnabled={true} zoomEnabled={true} showWindowedMinMaxBars={false}/>
|
|
733
|
+
</Chart>
|
|
734
|
+
```
|
|
1005
735
|
|
|
1006
|
-
|
|
1007
|
-
Optional property specifies the location of the label. The `TrackerLabelLocation` is an enumeration with three values `Nowhere`, `WithMouse`, and `ByAxes`. By default, the label location is with the mouse (i.e. `WithMouse`). The `Nowhere` location can be used to hide the label, in cases where you would like to use your own. The `WithMouse` value has the label follow the mouse in the vertical direction along the tracker line. And the `ByAxes` displays the labels next to their respective axes, with the tracker line.
|
|
736
|
+
#### <span id="poincare-plot"><PoincarePlot/></span>
|
|
1008
737
|
|
|
1009
|
-
|
|
1010
|
-
Optional callback function that accepts the update from the tracker. Use this to display the tracker and data information outside of the <Chart/>. The `TrackerAxisUpdate` is a `Map<string, TrackerAxisInfo>`, the `TrackerAxisInfo` has the shape `{x: number, axisLocation: AxisLocation}`, and the `AxisLocation` is an same enumeration used for placing the axes on the chart (i.e. `AxisLocation.Bottom`, `AxisLocation.Top`).
|
|
738
|
+
A Poincaré (iterates) plot shows each value of a sequence against a later value of the same sequence: `(f[m](x), f[m+n](x))`. Patterns that are hard to see over time, such as cycles and attractors, show up as shapes. Random noise fills the plot with a shapeless cloud, but a series generated by a rule, even a chaotic one that *looks* random over time, traces out a distinct shape. That makes the plot handy for spotting auto-correlations, and for telling random apart from complicated-but-deterministic. Both axes are `<ContinuousAxis>` and don't scroll, so set `updateAxisBasedOnDomainValues={false}` on them. Uses an `IteratesDataSource`, usually fed by [`iteratesObservable`](#chart-data). ([full example](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/StreamingPoincareChart.tsx), [walkthrough](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/poincare-chart.md), with the tent, logistic, and Gauss maps)
|
|
1011
739
|
|
|
740
|
+
<picture>
|
|
741
|
+
<source media="(prefers-color-scheme: dark)" srcset="packages/examples/public/images/poincare-chart-dark.png">
|
|
742
|
+
<img src="packages/examples/public/images/poincare-chart-light.png" alt="the Poincaré chart example: the tent map at a lag of 1, with colorized lines">
|
|
743
|
+
</picture>
|
|
1012
744
|
|
|
1013
|
-
|
|
745
|
+
[▶ watch it stream](packages/examples/public/images/poincare-chart-streaming.gif) (animated)
|
|
1014
746
|
|
|
1015
|
-
|
|
1016
|
-
|
|
747
|
+
| prop | type | description |
|
|
748
|
+
|---|---|---|
|
|
749
|
+
| `interpolation` | `d3.CurveFactory` | How the line between successive points is drawn. Use `NoCurveFactory` to draw no line. |
|
|
750
|
+
| `showPoints` | `boolean` | Draws the points as well as the line (default `true`). |
|
|
751
|
+
| `colorizeLines` | `boolean` | Colors the line along its length, one step per point, so that its direction and age can be seen (default `false`). |
|
|
752
|
+
| `lineStartColor`, `lineEndColor` | `string` | The colors the colorized line sweeps between (defaults `DEFAULT_LINE_START_COLOR`, a blue, and `DEFAULT_LINE_END_COLOR`, a red). |
|
|
753
|
+
| `lineColorSteps` | `number` | The number of points it takes the color to sweep from start to end, and as many to sweep back (default `DEFAULT_LINE_COLOR_STEPS`, 1,000). |
|
|
754
|
+
| `zoomMinScaleFactor`, `zoomMaxScaleFactor` | `number` | Limit how far the user can zoom in and out. The plot starts at a scale factor of 1; smaller factors zoom in, and larger ones zoom out (2 shows twice the range). The defaults, 0 and 1, let the user zoom in as far as they like, but not out past the starting view. |
|
|
755
|
+
| `zoomState`, `onZoomStateChange` | `PoincarePlotZoomState`, `(state) => void` | Keep the zoom across remounts, as for the bar plot. |
|
|
1017
756
|
|
|
1018
|
-
|
|
1019
|
-
|
|
757
|
+
```tsx
|
|
758
|
+
<Chart chartId={4} width={width} height={height} dataSource={dataSource} seriesStyles={seriesStyles}
|
|
759
|
+
windowingTime={25} onUpdateChartTime={setChartTime}>
|
|
760
|
+
<ContinuousAxis axisId="x-axis-1" location={AxisLocation.Bottom} domain={[0, 1]} label="f[n](x)" updateAxisBasedOnDomainValues={false}/>
|
|
761
|
+
<ContinuousAxis axisId="y-axis-1" location={AxisLocation.Left} domain={[0, 1]} label="f[n+1](x)" updateAxisBasedOnDomainValues={false}/>
|
|
762
|
+
<Tracker visible={true} trackerAxis={AxisLocation.Bottom} labelFormatter={x => d3.format(",.3f")(x)}/>
|
|
763
|
+
<Tracker visible={true} trackerAxis={AxisLocation.Left} labelFormatter={y => d3.format(",.3f")(y)}/>
|
|
764
|
+
<Tooltip visible={true}>
|
|
765
|
+
<PoincarePlotTooltipContent xLabel="t (ms)" yLabel="f(x)" yValueFormatter={value => formatNumber(value, " ,.4f")}/>
|
|
766
|
+
</Tooltip>
|
|
767
|
+
<PoincarePlot interpolation={d3.curveLinear} colorizeLines={true} panEnabled={true} zoomEnabled={true}/>
|
|
768
|
+
</Chart>
|
|
769
|
+
```
|
|
1020
770
|
|
|
771
|
+
#### <span id="outlier-plot"><OutlierPlot/></span>
|
|
1021
772
|
|
|
1022
|
-
|
|
773
|
+
Values over time, like the scatter plot, with a translucent band for each confidence level (measure) around the line. Points that fall outside a band are marked, so outliers stand out. Both axes are `<ContinuousAxis>`. Uses an `OutlierDataSource`. ([full example](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/StreamingOutlierChart.tsx), [walkthrough](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/outlier-chart.md))
|
|
1023
774
|
|
|
1024
|
-
|
|
775
|
+
<picture>
|
|
776
|
+
<source media="(prefers-color-scheme: dark)" srcset="packages/examples/public/images/outlier-chart-dark.png">
|
|
777
|
+
<img src="packages/examples/public/images/outlier-chart-light.png" alt="the outlier chart example: a price index inside its 1σ, 2σ, and 3σ bands, with the points outside the bands marked">
|
|
778
|
+
</picture>
|
|
1025
779
|
|
|
1026
|
-
|
|
780
|
+
[▶ watch it stream](packages/examples/public/images/outlier-chart-streaming.gif) (animated)
|
|
1027
781
|
|
|
1028
|
-
|
|
1029
|
-
|
|
782
|
+
| prop | type | description |
|
|
783
|
+
|---|---|---|
|
|
784
|
+
| `interpolation` | `d3.CurveFactory` | How the central line is drawn between points (default `d3.curveLinear`). |
|
|
785
|
+
| `timeWindowBehavior` | `TimeWindowBehavior` | `SCROLL` (the default) or `SQUEEZE`, as for the scatter plot. |
|
|
786
|
+
| `bandOpacity` | `number` | The fill opacity of the outermost (widest) band (default 0.15). |
|
|
787
|
+
| `bandOpacityStep` | `number` | The opacity added for each tighter band (default 0.12). |
|
|
788
|
+
| `markerRadius` | `number` (px) | Draws a marker at each point while the data source is paused. |
|
|
789
|
+
| `outlierMarkerColors` | `Array<string>` | The color of an outlier's marker, by band: `outlierMarkerColors[i]` marks a point outside band `i`. When a point is outside several bands, the widest band's color wins (default red). |
|
|
1030
790
|
|
|
1031
|
-
|
|
1032
|
-
Specifies where to anchor the legend within the plot area. Ignored when `container` is provided. Available locations are `TOP_LEFT`, `TOP_RIGHT`, `BOTTOM_LEFT`, and `BOTTOM_RIGHT`.
|
|
791
|
+
The plot's type parameter is the measures tuple:
|
|
1033
792
|
|
|
1034
|
-
|
|
1035
|
-
|
|
793
|
+
```tsx
|
|
794
|
+
const MEASURES = [0.68, 0.95, 0.997] as const // 1σ, 2σ, 3σ
|
|
795
|
+
type Measures = typeof MEASURES
|
|
796
|
+
|
|
797
|
+
<Chart chartId={5} width={width} height={height} dataSource={dataSource} windowingTime={50} dataUpdatePeriod={25}>
|
|
798
|
+
<ContinuousAxis axisId="x-axis-1" location={AxisLocation.Bottom} domain={[0, 10_000]} label="time (ms)"/>
|
|
799
|
+
<ContinuousAxis axisId="y-axis-1" location={AxisLocation.Left} domain={[-50, 150]} label="spot price index (USD)" autoScale={true}/>
|
|
800
|
+
<OutlierPlot<Measures>
|
|
801
|
+
bandOpacity={0.12}
|
|
802
|
+
bandOpacityStep={0.14}
|
|
803
|
+
outlierMarkerColors={['#f4c542', '#f08a3b', '#d62728']}
|
|
804
|
+
panEnabled={true}
|
|
805
|
+
zoomEnabled={true}
|
|
806
|
+
/>
|
|
807
|
+
<Tooltip visible={true}>
|
|
808
|
+
<OutlierPlotTooltipContent
|
|
809
|
+
datumFormatter={(x, y) => `${y.toFixed(2)} USD @ ${x.toFixed(0)} ms`}
|
|
810
|
+
measureFormatter={(inside, outside) => `${(inside * 100).toFixed(1)}% inside, ${(outside * 100).toFixed(1)}% outside`}
|
|
811
|
+
/>
|
|
812
|
+
</Tooltip>
|
|
813
|
+
</Chart>
|
|
814
|
+
```
|
|
1036
815
|
|
|
1037
|
-
|
|
1038
|
-
When provided, the legend renders as an HTML element portal into this external container instead of inside the chart SVG. Position the container however you like — the legend fills it.
|
|
816
|
+
### <span id="tracker"><Tracker/></span> [↑](#content "back to contents")
|
|
1039
817
|
|
|
1040
|
-
|
|
818
|
+
The tracker follows the mouse over the plot with a line, and labels the axes' values at the mouse. By default it's a vertical line that tracks the x-axes. Use `trackerAxis={AxisLocation.Left}` (or `Right`) for a horizontal line that tracks the y-axes, and add two trackers to track both.
|
|
1041
819
|
|
|
1042
|
-
|
|
1043
|
-
|
|
820
|
+
| prop | type | description |
|
|
821
|
+
|---|---|---|
|
|
822
|
+
| `visible` | `boolean` | **Required.** Whether the tracker is shown. |
|
|
823
|
+
| `trackerAxis` | `AxisLocation` | The axes the tracker reports (default `AxisLocation.Bottom`). `Bottom` or `Top` gives a vertical line; `Left` or `Right` a horizontal one. |
|
|
824
|
+
| `labelLocation` | `TrackerLabelLocation` | `ByAxis` (the default) shows each value next to its axis. `Nowhere` hides the labels, for when you show the values yourself with `onTrackerUpdate`. |
|
|
825
|
+
| `labelFormatter` | `(value: number) => string` | Formats the labels (default: a whole number, with thousands separators). |
|
|
826
|
+
| `style` | `Partial<TrackerStyle>` | The line's style: `{visible, color, lineWidth}` (default color `'#d2933f'`, width 1). |
|
|
827
|
+
| `font` | `Partial<TrackerLabelFont>` | The labels' font: `{size, color, family, weight}`. |
|
|
828
|
+
| `onTrackerUpdate` | `(update: TrackerAxisUpdate) => void` | Called as the mouse moves, with a map from axis ID to `{x, axisLocation}`, where `x` is the axis' value at the mouse (for a y-axis, too). Use it to show the values outside the chart. |
|
|
1044
829
|
|
|
1045
|
-
|
|
1046
|
-
2. **fontFamily** (string): Font family for the legend labels.
|
|
1047
|
-
3. **fontColor** (string): Font color for the legend labels.
|
|
1048
|
-
4. **backgroundColor** (string): Background fill color for the legend box.
|
|
1049
|
-
5. **backgroundOpacity** (number): Background opacity for the legend box.
|
|
1050
|
-
6. **borderColor** (string): Border/stroke color for the legend box.
|
|
1051
|
-
7. **borderWidth** (number): Border width for the legend box.
|
|
1052
|
-
8. **borderOpacity** (number): Border opacity for the legend box.
|
|
1053
|
-
9. **borderRadius** (number): Corner radius of the legend box.
|
|
1054
|
-
10. **padding** (number): Padding inside the legend box (in pixels).
|
|
1055
|
-
11. **rowGap** (number): Vertical space between legend entries.
|
|
1056
|
-
12. **swatchWidth** (number): Width of the color swatch next to each label.
|
|
1057
|
-
13. **swatchHeight** (number): Height of the color swatch next to each label.
|
|
1058
|
-
14. **swatchLabelGap** (number): Gap between the swatch and the label text.
|
|
1059
|
-
15. **maxHeight** (number, optional): Maximum height of the legend before it starts scrolling.
|
|
1060
|
-
16. **transitionDuration** (number): Duration of the visibility transition in milliseconds.
|
|
830
|
+
### <span id="legend"><Legend/></span> [↑](#content "back to contents")
|
|
1061
831
|
|
|
1062
|
-
|
|
832
|
+
The legend shows each series' name and color. It reads the names from the chart's data and the colors from the chart's `seriesStyles`, so there's nothing to keep in sync. It lists only the series that pass the chart's `seriesFilter`, and scrolls when there are more series than fit. Hovering over a legend entry highlights its series (and fades the rest), and hovering over a series highlights its entry. It can sit over a corner of the plot, or render into a container of your own, outside the chart. ([walkthrough](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/legends.md))
|
|
1063
833
|
|
|
1064
|
-
**Basic internal legend**
|
|
1065
834
|
```tsx
|
|
835
|
+
// over the plot's top-right corner
|
|
836
|
+
<Legend visible={true} location={LegendLocation.TOP_RIGHT}/>
|
|
837
|
+
|
|
838
|
+
// in your own container, anywhere on the page
|
|
839
|
+
const legendContainer = useRef<HTMLDivElement>(null)
|
|
840
|
+
...
|
|
841
|
+
<div ref={legendContainer} style={{width: 200}}/>
|
|
1066
842
|
<Chart ...>
|
|
1067
843
|
...
|
|
1068
|
-
<Legend visible={true}
|
|
844
|
+
<Legend visible={true} container={legendContainer}/>
|
|
1069
845
|
</Chart>
|
|
1070
846
|
```
|
|
1071
847
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
...
|
|
1080
|
-
<Legend visible={true} container={legendContainer} />
|
|
1081
|
-
</Chart>
|
|
1082
|
-
</>
|
|
1083
|
-
```
|
|
848
|
+
| prop | type | description |
|
|
849
|
+
|---|---|---|
|
|
850
|
+
| `visible` | `boolean` | **Required.** Whether the legend is shown. |
|
|
851
|
+
| `location` | `LegendLocation` | The corner of the plot the legend sits in: `TOP_LEFT`, `TOP_RIGHT` (the default), `BOTTOM_LEFT`, or `BOTTOM_RIGHT`. Ignored when `container` is set. |
|
|
852
|
+
| `offset` | `{x, y}` | The offset (px) from that corner (default `{x: 10, y: 10}`). Ignored when `container` is set. |
|
|
853
|
+
| `container` | `RefObject<HTMLElement \| null>` | Renders the legend into this element (through a React portal), instead of over the plot. The legend fills the container. |
|
|
854
|
+
| `style` | `Partial<LegendStyle>` | `fontSize`, `fontFamily`, `fontColor`, `backgroundColor`, `backgroundOpacity`, `borderColor`, `borderWidth`, `borderOpacity`, `borderRadius`, `padding`, `rowGap`, `swatchWidth`, `swatchHeight`, `swatchLabelGap`, `maxHeight` (the legend scrolls beyond it), and `transitionDuration` (ms). Defaults are in `defaultLegendStyle`. |
|
|
1084
855
|
|
|
1085
|
-
###
|
|
856
|
+
### <span id="tooltip"><Tooltip/> and tooltip content</span> [↑](#content "back to contents")
|
|
1086
857
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
The properties for the lt;Tooltip/> are simple and limited. The tooltip content components have more options.
|
|
1090
|
-
|
|
1091
|
-
**visible (boolean)**<br>
|
|
1092
|
-
When set to `true` the the tooltip is shown when the mouse hovers over a data series or point.
|
|
1093
|
-
|
|
1094
|
-
**style ([TooltipStyle](https://github.com/robphilipp/stream-charts/blob/master/src/app/charts/tooltipUtils.ts), optional, default = [defaultTooltipStyle](https://github.com/robphilipp/stream-charts/blob/master/src/app/charts/tooltipUtils.ts) )**<br>
|
|
1095
|
-
Optional property defining the style of the tooltip. The following style elements are available
|
|
1096
|
-
1. **visible**<br>Visibility of the tooltip when the mouse hovers over a data series or point
|
|
1097
|
-
---
|
|
1098
|
-
2. **fontSize**<br>The size of the font displayed in the tooltip
|
|
1099
|
-
3. **fontColor**<br>The color of the text displayed in the tooltip
|
|
1100
|
-
4. **fontFamily**<br>The font weight for the text displayed in the tooltip
|
|
1101
|
-
5. **fontWeight**<br>The font weight for the text displayed in the tooltip
|
|
1102
|
-
---
|
|
1103
|
-
6. **backgroundColor**<br>The background color of the tooltip
|
|
1104
|
-
7. **backgroundOpacity**<br>The opacity of the background (i.e. how transparent it is)
|
|
1105
|
-
---
|
|
1106
|
-
8. **borderColor**<br>The color of the border surrounding the tooltip content
|
|
1107
|
-
9. **borderWidth**<br>The width of the border surrounding the tooltip content
|
|
1108
|
-
10. **borderRadius**<br>The radius of the border surrounding the tooltip content
|
|
1109
|
-
---
|
|
1110
|
-
11. **paddingLeft**<br>The padding to the left of the tooltip content
|
|
1111
|
-
12. **paddingRight**<br>The padding to the right of the tooltip content
|
|
1112
|
-
13. **paddingTop**<br>The padding to the top of the tooltip content
|
|
1113
|
-
14. **paddingBottom**<br>The padding to the bottom of the tooltip content
|
|
858
|
+
The tooltip shows information about the *data* under the mouse: a line, a spike, a point, a bar, or a band. (The [tracker](#tracker), by contrast, shows the mouse's *position*.) The `<Tooltip>` provides the box and its style. Its child, the *tooltip content*, decides what goes in the box. Each plot has its own content component, most of which take formatters for their values, and you can [write your own](#custom-tooltip). ([walkthrough](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/tooltips-and-trackers.md))
|
|
1114
859
|
|
|
1115
|
-
|
|
1116
|
-
|
|
860
|
+
```tsx
|
|
861
|
+
<Tooltip visible={true} style={{fontColor: theme.color, backgroundColor: theme.backgroundColor, borderColor: theme.color, backgroundOpacity: 0.9}}>
|
|
862
|
+
<ScatterPlotTooltipContent xLabel="t (ms)" yLabel="count"/>
|
|
863
|
+
</Tooltip>
|
|
864
|
+
```
|
|
1117
865
|
|
|
866
|
+
| `<Tooltip>` prop | type | description |
|
|
867
|
+
|---|---|---|
|
|
868
|
+
| `visible` | `boolean` | **Required.** Whether the tooltip is shown when the mouse is over one of the plot's elements. |
|
|
869
|
+
| `style` | `Partial<TooltipStyle>` | The box's style: `fontSize`, `fontColor`, `fontFamily`, `fontWeight`, `backgroundColor`, `backgroundOpacity`, `borderColor`, `borderOpacity`, `borderWidth`, `borderRadius`, and `paddingLeft`/`Right`/`Top`/`Bottom`. Defaults are in `defaultTooltipStyle`. |
|
|
870
|
+
| `children` | | **Required.** The tooltip content. |
|
|
1118
871
|
|
|
1119
|
-
|
|
872
|
+
Each content component also takes a `style` that overrides parts of the `<Tooltip>`'s style.
|
|
1120
873
|
|
|
1121
|
-
|
|
874
|
+
**<ScatterPlotTooltipContent/>** shows a table of the points around the mouse – the point before and the point after (or the point under the mouse, when it's on one), and the change between them – followed by statistics of the series (its number of points, mean value, standard deviation, and mean interval between points) and a sparkline of its autocorrelation.
|
|
1122
875
|
|
|
1123
|
-
|
|
1124
|
-
|
|
876
|
+
| prop | description |
|
|
877
|
+
|---|---|
|
|
878
|
+
| `xLabel`, `yLabel` | **Required.** The row headers for the x-values and y-values. |
|
|
879
|
+
| `beforeHeader`, `pointHeader`, `afterHeader`, `deltaHeader` | The column headers (defaults: before, point, after, ∆). |
|
|
880
|
+
| `xValueFormatter`, `yValueFormatter` | Format the values (defaults `formatTime` and `formatValue`). |
|
|
881
|
+
| `xChangeFormatter`, `yChangeFormatter` | Format the change, `(value1, value2) => string` (defaults `formatTimeChange` and `formatValueChange`). |
|
|
882
|
+
| `intervalFormatter` | Formats the statistics' mean interval between points, a duration (defaults to the `xValueFormatter`). Give it its own formatter when the x-values aren't formatted as durations, for example as the time of day. |
|
|
1125
883
|
|
|
1126
|
-
|
|
1127
|
-
|---|-------------|-------------|-------------|
|
|
1128
|
-
| xLabel | xValueFormatter | xValueFormatter | xChangeFormatter |
|
|
1129
|
-
| yLabel | yValueFormatter | yValueFormatter | yChangeFormatter |
|
|
884
|
+
**<RasterPlotTooltipContent/>** shows the spike under the mouse (its series, time, and value) along with statistics of the series' spikes: their count, the mean and standard deviation of their values, and the mean time between them, with a sparkline of their values.
|
|
1130
885
|
|
|
1131
|
-
|
|
886
|
+
| prop | description |
|
|
887
|
+
|---|---|
|
|
888
|
+
| `xFormatter` | Formats the spike's time. |
|
|
889
|
+
| `yFormatter` | Formats the values (the spike's value, and the mean and standard deviation of the values). |
|
|
890
|
+
| `intervalFormatter` | Formats the mean time between spikes, a duration (defaults to the `xFormatter`). Give it its own formatter when the times aren't formatted as durations, for example as the time of day. |
|
|
1132
891
|
|
|
1133
|
-
|
|
1134
|
-
Required property that gives a name to the x-values. This is the label for the x-values in the tooltip.
|
|
892
|
+
**<BarPlotTooltipContent/>** shows the category under the mouse, with its value and statistics, and a sparkline of its values over the data the chart holds.
|
|
1135
893
|
|
|
1136
|
-
|
|
1137
|
-
|
|
894
|
+
| prop | description |
|
|
895
|
+
|---|---|
|
|
896
|
+
| `ordinalUnits` | The units shown after the values, for example `"mV"`. |
|
|
1138
897
|
|
|
1139
|
-
|
|
1140
|
-
Optional property that gives a name to the data point that is immediately before the mouse location.
|
|
898
|
+
**<PoincarePlotTooltipContent/>** shows the point under the mouse, with the iterates before and after it, and a sparkline of the series' values around it.
|
|
1141
899
|
|
|
1142
|
-
|
|
1143
|
-
|
|
900
|
+
| prop | description |
|
|
901
|
+
|---|---|
|
|
902
|
+
| `xLabel`, `yLabel` | **Required.** The row headers. |
|
|
903
|
+
| `nMinusLagHeader`, `nHeader`, `nPlusLagHeader` | The column headers for the previous, current, and next iterates. |
|
|
904
|
+
| `xValueFormatter`, `yValueFormatter`, `xChangeFormatter`, `yChangeFormatter` | Format the values and their changes. |
|
|
1144
905
|
|
|
1145
|
-
|
|
1146
|
-
Optional property that gives a name to the difference between the datum immediately before and after the mouse location.
|
|
906
|
+
**<OutlierPlotTooltipContent/>** shows the point and band under the mouse.
|
|
1147
907
|
|
|
1148
|
-
|
|
1149
|
-
|
|
908
|
+
| prop | description |
|
|
909
|
+
|---|---|
|
|
910
|
+
| `datumFormatter` | `(x, y) => string \| JSX.Element`: describes the point. |
|
|
911
|
+
| `bandFormatter` | `(lower, upper) => string \| JSX.Element`: describes the band. It's handed the band's *measures* (for example 0.68 and 0.95), not its bounds. |
|
|
912
|
+
| `measureFormatter` | `(innerProb, outerProb) => string \| JSX.Element \| Array<string>`: describes the probabilities of a point being inside and outside the band. |
|
|
913
|
+
| `children` | Optional custom content. Inside it, `useOutlierTooltip()` hands you the hovered point and band (`tooltipContent`), the style, and the formatters. Defaults to `<DefaultOutlierTooltipContent/>`. |
|
|
1150
914
|
|
|
1151
|
-
|
|
1152
|
-
Optional function that formats the x-value immediately before and after the mouse location. The default function formats the y-values as floating point with 3 values to the right of the decimal point.
|
|
915
|
+
#### <span id="custom-tooltip">writing your own tooltip content</span>
|
|
1153
916
|
|
|
1154
|
-
|
|
1155
|
-
Optional function that formats the change in the x-value of the points immediately before and after the mouse location. The default function formats the value as a natural number.
|
|
917
|
+
All of the content components above are built on `<TooltipContent>`, and so can yours. It does the plumbing (it registers with the chart, hides when the mouse leaves, and places the box next to the mouse, flipping it to stay on the chart). You supply two functions:
|
|
1156
918
|
|
|
1157
|
-
|
|
1158
|
-
|
|
919
|
+
- **`capture(seriesName, time, tooltipData, providerId?)`** turns what the plot knows about the element under the mouse into your own content (any value you like), or `undefined` to show nothing. It's called on every mouse move, so keep it quick, and copy what you need out of the series, which keep changing.
|
|
920
|
+
- **`children(content, style)`** renders that content.
|
|
1159
921
|
|
|
1160
|
-
|
|
1161
|
-
|
|
922
|
+
```tsx
|
|
923
|
+
import {type ReactElement} from "react"
|
|
924
|
+
import {TooltipContent, type Datum, type ScatterTooltipMetadata} from "stream-charts"
|
|
925
|
+
|
|
926
|
+
function MyScatterTooltip(): ReactElement {
|
|
927
|
+
return (
|
|
928
|
+
<TooltipContent<Datum, ScatterTooltipMetadata, {seriesName: string, count: number}>
|
|
929
|
+
capture={(seriesName, _time, {series}) => ({seriesName, count: series.length})}
|
|
930
|
+
>
|
|
931
|
+
{({seriesName, count}) => <div>{seriesName}: {count} points</div>}
|
|
932
|
+
</TooltipContent>
|
|
933
|
+
)
|
|
934
|
+
}
|
|
1162
935
|
|
|
936
|
+
<Tooltip visible={true}>
|
|
937
|
+
<MyScatterTooltip/>
|
|
938
|
+
</Tooltip>
|
|
939
|
+
```
|
|
1163
940
|
|
|
1164
|
-
|
|
1165
|
-
The <RasterPlotTooltipContent/> shows the series name, the time, and value of the point over which the mouse is hovering. There are only three properties.
|
|
941
|
+
What `tooltipData` holds (`{series, metadata}`) depends on the plot:
|
|
1166
942
|
|
|
1167
|
-
|
|
1168
|
-
|
|
943
|
+
| plot | `series` | `metadata` |
|
|
944
|
+
|---|---|---|
|
|
945
|
+
| scatter | the series' data (`Datum`s) | `ScatterTooltipMetadata`: `points`, either `{kind: 'point', before, point, after}` or `{kind: 'segment', before, after}` |
|
|
946
|
+
| raster | the spike under the mouse (`series[0]`) | `RasterTooltipMetadata`: `spikes`, all of the series' spikes |
|
|
947
|
+
| bar | the category's data | `WindowedOrdinalStats`, the lifetime and windowed statistics. `providerId` says which element is under the mouse (see `BAR_CHART_TOOLTIP_PROVIDER_IDS`). |
|
|
948
|
+
| outlier | the series' data | `OutlierBandTooltipMetadata`: the `datum`, the band's `upperMeasure` and `lowerMeasure`, its `bandIndex`, and `pointsInBand` |
|
|
1169
949
|
|
|
1170
|
-
|
|
1171
|
-
Optional function that formats the x-value immediately before and after the mouse location. The default function formats the y-values as floating point with 3 values to the right of the decimal point.
|
|
950
|
+
Other props: `style` overrides the tooltip's style, `boxStyle` adds CSS to the box, and `topFor(content, height)` places the box's top yourself. The tutorial [writing your own tooltips](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/writing-tooltips.md) builds a custom tooltip for each plot.
|
|
1172
951
|
|
|
1173
|
-
|
|
1174
|
-
Optional styles for the tooltip content. The styles are the same as those for the <Tooltip/>.
|
|
952
|
+
---
|
|
1175
953
|
|
|
1176
|
-
|
|
954
|
+
## <span id="more">more</span> [↑](#content "back to contents")
|
|
1177
955
|
|
|
1178
|
-
|
|
956
|
+
### <span id="theming">theming</span> [↑](#content "back to contents")
|
|
1179
957
|
|
|
1180
|
-
|
|
1181
|
-
Optional units to display alongside the values.
|
|
958
|
+
The library has no theme object. It has colors: every component takes its colors as props, so to theme a chart, you hand it a different set of colors. When the colors change, the chart redraws in them, even while it's paused.
|
|
1182
959
|
|
|
1183
|
-
|
|
1184
|
-
|
|
960
|
+
| component | its colors come from |
|
|
961
|
+
|---|---|
|
|
962
|
+
| `<Chart>` | `color` (the axes, their ticks, and labels) and `backgroundColor` |
|
|
963
|
+
| the series | the chart's `seriesStyles`, one style per series, with its `color` and `highlightColor` |
|
|
964
|
+
| `<ContinuousAxis>`, `<OrdinalAxis>` | the chart's `color`, unless you give the axis its own `font` |
|
|
965
|
+
| `<Tooltip>` | its `style`: `fontColor`, `backgroundColor`, `borderColor`, and their opacities. The tooltip content (tables, sparklines) draws in the `fontColor`. |
|
|
966
|
+
| `<Tracker>` | its `style` (the line) and `font` (the labels) |
|
|
967
|
+
| `<Legend>` | its `style`: `fontColor`, `backgroundColor`, and `borderColor` |
|
|
968
|
+
| `<BarPlot>` | its series styles (start from `defaultBarSeriesStyle(color)`) |
|
|
969
|
+
| `<OutlierPlot>` | the series' color (the line and its bands), and `outlierMarkerColors` |
|
|
970
|
+
| `<PoincarePlot>` | the series' colors, and `lineStartColor`/`lineEndColor` when colorizing |
|
|
1185
971
|
|
|
1186
|
-
|
|
972
|
+
A component you don't hand colors to uses the library's defaults, which are tuned for a dark background: orange (`#d2933f`) axes on near-black (`#202020`), with teal (`#008aad`) series. On a light page, pass colors to every component. The simplest way is to collect them in one theme object and spread it into each component's props. Themes change only colors; fonts, line widths, and margins each have their own props.
|
|
1187
973
|
|
|
1188
|
-
The
|
|
974
|
+
The demo app's [theming page](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/theming.md) shows a light/dark theme held in a store, and how to add a third theme.
|
|
1189
975
|
|
|
1190
|
-
|
|
1191
|
-
Required label for the x-axis values.
|
|
976
|
+
### <span id="filtering">filtering series</span> [↑](#content "back to contents")
|
|
1192
977
|
|
|
1193
|
-
|
|
1194
|
-
Required label for the y-axis values.
|
|
978
|
+
The chart's `seriesFilter` is a `RegExp` that's tested against each series' name on every redraw. For a filter typed in by the user, build the regex with one of these helpers. Each returns a `Result` rather than throwing, so a half-typed pattern is easy to handle:
|
|
1195
979
|
|
|
1196
|
-
|
|
1197
|
-
|
|
980
|
+
- `regexFilter(text)` compiles `text` as a regex. It fails when the pattern is invalid, *or* when it risks catastrophic backtracking (which would freeze the render loop).
|
|
981
|
+
- `literalFilter(text)` matches `text` literally, escaping regex characters such as `.` and `*`.
|
|
982
|
+
- `isSafeRegex(pattern)` checks a pattern on its own.
|
|
1198
983
|
|
|
1199
|
-
|
|
1200
|
-
|
|
984
|
+
```ts
|
|
985
|
+
const filter = regexFilter(filterText).getOrElse(/./)
|
|
986
|
+
```
|
|
1201
987
|
|
|
1202
|
-
|
|
1203
|
-
Optional styles for the tooltip content.
|
|
988
|
+
### <span id="hooks">hooks</span> [↑](#content "back to contents")
|
|
1204
989
|
|
|
1205
|
-
|
|
990
|
+
For advanced use, such as building your own components inside a `<Chart>`:
|
|
1206
991
|
|
|
1207
|
-
|
|
992
|
+
| hook | description |
|
|
993
|
+
|---|---|
|
|
994
|
+
| `useDataSourceRunning(dataSource)` | Whether the data source is running, re-rendering when it starts or stops. Works anywhere, inside a chart or not. Handy for a Run/Pause button. |
|
|
995
|
+
| `useDataSource()` | The chart's data source (inside a `<Chart>`). |
|
|
996
|
+
| `useChart()` | The chart's shared state: its ID, canvas and drawing context, colors, series styles, series filter, axes, mouse, and tooltip. Custom components draw onto the canvas by registering a draw function with `useChart().canvasContext`. |
|
|
997
|
+
| `usePlotDimensions()` | The plot area's dimensions and the margins. |
|
|
998
|
+
| `useHoveredSeries()` | The name of the series under the mouse (in the plot or the legend), and a setter. |
|
|
999
|
+
| `useTooltipStyle()` | The enclosing tooltip's style (inside a tooltip's content). |
|
|
1000
|
+
| `useOutlierTooltip()` | The hovered point and band, the style, and the formatters (inside an `<OutlierPlotTooltipContent>`). |
|
|
1208
1001
|
|
|
1209
|
-
|
|
1210
|
-
- **useAxes()**: Access the state of the x and y axes.
|
|
1211
|
-
- **usePlotDimensions()**: Access the dimensions of the plot area and margins.
|
|
1212
|
-
- **useDataObservable()**: Access the data stream observable.
|
|
1002
|
+
The full API, including the formatting helpers (`formatNumber`, `formatTime`, `formatValue`, and friends), is in the [API docs](https://robphilipp.github.io/stream-charts/docs/modules.html).
|
|
1213
1003
|
|
|
1214
|
-
|
|
1004
|
+
### <span id="further-reading">further reading</span> [↑](#content "back to contents")
|
|
1215
1005
|
|
|
1216
|
-
|
|
1217
|
-
```shell
|
|
1218
|
-
npm run test
|
|
1219
|
-
```
|
|
1006
|
+
The [demo app](#run-demo) in `packages/examples` has one tab for each plot type, and a set of guide pages (shown in the app's intro, and readable on GitHub):
|
|
1220
1007
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1008
|
+
- **Getting oriented:** [intro](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/intro.md), [what the pages cover](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/contents.md), [anatomy of an example](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/anatomy-of-example.md), and [the common controls](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/common-controls.md), which says which library option each control is wired to.
|
|
1009
|
+
- **The plots:** [scatter](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/scatter-chart.md), [raster](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/raster-chart.md), [Poincaré](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/poincare-chart.md), [bar](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/bar-chart.md), and [outlier](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/outlier-chart.md).
|
|
1010
|
+
- **The extras:** [axes](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/axes.md) (the kinds of axes, their options, and which plots take which), [legends](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/legends.md), [theming](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/theming.md), and [tooltips and trackers](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/tooltips-and-trackers.md).
|
|
1011
|
+
- **Under the hood:** [some context](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/some-context.md) (which supporting libraries you need, sizing charts, and keeping state), [where the code lives](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/where-the-code-lives.md), and [how the data flows](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/how-the-data-flows.md).
|
|
1012
|
+
- **Tutorials:** [adding your own data source](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/adding-a-data-source.md), from an RxJS observable to a chart; [writing your own tooltips](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/writing-tooltips.md), one for each plot; and [your own scatter chart, from sensor data](https://github.com/robphilipp/stream-charts/blob/master/packages/examples/src/app/examples/docs/scatter-chart-from-sensor-data.md), with irregular readings, two y-axes, and a legend, tracker, and tooltip.
|