@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
package/src/grapher.js ADDED
@@ -0,0 +1,367 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import CustomPropTypes from './helpers/custom_prop_types';
4
+ import GraphBody from './components/graph_body';
5
+ import './grapher.scss';
6
+ import XAxis from './components/x_axis';
7
+ import YAxis from './components/y_axis';
8
+ import RangeSelection from './components/range_selection';
9
+ import RangeGraph from './components/range_graph';
10
+ import SeriesKey from './components/series_key';
11
+ import {Y_AXIS_WIDTH} from './helpers/axis_sizes';
12
+ import GraphTitle from './components/graph_title';
13
+ import {LINE_COLORS} from './helpers/colors';
14
+ import StateController from './state/state_controller';
15
+ import {useDraggingY, useLeftAxes, useRightAxes, useShowingSidebar, useTheme} from './state/hooks';
16
+ import Sidebar from './components/sidebar';
17
+ import SyncPool from './state/sync_pool.js';
18
+ import BOUND_CALCULATORS from './state/bound_calculators.js';
19
+
20
+ function calculateClassNamesAndStyles(props, { draggingY, theme }) {
21
+ const { fullscreen, height, width } = props;
22
+
23
+ const classNames = [
24
+ 'grapher',
25
+ `grapher-${theme}`
26
+ ];
27
+
28
+ const styles = {};
29
+
30
+ if (draggingY) {
31
+ classNames.push('grapher-dragging-y');
32
+ }
33
+
34
+ if (fullscreen) {
35
+ classNames.push('grapher-fullscreen');
36
+ classNames.push('grapher-fixed-height');
37
+ } else if (height) {
38
+ classNames.push('grapher-fixed-height');
39
+ if (typeof height === 'number') {
40
+ styles.height = height - 20;
41
+ } else {
42
+ styles.height = `calc(${height} - 20px)`;
43
+ }
44
+ }
45
+
46
+ if (width) {
47
+ styles.width = width;
48
+ }
49
+
50
+ return {
51
+ styles,
52
+ classNames
53
+ };
54
+ }
55
+
56
+ export default React.memo(Grapher);
57
+
58
+ const grapherDefaultProps = {
59
+ theme: 'night',
60
+ showAxes: true,
61
+ showRangeGraph: true,
62
+ showRangeSelectors: true,
63
+ showSeriesKey: true,
64
+ showTooltips: true,
65
+ boundsSelectionEnabled: true,
66
+ customBoundsSelectors: [],
67
+ sidebarEnabled: false,
68
+ defaultShowAnnotations: true,
69
+ defaultShowOptions: true
70
+ };
71
+
72
+ function Grapher(props) {
73
+ props = {...grapherDefaultProps, ...props};
74
+
75
+ const stateController = useMemo(() => new StateController({
76
+ grapherID: props.id,
77
+ ...props,
78
+ ...props.stateControllerInitialization
79
+ }), []);
80
+
81
+ useEffect(() => {
82
+ if (process.env.NODE_ENV === 'development') {
83
+ window.stateController = stateController;
84
+ }
85
+
86
+ return () => {
87
+ stateController.dispose();
88
+ };
89
+ }, [stateController]);
90
+
91
+ useEffect(() => {
92
+ props.exportStateController && props.exportStateController(stateController);
93
+ }, [stateController, props.exportStateController]);
94
+
95
+ useEffect(() => {
96
+ stateController.timingFrameCount = props.timingFrameCount;
97
+ }, [stateController, props.timingFrameCount]);
98
+
99
+ useEffect(() => {
100
+ props.onRenderTime && stateController.on('render_time', props.onRenderTime);
101
+
102
+ return () => {
103
+ props.onRenderTime && stateController.off('render_time', props.onRenderTime);
104
+ };
105
+ }, [stateController, props.onRenderTime]);
106
+
107
+ useEffect(() => {
108
+ stateController.setSeries(props.series);
109
+ }, [stateController, props.series]);
110
+
111
+ useEffect(() => {
112
+ stateController.theme = props.theme;
113
+ }, [stateController, props.theme]);
114
+
115
+ const theme = useTheme(stateController);
116
+ const bigLabels = props.bigLabels || theme === 'export';
117
+
118
+ const defaultLineWidth = props.defaultLineWidth || (theme === 'export' ? 3 : undefined);
119
+ useEffect(() => {
120
+ stateController.defaultLineWidth = defaultLineWidth;
121
+ }, [stateController, defaultLineWidth]);
122
+
123
+ useEffect(() => {
124
+ stateController.percentile = props.percentile;
125
+ }, [stateController, props.percentile]);
126
+
127
+ useEffect(() => {
128
+ stateController.customBoundsSelectors = props.customBoundsSelectors;
129
+ }, [stateController, props.customBoundsSelectors]);
130
+
131
+ useEffect(() => {
132
+ stateController.annotations = props.annotations;
133
+ }, [stateController, props.annotations]);
134
+
135
+ useEffect(() => {
136
+ if (!props.onAxisChange) {
137
+ return () => {};
138
+ }
139
+
140
+ stateController.on('exported_axes_changed', props.onAxisChange);
141
+ return () => {
142
+ stateController.off('exported_axes_changed', props.onAxisChange);
143
+ };
144
+ }, [stateController, props.onAxisChange]);
145
+
146
+ useEffect(() => {
147
+ stateController.primaryRenderer.resize();
148
+ }, [props.height]);
149
+
150
+ const draggingY = useDraggingY(stateController);
151
+
152
+ const {styles, classNames} = calculateClassNamesAndStyles(props, { draggingY, theme });
153
+
154
+ const rightAxes = useRightAxes(stateController);
155
+ const leftAxes = useLeftAxes(stateController);
156
+ const showingSidebar = useShowingSidebar(stateController);
157
+
158
+ const showAxisColors = typeof props.showAxisColors === 'boolean' ? props.showAxisColors : (theme !== 'export');
159
+ const showGrid = typeof props.showGrid === 'boolean' ? props.showGrid : (theme !== 'export');
160
+
161
+ const commonYAxisProps = {
162
+ stateController,
163
+ showAxes: props.showAxes,
164
+ showGrid,
165
+ showSeriesKey: props.showSeriesKey,
166
+ bodyHeight: props.bodyHeight,
167
+ theme,
168
+ grapherID: props.id,
169
+ dragPositionYOffset: props.dragPositionYOffset,
170
+ showAxisColors,
171
+ bigLabels
172
+ };
173
+
174
+ return (
175
+ <div className={classNames.join(' ')} style={styles} data-grapher-id={props.id}>
176
+ {
177
+ props.title &&
178
+ <GraphTitle title={props.title} />
179
+ }
180
+
181
+ <div className="grapher-primary-container-outer">
182
+ {
183
+ showingSidebar &&
184
+ <Sidebar stateController={stateController} />
185
+ }
186
+
187
+ <div className="grapher-primary-container-body">
188
+ {
189
+ props.showSeriesKey &&
190
+ <SeriesKey
191
+ stateController={stateController}
192
+ draggingY={draggingY}
193
+ theme={props.theme}
194
+ grapherID={props.id}
195
+ dragPositionYOffset={props.dragPositionYOffset}
196
+ />
197
+ }
198
+
199
+ {
200
+ props.showRangeSelectors &&
201
+ <RangeSelection
202
+ stateController={stateController}
203
+ customBoundsSelectors={props.customBoundsSelectors}
204
+ customBoundsSelectorsOnly={props.customBoundsSelectorsOnly}
205
+ sidebarEnabled={props.sidebarEnabled}
206
+ />
207
+ }
208
+
209
+ <div className="grapher-main-row">
210
+ {
211
+ draggingY &&
212
+ <div
213
+ className="axis y-axis"
214
+ data-axis-index="new-left"
215
+ data-grapher-id={props.id}
216
+ style={{
217
+ width: Y_AXIS_WIDTH,
218
+ height: typeof props.bodyHeight === 'number' ? props.bodyHeight : undefined
219
+ }}
220
+ />
221
+ }
222
+
223
+ {
224
+ leftAxes.map((axis, i) => {
225
+ return <YAxis
226
+ key={i}
227
+ axis={axis}
228
+ sideIndex={leftAxes.length - i - 1}
229
+ {...commonYAxisProps}
230
+ />;
231
+ })
232
+ }
233
+
234
+ <div className="central-container">
235
+ <GraphBody
236
+ stateController={stateController}
237
+ webgl={props.webgl}
238
+ bodyHeight={props.bodyHeight}
239
+ boundsSelectionEnabled={props.boundsSelectionEnabled}
240
+ showTooltips={props.showTooltips}
241
+ tooltipOptions={props.tooltipOptions}
242
+ checkIntersection={props.checkIntersection}
243
+ draggablePoints={props.draggablePoints}
244
+ onPointDrag={props.onPointDrag}
245
+ onDraggablePointsDoubleClick={props.onDraggablePointsDoubleClick}
246
+ verticalLines={props.verticalLines}
247
+ clockStyle={props.clockStyle}
248
+ timeZone={props.timeZone}
249
+ />
250
+
251
+ <XAxis
252
+ showGrid={showGrid}
253
+ showAxes={props.showAxes}
254
+ stateController={stateController}
255
+ bigLabels={bigLabels}
256
+ xTickUnit={props.xTickUnit}
257
+ clockStyle={props.clockStyle}
258
+ timeZone={props.timeZone}
259
+ integersOnly={props.xAxisIntegersOnly}
260
+ />
261
+
262
+ {
263
+ props.showRangeGraph &&
264
+ <div className="range-graph-container">
265
+ <RangeGraph
266
+ stateController={stateController}
267
+ webgl={props.webgl}
268
+ checkIntersection={props.checkIntersection}
269
+ markDates={props.markRangeGraphDates}
270
+ timeZone={props.timeZone}
271
+ />
272
+ </div>
273
+ }
274
+ </div>
275
+
276
+ {
277
+ rightAxes.map((axis, i) => {
278
+ return <YAxis
279
+ key={i}
280
+ axis={axis}
281
+ sideIndex={i}
282
+ {...commonYAxisProps}
283
+ />;
284
+ })
285
+ }
286
+
287
+ {
288
+ draggingY &&
289
+ <div
290
+ className="axis y-axis"
291
+ data-axis-index="new-right"
292
+ data-grapher-id={props.id}
293
+ style={{
294
+ width: Y_AXIS_WIDTH,
295
+ height: typeof props.bodyHeight === 'number' ? props.bodyHeight : undefined
296
+ }}
297
+ />
298
+ }
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </div>
303
+ );
304
+ }
305
+
306
+ Grapher.propTypes = {
307
+ series: CustomPropTypes.Series.isRequired,
308
+ webgl: PropTypes.bool,
309
+ requireWASM: PropTypes.bool,
310
+ checkIntersection: PropTypes.bool,
311
+
312
+ onAxisChange: PropTypes.func,
313
+ onRenderTime: PropTypes.func,
314
+ exportStateController: PropTypes.func,
315
+ timingFrameCount: PropTypes.number,
316
+
317
+ stateControllerInitialization: PropTypes.object,
318
+ syncPool: PropTypes.instanceOf(SyncPool),
319
+ id: PropTypes.string,
320
+ dragPositionYOffset: PropTypes.number,
321
+
322
+ theme: PropTypes.oneOf(['day', 'night', 'export']),
323
+ title: PropTypes.string,
324
+ fullscreen: PropTypes.bool,
325
+ bodyHeight: PropTypes.number,
326
+ height: PropTypes.number,
327
+ width: PropTypes.number,
328
+
329
+ showAxes: PropTypes.bool,
330
+ showRangeGraph: PropTypes.bool,
331
+ showRangeSelectors: PropTypes.bool,
332
+ showSeriesKey: PropTypes.bool,
333
+ showTooltips: PropTypes.bool,
334
+ showGrid: PropTypes.bool,
335
+ showAxisColors: PropTypes.bool,
336
+ bigLabels: PropTypes.bool,
337
+ xTickUnit: PropTypes.oneOf(['year']),
338
+ xAxisIntegersOnly: PropTypes.bool,
339
+ clockStyle: PropTypes.oneOf(['12h', '24h']),
340
+ timeZone: PropTypes.string, // local, utc, or a full timezone string
341
+ markRangeGraphDates: PropTypes.bool,
342
+
343
+ boundsSelectionEnabled: PropTypes.bool,
344
+ sidebarEnabled: PropTypes.bool,
345
+
346
+ percentile: PropTypes.number,
347
+ defaultShowOptions: PropTypes.bool,
348
+ defaultShowIndividualPoints: PropTypes.bool,
349
+ defaultShowSidebar: PropTypes.bool,
350
+ defaultShowAnnotations: PropTypes.bool,
351
+ defaultLineWidth: PropTypes.number,
352
+
353
+ tooltipOptions: CustomPropTypes.TooltipOptions,
354
+
355
+ customBoundsSelectors: CustomPropTypes.CustomBoundsSelectors,
356
+ customBoundsSelectorsOnly: PropTypes.bool,
357
+ defaultBoundsCalculator: PropTypes.string,
358
+
359
+ annotations: CustomPropTypes.Annotations,
360
+ draggablePoints: CustomPropTypes.DraggablePoints,
361
+ onPointDrag: PropTypes.func,
362
+ onDraggablePointsDoubleClick: PropTypes.func,
363
+ verticalLines: CustomPropTypes.VerticalLines
364
+ };
365
+
366
+ export const AVAILABLE_COLORS = LINE_COLORS;
367
+ export const BUILT_IN_BOUND_CALCULATORS = BOUND_CALCULATORS;