@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,352 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import GraphBodyRenderer from '../renderer/graph_body_renderer';
4
+ import StateController from '../state/state_controller';
5
+ import placeGrid from '../helpers/place_grid';
6
+ import {formatX} from '../helpers/format';
7
+
8
+ export default class RangeGraph extends React.PureComponent {
9
+
10
+ constructor(props) {
11
+ super(props);
12
+
13
+ this.state = {
14
+ elementWidth: 0,
15
+ elementHeight: 0,
16
+ selectionBounds: {
17
+ minX: 0,
18
+ maxX: 0
19
+ },
20
+ globalBounds: {
21
+ minX: 0,
22
+ maxX: 0
23
+ }
24
+ };
25
+
26
+ this.onMouseMove = this.onMouseMove.bind(this);
27
+ this.stopDragging = this.stopDragging.bind(this);
28
+ this.startScroll = this.startScroll.bind(this);
29
+ this.startLeftDrag = this.startLeftDrag.bind(this);
30
+ this.startRightDrag = this.startRightDrag.bind(this);
31
+ }
32
+
33
+ componentDidMount() {
34
+ this._renderer = new GraphBodyRenderer({
35
+ stateController: this.props.stateController,
36
+ canvasElement: this.el,
37
+ webgl: this.props.webgl,
38
+ checkIntersection: this.props.checkIntersection
39
+ });
40
+ this.props.stateController.rangeGraphRenderer = this._renderer;
41
+
42
+ this._renderer.on('size_changed', (sizing) => {
43
+ this.setState(sizing);
44
+ });
45
+
46
+ this.setState({
47
+ selectionBounds: this.props.stateController._selection,
48
+ globalBounds: this.props.stateController._globalBounds
49
+ });
50
+
51
+ this.props.stateController.on('selection_changed', (selectionBounds) =>{
52
+ this.setState({ selectionBounds });
53
+ });
54
+
55
+ this.props.stateController.on('global_bounds_changed', (globalBounds) =>{
56
+ this.setState({ globalBounds: globalBounds });
57
+ });
58
+ }
59
+
60
+ componentDidUpdate(prevProps) {
61
+ if (prevProps.draggingY !== this.props.draggingY) {
62
+ this._renderer.resize();
63
+ }
64
+ }
65
+
66
+ componentWillUnmount() {
67
+ this._renderer.dispose();
68
+ this._renderer = null;
69
+ }
70
+
71
+ onMouseMove(event) {
72
+ if (!this._dragType) {
73
+ return;
74
+ }
75
+
76
+ let boundCalculator;
77
+ const leftX = this.el.getBoundingClientRect().left;
78
+
79
+ this.setState(({selectionBounds, globalBounds, elementWidth}) => {
80
+ const pixelX = event.clientX - leftX;
81
+ let percentage = pixelX/elementWidth;
82
+
83
+ percentage = Math.max(percentage, 0);
84
+ percentage = Math.min(percentage, 1);
85
+
86
+ let trueX = percentage * (globalBounds.maxX - globalBounds.minX) + globalBounds.minX;
87
+
88
+ if (this._dragType === 'scroll') {
89
+ const range = selectionBounds.maxX - selectionBounds.minX;
90
+ let minX = trueX - range*this._scrollAnchorPercentage;
91
+ let maxX = trueX + range*(1-this._scrollAnchorPercentage);
92
+
93
+ if (minX < globalBounds.minX) {
94
+ minX = globalBounds.minX;
95
+ maxX = minX + range;
96
+ }
97
+
98
+ if (maxX > globalBounds.maxX) {
99
+ maxX = globalBounds.maxX;
100
+ minX = maxX - range;
101
+ }
102
+
103
+ boundCalculator = () => {
104
+ return {minX, maxX};
105
+ };
106
+
107
+ return {
108
+ selectionBounds: Object.assign({}, selectionBounds, {
109
+ minX, maxX
110
+ })
111
+ };
112
+ } else if (this._dragType === 'left') {
113
+ if (trueX > selectionBounds.maxX) {
114
+ trueX = selectionBounds.maxX;
115
+ }
116
+
117
+ const boundToRight = globalBounds.maxX === selectionBounds.maxX;
118
+
119
+ boundCalculator = () => {
120
+ if (boundToRight) {
121
+ return {
122
+ minX: trueX
123
+ };
124
+ } else {
125
+ return {
126
+ minX: trueX,
127
+ maxX: selectionBounds.maxX
128
+ };
129
+ }
130
+ };
131
+
132
+ return {
133
+ selectionBounds: Object.assign({}, selectionBounds, {
134
+ minX: trueX
135
+ })
136
+ };
137
+ } else if (this._dragType === 'right') {
138
+ if (trueX < selectionBounds.minX) {
139
+ trueX = selectionBounds.minX;
140
+ }
141
+
142
+ const boundToLeft = globalBounds.minX === selectionBounds.minX;
143
+
144
+ boundCalculator = () => {
145
+ if (boundToLeft) {
146
+ return {
147
+ maxX: trueX
148
+ };
149
+ } else {
150
+ return {
151
+ minX: selectionBounds.minX,
152
+ maxX: trueX
153
+ };
154
+ }
155
+ };
156
+
157
+ return {
158
+ selectionBounds: Object.assign({}, selectionBounds, {
159
+ maxX: trueX
160
+ })
161
+ };
162
+ }
163
+ }, () => {
164
+ if (!boundCalculator) {
165
+ return;
166
+ }
167
+
168
+ boundCalculator.debounceHistory = true;
169
+ this.props.stateController.boundCalculator = boundCalculator;
170
+ });
171
+ }
172
+
173
+ addListeners() {
174
+ window.addEventListener('mousemove', this.onMouseMove);
175
+ window.addEventListener('mouseup', this.stopDragging);
176
+ }
177
+
178
+ stopDragging() {
179
+ this._dragType = null;
180
+ window.removeEventListener('mousemove', this.onMouseMove);
181
+ window.removeEventListener('mouseup', this.stopDragging);
182
+ }
183
+
184
+ startScroll(event) {
185
+ this._dragType = 'scroll';
186
+
187
+ const {selectionBounds, globalBounds, elementWidth} = this.state;
188
+ const leftX = this.el.getBoundingClientRect().left;
189
+
190
+ const pixelStartX = event.clientX - leftX;
191
+ const pixelMinX = (selectionBounds.minX - globalBounds.minX)/(globalBounds.maxX - globalBounds.minX) * elementWidth || 0;
192
+ const pixelMaxX = (selectionBounds.maxX - globalBounds.minX)/(globalBounds.maxX - globalBounds.minX) * elementWidth || 0;
193
+
194
+ this._scrollAnchorPercentage = (pixelStartX-pixelMinX)/(pixelMaxX - pixelMinX);
195
+ this.addListeners();
196
+ }
197
+
198
+ startLeftDrag() {
199
+ this._dragType = 'left';
200
+ this.addListeners();
201
+ }
202
+
203
+ startRightDrag() {
204
+ this._dragType = 'right';
205
+ this.addListeners();
206
+ }
207
+
208
+ render() {
209
+ const { globalBounds, selectionBounds, elementWidth, elementHeight } = this.state;
210
+
211
+ let pixelMinX = Math.min(Math.max((selectionBounds.minX - globalBounds.minX)/(globalBounds.maxX - globalBounds.minX), 0), 1) * elementWidth || 0;
212
+ let pixelMaxX = Math.min(Math.max((selectionBounds.maxX - globalBounds.minX)/(globalBounds.maxX - globalBounds.minX), 0), 1) * elementWidth || 0;
213
+
214
+ if (isNaN(pixelMinX) || !isFinite(pixelMinX) || selectionBounds.maxX < selectionBounds.minX) {
215
+ pixelMinX = 0;
216
+ }
217
+
218
+ if (isNaN(pixelMaxX) || !isFinite(pixelMaxX) || selectionBounds.maxX < selectionBounds.minX) {
219
+ pixelMaxX = 0;
220
+ }
221
+
222
+ const barSize = 14;
223
+ let ticks;
224
+
225
+ if (selectionBounds.dates && this.props.markDates) {
226
+ ticks = placeGrid({
227
+ min: globalBounds.minX,
228
+ max: globalBounds.maxX,
229
+ totalSize: elementWidth,
230
+ precision: 'day',
231
+ dates: selectionBounds.dates,
232
+ formatter: formatX,
233
+ expectedLabelSize: 30,
234
+ formatOptions: {
235
+ justMonthAndDay: true,
236
+ unitOverride: 'day',
237
+ timeZone: this.props.timeZone
238
+ },
239
+ skipFirst: true,
240
+ skipLast: true
241
+ });
242
+ }
243
+
244
+ return (
245
+ <div className="range-selection-graph">
246
+ <div className="graph-body graph-body-secondary">
247
+ <canvas ref={(el) => this.el = el} />
248
+
249
+ <svg>
250
+ <g>
251
+ <rect
252
+ x={0}
253
+ y={elementHeight}
254
+ width={elementWidth}
255
+ height={barSize}
256
+ className="selection-bar-track"
257
+ />
258
+
259
+ {
260
+ ticks && ticks.map(({ pixelValue, label, size, position }, i) => {
261
+ if (isNaN(pixelValue)) {
262
+ return null;
263
+ }
264
+
265
+ const classes = ['axis-item', `axis-item-${size}`, `axis-item-${position}`];
266
+
267
+ return (
268
+ <g key={i} className={classes.join(' ')}>
269
+ <path d={`M${pixelValue},0 v${elementHeight}`} />
270
+
271
+ <text x={pixelValue + 3} y={elementHeight}>
272
+ {label}
273
+ </text>
274
+ </g>
275
+ );
276
+ })
277
+ }
278
+
279
+ <rect
280
+ x={pixelMinX}
281
+ y={elementHeight}
282
+ width={pixelMaxX - pixelMinX}
283
+ height={barSize}
284
+ className="selection-bar"
285
+ onMouseDown={this.startScroll}
286
+ />
287
+
288
+ <path
289
+ d="M -3 3.5 L -3 9.333333333333334 M 0 3.5 L 0 9.333333333333334 M 3 3.5 L 3 9.333333333333334"
290
+ className="selection-bar-rifles"
291
+ transform={`translate(${pixelMinX + (pixelMaxX - pixelMinX)/2},${elementHeight})`}
292
+ onMouseDown={this.startScroll}
293
+ />
294
+ </g>
295
+
296
+ <g>
297
+ <rect
298
+ x={pixelMinX}
299
+ y={0}
300
+ width={pixelMaxX - pixelMinX}
301
+ height={elementHeight}
302
+ className="target-selection"
303
+ onMouseDown={this.startScroll}
304
+ />
305
+
306
+ <rect
307
+ x={pixelMinX}
308
+ y={0}
309
+ width={pixelMaxX - pixelMinX}
310
+ height={elementHeight + barSize}
311
+ className="target-selection-outline"
312
+ />
313
+ </g>
314
+
315
+ <g>
316
+ <path
317
+ d="M -4.5 0.5 L 3.5 0.5 L 3.5 15.5 L -4.5 15.5 L -4.5 0.5 M -1.5 4 L -1.5 12 M 0.5 4 L 0.5 12"
318
+ className="selection-bar-handle"
319
+ transform={`translate(${pixelMinX},${(elementHeight - 15)/2})`}
320
+ onMouseDown={this.startLeftDrag}
321
+ />
322
+ </g>
323
+
324
+ <g>
325
+ <path
326
+ d="M -4.5 0.5 L 3.5 0.5 L 3.5 15.5 L -4.5 15.5 L -4.5 0.5 M -1.5 4 L -1.5 12 M 0.5 4 L 0.5 12"
327
+ className="selection-bar-handle"
328
+ transform={`translate(${pixelMaxX},${(elementHeight - 15)/2})`}
329
+ onMouseDown={this.startRightDrag}
330
+ />
331
+ </g>
332
+ </svg>
333
+ </div>
334
+ </div>
335
+ );
336
+ }
337
+
338
+ }
339
+
340
+ RangeGraph.defaultProps = {
341
+ width: 3,
342
+ shadowColor: 'transparent'
343
+ };
344
+
345
+ RangeGraph.propTypes = {
346
+ stateController: PropTypes.instanceOf(StateController).isRequired,
347
+ webgl: PropTypes.bool,
348
+ draggingY: PropTypes.bool,
349
+ checkIntersection: PropTypes.bool,
350
+ markDates: PropTypes.bool,
351
+ timeZone: PropTypes.string
352
+ };
@@ -0,0 +1,175 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import RangeSelectionButton from './range_selection_button';
4
+ import RangeSelectionButtonBase from './range_selection_button_base';
5
+ import {Y_AXIS_WIDTH} from '../helpers/axis_sizes';
6
+ import CustomPropTypes from '../helpers/custom_prop_types';
7
+ import StateController from '../state/state_controller';
8
+ import {
9
+ useBoundCalculator, useBoundHistory,
10
+ useGlobalBounds,
11
+ useLeftAxisCount,
12
+ useRightAxisCount,
13
+ useShowingOptions
14
+ } from '../state/hooks';
15
+ import BOUND_CALCULATORS from '../state/bound_calculators';
16
+ import Options from './options';
17
+
18
+ export default React.memo(RangeSelection);
19
+
20
+ function RangeSelection({stateController, customBoundsSelectors, customBoundsSelectorsOnly, sidebarEnabled}) {
21
+ const rightAxisCount = useRightAxisCount(stateController);
22
+ const leftAxisCount = useLeftAxisCount(stateController);
23
+ const showingOptions = useShowingOptions(stateController);
24
+
25
+ let marginRight = Y_AXIS_WIDTH*rightAxisCount;
26
+ if (rightAxisCount > 0) {
27
+ marginRight += 5;
28
+ }
29
+
30
+ let marginLeft = Y_AXIS_WIDTH*leftAxisCount;
31
+ if (leftAxisCount > 0) {
32
+ marginLeft += 5;
33
+ }
34
+
35
+ const { dates } = useGlobalBounds(stateController);
36
+
37
+ const currentBoundCalculator = useBoundCalculator(stateController);
38
+ const { hasPreviousBounds, hasNextBounds } = useBoundHistory(stateController);
39
+
40
+ const customBoundSelectorNames = new Set(customBoundsSelectors.map(({ label }) => label));
41
+
42
+ return (
43
+ <div className={`range-selection${dates ? '' : ' range-not-dates'}`} style={{ marginRight, marginLeft }}>
44
+ <div className="range-buttons">
45
+ {
46
+ showingOptions &&
47
+ <Options
48
+ stateController={stateController}
49
+ sidebarEnabled={sidebarEnabled}
50
+ />
51
+ }
52
+
53
+ <RangeSelectionButtonBase
54
+ className="showing-options-button"
55
+ selected={showingOptions}
56
+ onClick={() => stateController.toggleShowingOptions()}
57
+ description="Show additional options"
58
+ >
59
+ <div className="icon-container icon-container-square">
60
+ <svg focusable="false" viewBox="0 0 512 512">
61
+ <path fill="currentColor" d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z" />
62
+ </svg>
63
+ </div>
64
+ </RangeSelectionButtonBase>
65
+
66
+ <RangeSelectionButtonBase
67
+ className="range-selection-history"
68
+ selected={false}
69
+ onClick={() => stateController.previousBounds()}
70
+ disabled={!hasPreviousBounds}
71
+ >
72
+ <div className="icon-container">
73
+ <svg focusable="false" viewBox="0 0 256 512">
74
+ <path fill="currentColor" d="M31.7 239l136-136c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9L127.9 256l96.4 96.4c9.4 9.4 9.4 24.6 0 33.9L201.7 409c-9.4 9.4-24.6 9.4-33.9 0l-136-136c-9.5-9.4-9.5-24.6-.1-34z" />
75
+ </svg>
76
+ </div>
77
+ </RangeSelectionButtonBase>
78
+
79
+ <RangeSelectionButtonBase
80
+ className="range-selection-history"
81
+ selected={false}
82
+ onClick={() => stateController.nextBounds()}
83
+ disabled={!hasNextBounds}
84
+ >
85
+ <div className="icon-container">
86
+ <svg focusable="false" viewBox="0 0 256 512">
87
+ <path fill="currentColor" d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"/>
88
+ </svg>
89
+ </div>
90
+ </RangeSelectionButtonBase>
91
+
92
+ {
93
+ customBoundsSelectors.map(({label, calculator, datesOnly}, i) => {
94
+ return (
95
+ <RangeSelectionButton
96
+ key={i}
97
+ stateController={stateController}
98
+ currentBoundCalculator={currentBoundCalculator}
99
+ boundCalculator={calculator}
100
+ disabled={datesOnly && !dates}
101
+ >
102
+ {label}
103
+ </RangeSelectionButton>
104
+ );
105
+ })
106
+ }
107
+
108
+ {
109
+ !customBoundSelectorNames.has('1m') && !customBoundsSelectorsOnly &&
110
+ <RangeSelectionButton
111
+ stateController={stateController}
112
+ currentBoundCalculator={currentBoundCalculator}
113
+ boundCalculator={BOUND_CALCULATORS.lastMinute}
114
+ disabled={!dates}
115
+ >
116
+ 1m
117
+ </RangeSelectionButton>
118
+ }
119
+
120
+ {
121
+ !customBoundSelectorNames.has('10m') && !customBoundsSelectorsOnly &&
122
+ <RangeSelectionButton
123
+ stateController={stateController}
124
+ currentBoundCalculator={currentBoundCalculator}
125
+ boundCalculator={BOUND_CALCULATORS.last10Minutes}
126
+ disabled={!dates}
127
+ >
128
+ 10m
129
+ </RangeSelectionButton>
130
+ }
131
+
132
+ {
133
+ !customBoundSelectorNames.has('1h') && !customBoundsSelectorsOnly &&
134
+ <RangeSelectionButton
135
+ stateController={stateController}
136
+ currentBoundCalculator={currentBoundCalculator}
137
+ boundCalculator={BOUND_CALCULATORS.lastHour}
138
+ disabled={!dates}
139
+ >
140
+ 1h
141
+ </RangeSelectionButton>
142
+ }
143
+
144
+ {
145
+ !customBoundSelectorNames.has('1d') && !customBoundsSelectorsOnly &&
146
+ <RangeSelectionButton
147
+ stateController={stateController}
148
+ currentBoundCalculator={currentBoundCalculator}
149
+ boundCalculator={BOUND_CALCULATORS.lastDay}
150
+ disabled={!dates}
151
+ >
152
+ 1d
153
+ </RangeSelectionButton>
154
+ }
155
+
156
+ <RangeSelectionButton
157
+ stateController={stateController}
158
+ currentBoundCalculator={currentBoundCalculator}
159
+ boundCalculator={BOUND_CALCULATORS.all}
160
+ disabled={false}
161
+ >
162
+ All
163
+ </RangeSelectionButton>
164
+ </div>
165
+ </div>
166
+ );
167
+
168
+ }
169
+
170
+ RangeSelection.propTypes = {
171
+ stateController: PropTypes.instanceOf(StateController).isRequired,
172
+ customBoundsSelectors: CustomPropTypes.CustomBoundsSelectors.isRequired,
173
+ customBoundsSelectorsOnly: PropTypes.bool,
174
+ sidebarEnabled: PropTypes.bool
175
+ };
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import RangeSelectionButtonBase from './range_selection_button_base';
4
+ import StateController from '../state/state_controller';
5
+
6
+ export default React.memo(RangeSelectionButton);
7
+
8
+ function RangeSelectionButton({ stateController, currentBoundCalculator, boundCalculator, children, disabled }) {
9
+ return (
10
+ <RangeSelectionButtonBase
11
+ selected={currentBoundCalculator === boundCalculator}
12
+ onClick={() => stateController.boundCalculator = boundCalculator}
13
+ disabled={disabled}
14
+ >
15
+ {children}
16
+ </RangeSelectionButtonBase>
17
+ );
18
+ }
19
+
20
+ RangeSelectionButton.propTypes = {
21
+ stateController: PropTypes.instanceOf(StateController).isRequired,
22
+ boundCalculator: PropTypes.func.isRequired,
23
+ currentBoundCalculator: PropTypes.func.isRequired,
24
+ children: PropTypes.node.isRequired,
25
+ disabled: PropTypes.bool
26
+ };
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ export default React.memo(RangeSelectionButtonBase);
5
+
6
+ function RangeSelectionButtonBase({ selected, disabled, className, onClick, children, description }) {
7
+ const classes = [
8
+ 'range-button'
9
+ ];
10
+
11
+ if (selected) {
12
+ classes.push('range-button-selected');
13
+ }
14
+
15
+ if (disabled) {
16
+ classes.push('range-button-disabled');
17
+ }
18
+
19
+ if (className) {
20
+ classes.push(className);
21
+ }
22
+
23
+ if (description) {
24
+ classes.push('option-tooltip');
25
+ }
26
+
27
+ return (
28
+ <div
29
+ className={classes.join(' ')}
30
+ onClick={onClick && ((event) => disabled || onClick(event))}
31
+ >
32
+ {children}
33
+
34
+ {
35
+ description &&
36
+ <div className="option-tooltip-text">
37
+ {description}
38
+ </div>
39
+ }
40
+ </div>
41
+ );
42
+ }
43
+
44
+ RangeSelectionButtonBase.propTypes = {
45
+ selected: PropTypes.bool.isRequired,
46
+ onClick: PropTypes.func,
47
+ children: PropTypes.node.isRequired,
48
+ disabled: PropTypes.bool,
49
+ className: PropTypes.string,
50
+ description: PropTypes.string
51
+ };