stream-charts 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +756 -343
- package/changes.md +7 -0
- package/dist/src/app/charts/Chart.d.ts +116 -87
- package/dist/src/app/charts/Chart.js +96 -101
- package/dist/src/app/charts/Chart.js.map +1 -1
- package/dist/src/app/charts/axes/AxesState.d.ts +64 -0
- package/dist/src/app/charts/axes/AxesState.js +97 -0
- package/dist/src/app/charts/axes/AxesState.js.map +1 -0
- package/dist/src/app/charts/axes/AxesState.test.js +30 -0
- package/dist/src/app/charts/axes/AxesState.test.js.map +1 -0
- package/dist/src/app/charts/axes/AxisInterval.d.ts +92 -0
- package/dist/src/app/charts/axes/AxisInterval.js +126 -0
- package/dist/src/app/charts/axes/AxisInterval.js.map +1 -0
- package/dist/src/app/charts/axes/AxisInterval.test.js +120 -0
- package/dist/src/app/charts/axes/AxisInterval.test.js.map +1 -0
- package/dist/src/app/charts/axes/BaseAxisRange.d.ts +83 -0
- package/dist/src/app/charts/axes/BaseAxisRange.js +58 -0
- package/dist/src/app/charts/axes/BaseAxisRange.js.map +1 -0
- package/dist/src/app/charts/axes/ContinuousAxis.d.ts +22 -0
- package/dist/src/app/charts/axes/ContinuousAxis.js +96 -0
- package/dist/src/app/charts/axes/ContinuousAxis.js.map +1 -0
- package/dist/src/app/charts/axes/ContinuousAxisRange.d.ts +59 -0
- package/dist/src/app/charts/axes/ContinuousAxisRange.js +84 -0
- package/dist/src/app/charts/axes/ContinuousAxisRange.js.map +1 -0
- package/dist/src/app/charts/axes/ContinuousAxisRange.test.js +47 -0
- package/dist/src/app/charts/axes/ContinuousAxisRange.test.js.map +1 -0
- package/dist/src/app/charts/axes/OrdinalAxis.d.ts +23 -0
- package/dist/src/app/charts/axes/OrdinalAxis.js +124 -0
- package/dist/src/app/charts/axes/OrdinalAxis.js.map +1 -0
- package/dist/src/app/charts/axes/OrdinalAxisRange.d.ts +69 -0
- package/dist/src/app/charts/axes/OrdinalAxisRange.js +100 -0
- package/dist/src/app/charts/axes/OrdinalAxisRange.js.map +1 -0
- package/dist/src/app/charts/axes/OrdinalAxisRange.test.js +48 -0
- package/dist/src/app/charts/axes/OrdinalAxisRange.test.js.map +1 -0
- package/dist/src/app/charts/axes/axes.d.ts +391 -0
- package/dist/src/app/charts/axes/axes.js +1046 -0
- package/dist/src/app/charts/axes/axes.js.map +1 -0
- package/dist/src/app/charts/d3types.d.ts +10 -10
- package/dist/src/app/charts/filters/regexFilter.d.ts +9 -0
- package/dist/src/app/charts/filters/regexFilter.js +18 -0
- package/dist/src/app/charts/filters/regexFilter.js.map +1 -0
- package/dist/src/app/charts/filters/regexFilter.test.d.ts +1 -0
- package/dist/src/app/charts/filters/regexFilter.test.js +11 -0
- package/dist/src/app/charts/filters/regexFilter.test.js.map +1 -0
- package/dist/src/app/charts/hooks/useAxes.d.ts +144 -0
- package/dist/src/app/charts/hooks/useAxes.js +187 -0
- package/dist/src/app/charts/hooks/useAxes.js.map +1 -0
- package/dist/src/app/charts/hooks/useChart.d.ts +30 -201
- package/dist/src/app/charts/hooks/useChart.js +33 -134
- package/dist/src/app/charts/hooks/useChart.js.map +1 -1
- package/dist/src/app/charts/hooks/useDataObservable.d.ts +74 -0
- package/dist/src/app/charts/hooks/useDataObservable.js +76 -0
- package/dist/src/app/charts/hooks/useDataObservable.js.map +1 -0
- package/dist/src/app/charts/hooks/useInitialData.d.ts +38 -0
- package/dist/src/app/charts/hooks/useInitialData.js +29 -0
- package/dist/src/app/charts/hooks/useInitialData.js.map +1 -0
- package/dist/src/app/charts/hooks/useMouse.d.ts +53 -0
- package/dist/src/app/charts/hooks/useMouse.js +65 -0
- package/dist/src/app/charts/hooks/useMouse.js.map +1 -0
- package/dist/src/app/charts/hooks/usePlotDimensions.d.ts +34 -0
- package/dist/src/app/charts/hooks/usePlotDimensions.js +73 -0
- package/dist/src/app/charts/hooks/usePlotDimensions.js.map +1 -0
- package/dist/src/app/charts/hooks/useTooltip.d.ts +64 -0
- package/dist/src/app/charts/hooks/useTooltip.js +52 -0
- package/dist/src/app/charts/hooks/useTooltip.js.map +1 -0
- package/dist/src/app/charts/observables/ChartData.d.ts +23 -0
- package/dist/src/app/charts/observables/ChartData.js +20 -0
- package/dist/src/app/charts/observables/ChartData.js.map +1 -0
- package/dist/src/app/charts/observables/iterates.d.ts +52 -0
- package/dist/src/app/charts/observables/iterates.js +121 -0
- package/dist/src/app/charts/observables/iterates.js.map +1 -0
- package/dist/src/app/charts/observables/iterates.test.d.ts +1 -0
- package/dist/src/app/charts/observables/iterates.test.js +180 -0
- package/dist/src/app/charts/observables/iterates.test.js.map +1 -0
- package/dist/src/app/charts/observables/ordinals.d.ts +75 -0
- package/dist/src/app/charts/observables/ordinals.js +141 -0
- package/dist/src/app/charts/observables/ordinals.js.map +1 -0
- package/dist/src/app/charts/observables/ordinals.test.d.ts +1 -0
- package/dist/src/app/charts/observables/ordinals.test.js +102 -0
- package/dist/src/app/charts/observables/ordinals.test.js.map +1 -0
- package/dist/src/app/charts/plots/BarPlot.d.ts +81 -0
- package/dist/src/app/charts/plots/BarPlot.js +557 -0
- package/dist/src/app/charts/plots/BarPlot.js.map +1 -0
- package/dist/src/app/charts/plots/PoincarePlot.d.ts +78 -0
- package/dist/src/app/charts/plots/PoincarePlot.js +504 -0
- package/dist/src/app/charts/plots/PoincarePlot.js.map +1 -0
- package/dist/src/app/charts/{RasterPlot.d.ts → plots/RasterPlot.d.ts} +1 -1
- package/dist/src/app/charts/{RasterPlot.js → plots/RasterPlot.js} +152 -133
- package/dist/src/app/charts/plots/RasterPlot.js.map +1 -0
- package/dist/src/app/charts/{ScatterPlot.d.ts → plots/ScatterPlot.d.ts} +10 -8
- package/dist/src/app/charts/plots/ScatterPlot.js +379 -0
- package/dist/src/app/charts/plots/ScatterPlot.js.map +1 -0
- package/dist/src/app/charts/{plot.d.ts → plots/plot.d.ts} +12 -14
- package/dist/src/app/charts/{plot.js → plots/plot.js} +19 -17
- package/dist/src/app/charts/plots/plot.js.map +1 -0
- package/dist/src/app/charts/series/baseSeries.d.ts +38 -0
- package/dist/src/app/charts/series/baseSeries.js +36 -0
- package/dist/src/app/charts/series/baseSeries.js.map +1 -0
- package/dist/src/app/charts/series/datumSeries.test.d.ts +1 -0
- package/dist/src/app/charts/series/datumSeries.test.js +50 -0
- package/dist/src/app/charts/series/datumSeries.test.js.map +1 -0
- package/dist/src/app/charts/series/iterateSeries.d.ts +42 -0
- package/dist/src/app/charts/series/iterateSeries.js +34 -0
- package/dist/src/app/charts/series/iterateSeries.js.map +1 -0
- package/dist/src/app/charts/series/ordinalSeries.d.ts +54 -0
- package/dist/src/app/charts/series/ordinalSeries.js +98 -0
- package/dist/src/app/charts/series/ordinalSeries.js.map +1 -0
- package/dist/src/app/charts/series/timeSeries.d.ts +30 -0
- package/dist/src/app/charts/series/timeSeries.js +19 -0
- package/dist/src/app/charts/series/timeSeries.js.map +1 -0
- package/dist/src/app/charts/{chartData.d.ts → series/timeSeriesChartData.d.ts} +11 -6
- package/dist/src/app/charts/series/timeSeriesChartData.js +32 -0
- package/dist/src/app/charts/series/timeSeriesChartData.js.map +1 -0
- package/dist/src/app/charts/styling/barPlotStyle.d.ts +41 -0
- package/dist/src/app/charts/styling/barPlotStyle.js +84 -0
- package/dist/src/app/charts/styling/barPlotStyle.js.map +1 -0
- package/dist/src/app/charts/{margins.d.ts → styling/margins.d.ts} +7 -5
- package/dist/src/app/charts/{margins.js → styling/margins.js} +19 -9
- package/dist/src/app/charts/styling/margins.js.map +1 -0
- package/dist/src/app/charts/styling/margins.test.d.ts +1 -0
- package/dist/src/app/charts/styling/margins.test.js +19 -0
- package/dist/src/app/charts/styling/margins.test.js.map +1 -0
- package/dist/src/app/charts/styling/svgStyle.d.ts +76 -0
- package/dist/src/app/charts/styling/svgStyle.js +84 -0
- package/dist/src/app/charts/styling/svgStyle.js.map +1 -0
- package/dist/src/app/charts/subscriptions/subscription.test.d.ts +1 -0
- package/dist/src/app/charts/subscriptions/subscription.test.js +395 -0
- package/dist/src/app/charts/subscriptions/subscription.test.js.map +1 -0
- package/dist/src/app/charts/subscriptions/subscriptions.d.ts +100 -0
- package/dist/src/app/charts/subscriptions/subscriptions.js +449 -0
- package/dist/src/app/charts/subscriptions/subscriptions.js.map +1 -0
- package/dist/src/app/charts/tooltips/BarPlotTooltipContent.d.ts +90 -0
- package/dist/src/app/charts/tooltips/BarPlotTooltipContent.js +302 -0
- package/dist/src/app/charts/tooltips/BarPlotTooltipContent.js.map +1 -0
- package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.d.ts +100 -0
- package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.js +190 -0
- package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.js.map +1 -0
- package/dist/src/app/charts/{RasterPlotTooltipContent.js → tooltips/RasterPlotTooltipContent.js} +40 -44
- package/dist/src/app/charts/tooltips/RasterPlotTooltipContent.js.map +1 -0
- package/dist/src/app/charts/tooltips/ScatterPlotTooltipContent.js +154 -0
- package/dist/src/app/charts/tooltips/ScatterPlotTooltipContent.js.map +1 -0
- package/dist/src/app/charts/{Tooltip.d.ts → tooltips/Tooltip.d.ts} +4 -2
- package/dist/src/app/charts/{Tooltip.js → tooltips/Tooltip.js} +23 -28
- package/dist/src/app/charts/tooltips/Tooltip.js.map +1 -0
- package/dist/src/app/charts/{tooltipUtils.d.ts → tooltips/tooltipUtils.d.ts} +11 -6
- package/dist/src/app/charts/{tooltipUtils.js → tooltips/tooltipUtils.js} +33 -17
- package/dist/src/app/charts/tooltips/tooltipUtils.js.map +1 -0
- package/dist/src/app/charts/{Tracker.d.ts → trackers/Tracker.d.ts} +6 -5
- package/dist/src/app/charts/trackers/Tracker.js +60 -0
- package/dist/src/app/charts/trackers/Tracker.js.map +1 -0
- package/dist/src/app/charts/{trackerUtils.d.ts → trackers/trackerUtils.d.ts} +13 -8
- package/dist/src/app/charts/trackers/trackerUtils.js +359 -0
- package/dist/src/app/charts/trackers/trackerUtils.js.map +1 -0
- package/dist/src/app/charts/utils.d.ts +22 -32
- package/dist/src/app/charts/utils.js +36 -37
- package/dist/src/app/charts/utils.js.map +1 -1
- package/dist/src/index.d.ts +48 -15
- package/dist/src/index.js +71 -15
- package/dist/src/index.js.map +1 -1
- package/package.json +30 -20
- package/src/app/charts/Chart.tsx +334 -0
- package/src/app/charts/axes/AxesState.test.ts +34 -0
- package/src/app/charts/axes/AxesState.ts +112 -0
- package/src/app/charts/axes/AxisInterval.test.ts +143 -0
- package/src/app/charts/axes/AxisInterval.ts +139 -0
- package/src/app/charts/axes/BaseAxisRange.ts +113 -0
- package/src/app/charts/axes/ContinuousAxis.tsx +192 -0
- package/src/app/charts/axes/ContinuousAxisRange.test.ts +55 -0
- package/src/app/charts/axes/ContinuousAxisRange.ts +96 -0
- package/src/app/charts/axes/OrdinalAxis.tsx +223 -0
- package/src/app/charts/axes/OrdinalAxisRange.test.ts +55 -0
- package/src/app/charts/axes/OrdinalAxisRange.ts +112 -0
- package/src/app/charts/axes/axes.ts +1569 -0
- package/src/app/charts/d3types.ts +16 -0
- package/src/app/charts/filters/regexFilter.test.ts +13 -0
- package/src/app/charts/filters/regexFilter.ts +17 -0
- package/src/app/charts/hooks/useAxes.tsx +341 -0
- package/src/app/charts/hooks/useChart.tsx +159 -0
- package/src/app/charts/hooks/useDataObservable.tsx +175 -0
- package/src/app/charts/hooks/useInitialData.tsx +65 -0
- package/src/app/charts/hooks/useMouse.tsx +125 -0
- package/src/app/charts/hooks/usePlotDimensions.tsx +120 -0
- package/src/app/charts/hooks/useTooltip.tsx +112 -0
- package/src/app/charts/observables/ChartData.ts +31 -0
- package/src/app/charts/observables/iterates.test.ts +201 -0
- package/src/app/charts/observables/iterates.ts +203 -0
- package/src/app/charts/observables/ordinals.test.ts +135 -0
- package/src/app/charts/observables/ordinals.ts +248 -0
- package/src/app/charts/plots/BarPlot.tsx +1088 -0
- package/src/app/charts/plots/PoincarePlot.tsx +832 -0
- package/src/app/charts/plots/RasterPlot.tsx +676 -0
- package/src/app/charts/plots/ScatterPlot.tsx +653 -0
- package/src/app/charts/plots/plot.ts +75 -0
- package/src/app/charts/series/baseSeries.ts +52 -0
- package/src/app/charts/series/datumSeries.test.ts +60 -0
- package/src/app/charts/series/iterateSeries.ts +56 -0
- package/src/app/charts/series/ordinalSeries.ts +164 -0
- package/src/app/charts/series/timeSeries.ts +40 -0
- package/src/app/charts/series/timeSeriesChartData.ts +68 -0
- package/src/app/charts/styling/barPlotStyle.ts +126 -0
- package/src/app/charts/styling/margins.test.ts +22 -0
- package/src/app/charts/styling/margins.ts +58 -0
- package/src/app/charts/styling/svgStyle.ts +118 -0
- package/src/app/charts/subscriptions/subscription.test.ts +499 -0
- package/src/app/charts/subscriptions/subscriptions.ts +635 -0
- package/src/app/charts/tooltips/BarPlotTooltipContent.tsx +440 -0
- package/src/app/charts/tooltips/PoincarePlotTooltipContent.tsx +352 -0
- package/src/app/charts/tooltips/RasterPlotTooltipContent.tsx +274 -0
- package/src/app/charts/tooltips/ScatterPlotTooltipContent.tsx +319 -0
- package/src/app/charts/tooltips/Tooltip.tsx +148 -0
- package/src/app/charts/tooltips/tooltipUtils.ts +216 -0
- package/src/app/charts/trackers/Tracker.tsx +129 -0
- package/src/app/charts/trackers/trackerUtils.ts +494 -0
- package/src/app/charts/utils.ts +109 -0
- package/dist/src/app/charts/CategoryAxis.d.ts +0 -20
- package/dist/src/app/charts/CategoryAxis.js +0 -112
- package/dist/src/app/charts/CategoryAxis.js.map +0 -1
- package/dist/src/app/charts/ContinuousAxis.d.ts +0 -16
- package/dist/src/app/charts/ContinuousAxis.js +0 -186
- package/dist/src/app/charts/ContinuousAxis.js.map +0 -1
- package/dist/src/app/charts/RasterPlot.js.map +0 -1
- package/dist/src/app/charts/RasterPlotTooltipContent.js.map +0 -1
- package/dist/src/app/charts/ScatterPlot.js +0 -351
- package/dist/src/app/charts/ScatterPlot.js.map +0 -1
- package/dist/src/app/charts/ScatterPlotTooltipContent.js +0 -163
- package/dist/src/app/charts/ScatterPlotTooltipContent.js.map +0 -1
- package/dist/src/app/charts/Tooltip.js.map +0 -1
- package/dist/src/app/charts/Tracker.js +0 -65
- package/dist/src/app/charts/Tracker.js.map +0 -1
- package/dist/src/app/charts/axes.d.ts +0 -132
- package/dist/src/app/charts/axes.js +0 -265
- package/dist/src/app/charts/axes.js.map +0 -1
- package/dist/src/app/charts/chartData.js +0 -32
- package/dist/src/app/charts/chartData.js.map +0 -1
- package/dist/src/app/charts/continuousAxisRangeFor.d.ts +0 -20
- package/dist/src/app/charts/continuousAxisRangeFor.js +0 -68
- package/dist/src/app/charts/continuousAxisRangeFor.js.map +0 -1
- package/dist/src/app/charts/datumSeries.d.ts +0 -59
- package/dist/src/app/charts/datumSeries.js +0 -55
- package/dist/src/app/charts/datumSeries.js.map +0 -1
- package/dist/src/app/charts/datumSeries.test.js +0 -50
- package/dist/src/app/charts/datumSeries.test.js.map +0 -1
- package/dist/src/app/charts/hooks/AxesState.d.ts +0 -37
- package/dist/src/app/charts/hooks/AxesState.js +0 -44
- package/dist/src/app/charts/hooks/AxesState.js.map +0 -1
- package/dist/src/app/charts/margins.js.map +0 -1
- package/dist/src/app/charts/margins.test.js +0 -14
- package/dist/src/app/charts/margins.test.js.map +0 -1
- package/dist/src/app/charts/plot.js.map +0 -1
- package/dist/src/app/charts/regexFilter.d.ts +0 -9
- package/dist/src/app/charts/regexFilter.js +0 -17
- package/dist/src/app/charts/regexFilter.js.map +0 -1
- package/dist/src/app/charts/regexFilter.test.js +0 -11
- package/dist/src/app/charts/regexFilter.test.js.map +0 -1
- package/dist/src/app/charts/subscriptions.d.ts +0 -43
- package/dist/src/app/charts/subscriptions.js +0 -213
- package/dist/src/app/charts/subscriptions.js.map +0 -1
- package/dist/src/app/charts/svgStyle.d.ts +0 -27
- package/dist/src/app/charts/svgStyle.js +0 -32
- package/dist/src/app/charts/svgStyle.js.map +0 -1
- package/dist/src/app/charts/timeRange.test.js +0 -54
- package/dist/src/app/charts/timeRange.test.js.map +0 -1
- package/dist/src/app/charts/tooltipUtils.js.map +0 -1
- package/dist/src/app/charts/trackerUtils.js +0 -130
- package/dist/src/app/charts/trackerUtils.js.map +0 -1
- /package/dist/src/app/charts/{datumSeries.test.d.ts → axes/AxesState.test.d.ts} +0 -0
- /package/dist/src/app/charts/{margins.test.d.ts → axes/AxisInterval.test.d.ts} +0 -0
- /package/dist/src/app/charts/{regexFilter.test.d.ts → axes/ContinuousAxisRange.test.d.ts} +0 -0
- /package/dist/src/app/charts/{timeRange.test.d.ts → axes/OrdinalAxisRange.test.d.ts} +0 -0
- /package/dist/src/app/charts/{RasterPlotTooltipContent.d.ts → tooltips/RasterPlotTooltipContent.d.ts} +0 -0
- /package/dist/src/app/charts/{ScatterPlotTooltipContent.d.ts → tooltips/ScatterPlotTooltipContent.d.ts} +0 -0
|
@@ -0,0 +1,1569 @@
|
|
|
1
|
+
import {Dimensions, Margin} from "../styling/margins";
|
|
2
|
+
import {ContinuousAxisRange} from "./ContinuousAxisRange";
|
|
3
|
+
import * as d3 from "d3";
|
|
4
|
+
import {Axis, ScaleBand, ScaleContinuousNumeric, ScaleLinear, ZoomTransform} from "d3";
|
|
5
|
+
import {AxisElementSelection, SvgSelection} from "../d3types";
|
|
6
|
+
import {AxesState} from "./AxesState";
|
|
7
|
+
import {AxesAssignment} from "../plots/plot";
|
|
8
|
+
import {BaseSeries} from "../series/baseSeries";
|
|
9
|
+
import {noop} from "../utils";
|
|
10
|
+
import {OrdinalAxisRange} from "./OrdinalAxisRange";
|
|
11
|
+
import {BaseAxisRange} from "./BaseAxisRange";
|
|
12
|
+
import {AxisInterval} from "./AxisInterval";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Holds the style for the axis tick labels
|
|
16
|
+
*/
|
|
17
|
+
export type AxisTickStyle = {
|
|
18
|
+
font: AxesFont
|
|
19
|
+
// the rotation of the tick labels in degrees
|
|
20
|
+
rotation: number
|
|
21
|
+
// whether to automatically rotate tick labels to fit within the axis space
|
|
22
|
+
useAutoRotation: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Factory function to create a default AxisTickStyle with the default font,
|
|
27
|
+
* no rotation, and no auto-rotation.
|
|
28
|
+
*/
|
|
29
|
+
export function defaultAxisTickStyle(): AxisTickStyle {
|
|
30
|
+
return {
|
|
31
|
+
font: defaultAxesFont(),
|
|
32
|
+
rotation: 0,
|
|
33
|
+
useAutoRotation: false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Holds the font information for the axis labels
|
|
39
|
+
*/
|
|
40
|
+
export interface AxesFont {
|
|
41
|
+
size: number
|
|
42
|
+
color: string
|
|
43
|
+
family: string
|
|
44
|
+
weight: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Factory function to create a default AxesFont with the default font size, color,
|
|
49
|
+
* family, and weight.
|
|
50
|
+
*/
|
|
51
|
+
export function defaultAxesFont(): AxesFont {
|
|
52
|
+
return {
|
|
53
|
+
size: 12,
|
|
54
|
+
color: '#d2933f',
|
|
55
|
+
weight: 300,
|
|
56
|
+
family: 'sans-serif'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Holds the style information for a series in a chart
|
|
62
|
+
*/
|
|
63
|
+
export interface SeriesStyle {
|
|
64
|
+
color: string
|
|
65
|
+
highlightColor: string
|
|
66
|
+
margin?: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Holds the style information for a series line-style in a chart.
|
|
71
|
+
*/
|
|
72
|
+
export interface SeriesLineStyle extends SeriesStyle {
|
|
73
|
+
lineWidth: number
|
|
74
|
+
highlightWidth: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Factory function to create a default SeriesLineStyle with the default color, line width,
|
|
79
|
+
* highlight color, and highlight width.
|
|
80
|
+
*/
|
|
81
|
+
export function defaultLineStyle(): SeriesLineStyle {
|
|
82
|
+
return {
|
|
83
|
+
color: '#008aad',
|
|
84
|
+
lineWidth: 1,
|
|
85
|
+
highlightColor: '#008aad',
|
|
86
|
+
highlightWidth: 3,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The base interface that all Axes must implement. Each axis must have a unique ID,
|
|
92
|
+
* a location (top, bottom, left, right), and d3 SVG selection for the axis.
|
|
93
|
+
*/
|
|
94
|
+
export interface BaseAxis {
|
|
95
|
+
axisId: string
|
|
96
|
+
location: AxisLocation
|
|
97
|
+
selection: AxisElementSelection
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Represents a continuous numeric axis.
|
|
102
|
+
*/
|
|
103
|
+
export interface ContinuousNumericAxis extends BaseAxis {
|
|
104
|
+
/**
|
|
105
|
+
* A d3 scale for a continuous numeric axis.
|
|
106
|
+
*/
|
|
107
|
+
scale: ScaleContinuousNumeric<number, number>
|
|
108
|
+
/**
|
|
109
|
+
* A d3 axis generator for a continuous numeric axis.
|
|
110
|
+
*/
|
|
111
|
+
generator: Axis<number | { valueOf(): number }>
|
|
112
|
+
/**
|
|
113
|
+
* Updates the axis based on the specified domain and plot dimensions.
|
|
114
|
+
* @param domain The interval representing the domain of the axis.
|
|
115
|
+
* @param plotDimensions The dimensions of the plot (without the margins).
|
|
116
|
+
* @param margin The margins for the plot
|
|
117
|
+
*/
|
|
118
|
+
update: (domain: AxisInterval, plotDimensions: Dimensions, margin: Margin) => void
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Represents an ordinal string axis.
|
|
123
|
+
*/
|
|
124
|
+
export interface OrdinalStringAxis extends BaseAxis {
|
|
125
|
+
/**
|
|
126
|
+
* A d3 scale for an ordinal string axis.
|
|
127
|
+
*/
|
|
128
|
+
scale: ScaleBand<string>
|
|
129
|
+
/**
|
|
130
|
+
* A d3 axis generator for an ordinal string axis.
|
|
131
|
+
*/
|
|
132
|
+
generator: Axis<string>
|
|
133
|
+
/**
|
|
134
|
+
* The size of each category on the axis.
|
|
135
|
+
*/
|
|
136
|
+
categorySize: number
|
|
137
|
+
/**
|
|
138
|
+
* Updates the axis based on the specified domain and plot dimensions.
|
|
139
|
+
* @param range An interval representing the range of the axis.
|
|
140
|
+
* @param originalRange An interval representing the original range of the axis (before zooming or panning).
|
|
141
|
+
* @param plotDimensions The dimensions of the plot (without the margins).
|
|
142
|
+
* @param margin The margins for the plot
|
|
143
|
+
* @return The number of pixels for each category on the axis.
|
|
144
|
+
*/
|
|
145
|
+
update: (range: AxisInterval, originalRange: AxisInterval, plotDimensions: Dimensions, margin: Margin) => number
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Enumerates the possible locations of an axis.
|
|
150
|
+
*/
|
|
151
|
+
export enum AxisLocation {
|
|
152
|
+
// y-axes
|
|
153
|
+
Left,
|
|
154
|
+
Right,
|
|
155
|
+
// x-axes
|
|
156
|
+
Bottom,
|
|
157
|
+
Top
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
axis clip path definition
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Adds a clip area for the chart to the specified SVG element. The clip-area is given
|
|
166
|
+
* an `id` of `clip-series-<chart_id>`, which because the chart ID should be unique, makes
|
|
167
|
+
* this unique as well
|
|
168
|
+
* @param chartId The ID of the chart to which the clip area is to be added
|
|
169
|
+
* @param axisId The ID of the axis to which the clip area is to be added
|
|
170
|
+
* @param svg The SVG element to which the clip area is to be added
|
|
171
|
+
* @param location The location of the axis
|
|
172
|
+
* @param plotDimensions The dimensions of the plot
|
|
173
|
+
* @param margin The margins around the plot
|
|
174
|
+
* @return The ID of the clip-path
|
|
175
|
+
*/
|
|
176
|
+
export function setClipPath(
|
|
177
|
+
chartId: number,
|
|
178
|
+
axisId: string,
|
|
179
|
+
svg: SvgSelection,
|
|
180
|
+
location: AxisLocation,
|
|
181
|
+
plotDimensions: Dimensions,
|
|
182
|
+
margin: Margin
|
|
183
|
+
): string {
|
|
184
|
+
const clipPathId = `chart-clip-path-${chartId}-${axisId}`
|
|
185
|
+
|
|
186
|
+
// calculate the width and height of the clip-area for the axis
|
|
187
|
+
const width = location === AxisLocation.Left ? margin.right :
|
|
188
|
+
location === AxisLocation.Right ? margin.left :
|
|
189
|
+
plotDimensions.width
|
|
190
|
+
|
|
191
|
+
const height = location === AxisLocation.Top ? margin.top + 1 :
|
|
192
|
+
location === AxisLocation.Bottom ? margin.bottom - 1:
|
|
193
|
+
plotDimensions.height
|
|
194
|
+
|
|
195
|
+
// remove the old clipping region and add a new one with the updated plot dimensions
|
|
196
|
+
svg.select(`#${clipPathId}-defs`).remove();
|
|
197
|
+
svg
|
|
198
|
+
.append('defs')
|
|
199
|
+
.attr('id', `${clipPathId}-defs`)
|
|
200
|
+
.append("clipPath")
|
|
201
|
+
.attr("id", clipPathId)
|
|
202
|
+
.append("rect")
|
|
203
|
+
.attr("x", 0)
|
|
204
|
+
.attr("y", location === AxisLocation.Bottom ? -1 : -margin.top)
|
|
205
|
+
.attr("width", width)
|
|
206
|
+
.attr("height", height)
|
|
207
|
+
|
|
208
|
+
return clipPathId
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
/*
|
|
213
|
+
category axes
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Adds a category axis to the specified location. When the location is top or bottom,
|
|
218
|
+
* the category axis represents the x-axis. When the location is left or right, then the
|
|
219
|
+
* category axis represents the y-axis.
|
|
220
|
+
* @param chartId The unique ID of the chart to which this axis belongs
|
|
221
|
+
* @param axisId A unique ID for the axis
|
|
222
|
+
* @param svg The SVG selection (d3)
|
|
223
|
+
* @param location The location of the axis
|
|
224
|
+
* @param categories An array holding the category names
|
|
225
|
+
* @param axisLabel The axis label
|
|
226
|
+
* @param axesLabelFont The font for the axis label
|
|
227
|
+
* @param axisTickStyle Styling information for the ticks (e.g. font, rotation, etc)
|
|
228
|
+
* @param plotDimensions The dimensions of the plot
|
|
229
|
+
* @param margin The plot margin
|
|
230
|
+
* @param setAxisRangeFor Lambda that sets the axis range for the specified axis
|
|
231
|
+
* @param setOriginalAxisRangeFor Lambda that sets the original axis range for the specified axis
|
|
232
|
+
* @return A category axis
|
|
233
|
+
*/
|
|
234
|
+
export function addOrdinalStringAxis(
|
|
235
|
+
chartId: number,
|
|
236
|
+
axisId: string,
|
|
237
|
+
svg: SvgSelection,
|
|
238
|
+
location: AxisLocation,
|
|
239
|
+
categories: Array<string>,
|
|
240
|
+
axisLabel: string,
|
|
241
|
+
axesLabelFont: AxesFont,
|
|
242
|
+
axisTickStyle: AxisTickStyle,
|
|
243
|
+
plotDimensions: Dimensions,
|
|
244
|
+
margin: Margin,
|
|
245
|
+
setAxisRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
246
|
+
setOriginalAxisRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
247
|
+
): OrdinalStringAxis {
|
|
248
|
+
switch (location) {
|
|
249
|
+
case AxisLocation.Top:
|
|
250
|
+
case AxisLocation.Bottom:
|
|
251
|
+
return addOrdinalStringXAxis(
|
|
252
|
+
chartId, axisId,
|
|
253
|
+
svg,
|
|
254
|
+
location,
|
|
255
|
+
categories,
|
|
256
|
+
axisLabel, axesLabelFont, axisTickStyle,
|
|
257
|
+
plotDimensions,
|
|
258
|
+
margin,
|
|
259
|
+
setAxisRangeFor,
|
|
260
|
+
setOriginalAxisRangeFor
|
|
261
|
+
)
|
|
262
|
+
case AxisLocation.Left:
|
|
263
|
+
case AxisLocation.Right:
|
|
264
|
+
return addOrdinalStringYAxis(
|
|
265
|
+
chartId, axisId,
|
|
266
|
+
svg,
|
|
267
|
+
location,
|
|
268
|
+
categories,
|
|
269
|
+
axisLabel, axesLabelFont, axisTickStyle,
|
|
270
|
+
plotDimensions,
|
|
271
|
+
margin,
|
|
272
|
+
setAxisRangeFor,
|
|
273
|
+
setOriginalAxisRangeFor
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Adds a category axis representing the x-axis.
|
|
281
|
+
* @param chartId The unique ID of the chart to which this axis belongs
|
|
282
|
+
* @param axisId A unique ID for the axis
|
|
283
|
+
* @param svg The SVG selection (d3)
|
|
284
|
+
* @param location The location of the axis
|
|
285
|
+
* @param categories An array holding the category names
|
|
286
|
+
* @param axisLabel The axis label
|
|
287
|
+
* @param axesLabelFont The font for the axis label
|
|
288
|
+
* @param axisTickStyle Styling information for the ticks (e.g. font, rotation, etc)
|
|
289
|
+
* @param plotDimensions The dimensions of the plot
|
|
290
|
+
* @param margin The plot margin
|
|
291
|
+
* @param setAxisRangeFor Lambda that sets the axis range for the specified axis
|
|
292
|
+
* @param setOriginalAxisRangeFor Lambda that sets the original axis range for the specified axis
|
|
293
|
+
* @return A category axis
|
|
294
|
+
*/
|
|
295
|
+
function addOrdinalStringXAxis(
|
|
296
|
+
chartId: number,
|
|
297
|
+
axisId: string,
|
|
298
|
+
svg: SvgSelection,
|
|
299
|
+
location: AxisLocation.Bottom | AxisLocation.Top,
|
|
300
|
+
categories: Array<string>,
|
|
301
|
+
axisLabel: string,
|
|
302
|
+
axesLabelFont: AxesFont,
|
|
303
|
+
axisTickStyle: AxisTickStyle,
|
|
304
|
+
plotDimensions: Dimensions,
|
|
305
|
+
margin: Margin,
|
|
306
|
+
setAxisRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
307
|
+
setOriginalAxisRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
308
|
+
): OrdinalStringAxis {
|
|
309
|
+
const scale = d3.scaleBand()
|
|
310
|
+
.domain(categories)
|
|
311
|
+
.range([0, plotDimensions.width])
|
|
312
|
+
const categorySize = scale.bandwidth()
|
|
313
|
+
|
|
314
|
+
// create and add the axes
|
|
315
|
+
const generator = location === AxisLocation.Bottom ? d3.axisBottom(scale) : d3.axisTop(scale)
|
|
316
|
+
|
|
317
|
+
const clipPathId = setClipPath(chartId, axisId, svg, location, plotDimensions, margin)
|
|
318
|
+
const selection = svg
|
|
319
|
+
.append<SVGGElement>('g')
|
|
320
|
+
.attr('id', `x-axis-selection-${chartId}-${axisId}`)
|
|
321
|
+
.attr("clip-path", `url(#${clipPathId})`)
|
|
322
|
+
.classed('x-axis', true)
|
|
323
|
+
.attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`)
|
|
324
|
+
.call(generator)
|
|
325
|
+
|
|
326
|
+
// rotate the tick-labels by the specified amount (in degrees)
|
|
327
|
+
let maxTickLabelHeight = 0
|
|
328
|
+
const {font, rotation} = axisTickStyle
|
|
329
|
+
selection
|
|
330
|
+
.selectAll("text")
|
|
331
|
+
.style("text-anchor", "end")
|
|
332
|
+
.each(function () {
|
|
333
|
+
const degrees = location === AxisLocation.Bottom ? -rotation : rotation
|
|
334
|
+
const radians = degrees * Math.PI / 180
|
|
335
|
+
const {width, height, x, y} = (this as SVGTextElement).getBBox()
|
|
336
|
+
const xOrigin = x + width
|
|
337
|
+
const yOrigin = location === AxisLocation.Bottom ? y + height / 2 : y + height / 2
|
|
338
|
+
|
|
339
|
+
d3.select(this)
|
|
340
|
+
.attr("transform", () => `translate(${width * Math.cos(radians) / 2}, 0) rotate(${degrees}, ${xOrigin}, ${yOrigin})`)
|
|
341
|
+
|
|
342
|
+
const newHeight = Math.abs(width * Math.sin(radians))
|
|
343
|
+
if (newHeight > maxTickLabelHeight) {
|
|
344
|
+
maxTickLabelHeight = newHeight
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
svg
|
|
349
|
+
.append<SVGTextElement>('text')
|
|
350
|
+
.attr('id', labelIdFor(chartId, location))
|
|
351
|
+
.style('text-anchor', 'middle')
|
|
352
|
+
// when the axis is at the top, want the label to hang from the top (y=0) of the plot
|
|
353
|
+
.attr('dominant-baseline', location === AxisLocation.Top ? "hanging" : "baseline")
|
|
354
|
+
.attr('font-size', font.size)
|
|
355
|
+
.attr('fill', font.color)
|
|
356
|
+
.attr('font-family', font.family)
|
|
357
|
+
.attr('font-weight', font.weight)
|
|
358
|
+
.text(axisLabel)
|
|
359
|
+
|
|
360
|
+
const axis = {axisId, selection, location, scale, generator, categorySize, update: () => categorySize}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
...axis,
|
|
364
|
+
update: (range: AxisInterval, originalRange: AxisInterval, plotDimensions: Dimensions, margin: Margin) => {
|
|
365
|
+
const categorySize = updateOrdinalStringXAxis(chartId, axis, svg, location, categories, range, axesLabelFont, plotDimensions, margin)
|
|
366
|
+
setAxisRangeFor(axisId, range)
|
|
367
|
+
setOriginalAxisRangeFor(axisId, originalRange)
|
|
368
|
+
return categorySize
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Adds a category axis representing the y-axis.
|
|
375
|
+
* @param chartId The unique ID of the chart to which this axis belongs
|
|
376
|
+
* @param axisId A unique ID for the axis
|
|
377
|
+
* @param svg The SVG selection (d3)
|
|
378
|
+
* @param location The location of the axis
|
|
379
|
+
* @param categories An array holding the category names
|
|
380
|
+
* @param axisLabel The axis label
|
|
381
|
+
* @param axesLabelFont The font for the axis label
|
|
382
|
+
* @param axisTickStyle Styling information for the ticks (e.g. font, rotation, etc)
|
|
383
|
+
* @param plotDimensions The dimensions of the plot
|
|
384
|
+
* @param margin The plot margin
|
|
385
|
+
* @param setAxisRangeFor Lambda that sets the axis range for the specified axis
|
|
386
|
+
* @param setOriginalAxisRangeFor Lambda that sets the original axis range for the specified axis
|
|
387
|
+
* @return A category axis
|
|
388
|
+
*/
|
|
389
|
+
function addOrdinalStringYAxis(
|
|
390
|
+
chartId: number,
|
|
391
|
+
axisId: string,
|
|
392
|
+
svg: SvgSelection,
|
|
393
|
+
location: AxisLocation.Left | AxisLocation.Right,
|
|
394
|
+
categories: Array<string>,
|
|
395
|
+
axisLabel: string,
|
|
396
|
+
axesLabelFont: AxesFont,
|
|
397
|
+
axisTickStyle: AxisTickStyle,
|
|
398
|
+
plotDimensions: Dimensions,
|
|
399
|
+
margin: Margin,
|
|
400
|
+
setAxisRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
401
|
+
setOriginalAxisRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
402
|
+
): OrdinalStringAxis {
|
|
403
|
+
const scale = d3.scaleBand()
|
|
404
|
+
.domain(categories)
|
|
405
|
+
.range([0, plotDimensions.height])
|
|
406
|
+
const categorySize = scale.bandwidth()
|
|
407
|
+
|
|
408
|
+
// create and add the axes
|
|
409
|
+
const generator = location === AxisLocation.Left ? d3.axisLeft(scale) : d3.axisRight(scale)
|
|
410
|
+
|
|
411
|
+
const selection = svg
|
|
412
|
+
.append<SVGGElement>('g')
|
|
413
|
+
.attr('id', `y-axis-selection-${chartId}`)
|
|
414
|
+
.attr('class', 'y-axis')
|
|
415
|
+
.attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`)
|
|
416
|
+
.call(generator);
|
|
417
|
+
|
|
418
|
+
// rotate the tick-labels by the specified amount (in degrees)
|
|
419
|
+
const {font, rotation} = axisTickStyle
|
|
420
|
+
selection
|
|
421
|
+
.selectAll("text")
|
|
422
|
+
.style("text-anchor", "end")
|
|
423
|
+
.each(function () {
|
|
424
|
+
const degrees = location === AxisLocation.Left ? -rotation : rotation
|
|
425
|
+
const {width, height, x, y} = (this as SVGTextElement).getBBox()
|
|
426
|
+
const xTranslation = location === AxisLocation.Left ? 0 : width
|
|
427
|
+
const xOrigin = x + width
|
|
428
|
+
const yOrigin = location === AxisLocation.Left ? y + height / 2 : y + height / 2
|
|
429
|
+
|
|
430
|
+
d3.select(this)
|
|
431
|
+
.attr("transform", () => `translate(${xTranslation}, 0) rotate(${degrees}, ${xOrigin}, ${yOrigin})`)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
svg
|
|
435
|
+
.append<SVGTextElement>('text')
|
|
436
|
+
.attr('id', labelIdFor(chartId, location))
|
|
437
|
+
.attr('text-anchor', 'middle')
|
|
438
|
+
.attr('font-size', font.size)
|
|
439
|
+
.attr('fill', font.color)
|
|
440
|
+
.attr('font-family', font.family)
|
|
441
|
+
.attr('font-weight', font.weight)
|
|
442
|
+
.attr('transform', `translate(${ordinalLabelXTranslation(location, plotDimensions, margin, axesLabelFont)}, ${ordinalLabelYTranslation(location, plotDimensions, margin)}) rotate(-90)`)
|
|
443
|
+
.text(axisLabel)
|
|
444
|
+
|
|
445
|
+
const axis = {axisId, selection, location, scale, generator, categorySize, update: () => categorySize}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
...axis,
|
|
449
|
+
update: (range: AxisInterval, originalRange: AxisInterval, plotDimensions: Dimensions, margin: Margin) => {
|
|
450
|
+
const categorySize = updateOrdinalStringYAxis(chartId, axis, svg, location, categories, range, axesLabelFont, plotDimensions, margin)
|
|
451
|
+
setAxisRangeFor(axisId, range)
|
|
452
|
+
setOriginalAxisRangeFor(axisId, originalRange)
|
|
453
|
+
return categorySize
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Updates the category axis representing the x-axis, calculates and returns the
|
|
460
|
+
* number of pixels for each category on the x-axis.
|
|
461
|
+
* @param chartId The unique ID of the chart to which this axis belongs
|
|
462
|
+
* @param axis The category axis to be updated
|
|
463
|
+
* @param svg The SVG selection (d3)
|
|
464
|
+
* @param location The location of the axis
|
|
465
|
+
* @param names An array holding the category names
|
|
466
|
+
* @param axesLabelFont The font for the axis label
|
|
467
|
+
* @param plotDimensions The dimensions of the plot
|
|
468
|
+
* @param margin The plot margin
|
|
469
|
+
* @param range The range of the axis (e.g., start and end) in pixels
|
|
470
|
+
* @param axesLabelFont The font for the axis label
|
|
471
|
+
* @param plotDimensions The dimensions of the plot
|
|
472
|
+
* @param margin The plot margin
|
|
473
|
+
* adjust the axis label location when needed
|
|
474
|
+
* @return The number of pixels for each category
|
|
475
|
+
*/
|
|
476
|
+
function updateOrdinalStringXAxis(
|
|
477
|
+
chartId: number,
|
|
478
|
+
axis: OrdinalStringAxis,
|
|
479
|
+
svg: SvgSelection,
|
|
480
|
+
location: AxisLocation.Bottom | AxisLocation.Top,
|
|
481
|
+
names: Array<string>, // domain (categories)
|
|
482
|
+
range: AxisInterval, // range (pixels)
|
|
483
|
+
axesLabelFont: AxesFont,
|
|
484
|
+
plotDimensions: Dimensions,
|
|
485
|
+
margin: Margin
|
|
486
|
+
): number {
|
|
487
|
+
const updatedRange = AxisInterval.from(
|
|
488
|
+
Math.min(range.start, 0),
|
|
489
|
+
Math.max(range.end, plotDimensions.width)
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
axis.scale = axis.scale
|
|
493
|
+
.domain(names)
|
|
494
|
+
.range(updatedRange.asTuple())
|
|
495
|
+
|
|
496
|
+
axis.categorySize = axis.scale.bandwidth()
|
|
497
|
+
|
|
498
|
+
const clipPathId = setClipPath(chartId, axis.axisId, svg, location, plotDimensions, margin)
|
|
499
|
+
axis.selection = axis.selection
|
|
500
|
+
.attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`)
|
|
501
|
+
.attr("clip-path", `url(#${clipPathId})`)
|
|
502
|
+
.call(axis.generator)
|
|
503
|
+
|
|
504
|
+
const xLabelTranslation = ordinalLabelXTranslation(location, plotDimensions, margin, axesLabelFont)
|
|
505
|
+
const yLabelTranslation = ordinalLabelYTranslation(location, plotDimensions, margin)
|
|
506
|
+
svg
|
|
507
|
+
.select(`#${labelIdFor(chartId, location)}`)
|
|
508
|
+
.attr('transform', `translate(${xLabelTranslation}, ${yLabelTranslation})`)
|
|
509
|
+
|
|
510
|
+
return axis.categorySize
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Updates the category axis representing the y-axis, calculates and returns the
|
|
515
|
+
* number of pixels for each category on the y-axis.
|
|
516
|
+
* @param chartId The unique ID of the chart to which this axis belongs
|
|
517
|
+
* @param axis The category axis to be updated
|
|
518
|
+
* @param svg The SVG selection (d3)
|
|
519
|
+
* @param location The location of the axis
|
|
520
|
+
* @param names An array holding the category names
|
|
521
|
+
* @param range The range of the axis (e.g. start and end)
|
|
522
|
+
* @param axesLabelFont The font for the axis label
|
|
523
|
+
* @param plotDimensions The dimensions of the plot
|
|
524
|
+
* @param margin The plot margin
|
|
525
|
+
* @param axesLabelFont The font for the axis label
|
|
526
|
+
* @param plotDimensions The dimensions of the plot
|
|
527
|
+
* @param margin The plot margin
|
|
528
|
+
* @return The number of pixels for each category
|
|
529
|
+
*/
|
|
530
|
+
function updateOrdinalStringYAxis(
|
|
531
|
+
chartId: number,
|
|
532
|
+
axis: OrdinalStringAxis,
|
|
533
|
+
svg: SvgSelection,
|
|
534
|
+
location: AxisLocation.Left | AxisLocation.Right,
|
|
535
|
+
names: Array<string>, // domain
|
|
536
|
+
range: AxisInterval, // range
|
|
537
|
+
axesLabelFont: AxesFont,
|
|
538
|
+
plotDimensions: Dimensions,
|
|
539
|
+
margin: Margin,
|
|
540
|
+
): number {
|
|
541
|
+
const updatedRange = AxisInterval.from(
|
|
542
|
+
Math.min(range.start, 0),
|
|
543
|
+
Math.max(range.end, plotDimensions.height)
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
axis.scale = axis.scale
|
|
547
|
+
.domain(names)
|
|
548
|
+
.range(updatedRange.asTuple())
|
|
549
|
+
axis.categorySize = axis.scale.bandwidth()
|
|
550
|
+
axis.selection = axis.selection
|
|
551
|
+
.attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`)
|
|
552
|
+
.call(axis.generator)
|
|
553
|
+
|
|
554
|
+
svg
|
|
555
|
+
.select(`#${labelIdFor(chartId, location)}`)
|
|
556
|
+
.attr('transform', `translate(${ordinalLabelXTranslation(location, plotDimensions, margin, axesLabelFont)}, ${ordinalLabelYTranslation(location, plotDimensions, margin)}) rotate(-90)`)
|
|
557
|
+
|
|
558
|
+
return axis.categorySize
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Calculates the number of pixels by which to translate the label in the x-direction for the axis.
|
|
563
|
+
* The calculation uses the dimensions and margins of the plot, the location of the
|
|
564
|
+
* axis, and the font for the label.
|
|
565
|
+
* @param location The axis location (i.e. top, bottom, left, right)
|
|
566
|
+
* @param plotDimensions The dimensions of the plot
|
|
567
|
+
* @param margin The margins for the plot
|
|
568
|
+
* @param axesLabelFont The font for the axis label
|
|
569
|
+
* @return The number of pixels to translate the label in the x-direction
|
|
570
|
+
*/
|
|
571
|
+
function ordinalLabelXTranslation(
|
|
572
|
+
location: AxisLocation,
|
|
573
|
+
plotDimensions: Dimensions,
|
|
574
|
+
margin: Margin,
|
|
575
|
+
axesLabelFont: AxesFont,
|
|
576
|
+
): number {
|
|
577
|
+
switch (location) {
|
|
578
|
+
case AxisLocation.Left:
|
|
579
|
+
case AxisLocation.Right:
|
|
580
|
+
return location === AxisLocation.Left ?
|
|
581
|
+
axesLabelFont.size :
|
|
582
|
+
margin.left + plotDimensions.width + margin.right - axesLabelFont.size
|
|
583
|
+
case AxisLocation.Top:
|
|
584
|
+
case AxisLocation.Bottom:
|
|
585
|
+
return (plotDimensions.width + margin.left + margin.right) / 2
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Calculates the number of pixels by which to translate the label in the y-direction the axis.
|
|
591
|
+
* The calculation uses the dimensions and margins of the plot, the location of the
|
|
592
|
+
* axis, and the font for the label.
|
|
593
|
+
* @param location The axis location (i.e. top, bottom, left, right)
|
|
594
|
+
* @param plotDimensions The dimensions of the plot
|
|
595
|
+
* @param margin The margins for the plot
|
|
596
|
+
* @return The number of pixels to translate the label for the y-direction
|
|
597
|
+
*/
|
|
598
|
+
function ordinalLabelYTranslation(
|
|
599
|
+
location: AxisLocation,
|
|
600
|
+
plotDimensions: Dimensions,
|
|
601
|
+
margin: Margin
|
|
602
|
+
): number {
|
|
603
|
+
switch (location) {
|
|
604
|
+
case AxisLocation.Left:
|
|
605
|
+
case AxisLocation.Right:
|
|
606
|
+
return (margin.top + margin.bottom + plotDimensions.height) / 2
|
|
607
|
+
case AxisLocation.Top:
|
|
608
|
+
return 0
|
|
609
|
+
case AxisLocation.Bottom:
|
|
610
|
+
return margin.top + margin.bottom + plotDimensions.height
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
/*
|
|
616
|
+
continuous numeric axes
|
|
617
|
+
*/
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Adds a new x-axis to the SVG element at the specified location
|
|
621
|
+
* @param chartId The ID of the chart
|
|
622
|
+
* @param axisId The ID of the axis
|
|
623
|
+
* @param svg The SVG selection to which to add the axis
|
|
624
|
+
* @param plotDimensions The dimensions of the plot
|
|
625
|
+
* @param location The location of the axis
|
|
626
|
+
* @param scaleGenerator The higher-order function that returns the axis d3 "scale" function
|
|
627
|
+
* @param domain The axis range (start, end)
|
|
628
|
+
* @param axesLabelFont The font for the axis labels
|
|
629
|
+
* @param margin The plot margins for the border of main SVG group
|
|
630
|
+
* @param axisLabel The label for the axis
|
|
631
|
+
* @param setAxisRangeFor A callback used to set the axis range
|
|
632
|
+
* @return A {@link ContinuousNumericAxis} based on the arguments to this function
|
|
633
|
+
*/
|
|
634
|
+
export function addContinuousNumericXAxis(
|
|
635
|
+
chartId: number,
|
|
636
|
+
axisId: string,
|
|
637
|
+
svg: SvgSelection,
|
|
638
|
+
plotDimensions: Dimensions,
|
|
639
|
+
location: AxisLocation.Bottom | AxisLocation.Top,
|
|
640
|
+
scaleGenerator: ScaleContinuousNumeric<number, number>,
|
|
641
|
+
domain: [minValue: number, maxValue: number],
|
|
642
|
+
axesLabelFont: AxesFont,
|
|
643
|
+
margin: Margin,
|
|
644
|
+
axisLabel: string,
|
|
645
|
+
setAxisRangeFor: (axisId: string, timeRange: AxisInterval) => void,
|
|
646
|
+
): ContinuousNumericAxis {
|
|
647
|
+
const scale = scaleGenerator.domain(domain).range([0, plotDimensions.width])
|
|
648
|
+
|
|
649
|
+
const selection = svg
|
|
650
|
+
.append<SVGGElement>('g')
|
|
651
|
+
.attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`)
|
|
652
|
+
|
|
653
|
+
svg
|
|
654
|
+
.append<SVGTextElement>('text')
|
|
655
|
+
.attr('id', labelIdFor(chartId, location))
|
|
656
|
+
.style('text-anchor', 'middle')
|
|
657
|
+
// when the axis is at the top, want the label to hang from the top (y=0) of the plot
|
|
658
|
+
.style('dominant-baseline', location === AxisLocation.Top ? "hanging" : "baseline")
|
|
659
|
+
.style('font-size', axesLabelFont.size)
|
|
660
|
+
.style('fill', axesLabelFont.color)
|
|
661
|
+
.style('font-family', axesLabelFont.family)
|
|
662
|
+
.style('font-weight', axesLabelFont.weight)
|
|
663
|
+
.attr('transform', `translate(${margin.left + plotDimensions.width / 2}, ${continuousLabelYTranslation(location, plotDimensions, margin)})`)
|
|
664
|
+
.text(axisLabel)
|
|
665
|
+
|
|
666
|
+
const axis: ContinuousNumericAxis = {
|
|
667
|
+
axisId,
|
|
668
|
+
location,
|
|
669
|
+
selection,
|
|
670
|
+
scale,
|
|
671
|
+
generator: location === AxisLocation.Bottom ? d3.axisBottom(scale) : d3.axisTop(scale),
|
|
672
|
+
update: noop
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
...axis,
|
|
676
|
+
update: (domain: AxisInterval, plotDimensions: Dimensions, margin: Margin) => {
|
|
677
|
+
updateLinearXAxis(domain, axisLabel, chartId, svg, axis, plotDimensions, margin, location)
|
|
678
|
+
setAxisRangeFor(axisId, domain)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Updates the x-axis with the new domain and axis label
|
|
685
|
+
* @param domain The new (start, end) range of the axis
|
|
686
|
+
* @param label The new label for the axis
|
|
687
|
+
* @param chartId The ID of the chart
|
|
688
|
+
* @param svg The SVG selection of which the axis is a child node
|
|
689
|
+
* @param axis The x-axis
|
|
690
|
+
* @param plotDimensions The dimensions of the plot
|
|
691
|
+
* @param margin The plot margins for the border of main SVG group
|
|
692
|
+
* @param location The location of the axis (i.e. top, bottom, left, right)
|
|
693
|
+
*/
|
|
694
|
+
function updateLinearXAxis(
|
|
695
|
+
domain: AxisInterval,
|
|
696
|
+
label: string,
|
|
697
|
+
chartId: number,
|
|
698
|
+
svg: SvgSelection,
|
|
699
|
+
axis: ContinuousNumericAxis,
|
|
700
|
+
plotDimensions: Dimensions,
|
|
701
|
+
margin: Margin,
|
|
702
|
+
location: AxisLocation.Bottom | AxisLocation.Top,
|
|
703
|
+
): void {
|
|
704
|
+
axis.scale.domain(domain.asTuple()).range([0, plotDimensions.width])
|
|
705
|
+
|
|
706
|
+
axis.selection
|
|
707
|
+
.attr('transform', `translate(${margin.left}, ${yTranslation(location, plotDimensions, margin)})`)
|
|
708
|
+
.call(axis.generator)
|
|
709
|
+
svg
|
|
710
|
+
.select(`#${labelIdFor(chartId, location)}`)
|
|
711
|
+
.attr('transform', `translate(${margin.left + plotDimensions.width / 2}, ${continuousLabelYTranslation(location, plotDimensions, margin)})`)
|
|
712
|
+
.text(label)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* The number of pixels to translate the x-axis label to the right
|
|
717
|
+
* @param location The location of the x-axis
|
|
718
|
+
* @param plotDimensions The dimensions of the plot
|
|
719
|
+
* @param margin The plot margins for the border of main SVG group
|
|
720
|
+
* @return The number of pixels to translate the x-axis label to the right
|
|
721
|
+
*/
|
|
722
|
+
function continuousLabelYTranslation(location: AxisLocation.Bottom | AxisLocation.Top, plotDimensions: Dimensions, margin: Margin): number {
|
|
723
|
+
return location === AxisLocation.Bottom ? plotDimensions.height + margin.top + margin.bottom : 0
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Adds a new y-axis to the SVG element at the specified location
|
|
728
|
+
* @param chartId The ID of the chart
|
|
729
|
+
* @param axisId The ID of the axis
|
|
730
|
+
* @param svg The SVG selection to which to add the axis
|
|
731
|
+
* @param plotDimensions The dimensions of the plot
|
|
732
|
+
* @param location The location of the axis
|
|
733
|
+
* @param scaleGenerator The higher-order function that returns the axis d3 "scale" function
|
|
734
|
+
* @param domain The axis range (start, end)
|
|
735
|
+
* @param axesLabelFont The font for the axis labels
|
|
736
|
+
* @param margin The plot margins for the border of main SVG group
|
|
737
|
+
* @param axisLabel The label for the axis
|
|
738
|
+
* @param setAxisRangeFor A callback used to set the axis range
|
|
739
|
+
* @return A {@link ContinuousNumericAxis} based on the arguments to this function
|
|
740
|
+
*/
|
|
741
|
+
export function addContinuousNumericYAxis(
|
|
742
|
+
chartId: number,
|
|
743
|
+
axisId: string,
|
|
744
|
+
svg: SvgSelection,
|
|
745
|
+
plotDimensions: Dimensions,
|
|
746
|
+
location: AxisLocation.Left | AxisLocation.Right,
|
|
747
|
+
scaleGenerator: ScaleContinuousNumeric<number, number>,
|
|
748
|
+
domain: [minValue: number, maxValue: number],
|
|
749
|
+
axesLabelFont: AxesFont,
|
|
750
|
+
margin: Margin,
|
|
751
|
+
axisLabel: string,
|
|
752
|
+
setAxisRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
753
|
+
): ContinuousNumericAxis {
|
|
754
|
+
const scale = scaleGenerator
|
|
755
|
+
.domain(domain)
|
|
756
|
+
.range([Math.max(margin.bottom, plotDimensions.height), 0])
|
|
757
|
+
|
|
758
|
+
const selection = svg
|
|
759
|
+
.append<SVGGElement>('g')
|
|
760
|
+
.attr('class', 'y-axis')
|
|
761
|
+
.attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`)
|
|
762
|
+
|
|
763
|
+
svg
|
|
764
|
+
.append<SVGTextElement>('text')
|
|
765
|
+
.attr('id', labelIdFor(chartId, location))
|
|
766
|
+
.attr('text-anchor', 'start')
|
|
767
|
+
.attr('font-size', axesLabelFont.size)
|
|
768
|
+
.attr('fill', axesLabelFont.color)
|
|
769
|
+
.attr('font-family', axesLabelFont.family)
|
|
770
|
+
.attr('font-weight', axesLabelFont.weight)
|
|
771
|
+
.attr('transform', `translate(${continuousLabelXTranslation(location, plotDimensions, margin, axesLabelFont)}, ${margin.top + plotDimensions.height / 2}) rotate(-90)`)
|
|
772
|
+
.text(axisLabel)
|
|
773
|
+
|
|
774
|
+
const axis: ContinuousNumericAxis = {
|
|
775
|
+
axisId,
|
|
776
|
+
location,
|
|
777
|
+
selection,
|
|
778
|
+
scale,
|
|
779
|
+
generator: location === AxisLocation.Left ? d3.axisLeft(scale) : d3.axisRight(scale),
|
|
780
|
+
update: noop
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
...axis,
|
|
784
|
+
update: (domain: AxisInterval, plotDimensions: Dimensions, margin: Margin) => {
|
|
785
|
+
updateLinearYAxis(domain, axisLabel, chartId, svg, axis, plotDimensions, margin, axesLabelFont, location)
|
|
786
|
+
setAxisRangeFor(axisId, domain)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Updates the y-axis with the new domain and axis label
|
|
793
|
+
* @param domain The new (start, end) range of the axis
|
|
794
|
+
* @param label The new label for the axis
|
|
795
|
+
* @param chartId The ID of the chart
|
|
796
|
+
* @param svg The SVG selection of which the axis is a child node
|
|
797
|
+
* @param axis The y-axis
|
|
798
|
+
* @param plotDimensions The dimensions of the plot
|
|
799
|
+
* @param margin The plot margins for the border of main SVG group
|
|
800
|
+
* @param axesLabelFont The font for the axis label
|
|
801
|
+
* @param location The location of the axis (i.e. top, bottom, left, right)
|
|
802
|
+
*/
|
|
803
|
+
function updateLinearYAxis(
|
|
804
|
+
domain: AxisInterval,
|
|
805
|
+
label: string,
|
|
806
|
+
chartId: number,
|
|
807
|
+
svg: SvgSelection,
|
|
808
|
+
axis: ContinuousNumericAxis,
|
|
809
|
+
plotDimensions: Dimensions,
|
|
810
|
+
margin: Margin,
|
|
811
|
+
axesLabelFont: AxesFont,
|
|
812
|
+
location: AxisLocation.Left | AxisLocation.Right,
|
|
813
|
+
): void {
|
|
814
|
+
axis.scale.domain(domain.asTuple()).range([Math.max(margin.bottom, plotDimensions.height), 0])
|
|
815
|
+
axis.selection
|
|
816
|
+
.attr('transform', `translate(${xTranslation(location, plotDimensions, margin)}, ${margin.top})`)
|
|
817
|
+
.call(axis.generator)
|
|
818
|
+
|
|
819
|
+
svg
|
|
820
|
+
.select(`#${labelIdFor(chartId, location)}`)
|
|
821
|
+
.attr('transform', `translate(${continuousLabelXTranslation(location, plotDimensions, margin, axesLabelFont)}, ${margin.top + plotDimensions.height / 2}) rotate(-90)`)
|
|
822
|
+
.text(label)
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* The number of pixels to translate the y-axis label down
|
|
827
|
+
* @param location The location of the y-axis
|
|
828
|
+
* @param plotDimensions The dimensions of the plot
|
|
829
|
+
* @param margin The plot margins for the border of main SVG group
|
|
830
|
+
* @param axesLabelFont The font for the axis label
|
|
831
|
+
* @return The number of pixels to translate the y-axis label down
|
|
832
|
+
*/
|
|
833
|
+
function continuousLabelXTranslation(location: AxisLocation.Left | AxisLocation.Right, plotDimensions: Dimensions, margin: Margin, axesLabelFont: AxesFont): number {
|
|
834
|
+
return location === AxisLocation.Left ?
|
|
835
|
+
axesLabelFont.size :
|
|
836
|
+
margin.left + plotDimensions.width + margin.right - axesLabelFont.size
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/*
|
|
840
|
+
common axis functions
|
|
841
|
+
*/
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Calculates the number of pixels by which to translate the x-axis.
|
|
845
|
+
* @param location The axis location (i.e. top, bottom, left, right)
|
|
846
|
+
* @param plotDimensions The dimensions of the plot
|
|
847
|
+
* @param margin The margins for the plot
|
|
848
|
+
* @return The number of pixels to translate the x-axis
|
|
849
|
+
*/
|
|
850
|
+
function xTranslation(location: AxisLocation.Left | AxisLocation.Right, plotDimensions: Dimensions, margin: Margin): number {
|
|
851
|
+
return location === AxisLocation.Left ? margin.left : margin.left + plotDimensions.width
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* The number of pixels to translate the x-axis to the right
|
|
856
|
+
* @param location The location of the x-axis
|
|
857
|
+
* @param plotDimensions The dimensions of the plot
|
|
858
|
+
* @param margin The plot margins for the border of main SVG group
|
|
859
|
+
* @return The number of pixels to translate the y-axis to the down
|
|
860
|
+
*/
|
|
861
|
+
function yTranslation(location: AxisLocation.Bottom | AxisLocation.Top, plotDimensions: Dimensions, margin: Margin): number {
|
|
862
|
+
return location === AxisLocation.Bottom ?
|
|
863
|
+
plotDimensions.height + margin.top :
|
|
864
|
+
margin.top
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Calculates a unique ID for the axis based on the location and chart ID
|
|
869
|
+
* @param chartId The unique ID of the chart
|
|
870
|
+
* @param location The axis location (i.e. top, bottom, left, right)
|
|
871
|
+
* @return A unique ID for the axis
|
|
872
|
+
*/
|
|
873
|
+
export function labelIdFor(chartId: number, location: AxisLocation): string {
|
|
874
|
+
switch (location) {
|
|
875
|
+
case AxisLocation.Left:
|
|
876
|
+
case AxisLocation.Right:
|
|
877
|
+
return `stream-chart-y-axis-${location}-label-${chartId}`
|
|
878
|
+
case AxisLocation.Top:
|
|
879
|
+
case AxisLocation.Bottom:
|
|
880
|
+
return `stream-chart-x-axis-${location}-label-${chartId}`
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
/*
|
|
886
|
+
zooming
|
|
887
|
+
*/
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* The result of a zoom action
|
|
891
|
+
*/
|
|
892
|
+
export interface ZoomResult<AR extends BaseAxisRange> {
|
|
893
|
+
range: AR
|
|
894
|
+
// range: ContinuousAxisRange
|
|
895
|
+
zoomFactor: number
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// /**
|
|
899
|
+
// * todo this can be generalized to any continuous numeric axis, replace x with value
|
|
900
|
+
// * Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
|
|
901
|
+
// * at the location of the mouse when the scroll wheel or gesture was applied.
|
|
902
|
+
// * @param transform The d3 zoom transformation information
|
|
903
|
+
// * @param x The x-position of the mouse when the scroll wheel or gesture is used
|
|
904
|
+
// * @param axis The axis being zoomed
|
|
905
|
+
// * @param range The current range for the axis being zoomed
|
|
906
|
+
// * @return The updated range and the new zoom factor
|
|
907
|
+
// */
|
|
908
|
+
// // export function calculateZoomFor<AR extends BaseAxisRange>(
|
|
909
|
+
// export function calculateZoomFor(
|
|
910
|
+
// transform: ZoomTransform,
|
|
911
|
+
// x: number,
|
|
912
|
+
// axis: ContinuousNumericAxis,
|
|
913
|
+
// range: ContinuousAxisRange,
|
|
914
|
+
// ): ZoomResult<ContinuousAxisRange> {
|
|
915
|
+
// const time = axis.generator.scale<ScaleLinear<number, number>>().invert(x);
|
|
916
|
+
// return {
|
|
917
|
+
// range: range.scale(transform.k, time),
|
|
918
|
+
// zoomFactor: transform.k
|
|
919
|
+
// } as ZoomResult<ContinuousAxisRange>
|
|
920
|
+
// }
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
|
|
924
|
+
* at the location of the mouse when the scroll wheel or gesture was applied, while ensure that
|
|
925
|
+
* the range (start, end) is contained within the constraint (min, max).
|
|
926
|
+
* @param transform The d3 zoom transformation information
|
|
927
|
+
* @param x The x-position of the mouse when the scroll wheel or gesture is used
|
|
928
|
+
* @param axis The axis being zoomed
|
|
929
|
+
* @param range The current range for the axis being zoomed
|
|
930
|
+
* @param constraint The minimum and maximum value the scaled range can have
|
|
931
|
+
* @return The updated range and the new zoom factor
|
|
932
|
+
*/
|
|
933
|
+
export function calculateConstrainedZoomFor(
|
|
934
|
+
transform: ZoomTransform,
|
|
935
|
+
x: number,
|
|
936
|
+
axis: ContinuousNumericAxis,
|
|
937
|
+
range: ContinuousAxisRange,
|
|
938
|
+
constraint: [min: number, max: number],
|
|
939
|
+
): ZoomResult<ContinuousAxisRange> {
|
|
940
|
+
const domainValue = axis.generator.scale<ScaleLinear<number, number>>().invert(x);
|
|
941
|
+
return {
|
|
942
|
+
range: range.constrainedScale(transform.k, domainValue, constraint),
|
|
943
|
+
zoomFactor: transform.k
|
|
944
|
+
} as ZoomResult<ContinuousAxisRange>
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Calculates the zoom for an ordinal axis.
|
|
949
|
+
* @param transform The d3 zoom transformation information
|
|
950
|
+
* @param x The x-position of the mouse when the scroll wheel or gesture is used
|
|
951
|
+
* @param range The current range for the axis being zoomed
|
|
952
|
+
* @param constraint The minimum and maximum value the scaled range can have
|
|
953
|
+
* @return A ZoomResult holding the updated range and the new zoom factor
|
|
954
|
+
*/
|
|
955
|
+
export function calculateOrdinalConstrainedZoomFor(
|
|
956
|
+
transform: ZoomTransform,
|
|
957
|
+
x: number,
|
|
958
|
+
range: OrdinalAxisRange,
|
|
959
|
+
constraint: [min: number, max: number],
|
|
960
|
+
): ZoomResult<OrdinalAxisRange> {
|
|
961
|
+
const updatedRange = range.constrainedScale(transform.k, x, constraint) as OrdinalAxisRange
|
|
962
|
+
const k = range.original.equals(updatedRange.original) ? 1 : transform.k
|
|
963
|
+
return {
|
|
964
|
+
range: updatedRange,
|
|
965
|
+
zoomFactor: k
|
|
966
|
+
} as ZoomResult<OrdinalAxisRange>
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/*
|
|
970
|
+
panning
|
|
971
|
+
*/
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Adjusts the range and updates the plot when the plot is dragged to the left or right
|
|
975
|
+
* @param delta The amount that the plot is dragged
|
|
976
|
+
* @param axis The axis being zoomed
|
|
977
|
+
* @param range The current range for the axis being zoomed
|
|
978
|
+
* @param [constrainToOriginalRange=false] When set to `true` then the pan requires that the axis
|
|
979
|
+
* range remains a subset of the origin axis range; when `false` the pan allows and range
|
|
980
|
+
* @return The updated range
|
|
981
|
+
*/
|
|
982
|
+
export function calculatePanFor(
|
|
983
|
+
delta: number,
|
|
984
|
+
axis: ContinuousNumericAxis,
|
|
985
|
+
range: ContinuousAxisRange,
|
|
986
|
+
constrainToOriginalRange: boolean = false
|
|
987
|
+
): ContinuousAxisRange {
|
|
988
|
+
const scale = axis.generator.scale<ScaleLinear<number, number>>()
|
|
989
|
+
const value = scale(range.current.start)
|
|
990
|
+
if (value !== undefined) {
|
|
991
|
+
const deltaValue = scale.invert(value + delta) - range.current.start
|
|
992
|
+
const constraint: [start: number, end: number] = constrainToOriginalRange ?
|
|
993
|
+
range.original.asTuple() :
|
|
994
|
+
[-Infinity, Infinity]
|
|
995
|
+
return range.translate(-deltaValue, constraint)
|
|
996
|
+
}
|
|
997
|
+
return range
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
export function calculateOrdinalPanFor(
|
|
1001
|
+
delta: number,
|
|
1002
|
+
range: OrdinalAxisRange,
|
|
1003
|
+
plotDimensions: Dimensions,
|
|
1004
|
+
constrainToOriginalRange: boolean = false
|
|
1005
|
+
): OrdinalAxisRange {
|
|
1006
|
+
const constraint: [start: number, end: number] = constrainToOriginalRange ?
|
|
1007
|
+
range.original.asTuple() :
|
|
1008
|
+
[-Infinity, Infinity]
|
|
1009
|
+
// only allow panning if the plot dimensions (i.e. [0, width]) is in the current axis range
|
|
1010
|
+
// todo deal with the fact that this could be a pan of the y-axis
|
|
1011
|
+
if (range.current.start + delta > 0 || range.current.end + delta < plotDimensions.width) {
|
|
1012
|
+
return range
|
|
1013
|
+
}
|
|
1014
|
+
return range.translate(delta, constraint) as OrdinalAxisRange
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/*
|
|
1018
|
+
|
|
1019
|
+
*/
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Accepts the series, the assignment of the series to axes, and the current x-axes state, and
|
|
1023
|
+
* returns an array of the distinct axis IDs that cover all the series in the plot.
|
|
1024
|
+
*
|
|
1025
|
+
* @param series The array of series
|
|
1026
|
+
* @param axisAssignments A map association a series name with its axis assignments
|
|
1027
|
+
* @param axesState The current axis state
|
|
1028
|
+
* @return an array of the distinct axes that cover all the series in the plot
|
|
1029
|
+
*/
|
|
1030
|
+
export function axesForSeriesGen<D, A extends BaseAxis>(
|
|
1031
|
+
series: Array<BaseSeries<D>>,
|
|
1032
|
+
axisAssignments: Map<string, AxesAssignment>,
|
|
1033
|
+
axesState: AxesState<A>
|
|
1034
|
+
): Array<string> {
|
|
1035
|
+
return series.map(srs => srs.name)
|
|
1036
|
+
// grab the x-axis assigned to the series, or use the default x-axis if not
|
|
1037
|
+
// assignment has been made
|
|
1038
|
+
.map(name => axisAssignments.get(name)?.xAxis || axesState.axisDefaultId().getOrElse(""))
|
|
1039
|
+
// de-dup the array of axis IDs so that we don't end up applying the pan or zoom
|
|
1040
|
+
// transformation more than once
|
|
1041
|
+
.reduce((accum: Array<string>, axisId: string) => {
|
|
1042
|
+
if (!accum.find(id => id === axisId)) {
|
|
1043
|
+
accum.push(axisId)
|
|
1044
|
+
}
|
|
1045
|
+
return accum
|
|
1046
|
+
}, [])
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Pans the axis range by the specified amount.
|
|
1051
|
+
* @param delta The pan amount in the axis specified for the series
|
|
1052
|
+
* @param axesForSeries The names of the axes of a dimension (x or y)
|
|
1053
|
+
* @param axesState The state for the axes of a dimension
|
|
1054
|
+
* @param ranges The current ranges for the axes of a dimension
|
|
1055
|
+
* @param setAxisRange Function for setting the new time-range for a specific axis
|
|
1056
|
+
* @param plotDimensions The current plot dimensions (width, height)
|
|
1057
|
+
* @param margin The plot margin
|
|
1058
|
+
* @param [constrainToOriginalRange=true] Optional argument, that when set to `true`, constrains the
|
|
1059
|
+
* axis range to remain in the origin axis range; when `false` the axis range is unconstrained
|
|
1060
|
+
*/
|
|
1061
|
+
function panAxes(
|
|
1062
|
+
delta: number,
|
|
1063
|
+
axesForSeries: Array<string>,
|
|
1064
|
+
axesState: AxesState<ContinuousNumericAxis>,
|
|
1065
|
+
ranges: Map<string, ContinuousAxisRange>,
|
|
1066
|
+
setAxisRange: (axisId: string, axisRange: AxisInterval) => void,
|
|
1067
|
+
plotDimensions: Dimensions,
|
|
1068
|
+
margin: Margin,
|
|
1069
|
+
constrainToOriginalRange: boolean = true
|
|
1070
|
+
): void {
|
|
1071
|
+
axesForSeries.forEach(axisId => {
|
|
1072
|
+
axesState.axisFor(axisId).ifPresent((axis) => {
|
|
1073
|
+
const currentRange = ranges.get(axisId)
|
|
1074
|
+
if (currentRange) {
|
|
1075
|
+
// calculate the change in the axis-range based on the pixel change from the drag event
|
|
1076
|
+
const range = calculatePanFor(delta, axis, currentRange, constrainToOriginalRange)
|
|
1077
|
+
|
|
1078
|
+
// update the time-range for the axis
|
|
1079
|
+
ranges.set(axisId, range)
|
|
1080
|
+
|
|
1081
|
+
setAxisRange(axisId, range.current)
|
|
1082
|
+
|
|
1083
|
+
// update the axis' time-range
|
|
1084
|
+
axis.update(range.current.copy(), plotDimensions, margin)
|
|
1085
|
+
}
|
|
1086
|
+
})
|
|
1087
|
+
})
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Pans the axis range by the specified amount.
|
|
1092
|
+
* @param delta The pan amount in the axis specified for the series
|
|
1093
|
+
* @param axesForSeries The names of the axes of a dimension (x or y)
|
|
1094
|
+
* @param axesState The state for the axes of a dimension
|
|
1095
|
+
* @param ranges The current ranges for the axes of a dimension
|
|
1096
|
+
* @param setAxisRange Function for setting the new time-range for a specific axis
|
|
1097
|
+
* @param plotDimensions The current plot dimensions (width, height)
|
|
1098
|
+
* @param margin The plot margin
|
|
1099
|
+
* @param [constrainToOriginalRange=false] Optional argument, that when set to `true`, constrains the
|
|
1100
|
+
* axis range to remain in the origin axis range; when `false` the axis range is unconstrained
|
|
1101
|
+
*/
|
|
1102
|
+
function ordinalPanAxes(
|
|
1103
|
+
delta: number,
|
|
1104
|
+
axesForSeries: Array<string>,
|
|
1105
|
+
axesState: AxesState<OrdinalStringAxis>,
|
|
1106
|
+
ranges: Map<string, OrdinalAxisRange>,
|
|
1107
|
+
setAxisRange: (axisId: string, axisRange: AxisInterval) => void,
|
|
1108
|
+
plotDimensions: Dimensions,
|
|
1109
|
+
margin: Margin,
|
|
1110
|
+
constrainToOriginalRange: boolean = false
|
|
1111
|
+
): void {
|
|
1112
|
+
axesForSeries.forEach(axisId => {
|
|
1113
|
+
axesState.axisFor(axisId).ifPresent(axis => {
|
|
1114
|
+
const currentRange = ranges.get(axisId)
|
|
1115
|
+
if (currentRange && axis) {
|
|
1116
|
+
// calculate the change in the axis-range based on the pixel change from the drag event
|
|
1117
|
+
const range = calculateOrdinalPanFor(delta, currentRange, plotDimensions, constrainToOriginalRange)
|
|
1118
|
+
|
|
1119
|
+
// update the time-range for the axis
|
|
1120
|
+
ranges.set(axisId, range)
|
|
1121
|
+
|
|
1122
|
+
setAxisRange(axisId, range.current)
|
|
1123
|
+
|
|
1124
|
+
// update the axis' time-range
|
|
1125
|
+
axis.update(range.current, range.original, plotDimensions, margin)
|
|
1126
|
+
}
|
|
1127
|
+
})
|
|
1128
|
+
})
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Higher-order function that generates a handler for pan events, given the distinct series IDs that cover all
|
|
1133
|
+
* the axes in the chart, the margin, axis-range update function, and the current state of the x-axes. This
|
|
1134
|
+
* function returns a handler function. And this handler function adjusts the time-range when the plot is dragged
|
|
1135
|
+
* to the left or right. After calling the handler function, the plot needs to be updated as well, and this is
|
|
1136
|
+
* left for the caller.
|
|
1137
|
+
*
|
|
1138
|
+
* Please note that the function generated by this function has side effects -- it updates the axes ranges.
|
|
1139
|
+
*
|
|
1140
|
+
* @param axesForSeries The distinct axes that cover all the series
|
|
1141
|
+
* @param margin The plot margin
|
|
1142
|
+
* @param setAxisRangeFor Function for setting the new axis-range for a specific axis
|
|
1143
|
+
* @param axesState The current state of the x-axes or y-axes
|
|
1144
|
+
* @param [constrainToOriginalRange=false] Optional argument, that when set to `true`, constrains the
|
|
1145
|
+
* axis range to remain in the origin axis range; when `false` the axis range is unconstrained
|
|
1146
|
+
* @return A handler function for pan events
|
|
1147
|
+
*/
|
|
1148
|
+
export function panHandler(
|
|
1149
|
+
axesForSeries: Array<string>,
|
|
1150
|
+
margin: Margin,
|
|
1151
|
+
setAxisRangeFor: (axisId: string, axisRange: AxisInterval) => void,
|
|
1152
|
+
axesState: AxesState<ContinuousNumericAxis>,
|
|
1153
|
+
constrainToOriginalRange: boolean = false
|
|
1154
|
+
): (
|
|
1155
|
+
x: number,
|
|
1156
|
+
plotDimensions: Dimensions,
|
|
1157
|
+
// series: Array<string>,
|
|
1158
|
+
ranges: Map<string, ContinuousAxisRange>,
|
|
1159
|
+
) => void {
|
|
1160
|
+
/**
|
|
1161
|
+
* Adjusts the time-range and updates the plot when the plot is dragged to the left or right
|
|
1162
|
+
* @param deltaX The amount that the plot is dragged
|
|
1163
|
+
* @param plotDimensions The dimensions of the plot
|
|
1164
|
+
* @param series An array of series names
|
|
1165
|
+
* @param ranges A map holding the axis ID and its associated time range
|
|
1166
|
+
*/
|
|
1167
|
+
return (delta: number, plotDimensions: Dimensions, ranges: Map<string, ContinuousAxisRange>) => {
|
|
1168
|
+
// return (delta: number, plotDimensions: Dimensions, series: Array<string>, ranges: Map<string, ContinuousAxisRange>) => {
|
|
1169
|
+
// run through the axis IDs, adjust their domain, and update the time-range set for that axis
|
|
1170
|
+
panAxes(delta, axesForSeries, axesState, ranges, setAxisRangeFor, plotDimensions, margin, constrainToOriginalRange)
|
|
1171
|
+
// hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Higher-order function that generates a handler for pan events, given the distinct series IDs that cover all
|
|
1177
|
+
* the axes in the chart, the margin, axis-range update function, and the current state of the x-axes. This
|
|
1178
|
+
* function returns a handler function. And this handler function adjusts the time-range when the plot is dragged
|
|
1179
|
+
* to the left or right. After calling the handler function, the plot needs to be updated as well, and this is
|
|
1180
|
+
* left for the caller.
|
|
1181
|
+
*
|
|
1182
|
+
* Please note that the function generated by this function has side effects -- it updates the axes ranges.
|
|
1183
|
+
*
|
|
1184
|
+
* @param axesForSeries The distinct axes that cover all the series
|
|
1185
|
+
* @param margin The plot margin
|
|
1186
|
+
* @param setAxisRangeFor Function for setting the new axis-range for a specific axis
|
|
1187
|
+
* @param axesState The current state of the x-axes or y-axes
|
|
1188
|
+
* @param [constrainToOriginalRange=false] Optional argument, that when set to `true`, constrains the
|
|
1189
|
+
* axis range to remain in the origin axis range; when `false` the axis range is unconstrained
|
|
1190
|
+
* @return A handler function for pan events
|
|
1191
|
+
*/
|
|
1192
|
+
export function ordinalPanHandler(
|
|
1193
|
+
axesForSeries: Array<string>,
|
|
1194
|
+
margin: Margin,
|
|
1195
|
+
setAxisRangeFor: (axisId: string, axisRange: AxisInterval) => void,
|
|
1196
|
+
axesState: AxesState<OrdinalStringAxis>,
|
|
1197
|
+
constrainToOriginalRange: boolean = false
|
|
1198
|
+
): (
|
|
1199
|
+
x: number,
|
|
1200
|
+
plotDimensions: Dimensions,
|
|
1201
|
+
series: Array<string>,
|
|
1202
|
+
ranges: Map<string, OrdinalAxisRange>,
|
|
1203
|
+
) => void {
|
|
1204
|
+
/**
|
|
1205
|
+
* Adjusts the time-range and updates the plot when the plot is dragged to the left or right
|
|
1206
|
+
* @param deltaX The amount that the plot is dragged
|
|
1207
|
+
* @param plotDimensions The dimensions of the plot
|
|
1208
|
+
* @param series An array of series names
|
|
1209
|
+
* @param ranges A map holding the axis ID and its associated time range
|
|
1210
|
+
*/
|
|
1211
|
+
return (delta: number, plotDimensions: Dimensions, series: Array<string>, ranges: Map<string, OrdinalAxisRange>) => {
|
|
1212
|
+
// run through the axis IDs, adjust their domain, and update the time-range set for that axis
|
|
1213
|
+
ordinalPanAxes(delta, axesForSeries, axesState, ranges, setAxisRangeFor, plotDimensions, margin, constrainToOriginalRange)
|
|
1214
|
+
// hey, don't forget to update the plot with the new ranges in the code calling this... :)
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Higher-order function that generates a handler for pan events, given the distinct series IDs that cover all
|
|
1220
|
+
* the axes in the chart, the margin, time-range update function, and the current state of the x-axes. This
|
|
1221
|
+
* function returns a handler function. And this handler function adjusts the time-range when the plot is dragged
|
|
1222
|
+
* to the left or right. After calling the handler function, the plot needs to be updated as well, and this is
|
|
1223
|
+
* left for the caller.
|
|
1224
|
+
*
|
|
1225
|
+
* Please note that the function generated by this function has side effects -- it updates the axes ranges.
|
|
1226
|
+
*
|
|
1227
|
+
* @param xAxesForSeries The distinct x-axes that cover all the series
|
|
1228
|
+
* @param yAxesForSeries The distinct y-axes that cover all the series
|
|
1229
|
+
* @param margin The plot margin
|
|
1230
|
+
* @param setAxisRange Function for setting the new time-range for a specific axis
|
|
1231
|
+
* @param xAxesState The current state of the x-axes
|
|
1232
|
+
* @param yAxesState The current state of the y-axes
|
|
1233
|
+
* @param [constrainToOriginalRange=true] Optional argument, that when set to `true`, constrains the
|
|
1234
|
+
* axis range to remain in the origin axis range; when `false` the axis range is unconstrained
|
|
1235
|
+
* @return A handler function for pan events
|
|
1236
|
+
*/
|
|
1237
|
+
export function panHandler2D(
|
|
1238
|
+
xAxesForSeries: Array<string>,
|
|
1239
|
+
yAxesForSeries: Array<string>,
|
|
1240
|
+
margin: Margin,
|
|
1241
|
+
setAxisRange: (axisId: string, axisRange: AxisInterval) => void,
|
|
1242
|
+
xAxesState: AxesState<ContinuousNumericAxis>,
|
|
1243
|
+
yAxesState: AxesState<ContinuousNumericAxis>,
|
|
1244
|
+
constrainToOriginalRange: boolean = true
|
|
1245
|
+
): (
|
|
1246
|
+
x: number,
|
|
1247
|
+
y: number,
|
|
1248
|
+
plotDimensions: Dimensions,
|
|
1249
|
+
series: Array<string>,
|
|
1250
|
+
xRanges: Map<string, ContinuousAxisRange>,
|
|
1251
|
+
yRanges: Map<string, ContinuousAxisRange>,
|
|
1252
|
+
) => void {
|
|
1253
|
+
/**
|
|
1254
|
+
* Adjusts the time-range and updates the plot when the plot is dragged to the left or right
|
|
1255
|
+
* @param deltaX The amount that the plot is dragged
|
|
1256
|
+
* @param plotDimensions The dimensions of the plot
|
|
1257
|
+
* @param series An array of series names
|
|
1258
|
+
* @param ranges A map holding the axis ID and its associated time range
|
|
1259
|
+
*/
|
|
1260
|
+
return (deltaX, deltaY, plotDimensions, series, xRanges, yRanges) => {
|
|
1261
|
+
// run through the x- and y-axes and update them by delta, within the original bounds
|
|
1262
|
+
panAxes(deltaX, xAxesForSeries, xAxesState, xRanges, setAxisRange, plotDimensions, margin, constrainToOriginalRange)
|
|
1263
|
+
panAxes(deltaY, yAxesForSeries, yAxesState, yRanges, setAxisRange, plotDimensions, margin, constrainToOriginalRange)
|
|
1264
|
+
// hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Calculates the zoom for the specified axis and updates the axis and the axis ranges
|
|
1270
|
+
* @param value The x- or y-coordinate of the mouse
|
|
1271
|
+
* @param axisId
|
|
1272
|
+
* @param margin The plot margin
|
|
1273
|
+
* @param setRangeFor Function for setting the new time-range for a specific axis * @param scaleExtent The smallest and largest scale factors allowed
|
|
1274
|
+
* @param axesState
|
|
1275
|
+
* @param ranges A map associating axis IDs with axis ranges
|
|
1276
|
+
* @param scaleExtent The smallest and largest scale factors allowed
|
|
1277
|
+
* @param transform The d3 zoom transformation information
|
|
1278
|
+
* @param plotDimensions The dimensions of the plot
|
|
1279
|
+
*/
|
|
1280
|
+
function calcZoomAndUpdate(
|
|
1281
|
+
value: number,
|
|
1282
|
+
axisId: string,
|
|
1283
|
+
margin: Margin,
|
|
1284
|
+
setRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
1285
|
+
axesState: AxesState<ContinuousNumericAxis>,
|
|
1286
|
+
ranges: Map<string, ContinuousAxisRange>,
|
|
1287
|
+
scaleExtent: [min: number, max: number],
|
|
1288
|
+
transform: ZoomTransform,
|
|
1289
|
+
plotDimensions: Dimensions,
|
|
1290
|
+
): void {
|
|
1291
|
+
const [, zoomMax] = scaleExtent
|
|
1292
|
+
|
|
1293
|
+
axesState.axisFor(axisId).ifPresent(axis => {
|
|
1294
|
+
const range = ranges.get(axisId)
|
|
1295
|
+
if (range && axis) {
|
|
1296
|
+
|
|
1297
|
+
// calculate the constraint for the zoom
|
|
1298
|
+
const constraint: [number, number] = isFinite(zoomMax) ?
|
|
1299
|
+
[range.original.start * zoomMax, range.original.end * zoomMax] :
|
|
1300
|
+
[0, Infinity]
|
|
1301
|
+
|
|
1302
|
+
const zoom = calculateConstrainedZoomFor(transform, value, axis, range, constraint)
|
|
1303
|
+
|
|
1304
|
+
// update the axis range
|
|
1305
|
+
ranges.set(axisId, zoom.range)
|
|
1306
|
+
|
|
1307
|
+
setRangeFor(axisId, zoom.range.current)
|
|
1308
|
+
|
|
1309
|
+
// update the axis' range
|
|
1310
|
+
axis.update(zoom.range.current, plotDimensions, margin)
|
|
1311
|
+
}
|
|
1312
|
+
})
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function calcOrdinalZoomAndUpdate(
|
|
1316
|
+
value: number,
|
|
1317
|
+
axisId: string,
|
|
1318
|
+
margin: Margin,
|
|
1319
|
+
setRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
1320
|
+
setOriginalRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
1321
|
+
axesState: AxesState<OrdinalStringAxis>,
|
|
1322
|
+
ranges: Map<string, OrdinalAxisRange>,
|
|
1323
|
+
scaleExtent: [min: number, max: number],
|
|
1324
|
+
transform: ZoomTransform,
|
|
1325
|
+
plotDimensions: Dimensions,
|
|
1326
|
+
): void {
|
|
1327
|
+
const [, zoomMax] = scaleExtent
|
|
1328
|
+
|
|
1329
|
+
axesState.axisFor(axisId).ifPresent(axis => {
|
|
1330
|
+
const range = ranges.get(axisId)
|
|
1331
|
+
if (range && axis) {
|
|
1332
|
+
// calculate the constraint for the zoom
|
|
1333
|
+
// const [originalStart, originalEnd] = range.original
|
|
1334
|
+
const constraint: [number, number] = isFinite(zoomMax) ?
|
|
1335
|
+
[range.original.start * zoomMax, range.original.end * zoomMax] :
|
|
1336
|
+
[range.original.start, range.original.end]
|
|
1337
|
+
|
|
1338
|
+
const zoom = calculateOrdinalConstrainedZoomFor(transform, value, range, constraint)
|
|
1339
|
+
|
|
1340
|
+
// update the axis range
|
|
1341
|
+
ranges.set(axisId, zoom.range)
|
|
1342
|
+
|
|
1343
|
+
setRangeFor(axisId, zoom.range.current)
|
|
1344
|
+
const origRange = AxisInterval.from(0, plotDimensions.width)
|
|
1345
|
+
setOriginalRangeFor(axisId, origRange)
|
|
1346
|
+
|
|
1347
|
+
// update the axis' range
|
|
1348
|
+
axis.update(zoom.range.current, origRange, plotDimensions, margin)
|
|
1349
|
+
}
|
|
1350
|
+
})
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
/**
|
|
1354
|
+
* Higher-order function that generates a handler for zoom events, given the distinct series IDs that cover all
|
|
1355
|
+
* the axes in the chart, the margin, range update function, and the current state of the x- or y-axes. This
|
|
1356
|
+
* function returns a handler function. And this handler function adjusts the range when the plot is zoomed.
|
|
1357
|
+
* After calling the handler function, the plot needs to be updated as well, and this is left for the caller.
|
|
1358
|
+
*
|
|
1359
|
+
* Please note that the function generated by this function has side effects -- it updates the axes ranges.
|
|
1360
|
+
*
|
|
1361
|
+
* @param axesForSeries The distinct axes that cover all the series
|
|
1362
|
+
* @param margin The plot margin
|
|
1363
|
+
* @param setRangeFor Function for setting the new time-range for a specific axis
|
|
1364
|
+
* @param axesState The current state of the x- or y-axes
|
|
1365
|
+
* @param scaleExtent The minimum and maximum allowed scale factors
|
|
1366
|
+
* @return A handler function for pan events
|
|
1367
|
+
*/
|
|
1368
|
+
export function continuousAxisZoomHandler(
|
|
1369
|
+
axesForSeries: Array<string>,
|
|
1370
|
+
margin: Margin,
|
|
1371
|
+
setRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
1372
|
+
axesState: AxesState<ContinuousNumericAxis>,
|
|
1373
|
+
scaleExtent: [min: number, max: number] = [0, Infinity],
|
|
1374
|
+
): (
|
|
1375
|
+
transform: ZoomTransform,
|
|
1376
|
+
x: number,
|
|
1377
|
+
plotDimensions: Dimensions,
|
|
1378
|
+
ranges: Map<string, ContinuousAxisRange>,
|
|
1379
|
+
) => void {
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
|
|
1383
|
+
* at the location of the mouse when the scroll wheel or gesture was applied.
|
|
1384
|
+
* @param transform The d3 zoom transformation information
|
|
1385
|
+
* @param x The x-position of the mouse when the scroll wheel or gesture is used
|
|
1386
|
+
* @param plotDimensions The dimensions of the plot
|
|
1387
|
+
* @param ranges A map holding the axis ID and its associated time-range
|
|
1388
|
+
*/
|
|
1389
|
+
return (transform, x, plotDimensions, ranges) => {
|
|
1390
|
+
// run through the axis IDs, adjust their domain, and update the time-range set for that axis
|
|
1391
|
+
axesForSeries.forEach(axisId =>
|
|
1392
|
+
calcZoomAndUpdate(x, axisId, margin, setRangeFor, axesState, ranges, scaleExtent, transform, plotDimensions)
|
|
1393
|
+
)
|
|
1394
|
+
// hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* Higher-order function that generates a handler for zoom events, given the distinct series IDs that cover all
|
|
1400
|
+
* the axes in the chart, the margin, range update function, and the current state of the x- or y-axes. This
|
|
1401
|
+
* function returns a handler function. And this handler function adjusts the range when the plot is zoomed.
|
|
1402
|
+
* After calling the handler function, the plot needs to be updated as well, and this is left for the caller.
|
|
1403
|
+
*
|
|
1404
|
+
* Please note that the function generated by this function has side effects -- it updates the axes ranges.
|
|
1405
|
+
*
|
|
1406
|
+
* @param axesForSeries The distinct axes that cover all the series
|
|
1407
|
+
* @param margin The plot margin
|
|
1408
|
+
* @param setRangeFor Function for setting the new time-range for a specific axis
|
|
1409
|
+
* @param setOriginalRangeFor Function for setting the new original range for a specific axis
|
|
1410
|
+
* @param axesState The current state of the x- or y-axes
|
|
1411
|
+
* @param scaleExtent The minimum and maximum allowed scale factors
|
|
1412
|
+
* @return A handler function for pan events
|
|
1413
|
+
*/
|
|
1414
|
+
export function ordinalAxisZoomHandler(
|
|
1415
|
+
axesForSeries: Array<string>,
|
|
1416
|
+
margin: Margin,
|
|
1417
|
+
setRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
1418
|
+
setOriginalRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
1419
|
+
axesState: AxesState<OrdinalStringAxis>,
|
|
1420
|
+
scaleExtent: [min: number, max: number] = [0, Infinity],
|
|
1421
|
+
): (
|
|
1422
|
+
transform: ZoomTransform,
|
|
1423
|
+
x: number,
|
|
1424
|
+
plotDimensions: Dimensions,
|
|
1425
|
+
ranges: Map<string, OrdinalAxisRange>,
|
|
1426
|
+
) => void {
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
|
|
1430
|
+
* at the location of the mouse when the scroll wheel or gesture was applied.
|
|
1431
|
+
* @param transform The d3 zoom transformation information
|
|
1432
|
+
* @param x The x-position of the mouse when the scroll wheel or gesture is used
|
|
1433
|
+
* @param plotDimensions The dimensions of the plot
|
|
1434
|
+
* @param ranges A map holding the axis ID and its associated time-range
|
|
1435
|
+
*/
|
|
1436
|
+
return (transform, x, plotDimensions, ranges) => {
|
|
1437
|
+
// run through the axis IDs, adjust their domain, and update the time-range set for that axis
|
|
1438
|
+
axesForSeries.forEach(axisId =>
|
|
1439
|
+
calcOrdinalZoomAndUpdate(x, axisId, margin, setRangeFor, setOriginalRangeFor, axesState, ranges, scaleExtent, transform, plotDimensions)
|
|
1440
|
+
)
|
|
1441
|
+
// hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* Higher-order function that generates a handler for zoom events, given the distinct series IDs that cover all
|
|
1447
|
+
* the axes in the chart, the margin, range update function, and the current state of the x- or y-axes. This
|
|
1448
|
+
* function returns a handler function. And this handler function adjusts the time-range when the plot is zoomed.
|
|
1449
|
+
* After calling the handler function, the plot needs to be updated as well, and this is left for the caller.
|
|
1450
|
+
*
|
|
1451
|
+
* Please note that the function generated by this function has side effects -- it updates the axes ranges.
|
|
1452
|
+
*
|
|
1453
|
+
* @param xAxesForSeries The distinct x-axes that cover all the series
|
|
1454
|
+
* @param yAxesForSeries The distinct y-axes that cover all the series
|
|
1455
|
+
* @param margin The plot margin
|
|
1456
|
+
* @param setRangeFor Function for setting the new time-range for a specific axis
|
|
1457
|
+
* @param xAxesState The current state of the x-axes
|
|
1458
|
+
* @param yAxesState The current state of the y-axes
|
|
1459
|
+
* @param scaleExtent The smallest and largest scale factors allowed
|
|
1460
|
+
* @return A handler function for pan events
|
|
1461
|
+
*/
|
|
1462
|
+
export function axesZoomHandler(
|
|
1463
|
+
xAxesForSeries: Array<string>,
|
|
1464
|
+
yAxesForSeries: Array<string>,
|
|
1465
|
+
margin: Margin,
|
|
1466
|
+
setRangeFor: (axisId: string, range: AxisInterval) => void,
|
|
1467
|
+
xAxesState: AxesState<ContinuousNumericAxis>,
|
|
1468
|
+
yAxesState: AxesState<ContinuousNumericAxis>,
|
|
1469
|
+
scaleExtent: [min: number, max: number],
|
|
1470
|
+
): (
|
|
1471
|
+
transform: ZoomTransform,
|
|
1472
|
+
mousePosition: [x: number, y: number],
|
|
1473
|
+
plotDimensions: Dimensions,
|
|
1474
|
+
xRanges: Map<string, ContinuousAxisRange>,
|
|
1475
|
+
yRanges: Map<string, ContinuousAxisRange>,
|
|
1476
|
+
) => void {
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Called when the user uses the scroll wheel (or scroll gesture) to zoom in or out. Zooms in/out
|
|
1480
|
+
* at the location of the mouse when the scroll wheel or gesture was applied.
|
|
1481
|
+
* @param transform The d3 zoom transformation information
|
|
1482
|
+
* @param mousePosistion The position of the mouse when the scroll wheel or gesture is used
|
|
1483
|
+
* @param plotDimensions The dimensions of the plot
|
|
1484
|
+
* @param ranges A map holding the axis ID and its associated time-range
|
|
1485
|
+
*/
|
|
1486
|
+
return (transform, mousePosition, plotDimensions, xRanges, yRanges) => {
|
|
1487
|
+
// run through the axis IDs, adjust their domain, and update the time-range set for that axis
|
|
1488
|
+
const [x, y] = mousePosition
|
|
1489
|
+
xAxesForSeries.forEach(id =>
|
|
1490
|
+
calcZoomAndUpdate(x, id, margin, setRangeFor, xAxesState, xRanges, scaleExtent, transform, plotDimensions)
|
|
1491
|
+
)
|
|
1492
|
+
yAxesForSeries.forEach(id =>
|
|
1493
|
+
calcZoomAndUpdate(y, id, margin, setRangeFor, yAxesState, yRanges, scaleExtent, transform, plotDimensions)
|
|
1494
|
+
)
|
|
1495
|
+
// hey, don't forget to update the plot with the new time-ranges in the code calling this... :)
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Calculates the axis-ranges for each of the continuous numeric axes in the map
|
|
1501
|
+
* @param axes The map containing the axes and their associated IDs
|
|
1502
|
+
* @return a map associating the axis IDs to their continuous axis-range
|
|
1503
|
+
*/
|
|
1504
|
+
export function continuousAxisRanges(axes: Map<string, ContinuousNumericAxis>): Map<string, ContinuousAxisRange> {
|
|
1505
|
+
return continuousRange(axes)
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* Calculates the axis-ranges for each of the ordinal axes in the map
|
|
1510
|
+
* @param axes The map containing the axes and their associated IDs
|
|
1511
|
+
* @param originalRange The original range of the axis
|
|
1512
|
+
* @return a map associating the axis IDs to their ordinal axis-range
|
|
1513
|
+
*/
|
|
1514
|
+
export function ordinalAxisRanges(axes: Map<string, OrdinalStringAxis>, originalRange: AxisInterval): Map<string, OrdinalAxisRange> {
|
|
1515
|
+
return ordinalRange(axes, originalRange)
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Calculates the axis interval (start, end) for each of the axis
|
|
1520
|
+
* @param axes The axes representing the time
|
|
1521
|
+
* @return A map associating each axis with a (start, end) interval
|
|
1522
|
+
*/
|
|
1523
|
+
export function continuousAxisIntervals(axes: Map<string, ContinuousNumericAxis>): Map<string, AxisInterval> {
|
|
1524
|
+
return new Map(Array.from(axes.entries())
|
|
1525
|
+
.map(([id, axis]) => {
|
|
1526
|
+
const [start, end] = axis.scale.domain()
|
|
1527
|
+
return [id, AxisInterval.from(start, end)] as [string, AxisInterval]
|
|
1528
|
+
}))
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* Calculates the axis interval (start, end) for each of the axis
|
|
1533
|
+
* @param axes The axes representing the time
|
|
1534
|
+
* @return A map associating each axis with a (start, end) interval
|
|
1535
|
+
*/
|
|
1536
|
+
export function ordinalAxisIntervals(axes: Map<string, OrdinalStringAxis>): Map<string, {interval: AxisInterval, categories: Array<string>}> {
|
|
1537
|
+
return new Map(Array.from(axes.entries())
|
|
1538
|
+
.map(([id, axis]) => {
|
|
1539
|
+
const [start, end] = axis.scale.range()
|
|
1540
|
+
return [id, {interval: AxisInterval.from(start, end), categories: axis.scale.domain()}]
|
|
1541
|
+
}))
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* Returns the bounds on the specified continuous numeric axes
|
|
1546
|
+
* @param axes A map associating an axis ID with a {@link ContinuousNumericAxis}
|
|
1547
|
+
* @return A map associating each specified axis ID with the interval covered (bounds) by the axis
|
|
1548
|
+
*/
|
|
1549
|
+
export function continuousRange(axes: Map<string, ContinuousNumericAxis>): Map<string, ContinuousAxisRange> {
|
|
1550
|
+
return new Map(Array.from(axes.entries())
|
|
1551
|
+
.map(([id, axis]) => {
|
|
1552
|
+
const [start, end] = axis.scale.domain()
|
|
1553
|
+
return [id, ContinuousAxisRange.from(start, end)]
|
|
1554
|
+
}))
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* Returns the bounds on the specified continuous numeric axes
|
|
1559
|
+
* @param axes A map associating an axis ID with a {@link OrdinalStringAxis}
|
|
1560
|
+
* @param originalRange The original range of the axis
|
|
1561
|
+
* @return A map associating each specified axis ID with the interval covered (bounds) by the axis
|
|
1562
|
+
*/
|
|
1563
|
+
export function ordinalRange(axes: Map<string, OrdinalStringAxis>, originalRange: AxisInterval): Map<string, OrdinalAxisRange> {
|
|
1564
|
+
return new Map(Array.from(axes.entries())
|
|
1565
|
+
.map(([id, _]) =>
|
|
1566
|
+
[id, OrdinalAxisRange.from(originalRange.start, originalRange.end)]
|
|
1567
|
+
)
|
|
1568
|
+
)
|
|
1569
|
+
}
|