stream-charts 1.0.0 → 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.
Files changed (271) hide show
  1. package/README.md +758 -345
  2. package/changes.md +10 -0
  3. package/dist/src/app/charts/Chart.d.ts +116 -87
  4. package/dist/src/app/charts/Chart.js +96 -101
  5. package/dist/src/app/charts/Chart.js.map +1 -1
  6. package/dist/src/app/charts/axes/AxesState.d.ts +64 -0
  7. package/dist/src/app/charts/axes/AxesState.js +97 -0
  8. package/dist/src/app/charts/axes/AxesState.js.map +1 -0
  9. package/dist/src/app/charts/axes/AxesState.test.js +30 -0
  10. package/dist/src/app/charts/axes/AxesState.test.js.map +1 -0
  11. package/dist/src/app/charts/axes/AxisInterval.d.ts +92 -0
  12. package/dist/src/app/charts/axes/AxisInterval.js +126 -0
  13. package/dist/src/app/charts/axes/AxisInterval.js.map +1 -0
  14. package/dist/src/app/charts/axes/AxisInterval.test.js +120 -0
  15. package/dist/src/app/charts/axes/AxisInterval.test.js.map +1 -0
  16. package/dist/src/app/charts/axes/BaseAxisRange.d.ts +83 -0
  17. package/dist/src/app/charts/axes/BaseAxisRange.js +58 -0
  18. package/dist/src/app/charts/axes/BaseAxisRange.js.map +1 -0
  19. package/dist/src/app/charts/axes/ContinuousAxis.d.ts +22 -0
  20. package/dist/src/app/charts/axes/ContinuousAxis.js +96 -0
  21. package/dist/src/app/charts/axes/ContinuousAxis.js.map +1 -0
  22. package/dist/src/app/charts/axes/ContinuousAxisRange.d.ts +59 -0
  23. package/dist/src/app/charts/axes/ContinuousAxisRange.js +84 -0
  24. package/dist/src/app/charts/axes/ContinuousAxisRange.js.map +1 -0
  25. package/dist/src/app/charts/axes/ContinuousAxisRange.test.js +47 -0
  26. package/dist/src/app/charts/axes/ContinuousAxisRange.test.js.map +1 -0
  27. package/dist/src/app/charts/axes/OrdinalAxis.d.ts +23 -0
  28. package/dist/src/app/charts/axes/OrdinalAxis.js +124 -0
  29. package/dist/src/app/charts/axes/OrdinalAxis.js.map +1 -0
  30. package/dist/src/app/charts/axes/OrdinalAxisRange.d.ts +69 -0
  31. package/dist/src/app/charts/axes/OrdinalAxisRange.js +100 -0
  32. package/dist/src/app/charts/axes/OrdinalAxisRange.js.map +1 -0
  33. package/dist/src/app/charts/axes/OrdinalAxisRange.test.js +48 -0
  34. package/dist/src/app/charts/axes/OrdinalAxisRange.test.js.map +1 -0
  35. package/dist/src/app/charts/axes/axes.d.ts +391 -0
  36. package/dist/src/app/charts/axes/axes.js +1046 -0
  37. package/dist/src/app/charts/axes/axes.js.map +1 -0
  38. package/dist/src/app/charts/d3types.d.ts +10 -10
  39. package/dist/src/app/charts/filters/regexFilter.d.ts +9 -0
  40. package/dist/src/app/charts/filters/regexFilter.js +18 -0
  41. package/dist/src/app/charts/filters/regexFilter.js.map +1 -0
  42. package/dist/src/app/charts/filters/regexFilter.test.d.ts +1 -0
  43. package/dist/src/app/charts/filters/regexFilter.test.js +11 -0
  44. package/dist/src/app/charts/filters/regexFilter.test.js.map +1 -0
  45. package/dist/src/app/charts/hooks/useAxes.d.ts +144 -0
  46. package/dist/src/app/charts/hooks/useAxes.js +187 -0
  47. package/dist/src/app/charts/hooks/useAxes.js.map +1 -0
  48. package/dist/src/app/charts/hooks/useChart.d.ts +30 -201
  49. package/dist/src/app/charts/hooks/useChart.js +33 -134
  50. package/dist/src/app/charts/hooks/useChart.js.map +1 -1
  51. package/dist/src/app/charts/hooks/useDataObservable.d.ts +74 -0
  52. package/dist/src/app/charts/hooks/useDataObservable.js +76 -0
  53. package/dist/src/app/charts/hooks/useDataObservable.js.map +1 -0
  54. package/dist/src/app/charts/hooks/useInitialData.d.ts +38 -0
  55. package/dist/src/app/charts/hooks/useInitialData.js +29 -0
  56. package/dist/src/app/charts/hooks/useInitialData.js.map +1 -0
  57. package/dist/src/app/charts/hooks/useMouse.d.ts +53 -0
  58. package/dist/src/app/charts/hooks/useMouse.js +65 -0
  59. package/dist/src/app/charts/hooks/useMouse.js.map +1 -0
  60. package/dist/src/app/charts/hooks/usePlotDimensions.d.ts +34 -0
  61. package/dist/src/app/charts/hooks/usePlotDimensions.js +73 -0
  62. package/dist/src/app/charts/hooks/usePlotDimensions.js.map +1 -0
  63. package/dist/src/app/charts/hooks/useTooltip.d.ts +64 -0
  64. package/dist/src/app/charts/hooks/useTooltip.js +52 -0
  65. package/dist/src/app/charts/hooks/useTooltip.js.map +1 -0
  66. package/dist/src/app/charts/observables/ChartData.d.ts +23 -0
  67. package/dist/src/app/charts/observables/ChartData.js +20 -0
  68. package/dist/src/app/charts/observables/ChartData.js.map +1 -0
  69. package/dist/src/app/charts/observables/iterates.d.ts +52 -0
  70. package/dist/src/app/charts/observables/iterates.js +121 -0
  71. package/dist/src/app/charts/observables/iterates.js.map +1 -0
  72. package/dist/src/app/charts/observables/iterates.test.d.ts +1 -0
  73. package/dist/src/app/charts/observables/iterates.test.js +180 -0
  74. package/dist/src/app/charts/observables/iterates.test.js.map +1 -0
  75. package/dist/src/app/charts/observables/ordinals.d.ts +75 -0
  76. package/dist/src/app/charts/observables/ordinals.js +141 -0
  77. package/dist/src/app/charts/observables/ordinals.js.map +1 -0
  78. package/dist/src/app/charts/observables/ordinals.test.d.ts +1 -0
  79. package/dist/src/app/charts/observables/ordinals.test.js +102 -0
  80. package/dist/src/app/charts/observables/ordinals.test.js.map +1 -0
  81. package/dist/src/app/charts/plots/BarPlot.d.ts +81 -0
  82. package/dist/src/app/charts/plots/BarPlot.js +557 -0
  83. package/dist/src/app/charts/plots/BarPlot.js.map +1 -0
  84. package/dist/src/app/charts/plots/PoincarePlot.d.ts +78 -0
  85. package/dist/src/app/charts/plots/PoincarePlot.js +504 -0
  86. package/dist/src/app/charts/plots/PoincarePlot.js.map +1 -0
  87. package/dist/src/app/charts/{RasterPlot.d.ts → plots/RasterPlot.d.ts} +1 -1
  88. package/dist/src/app/charts/{RasterPlot.js → plots/RasterPlot.js} +152 -133
  89. package/dist/src/app/charts/plots/RasterPlot.js.map +1 -0
  90. package/dist/src/app/charts/{ScatterPlot.d.ts → plots/ScatterPlot.d.ts} +10 -8
  91. package/dist/src/app/charts/plots/ScatterPlot.js +379 -0
  92. package/dist/src/app/charts/plots/ScatterPlot.js.map +1 -0
  93. package/dist/src/app/charts/{plot.d.ts → plots/plot.d.ts} +12 -14
  94. package/dist/src/app/charts/{plot.js → plots/plot.js} +19 -17
  95. package/dist/src/app/charts/plots/plot.js.map +1 -0
  96. package/dist/src/app/charts/series/baseSeries.d.ts +38 -0
  97. package/dist/src/app/charts/series/baseSeries.js +36 -0
  98. package/dist/src/app/charts/series/baseSeries.js.map +1 -0
  99. package/dist/src/app/charts/series/datumSeries.test.d.ts +1 -0
  100. package/dist/src/app/charts/series/datumSeries.test.js +50 -0
  101. package/dist/src/app/charts/series/datumSeries.test.js.map +1 -0
  102. package/dist/src/app/charts/series/iterateSeries.d.ts +42 -0
  103. package/dist/src/app/charts/series/iterateSeries.js +34 -0
  104. package/dist/src/app/charts/series/iterateSeries.js.map +1 -0
  105. package/dist/src/app/charts/series/ordinalSeries.d.ts +54 -0
  106. package/dist/src/app/charts/series/ordinalSeries.js +98 -0
  107. package/dist/src/app/charts/series/ordinalSeries.js.map +1 -0
  108. package/dist/src/app/charts/series/timeSeries.d.ts +30 -0
  109. package/dist/src/app/charts/series/timeSeries.js +19 -0
  110. package/dist/src/app/charts/series/timeSeries.js.map +1 -0
  111. package/dist/src/app/charts/{chartData.d.ts → series/timeSeriesChartData.d.ts} +11 -6
  112. package/dist/src/app/charts/series/timeSeriesChartData.js +32 -0
  113. package/dist/src/app/charts/series/timeSeriesChartData.js.map +1 -0
  114. package/dist/src/app/charts/styling/barPlotStyle.d.ts +41 -0
  115. package/dist/src/app/charts/styling/barPlotStyle.js +84 -0
  116. package/dist/src/app/charts/styling/barPlotStyle.js.map +1 -0
  117. package/dist/src/app/charts/{margins.d.ts → styling/margins.d.ts} +7 -5
  118. package/dist/src/app/charts/{margins.js → styling/margins.js} +19 -9
  119. package/dist/src/app/charts/styling/margins.js.map +1 -0
  120. package/dist/src/app/charts/styling/margins.test.d.ts +1 -0
  121. package/dist/src/app/charts/styling/margins.test.js +19 -0
  122. package/dist/src/app/charts/styling/margins.test.js.map +1 -0
  123. package/dist/src/app/charts/styling/svgStyle.d.ts +76 -0
  124. package/dist/src/app/charts/styling/svgStyle.js +84 -0
  125. package/dist/src/app/charts/styling/svgStyle.js.map +1 -0
  126. package/dist/src/app/charts/subscriptions/subscription.test.d.ts +1 -0
  127. package/dist/src/app/charts/subscriptions/subscription.test.js +395 -0
  128. package/dist/src/app/charts/subscriptions/subscription.test.js.map +1 -0
  129. package/dist/src/app/charts/subscriptions/subscriptions.d.ts +100 -0
  130. package/dist/src/app/charts/subscriptions/subscriptions.js +449 -0
  131. package/dist/src/app/charts/subscriptions/subscriptions.js.map +1 -0
  132. package/dist/src/app/charts/tooltips/BarPlotTooltipContent.d.ts +90 -0
  133. package/dist/src/app/charts/tooltips/BarPlotTooltipContent.js +302 -0
  134. package/dist/src/app/charts/tooltips/BarPlotTooltipContent.js.map +1 -0
  135. package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.d.ts +100 -0
  136. package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.js +190 -0
  137. package/dist/src/app/charts/tooltips/PoincarePlotTooltipContent.js.map +1 -0
  138. package/dist/src/app/charts/{RasterPlotTooltipContent.js → tooltips/RasterPlotTooltipContent.js} +40 -44
  139. package/dist/src/app/charts/tooltips/RasterPlotTooltipContent.js.map +1 -0
  140. package/dist/src/app/charts/tooltips/ScatterPlotTooltipContent.js +154 -0
  141. package/dist/src/app/charts/tooltips/ScatterPlotTooltipContent.js.map +1 -0
  142. package/dist/src/app/charts/{Tooltip.d.ts → tooltips/Tooltip.d.ts} +4 -2
  143. package/dist/src/app/charts/{Tooltip.js → tooltips/Tooltip.js} +23 -28
  144. package/dist/src/app/charts/tooltips/Tooltip.js.map +1 -0
  145. package/dist/src/app/charts/{tooltipUtils.d.ts → tooltips/tooltipUtils.d.ts} +17 -11
  146. package/dist/src/app/charts/{tooltipUtils.js → tooltips/tooltipUtils.js} +52 -25
  147. package/dist/src/app/charts/tooltips/tooltipUtils.js.map +1 -0
  148. package/dist/src/app/charts/{Tracker.d.ts → trackers/Tracker.d.ts} +6 -5
  149. package/dist/src/app/charts/trackers/Tracker.js +60 -0
  150. package/dist/src/app/charts/trackers/Tracker.js.map +1 -0
  151. package/dist/src/app/charts/{trackerUtils.d.ts → trackers/trackerUtils.d.ts} +13 -8
  152. package/dist/src/app/charts/trackers/trackerUtils.js +359 -0
  153. package/dist/src/app/charts/trackers/trackerUtils.js.map +1 -0
  154. package/dist/src/app/charts/utils.d.ts +22 -32
  155. package/dist/src/app/charts/utils.js +36 -37
  156. package/dist/src/app/charts/utils.js.map +1 -1
  157. package/dist/src/index.d.ts +48 -15
  158. package/dist/src/index.js +71 -15
  159. package/dist/src/index.js.map +1 -1
  160. package/package.json +30 -20
  161. package/src/app/charts/Chart.tsx +334 -0
  162. package/src/app/charts/axes/AxesState.test.ts +34 -0
  163. package/src/app/charts/axes/AxesState.ts +112 -0
  164. package/src/app/charts/axes/AxisInterval.test.ts +143 -0
  165. package/src/app/charts/axes/AxisInterval.ts +139 -0
  166. package/src/app/charts/axes/BaseAxisRange.ts +113 -0
  167. package/src/app/charts/axes/ContinuousAxis.tsx +192 -0
  168. package/src/app/charts/axes/ContinuousAxisRange.test.ts +55 -0
  169. package/src/app/charts/axes/ContinuousAxisRange.ts +96 -0
  170. package/src/app/charts/axes/OrdinalAxis.tsx +223 -0
  171. package/src/app/charts/axes/OrdinalAxisRange.test.ts +55 -0
  172. package/src/app/charts/axes/OrdinalAxisRange.ts +112 -0
  173. package/src/app/charts/axes/axes.ts +1569 -0
  174. package/src/app/charts/d3types.ts +16 -0
  175. package/src/app/charts/filters/regexFilter.test.ts +13 -0
  176. package/src/app/charts/filters/regexFilter.ts +17 -0
  177. package/src/app/charts/hooks/useAxes.tsx +341 -0
  178. package/src/app/charts/hooks/useChart.tsx +159 -0
  179. package/src/app/charts/hooks/useDataObservable.tsx +175 -0
  180. package/src/app/charts/hooks/useInitialData.tsx +65 -0
  181. package/src/app/charts/hooks/useMouse.tsx +125 -0
  182. package/src/app/charts/hooks/usePlotDimensions.tsx +120 -0
  183. package/src/app/charts/hooks/useTooltip.tsx +112 -0
  184. package/src/app/charts/observables/ChartData.ts +31 -0
  185. package/src/app/charts/observables/iterates.test.ts +201 -0
  186. package/src/app/charts/observables/iterates.ts +203 -0
  187. package/src/app/charts/observables/ordinals.test.ts +135 -0
  188. package/src/app/charts/observables/ordinals.ts +248 -0
  189. package/src/app/charts/plots/BarPlot.tsx +1088 -0
  190. package/src/app/charts/plots/PoincarePlot.tsx +832 -0
  191. package/src/app/charts/plots/RasterPlot.tsx +676 -0
  192. package/src/app/charts/plots/ScatterPlot.tsx +653 -0
  193. package/src/app/charts/plots/plot.ts +75 -0
  194. package/src/app/charts/series/baseSeries.ts +52 -0
  195. package/src/app/charts/series/datumSeries.test.ts +60 -0
  196. package/src/app/charts/series/iterateSeries.ts +56 -0
  197. package/src/app/charts/series/ordinalSeries.ts +164 -0
  198. package/src/app/charts/series/timeSeries.ts +40 -0
  199. package/src/app/charts/series/timeSeriesChartData.ts +68 -0
  200. package/src/app/charts/styling/barPlotStyle.ts +126 -0
  201. package/src/app/charts/styling/margins.test.ts +22 -0
  202. package/src/app/charts/styling/margins.ts +58 -0
  203. package/src/app/charts/styling/svgStyle.ts +118 -0
  204. package/src/app/charts/subscriptions/subscription.test.ts +499 -0
  205. package/src/app/charts/subscriptions/subscriptions.ts +635 -0
  206. package/src/app/charts/tooltips/BarPlotTooltipContent.tsx +440 -0
  207. package/src/app/charts/tooltips/PoincarePlotTooltipContent.tsx +352 -0
  208. package/src/app/charts/tooltips/RasterPlotTooltipContent.tsx +274 -0
  209. package/src/app/charts/tooltips/ScatterPlotTooltipContent.tsx +319 -0
  210. package/src/app/charts/tooltips/Tooltip.tsx +148 -0
  211. package/src/app/charts/tooltips/tooltipUtils.ts +216 -0
  212. package/src/app/charts/trackers/Tracker.tsx +129 -0
  213. package/src/app/charts/trackers/trackerUtils.ts +494 -0
  214. package/src/app/charts/utils.ts +109 -0
  215. package/dist/src/app/charts/CategoryAxis.d.ts +0 -20
  216. package/dist/src/app/charts/CategoryAxis.js +0 -112
  217. package/dist/src/app/charts/CategoryAxis.js.map +0 -1
  218. package/dist/src/app/charts/ContinuousAxis.d.ts +0 -16
  219. package/dist/src/app/charts/ContinuousAxis.js +0 -186
  220. package/dist/src/app/charts/ContinuousAxis.js.map +0 -1
  221. package/dist/src/app/charts/RasterPlot.js.map +0 -1
  222. package/dist/src/app/charts/RasterPlotTooltipContent.js.map +0 -1
  223. package/dist/src/app/charts/ScatterPlot.js +0 -351
  224. package/dist/src/app/charts/ScatterPlot.js.map +0 -1
  225. package/dist/src/app/charts/ScatterPlotTooltipContent.js +0 -163
  226. package/dist/src/app/charts/ScatterPlotTooltipContent.js.map +0 -1
  227. package/dist/src/app/charts/Tooltip.js.map +0 -1
  228. package/dist/src/app/charts/Tracker.js +0 -65
  229. package/dist/src/app/charts/Tracker.js.map +0 -1
  230. package/dist/src/app/charts/axes.d.ts +0 -132
  231. package/dist/src/app/charts/axes.js +0 -265
  232. package/dist/src/app/charts/axes.js.map +0 -1
  233. package/dist/src/app/charts/chartData.js +0 -32
  234. package/dist/src/app/charts/chartData.js.map +0 -1
  235. package/dist/src/app/charts/continuousAxisRangeFor.d.ts +0 -20
  236. package/dist/src/app/charts/continuousAxisRangeFor.js +0 -68
  237. package/dist/src/app/charts/continuousAxisRangeFor.js.map +0 -1
  238. package/dist/src/app/charts/datumSeries.d.ts +0 -59
  239. package/dist/src/app/charts/datumSeries.js +0 -55
  240. package/dist/src/app/charts/datumSeries.js.map +0 -1
  241. package/dist/src/app/charts/datumSeries.test.js +0 -50
  242. package/dist/src/app/charts/datumSeries.test.js.map +0 -1
  243. package/dist/src/app/charts/hooks/AxesState.d.ts +0 -37
  244. package/dist/src/app/charts/hooks/AxesState.js +0 -44
  245. package/dist/src/app/charts/hooks/AxesState.js.map +0 -1
  246. package/dist/src/app/charts/margins.js.map +0 -1
  247. package/dist/src/app/charts/margins.test.js +0 -14
  248. package/dist/src/app/charts/margins.test.js.map +0 -1
  249. package/dist/src/app/charts/plot.js.map +0 -1
  250. package/dist/src/app/charts/regexFilter.d.ts +0 -9
  251. package/dist/src/app/charts/regexFilter.js +0 -17
  252. package/dist/src/app/charts/regexFilter.js.map +0 -1
  253. package/dist/src/app/charts/regexFilter.test.js +0 -11
  254. package/dist/src/app/charts/regexFilter.test.js.map +0 -1
  255. package/dist/src/app/charts/subscriptions.d.ts +0 -43
  256. package/dist/src/app/charts/subscriptions.js +0 -213
  257. package/dist/src/app/charts/subscriptions.js.map +0 -1
  258. package/dist/src/app/charts/svgStyle.d.ts +0 -27
  259. package/dist/src/app/charts/svgStyle.js +0 -32
  260. package/dist/src/app/charts/svgStyle.js.map +0 -1
  261. package/dist/src/app/charts/timeRange.test.js +0 -54
  262. package/dist/src/app/charts/timeRange.test.js.map +0 -1
  263. package/dist/src/app/charts/tooltipUtils.js.map +0 -1
  264. package/dist/src/app/charts/trackerUtils.js +0 -130
  265. package/dist/src/app/charts/trackerUtils.js.map +0 -1
  266. /package/dist/src/app/charts/{datumSeries.test.d.ts → axes/AxesState.test.d.ts} +0 -0
  267. /package/dist/src/app/charts/{margins.test.d.ts → axes/AxisInterval.test.d.ts} +0 -0
  268. /package/dist/src/app/charts/{regexFilter.test.d.ts → axes/ContinuousAxisRange.test.d.ts} +0 -0
  269. /package/dist/src/app/charts/{timeRange.test.d.ts → axes/OrdinalAxisRange.test.d.ts} +0 -0
  270. /package/dist/src/app/charts/{RasterPlotTooltipContent.d.ts → tooltips/RasterPlotTooltipContent.d.ts} +0 -0
  271. /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
+ }