@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,1770 @@
1
+ import simpleSeriesToDataSpace from './space_conversions/simple_series_to_data_space';
2
+ import calculateDataBounds from './calculate_data_bounds';
3
+ import mergeBounds from './merge_bounds';
4
+ import expandBounds from './expand_bounds';
5
+ import selectionFromGlobalBounds from './selection_from_global_bounds';
6
+ import dataSpaceToSelectedSpace from './space_conversions/data_space_to_selected_space';
7
+ import selectedSpaceToRenderSpace from './space_conversions/selected_space_to_render_space';
8
+ import Eventable from '../eventable';
9
+ import boundCalculatorFromSelection from './bound_calculator_from_selection';
10
+ import calculateTooltipState, {toggleTooltipSaved} from './calculate_tooltip_state';
11
+ import getDefaultBoundsCalculator from './get_default_bounds_calculator';
12
+ import inferType from './infer_type';
13
+ import {SIMPLE_DATA_TYPES} from './data_types';
14
+ import generatorParamsEqual from '../helpers/generator_params_equal';
15
+ import findMatchingAxis from './find_matching_axis';
16
+ import {DPI_INCREASE} from '../renderer/size_canvas';
17
+ import {averageLoopTimes} from './average_loop_times';
18
+ import condenseDataSpace from './space_conversions/condense_data_space.js';
19
+ import calculateAnnotationsState from './calculate_annotations_state.js';
20
+ import {selectedSpaceToBackgroundSpace} from './space_conversions/selected_space_to_background_space.js';
21
+ let RustAPI;
22
+ const RustAPIPromise = import('../rust/pkg/index.js').then((module) => {
23
+ RustAPI = module;
24
+ });
25
+
26
+ export default class StateController extends Eventable {
27
+
28
+ constructor({ defaultBoundsCalculator, customBoundsSelectors, requireWASM, defaultShowIndividualPoints, defaultShowSidebar, defaultShowAnnotations, defaultShowOptions, syncPool, grapherID, sharedDataCache, sharedSubscriptions, fullscreen }) {
29
+ super();
30
+
31
+ this._requireWASM = requireWASM;
32
+ if (requireWASM) {
33
+ RustAPIPromise.then(() => {
34
+ this._markDirty();
35
+ });
36
+ }
37
+
38
+ this._series = [];
39
+ this._seriesFromOriginalSeries = new Map();
40
+
41
+ this._axes = [
42
+ {
43
+ series: [],
44
+ scale: 'linear',
45
+ side: 'left',
46
+ axisIndex: 0
47
+ }
48
+ ];
49
+ this._highlightedSeries = null;
50
+
51
+ this._showIndividualPoints = defaultShowIndividualPoints || false;
52
+ this._autoscaleY = true;
53
+ this._percentile = 100;
54
+ this._percentileAsymmetry = 0;
55
+ this._showingOptions = defaultShowOptions;
56
+ this._maxPrecision = false;
57
+ this._showingSidebar = defaultShowSidebar || false;
58
+ this._showingAnnotations = defaultShowAnnotations || false;
59
+ this._grapherID = grapherID;
60
+ this._fullscreen = fullscreen || false;
61
+
62
+ this._alwaysTooltipped = new Set();
63
+ this._tooltipState = {
64
+ mousePresent: false,
65
+ mouseX: 0,
66
+ mouseY: 0,
67
+ elementWidth: 0,
68
+ elementHeight: 0,
69
+ tooltips: []
70
+ };
71
+ this._contextMenuPosition = {
72
+ x: 0,
73
+ y: 0,
74
+ showing: false,
75
+ value: null
76
+ };
77
+ this._savedTooltips = [];
78
+ this._draggingY = false;
79
+ this._annotations = [];
80
+ this._annotationsState = {
81
+ elementWidth: 0,
82
+ annotations: []
83
+ };
84
+ this._enumMap = {};
85
+ this._hasXEnum = false;
86
+
87
+ this._timingBuffer = [];
88
+ this._timingIndex = 0;
89
+ this._timingFrameCount = 0;
90
+
91
+ this._modifiedSeries = new Set();
92
+ this._deferredEmissions = {};
93
+ this._deferredPriorityEmissions = {};
94
+ this.primaryRenderer = null;
95
+ this.rangeGraphRenderer = null;
96
+
97
+ this._boundsCalculator = getDefaultBoundsCalculator(defaultBoundsCalculator, customBoundsSelectors);
98
+ this._boundsHistory = [this._boundsCalculator];
99
+ this._boundsIndex = 0;
100
+
101
+ this._dataCache = sharedDataCache || new Map();
102
+ this._subscriptions = sharedSubscriptions || new Map();
103
+ this._subscriptionsShared = !!sharedSubscriptions;
104
+ this._observablesToSeries = new Map();
105
+ this._generators = new Set();
106
+ this._generatorsToSeries = new Map();
107
+ this._generatorCallArgs = new Map();
108
+ this._seriesChangedFromPromises = new Set();
109
+
110
+ this._syncPool = syncPool;
111
+ if (this._syncPool) {
112
+ this._syncPool.add(this);
113
+ }
114
+
115
+ this._onDataChange();
116
+ }
117
+
118
+ dispose() {
119
+ this.emit('dispose', this);
120
+
121
+ this.clearListeners();
122
+
123
+ if (!this._subscriptionsShared) {
124
+ for (let listener of this._subscriptions.values()) {
125
+ listener.unsubscribe();
126
+ }
127
+ this._subscriptions.clear();
128
+ }
129
+
130
+ for (let singleSeries of this._series) {
131
+ this._removeSeries(singleSeries);
132
+ }
133
+
134
+ if (this._syncPool) {
135
+ this._syncPool.remove(this);
136
+ }
137
+
138
+ this.disposed = true;
139
+ }
140
+
141
+ setSeries(series) {
142
+ const userSeries = this._series.filter((singleSeries) => singleSeries.userCreated);
143
+ const propsSeries = this._series.filter((singleSeries) => !singleSeries.userCreated);
144
+
145
+ if (series.length === propsSeries.length) {
146
+ let anyDifferent = false;
147
+ for (let i = 0; i < series.length; i++) {
148
+ if (series[i] !== this._series[i].originalSeries) {
149
+ anyDifferent = true;
150
+ break;
151
+ }
152
+ }
153
+
154
+ if (!anyDifferent) {
155
+ return;
156
+ }
157
+ } else {
158
+ this._mustResize = this._mustResize || this._fullscreen;
159
+ }
160
+
161
+ const newSeriesSet = new Set(series);
162
+ for (let singleSeries of propsSeries) {
163
+ if (!newSeriesSet.has(singleSeries.originalSeries)) {
164
+ this._removeSeries(singleSeries);
165
+ }
166
+ }
167
+
168
+ this._series.splice(0);
169
+ for (let i = 0; i < series.length; i++) {
170
+ const originalSeries = series[i];
171
+
172
+ let singleSeries = this._seriesFromOriginalSeries.get(originalSeries);
173
+ if (!singleSeries) {
174
+ singleSeries = {
175
+ ...originalSeries,
176
+ originalSeries
177
+ };
178
+ this._seriesFromOriginalSeries.set(originalSeries, singleSeries);
179
+
180
+ if (singleSeries.defaultAlwaysTooltipped) {
181
+ this._alwaysTooltipped.add(singleSeries);
182
+ this._tooltipsChanged = true;
183
+ this.deferredEmit('always_tooltipped_changed', this._alwaysTooltipped);
184
+ }
185
+ }
186
+
187
+ singleSeries.index = i;
188
+ this._series.push(singleSeries);
189
+ this._assignAxisTo(singleSeries);
190
+
191
+ if (singleSeries.hidden) {
192
+ this._hideSeries(singleSeries);
193
+ }
194
+ }
195
+
196
+ for (let i = 0; i < userSeries.length; i++) {
197
+ const singleSeries = userSeries[i];
198
+ singleSeries.index = i + series.length;
199
+ this._series.push(singleSeries);
200
+ }
201
+
202
+ this.deferredEmit('series_changed', this._series);
203
+
204
+ this._dataChanged = true;
205
+ this._markDirty();
206
+ }
207
+
208
+ _markDirty() {
209
+ if (this._frameRequested) {
210
+ return;
211
+ }
212
+
213
+ if (this._requireWASM && !RustAPI) {
214
+ return;
215
+ }
216
+
217
+ const frameRequestStart = performance.now();
218
+ this._frameRequested = true;
219
+ requestAnimationFrame(() => {
220
+ if (this.disposed) {
221
+ return;
222
+ }
223
+
224
+ const frameExecutionStart = performance.now();
225
+
226
+ const dataProcessingStart = performance.now();
227
+ if (this._dataChanged) {
228
+ this._onDataChange();
229
+ this._mustRerender = true;
230
+ this._tooltipsChanged = true;
231
+ this._annotationsChanged = true;
232
+ } else if (this._modifiedSeries.size) {
233
+ this._onDataAdd(this._modifiedSeries);
234
+ this._mustRerender = true;
235
+ this._tooltipsChanged = true;
236
+ this._annotationsChanged = true;
237
+ } else {
238
+ if (this._primarySizeChanged) {
239
+ this._calculatePrimarySizeDependents();
240
+ this._mustRerender = true;
241
+ }
242
+ if (this._rangeGraphSizeChanged) {
243
+ this._calculateRangeGraphSizeDependents();
244
+ this._mustRerender = true;
245
+ }
246
+ }
247
+ const dataProcessingEnd = performance.now();
248
+
249
+ const renderPipelineStart = performance.now();
250
+ if (this._mustRerender) {
251
+ this._render();
252
+ }
253
+ const renderPipelineEnd = performance.now();
254
+
255
+ const generatorsStart = performance.now();
256
+ if (this._mustCallGenerators) {
257
+ this._callGenerators();
258
+ }
259
+ const generatorsEnd = performance.now();
260
+
261
+ const tooltipStart = performance.now();
262
+ if (this._tooltipsChanged) {
263
+ this._recalculateTooltips();
264
+ }
265
+ const tooltipEnd = performance.now();
266
+
267
+ const contextMenuStart = performance.now();
268
+ if (this._contextMenuChanged) {
269
+ this._recalculateContextMenu();
270
+ }
271
+ const contextMenuEnd = performance.now();
272
+
273
+ const annotationStart = performance.now();
274
+ if (this._annotationsChanged) {
275
+ this._recalculateAnnotations();
276
+ }
277
+ const annotationEnd = performance.now();
278
+
279
+ if (this._mustResize) {
280
+ this.primaryRenderer.resizeDebounced();
281
+ this.rangeGraphRenderer && this.rangeGraphRenderer.resizeDebounced();
282
+ }
283
+
284
+ this._frameRequested = false;
285
+ this._mustRerender = false;
286
+ this._dataChanged = false;
287
+ this._primarySizeChanged = false;
288
+ this._rangeGraphSizeChanged = false;
289
+ this._modifiedSeries.clear();
290
+ this._seriesChangedFromPromises.clear();
291
+ this._tooltipsChanged = false;
292
+ this._contextMenuChanged = false;
293
+ this._annotationsChanged = false;
294
+ this._mustCallGenerators = false;
295
+ this._mustResize = false;
296
+
297
+ const callbacksStart = performance.now();
298
+ for (let emission of [...Object.values(this._deferredPriorityEmissions), ...Object.values(this._deferredEmissions)]) {
299
+ this.emit(...emission);
300
+ }
301
+ const callbacksEnd = performance.now();
302
+ this._deferredEmissions = {};
303
+ this._deferredPriorityEmissions = {};
304
+
305
+ const completionTime = performance.now();
306
+ const loopTime = {
307
+ frameExecution: completionTime - frameExecutionStart,
308
+ frameRequestAndExecution: completionTime - frameRequestStart,
309
+ generators: generatorsEnd - generatorsStart,
310
+ dataProcessing: dataProcessingEnd - dataProcessingStart,
311
+ renderPipeline: renderPipelineEnd - renderPipelineStart,
312
+ tooltips: tooltipEnd - tooltipStart,
313
+ contextMenu: contextMenuEnd - contextMenuStart,
314
+ annotations: annotationEnd - annotationStart,
315
+ callbacks: callbacksEnd - callbacksStart
316
+ };
317
+ this.lastLoopTime = loopTime;
318
+ if (this._timingFrameCount) {
319
+ if (this._timingBuffer.length < this._timingFrameCount) {
320
+ this._timingBuffer.push(loopTime);
321
+ } else {
322
+ this._timingBuffer[this._timingIndex % this._timingFrameCount] = loopTime;
323
+ }
324
+ this._timingIndex++;
325
+ }
326
+ this.emit('render_time', completionTime - frameExecutionStart, loopTime);
327
+ });
328
+ }
329
+
330
+ deferredEmit(...args) {
331
+ this._deferredEmissions[args[0]] = args;
332
+ }
333
+
334
+ deferredPriorityEmit(...args) {
335
+ this._deferredPriorityEmissions[args[0]] = args;
336
+ }
337
+
338
+ _seriesToSimpleData(singleSeries) {
339
+ const type = inferType(singleSeries);
340
+ if (SIMPLE_DATA_TYPES.includes(type)) {
341
+ return singleSeries.data;
342
+ }
343
+
344
+ // get a reference to whatever you already have
345
+ let currentData = this._dataCache.get(singleSeries.data);
346
+
347
+ if (currentData && this._seriesChangedFromPromises.has(singleSeries.data)) {
348
+ return currentData;
349
+ }
350
+
351
+ if (!currentData) {
352
+ currentData = [];
353
+ this._dataCache.set(singleSeries.data, currentData);
354
+ singleSeries.simpleData = currentData;
355
+ }
356
+
357
+ if (type === 'object_observable' || type === 'tuple_observable') {
358
+ let sameDataSet = this._observablesToSeries.get(singleSeries.data);
359
+ if (sameDataSet) {
360
+ const firstSameDataSeries = sameDataSet.values().next().value;
361
+ currentData = firstSameDataSeries.simpleData;
362
+ if (!currentData) {
363
+ throw new Error(`Cannot find simpleData in ${firstSameDataSeries.name || firstSameDataSeries.yKey || firstSameDataSeries.data}`);
364
+ }
365
+ this._dataCache.set(singleSeries.data, currentData);
366
+ singleSeries.simpleData = currentData;
367
+ } else {
368
+ sameDataSet = new Set();
369
+ singleSeries.simpleData = currentData;
370
+ this._observablesToSeries.set(singleSeries.data, sameDataSet);
371
+ }
372
+
373
+ sameDataSet.add(singleSeries);
374
+
375
+ this._listenToObservableData({
376
+ observable: singleSeries.data,
377
+ currentData
378
+ });
379
+ }
380
+
381
+ if (type === 'generator') {
382
+ if (!this._generatorsToSeries.has(singleSeries.data)) {
383
+ this._generatorsToSeries.set(singleSeries.data, new Set());
384
+ }
385
+
386
+ this._generatorsToSeries.get(singleSeries.data).add(singleSeries);
387
+
388
+ if (!this._generators.has(singleSeries.data)) {
389
+ this._generators.add(singleSeries.data);
390
+ this._callGenerator(singleSeries.data);
391
+ }
392
+ }
393
+
394
+ // return whatever you have; it'll fetch more in the background
395
+ return currentData;
396
+ }
397
+
398
+ _listenToPromise(singleSeries, dataPromise) {
399
+ dataPromise.then((resolved) => {
400
+ this._dataCache.set(singleSeries.data, resolved);
401
+ singleSeries.simpleData = resolved;
402
+ this._seriesChangedFromPromises.add(singleSeries.data);
403
+ this._dataChanged = true;
404
+ this._markDirty();
405
+ });
406
+ }
407
+
408
+ _listenToObservableData({ observable, currentData }) {
409
+ if (this._subscriptions.has(observable)) { // someone else already listened to it
410
+ return;
411
+ }
412
+ this._subscriptions.set(observable, true);
413
+
414
+ // listen to the new data
415
+ const listener = observable.observe((point) => {
416
+ if (Array.isArray(point)) {
417
+ for (let datum of point) {
418
+ currentData.push(datum);
419
+ }
420
+ } else {
421
+ currentData.push(point);
422
+ }
423
+
424
+ this.emit('observable_modified', observable);
425
+
426
+ this.markObservableModified(observable);
427
+ });
428
+ this._subscriptions.set(observable, listener);
429
+ }
430
+
431
+ markObservableModified(observable) {
432
+ if (!this._observablesToSeries.has(observable)) {
433
+ return;
434
+ }
435
+
436
+ for (let singleSeries of this._observablesToSeries.get(observable)) {
437
+ this._modifiedSeries.add(singleSeries);
438
+ }
439
+ this._markDirty();
440
+ }
441
+
442
+ _unsubscribeFromStaleSeries() {
443
+ const currentSeries = new Set(this._series);
444
+ for (let [observable, seriesSet] of this._observablesToSeries) {
445
+ let stillHasListener = false;
446
+
447
+ for (let singleSeries of seriesSet) {
448
+ if (currentSeries.has(singleSeries)) {
449
+ stillHasListener = true;
450
+ break;
451
+ }
452
+ }
453
+
454
+ if (!stillHasListener) {
455
+ this._subscriptions.get(observable).unsubscribe();
456
+ this._subscriptions.delete(observable);
457
+ this._observablesToSeries.delete(observable);
458
+ }
459
+ }
460
+
461
+ for (let [generator, seriesSet ] of this._generatorsToSeries) {
462
+ let stillHasSeries = false;
463
+
464
+ for (let singleSeries of seriesSet) {
465
+ if (currentSeries.has(singleSeries)) {
466
+ stillHasSeries = true;
467
+ break;
468
+ }
469
+ }
470
+
471
+ if (!stillHasSeries) {
472
+ this._generatorsToSeries.delete(generator);
473
+ this._generators.delete(generator);
474
+ this._generatorCallArgs.delete(generator);
475
+ }
476
+ }
477
+ }
478
+
479
+ async _callGenerator(generator) {
480
+ const parameters = {
481
+ minX: this._selection.minX,
482
+ maxX: this._selection.maxX,
483
+ sizing: this.primaryRenderer.sizing
484
+ };
485
+
486
+ if (generatorParamsEqual(this._generatorCallArgs.get(generator), parameters)) {
487
+ return;
488
+ }
489
+ this._generatorCallArgs.set(generator, parameters);
490
+
491
+ // Note: potential race condition if generator is stupid. For now, we'll rely on generator being smart
492
+ // Race condition: a new, faster one runs before completed
493
+ const data = await Promise.resolve(generator(parameters));
494
+
495
+ // do nothing if the generator returned nothing
496
+ if (!data) {
497
+ return;
498
+ }
499
+
500
+ // because calling the generator may have taken some time, check that there are still listeners for it
501
+ if (!this._generatorsToSeries.has(generator)) {
502
+ return;
503
+ }
504
+
505
+ this._dataChanged = true;
506
+ this._markDirty();
507
+
508
+ if (typeof data.observe === 'function') {
509
+ const currentData = [];
510
+ this._dataCache.set(generator, currentData);
511
+ for (let singleSeries of this._generatorsToSeries.get(generator)) {
512
+ singleSeries.simpleData = currentData;
513
+ }
514
+
515
+ for (let singleSeries of this._generatorsToSeries.get(generator)) {
516
+ if (!this._observablesToSeries.has(data)) {
517
+ this._observablesToSeries.set(data, new Set());
518
+ }
519
+
520
+ this._observablesToSeries.get(data).add(singleSeries);
521
+ }
522
+
523
+ this._listenToObservableData({
524
+ observable: data,
525
+ currentData
526
+ });
527
+ } else {
528
+ this._dataCache.set(generator, data);
529
+ for (let singleSeries of this._generatorsToSeries.get(generator)) {
530
+ singleSeries.simpleData = data;
531
+ }
532
+ }
533
+ }
534
+
535
+ _callGenerators() {
536
+ for (let generator of this._generators) {
537
+ this._callGenerator(generator);
538
+ }
539
+ }
540
+
541
+ _recalculateTooltips() {
542
+ if (!this.primaryRenderer || !this.primaryRenderer.sizing) {
543
+ return;
544
+ }
545
+
546
+ this._tooltipState = calculateTooltipState({
547
+ mousePresent: this._tooltipState.mousePresent,
548
+ mouseX: this._tooltipState.mouseX,
549
+ mouseY: this._tooltipState.mouseY,
550
+ sizing: this.primaryRenderer.sizing,
551
+ series: this._series,
552
+ alwaysTooltipped: this._alwaysTooltipped,
553
+ savedTooltips: this._savedTooltips,
554
+ allTooltipped: this._tooltipAllNext,
555
+ closestSpacing: this._globalBounds.closestSpacing
556
+ });
557
+ this._tooltipAllNext = false;
558
+ this.deferredEmit('tooltip_state_changed', this._tooltipState, this._tooltipStateArg);
559
+ this._tooltipStateArg = null;
560
+ }
561
+
562
+ _recalculateContextMenu() {
563
+ if (!this.primaryRenderer || !this.primaryRenderer.sizing) {
564
+ return;
565
+ }
566
+
567
+ const boundingRect = this.primaryRenderer.boundingRect;
568
+ const sizing = this.primaryRenderer.sizing;
569
+ let value = null;
570
+ let newX = null;
571
+
572
+ for (let series of this._series) {
573
+ const bounds = series.axis.currentBounds;
574
+
575
+ newX = this._contextMenuPosition.x - boundingRect.left;
576
+
577
+ // if it's on the right side, move it to the left one width
578
+ if (this._contextMenuPosition.x > (boundingRect.right - sizing.elementWidth) * 2) newX -= 130;
579
+
580
+ if (series.xKey === 'timestamp' || series.xKey === 'time' || series.xKey === 'date') {
581
+ value = new Date((this._contextMenuPosition.x - boundingRect.left) / sizing.elementWidth * (bounds.maxX - bounds.minX) + bounds.minX);
582
+ } else {
583
+ value = (this._contextMenuPosition.x - boundingRect.left) / sizing.elementWidth * (bounds.maxX - bounds.minX) + bounds.minX;
584
+ }
585
+ }
586
+ this._contextMenuPosition = {
587
+ x: newX,
588
+ y: this._contextMenuPosition.y - boundingRect.top,
589
+ showing: this._contextMenuPosition.showing,
590
+ value: value
591
+ };
592
+ this.deferredEmit('context_menu_position_changed', this._contextMenuPosition);
593
+ }
594
+
595
+ _recalculateAnnotations() {
596
+ if (!this.primaryRenderer || !this.primaryRenderer.sizing) {
597
+ return;
598
+ }
599
+
600
+ this._annotationsState = calculateAnnotationsState({
601
+ annotations: this._annotations,
602
+ series: this._series,
603
+ sizing: this.primaryRenderer.sizing,
604
+ selection: this._selection
605
+ });
606
+ this.deferredEmit('annotations_changed', this._annotationsState);
607
+ }
608
+
609
+ _onDataChange() {
610
+ this._expandYWith = [];
611
+ const dataBoundsList = [];
612
+
613
+ for (let singleSeries of this._series) {
614
+ if (singleSeries.hidden) {
615
+ continue;
616
+ }
617
+
618
+ const simpleData = this._seriesToSimpleData(singleSeries);
619
+ singleSeries.inDataSpace = simpleSeriesToDataSpace({
620
+ ...singleSeries,
621
+ data: simpleData
622
+ }, {
623
+ stateController: this
624
+ });
625
+ singleSeries.simpleDataSliceStart = simpleData.length;
626
+ singleSeries.dataBounds = calculateDataBounds(singleSeries.inDataSpace);
627
+ if (singleSeries.rendering === 'bar') {
628
+ singleSeries.dataBounds = expandBounds(singleSeries.dataBounds, { extendXForNBars: singleSeries.inDataSpace.length });
629
+ }
630
+
631
+ dataBoundsList.push(singleSeries.dataBounds);
632
+
633
+ if (singleSeries.expandYWith) {
634
+ this._expandYWith.push(singleSeries.expandYWith);
635
+ }
636
+ }
637
+
638
+ this._unsubscribeFromStaleSeries();
639
+
640
+ this._dataBounds = mergeBounds(dataBoundsList);
641
+
642
+ this._recalculateSelection({ disableSwap: true});
643
+
644
+ for (let singleSeries of this._series) {
645
+ if (singleSeries.hidden) {
646
+ continue;
647
+ }
648
+
649
+ singleSeries.selectedBounds = calculateDataBounds(singleSeries.inSelectedSpace.data, { percentile: this._percentile, percentileAsymmetry: this._percentileAsymmetry });
650
+ }
651
+
652
+ this._recalculateAxisBounds();
653
+
654
+ for (let singleSeries of this._series) {
655
+ if (singleSeries.hidden) {
656
+ continue;
657
+ }
658
+
659
+ this._calculatePrimarySizeDependents(singleSeries, { dataChanged: true });
660
+ this._calculateRangeGraphSizeDependents(singleSeries, { dataChanged: true });
661
+ }
662
+ }
663
+
664
+ _onDataAdd(modifiedSeries) {
665
+ const newData = new Map();
666
+ const modifiedAxes = new Set();
667
+
668
+ const modifiedSeriesAndDependents = new Set();
669
+ for (let singleSeries of modifiedSeries) {
670
+ modifiedSeriesAndDependents.add(singleSeries);
671
+ }
672
+
673
+ for (let singleSeries of modifiedSeries) {
674
+ modifiedAxes.add(singleSeries.axis);
675
+
676
+ if (!singleSeries.inDataSpace) {
677
+ throw new Error('inDataSpace must be present for onDataAdd to be called');
678
+ }
679
+
680
+ const simpleData = this._seriesToSimpleData(singleSeries);
681
+
682
+ let prevY;
683
+ if (singleSeries.inDataSpace.length) {
684
+ prevY = singleSeries.inDataSpace[singleSeries.inDataSpace.length - 1][1];
685
+ }
686
+
687
+ const newDataInDataSpace = simpleSeriesToDataSpace(singleSeries, {
688
+ data: simpleData.slice(singleSeries.simpleDataSliceStart || 0),
689
+ valueXStart: singleSeries.inDataSpace.length,
690
+ prevY,
691
+ stateController: this
692
+ });
693
+ newData.set(singleSeries, newDataInDataSpace);
694
+ singleSeries.simpleDataSliceStart = simpleData.length;
695
+
696
+ if (newDataInDataSpace.length < 32) {
697
+ singleSeries.inDataSpace.push(...newDataInDataSpace);
698
+ } else {
699
+ for (let point of newDataInDataSpace) {
700
+ singleSeries.inDataSpace.push(point);
701
+ }
702
+ }
703
+
704
+ let newDataBounds = calculateDataBounds(newDataInDataSpace);
705
+ if (singleSeries.rendering === 'bar') {
706
+ newDataBounds = expandBounds(newDataBounds, { extendXForNBars: singleSeries.inDataSpace.length });
707
+ }
708
+
709
+ singleSeries.dataBounds = mergeBounds([singleSeries.dataBounds, newDataBounds]);
710
+ this._dataBounds = mergeBounds([this._dataBounds, newDataBounds]);
711
+
712
+ // save these off for debugging only
713
+ singleSeries.newPointCount = newDataInDataSpace.length;
714
+ singleSeries.newDataBounds = newDataBounds;
715
+ }
716
+
717
+ const previousSelection = this._selection;
718
+ this._recalculateSelection();
719
+
720
+ for (let singleSeries of modifiedSeries) {
721
+ if (!singleSeries.inSelectedSpace.data.length) { // empty, trivially fast
722
+ singleSeries.selectedBounds = calculateDataBounds(singleSeries.inSelectedSpace.data);
723
+ continue;
724
+ }
725
+
726
+ const firstX = singleSeries.inSelectedSpace.data[0][0];
727
+ const lastX = singleSeries.inSelectedSpace.data[singleSeries.inSelectedSpace.data.length - 1][0];
728
+
729
+ const oldBoundSubsetOfNewBounds = previousSelection.minX >= firstX && previousSelection.maxX <= lastX;
730
+
731
+ if (!oldBoundSubsetOfNewBounds || this._percentile !== 100) {
732
+ // this is significantly slower, but it's too complex to diff the old bounds and new bounds or diff percentiles
733
+ // besides, it not being a subset hopefully means its a lower volume of data
734
+ singleSeries.selectedBounds = calculateDataBounds(singleSeries.inSelectedSpace.data, { percentile: this._percentile, percentileAsymmetry: this._percentileAsymmetry});
735
+ continue;
736
+ }
737
+
738
+ // note: we could do a binary search here, but there are typically only a few points added each frame, so it isn't worth it
739
+ const newSelectedData = newData.get(singleSeries)
740
+ .filter((tuple) => tuple[0] >= this._selection.minX && tuple[0] <= this._selection.maxX);
741
+
742
+ singleSeries.newSelectedData = newSelectedData;
743
+ singleSeries.selectedBounds = mergeBounds([singleSeries.selectedBounds, calculateDataBounds(newSelectedData)]);
744
+ }
745
+
746
+ this._recalculateAxisBounds();
747
+
748
+ this.deferredPriorityEmit('axis_bounds_changed', this._axes.map(({ currentBounds }) => currentBounds));
749
+
750
+ for (let axis of modifiedAxes) {
751
+ for (let singleSeries of axis.series) {
752
+ this._calculatePrimarySizeDependents(singleSeries);
753
+ this._calculateRangeGraphSizeDependents(singleSeries);
754
+ }
755
+ }
756
+ }
757
+
758
+ _recalculateAxisBounds() {
759
+ for (let axis of this._axes) {
760
+ if (this._selection.fixedY || !this._autoscaleY) {
761
+ axis.targetBounds = this._selection;
762
+ if (axis.targetBounds.byAxis) {
763
+ axis.targetBounds = axis.targetBounds.byAxis[axis.axisIndex];
764
+ }
765
+
766
+ axis.currentBounds = axis.targetBounds;
767
+ continue;
768
+ }
769
+
770
+ const selectedBoundsList = [];
771
+ const expandYWith = [];
772
+
773
+ for (let singleSeries of axis.series) {
774
+ selectedBoundsList.push(singleSeries.selectedBounds);
775
+
776
+ if (singleSeries.expandYWith) {
777
+ expandYWith.push(...singleSeries.expandYWith);
778
+ }
779
+
780
+ if (singleSeries.rendering === 'bar') {
781
+ expandYWith.push(singleSeries.dataBounds.minY, singleSeries.dataBounds.maxY);
782
+ }
783
+ }
784
+
785
+ axis.selectedDataBounds = mergeBounds(selectedBoundsList);
786
+ axis.targetBounds = axis.currentBounds = expandBounds(axis.selectedDataBounds, {expandYWith});
787
+ }
788
+ this.deferredPriorityEmit('axis_bounds_changed', this._axes.map(({ currentBounds }) => currentBounds));
789
+ }
790
+
791
+ /**
792
+ * Recalculates global bounds, selection, and data in selected space (for each series)
793
+ * REQUIRES that this._dataBounds and this._expandYWith are set & up-to-date
794
+ *
795
+ * @private
796
+ */
797
+ _recalculateSelection({disableSwap = false}={}) {
798
+ this._globalBounds = expandBounds(this._dataBounds, {expandYWith: this._expandYWith.flat()});
799
+ this.deferredPriorityEmit('global_bounds_changed', this._globalBounds);
800
+ this._selection = selectionFromGlobalBounds(this._globalBounds, this._boundsCalculator);
801
+ this.deferredPriorityEmit('selection_changed', this._selection);
802
+
803
+ for (let singleSeries of this._series) {
804
+ if (singleSeries.hidden) {
805
+ continue;
806
+ }
807
+
808
+ singleSeries.inSelectedSpace = dataSpaceToSelectedSpace({
809
+ data: singleSeries.inDataSpace,
810
+ swap: disableSwap ? null : singleSeries.inSelectedSpace,
811
+ minX: this._selection.minX,
812
+ maxX: this._selection.maxX,
813
+ ignoreDiscontinuities: singleSeries.ignoreDiscontinuities,
814
+ square: singleSeries.square
815
+ });
816
+ }
817
+ }
818
+
819
+ _render() {
820
+ if (!this.primaryRenderer || !this.primaryRenderer.sizing) {
821
+ return;
822
+ }
823
+
824
+ this.primaryRenderer.clear();
825
+ if (this.rangeGraphRenderer) {
826
+ this.rangeGraphRenderer.clear();
827
+ }
828
+
829
+ for (let singleSeries of this._series) {
830
+ if (singleSeries.hidden) {
831
+ continue;
832
+ }
833
+
834
+ const shadowColor = {
835
+ day: 'white',
836
+ export: 'transparent',
837
+ night: 'black'
838
+ }[this._theme] || 'black';
839
+
840
+ const shadowBlur = undefined;
841
+
842
+ this.primaryRenderer.renderBackground(singleSeries.inBackgroundSpacePrimary);
843
+
844
+ this.primaryRenderer.render(singleSeries, singleSeries.inRenderSpacePrimary, {
845
+ highlighted: this._highlightedSeries === singleSeries.index,
846
+ showIndividualPoints: this._showIndividualPoints,
847
+ shadowColor,
848
+ shadowBlur,
849
+ defaultLineWidth: this._defaultLineWidth,
850
+ globalBounds: this._globalBounds
851
+ });
852
+
853
+ if (this.rangeGraphRenderer && this.rangeGraphRenderer.sizing) {
854
+ this.rangeGraphRenderer.render(singleSeries, singleSeries.inRenderSpaceRangeGraph, {
855
+ shadowColor: 'transparent',
856
+ shadowBlur: 0,
857
+ width: 1,
858
+ bounds: this._globalBounds,
859
+ globalBounds: this._globalBounds
860
+ });
861
+ }
862
+ }
863
+ }
864
+
865
+ _calculatePrimarySizeDependents(singleSeries, { dataChanged=false } = {}) {
866
+ if (!this.primaryRenderer || !this.primaryRenderer.sizing) {
867
+ return;
868
+ }
869
+
870
+ if (!singleSeries) {
871
+ for (let s of this._series) {
872
+ this._calculatePrimarySizeDependents(s);
873
+ }
874
+ return;
875
+ }
876
+
877
+ if (!this._series.includes(singleSeries)) {
878
+ throw new Error('Series no longer exists');
879
+ }
880
+
881
+ if (singleSeries.hidden) {
882
+ return;
883
+ }
884
+
885
+ const { currentBounds, scale } = singleSeries.axis;
886
+ const renderWidth = Math.ceil(this.primaryRenderer.sizing.renderWidth/DPI_INCREASE);
887
+ const renderHeight = Math.ceil(this.primaryRenderer.sizing.renderHeight);
888
+
889
+ singleSeries.inCondensedSelectedSpacePrimary = condenseDataSpace({
890
+ data: singleSeries.inSelectedSpace.data,
891
+ swap: singleSeries.inCondensedSelectedSpacePrimary,
892
+ minX: currentBounds.minX,
893
+ maxX: currentBounds.maxX,
894
+ renderWidth,
895
+ dataChanged
896
+ });
897
+
898
+ singleSeries.inRenderSpacePrimary = selectedSpaceToRenderSpace({
899
+ data: singleSeries.inCondensedSelectedSpacePrimary.data,
900
+ swap: singleSeries.inRenderSpacePrimary,
901
+ minX: currentBounds.minX,
902
+ maxX: currentBounds.maxX,
903
+ minY: currentBounds.minY,
904
+ maxY: currentBounds.maxY,
905
+ renderWidth,
906
+ renderHeight,
907
+ scale,
908
+ dataChanged
909
+ });
910
+
911
+ singleSeries.inBackgroundSpacePrimary = selectedSpaceToBackgroundSpace({
912
+ data: singleSeries.inCondensedSelectedSpacePrimary.data,
913
+ background: singleSeries.background,
914
+ swap: singleSeries.inBackgroundSpacePrimary,
915
+ minX: currentBounds.minX,
916
+ maxX: currentBounds.maxX
917
+ });
918
+ }
919
+
920
+ _calculateRangeGraphSizeDependents(singleSeries, { dataChanged=false } = {}) {
921
+ if (!this.rangeGraphRenderer || !this.rangeGraphRenderer.sizing) {
922
+ return;
923
+ }
924
+
925
+ if (!singleSeries) {
926
+ for (let s of this._series) {
927
+ this._calculateRangeGraphSizeDependents(s);
928
+ }
929
+ return;
930
+ }
931
+
932
+ if (!this._series.includes(singleSeries)) {
933
+ throw new Error('Series no longer exists');
934
+ }
935
+
936
+ if (singleSeries.hidden) {
937
+ return;
938
+ }
939
+
940
+ const { scale } = singleSeries.axis;
941
+ const globalBounds = this._globalBounds;
942
+ const renderWidth = Math.ceil(this.rangeGraphRenderer.sizing.renderWidth/DPI_INCREASE);
943
+ const renderHeight = Math.ceil(this.rangeGraphRenderer.sizing.renderHeight);
944
+
945
+ singleSeries.inSelectedSpaceRangeGraph = dataSpaceToSelectedSpace({
946
+ data: singleSeries.inDataSpace,
947
+ swap: singleSeries.inSelectedSpaceRangeGraph,
948
+ minX: globalBounds.minX,
949
+ maxX: globalBounds.maxX,
950
+ ignoreDiscontinuities: singleSeries.ignoreDiscontinuities,
951
+ square: singleSeries.square
952
+ });
953
+
954
+ singleSeries.inCondensedSelectedSpaceRangeGraph = condenseDataSpace({
955
+ data: singleSeries.inSelectedSpaceRangeGraph.data,
956
+ swap: singleSeries.inCondensedSelectedSpaceRangeGraph,
957
+ minX: globalBounds.minX,
958
+ maxX: globalBounds.maxX,
959
+ renderWidth,
960
+ dataChanged
961
+ });
962
+
963
+ singleSeries.inRenderSpaceRangeGraph = selectedSpaceToRenderSpace({
964
+ data: singleSeries.inCondensedSelectedSpaceRangeGraph.data,
965
+ swap: singleSeries.inRenderSpaceRangeGraph,
966
+ minX: globalBounds.minX,
967
+ maxX: globalBounds.maxX,
968
+ minY: globalBounds.minY,
969
+ maxY: globalBounds.maxY,
970
+ renderWidth,
971
+ renderHeight,
972
+ scale,
973
+ dataChanged
974
+ });
975
+ }
976
+
977
+ _createAxis({ side }) {
978
+ const axis = {
979
+ series: [],
980
+ scale: 'linear',
981
+ side,
982
+ axisIndex: this._axes.length
983
+ };
984
+ this._axes.push(axis);
985
+ return axis;
986
+ }
987
+
988
+ _moveAxis(singleSeries, axisIndex) {
989
+ if (!this._series.includes(singleSeries)) {
990
+ throw new Error('Series no longer exists');
991
+ }
992
+
993
+ let newAxis;
994
+ if (axisIndex === 'new-left' || axisIndex === 'new-right') {
995
+ newAxis = this._createAxis({ side: axisIndex.split('-')[1] });
996
+ } else {
997
+ newAxis = this._axes[parseInt(axisIndex)];
998
+ }
999
+
1000
+ if (singleSeries.axis === newAxis) {
1001
+ return;
1002
+ }
1003
+
1004
+ const oldAxis = singleSeries.axis;
1005
+ const oldIndex = oldAxis.series.indexOf(singleSeries);
1006
+ if (oldIndex === -1) {
1007
+ throw new Error('Series not present in axis');
1008
+ }
1009
+ oldAxis.series.splice(oldIndex, 1);
1010
+
1011
+ newAxis.series.push(singleSeries);
1012
+ singleSeries.axis = newAxis;
1013
+
1014
+ this._dataChanged = true;
1015
+ this.deferredEmit('axes_changed', this._axes);
1016
+ this.deferredEmit('left_axes_changed', this.leftAxes);
1017
+ this.deferredEmit('right_axes_changed', this.rightAxes);
1018
+ this.deferredEmit('exported_axes_changed', this.exportedAxes);
1019
+ this._markDirty();
1020
+ }
1021
+
1022
+ _assignAxisTo(singleSeries) {
1023
+ if (singleSeries.axis && typeof singleSeries.axis === 'object') {
1024
+ return;
1025
+ }
1026
+
1027
+ let axis;
1028
+
1029
+ if (singleSeries.axisIndex) {
1030
+ const { axisIndex } = singleSeries;
1031
+
1032
+ if (axisIndex === 'new-left' || axisIndex === 'new-right') {
1033
+ axis = this._createAxis({ side: axisIndex.split('-')[1] });
1034
+ } else {
1035
+ axis = this._axes[parseInt(axisIndex)];
1036
+ }
1037
+ } else if (singleSeries.axis) {
1038
+ singleSeries.originalAxis = singleSeries.axis;
1039
+ let [side, number] = singleSeries.axis.split('-');
1040
+ axis = findMatchingAxis({ axes: this._axes, side, number });
1041
+
1042
+ if (!axis) {
1043
+ axis = this._createAxis({
1044
+ side
1045
+ });
1046
+ }
1047
+ } else {
1048
+ axis = this._axes[0];
1049
+ }
1050
+
1051
+ axis.series.push(singleSeries);
1052
+ singleSeries.axis = axis;
1053
+
1054
+ this.deferredEmit('axes_changed', this._axes);
1055
+ this.deferredEmit('exported_axes_changed', this.exportedAxes);
1056
+
1057
+ if (singleSeries.axis.side === 'left') {
1058
+ this.deferredEmit('left_axes_changed', this.leftAxes);
1059
+ } else {
1060
+ this.deferredEmit('right_axes_changed', this.rightAxes);
1061
+ }
1062
+ }
1063
+
1064
+ _removeSeries(singleSeries) {
1065
+ this._seriesFromOriginalSeries.delete(singleSeries.originalSeries);
1066
+
1067
+ const { axis, data } = singleSeries;
1068
+ axis.series.splice(axis.series.indexOf(singleSeries), 1);
1069
+ const sameDataSet = this._observablesToSeries.get(data);
1070
+ if (sameDataSet) {
1071
+ sameDataSet.delete(singleSeries);
1072
+ if (sameDataSet.size === 0) {
1073
+ this._observablesToSeries.delete(data);
1074
+ }
1075
+ }
1076
+
1077
+ if (this._generatorsToSeries.has(data)) {
1078
+ this._generatorsToSeries.get(data).delete(singleSeries);
1079
+ if (this._generatorsToSeries.get(data).size === 0) {
1080
+ this._generatorsToSeries.delete(data);
1081
+ this._generators.delete(data);
1082
+ }
1083
+ }
1084
+
1085
+ this._alwaysTooltipped.delete(singleSeries);
1086
+
1087
+ singleSeries.axis = singleSeries.originalAxis;
1088
+ delete singleSeries.originalAxis;
1089
+ delete singleSeries.inDataSpace;
1090
+ delete singleSeries.inSelectedSpace;
1091
+ delete singleSeries.inValueSpacePrimary;
1092
+ delete singleSeries.inValueSpaceRangeGraph;
1093
+ delete singleSeries.inRenderSpacePrimary;
1094
+ delete singleSeries.inRenderSpaceRangeGraph;
1095
+ delete singleSeries.newPointCount;
1096
+ delete singleSeries.newDataBounds;
1097
+ delete singleSeries.simpleDataSliceStart;
1098
+
1099
+ this.deferredEmit('axes_changed', this._axes);
1100
+ this.deferredEmit('exported_axes_changed', this.exportedAxes);
1101
+
1102
+ if (axis.side === 'left') {
1103
+ this.deferredEmit('left_axes_changed', this.leftAxes);
1104
+ } else {
1105
+ this.deferredEmit('right_axes_changed', this.rightAxes);
1106
+ }
1107
+ }
1108
+
1109
+ _hideSeries(singleSeries) {
1110
+ const { axis } = singleSeries;
1111
+ const indexInAxis = axis.series.indexOf(singleSeries);
1112
+ singleSeries.indexInAxis = indexInAxis;
1113
+ axis.series.splice(indexInAxis, 1);
1114
+
1115
+ this.deferredEmit('axes_changed', this._axes);
1116
+
1117
+ if (axis.side === 'left') {
1118
+ this.deferredEmit('left_axes_changed', this.leftAxes);
1119
+ } else {
1120
+ this.deferredEmit('right_axes_changed', this.rightAxes);
1121
+ }
1122
+ }
1123
+
1124
+ _showSeries(singleSeries) {
1125
+ const { axis } = singleSeries;
1126
+ axis.series.splice(singleSeries.indexInAxis, 0, singleSeries);
1127
+
1128
+ this.deferredEmit('axes_changed', this._axes);
1129
+
1130
+ if (axis.side === 'left') {
1131
+ this.deferredEmit('left_axes_changed', this.leftAxes);
1132
+ } else {
1133
+ this.deferredEmit('right_axes_changed', this.rightAxes);
1134
+ }
1135
+ }
1136
+
1137
+ /*
1138
+ * Getters
1139
+ *******************
1140
+ */
1141
+
1142
+ get boundCalculator() {
1143
+ return this._boundsCalculator;
1144
+ }
1145
+
1146
+ get axes() {
1147
+ return this._axes;
1148
+ }
1149
+
1150
+ get leftAxes() {
1151
+ const leftAxes = this._axes.filter(({ side, series }) => side === 'left' && series.length > 0).reverse();
1152
+
1153
+ if (leftAxes.length === 0 && this.rightAxes.length === 0) {
1154
+ leftAxes.push(this._axes[0]);
1155
+ }
1156
+
1157
+ return leftAxes;
1158
+ }
1159
+
1160
+ get rightAxes() {
1161
+ return this._axes.filter(({ side, series }) => side === 'right' && series.length > 0 );
1162
+ }
1163
+
1164
+ get bounds() {
1165
+ return this._axes.map(({ targetBounds }) => targetBounds);
1166
+ }
1167
+
1168
+ get selection() {
1169
+ return this._selection;
1170
+ }
1171
+
1172
+ get globalBounds() {
1173
+ return this._globalBounds;
1174
+ }
1175
+
1176
+ get series() {
1177
+ return this._series;
1178
+ }
1179
+
1180
+ get highlightedSeries() {
1181
+ return this._highlightedSeries;
1182
+ }
1183
+
1184
+ get showIndividualPoints() {
1185
+ return this._showIndividualPoints;
1186
+ }
1187
+
1188
+ get autoscaleY() {
1189
+ return this._autoscaleY;
1190
+ }
1191
+
1192
+ get boundHistory() {
1193
+ return {
1194
+ hasNextBounds: this._boundsIndex < this._boundsHistory.length - 1,
1195
+ hasPreviousBounds: this._boundsIndex > 0
1196
+ };
1197
+ }
1198
+
1199
+ get tooltipState() {
1200
+ return this._tooltipState;
1201
+ }
1202
+
1203
+ get contextMenuState() {
1204
+ return this._contextMenuPosition;
1205
+ }
1206
+
1207
+ get alwaysTooltipped() {
1208
+ return this._alwaysTooltipped;
1209
+ }
1210
+
1211
+ get draggingY() {
1212
+ return this._draggingY;
1213
+ }
1214
+
1215
+ get averageLoopTime() {
1216
+ return averageLoopTimes(this._timingBuffer);
1217
+ }
1218
+
1219
+ get exportedAxes() {
1220
+ const axisToName = new Map();
1221
+ let leftCount = 0;
1222
+ let rightCount = 0;
1223
+
1224
+ for (let axis of this._axes) {
1225
+ let name;
1226
+ if (axis.side === 'left') {
1227
+ name = `left-${leftCount}`;
1228
+ leftCount++;
1229
+ } else {
1230
+ name = `right-${rightCount}`;
1231
+ rightCount++;
1232
+ }
1233
+
1234
+ for (let singleSeries of axis.series) {
1235
+ axisToName.set(singleSeries, name);
1236
+ }
1237
+ }
1238
+
1239
+ return this.series.map((singleSeries) => {
1240
+ return {
1241
+ ...singleSeries.originalSeries,
1242
+ axis: axisToName.get(singleSeries)
1243
+ };
1244
+ });
1245
+ }
1246
+
1247
+ get percentile() {
1248
+ return this._percentile;
1249
+ }
1250
+
1251
+ get percentileAsymmetry() {
1252
+ return this._percentileAsymmetry;
1253
+ }
1254
+
1255
+ get showingOptions() {
1256
+ return this._showingOptions;
1257
+ }
1258
+
1259
+ get maxPrecision() {
1260
+ return this._maxPrecision;
1261
+ }
1262
+
1263
+ get showingSidebar() {
1264
+ return this._showingSidebar;
1265
+ }
1266
+
1267
+ get showingAnnotations() {
1268
+ return this._showingAnnotations;
1269
+ }
1270
+
1271
+ get userCreatedSeries() {
1272
+ return this._series.filter((singleSeries) => singleSeries.userCreated);
1273
+ }
1274
+
1275
+ get grapherID() {
1276
+ return this._grapherID;
1277
+ }
1278
+
1279
+ get annotationState() {
1280
+ return this._annotationsState;
1281
+ }
1282
+
1283
+ get sizing() {
1284
+ return this.primaryRenderer && this.primaryRenderer.sizing;
1285
+ }
1286
+
1287
+ get theme() {
1288
+ return this._theme;
1289
+ }
1290
+
1291
+ get exportMode() {
1292
+ return this._exportMode;
1293
+ }
1294
+
1295
+ get enumMap() {
1296
+ return this._enumMap;
1297
+ }
1298
+
1299
+ get hasXEnum() {
1300
+ return this._hasXEnum;
1301
+ }
1302
+
1303
+ /*
1304
+ * Setters / ways to mutate the state that aren't the underlying data changing
1305
+ ***********************************************************************************
1306
+ */
1307
+
1308
+ markSizeChanged(renderer) {
1309
+ if (renderer === this.primaryRenderer) {
1310
+ this.deferredEmit('primary_size_change', this.primaryRenderer.sizing);
1311
+ this.deferredEmit('primary_bounding_rect_change', this.primaryRenderer.boundingRect);
1312
+ this._primarySizeChanged = true;
1313
+ } else if (renderer === this.rangeGraphRenderer) {
1314
+ this.deferredEmit('range_graph_size_change', this.rangeGraphRenderer.sizing);
1315
+ this.deferredEmit('range_graph_bounding_rect_change', this.rangeGraphRenderer.boundingRect);
1316
+ this._rangeGraphSizeChanged = true;
1317
+ }
1318
+
1319
+ this._mustRerender = true;
1320
+ this._tooltipsChanged = true;
1321
+ this._annotationsChanged = true;
1322
+ this._mustCallGenerators = true;
1323
+ this._markDirty();
1324
+ }
1325
+
1326
+ set theme(value) {
1327
+ this._theme = value;
1328
+ this._mustRerender = true;
1329
+ this.deferredEmit('theme_change', this._theme);
1330
+ this._markDirty();
1331
+ }
1332
+
1333
+ set exportMode(value) {
1334
+ this._exportMode = value;
1335
+
1336
+ if (value) {
1337
+ this._nonExportTheme = this.theme;
1338
+ this.theme = 'export';
1339
+ } else {
1340
+ this.theme = this._nonExportTheme;
1341
+ }
1342
+
1343
+ this.deferredEmit('export_mode_change', this._exportMode);
1344
+ this._markDirty();
1345
+ }
1346
+
1347
+ set defaultLineWidth(value) {
1348
+ this._defaultLineWidth = value;
1349
+ this._mustRerender = true;
1350
+ this._markDirty();
1351
+ }
1352
+
1353
+ set boundCalculator(boundingFunction) {
1354
+ if (!boundingFunction) {
1355
+ return;
1356
+ }
1357
+
1358
+ if (boundingFunction === this._boundsCalculator) {
1359
+ return;
1360
+ }
1361
+
1362
+ if (boundingFunction.debounceHistory) {
1363
+ clearTimeout(this._boundingCalculatorDebouncer);
1364
+ this._boundingCalculatorDebouncer = setTimeout(() => {
1365
+ this._addBoundCalculatorToHistory(boundingFunction);
1366
+ }, 250);
1367
+ } else {
1368
+ this._addBoundCalculatorToHistory(boundingFunction);
1369
+ }
1370
+
1371
+ this._boundsCalculator = boundingFunction;
1372
+ this.deferredEmit('bound_calculator_changed', this._boundsCalculator);
1373
+ this._dataChanged = true;
1374
+ this._mustCallGenerators = true;
1375
+ this._markDirty();
1376
+ }
1377
+
1378
+ set customBoundsSelectors(boundsSelectors) {
1379
+ this._customBoundsSelectors = this._customBoundsSelectors || {};
1380
+
1381
+ const indexedCustomBoundSelectors = {};
1382
+
1383
+ for (let { label, calculator } of boundsSelectors) {
1384
+ indexedCustomBoundSelectors[label] = calculator;
1385
+
1386
+ if (this._boundsCalculator === this._customBoundsSelectors[label] && this._boundsCalculator !== calculator) {
1387
+ this.boundCalculator = calculator;
1388
+ }
1389
+ }
1390
+
1391
+ this._customBoundsSelectors = indexedCustomBoundSelectors;
1392
+ }
1393
+
1394
+ _addBoundCalculatorToHistory(boundingFunction) {
1395
+ this._boundsIndex++;
1396
+ this._boundsHistory = this._boundsHistory.slice(0, this._boundsIndex);
1397
+ this._boundsHistory.push(boundingFunction);
1398
+ this.emit('bound_history_changed', this.boundHistory);
1399
+ }
1400
+
1401
+ registerSeriesClick(selectedSeriesIndex) {
1402
+ this.emit('series_click', this._series[selectedSeriesIndex], selectedSeriesIndex);
1403
+ }
1404
+
1405
+ setHighlightedSeries(highlightedSeries) {
1406
+ this._highlightedSeries = highlightedSeries;
1407
+ this.deferredEmit('highlighted_series_changed', highlightedSeries);
1408
+ this._mustRerender = true;
1409
+ this._markDirty();
1410
+ }
1411
+
1412
+ toggleIndividualPoints() {
1413
+ this._showIndividualPoints = !this._showIndividualPoints;
1414
+ this.deferredEmit('show_individual_points_changed', this._showIndividualPoints);
1415
+ this._mustRerender = true;
1416
+ this._markDirty();
1417
+ }
1418
+
1419
+ toggleYAutoscaling() {
1420
+ this._autoscaleY = !this._autoscaleY;
1421
+ this.deferredEmit('autoscale_y_changed', this._autoscaleY);
1422
+ this._markDirty();
1423
+ }
1424
+
1425
+ toggleExportMode() {
1426
+ this.exportMode = !this._exportMode;
1427
+ }
1428
+
1429
+ setBoundsFromSelection(pixelSelection) {
1430
+ this.boundCalculator = boundCalculatorFromSelection(pixelSelection, {
1431
+ elementWidth: this.primaryRenderer.sizing.elementWidth,
1432
+ elementHeight: this.primaryRenderer.sizing.elementHeight,
1433
+ selection: this._selection,
1434
+ axes: this._axes
1435
+ });
1436
+ }
1437
+
1438
+ nextBounds() {
1439
+ this._boundsIndex++;
1440
+ this._boundsCalculator = this._boundsHistory[this._boundsIndex];
1441
+ this.deferredEmit('bound_calculator_changed', this._boundsCalculator);
1442
+ this.deferredEmit('bound_history_changed', this.boundHistory);
1443
+ this._dataChanged = true;
1444
+ this._markDirty();
1445
+ }
1446
+
1447
+ previousBounds() {
1448
+ this._boundsIndex--;
1449
+ this._boundsCalculator = this._boundsHistory[this._boundsIndex];
1450
+ this.deferredEmit('bound_calculator_changed', this._boundsCalculator);
1451
+ this.deferredEmit('bound_history_changed', this.boundHistory);
1452
+ this._dataChanged = true;
1453
+ this._markDirty();
1454
+ }
1455
+
1456
+ setLabel({ axisIndex, label }) {
1457
+ this._axes[axisIndex].label = label;
1458
+ this.deferredEmit('axes_changed', [...this._axes]);
1459
+ this._markDirty();
1460
+ }
1461
+
1462
+ toggleScale({ axisIndex }) {
1463
+ const oldScale = this._axes[axisIndex].scale;
1464
+ this._axes[axisIndex].scale = oldScale === 'log' ? 'linear' : 'log';
1465
+
1466
+ this._dataChanged = true;
1467
+ this.deferredEmit('axes_changed', [...this._axes]);
1468
+ this._markDirty();
1469
+ }
1470
+
1471
+ recalculateTooltips() {
1472
+ this.primaryRenderer.recalculatePosition();
1473
+ this.setTooltipMousePosition({
1474
+ clientX: this._tooltipClientX,
1475
+ clientY: this._tooltipClientY,
1476
+ shiftKey: this.shiftKeyPressedOnMove,
1477
+ tooltipAllNext: this._tooltipAllNext,
1478
+ tooltipStateArg: this._tooltipStateArg
1479
+ });
1480
+ }
1481
+
1482
+ setTooltipMousePosition({ clientX, clientY, shiftKey, mouseX, mouseY, tooltipAllNext, tooltipStateArg }) {
1483
+ const sizing = this.primaryRenderer.sizing;
1484
+ if (!sizing) {
1485
+ return;
1486
+ }
1487
+
1488
+ if (typeof clientX === 'number') {
1489
+ this._tooltipClientX = clientX;
1490
+
1491
+ if (clientX < sizing.boundingRect.left || clientX > sizing.boundingRect.right) {
1492
+ this.showOnlySavedTooltips();
1493
+ return;
1494
+ }
1495
+ } else if (typeof mouseY !== 'number') {
1496
+ return;
1497
+ }
1498
+
1499
+ if (typeof clientY === 'number') {
1500
+ this._tooltipClientY = clientY;
1501
+
1502
+ if (clientY < sizing.boundingRect.top || clientY > sizing.boundingRect.bottom) {
1503
+ this.showOnlySavedTooltips();
1504
+ return;
1505
+ }
1506
+ } else if (typeof mouseY !== 'number') {
1507
+ return;
1508
+ }
1509
+
1510
+ const newMouseX = mouseX || (clientX - sizing.boundingRect.left);
1511
+ const newMouseY = mouseY || (clientY - sizing.boundingRect.top);
1512
+ if (this._tooltipState.mousePresent && newMouseX === this._tooltipState.mouseX && newMouseY === this._tooltipState.mouseY) {
1513
+ return;
1514
+ }
1515
+
1516
+ this._tooltipsChanged = true;
1517
+ this._tooltipState.mousePresent = true;
1518
+ this._tooltipState.mouseX = newMouseX;
1519
+ this._tooltipState.mouseY = newMouseY;
1520
+ this.shiftKeyPressedOnMove = shiftKey;
1521
+ this._tooltipAllNext = tooltipAllNext;
1522
+ this._tooltipStateArg = tooltipStateArg;
1523
+ this._markDirty();
1524
+ }
1525
+
1526
+ setContextMenuMousePosition({ clientX, clientY }) {
1527
+ this._contextMenuPosition = { x: clientX, y: clientY, showing: !this._contextMenuPosition.showing };
1528
+ this._contextMenuChanged = true;
1529
+ this._markDirty();
1530
+ }
1531
+
1532
+ toggleAlwaysTooltipped(singleSeries, shiftKey) {
1533
+ if (this._alwaysTooltipped.has(singleSeries)) {
1534
+ if (shiftKey) {
1535
+ this._alwaysTooltipped.clear();
1536
+ } else {
1537
+ this._alwaysTooltipped.delete(singleSeries);
1538
+ }
1539
+ } else {
1540
+ if (shiftKey) {
1541
+ for (let series of this._series) {
1542
+ this._alwaysTooltipped.add(series);
1543
+ }
1544
+ } else {
1545
+ this._alwaysTooltipped.add(singleSeries);
1546
+ }
1547
+ }
1548
+
1549
+ this._tooltipsChanged = true;
1550
+ this.deferredEmit('always_tooltipped_changed', this._alwaysTooltipped);
1551
+ this._markDirty();
1552
+ }
1553
+
1554
+ showOnlySavedTooltips(tooltipStateArg) {
1555
+ if (!this._tooltipState.mousePresent) {
1556
+ return;
1557
+ }
1558
+
1559
+ this._tooltipsChanged = true;
1560
+ this._tooltipState.mousePresent = false;
1561
+ this._tooltipStateArg = tooltipStateArg;
1562
+ this._markDirty();
1563
+ }
1564
+
1565
+ registerClick({ clientX }) {
1566
+ if (!this._listeners['series_click']) {
1567
+ return;
1568
+ }
1569
+
1570
+ const boundingRect = this.primaryRenderer.boundingRect;
1571
+ const sizing = this.primaryRenderer.sizing;
1572
+
1573
+ for (let series of this._series) {
1574
+ const bounds = series.axis.currentBounds;
1575
+
1576
+ const x = (clientX - boundingRect.left) / sizing.elementWidth * (bounds.maxX - bounds.minX) + bounds.minX;
1577
+ this.emit('series_click', { x, series });
1578
+ }
1579
+ }
1580
+
1581
+ toggleTooltipSaved() {
1582
+ const oldSavedTooltips = this._savedTooltips;
1583
+ this._savedTooltips = toggleTooltipSaved({
1584
+ currentTooltips: this._tooltipState.tooltips,
1585
+ savedTooltips: this._savedTooltips
1586
+ });
1587
+ this._tooltipsChanged = this._savedTooltips !== oldSavedTooltips;
1588
+ this._markDirty();
1589
+ }
1590
+
1591
+ clearSavedTooltips() {
1592
+ if (this._savedTooltips.length === 0) {
1593
+ return;
1594
+ }
1595
+
1596
+ this._savedTooltips = [];
1597
+ this._tooltipsChanged = true;
1598
+ this._markDirty();
1599
+ }
1600
+
1601
+ markDragStart() {
1602
+ if (this._draggingY) {
1603
+ return;
1604
+ }
1605
+
1606
+ this._draggingY = true;
1607
+ this.deferredEmit('dragging_y_changed', this._draggingY);
1608
+ this._markDirty();
1609
+ }
1610
+
1611
+ finalizeDrag(draggedSeries, axisIndex, grapherID) {
1612
+ if (!this._draggingY) {
1613
+ return;
1614
+ }
1615
+
1616
+ this._draggingY = false;
1617
+ this.deferredEmit('dragging_y_changed', this._draggingY);
1618
+
1619
+ const hasAxis = axisIndex && axisIndex !== 0;
1620
+ const sameGrapherID = grapherID === this._grapherID;
1621
+
1622
+ if (hasAxis && sameGrapherID) {
1623
+ this._moveAxis(draggedSeries, axisIndex);
1624
+ this.deferredEmit('dragging_y_finalized', { draggedSeries, axisIndex, grapherID });
1625
+ } else if (grapherID) {
1626
+ this.deferredEmit('dragging_y_finalized', { draggedSeries, axisIndex, grapherID });
1627
+ }
1628
+
1629
+ this._markDirty();
1630
+ }
1631
+
1632
+ set timingFrameCount(value) {
1633
+ if (typeof value !== 'number') {
1634
+ return;
1635
+ }
1636
+
1637
+ this._timingBuffer = [];
1638
+ this._timingIndex = 0;
1639
+ this._timingFrameCount = value;
1640
+ }
1641
+
1642
+ set percentile(value) {
1643
+ if (value === undefined) {
1644
+ return;
1645
+ }
1646
+
1647
+ this.deferredEmit('percentile_changed', value);
1648
+
1649
+ if (value === '') {
1650
+ value = 100;
1651
+ }
1652
+
1653
+ value = parseFloat(value);
1654
+ if (!isNaN(value) && value <= 100 && value >= 0) {
1655
+ this._percentile = value;
1656
+ this._dataChanged = true;
1657
+ }
1658
+
1659
+ this._markDirty();
1660
+ }
1661
+
1662
+ set percentileAsymmetry(value) {
1663
+ if (value === undefined) {
1664
+ return;
1665
+ }
1666
+
1667
+ this.deferredEmit('percentile_asymmetry_changed', value);
1668
+
1669
+ if (value === '') {
1670
+ value = 0;
1671
+ }
1672
+
1673
+ value = parseFloat(value);
1674
+ if (!isNaN(value) && value <= 50 && value >= -50) {
1675
+ this._percentileAsymmetry = value;
1676
+ this._dataChanged = true;
1677
+ }
1678
+
1679
+ this._markDirty();
1680
+ }
1681
+
1682
+ set showingOptions(value) {
1683
+ if (value === undefined) {
1684
+ return;
1685
+ }
1686
+
1687
+ this._showingOptions = value;
1688
+ this.deferredEmit('showing_options_changed', value);
1689
+ this._markDirty();
1690
+ }
1691
+
1692
+ toggleShowingOptions() {
1693
+ this.showingOptions = !this.showingOptions;
1694
+ }
1695
+
1696
+ toggleMaxPrecision() {
1697
+ this._maxPrecision = !this._maxPrecision;
1698
+ this.deferredEmit('max_precision_changed', this._maxPrecision);
1699
+ this._markDirty();
1700
+ }
1701
+
1702
+ toggleShowingAnnotations() {
1703
+ this._showingAnnotations = !this._showingAnnotations;
1704
+ this.deferredEmit('showing_annotations_changed', this._showingAnnotations);
1705
+ this._markDirty();
1706
+ }
1707
+
1708
+ toggleShowingSidebar() {
1709
+ this._showingSidebar = !this._showingSidebar;
1710
+ this.deferredEmit('showing_sidebar_changed', this._showingSidebar);
1711
+ this._markDirty();
1712
+ }
1713
+
1714
+ setShowing(singleSeries, showing) {
1715
+ singleSeries.hidden = !showing;
1716
+ this._series = [...this._series];
1717
+
1718
+ if (singleSeries.hidden) {
1719
+ this._hideSeries(singleSeries);
1720
+ } else {
1721
+ this._showSeries(singleSeries);
1722
+ }
1723
+
1724
+ this.deferredEmit('series_changed', this._series, { skipResize: true });
1725
+ this._dataChanged = true;
1726
+ this._markDirty();
1727
+ }
1728
+
1729
+ set annotations(value) {
1730
+ this._annotations = value || [];
1731
+ this._annotationsChanged = true;
1732
+ this._markDirty();
1733
+ }
1734
+
1735
+ /**
1736
+ * Converts an enum to a number, mutating saved state if necessary to track that
1737
+ *
1738
+ * @param {String} value
1739
+ * @param {Object} singleSeries
1740
+ * @param {Boolean} [isX]
1741
+ * @return {Number}
1742
+ */
1743
+ enumToNumber(value, singleSeries, isX) {
1744
+ const existingIndex = this._enumMap[value];
1745
+ if (typeof existingIndex === 'number') {
1746
+ return existingIndex;
1747
+ }
1748
+
1749
+ const indexToSet = Object.keys(this._enumMap).length;
1750
+ this._enumMap[value] = indexToSet;
1751
+
1752
+ // safely handle reference changes
1753
+ this._series[singleSeries.index].hasEnum = true;
1754
+ if (isX) {
1755
+ this._series[singleSeries.index].hasXEnum = true;
1756
+ this._hasXEnum = true;
1757
+ this.deferredEmit('has_x_enum_change', this._hasXEnum);
1758
+ this.deferredEmit('x_enum_map_change', this._enumMap);
1759
+ }
1760
+
1761
+ this.deferredEmit('enum_map_change', this._enumMap);
1762
+
1763
+ return indexToSet;
1764
+ }
1765
+
1766
+ triggerResize() {
1767
+ this._mustResize = true;
1768
+ this._markDirty();
1769
+ }
1770
+ }