@windborne/grapher 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +85 -0
- package/.idea/codeStyles/Project.xml +19 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/grapher.iml +12 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/0.bundle.js +2 -0
- package/0.bundle.js.map +1 -0
- package/1767282193a714f63082.module.wasm +0 -0
- package/537.bundle.js +2 -0
- package/537.bundle.js.map +1 -0
- package/831.bundle.js +2 -0
- package/831.bundle.js.map +1 -0
- package/bundle.js +2 -0
- package/bundle.js.map +1 -0
- package/package.json +75 -0
- package/readme.md +129 -0
- package/src/components/annotations.js +62 -0
- package/src/components/context_menu.js +73 -0
- package/src/components/draggable_points.js +114 -0
- package/src/components/graph_body.js +292 -0
- package/src/components/graph_title.js +16 -0
- package/src/components/options.js +111 -0
- package/src/components/percentile_button.js +72 -0
- package/src/components/range_graph.js +352 -0
- package/src/components/range_selection.js +175 -0
- package/src/components/range_selection_button.js +26 -0
- package/src/components/range_selection_button_base.js +51 -0
- package/src/components/series_key.js +235 -0
- package/src/components/series_key_axis_container.js +70 -0
- package/src/components/series_key_item.js +52 -0
- package/src/components/sidebar.js +76 -0
- package/src/components/tooltip.js +244 -0
- package/src/components/vertical_lines.js +70 -0
- package/src/components/x_axis.js +124 -0
- package/src/components/y_axis.js +239 -0
- package/src/eventable.js +65 -0
- package/src/grapher.js +367 -0
- package/src/grapher.scss +914 -0
- package/src/helpers/axis_sizes.js +2 -0
- package/src/helpers/binary_search.js +67 -0
- package/src/helpers/color_to_vector.js +35 -0
- package/src/helpers/colors.js +27 -0
- package/src/helpers/custom_prop_types.js +159 -0
- package/src/helpers/flatten_simple_data.js +81 -0
- package/src/helpers/format.js +233 -0
- package/src/helpers/generator_params_equal.js +10 -0
- package/src/helpers/name_for_series.js +16 -0
- package/src/helpers/place_grid.js +257 -0
- package/src/helpers/pyodide_ready.js +13 -0
- package/src/multigrapher.js +105 -0
- package/src/renderer/background.frag +7 -0
- package/src/renderer/background.vert +7 -0
- package/src/renderer/background_program.js +48 -0
- package/src/renderer/circle.frag +26 -0
- package/src/renderer/circle.vert +12 -0
- package/src/renderer/create_gl_program.js +36 -0
- package/src/renderer/draw_area.js +159 -0
- package/src/renderer/draw_background.js +15 -0
- package/src/renderer/draw_bars.js +80 -0
- package/src/renderer/draw_line.js +69 -0
- package/src/renderer/draw_zero_line.js +24 -0
- package/src/renderer/extract_vertices.js +137 -0
- package/src/renderer/graph_body_renderer.js +293 -0
- package/src/renderer/line.frag +51 -0
- package/src/renderer/line.vert +32 -0
- package/src/renderer/line_program.js +125 -0
- package/src/renderer/paths_from.js +72 -0
- package/src/renderer/scale_bounds.js +28 -0
- package/src/renderer/size_canvas.js +59 -0
- package/src/rust/Cargo.lock +233 -0
- package/src/rust/Cargo.toml +35 -0
- package/src/rust/pkg/grapher_rs.d.ts +42 -0
- package/src/rust/pkg/grapher_rs.js +351 -0
- package/src/rust/pkg/grapher_rs_bg.d.ts +11 -0
- package/src/rust/pkg/grapher_rs_bg.wasm +0 -0
- package/src/rust/pkg/index.js +342 -0
- package/src/rust/pkg/index_bg.wasm +0 -0
- package/src/rust/pkg/package.json +14 -0
- package/src/rust/src/extract_vertices.rs +83 -0
- package/src/rust/src/get_point_number.rs +50 -0
- package/src/rust/src/lib.rs +15 -0
- package/src/rust/src/selected_space_to_render_space.rs +131 -0
- package/src/state/average_loop_times.js +15 -0
- package/src/state/bound_calculator_from_selection.js +36 -0
- package/src/state/bound_calculators.js +41 -0
- package/src/state/calculate_annotations_state.js +59 -0
- package/src/state/calculate_data_bounds.js +104 -0
- package/src/state/calculate_tooltip_state.js +241 -0
- package/src/state/data_types.js +13 -0
- package/src/state/expand_bounds.js +58 -0
- package/src/state/find_matching_axis.js +31 -0
- package/src/state/get_default_bounds_calculator.js +15 -0
- package/src/state/hooks.js +164 -0
- package/src/state/infer_type.js +74 -0
- package/src/state/merge_bounds.js +64 -0
- package/src/state/multigraph_state_controller.js +334 -0
- package/src/state/selection_from_global_bounds.js +25 -0
- package/src/state/space_conversions/condense_data_space.js +115 -0
- package/src/state/space_conversions/data_space_to_selected_space.js +328 -0
- package/src/state/space_conversions/selected_space_to_background_space.js +144 -0
- package/src/state/space_conversions/selected_space_to_render_space.js +161 -0
- package/src/state/space_conversions/simple_series_to_data_space.js +229 -0
- package/src/state/state_controller.js +1770 -0
- package/src/state/sync_pool.js +101 -0
- package/test/setup.js +15 -0
- package/test/space_conversions/data_space_to_selected_space.test.js +434 -0
- package/webpack.dev.config.js +109 -0
- package/webpack.prod.config.js +60 -0
- package/webpack.test.config.js +59 -0
@@ -0,0 +1,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
|
+
}
|