@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.
Files changed (112) hide show
  1. package/.eslintrc.js +85 -0
  2. package/.idea/codeStyles/Project.xml +19 -0
  3. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  4. package/.idea/grapher.iml +12 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  6. package/.idea/misc.xml +6 -0
  7. package/.idea/modules.xml +8 -0
  8. package/.idea/vcs.xml +6 -0
  9. package/0.bundle.js +2 -0
  10. package/0.bundle.js.map +1 -0
  11. package/1767282193a714f63082.module.wasm +0 -0
  12. package/537.bundle.js +2 -0
  13. package/537.bundle.js.map +1 -0
  14. package/831.bundle.js +2 -0
  15. package/831.bundle.js.map +1 -0
  16. package/bundle.js +2 -0
  17. package/bundle.js.map +1 -0
  18. package/package.json +75 -0
  19. package/readme.md +129 -0
  20. package/src/components/annotations.js +62 -0
  21. package/src/components/context_menu.js +73 -0
  22. package/src/components/draggable_points.js +114 -0
  23. package/src/components/graph_body.js +292 -0
  24. package/src/components/graph_title.js +16 -0
  25. package/src/components/options.js +111 -0
  26. package/src/components/percentile_button.js +72 -0
  27. package/src/components/range_graph.js +352 -0
  28. package/src/components/range_selection.js +175 -0
  29. package/src/components/range_selection_button.js +26 -0
  30. package/src/components/range_selection_button_base.js +51 -0
  31. package/src/components/series_key.js +235 -0
  32. package/src/components/series_key_axis_container.js +70 -0
  33. package/src/components/series_key_item.js +52 -0
  34. package/src/components/sidebar.js +76 -0
  35. package/src/components/tooltip.js +244 -0
  36. package/src/components/vertical_lines.js +70 -0
  37. package/src/components/x_axis.js +124 -0
  38. package/src/components/y_axis.js +239 -0
  39. package/src/eventable.js +65 -0
  40. package/src/grapher.js +367 -0
  41. package/src/grapher.scss +914 -0
  42. package/src/helpers/axis_sizes.js +2 -0
  43. package/src/helpers/binary_search.js +67 -0
  44. package/src/helpers/color_to_vector.js +35 -0
  45. package/src/helpers/colors.js +27 -0
  46. package/src/helpers/custom_prop_types.js +159 -0
  47. package/src/helpers/flatten_simple_data.js +81 -0
  48. package/src/helpers/format.js +233 -0
  49. package/src/helpers/generator_params_equal.js +10 -0
  50. package/src/helpers/name_for_series.js +16 -0
  51. package/src/helpers/place_grid.js +257 -0
  52. package/src/helpers/pyodide_ready.js +13 -0
  53. package/src/multigrapher.js +105 -0
  54. package/src/renderer/background.frag +7 -0
  55. package/src/renderer/background.vert +7 -0
  56. package/src/renderer/background_program.js +48 -0
  57. package/src/renderer/circle.frag +26 -0
  58. package/src/renderer/circle.vert +12 -0
  59. package/src/renderer/create_gl_program.js +36 -0
  60. package/src/renderer/draw_area.js +159 -0
  61. package/src/renderer/draw_background.js +15 -0
  62. package/src/renderer/draw_bars.js +80 -0
  63. package/src/renderer/draw_line.js +69 -0
  64. package/src/renderer/draw_zero_line.js +24 -0
  65. package/src/renderer/extract_vertices.js +137 -0
  66. package/src/renderer/graph_body_renderer.js +293 -0
  67. package/src/renderer/line.frag +51 -0
  68. package/src/renderer/line.vert +32 -0
  69. package/src/renderer/line_program.js +125 -0
  70. package/src/renderer/paths_from.js +72 -0
  71. package/src/renderer/scale_bounds.js +28 -0
  72. package/src/renderer/size_canvas.js +59 -0
  73. package/src/rust/Cargo.lock +233 -0
  74. package/src/rust/Cargo.toml +35 -0
  75. package/src/rust/pkg/grapher_rs.d.ts +42 -0
  76. package/src/rust/pkg/grapher_rs.js +351 -0
  77. package/src/rust/pkg/grapher_rs_bg.d.ts +11 -0
  78. package/src/rust/pkg/grapher_rs_bg.wasm +0 -0
  79. package/src/rust/pkg/index.js +342 -0
  80. package/src/rust/pkg/index_bg.wasm +0 -0
  81. package/src/rust/pkg/package.json +14 -0
  82. package/src/rust/src/extract_vertices.rs +83 -0
  83. package/src/rust/src/get_point_number.rs +50 -0
  84. package/src/rust/src/lib.rs +15 -0
  85. package/src/rust/src/selected_space_to_render_space.rs +131 -0
  86. package/src/state/average_loop_times.js +15 -0
  87. package/src/state/bound_calculator_from_selection.js +36 -0
  88. package/src/state/bound_calculators.js +41 -0
  89. package/src/state/calculate_annotations_state.js +59 -0
  90. package/src/state/calculate_data_bounds.js +104 -0
  91. package/src/state/calculate_tooltip_state.js +241 -0
  92. package/src/state/data_types.js +13 -0
  93. package/src/state/expand_bounds.js +58 -0
  94. package/src/state/find_matching_axis.js +31 -0
  95. package/src/state/get_default_bounds_calculator.js +15 -0
  96. package/src/state/hooks.js +164 -0
  97. package/src/state/infer_type.js +74 -0
  98. package/src/state/merge_bounds.js +64 -0
  99. package/src/state/multigraph_state_controller.js +334 -0
  100. package/src/state/selection_from_global_bounds.js +25 -0
  101. package/src/state/space_conversions/condense_data_space.js +115 -0
  102. package/src/state/space_conversions/data_space_to_selected_space.js +328 -0
  103. package/src/state/space_conversions/selected_space_to_background_space.js +144 -0
  104. package/src/state/space_conversions/selected_space_to_render_space.js +161 -0
  105. package/src/state/space_conversions/simple_series_to_data_space.js +229 -0
  106. package/src/state/state_controller.js +1770 -0
  107. package/src/state/sync_pool.js +101 -0
  108. package/test/setup.js +15 -0
  109. package/test/space_conversions/data_space_to_selected_space.test.js +434 -0
  110. package/webpack.dev.config.js +109 -0
  111. package/webpack.prod.config.js +60 -0
  112. package/webpack.test.config.js +59 -0
@@ -0,0 +1,334 @@
1
+ import Eventable from '../eventable.js';
2
+ import getColor from '../helpers/colors.js';
3
+ import nameForSeries from '../helpers/name_for_series.js';
4
+
5
+ export default class MultigraphStateController extends Eventable {
6
+
7
+ constructor({ id }) {
8
+ super();
9
+
10
+ this._id = id;
11
+
12
+ this._multiSeries = [];
13
+ this._seriesToGraphIndices = new Map();
14
+ this._graphIndicesToSeries = new Map();
15
+ this._modifiedSeries = new Map();
16
+ this._originalSeriesByMultigrapherIndex = new Map();
17
+ this._stateControllers = new Set();
18
+ this._prevSeries = [];
19
+
20
+ this._dataCache = new Map();
21
+ this._subscriptions = new Map();
22
+
23
+ this._draggingY = false;
24
+
25
+ this.on('multi_series_changed', () => {
26
+ for (let stateController of this._stateControllers) {
27
+ stateController._multiSeries = this._multiSeries;
28
+ }
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Takes an array of user-supplied series, then splits, modifies, and broadcasts them
34
+ *
35
+ * @param {Array<Object>} series
36
+ */
37
+ setSeries(series) {
38
+
39
+ if (this._prevSeries.length === series.length) {
40
+ let anyDifferent = false;
41
+ for (let i = 0; i < series.length; i++) {
42
+ if (series[i] !== this._prevSeries[i]) {
43
+ anyDifferent = true;
44
+ break;
45
+ }
46
+ }
47
+
48
+ if (!anyDifferent) {
49
+ return;
50
+ }
51
+ }
52
+
53
+ this._prevSeries = series;
54
+
55
+ const graphIndices = new Set();
56
+ const currentSeriesSet = new Set(series);
57
+
58
+ for (let singleSeries of series) {
59
+ let graphIndex = singleSeries.graph || 0;
60
+
61
+ if (this._seriesToGraphIndices.has(singleSeries)) {
62
+ graphIndex = this._seriesToGraphIndices.get(singleSeries);
63
+ }
64
+
65
+ graphIndices.add(graphIndex);
66
+
67
+ let seriesSet = this._graphIndicesToSeries.get(graphIndex);
68
+ if (!seriesSet) {
69
+ seriesSet = new Set();
70
+ this._graphIndicesToSeries.set(graphIndex, seriesSet);
71
+ }
72
+
73
+ seriesSet.add(singleSeries);
74
+ }
75
+
76
+ const sortedGraphIndices = [...graphIndices].sort();
77
+
78
+ this._multiSeries = [];
79
+ let globalSeriesIndex = 0;
80
+
81
+ for (let graphIndex of sortedGraphIndices) {
82
+ const series = [];
83
+
84
+ for (let singleSeries of this._graphIndicesToSeries.get(graphIndex)) {
85
+ if (!currentSeriesSet.has(singleSeries)) {
86
+ this._graphIndicesToSeries.get(graphIndex).delete(singleSeries);
87
+ continue;
88
+ }
89
+
90
+ if (this._modifiedSeries.has(singleSeries)) {
91
+ globalSeriesIndex++;
92
+ series.push(this._modifiedSeries.get(singleSeries));
93
+ continue;
94
+ }
95
+
96
+ const color = getColor(singleSeries.color, globalSeriesIndex);
97
+ const name = nameForSeries(singleSeries, globalSeriesIndex);
98
+ const modifiedSeries = {
99
+ ...singleSeries,
100
+ multigrapherSeriesIndex: globalSeriesIndex,
101
+ multigrapherGraphIndex: graphIndex,
102
+ color,
103
+ name
104
+ };
105
+
106
+ this._modifiedSeries.set(singleSeries, modifiedSeries);
107
+ this._originalSeriesByMultigrapherIndex.set(globalSeriesIndex, singleSeries);
108
+
109
+ globalSeriesIndex++;
110
+ series.push(modifiedSeries);
111
+ }
112
+
113
+ this._multiSeries.push(series);
114
+ }
115
+
116
+ if (this._nextMultigrapherSeriesIndex) {
117
+ this._nextMultigrapherSeriesIndex = this._nextMultigrapherSeriesIndex - this._multiSeriesCount + globalSeriesIndex;
118
+ } else {
119
+ this._nextMultigrapherSeriesIndex = globalSeriesIndex;
120
+ }
121
+ this._multiSeriesCount = globalSeriesIndex;
122
+
123
+ this.emit('multi_series_changed', this.multiSeries);
124
+ }
125
+
126
+ /**
127
+ * Registers a state controller for series switching
128
+ *
129
+ * @param {StateController} stateController
130
+ */
131
+ registerStateController(stateController) {
132
+ if (this._stateControllers.has(stateController)) {
133
+ return;
134
+ }
135
+
136
+ this._stateControllers.add(stateController);
137
+ stateController._multigraphStateController = this;
138
+ stateController._multiSeries = this._multiSeries;
139
+
140
+ stateController.on('dragging_y_finalized', ({ grapherID, axisIndex, draggedSeries }={}) => {
141
+ if (grapherID === stateController.grapherID) {
142
+ return;
143
+ }
144
+
145
+ const [check, multigrapherID, graphIndex] = grapherID.split('-');
146
+ if (check !== 'multigrapher' || multigrapherID !== this._id) {
147
+ return;
148
+ }
149
+
150
+ setTimeout(() => {
151
+ this.moveSeries({ axisIndex, draggedSeries, graphIndex });
152
+ });
153
+ });
154
+
155
+ stateController.on('dragging_y_changed', (draggingY) => {
156
+ if (draggingY === this._draggingY) {
157
+ return;
158
+ }
159
+
160
+ this._draggingY = draggingY;
161
+ this.emit('dragging_y_changed', this._draggingY);
162
+ });
163
+
164
+ stateController.on('observable_modified', (observable) => {
165
+ for (let otherStateController of this._stateControllers) {
166
+ if (stateController === otherStateController) {
167
+ continue;
168
+ }
169
+
170
+ otherStateController.markObservableModified(observable);
171
+ }
172
+ });
173
+
174
+ stateController.on('dispose', () => {
175
+ this._stateControllers.delete(stateController);
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Moves the given dragged series (as specified by the child state controller) to a different graph
181
+ *
182
+ * @param {String} axisIndex - the axis index on the new graph to move the series to
183
+ * @param {Object} draggedSeries - the series that got dragged. Different identity than what this may access
184
+ * @param {String} graphIndex - the index of the graph to assign it to
185
+ */
186
+ moveSeries({ axisIndex, draggedSeries, graphIndex }) {
187
+ const prevGraphCount = this.graphCount;
188
+
189
+ const originalSeries = this._originalSeriesByMultigrapherIndex.get(draggedSeries.multigrapherSeriesIndex);
190
+ const modifiedSeries = this._modifiedSeries.get(originalSeries);
191
+
192
+ this._multiSeries[modifiedSeries.multigrapherGraphIndex].splice(
193
+ this._multiSeries[modifiedSeries.multigrapherGraphIndex].indexOf(modifiedSeries), 1
194
+ );
195
+ this._multiSeries[modifiedSeries.multigrapherGraphIndex] = [...this._multiSeries[modifiedSeries.multigrapherGraphIndex]];
196
+
197
+ if (graphIndex === 'top') {
198
+ modifiedSeries.multigrapherGraphIndex = this._createGraphAtTop();
199
+ } else if (graphIndex === 'bottom') {
200
+ modifiedSeries.multigrapherGraphIndex = this._createGraphAtBottom();
201
+ } else {
202
+ modifiedSeries.multigrapherGraphIndex = parseInt(graphIndex);
203
+ }
204
+
205
+ modifiedSeries.axisIndex = axisIndex;
206
+ // safe because stateController operates on a copy. We could also have changed the identify of modifiedSeries,
207
+ // but with that we might reset data when moving it back
208
+ delete modifiedSeries.axis;
209
+
210
+ this._multiSeries[modifiedSeries.multigrapherGraphIndex] = [...this._multiSeries[modifiedSeries.multigrapherGraphIndex], modifiedSeries];
211
+ this._multiSeries = [...this._multiSeries];
212
+
213
+ for (let graphIndex = 0; graphIndex < this._multiSeries.length; graphIndex++) {
214
+ const originalSeriesAtIndex = this._multiSeries[graphIndex].map(({ multigrapherSeriesIndex }) =>
215
+ this._originalSeriesByMultigrapherIndex.get(multigrapherSeriesIndex));
216
+ this._graphIndicesToSeries.set(graphIndex, new Set(originalSeriesAtIndex));
217
+
218
+ for (let singleSeries of originalSeriesAtIndex) {
219
+ this._seriesToGraphIndices.set(singleSeries, graphIndex);
220
+ }
221
+ }
222
+
223
+ this.emit('multi_series_changed', this.multiSeries);
224
+ this.emit('graph_count_changed', this.graphCount, prevGraphCount);
225
+ }
226
+
227
+ /**
228
+ * Finds or creates an empty graph at the beginning and returns its index
229
+ *
230
+ * @return {number}
231
+ * @private
232
+ */
233
+ _createGraphAtTop() {
234
+ // check if there's anything at the beginning already
235
+ let emptyAtTopIndex = null;
236
+
237
+ for (let i = 0; i < this._multiSeries.length; i++) {
238
+ if (this._multiSeries[i].length === 0) {
239
+ emptyAtTopIndex = i;
240
+ } else {
241
+ break;
242
+ }
243
+ }
244
+
245
+ if (emptyAtTopIndex !== null) {
246
+ return emptyAtTopIndex;
247
+ }
248
+
249
+ // add a series at the beginning and mutate the graph index of each
250
+ this._multiSeries = [[], ...this._multiSeries];
251
+ for (let graphIndex = 0; graphIndex < this._multiSeries.length; graphIndex++) {
252
+ if (!this._multiSeries[graphIndex].length) {
253
+ continue;
254
+ }
255
+
256
+ this._multiSeries[graphIndex] = [...this._multiSeries[graphIndex]];
257
+
258
+ for (let modifiedSeries of this._multiSeries[graphIndex]) {
259
+ modifiedSeries.multigrapherGraphIndex = graphIndex;
260
+ }
261
+ }
262
+
263
+ return 0;
264
+ }
265
+
266
+ /**
267
+ * Finds or creates an empty graph at the end and returns its index
268
+ *
269
+ * @return {number}
270
+ * @private
271
+ */
272
+ _createGraphAtBottom() {
273
+ // check if there's anything at the beginning already
274
+ let emptyAtBottomIndex = null;
275
+
276
+ for (let i = this._multiSeries.length - 1; i >= 0; i--) {
277
+ if (this._multiSeries[i].length === 0) {
278
+ emptyAtBottomIndex = i;
279
+ } else {
280
+ break;
281
+ }
282
+ }
283
+
284
+ if (emptyAtBottomIndex !== null) {
285
+ return emptyAtBottomIndex;
286
+ }
287
+
288
+ // add something at the bottom
289
+ this._multiSeries = [...this._multiSeries, []];
290
+
291
+ return this._multiSeries.length - 1;
292
+ }
293
+
294
+ get multiSeries() {
295
+ return this._multiSeries.filter((series) => series.length);
296
+ }
297
+
298
+ get series() {
299
+ return [...this._stateControllers].map((stateController) => stateController.series).flat();
300
+ }
301
+
302
+ get graphCount() {
303
+ return this.multiSeries.length;
304
+ }
305
+
306
+ get draggingY() {
307
+ return this._draggingY;
308
+ }
309
+
310
+ dispose() {
311
+ this.clearListeners();
312
+
313
+ for (let listener of this._subscriptions.values()) {
314
+ listener.unsubscribe();
315
+ }
316
+
317
+ this._subscriptions.clear();
318
+ }
319
+
320
+ get stateControllerInitialization() {
321
+ return {
322
+ sharedDataCache: this._dataCache,
323
+ sharedSubscriptions: this._subscriptions
324
+ };
325
+ }
326
+
327
+ incrementMultigrapherSeriesIndex() {
328
+ const index = this._nextMultigrapherSeriesIndex;
329
+
330
+ this._nextMultigrapherSeriesIndex ++;
331
+
332
+ return index;
333
+ }
334
+ }
@@ -0,0 +1,25 @@
1
+ export default function selectionFromGlobalBounds(globalBounds, boundsCalculator) {
2
+ const targetBounds = Object.assign({}, globalBounds, boundsCalculator(globalBounds));
3
+
4
+ if (targetBounds.maxX instanceof Date) {
5
+ targetBounds.maxX = targetBounds.maxX.valueOf();
6
+ }
7
+
8
+ if (targetBounds.minX instanceof Date) {
9
+ targetBounds.minX = targetBounds.minX.valueOf();
10
+ }
11
+
12
+ if (targetBounds.maxX < targetBounds.minX) {
13
+ targetBounds.maxX = targetBounds.minX;
14
+ }
15
+
16
+ if (targetBounds.minX < globalBounds.minX) {
17
+ targetBounds.minX = globalBounds.minX;
18
+ }
19
+
20
+ if (targetBounds.maxX > globalBounds.maxX) {
21
+ targetBounds.maxX = globalBounds.maxX;
22
+ }
23
+
24
+ return targetBounds;
25
+ }
@@ -0,0 +1,115 @@
1
+ const BUCKETS_PER_PIXEL = 2;
2
+ const CONDENSE_THRESHOLD = 2;
3
+
4
+ /**
5
+ * Condenses the dataset down to a lower number of points to make subsequent operations more efficient
6
+ * Will only condense when data length passes a given threshold
7
+ * Note that this works best on datasets that are relatively evenly distributed across the x axis
8
+ *
9
+ * @param {Array<Array>} data
10
+ * @param {{data: [], minX: Number, maxX: Number, length: Number}} swap
11
+ * @param {Number} minX
12
+ * @param maxX
13
+ * @param renderWidth
14
+ * @param dataChanged
15
+ * @return {{data: [], minX: Number, maxX: Number, length: Number}}
16
+ */
17
+ export default function condenseDataSpace({ data, swap, minX, maxX, renderWidth, dataChanged }) {
18
+ const targetBucketCount = renderWidth*BUCKETS_PER_PIXEL;
19
+
20
+ const useSwap = !dataChanged && swap && swap.minX === minX && swap.maxX <= maxX && swap.length <= data.length;
21
+
22
+ let partiallyCondensedData;
23
+ if (useSwap) {
24
+ partiallyCondensedData = swap.data;
25
+
26
+ if (data.length > swap.length) {
27
+ // always overwrite the last in case it was mangled by the selected space interpolation
28
+ if (data.length > 0 && partiallyCondensedData.length > 0) {
29
+ partiallyCondensedData[partiallyCondensedData.length - 1] = data[swap.length - 1];
30
+ }
31
+
32
+ partiallyCondensedData = partiallyCondensedData.concat(data.slice(swap.length));
33
+ }
34
+ } else {
35
+ partiallyCondensedData = [...data];
36
+ }
37
+
38
+ if (partiallyCondensedData.length / targetBucketCount < CONDENSE_THRESHOLD*2) { // * 2 because min and max
39
+ return {
40
+ data: partiallyCondensedData,
41
+ minX,
42
+ maxX,
43
+ length: data.length
44
+ };
45
+ }
46
+
47
+ const condensedData = [];
48
+ const bucketSize = (maxX - minX)/targetBucketCount;
49
+ let minInBucket = null;
50
+ let maxInBucket = null;
51
+ let currentBucketIndex = 0;
52
+
53
+ // always add the first point so that x ranges are preserved
54
+ if (data.length) {
55
+ condensedData.push(data[0]);
56
+ }
57
+
58
+ for (let tuple of partiallyCondensedData) {
59
+ const [x, y] = tuple;
60
+
61
+ if (y === null) {
62
+ continue;
63
+ }
64
+
65
+ const bucketIndex = Math.floor((x - minX)/bucketSize);
66
+
67
+ if (bucketIndex !== currentBucketIndex) {
68
+ if (minInBucket && maxInBucket) {
69
+ if (minInBucket === maxInBucket) {
70
+ if (condensedData[condensedData.length - 1] !== minInBucket) {
71
+ condensedData.push(minInBucket);
72
+ }
73
+ } else if (minInBucket[0] < maxInBucket[0]) {
74
+ condensedData.push(maxInBucket, minInBucket);
75
+ } else {
76
+ condensedData.push(minInBucket, maxInBucket);
77
+ }
78
+ }
79
+
80
+ minInBucket = null;
81
+ maxInBucket = null;
82
+ currentBucketIndex = bucketIndex;
83
+ }
84
+
85
+ if (!minInBucket || y < minInBucket[1]) {
86
+ minInBucket = tuple;
87
+ }
88
+
89
+ if (!maxInBucket || y > maxInBucket[1]) {
90
+ maxInBucket = tuple;
91
+ }
92
+ }
93
+
94
+ if (minInBucket && maxInBucket) {
95
+ if (minInBucket === maxInBucket) {
96
+ condensedData.push(minInBucket);
97
+ } else if (minInBucket[0] < maxInBucket[0]) {
98
+ condensedData.push(maxInBucket, minInBucket);
99
+ } else {
100
+ condensedData.push(minInBucket, maxInBucket);
101
+ }
102
+ }
103
+
104
+ // always add the last point so that x ranges are preserved
105
+ if (data.length >= 2 && data[data.length - 1] !== minInBucket && data[data.length - 1] !== maxInBucket) {
106
+ condensedData.push(data[data.length - 1]);
107
+ }
108
+
109
+ return {
110
+ data: condensedData,
111
+ minX,
112
+ maxX,
113
+ length: data.length
114
+ };
115
+ }