@coinbase/cds-web-visualization 3.4.0-beta.5 → 3.4.0-beta.7

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 (167) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dts/chart/CartesianChart.d.ts +38 -2
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/Path.d.ts +27 -7
  5. package/dts/chart/Path.d.ts.map +1 -1
  6. package/dts/chart/PeriodSelector.d.ts +0 -4
  7. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  8. package/dts/chart/area/Area.d.ts +54 -24
  9. package/dts/chart/area/Area.d.ts.map +1 -1
  10. package/dts/chart/area/AreaChart.d.ts +33 -6
  11. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  12. package/dts/chart/area/DottedArea.d.ts +21 -44
  13. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  14. package/dts/chart/area/GradientArea.d.ts +21 -12
  15. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  16. package/dts/chart/area/SolidArea.d.ts +16 -1
  17. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  18. package/dts/chart/axis/Axis.d.ts +89 -43
  19. package/dts/chart/axis/Axis.d.ts.map +1 -1
  20. package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
  21. package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
  22. package/dts/chart/axis/XAxis.d.ts +1 -1
  23. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  24. package/dts/chart/axis/YAxis.d.ts +1 -1
  25. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  26. package/dts/chart/axis/index.d.ts +1 -0
  27. package/dts/chart/axis/index.d.ts.map +1 -1
  28. package/dts/chart/bar/Bar.d.ts +16 -13
  29. package/dts/chart/bar/Bar.d.ts.map +1 -1
  30. package/dts/chart/bar/BarChart.d.ts +17 -8
  31. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  32. package/dts/chart/bar/BarPlot.d.ts +2 -1
  33. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  34. package/dts/chart/bar/BarStack.d.ts +40 -48
  35. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  36. package/dts/chart/bar/BarStackGroup.d.ts +1 -0
  37. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  38. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  39. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  40. package/dts/chart/gradient/Gradient.d.ts +35 -0
  41. package/dts/chart/gradient/Gradient.d.ts.map +1 -0
  42. package/dts/chart/gradient/index.d.ts +2 -0
  43. package/dts/chart/gradient/index.d.ts.map +1 -0
  44. package/dts/chart/index.d.ts +2 -1
  45. package/dts/chart/index.d.ts.map +1 -1
  46. package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
  47. package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
  48. package/dts/chart/line/DottedLine.d.ts +15 -3
  49. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  50. package/dts/chart/line/Line.d.ts +70 -28
  51. package/dts/chart/line/Line.d.ts.map +1 -1
  52. package/dts/chart/line/LineChart.d.ts +26 -8
  53. package/dts/chart/line/LineChart.d.ts.map +1 -1
  54. package/dts/chart/line/ReferenceLine.d.ts +91 -44
  55. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  56. package/dts/chart/line/SolidLine.d.ts +14 -3
  57. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  58. package/dts/chart/line/index.d.ts +1 -1
  59. package/dts/chart/line/index.d.ts.map +1 -1
  60. package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
  61. package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
  62. package/dts/chart/point/Point.d.ts +201 -0
  63. package/dts/chart/point/Point.d.ts.map +1 -0
  64. package/dts/chart/point/index.d.ts +3 -0
  65. package/dts/chart/point/index.d.ts.map +1 -0
  66. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +24 -0
  67. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
  68. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
  69. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
  70. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +10 -0
  71. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
  72. package/dts/chart/scrubber/Scrubber.d.ts +207 -66
  73. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  74. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +70 -0
  75. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
  76. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +32 -0
  77. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
  78. package/dts/chart/scrubber/index.d.ts +3 -0
  79. package/dts/chart/scrubber/index.d.ts.map +1 -1
  80. package/dts/chart/text/ChartText.d.ts +46 -43
  81. package/dts/chart/text/ChartText.d.ts.map +1 -1
  82. package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
  83. package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
  84. package/dts/chart/text/index.d.ts +1 -1
  85. package/dts/chart/text/index.d.ts.map +1 -1
  86. package/dts/chart/utils/chart.d.ts +27 -7
  87. package/dts/chart/utils/chart.d.ts.map +1 -1
  88. package/dts/chart/utils/context.d.ts +6 -0
  89. package/dts/chart/utils/context.d.ts.map +1 -1
  90. package/dts/chart/utils/gradient.d.ts +104 -0
  91. package/dts/chart/utils/gradient.d.ts.map +1 -0
  92. package/dts/chart/utils/index.d.ts +4 -0
  93. package/dts/chart/utils/index.d.ts.map +1 -1
  94. package/dts/chart/utils/interpolate.d.ts +112 -0
  95. package/dts/chart/utils/interpolate.d.ts.map +1 -0
  96. package/dts/chart/utils/path.d.ts +24 -1
  97. package/dts/chart/utils/path.d.ts.map +1 -1
  98. package/dts/chart/utils/point.d.ts +29 -0
  99. package/dts/chart/utils/point.d.ts.map +1 -1
  100. package/dts/chart/utils/scrubber.d.ts +39 -0
  101. package/dts/chart/utils/scrubber.d.ts.map +1 -0
  102. package/dts/chart/utils/transition.d.ts +65 -0
  103. package/dts/chart/utils/transition.d.ts.map +1 -0
  104. package/esm/chart/CartesianChart.js +140 -85
  105. package/esm/chart/Path.js +51 -46
  106. package/esm/chart/PeriodSelector.js +4 -18
  107. package/esm/chart/area/Area.js +24 -34
  108. package/esm/chart/area/AreaChart.js +24 -15
  109. package/esm/chart/area/DottedArea.js +35 -89
  110. package/esm/chart/area/GradientArea.js +34 -80
  111. package/esm/chart/area/SolidArea.js +29 -11
  112. package/esm/chart/axis/Axis.js +4 -25
  113. package/esm/chart/axis/DefaultAxisTickLabel.js +15 -0
  114. package/esm/chart/axis/XAxis.js +53 -36
  115. package/esm/chart/axis/YAxis.js +55 -32
  116. package/esm/chart/axis/index.js +1 -0
  117. package/esm/chart/bar/Bar.js +3 -1
  118. package/esm/chart/bar/BarChart.js +15 -32
  119. package/esm/chart/bar/BarPlot.js +3 -2
  120. package/esm/chart/bar/BarStack.js +65 -23
  121. package/esm/chart/bar/BarStackGroup.js +7 -17
  122. package/esm/chart/bar/DefaultBar.js +4 -7
  123. package/esm/chart/bar/DefaultBarStack.js +5 -7
  124. package/esm/chart/gradient/Gradient.js +104 -0
  125. package/esm/chart/gradient/index.js +1 -0
  126. package/esm/chart/index.js +2 -1
  127. package/esm/chart/line/DefaultReferenceLineLabel.js +81 -0
  128. package/esm/chart/line/DottedLine.js +38 -17
  129. package/esm/chart/line/Line.js +96 -70
  130. package/esm/chart/line/LineChart.js +18 -6
  131. package/esm/chart/line/ReferenceLine.js +41 -43
  132. package/esm/chart/line/SolidLine.js +36 -15
  133. package/esm/chart/line/index.js +1 -1
  134. package/esm/chart/{line/GradientLine.js → point/DefaultPointLabel.js} +31 -45
  135. package/esm/chart/point/Point.css +2 -0
  136. package/esm/chart/{Point.js → point/Point.js} +66 -57
  137. package/esm/chart/point/index.js +2 -0
  138. package/esm/chart/scrubber/DefaultScrubberBeacon.js +155 -0
  139. package/esm/chart/scrubber/{ScrubberBeaconLabel.js → DefaultScrubberBeaconLabel.js} +23 -10
  140. package/esm/chart/scrubber/DefaultScrubberLabel.js +30 -0
  141. package/esm/chart/scrubber/Scrubber.js +98 -392
  142. package/esm/chart/scrubber/ScrubberBeaconGroup.js +166 -0
  143. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +186 -0
  144. package/esm/chart/scrubber/index.js +3 -1
  145. package/esm/chart/text/ChartText.js +13 -19
  146. package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +4 -3
  147. package/esm/chart/text/index.js +1 -1
  148. package/esm/chart/utils/chart.js +29 -3
  149. package/esm/chart/utils/gradient.js +257 -0
  150. package/esm/chart/utils/index.js +4 -0
  151. package/esm/chart/utils/interpolate.js +644 -0
  152. package/esm/chart/utils/path.js +32 -9
  153. package/esm/chart/utils/point.js +69 -0
  154. package/esm/chart/utils/scrubber.js +132 -0
  155. package/esm/chart/utils/transition.js +111 -0
  156. package/package.json +5 -5
  157. package/dts/chart/Point.d.ts +0 -153
  158. package/dts/chart/Point.d.ts.map +0 -1
  159. package/dts/chart/line/GradientLine.d.ts +0 -42
  160. package/dts/chart/line/GradientLine.d.ts.map +0 -1
  161. package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -93
  162. package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
  163. package/dts/chart/scrubber/ScrubberBeaconLabel.d.ts +0 -7
  164. package/dts/chart/scrubber/ScrubberBeaconLabel.d.ts.map +0 -1
  165. package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
  166. package/esm/chart/Point.css +0 -2
  167. package/esm/chart/scrubber/ScrubberBeacon.js +0 -195
@@ -0,0 +1,644 @@
1
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
3
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
6
+ // work in progress
7
+
8
+ /**
9
+ * de Casteljau's algorithm for drawing and splitting bezier curves.
10
+ * Inspired by https://pomax.github.io/bezierinfo/
11
+ *
12
+ * @param {Number[][]} points Array of [x,y] points: [start, control1, control2, ..., end]
13
+ * The original segment to split.
14
+ * @param {Number} t Where to split the curve (value between [0, 1])
15
+ * @return {Object} An object { left, right } where left is the segment from 0..t and
16
+ * right is the segment from t..1.
17
+ */
18
+ function decasteljau(points, t) {
19
+ const left = [];
20
+ const right = [];
21
+ function decasteljauRecurse(points, t) {
22
+ if (points.length === 1) {
23
+ left.push(points[0]);
24
+ right.push(points[0]);
25
+ } else {
26
+ const newPoints = Array(points.length - 1);
27
+ for (let i = 0; i < newPoints.length; i++) {
28
+ if (i === 0) {
29
+ left.push(points[0]);
30
+ }
31
+ if (i === newPoints.length - 1) {
32
+ right.push(points[i + 1]);
33
+ }
34
+ newPoints[i] = [(1 - t) * points[i][0] + t * points[i + 1][0], (1 - t) * points[i][1] + t * points[i + 1][1]];
35
+ }
36
+ decasteljauRecurse(newPoints, t);
37
+ }
38
+ }
39
+ if (points.length) {
40
+ decasteljauRecurse(points, t);
41
+ }
42
+ return {
43
+ left,
44
+ right: right.reverse()
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Convert segments represented as points back into a command object
50
+ *
51
+ * @param {Number[][]} points Array of [x,y] points: [start, control1, control2, ..., end]
52
+ * Represents a segment
53
+ * @return {Object} A command object representing the segment.
54
+ */
55
+ function pointsToCommand(points) {
56
+ const command = {};
57
+ if (points.length === 4) {
58
+ command.x2 = points[2][0];
59
+ command.y2 = points[2][1];
60
+ }
61
+ if (points.length >= 3) {
62
+ command.x1 = points[1][0];
63
+ command.y1 = points[1][1];
64
+ }
65
+ command.x = points[points.length - 1][0];
66
+ command.y = points[points.length - 1][1];
67
+ if (points.length === 4) {
68
+ // start, control1, control2, end
69
+ command.type = 'C';
70
+ } else if (points.length === 3) {
71
+ // start, control, end
72
+ command.type = 'Q';
73
+ } else {
74
+ // start, end
75
+ command.type = 'L';
76
+ }
77
+ return command;
78
+ }
79
+
80
+ /**
81
+ * Runs de Casteljau's algorithm enough times to produce the desired number of segments.
82
+ *
83
+ * @param {Number[][]} points Array of [x,y] points for de Casteljau (the initial segment to split)
84
+ * @param {Number} segmentCount Number of segments to split the original into
85
+ * @return {Number[][][]} Array of segments
86
+ */
87
+ function splitCurveAsPoints(points, segmentCount) {
88
+ segmentCount = segmentCount || 2;
89
+ const segments = [];
90
+ let remainingCurve = points;
91
+ const tIncrement = 1 / segmentCount;
92
+
93
+ // x-----x-----x-----x
94
+ // t= 0.33 0.66 1
95
+ // x-----o-----------x
96
+ // r= 0.33
97
+ // x-----o-----x
98
+ // r= 0.5 (0.33 / (1 - 0.33)) === tIncrement / (1 - (tIncrement * (i - 1))
99
+
100
+ // x-----x-----x-----x----x
101
+ // t= 0.25 0.5 0.75 1
102
+ // x-----o----------------x
103
+ // r= 0.25
104
+ // x-----o----------x
105
+ // r= 0.33 (0.25 / (1 - 0.25))
106
+ // x-----o----x
107
+ // r= 0.5 (0.25 / (1 - 0.5))
108
+
109
+ for (let i = 0; i < segmentCount - 1; i++) {
110
+ const tRelative = tIncrement / (1 - tIncrement * i);
111
+ const split = decasteljau(remainingCurve, tRelative);
112
+ segments.push(split.left);
113
+ remainingCurve = split.right;
114
+ }
115
+
116
+ // last segment is just to the end from the last point
117
+ segments.push(remainingCurve);
118
+ return segments;
119
+ }
120
+
121
+ /**
122
+ * Convert command objects to arrays of points, run de Casteljau's algorithm on it
123
+ * to split into to the desired number of segments.
124
+ *
125
+ * @param {Object} commandStart The start command object
126
+ * @param {Object} commandEnd The end command object
127
+ * @param {Number} segmentCount The number of segments to create
128
+ * @return {Object[]} An array of commands representing the segments in sequence
129
+ */
130
+ export function splitCurve(commandStart, commandEnd, segmentCount) {
131
+ const points = [[commandStart.x, commandStart.y]];
132
+ if (commandEnd.x1 != null) {
133
+ points.push([commandEnd.x1, commandEnd.y1]);
134
+ }
135
+ if (commandEnd.x2 != null) {
136
+ points.push([commandEnd.x2, commandEnd.y2]);
137
+ }
138
+ points.push([commandEnd.x, commandEnd.y]);
139
+ return splitCurveAsPoints(points, segmentCount).map(pointsToCommand);
140
+ }
141
+ const commandTokenRegex = /[MLCSTQAHVZmlcstqahv]|-?[\d.e+-]+/g;
142
+ /**
143
+ * List of params for each command type in a path `d` attribute
144
+ */
145
+ const typeMap = {
146
+ M: ['x', 'y'],
147
+ L: ['x', 'y'],
148
+ H: ['x'],
149
+ V: ['y'],
150
+ C: ['x1', 'y1', 'x2', 'y2', 'x', 'y'],
151
+ S: ['x2', 'y2', 'x', 'y'],
152
+ Q: ['x1', 'y1', 'x', 'y'],
153
+ T: ['x', 'y'],
154
+ A: ['rx', 'ry', 'xAxisRotation', 'largeArcFlag', 'sweepFlag', 'x', 'y'],
155
+ Z: []
156
+ };
157
+
158
+ // Add lower case entries too matching uppercase (e.g. 'm' == 'M')
159
+ Object.keys(typeMap).forEach(key => {
160
+ typeMap[key.toLowerCase()] = typeMap[key];
161
+ });
162
+ function arrayOfLength(length, value) {
163
+ const array = Array(length);
164
+ for (let i = 0; i < length; i++) {
165
+ array[i] = value;
166
+ }
167
+ return array;
168
+ }
169
+
170
+ /**
171
+ * Converts a command object to a string to be used in a `d` attribute
172
+ * @param {Object} command A command object
173
+ * @return {String} The string for the `d` attribute
174
+ */
175
+ function commandToString(command) {
176
+ return "".concat(command.type).concat(typeMap[command.type].map(p => command[p]).join(','));
177
+ }
178
+
179
+ /**
180
+ * Converts command A to have the same type as command B.
181
+ *
182
+ * e.g., L0,5 -> C0,5,0,5,0,5
183
+ *
184
+ * Uses these rules:
185
+ * x1 <- x
186
+ * x2 <- x
187
+ * y1 <- y
188
+ * y2 <- y
189
+ * rx <- 0
190
+ * ry <- 0
191
+ * xAxisRotation <- read from B
192
+ * largeArcFlag <- read from B
193
+ * sweepflag <- read from B
194
+ *
195
+ * @param {Object} aCommand Command object from path `d` attribute
196
+ * @param {Object} bCommand Command object from path `d` attribute to match against
197
+ * @return {Object} aCommand converted to type of bCommand
198
+ */
199
+ function convertToSameType(aCommand, bCommand) {
200
+ const conversionMap = {
201
+ x1: 'x',
202
+ y1: 'y',
203
+ x2: 'x',
204
+ y2: 'y'
205
+ };
206
+ const readFromBKeys = ['xAxisRotation', 'largeArcFlag', 'sweepFlag'];
207
+
208
+ // convert (but ignore M types)
209
+ if (aCommand.type !== bCommand.type && bCommand.type.toUpperCase() !== 'M') {
210
+ const aConverted = {};
211
+ Object.keys(bCommand).forEach(bKey => {
212
+ const bValue = bCommand[bKey];
213
+ // first read from the A command
214
+ let aValue = aCommand[bKey];
215
+
216
+ // if it is one of these values, read from B no matter what
217
+ if (aValue === undefined) {
218
+ if (readFromBKeys.includes(bKey)) {
219
+ aValue = bValue;
220
+ } else {
221
+ // if it wasn't in the A command, see if an equivalent was
222
+ if (aValue === undefined && conversionMap[bKey]) {
223
+ aValue = aCommand[conversionMap[bKey]];
224
+ }
225
+
226
+ // if it doesn't have a converted value, use 0
227
+ if (aValue === undefined) {
228
+ aValue = 0;
229
+ }
230
+ }
231
+ }
232
+ aConverted[bKey] = aValue;
233
+ });
234
+
235
+ // update the type to match B
236
+ aConverted.type = bCommand.type;
237
+ aCommand = aConverted;
238
+ }
239
+ return aCommand;
240
+ }
241
+
242
+ /**
243
+ * Interpolate between command objects commandStart and commandEnd segmentCount times.
244
+ * If the types are L, Q, or C then the curves are split as per de Casteljau's algorithm.
245
+ * Otherwise we just copy commandStart segmentCount - 1 times, finally ending with commandEnd.
246
+ *
247
+ * @param {Object} commandStart Command object at the beginning of the segment
248
+ * @param {Object} commandEnd Command object at the end of the segment
249
+ * @param {Number} segmentCount The number of segments to split this into. If only 1
250
+ * Then [commandEnd] is returned.
251
+ * @return {Object[]} Array of ~segmentCount command objects between commandStart and
252
+ * commandEnd. (Can be segmentCount+1 objects if commandStart is type M).
253
+ */
254
+ function splitSegment(commandStart, commandEnd, segmentCount) {
255
+ let segments = [];
256
+
257
+ // line, quadratic bezier, or cubic bezier
258
+ if (commandEnd.type === 'L' || commandEnd.type === 'Q' || commandEnd.type === 'C') {
259
+ segments = segments.concat(splitCurve(commandStart, commandEnd, segmentCount));
260
+
261
+ // general case - just copy the same point
262
+ } else {
263
+ const copyCommand = Object.assign({}, commandStart);
264
+
265
+ // convert M to L
266
+ if (copyCommand.type === 'M') {
267
+ copyCommand.type = 'L';
268
+ }
269
+ segments = segments.concat(arrayOfLength(segmentCount - 1).map(() => copyCommand));
270
+ segments.push(commandEnd);
271
+ }
272
+ return segments;
273
+ }
274
+ /**
275
+ * Extends an array of commandsToExtend to the length of the referenceCommands by
276
+ * splitting segments until the number of commands match. Ensures all the actual
277
+ * points of commandsToExtend are in the extended array.
278
+ *
279
+ * @param {Object[]} commandsToExtend The command object array to extend
280
+ * @param {Object[]} referenceCommands The command object array to match in length
281
+ * @param {Function} excludeSegment a function that takes a start command object and
282
+ * end command object and returns true if the segment should be excluded from splitting.
283
+ * @return {Object[]} The extended commandsToExtend array
284
+ */
285
+ function extend(commandsToExtend, referenceCommands, excludeSegment) {
286
+ // compute insertion points:
287
+ // number of segments in the path to extend
288
+ const numSegmentsToExtend = commandsToExtend.length - 1;
289
+
290
+ // number of segments in the reference path.
291
+ const numReferenceSegments = referenceCommands.length - 1;
292
+
293
+ // this value is always between [0, 1].
294
+ const segmentRatio = numSegmentsToExtend / numReferenceSegments;
295
+
296
+ // create a map, mapping segments in referenceCommands to how many points
297
+ // should be added in that segment (should always be >= 1 since we need each
298
+ // point itself).
299
+ // 0 = segment 0-1, 1 = segment 1-2, n-1 = last vertex
300
+ const countPointsPerSegment = arrayOfLength(numReferenceSegments).reduce((accum, d, i) => {
301
+ let insertIndex = Math.floor(segmentRatio * i);
302
+
303
+ // handle excluding segments
304
+ if (excludeSegment && insertIndex < commandsToExtend.length - 1 && excludeSegment(commandsToExtend[insertIndex], commandsToExtend[insertIndex + 1])) {
305
+ // set the insertIndex to the segment that this point should be added to:
306
+
307
+ // round the insertIndex essentially so we split half and half on
308
+ // neighbouring segments. hence the segmentRatio * i < 0.5
309
+ const addToPriorSegment = segmentRatio * i % 1 < 0.5;
310
+
311
+ // only skip segment if we already have 1 point in it (can't entirely remove a segment)
312
+ if (accum[insertIndex]) {
313
+ // TODO - Note this is a naive algorithm that should work for most d3-area use cases
314
+ // but if two adjacent segments are supposed to be skipped, this will not perform as
315
+ // expected. Could be updated to search for nearest segment to place the point in, but
316
+ // will only do that if necessary.
317
+
318
+ // add to the prior segment
319
+ if (addToPriorSegment) {
320
+ if (insertIndex > 0) {
321
+ insertIndex -= 1;
322
+
323
+ // not possible to add to previous so adding to next
324
+ } else if (insertIndex < commandsToExtend.length - 1) {
325
+ insertIndex += 1;
326
+ }
327
+ // add to next segment
328
+ } else if (insertIndex < commandsToExtend.length - 1) {
329
+ insertIndex += 1;
330
+
331
+ // not possible to add to next so adding to previous
332
+ } else if (insertIndex > 0) {
333
+ insertIndex -= 1;
334
+ }
335
+ }
336
+ }
337
+ accum[insertIndex] = (accum[insertIndex] || 0) + 1;
338
+ return accum;
339
+ }, []);
340
+
341
+ // extend each segment to have the correct number of points for a smooth interpolation
342
+ const extended = countPointsPerSegment.reduce((extended, segmentCount, i) => {
343
+ // if last command, just add `segmentCount` number of times
344
+ if (i === commandsToExtend.length - 1) {
345
+ const lastCommandCopies = arrayOfLength(segmentCount, Object.assign({}, commandsToExtend[commandsToExtend.length - 1]));
346
+
347
+ // convert M to L
348
+ if (lastCommandCopies[0].type === 'M') {
349
+ lastCommandCopies.forEach(d => {
350
+ d.type = 'L';
351
+ });
352
+ }
353
+ return extended.concat(lastCommandCopies);
354
+ }
355
+
356
+ // otherwise, split the segment segmentCount times.
357
+ return extended.concat(splitSegment(commandsToExtend[i], commandsToExtend[i + 1], segmentCount));
358
+ }, []);
359
+
360
+ // add in the very first point since splitSegment only adds in the ones after it
361
+ extended.unshift(commandsToExtend[0]);
362
+ return extended;
363
+ }
364
+
365
+ /**
366
+ * Takes a path `d` string and converts it into an array of command
367
+ * objects. Drops the `Z` character.
368
+ *
369
+ * @param {String|null} d A path `d` string
370
+ */
371
+ export function pathCommandsFromString(d) {
372
+ // split into valid tokens
373
+ const tokens = (d || '').match(commandTokenRegex) || [];
374
+ const commands = [];
375
+ let commandArgs;
376
+ let command;
377
+
378
+ // iterate over each token, checking if we are at a new command
379
+ // by presence in the typeMap
380
+ for (let i = 0; i < tokens.length; ++i) {
381
+ commandArgs = typeMap[tokens[i]];
382
+
383
+ // new command found:
384
+ if (commandArgs) {
385
+ command = {
386
+ type: tokens[i]
387
+ };
388
+
389
+ // add each of the expected args for this command:
390
+ for (let a = 0; a < commandArgs.length; ++a) {
391
+ command[commandArgs[a]] = +tokens[i + a + 1];
392
+ }
393
+
394
+ // need to increment our token index appropriately since
395
+ // we consumed token args
396
+ i += commandArgs.length;
397
+ commands.push(command);
398
+ }
399
+ }
400
+ return commands;
401
+ }
402
+ /**
403
+ * Interpolate from A to B by extending A and B during interpolation to have
404
+ * the same number of points. This allows for a smooth transition when they
405
+ * have a different number of points.
406
+ *
407
+ * Ignores the `Z` command in paths unless both A and B end with it.
408
+ *
409
+ * This function works directly with arrays of command objects instead of with
410
+ * path `d` strings (see interpolatePath for working with `d` strings).
411
+ *
412
+ * @param {Object[]} aCommandsInput Array of path commands
413
+ * @param {Object[]} bCommandsInput Array of path commands
414
+ * @param {(Function|Object)} interpolateOptions
415
+ * @param {Function} interpolateOptions.excludeSegment a function that takes a start command object and
416
+ * end command object and returns true if the segment should be excluded from splitting.
417
+ * @param {Boolean} interpolateOptions.snapEndsToInput a boolean indicating whether end of input should
418
+ * be sourced from input argument or computed.
419
+ * @returns {Function} Interpolation function that maps t ([0, 1]) to an array of path commands.
420
+ */
421
+ export function interpolatePathCommands(aCommandsInput, bCommandsInput, interpolateOptions) {
422
+ // make a copy so we don't mess with the input arrays
423
+ let aCommands = aCommandsInput == null ? [] : aCommandsInput.slice();
424
+ let bCommands = bCommandsInput == null ? [] : bCommandsInput.slice();
425
+ const {
426
+ excludeSegment,
427
+ snapEndsToInput
428
+ } = typeof interpolateOptions === 'object' ? interpolateOptions : {
429
+ excludeSegment: interpolateOptions,
430
+ snapEndsToInput: true
431
+ };
432
+
433
+ // both input sets are empty, so we don't interpolate
434
+ if (!aCommands.length && !bCommands.length) {
435
+ return function nullInterpolator() {
436
+ return [];
437
+ };
438
+ }
439
+
440
+ // do we add Z during interpolation? yes if both have it. (we'd expect both to have it or not)
441
+ const addZ = (aCommands.length === 0 || aCommands[aCommands.length - 1].type === 'Z') && (bCommands.length === 0 || bCommands[bCommands.length - 1].type === 'Z');
442
+
443
+ // we temporarily remove Z
444
+ if (aCommands.length > 0 && aCommands[aCommands.length - 1].type === 'Z') {
445
+ aCommands.pop();
446
+ }
447
+ if (bCommands.length > 0 && bCommands[bCommands.length - 1].type === 'Z') {
448
+ bCommands.pop();
449
+ }
450
+
451
+ // if A is empty, treat it as if it used to contain just the first point
452
+ // of B. This makes it so the line extends out of from that first point.
453
+ if (!aCommands.length) {
454
+ aCommands.push(bCommands[0]);
455
+
456
+ // otherwise if B is empty, treat it as if it contains the first point
457
+ // of A. This makes it so the line retracts into the first point.
458
+ } else if (!bCommands.length) {
459
+ bCommands.push(aCommands[0]);
460
+ }
461
+
462
+ // extend to match equal size
463
+ const numPointsToExtend = Math.abs(bCommands.length - aCommands.length);
464
+ if (numPointsToExtend !== 0) {
465
+ // B has more points than A, so add points to A before interpolating
466
+ if (bCommands.length > aCommands.length) {
467
+ aCommands = extend(aCommands, bCommands, excludeSegment);
468
+
469
+ // else if A has more points than B, add more points to B
470
+ } else if (bCommands.length < aCommands.length) {
471
+ bCommands = extend(bCommands, aCommands, excludeSegment);
472
+ }
473
+ }
474
+
475
+ // commands have same length now.
476
+ // convert commands in A to the same type as those in B
477
+ aCommands = aCommands.map((aCommand, i) => convertToSameType(aCommand, bCommands[i]));
478
+
479
+ // create mutable interpolated command objects
480
+ const interpolatedCommands = aCommands.map(aCommand => _objectSpread({}, aCommand));
481
+ if (addZ) {
482
+ interpolatedCommands.push({
483
+ type: 'Z'
484
+ });
485
+ aCommands.push({
486
+ type: 'Z'
487
+ }); // required for when returning at t == 0
488
+ }
489
+ return function pathCommandInterpolator(t) {
490
+ // at 1 return the final value without the extensions used during interpolation
491
+ if (t === 1 && snapEndsToInput) {
492
+ return bCommandsInput == null ? [] : bCommandsInput;
493
+ }
494
+
495
+ // work with aCommands directly since interpolatedCommands are mutated
496
+ if (t === 0) {
497
+ return aCommands;
498
+ }
499
+
500
+ // interpolate the commands using the mutable interpolated command objs
501
+ for (let i = 0; i < interpolatedCommands.length; ++i) {
502
+ // if (interpolatedCommands[i].type === 'Z') continue;
503
+
504
+ const aCommand = aCommands[i];
505
+ const bCommand = bCommands[i];
506
+ const interpolatedCommand = interpolatedCommands[i];
507
+ for (const arg of typeMap[interpolatedCommand.type]) {
508
+ interpolatedCommand[arg] = (1 - t) * aCommand[arg] + t * bCommand[arg];
509
+
510
+ // do not use floats for flags (#27), round to integer
511
+ if (arg === 'largeArcFlag' || arg === 'sweepFlag') {
512
+ interpolatedCommand[arg] = Math.round(interpolatedCommand[arg]);
513
+ }
514
+ }
515
+ }
516
+ return interpolatedCommands;
517
+ };
518
+ }
519
+
520
+ /**
521
+ * Interpolate from A to B by extending A and B during interpolation to have
522
+ * the same number of points. This allows for a smooth transition when they
523
+ * have a different number of points.
524
+ *
525
+ * Ignores the `Z` character in paths unless both A and B end with it.
526
+ *
527
+ * @param {String} a The `d` attribute for a path
528
+ * @param {String} b The `d` attribute for a path
529
+ * @param {((command1, command2) => boolean|{
530
+ * excludeSegment?: (command1, command2) => boolean;
531
+ * snapEndsToInput?: boolean
532
+ * })} interpolateOptions The excludeSegment function or an options object
533
+ * - interpolateOptions.excludeSegment a function that takes a start command object and
534
+ * end command object and returns true if the segment should be excluded from splitting.
535
+ * - interpolateOptions.snapEndsToInput a boolean indicating whether end of input should
536
+ * be sourced from input argument or computed.
537
+ * @returns {Function} Interpolation function that maps t ([0, 1]) to a path `d` string.
538
+ */
539
+ export function interpolatePath(a, b, interpolateOptions) {
540
+ const aCommands = pathCommandsFromString(a);
541
+ const bCommands = pathCommandsFromString(b);
542
+ const {
543
+ excludeSegment,
544
+ snapEndsToInput
545
+ } = typeof interpolateOptions === 'object' ? interpolateOptions : {
546
+ excludeSegment: interpolateOptions,
547
+ snapEndsToInput: true
548
+ };
549
+ if (!aCommands.length && !bCommands.length) {
550
+ return function nullInterpolator() {
551
+ return '';
552
+ };
553
+ }
554
+ let commandInterpolator;
555
+ if (canTranslate(aCommands, bCommands)) {
556
+ commandInterpolator = createTranslateInterpolator(aCommands, bCommands, {
557
+ excludeSegment,
558
+ snapEndsToInput
559
+ });
560
+ } else {
561
+ commandInterpolator = interpolatePathCommands(aCommands, bCommands, {
562
+ excludeSegment,
563
+ snapEndsToInput
564
+ });
565
+ }
566
+ return function pathStringInterpolator(t) {
567
+ // at 1 return the final value without the extensions used during interpolation
568
+ if (t === 1 && snapEndsToInput) {
569
+ return b == null ? '' : b;
570
+ }
571
+ const interpolatedCommands = commandInterpolator(t);
572
+
573
+ // convert to a string (fastest concat: https://jsperf.com/join-concat/150)
574
+ let interpolatedString = '';
575
+ for (const interpolatedCommand of interpolatedCommands) {
576
+ interpolatedString += commandToString(interpolatedCommand);
577
+ }
578
+ return interpolatedString;
579
+ };
580
+ }
581
+
582
+ // Custom code
583
+ function canTranslate(aCommands, bCommands) {
584
+ if (!aCommands || !bCommands || aCommands.length !== bCommands.length || aCommands.length < 2) {
585
+ return false;
586
+ }
587
+ const n = aCommands.length;
588
+ for (let i = 0; i < n; i++) {
589
+ const aCommand = aCommands[i];
590
+ const bCommand = bCommands[i];
591
+ // Check X grid
592
+ if (aCommand !== null && aCommand !== void 0 && aCommand.x && bCommand !== null && bCommand !== void 0 && bCommand.x && Math.abs(aCommand.x - bCommand.x) > 0.001) {
593
+ return false;
594
+ }
595
+ // Check Y shift
596
+ if (i < n - 1) {
597
+ var _aCommands, _bCommands$i;
598
+ const y_a_shifted = (_aCommands = aCommands[i + 1]) === null || _aCommands === void 0 ? void 0 : _aCommands.y;
599
+ const y_b = (_bCommands$i = bCommands[i]) === null || _bCommands$i === void 0 ? void 0 : _bCommands$i.y;
600
+ if (y_a_shifted && y_b && Math.abs(y_a_shifted - y_b) > 0.001) {
601
+ return false;
602
+ }
603
+ }
604
+ }
605
+ return true;
606
+ }
607
+ function createTranslateInterpolator(aCommands, bCommands, options) {
608
+ var _bCommands$0$x, _bCommands$, _aCommands$1$x, _aCommands$, _aCommands$0$x, _aCommands$2;
609
+ if (aCommands.length < 2) {
610
+ // Not enough points to slide, fall back
611
+ return interpolatePathCommands(aCommands, bCommands, options);
612
+ }
613
+
614
+ // 1. Calculate the horizontal slide distance from one point to the next
615
+ const dx = ((_bCommands$0$x = (_bCommands$ = bCommands[0]) === null || _bCommands$ === void 0 ? void 0 : _bCommands$.x) !== null && _bCommands$0$x !== void 0 ? _bCommands$0$x : 0) - ((_aCommands$1$x = (_aCommands$ = aCommands[1]) === null || _aCommands$ === void 0 ? void 0 : _aCommands$.x) !== null && _aCommands$1$x !== void 0 ? _aCommands$1$x : 0);
616
+
617
+ // 2. Create the "fake" start point for B (this is where A[0] will animate TO)
618
+ // It's "off-screen" to the left, at the y-level of the new first point.
619
+ const b_fake_start = {
620
+ type: 'M',
621
+ // Must be 'M'
622
+ x: ((_aCommands$0$x = (_aCommands$2 = aCommands[0]) === null || _aCommands$2 === void 0 ? void 0 : _aCommands$2.x) !== null && _aCommands$0$x !== void 0 ? _aCommands$0$x : 0) + dx,
623
+ y: bCommands[0].y // Animate to the y-level of the next point
624
+ };
625
+
626
+ // 3. Create aPrime (n+1 points)
627
+ // This is [A0, A1, ..., An-1, An-1]
628
+ // We duplicate the last point of A to give the new point (Bn-1) something to animate *from*.
629
+ const aPrime = aCommands.slice();
630
+ aPrime.push(aCommands[aCommands.length - 1]);
631
+
632
+ // 4. Create bPrime (n+1 points)
633
+ // This is [B_fake, B0, B1, ..., Bn-1]
634
+ // We add our new "off-screen" point to the beginning of B.
635
+ const bPrime = [b_fake_start, ...bCommands];
636
+
637
+ // 5. Ensure first command of aPrime is 'M'
638
+ aPrime[0] = _objectSpread(_objectSpread({}, aPrime[0]), {}, {
639
+ type: 'M'
640
+ });
641
+
642
+ // Return the *original* morph interpolator with our new, smarter paths.
643
+ return interpolatePathCommands(aPrime, bPrime, options);
644
+ }