@windborne/grapher 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +85 -0
- package/.idea/codeStyles/Project.xml +19 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/grapher.iml +12 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/0.bundle.js +2 -0
- package/0.bundle.js.map +1 -0
- package/1767282193a714f63082.module.wasm +0 -0
- package/537.bundle.js +2 -0
- package/537.bundle.js.map +1 -0
- package/831.bundle.js +2 -0
- package/831.bundle.js.map +1 -0
- package/bundle.js +2 -0
- package/bundle.js.map +1 -0
- package/package.json +75 -0
- package/readme.md +129 -0
- package/src/components/annotations.js +62 -0
- package/src/components/context_menu.js +73 -0
- package/src/components/draggable_points.js +114 -0
- package/src/components/graph_body.js +292 -0
- package/src/components/graph_title.js +16 -0
- package/src/components/options.js +111 -0
- package/src/components/percentile_button.js +72 -0
- package/src/components/range_graph.js +352 -0
- package/src/components/range_selection.js +175 -0
- package/src/components/range_selection_button.js +26 -0
- package/src/components/range_selection_button_base.js +51 -0
- package/src/components/series_key.js +235 -0
- package/src/components/series_key_axis_container.js +70 -0
- package/src/components/series_key_item.js +52 -0
- package/src/components/sidebar.js +76 -0
- package/src/components/tooltip.js +244 -0
- package/src/components/vertical_lines.js +70 -0
- package/src/components/x_axis.js +124 -0
- package/src/components/y_axis.js +239 -0
- package/src/eventable.js +65 -0
- package/src/grapher.js +367 -0
- package/src/grapher.scss +914 -0
- package/src/helpers/axis_sizes.js +2 -0
- package/src/helpers/binary_search.js +67 -0
- package/src/helpers/color_to_vector.js +35 -0
- package/src/helpers/colors.js +27 -0
- package/src/helpers/custom_prop_types.js +159 -0
- package/src/helpers/flatten_simple_data.js +81 -0
- package/src/helpers/format.js +233 -0
- package/src/helpers/generator_params_equal.js +10 -0
- package/src/helpers/name_for_series.js +16 -0
- package/src/helpers/place_grid.js +257 -0
- package/src/helpers/pyodide_ready.js +13 -0
- package/src/multigrapher.js +105 -0
- package/src/renderer/background.frag +7 -0
- package/src/renderer/background.vert +7 -0
- package/src/renderer/background_program.js +48 -0
- package/src/renderer/circle.frag +26 -0
- package/src/renderer/circle.vert +12 -0
- package/src/renderer/create_gl_program.js +36 -0
- package/src/renderer/draw_area.js +159 -0
- package/src/renderer/draw_background.js +15 -0
- package/src/renderer/draw_bars.js +80 -0
- package/src/renderer/draw_line.js +69 -0
- package/src/renderer/draw_zero_line.js +24 -0
- package/src/renderer/extract_vertices.js +137 -0
- package/src/renderer/graph_body_renderer.js +293 -0
- package/src/renderer/line.frag +51 -0
- package/src/renderer/line.vert +32 -0
- package/src/renderer/line_program.js +125 -0
- package/src/renderer/paths_from.js +72 -0
- package/src/renderer/scale_bounds.js +28 -0
- package/src/renderer/size_canvas.js +59 -0
- package/src/rust/Cargo.lock +233 -0
- package/src/rust/Cargo.toml +35 -0
- package/src/rust/pkg/grapher_rs.d.ts +42 -0
- package/src/rust/pkg/grapher_rs.js +351 -0
- package/src/rust/pkg/grapher_rs_bg.d.ts +11 -0
- package/src/rust/pkg/grapher_rs_bg.wasm +0 -0
- package/src/rust/pkg/index.js +342 -0
- package/src/rust/pkg/index_bg.wasm +0 -0
- package/src/rust/pkg/package.json +14 -0
- package/src/rust/src/extract_vertices.rs +83 -0
- package/src/rust/src/get_point_number.rs +50 -0
- package/src/rust/src/lib.rs +15 -0
- package/src/rust/src/selected_space_to_render_space.rs +131 -0
- package/src/state/average_loop_times.js +15 -0
- package/src/state/bound_calculator_from_selection.js +36 -0
- package/src/state/bound_calculators.js +41 -0
- package/src/state/calculate_annotations_state.js +59 -0
- package/src/state/calculate_data_bounds.js +104 -0
- package/src/state/calculate_tooltip_state.js +241 -0
- package/src/state/data_types.js +13 -0
- package/src/state/expand_bounds.js +58 -0
- package/src/state/find_matching_axis.js +31 -0
- package/src/state/get_default_bounds_calculator.js +15 -0
- package/src/state/hooks.js +164 -0
- package/src/state/infer_type.js +74 -0
- package/src/state/merge_bounds.js +64 -0
- package/src/state/multigraph_state_controller.js +334 -0
- package/src/state/selection_from_global_bounds.js +25 -0
- package/src/state/space_conversions/condense_data_space.js +115 -0
- package/src/state/space_conversions/data_space_to_selected_space.js +328 -0
- package/src/state/space_conversions/selected_space_to_background_space.js +144 -0
- package/src/state/space_conversions/selected_space_to_render_space.js +161 -0
- package/src/state/space_conversions/simple_series_to_data_space.js +229 -0
- package/src/state/state_controller.js +1770 -0
- package/src/state/sync_pool.js +101 -0
- package/test/setup.js +15 -0
- package/test/space_conversions/data_space_to_selected_space.test.js +434 -0
- package/webpack.dev.config.js +109 -0
- package/webpack.prod.config.js +60 -0
- package/webpack.test.config.js +59 -0
@@ -0,0 +1,328 @@
|
|
1
|
+
import binarySearch from '../../helpers/binary_search';
|
2
|
+
|
3
|
+
function validateSelectedSpaceConversion({ data, inSelectedSpace, firstIndex, lastIndex, ignoreDiscontinuities }) { // eslint-disable-line no-unused-vars
|
4
|
+
let correct = data.slice(firstIndex + 1, lastIndex);
|
5
|
+
if (ignoreDiscontinuities) {
|
6
|
+
correct = correct.filter(([_x, y]) => (y !== null && y !== undefined));
|
7
|
+
}
|
8
|
+
|
9
|
+
if (correct.length !== inSelectedSpace.length) {
|
10
|
+
console.log({ // eslint-disable-line no-console
|
11
|
+
data,
|
12
|
+
attempt: inSelectedSpace.map(([x, y]) => [undateify(x), y]),
|
13
|
+
correct: correct.map(([x, y]) => [undateify(x), y]),
|
14
|
+
sdl: window.sdl
|
15
|
+
});
|
16
|
+
window.tacomaDataPaused = true;
|
17
|
+
throw new Error('Failed to select via swap');
|
18
|
+
}
|
19
|
+
|
20
|
+
for (let i = 0; i < correct.length; i++) {
|
21
|
+
if (undateify(correct[i][0]) !== undateify(inSelectedSpace[i][0]) || correct[i][1] !== inSelectedSpace[i][1]) {
|
22
|
+
console.log({ // eslint-disable-line no-console
|
23
|
+
i,
|
24
|
+
attempt: inSelectedSpace.map(([x, y]) => [undateify(x), y]),
|
25
|
+
correct: correct.map(([x, y]) => [undateify(x), y]),
|
26
|
+
correctX: undateify(correct[i][0]),
|
27
|
+
correctY: correct[i][1],
|
28
|
+
attemptX: undateify(inSelectedSpace[i][0]),
|
29
|
+
attemptY: inSelectedSpace[i][1]
|
30
|
+
});
|
31
|
+
window.tacomaDataPaused = true;
|
32
|
+
throw new Error('Failed to select via swap');
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Converts from data space to selected space
|
39
|
+
*
|
40
|
+
* @param {Array<Array<Number>>} data
|
41
|
+
* @param {{data: Array<Array<Number>>}|*} [swap]
|
42
|
+
* @param {Number} minX
|
43
|
+
* @param {Number} maxX
|
44
|
+
* @param {Boolean} ignoreDiscontinuities
|
45
|
+
* @param {Boolean} square
|
46
|
+
* @return {{data: Array<Array<Number>>, lastAdded: boolean, firstAdded: boolean, afterIndex: Number, ignoreDiscontinuities: boolean, minX: Number, maxX: Number, beforeIndex: Number}}
|
47
|
+
*/
|
48
|
+
export default function dataSpaceToSelectedSpace({ data, swap, minX, maxX, ignoreDiscontinuities, square }) {
|
49
|
+
if (!data.length || data.length && minX > data[data.length - 1][0] || data.length && maxX < data[0][0]) {
|
50
|
+
//let previouslyEmpty = !data.length;
|
51
|
+
return {
|
52
|
+
data: [
|
53
|
+
[minX, null],
|
54
|
+
[maxX, null]
|
55
|
+
],
|
56
|
+
firstAdded: true,
|
57
|
+
lastAdded: true
|
58
|
+
//previouslyEmpty
|
59
|
+
};
|
60
|
+
}
|
61
|
+
|
62
|
+
let beforeIndex = binarySearch(data, minX, { searchType: 'before', returnIndex: true }) || 0;
|
63
|
+
let afterIndex = binarySearch(data, maxX, { searchType: 'after', returnIndex: true }) || 0;
|
64
|
+
if (afterIndex === -1) {
|
65
|
+
afterIndex = 0;
|
66
|
+
}
|
67
|
+
|
68
|
+
while (beforeIndex >= 0 && data[beforeIndex][0] >= minX) {
|
69
|
+
beforeIndex --;
|
70
|
+
}
|
71
|
+
|
72
|
+
while (afterIndex < data.length && data[afterIndex][0] <= maxX) {
|
73
|
+
afterIndex ++;
|
74
|
+
}
|
75
|
+
|
76
|
+
let inSelectedSpace;
|
77
|
+
let addToEndOnly = false;
|
78
|
+
|
79
|
+
let firstAdded = false;
|
80
|
+
let lastAdded = false;
|
81
|
+
|
82
|
+
let beginningInterpolationIndex = beforeIndex;
|
83
|
+
let endInterpolationIndex = afterIndex;
|
84
|
+
if (ignoreDiscontinuities) {
|
85
|
+
while (beginningInterpolationIndex >= 0 && data[beginningInterpolationIndex][1] === null) {
|
86
|
+
beginningInterpolationIndex --;
|
87
|
+
}
|
88
|
+
while (endInterpolationIndex < data.length && data[endInterpolationIndex][1] === null) {
|
89
|
+
endInterpolationIndex++;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
if (swap) {
|
94
|
+
inSelectedSpace = swap.data;
|
95
|
+
if (swap.lastAdded) {
|
96
|
+
inSelectedSpace.pop();
|
97
|
+
}
|
98
|
+
if (minX === swap.minX) {
|
99
|
+
addToEndOnly = true;
|
100
|
+
}
|
101
|
+
if (beforeIndex !== swap.beforeIndex) {
|
102
|
+
addToEndOnly = false;
|
103
|
+
if (swap.firstAdded) {
|
104
|
+
inSelectedSpace.shift();
|
105
|
+
}
|
106
|
+
let index = swap.beforeIndex;
|
107
|
+
while (index >= 0 && inSelectedSpace.length && inSelectedSpace[0][0] >= minX) {
|
108
|
+
if (!ignoreDiscontinuities || data[index][1] || data[index][1] === 0) {
|
109
|
+
inSelectedSpace.unshift(data[index]);
|
110
|
+
}
|
111
|
+
index --;
|
112
|
+
}
|
113
|
+
while (index < data.length && inSelectedSpace.length && inSelectedSpace[0][0] < minX) {
|
114
|
+
inSelectedSpace.shift();
|
115
|
+
index ++;
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
if (ignoreDiscontinuities && !swap.ignoreDiscontinuities) {
|
120
|
+
inSelectedSpace = inSelectedSpace.filter(([_x, y]) => (y !== null && y !== undefined));
|
121
|
+
}
|
122
|
+
|
123
|
+
if (beforeIndex === swap.beforeIndex && swap.firstAdded) {
|
124
|
+
firstAdded = true;
|
125
|
+
|
126
|
+
let interpolationIndex = beforeIndex + 1;
|
127
|
+
if (ignoreDiscontinuities) {
|
128
|
+
while (interpolationIndex < data.length && data[interpolationIndex][1] === null) {
|
129
|
+
interpolationIndex ++;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
if (beginningInterpolationIndex === -1 && inSelectedSpace.length) {
|
134
|
+
inSelectedSpace[0] = [minX, null];
|
135
|
+
} else {
|
136
|
+
if (square && inSelectedSpace.length) {
|
137
|
+
inSelectedSpace[0] = [minX, data[beginningInterpolationIndex][1]];
|
138
|
+
} else {
|
139
|
+
if (inSelectedSpace.length) {
|
140
|
+
inSelectedSpace[0] = [minX, interpolate(data, beginningInterpolationIndex, interpolationIndex, minX)];
|
141
|
+
}
|
142
|
+
}
|
143
|
+
}
|
144
|
+
if (data[beforeIndex + 1][0] === inSelectedSpace[0][0] && data[beforeIndex + 1][1] === inSelectedSpace[0][1]) {
|
145
|
+
firstAdded = false;
|
146
|
+
}
|
147
|
+
}
|
148
|
+
let lastIncluded = swap.afterIndex;
|
149
|
+
if (swap.ignoreDiscontinuities && !ignoreDiscontinuities) {
|
150
|
+
let nullIndex = beforeIndex + 1;
|
151
|
+
let selectedIndex = 0;
|
152
|
+
if (firstAdded) {
|
153
|
+
selectedIndex ++;
|
154
|
+
}
|
155
|
+
while (selectedIndex <= inSelectedSpace.length && nullIndex < data.length && nullIndex < afterIndex) {
|
156
|
+
if (data[nullIndex][0] <= maxX && data[nullIndex][1] === null && (!inSelectedSpace[selectedIndex] || inSelectedSpace[selectedIndex][0] !== data[nullIndex][0] || inSelectedSpace[selectedIndex][1] !== data[nullIndex][1])) {
|
157
|
+
inSelectedSpace.splice(selectedIndex, 0, data[nullIndex]);
|
158
|
+
if (nullIndex >= lastIncluded) {
|
159
|
+
lastIncluded = nullIndex + 1;
|
160
|
+
}
|
161
|
+
}
|
162
|
+
nullIndex ++;
|
163
|
+
selectedIndex ++;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
let dataIndex = lastIncluded || 0;
|
168
|
+
while (dataIndex < data.length && data[dataIndex][0] <= maxX) {
|
169
|
+
if (data[dataIndex][0] >= minX) {
|
170
|
+
if (!ignoreDiscontinuities || data[dataIndex][1] || data[dataIndex][1] === 0) {
|
171
|
+
inSelectedSpace.push(data[dataIndex]);
|
172
|
+
}
|
173
|
+
}
|
174
|
+
dataIndex ++;
|
175
|
+
}
|
176
|
+
while (inSelectedSpace.length && inSelectedSpace[inSelectedSpace.length - 1][0] > maxX) {
|
177
|
+
inSelectedSpace.pop();
|
178
|
+
}
|
179
|
+
|
180
|
+
} else {
|
181
|
+
inSelectedSpace = data.slice(beforeIndex + 1, afterIndex);
|
182
|
+
if (ignoreDiscontinuities) {
|
183
|
+
inSelectedSpace = inSelectedSpace.filter(([_x, y]) => (y !== null && y !== undefined));
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
if (!inSelectedSpace.length) {
|
188
|
+
if (square) {
|
189
|
+
if (beginningInterpolationIndex < 0) {
|
190
|
+
inSelectedSpace = [[minX, null], [maxX, null]];
|
191
|
+
} else {
|
192
|
+
inSelectedSpace = [[minX, data[beginningInterpolationIndex][1]], [maxX, data[beginningInterpolationIndex][1]]];
|
193
|
+
}
|
194
|
+
} else {
|
195
|
+
inSelectedSpace = [[minX, interpolate(data, beginningInterpolationIndex, endInterpolationIndex, minX)], [maxX, interpolate(data, beginningInterpolationIndex, endInterpolationIndex, maxX)]];
|
196
|
+
}
|
197
|
+
firstAdded = true;
|
198
|
+
lastAdded = true;
|
199
|
+
}
|
200
|
+
|
201
|
+
if (!addToEndOnly) {
|
202
|
+
let interpolationIndex = beforeIndex + 1;
|
203
|
+
if (ignoreDiscontinuities) {
|
204
|
+
while (interpolationIndex < data.length && data[interpolationIndex][1] === null) {
|
205
|
+
interpolationIndex ++;
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
if (inSelectedSpace.length && inSelectedSpace[0][0] > minX) {
|
210
|
+
firstAdded = true;
|
211
|
+
if (beginningInterpolationIndex === -1) {
|
212
|
+
inSelectedSpace.unshift([minX, null]);
|
213
|
+
} else {
|
214
|
+
if (square) {
|
215
|
+
inSelectedSpace.unshift([minX, data[beginningInterpolationIndex][1]]);
|
216
|
+
} else {
|
217
|
+
inSelectedSpace.unshift([minX, interpolate(data, beginningInterpolationIndex, interpolationIndex, minX)]);
|
218
|
+
}
|
219
|
+
}
|
220
|
+
}
|
221
|
+
}
|
222
|
+
|
223
|
+
if (inSelectedSpace.length && inSelectedSpace[inSelectedSpace.length - 1][0] < maxX) {
|
224
|
+
lastAdded = true;
|
225
|
+
|
226
|
+
let interpolationIndex = afterIndex - 1;
|
227
|
+
if (ignoreDiscontinuities) {
|
228
|
+
while (interpolationIndex >= 0 && data[interpolationIndex][1] === null) {
|
229
|
+
interpolationIndex --;
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
233
|
+
if (endInterpolationIndex === data.length) {
|
234
|
+
inSelectedSpace.push([maxX, null]);
|
235
|
+
} else {
|
236
|
+
if (square) {
|
237
|
+
inSelectedSpace.push([maxX, inSelectedSpace[inSelectedSpace.length - 1][1]]);
|
238
|
+
} else {
|
239
|
+
inSelectedSpace.push([maxX, interpolate(data, interpolationIndex, endInterpolationIndex, maxX)]);
|
240
|
+
}
|
241
|
+
}
|
242
|
+
}
|
243
|
+
|
244
|
+
if (inSelectedSpace.length === 1) {
|
245
|
+
let begPoint;
|
246
|
+
let endPoint;
|
247
|
+
if (square) {
|
248
|
+
if (beginningInterpolationIndex < 0) {
|
249
|
+
begPoint = [minX, null];
|
250
|
+
} else {
|
251
|
+
begPoint = [minX, data[beginningInterpolationIndex][1]];
|
252
|
+
}
|
253
|
+
endPoint = [maxX, inSelectedSpace[0][1]];
|
254
|
+
} else {
|
255
|
+
begPoint = [minX, interpolate(data, beginningInterpolationIndex, beforeIndex + 1, minX)];
|
256
|
+
endPoint = [maxX, interpolate(data, afterIndex - 1, endInterpolationIndex, maxX)];
|
257
|
+
}
|
258
|
+
inSelectedSpace.unshift(begPoint);
|
259
|
+
inSelectedSpace.push(endPoint);
|
260
|
+
firstAdded = true;
|
261
|
+
lastAdded = true;
|
262
|
+
}
|
263
|
+
|
264
|
+
return {
|
265
|
+
data: inSelectedSpace,
|
266
|
+
minX,
|
267
|
+
maxX,
|
268
|
+
beforeIndex,
|
269
|
+
afterIndex,
|
270
|
+
firstAdded,
|
271
|
+
lastAdded,
|
272
|
+
ignoreDiscontinuities
|
273
|
+
//previouslyEmpty: false
|
274
|
+
};
|
275
|
+
}
|
276
|
+
|
277
|
+
function undateify(potentialDate) {
|
278
|
+
if (potentialDate instanceof Date) {
|
279
|
+
return potentialDate.valueOf();
|
280
|
+
}
|
281
|
+
|
282
|
+
return potentialDate;
|
283
|
+
}
|
284
|
+
|
285
|
+
/**
|
286
|
+
* Finds the point at the boundary via interpolation
|
287
|
+
*
|
288
|
+
* @param {Array<Array<Number>>} data
|
289
|
+
* @param {Number} firstIndex
|
290
|
+
* @param {Number} secondIndex
|
291
|
+
* @param {Number} boundary
|
292
|
+
* @return {null|*}
|
293
|
+
*/
|
294
|
+
function interpolate(data, firstIndex, secondIndex, boundary) {
|
295
|
+
if (firstIndex < 0 || secondIndex < 0) {
|
296
|
+
return null;
|
297
|
+
}
|
298
|
+
|
299
|
+
if (firstIndex >= data.length || secondIndex >= data.length) {
|
300
|
+
return null;
|
301
|
+
}
|
302
|
+
|
303
|
+
if (firstIndex === secondIndex) {
|
304
|
+
return data[firstIndex][1];
|
305
|
+
}
|
306
|
+
|
307
|
+
const [xBefore, yBefore] = data[firstIndex];
|
308
|
+
const [xAfter, yAfter] = data[secondIndex];
|
309
|
+
|
310
|
+
if (boundary === xBefore && yBefore !== null) {
|
311
|
+
return yBefore;
|
312
|
+
}
|
313
|
+
|
314
|
+
if (boundary === xAfter && yAfter !== null) {
|
315
|
+
return yAfter;
|
316
|
+
}
|
317
|
+
|
318
|
+
if (yBefore === null || yAfter === null) {
|
319
|
+
return null;
|
320
|
+
}
|
321
|
+
|
322
|
+
const percent = (boundary - xBefore)/(xAfter - xBefore);
|
323
|
+
if (percent < 0 || percent > 1) {
|
324
|
+
return null;
|
325
|
+
}
|
326
|
+
|
327
|
+
return percent*(yAfter - yBefore) + yBefore;
|
328
|
+
}
|
@@ -0,0 +1,144 @@
|
|
1
|
+
export function selectedSpaceToBackgroundSpace({ data, background, minX, maxX }) {
|
2
|
+
if (!background) {
|
3
|
+
return null;
|
4
|
+
}
|
5
|
+
|
6
|
+
const conditions = [];
|
7
|
+
for (let [key, color] of Object.entries(background)) {
|
8
|
+
if (typeof color === 'object') {
|
9
|
+
if (typeof color.evaluator !== 'function') {
|
10
|
+
throw new Error('Invalid background declaration: ' + key + ' (evaluator must be a function)');
|
11
|
+
}
|
12
|
+
|
13
|
+
conditions.push(Object.assign({
|
14
|
+
key,
|
15
|
+
comparator: 'custom',
|
16
|
+
comparedAgainst: null
|
17
|
+
}, color));
|
18
|
+
continue;
|
19
|
+
}
|
20
|
+
|
21
|
+
if (key === 'null') {
|
22
|
+
conditions.push({
|
23
|
+
evaluator: (y) => y === null && color,
|
24
|
+
color,
|
25
|
+
key,
|
26
|
+
comparator: '=',
|
27
|
+
comparedAgainst: null
|
28
|
+
});
|
29
|
+
continue;
|
30
|
+
}
|
31
|
+
|
32
|
+
const [comparator, value] = key.split(' ');
|
33
|
+
if (!comparator || !value || isNaN(parseFloat(value))) {
|
34
|
+
throw new Error('Invalid background declaration: ' + key);
|
35
|
+
}
|
36
|
+
|
37
|
+
const parsedValue = parseFloat(value);
|
38
|
+
|
39
|
+
let evaluator;
|
40
|
+
if (comparator === '=') {
|
41
|
+
evaluator = (y) => typeof y === 'number' && y === parsedValue && color;
|
42
|
+
} else if (comparator === '<') {
|
43
|
+
evaluator = (y) => typeof y === 'number' && y < parsedValue && color;
|
44
|
+
} else if (comparator === '>') {
|
45
|
+
evaluator = (y) => typeof y === 'number' && y > parsedValue && color;
|
46
|
+
} else if (comparator === '<=') {
|
47
|
+
evaluator = (y) => typeof y === 'number' && y <= parsedValue && color;
|
48
|
+
} else if (comparator === '>=') {
|
49
|
+
evaluator = (y) => typeof y === 'number' && y >= parsedValue && color;
|
50
|
+
} else {
|
51
|
+
throw new Error('Invalid background declaration: ' + key);
|
52
|
+
}
|
53
|
+
|
54
|
+
conditions.push({
|
55
|
+
evaluator,
|
56
|
+
color,
|
57
|
+
key,
|
58
|
+
comparator,
|
59
|
+
comparedAgainst: parsedValue
|
60
|
+
});
|
61
|
+
}
|
62
|
+
|
63
|
+
const inBackgroundSpace = [];
|
64
|
+
let currentSection = null;
|
65
|
+
|
66
|
+
for (let i = 0; i < data.length; i++) {
|
67
|
+
let [x, y] = data[i];
|
68
|
+
if (x instanceof Date) {
|
69
|
+
x = x.valueOf();
|
70
|
+
}
|
71
|
+
|
72
|
+
for (let condition of currentSection ? [currentSection.condition, ...conditions] : conditions) {
|
73
|
+
const color = condition.evaluator(y);
|
74
|
+
|
75
|
+
if (currentSection) {
|
76
|
+
if (currentSection.color === color) {
|
77
|
+
break;
|
78
|
+
}
|
79
|
+
|
80
|
+
let interpolatedMaxX = x;
|
81
|
+
if (i > 0) {
|
82
|
+
let [prevX, prevY] = data[i - 1];
|
83
|
+
if (prevX instanceof Date) {
|
84
|
+
prevX = prevX.valueOf();
|
85
|
+
}
|
86
|
+
|
87
|
+
if (currentSection.condition.comparedAgainst === null) {
|
88
|
+
interpolatedMaxX = x;
|
89
|
+
} else if (y === null) {
|
90
|
+
interpolatedMaxX = prevX;
|
91
|
+
} else {
|
92
|
+
interpolatedMaxX = prevX + (condition.comparedAgainst - prevY)/(y - prevY)*(x - prevX);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
inBackgroundSpace.push({
|
97
|
+
...currentSection,
|
98
|
+
maxX: interpolatedMaxX,
|
99
|
+
maxXt: (interpolatedMaxX - minX)/(maxX - minX)
|
100
|
+
});
|
101
|
+
currentSection = null;
|
102
|
+
}
|
103
|
+
|
104
|
+
if (color) {
|
105
|
+
let interpolatedMinX = x;
|
106
|
+
if (i > 0) {
|
107
|
+
let [prevX, prevY] = data[i - 1];
|
108
|
+
if (prevX instanceof Date) {
|
109
|
+
prevX = prevX.valueOf();
|
110
|
+
}
|
111
|
+
|
112
|
+
if (condition.comparedAgainst === null) {
|
113
|
+
interpolatedMinX = prevX;
|
114
|
+
} else if (prevY === null) {
|
115
|
+
interpolatedMinX = x;
|
116
|
+
} else {
|
117
|
+
interpolatedMinX = prevX + (condition.comparedAgainst - prevY)/(y - prevY)*(x - prevX);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
currentSection = {
|
122
|
+
minX: interpolatedMinX,
|
123
|
+
minXt: (interpolatedMinX - minX)/(maxX - minX),
|
124
|
+
color,
|
125
|
+
condition
|
126
|
+
};
|
127
|
+
|
128
|
+
break;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
if (currentSection) {
|
134
|
+
inBackgroundSpace.push({
|
135
|
+
...currentSection,
|
136
|
+
maxX: data[data.length - 1][0],
|
137
|
+
maxXt: (data[data.length - 1][0] - minX)/(maxX - minX)
|
138
|
+
});
|
139
|
+
}
|
140
|
+
|
141
|
+
return {
|
142
|
+
data: inBackgroundSpace
|
143
|
+
};
|
144
|
+
}
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import scaleBounds from '../../renderer/scale_bounds';
|
2
|
+
let RustAPI;
|
3
|
+
import('../../rust/pkg/index.js').then((module) => {
|
4
|
+
RustAPI = module;
|
5
|
+
});
|
6
|
+
|
7
|
+
function selectedSpaceToRenderSpaceInPlace({ data, renderWidth, renderHeight, minX, maxX, minY, maxY, scale }, { nullMask, yValues, minYValues, maxYValues }) {
|
8
|
+
let i = 0;
|
9
|
+
let prevI = i - 1;
|
10
|
+
|
11
|
+
for (let pixelX = 0; pixelX < renderWidth; pixelX++) {
|
12
|
+
// find the x value that corresponds to the x pixel
|
13
|
+
const x = (pixelX/(renderWidth - 1))*(maxX - minX) + minX;
|
14
|
+
|
15
|
+
// set i such that data[i][0] < x <= data[i+1][0]
|
16
|
+
let minSeenY = null;
|
17
|
+
let maxSeenY = null;
|
18
|
+
|
19
|
+
if (i > 0 && i <= data.length && data[i - 1][1] === null) {
|
20
|
+
i--;
|
21
|
+
}
|
22
|
+
|
23
|
+
if (i < data.length - 2 && data[i + 1][0] < x) {
|
24
|
+
i++;
|
25
|
+
}
|
26
|
+
|
27
|
+
for (i; i < data.length - 2 && data[i + 1][0] < x; i++) {
|
28
|
+
const curY = data[i][1];
|
29
|
+
|
30
|
+
if (curY === null) {
|
31
|
+
continue;
|
32
|
+
}
|
33
|
+
|
34
|
+
if (minSeenY === null || curY < minSeenY) {
|
35
|
+
minSeenY = curY;
|
36
|
+
}
|
37
|
+
|
38
|
+
if (maxSeenY === null || curY > maxSeenY) {
|
39
|
+
maxSeenY = curY;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
minYValues[pixelX] = minSeenY === null ? 0 : renderHeight*(1 - ((scale === 'log' ? Math.log10(minSeenY) : minSeenY) - minY)/(maxY - minY));
|
44
|
+
maxYValues[pixelX] = maxSeenY === null ? 0 : renderHeight*(1 - ((scale === 'log' ? Math.log10(maxSeenY) : maxSeenY) - minY)/(maxY - minY));
|
45
|
+
|
46
|
+
// pass any discontinuities along
|
47
|
+
if (i >= data.length - 1 || data[i][1] === null || data[i + 1][1] === null) {
|
48
|
+
const y = i >= data.length - 1 ? null : data[i][1];
|
49
|
+
|
50
|
+
nullMask[pixelX] = ((y === null) << 0) | ((minSeenY === null) << 1) | ((maxSeenY === null) << 2);
|
51
|
+
yValues[pixelX] = y === null ? 0 : renderHeight*(1 - ((scale === 'log' ? Math.log10(y) : y) - minY)/(maxY - minY));
|
52
|
+
|
53
|
+
i++;
|
54
|
+
|
55
|
+
continue;
|
56
|
+
}
|
57
|
+
|
58
|
+
// interpolate
|
59
|
+
const [xBefore, yBefore] = data[i];
|
60
|
+
const [xAfter, yAfter] = data[i + 1];
|
61
|
+
|
62
|
+
const percent = (x - xBefore) / (xAfter - xBefore);
|
63
|
+
let y = percent * (yAfter - yBefore) + yBefore;
|
64
|
+
|
65
|
+
// we're at the first point after the direction changed. Don't interpolate
|
66
|
+
if (prevI !== i) {
|
67
|
+
y = yBefore;
|
68
|
+
}
|
69
|
+
|
70
|
+
yValues[pixelX] = y === null ? 0 : renderHeight*(1 - ((scale === 'log' ? Math.log10(y) : y) - minY)/(maxY - minY));
|
71
|
+
nullMask[pixelX] = ((y === null) << 0) | ((minSeenY === null) << 1) | ((maxSeenY === null) << 2);
|
72
|
+
|
73
|
+
prevI = i;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Convert from selected space to value space
|
79
|
+
*
|
80
|
+
* @param data
|
81
|
+
* @param {Object} [swap]
|
82
|
+
* @param {Number} renderWidth - width, in pixels, of the area in which the graph is rendered
|
83
|
+
* @param {Number} renderHeight - height, in pixels, of the area in which the graph is rendered
|
84
|
+
* @param {Number} minX - the minimum x value that is rendered
|
85
|
+
* @param {Number} maxX - the maximum x value that is rendered
|
86
|
+
* @param {Number} minY - the minimum y value that is rendered
|
87
|
+
* @param {Number} maxY - the maximum y value that is rendered
|
88
|
+
* @param {'log'|'linear'} scale
|
89
|
+
* @param {Boolean} [dataChanged] - if true, will not rely on the prior data state from swap being accurate
|
90
|
+
* @return {{nullMask: Uint8Array, maxYValues: Float64Array, minYValues: Float64Array, yValues: Float64Array, dataF64: Float64Array, dataNullMask: Uint8Array}}
|
91
|
+
*/
|
92
|
+
export default function selectedSpaceToRenderSpace({ data, swap, renderWidth, renderHeight, minX, maxX, minY, maxY, scale, dataChanged }) {
|
93
|
+
if (swap && swap.yValues.length !== renderWidth) {
|
94
|
+
swap = null;
|
95
|
+
}
|
96
|
+
|
97
|
+
const nullMask = (swap && swap.nullMask) || new Uint8Array(renderWidth);
|
98
|
+
nullMask.fill(0);
|
99
|
+
const yValues = new Float64Array(renderWidth);
|
100
|
+
const minYValues = new Float64Array(renderWidth);
|
101
|
+
const maxYValues = new Float64Array(renderWidth);
|
102
|
+
|
103
|
+
const scaledBounds = scaleBounds({ minY, maxY, scale});
|
104
|
+
minY = scaledBounds.minY;
|
105
|
+
maxY = scaledBounds.maxY;
|
106
|
+
|
107
|
+
const inParams = { data, renderWidth, renderHeight, minX, maxX, minY, maxY, scale };
|
108
|
+
|
109
|
+
let dataF64, dataNullMask;
|
110
|
+
|
111
|
+
if (RustAPI) {
|
112
|
+
let copyIndexStart = 0;
|
113
|
+
|
114
|
+
const hasSwap = swap && swap.dataNullMask && swap.dataF64;
|
115
|
+
const useSwap = !dataChanged && hasSwap && swap.minX === minX && swap.maxX <= maxX && swap.length <= data.length;
|
116
|
+
|
117
|
+
if (!useSwap || swap.dataNullMask.length < data.length) {
|
118
|
+
const extraSpaceFactor = 1.25;
|
119
|
+
dataF64 = new Float64Array(Math.floor(data.length*2*extraSpaceFactor));
|
120
|
+
dataNullMask = new Uint8Array(Math.floor(data.length*extraSpaceFactor));
|
121
|
+
|
122
|
+
if (useSwap) {
|
123
|
+
dataNullMask.set(swap.dataNullMask);
|
124
|
+
dataF64.set(swap.dataF64);
|
125
|
+
}
|
126
|
+
} else {
|
127
|
+
dataF64 = swap.dataF64;
|
128
|
+
dataNullMask = swap.dataNullMask;
|
129
|
+
}
|
130
|
+
|
131
|
+
if (useSwap) {
|
132
|
+
copyIndexStart = Math.max(swap.length - 1, 0);
|
133
|
+
}
|
134
|
+
|
135
|
+
for (let i = copyIndexStart; i < data.length; i++) {
|
136
|
+
dataF64[2*i] = data[i][0];
|
137
|
+
dataF64[2*i + 1] = data[i][1];
|
138
|
+
|
139
|
+
if (data[i][1] === null) {
|
140
|
+
dataNullMask[i] = 1;
|
141
|
+
} else {
|
142
|
+
dataNullMask[i] = 0;
|
143
|
+
}
|
144
|
+
}
|
145
|
+
RustAPI.selected_space_to_render_space(data.length, dataF64, dataNullMask, inParams, nullMask, yValues, minYValues, maxYValues);
|
146
|
+
} else {
|
147
|
+
selectedSpaceToRenderSpaceInPlace(inParams, { nullMask, yValues, minYValues, maxYValues });
|
148
|
+
}
|
149
|
+
|
150
|
+
return {
|
151
|
+
nullMask,
|
152
|
+
yValues,
|
153
|
+
minYValues,
|
154
|
+
maxYValues,
|
155
|
+
dataF64,
|
156
|
+
dataNullMask,
|
157
|
+
minX,
|
158
|
+
maxX,
|
159
|
+
length: data.length
|
160
|
+
};
|
161
|
+
}
|