@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,241 @@
1
+ import binarySearch from '../helpers/binary_search';
2
+ import scaleBounds from '../renderer/scale_bounds';
3
+ import getColor from '../helpers/colors';
4
+ import flattenSimpleData, {extractXValue, extractYValue} from '../helpers/flatten_simple_data';
5
+ import {getBarWidths} from '../renderer/draw_bars';
6
+
7
+ const DISTANCE_THRESHOLD = 20;
8
+
9
+ /**
10
+ * Figures out the tooltip state
11
+ *
12
+ * @param {Boolean} mousePresent
13
+ * @param {Number} mouseX
14
+ * @param {Number} mouseY
15
+ * @param {Object} sizing
16
+ * @param {Array<Object>} series
17
+ * @param {Set} alwaysTooltipped
18
+ * @param {Array<Object>} savedTooltips
19
+ * @param {Boolean} [allTooltipped]
20
+ * @param {Number} closestSpacing
21
+ * @return {{mouseX: *, mouseY: *, elementWidth: number, elementHeight: number, tooltips: any[]}}
22
+ */
23
+ export default function calculateTooltipState({mousePresent, mouseX, mouseY, sizing, series, alwaysTooltipped, savedTooltips, allTooltipped, closestSpacing }) {
24
+ // filter out saved tooltips for nonexistent series
25
+ savedTooltips = savedTooltips.filter((tooltip) => tooltip.series.axis);
26
+
27
+ for (let savedTooltip of savedTooltips) {
28
+ moveTooltip({ mouseX, mouseY, sizing}, savedTooltip);
29
+ }
30
+
31
+ if (!mousePresent) {
32
+ return {
33
+ mouseX,
34
+ mouseY,
35
+ elementWidth: sizing.elementWidth,
36
+ elementHeight: sizing.elementHeight,
37
+ tooltips: [...savedTooltips]
38
+ };
39
+ }
40
+
41
+ const tooltips = [];
42
+
43
+ let minDistance = Infinity;
44
+
45
+ for (let i = 0; i < series.length; i++) {
46
+ const singleSeries = series[i];
47
+
48
+ if (singleSeries.hidden) {
49
+ continue;
50
+ }
51
+
52
+ const axis = singleSeries.axis;
53
+
54
+ const scale = axis.scale;
55
+ const bounds = axis.currentBounds;
56
+ const { minY, maxY } = scaleBounds({...bounds, scale });
57
+
58
+ const trueX = mouseX/sizing.elementWidth * (bounds.maxX - bounds.minX) + bounds.minX;
59
+
60
+ let data = singleSeries.inDataSpace;
61
+ if (singleSeries.ignoreDiscontinuities) {
62
+ data = data.filter((tuple) => typeof tuple[1] === 'number');
63
+ }
64
+
65
+ const closestIndex = binarySearch(data, trueX, { returnIndex: true });
66
+ const closestPoint = data[closestIndex];
67
+
68
+ if (!closestPoint) {
69
+ continue;
70
+ }
71
+
72
+ const [x, y] = closestPoint;
73
+
74
+ if (x === null) {
75
+ continue;
76
+ }
77
+
78
+ let pixelX = (x - bounds.minX)/(bounds.maxX - bounds.minX) * sizing.elementWidth;
79
+ const pixelY = (1 - ((scale === 'log' ? Math.log10(y) : y) - minY)/(maxY - minY)) * sizing.elementHeight;
80
+
81
+ if (pixelY > sizing.elementHeight || pixelY < 0) {
82
+ continue;
83
+ }
84
+
85
+ const ignoreYDistance = alwaysTooltipped.has(singleSeries) || allTooltipped;
86
+ let xDistanceThreshold = DISTANCE_THRESHOLD;
87
+ let yDistanceThreshold = DISTANCE_THRESHOLD;
88
+ let distanceThreshold = DISTANCE_THRESHOLD;
89
+
90
+ if (singleSeries.rendering === 'bar') {
91
+ const indexInAxis = singleSeries.axis.series.indexOf(singleSeries);
92
+ const axisSeriesCount = singleSeries.axis.series.length;
93
+
94
+ const { totalBarWidth, barWidth } = getBarWidths({
95
+ closestSpacing,
96
+ bounds,
97
+ sizing,
98
+ axisSeriesCount
99
+ });
100
+
101
+ // currently, pixelX is the center of all the bars
102
+ // shift it to start at the far left, then shift it to the center of the individual bar
103
+ pixelX -= totalBarWidth/2/sizing.pixelRatio;
104
+ pixelX += (barWidth*(indexInAxis + 0.5))/sizing.pixelRatio;
105
+
106
+ xDistanceThreshold = barWidth/2/sizing.pixelRatio;
107
+ yDistanceThreshold = 100;
108
+ distanceThreshold = xDistanceThreshold + yDistanceThreshold;
109
+ }
110
+
111
+ const xDistance = Math.abs(pixelX - mouseX);
112
+ const yDistance = Math.abs(pixelY - mouseY);
113
+ const distance = Math.sqrt((xDistance)**2 + (pixelY - mouseY)**2);
114
+
115
+ if (xDistance > xDistanceThreshold || (!ignoreYDistance && yDistance > yDistanceThreshold) || (!ignoreYDistance && distance > distanceThreshold)) {
116
+ continue;
117
+ }
118
+
119
+ let xLabel, yLabel;
120
+
121
+ const simpleData = singleSeries.simpleData || singleSeries.data;
122
+
123
+ const enumLike = simpleData.length && !!singleSeries.hasEnum;
124
+ if (singleSeries.xLabel || singleSeries.yLabel || enumLike) {
125
+ let simplePoint;
126
+
127
+ if (simpleData.length === data.length) {
128
+ simplePoint = simpleData[closestIndex];
129
+ } else {
130
+ const flattenedData = flattenSimpleData(simpleData, { series: singleSeries, inDataSpace: data });
131
+
132
+ simplePoint = flattenedData[closestIndex][1];
133
+ }
134
+
135
+ if (singleSeries.xLabel) {
136
+ xLabel = simplePoint[singleSeries.xLabel];
137
+ } else if (singleSeries.hasXEnum) {
138
+ xLabel = extractXValue(simplePoint, singleSeries);
139
+ }
140
+
141
+ if (singleSeries.yLabel) {
142
+ yLabel = simplePoint[singleSeries.yLabel];
143
+ } else if (enumLike) {
144
+ yLabel = extractYValue(simplePoint, singleSeries);
145
+ }
146
+ }
147
+
148
+ if (distance < minDistance) {
149
+ minDistance = distance;
150
+ }
151
+
152
+ let color = getColor(singleSeries.color, i, singleSeries.multigrapherSeriesIndex);
153
+ if (y < 0 && singleSeries.negativeColor) {
154
+ color = singleSeries.negativeColor;
155
+ } else if (y === 0 && singleSeries.zeroLineColor) {
156
+ color = singleSeries.zeroLineColor;
157
+ }
158
+
159
+ tooltips.push({
160
+ pixelWidth: sizing.elementWidth,
161
+ pixelX: pixelX,
162
+ pixelY: isNaN(pixelY) ? sizing.elementHeight/2 : pixelY,
163
+ x,
164
+ y,
165
+ color,
166
+ distance,
167
+ xDistance,
168
+ index: i,
169
+ series: singleSeries,
170
+ xLabel,
171
+ yLabel,
172
+ fullYPrecision: singleSeries.fullYPrecision,
173
+ ignoreYDistance
174
+ });
175
+ }
176
+
177
+ const unsavedTooltips = tooltips.filter(({ distance, ignoreYDistance }) => {
178
+ return distance === minDistance || ignoreYDistance;
179
+ }).sort((a, b) => b.distance - a.distance);
180
+
181
+ return {
182
+ mousePresent,
183
+ mouseX,
184
+ mouseY,
185
+ elementWidth: sizing.elementWidth,
186
+ elementHeight: sizing.elementHeight,
187
+ unsavedTooltipsCount: unsavedTooltips.length,
188
+ tooltips: [...savedTooltips, ...unsavedTooltips]
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Recalculates the tooltip position, given the new sizing, bounds, etc.
194
+ *
195
+ * @param {Number} mouseX
196
+ * @param {Number} mouseY
197
+ * @param {Object} sizing
198
+ * @param {Object} tooltip - The tooltip object to move
199
+ */
200
+ export function moveTooltip({ mouseX, mouseY, sizing }, tooltip) {
201
+ const { x, y } = tooltip;
202
+
203
+ const scale = tooltip.series.axis.scale;
204
+ const bounds = tooltip.series.axis.currentBounds;
205
+ const { minY, maxY } = scaleBounds({...bounds, scale });
206
+
207
+ const pixelX = (x - bounds.minX)/(bounds.maxX - bounds.minX) * sizing.elementWidth;
208
+ const pixelY = (1 - ((scale === 'log' ? Math.log10(y) : y) - minY)/(maxY - minY)) * sizing.elementHeight;
209
+
210
+ const distance = Math.sqrt((pixelX - mouseX)**2 + (pixelY - mouseY)**2);
211
+ const xDistance = Math.abs(pixelX - mouseX);
212
+
213
+ Object.assign(tooltip, {
214
+ pixelWidth: sizing.elementWidth,
215
+ pixelX: pixelX,
216
+ pixelY: isNaN(pixelY) ? sizing.elementHeight/2 : pixelY,
217
+ xDistance,
218
+ distance
219
+ });
220
+ }
221
+
222
+ export function toggleTooltipSaved({ currentTooltips, savedTooltips }) {
223
+ if (!currentTooltips.length) {
224
+ return savedTooltips;
225
+ }
226
+
227
+ const lastTooltip = currentTooltips[currentTooltips.length - 1];
228
+ if (lastTooltip.xDistance > DISTANCE_THRESHOLD || (!lastTooltip.ignoreYDistance && lastTooltip.distance > DISTANCE_THRESHOLD)) {
229
+ return savedTooltips;
230
+ }
231
+
232
+ const currentTooltipIndex = savedTooltips.findIndex((tooltip) => {
233
+ return tooltip.x === lastTooltip.x && tooltip.y === lastTooltip.y;
234
+ });
235
+
236
+ if (currentTooltipIndex === -1) {
237
+ return [...savedTooltips, lastTooltip];
238
+ } else {
239
+ return savedTooltips.filter((_, index) => index !== currentTooltipIndex);
240
+ }
241
+ }
@@ -0,0 +1,13 @@
1
+ export const SIMPLE_DATA_TYPES = [
2
+ 'values',
3
+ 'tuples',
4
+ 'objects'
5
+ ];
6
+
7
+ export const COMPLEX_DATA_TYPES = [
8
+ 'tuple_observable',
9
+ 'object_observable',
10
+ 'generator'
11
+ ];
12
+
13
+ export const DATA_TYPES = [...SIMPLE_DATA_TYPES, ...COMPLEX_DATA_TYPES];
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Expands bounds to give a little y padding
3
+ *
4
+ * @param {Object} bounds
5
+ * @param {?Number} bounds.minY
6
+ * @param {?Number} bounds.maxY
7
+ * @param {?Number} bounds.minX
8
+ * @param {?Number} bounds.maxX
9
+ * @param {Array<Number>} [expandYWith]
10
+ * @param {Number} [extendXForNBars=0]
11
+ * @return {Object}
12
+ */
13
+ export default function expandBounds(bounds, {expandYWith = [], extendXForNBars=0}) {
14
+ const expandedBounds = Object.assign({}, bounds);
15
+ for (let y of expandYWith) {
16
+ if (y === null || y === undefined) {
17
+ continue;
18
+ }
19
+
20
+ if (typeof expandedBounds.minY !== 'number' || y < expandedBounds.minY) {
21
+ expandedBounds.minY = y;
22
+ }
23
+
24
+ if (typeof expandedBounds.maxY !== 'number' || y > expandedBounds.maxY) {
25
+ expandedBounds.maxY = y;
26
+ }
27
+ }
28
+
29
+ expandedBounds.unscaledMinY = expandedBounds.minY;
30
+ expandedBounds.unscaledMaxY = expandedBounds.maxY;
31
+
32
+ const range = expandedBounds.maxY - expandedBounds.minY;
33
+ const midpoint = expandedBounds.minY + range/2;
34
+ expandedBounds.minY = midpoint - 1.05*range/2;
35
+ expandedBounds.maxY = midpoint + 1.05*range/2;
36
+
37
+ if (expandedBounds.minY === expandedBounds.maxY && expandedBounds.minY !== null) {
38
+ if (expandedBounds.minY > 0) {
39
+ expandedBounds.minY *= 0.95;
40
+ expandedBounds.maxY *= 1.05;
41
+ } else if (expandedBounds.minY < 0) {
42
+ expandedBounds.minY *= 1.05;
43
+ expandedBounds.maxY *= 0.95;
44
+ } else {
45
+ expandedBounds.minY -= 1;
46
+ expandedBounds.maxY += 1;
47
+ }
48
+ }
49
+
50
+ if (extendXForNBars && expandedBounds.minX !== expandedBounds.maxX && expandedBounds.minX !== null && expandedBounds.maxX !== null) {
51
+ // Base expansion factor on expected bar count
52
+ const barWidth = (expandedBounds.maxX - expandedBounds.minX) / extendXForNBars;
53
+ expandedBounds.minX -= barWidth/2;
54
+ expandedBounds.maxX += barWidth/2;
55
+ }
56
+
57
+ return expandedBounds;
58
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ *
3
+ * @param {Array<Object>} axes
4
+ * @param {'left'|'right'} [side]
5
+ * @param {String|Number} [number]
6
+ * @return {Object|undefined}
7
+ */
8
+ export default function findMatchingAxis({ axes, side, number }) {
9
+ if (number) {
10
+ number = parseInt(number) || 0;
11
+ } else {
12
+ number = 0;
13
+ }
14
+
15
+ let axis;
16
+ let seen = 0;
17
+ for (let candidateAxis of axes) {
18
+ if (candidateAxis.side !== side) {
19
+ continue;
20
+ }
21
+
22
+ if (seen === number) {
23
+ axis = candidateAxis;
24
+ break;
25
+ }
26
+
27
+ seen++;
28
+ }
29
+
30
+ return axis;
31
+ }
@@ -0,0 +1,15 @@
1
+ import BOUND_CALCULATORS from './bound_calculators';
2
+
3
+ export default function getDefaultBoundsCalculator(givenLabel, customCalculators) {
4
+ if (BOUND_CALCULATORS[givenLabel]) {
5
+ return BOUND_CALCULATORS[givenLabel];
6
+ }
7
+
8
+ for (let { label, calculator } of customCalculators) {
9
+ if (givenLabel === label) {
10
+ return calculator;
11
+ }
12
+ }
13
+
14
+ return BOUND_CALCULATORS.all;
15
+ }
@@ -0,0 +1,164 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ export function useEvent(eventSource, eventName, initialState=null, watch=[]) {
4
+ if (typeof initialState === 'function') {
5
+ const originalInitialState = initialState;
6
+ initialState = () => originalInitialState;
7
+ }
8
+
9
+ const [value, setValue] = useState(initialState);
10
+
11
+ useEffect(() => {
12
+ const listener = (updatedValue) => {
13
+ if (typeof updatedValue === 'function') {
14
+ const originalUpdatedValue = updatedValue;
15
+ updatedValue = () => originalUpdatedValue;
16
+ }
17
+ setValue(updatedValue);
18
+ };
19
+
20
+ eventSource.on(eventName, listener);
21
+ return () => {
22
+ eventSource.off(eventName, listener);
23
+ };
24
+ }, [...watch, setValue, eventSource]);
25
+
26
+ return value;
27
+ }
28
+
29
+ export function usePrimarySize(stateController) {
30
+ return useEvent(stateController, 'primary_size_change', stateController.primaryRenderer ? stateController.primaryRenderer.sizing : {
31
+ elementWidth: 0,
32
+ elementHeight: 0,
33
+ renderWidth: 0,
34
+ renderHeight: 0
35
+ });
36
+ }
37
+
38
+ export function useAxes(stateController) {
39
+ return useEvent(stateController, 'axes_changed', stateController.axes);
40
+ }
41
+
42
+ export function useSeries(stateController) {
43
+ return useEvent(stateController, 'series_changed', stateController.series);
44
+ }
45
+
46
+ export function useHighlightedSeries(stateController) {
47
+ return useEvent(stateController, 'highlighted_series_changed', stateController.highlightedSeries);
48
+ }
49
+
50
+ export function useRightAxisCount(stateController) {
51
+ return useRightAxes(stateController).length;
52
+ }
53
+
54
+ export function useLeftAxisCount(stateController) {
55
+ return useLeftAxes(stateController).length;
56
+ }
57
+
58
+ export function useRightAxes(stateController) {
59
+ return useEvent(stateController, 'right_axes_changed', stateController.rightAxes);
60
+ }
61
+
62
+ export function useLeftAxes(stateController) {
63
+ return useEvent(stateController, 'left_axes_changed', stateController.leftAxes);
64
+ }
65
+
66
+ export function useSelection(stateController) {
67
+ return useEvent(stateController, 'selection_changed', stateController.selection);
68
+ }
69
+
70
+ export function useGlobalBounds(stateController) {
71
+ return useEvent(stateController, 'global_bounds_changed', stateController.globalBounds);
72
+ }
73
+
74
+ export function useAxisBounds(stateController) {
75
+ return useEvent(stateController, 'axis_bounds_changed', stateController.axes.map(({ currentBounds }) => currentBounds));
76
+ }
77
+
78
+ export function useBoundCalculator(stateController) {
79
+ return useEvent(stateController, 'bound_calculator_changed', stateController.boundCalculator);
80
+ }
81
+
82
+ export function useShowIndividualPoints(stateController) {
83
+ return useEvent(stateController, 'show_individual_points_changed', stateController.showIndividualPoints);
84
+ }
85
+
86
+ export function useAutoscaleY(stateController) {
87
+ return useEvent(stateController, 'autoscale_y_changed', stateController.autoscaleY);
88
+ }
89
+
90
+ export function useBoundHistory(stateController) {
91
+ return useEvent(stateController, 'bound_history_changed', stateController.boundHistory);
92
+ }
93
+
94
+ export function useAlwaysTooltipped(stateController) {
95
+ return useEvent(stateController, 'always_tooltipped_changed', stateController.alwaysTooltipped);
96
+ }
97
+
98
+ export function useTooltipState(stateController) {
99
+ return useEvent(stateController, 'tooltip_state_changed', stateController.tooltipState);
100
+ }
101
+
102
+ export function useContextMenu(stateController) {
103
+ return useEvent(stateController, 'context_menu_position_changed', stateController.contextMenuState);
104
+ }
105
+
106
+ export function useDraggingY(stateController) {
107
+ return useEvent(stateController, 'dragging_y_changed', stateController.draggingY);
108
+ }
109
+
110
+ export function usePercentile(stateController) {
111
+ return useEvent(stateController, 'percentile_changed', stateController.percentile);
112
+ }
113
+
114
+ export function useShowingOptions(stateController) {
115
+ return useEvent(stateController, 'showing_options_changed', stateController.showingOptions);
116
+ }
117
+
118
+ export function useMaxPrecision(stateController) {
119
+ return useEvent(stateController, 'max_precision_changed', stateController.maxPrecision);
120
+ }
121
+
122
+ export function usePercentileAsymmetry(stateController) {
123
+ return useEvent(stateController, 'percentile_asymmetry_changed', stateController.percentileAsymmetry);
124
+ }
125
+
126
+ export function useShowingSidebar(stateController) {
127
+ return useEvent(stateController, 'showing_sidebar_changed', stateController.showingSidebar);
128
+ }
129
+
130
+ export function useShowingAnnotations(stateController) {
131
+ return useEvent(stateController, 'showing_annotations_changed', stateController.showingAnnotations);
132
+ }
133
+
134
+ export function useMultiSeries(multigraphStateController) {
135
+ return useEvent(multigraphStateController, 'multi_series_changed', multigraphStateController.multiSeries);
136
+ }
137
+
138
+ export function useAnnotationState(stateController) {
139
+ return useEvent(stateController, 'annotations_changed', stateController.annotationState);
140
+ }
141
+
142
+ export function useSizing(stateController) {
143
+ return useEvent(stateController, 'primary_size_change', stateController.sizing);
144
+ }
145
+
146
+ export function useTheme(stateController) {
147
+ return useEvent(stateController, 'theme_change', stateController.theme);
148
+ }
149
+
150
+ export function useExportMode(stateController) {
151
+ return useEvent(stateController, 'export_mode_change', stateController.exportMode);
152
+ }
153
+
154
+ export function useHasXEnum(stateController) {
155
+ return useEvent(stateController, 'has_x_enum_change', stateController.hasXEnum);
156
+ }
157
+
158
+ export function useEnumMap(stateController) {
159
+ return useEvent(stateController, 'enum_map_change', stateController.enumMap);
160
+ }
161
+
162
+ export function useXEnumMap(stateController) {
163
+ return useEvent(stateController, 'x_enum_map_change', stateController.enumMap);
164
+ }
@@ -0,0 +1,74 @@
1
+ import {extractYValue} from '../helpers/flatten_simple_data.js';
2
+
3
+ /**
4
+ * Given a series object, figures out the form of the data
5
+ * Note that this is not a validator, nor is it guaranteed to get it right 100% of the time!
6
+ * If it is giving the wrong type, it is recommended to specify the type manually
7
+ *
8
+ * @param {*} series
9
+ * @param {Boolean} options.useSimpleData
10
+ * @param {Array} options.data
11
+ * @return {String}
12
+ */
13
+ export default function inferType(series, options={ useSimpleData: false }) {
14
+ if (series.type && series.type !== 'infer') {
15
+ return series.type;
16
+ }
17
+
18
+ const data = options.data || (options.useSimpleData ? (series.simpleData || series.data) : series.data);
19
+
20
+ if (!data) {
21
+ throw new Error('Data must be provided');
22
+ }
23
+
24
+ if (Array.isArray(data)) {
25
+ if (data.length === 0) {
26
+ return 'tuples';
27
+ }
28
+
29
+ if (Array.isArray(data[0])) {
30
+ return 'tuples';
31
+ }
32
+
33
+ if (typeof data[0] === 'number' || !data[0]) {
34
+ return 'values';
35
+ }
36
+
37
+ return 'objects';
38
+ }
39
+
40
+ if (data.observe) {
41
+ if (series.xKey) {
42
+ return 'object_observable';
43
+ } else {
44
+ return 'tuple_observable';
45
+ }
46
+ }
47
+
48
+ if (typeof data === 'function') {
49
+ return 'generator';
50
+ }
51
+
52
+ throw new Error('Could not infer type');
53
+ }
54
+
55
+ /**
56
+ *
57
+ * @param simpleData
58
+ * @param singleSeries
59
+ * @return {boolean}
60
+ */
61
+ export function isEnumLike(simpleData, singleSeries) {
62
+ for (let i = 0; i < simpleData.length; i++) {
63
+ const y = extractYValue(simpleData[simpleData.length - 1], singleSeries);
64
+ if (y === null || y === undefined) {
65
+ continue;
66
+ }
67
+
68
+ if (typeof y === 'string') {
69
+ return true;
70
+ }
71
+ }
72
+
73
+ return false;
74
+ }
@@ -0,0 +1,64 @@
1
+ export default function mergeBounds(boundsList) {
2
+ const mergedBounds = {
3
+ minX: null,
4
+ maxX: null,
5
+ minY: null,
6
+ maxY: null,
7
+ closestSpacing: null,
8
+ dates: false,
9
+ initial: true
10
+ };
11
+
12
+ for (let { minX, maxX, minY, maxY, dates, initial, closestSpacing } of boundsList) {
13
+ if (dates) {
14
+ mergedBounds.dates = true;
15
+ }
16
+
17
+ if (initial) {
18
+ continue;
19
+ }
20
+ mergedBounds.initial = false;
21
+
22
+ if (mergedBounds.minX === null || minX < mergedBounds.minX) {
23
+ mergedBounds.minX = minX;
24
+ }
25
+
26
+ if (mergedBounds.maxX === null || maxX > mergedBounds.maxX) {
27
+ mergedBounds.maxX = maxX;
28
+ }
29
+
30
+ if (mergedBounds.closestSpacing === null || closestSpacing < mergedBounds.closestSpacing) {
31
+ mergedBounds.closestSpacing = closestSpacing;
32
+ }
33
+
34
+ if (mergedBounds.minY === null || minY < mergedBounds.minY) {
35
+ mergedBounds.minY = minY;
36
+ }
37
+
38
+ if (mergedBounds.maxY === null || maxY > mergedBounds.maxY) {
39
+ mergedBounds.maxY = maxY;
40
+ }
41
+ }
42
+
43
+ if (mergedBounds.minX === null) {
44
+ mergedBounds.minX = 0;
45
+ }
46
+
47
+ if (mergedBounds.maxX === null) {
48
+ mergedBounds.maxX = 0;
49
+ }
50
+
51
+ if (mergedBounds.closestSpacing === null) {
52
+ mergedBounds.closestSpacing = 1;
53
+ }
54
+
55
+ if (mergedBounds.minY === null) {
56
+ mergedBounds.minY = 0;
57
+ }
58
+
59
+ if (mergedBounds.maxY === null) {
60
+ mergedBounds.maxY = 0;
61
+ }
62
+
63
+ return mergedBounds;
64
+ }