@windborne/grapher 1.0.22 → 1.0.24

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 (44) hide show
  1. package/dist/26b23c7580ea3345d644.wasm +0 -0
  2. package/dist/744.bundle.cjs +2 -1
  3. package/dist/744.bundle.cjs.map +1 -0
  4. package/dist/744.bundle.esm.js +2 -1
  5. package/dist/744.bundle.esm.js.map +1 -0
  6. package/dist/bundle.cjs +1 -1
  7. package/dist/bundle.cjs.map +1 -1
  8. package/dist/bundle.esm.js +1 -1
  9. package/dist/bundle.esm.js.map +1 -1
  10. package/package.json +1 -1
  11. package/readme.md +1 -1
  12. package/src/components/graph_body.jsx +41 -1
  13. package/src/components/range_graph.jsx +70 -22
  14. package/src/components/x_axis.jsx +139 -21
  15. package/src/components/y_axis.jsx +0 -5
  16. package/src/grapher.jsx +4 -3
  17. package/src/grapher.scss +84 -43
  18. package/src/helpers/color_to_vector.js +6 -1
  19. package/src/helpers/colors.js +82 -16
  20. package/src/helpers/custom_prop_types.js +2 -1
  21. package/src/helpers/place_grid.js +219 -0
  22. package/src/index.d.ts +3 -2
  23. package/src/renderer/create_gl_program.js +11 -3
  24. package/src/renderer/draw_area.js +1161 -102
  25. package/src/renderer/draw_background.js +5 -0
  26. package/src/renderer/draw_bars.js +100 -11
  27. package/src/renderer/draw_line.js +338 -38
  28. package/src/renderer/draw_zero_line.js +5 -0
  29. package/src/renderer/extract_vertices.js +1 -1
  30. package/src/renderer/graph_body_renderer.js +278 -17
  31. package/src/renderer/line.frag +16 -1
  32. package/src/renderer/line_program.js +200 -10
  33. package/src/renderer/shadow.frag +98 -0
  34. package/src/renderer/shadow.vert +20 -0
  35. package/src/renderer/shadow_program.js +479 -0
  36. package/src/rust/Cargo.lock +22 -31
  37. package/src/rust/pkg/grapher_rs.d.ts +6 -0
  38. package/src/rust/pkg/grapher_rs.js +5 -0
  39. package/src/rust/pkg/grapher_rs_bg.js +305 -0
  40. package/src/rust/pkg/grapher_rs_bg.wasm +0 -0
  41. package/src/rust/pkg/grapher_rs_bg.wasm.d.ts +11 -0
  42. package/src/rust/pkg/index.js +397 -0
  43. package/src/state/calculate_tooltip_state.js +6 -6
  44. package/src/state/state_controller.js +20 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windborne/grapher",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Graphing library",
5
5
  "main": "src/index.js",
6
6
  "module": "dist/bundle.esm.js",
package/readme.md CHANGED
@@ -78,7 +78,7 @@ The `series` prop requires an array of objects, where each object represents a d
78
78
  - **name**: Series name for display in legend
79
79
  - **xLabel**: Label for x-axis
80
80
  - **yLabel**: Label for y-axis
81
- - **rendering**: Visual representation ('line', 'bar', or 'area', defaults to 'line')
81
+ - **rendering**: Visual representation ('line', 'bar', 'area', or 'shadow', defaults to 'line'). The 'shadow' option creates an area chart with individual point-based (trapezoid) gradients extending downward.
82
82
  - **ignoreDiscontinuities**: Whether to connect points across gaps (boolean)
83
83
  - **dashed**: Whether to use dashed lines (boolean)
84
84
  - **dashPattern**: Array defining dash pattern (array of numbers)
@@ -159,12 +159,52 @@ function GraphBody({ stateController, webgl, bodyHeight, boundsSelectionEnabled,
159
159
  });
160
160
  };
161
161
 
162
+ const getTouch = (event) => event.touches?.[0] || event.changedTouches?.[0];
163
+
164
+ const onGlobalTouchStart = (event) => {
165
+ if (!showTooltips) {
166
+ return;
167
+ }
168
+ const touch = getTouch(event);
169
+ if (!touch) return;
170
+ stateController.setTooltipMousePosition({
171
+ clientX: touch.clientX,
172
+ clientY: touch.clientY
173
+ });
174
+ if (event.cancelable) event.preventDefault();
175
+ };
176
+
177
+ const onGlobalTouchMove = (event) => {
178
+ if (!showTooltips) {
179
+ return;
180
+ }
181
+ const touch = getTouch(event);
182
+ if (!touch) return;
183
+ stateController.setTooltipMousePosition({
184
+ clientX: touch.clientX,
185
+ clientY: touch.clientY
186
+ });
187
+ if (event.cancelable) event.preventDefault();
188
+ };
189
+
190
+ const onGlobalTouchEnd = () => {
191
+ stateController.showOnlySavedTooltips();
192
+ };
193
+
162
194
  window.addEventListener('scroll', onScroll, { passive: true });
163
195
  window.addEventListener('mousemove', onGlobalMouseMove, { passive: true });
196
+ window.addEventListener('touchstart', onGlobalTouchStart, { passive: false });
197
+ window.addEventListener('touchmove', onGlobalTouchMove, { passive: false });
198
+ window.addEventListener('touchend', onGlobalTouchEnd, { passive: true });
199
+ window.addEventListener('touchcancel', onGlobalTouchEnd, { passive: true });
164
200
 
165
201
  return () => {
166
202
  window.removeEventListener('scroll', onScroll);
167
203
  window.removeEventListener('mousemove', onGlobalMouseMove);
204
+ window.removeEventListener('touchstart', onGlobalTouchStart);
205
+ window.removeEventListener('touchmove', onGlobalTouchMove);
206
+ window.removeEventListener('touchend', onGlobalTouchEnd);
207
+ window.removeEventListener('touchcancel', onGlobalTouchEnd);
168
208
  };
169
209
  }, []);
170
210
 
@@ -319,4 +359,4 @@ GraphBody.propTypes = {
319
359
  onPointClick: PropTypes.func,
320
360
  clockStyle: PropTypes.oneOf(['12h', '24h']),
321
361
  timeZone: PropTypes.string
322
- };
362
+ };
@@ -32,6 +32,16 @@ export default class RangeGraph extends React.PureComponent {
32
32
  this.startRightDrag = this.startRightDrag.bind(this);
33
33
  }
34
34
 
35
+ getClientX(event) {
36
+ if (event && event.touches && event.touches[0]) {
37
+ return event.touches[0].clientX;
38
+ }
39
+ if (event && event.changedTouches && event.changedTouches[0]) {
40
+ return event.changedTouches[0].clientX;
41
+ }
42
+ return event.clientX;
43
+ }
44
+
35
45
  componentDidMount() {
36
46
  this._renderer = new GraphBodyRenderer({
37
47
  stateController: this.props.stateController,
@@ -60,14 +70,16 @@ export default class RangeGraph extends React.PureComponent {
60
70
  }
61
71
 
62
72
  componentDidUpdate(prevProps) {
63
- if (prevProps.draggingY !== this.props.draggingY) {
73
+ if (prevProps.draggingY !== this.props.draggingY && this._renderer) {
64
74
  this._renderer.resize();
65
75
  }
66
76
  }
67
77
 
68
78
  componentWillUnmount() {
69
- this._renderer.dispose();
70
- this._renderer = null;
79
+ if (this._renderer) {
80
+ this._renderer.dispose();
81
+ this._renderer = null;
82
+ }
71
83
  }
72
84
 
73
85
  onMouseMove(event) {
@@ -75,11 +87,16 @@ export default class RangeGraph extends React.PureComponent {
75
87
  return;
76
88
  }
77
89
 
90
+ // Prevent scrolling during touch-drag
91
+ if (event && event.touches && event.preventDefault) {
92
+ event.preventDefault();
93
+ }
94
+
78
95
  let boundCalculator;
79
96
  const leftX = this.el.getBoundingClientRect().left;
80
97
 
81
98
  this.setState(({selectionBounds, globalBounds, elementWidth}) => {
82
- const pixelX = event.clientX - leftX;
99
+ const pixelX = this.getClientX(event) - leftX;
83
100
  let percentage = pixelX/elementWidth;
84
101
 
85
102
  percentage = Math.max(percentage, 0);
@@ -175,12 +192,19 @@ export default class RangeGraph extends React.PureComponent {
175
192
  addListeners() {
176
193
  window.addEventListener('mousemove', this.onMouseMove);
177
194
  window.addEventListener('mouseup', this.stopDragging);
195
+ window.addEventListener('touchmove', this.onMouseMove, { passive: false });
196
+ window.addEventListener('touchend', this.stopDragging);
197
+ window.addEventListener('touchcancel', this.stopDragging);
178
198
  }
179
199
 
180
200
  stopDragging() {
181
201
  this._dragType = null;
202
+ this.forceUpdate();
182
203
  window.removeEventListener('mousemove', this.onMouseMove);
183
204
  window.removeEventListener('mouseup', this.stopDragging);
205
+ window.removeEventListener('touchmove', this.onMouseMove);
206
+ window.removeEventListener('touchend', this.stopDragging);
207
+ window.removeEventListener('touchcancel', this.stopDragging);
184
208
  }
185
209
 
186
210
  startScroll(event) {
@@ -189,7 +213,7 @@ export default class RangeGraph extends React.PureComponent {
189
213
  const {selectionBounds, globalBounds, elementWidth} = this.state;
190
214
  const leftX = this.el.getBoundingClientRect().left;
191
215
 
192
- const pixelStartX = event.clientX - leftX;
216
+ const pixelStartX = this.getClientX(event) - leftX;
193
217
  const pixelMinX = (selectionBounds.minX - globalBounds.minX)/(globalBounds.maxX - globalBounds.minX) * elementWidth || 0;
194
218
  const pixelMaxX = (selectionBounds.maxX - globalBounds.minX)/(globalBounds.maxX - globalBounds.minX) * elementWidth || 0;
195
219
 
@@ -197,12 +221,12 @@ export default class RangeGraph extends React.PureComponent {
197
221
  this.addListeners();
198
222
  }
199
223
 
200
- startLeftDrag() {
224
+ startLeftDrag(event) {
201
225
  this._dragType = 'left';
202
226
  this.addListeners();
203
227
  }
204
228
 
205
- startRightDrag() {
229
+ startRightDrag(event) {
206
230
  this._dragType = 'right';
207
231
  this.addListeners();
208
232
  }
@@ -222,6 +246,7 @@ export default class RangeGraph extends React.PureComponent {
222
246
  }
223
247
 
224
248
  const barSize = 14;
249
+ const totalHeight = elementHeight + barSize;
225
250
  let ticks;
226
251
 
227
252
  if (selectionBounds.dates && this.props.markDates) {
@@ -246,10 +271,10 @@ export default class RangeGraph extends React.PureComponent {
246
271
 
247
272
  return (
248
273
  <div className="range-selection-graph">
249
- <div className="graph-body graph-body-secondary">
274
+ <div className="graph-body graph-body-secondary" style={{ touchAction: 'none' }}>
250
275
  <canvas ref={(el) => this.el = el} />
251
276
 
252
- <svg>
277
+ <svg style={{ height: totalHeight }}>
253
278
  <g>
254
279
  <rect
255
280
  x={0}
@@ -266,6 +291,11 @@ export default class RangeGraph extends React.PureComponent {
266
291
  }
267
292
 
268
293
  const classes = ['axis-item', `axis-item-${size}`, `axis-item-${position}`];
294
+
295
+ const isHighlighted = pixelValue >= pixelMinX && pixelValue <= pixelMaxX;
296
+ if (isHighlighted) {
297
+ classes.push('axis-item-highlighted');
298
+ }
269
299
 
270
300
  return (
271
301
  <g key={i} className={classes.join(' ')}>
@@ -286,6 +316,7 @@ export default class RangeGraph extends React.PureComponent {
286
316
  height={barSize}
287
317
  className="selection-bar"
288
318
  onMouseDown={this.startScroll}
319
+ onTouchStart={this.startScroll}
289
320
  />
290
321
 
291
322
  <path
@@ -293,6 +324,7 @@ export default class RangeGraph extends React.PureComponent {
293
324
  className="selection-bar-rifles"
294
325
  transform={`translate(${pixelMinX + (pixelMaxX - pixelMinX)/2},${elementHeight})`}
295
326
  onMouseDown={this.startScroll}
327
+ onTouchStart={this.startScroll}
296
328
  />
297
329
  </g>
298
330
 
@@ -301,37 +333,53 @@ export default class RangeGraph extends React.PureComponent {
301
333
  x={pixelMinX}
302
334
  y={0}
303
335
  width={pixelMaxX - pixelMinX}
304
- height={elementHeight}
305
- className="target-selection"
306
- onMouseDown={this.startScroll}
307
- />
308
-
309
- <rect
310
- x={pixelMinX}
311
- y={0}
312
- width={pixelMaxX - pixelMinX}
313
- height={elementHeight + barSize}
336
+ height={totalHeight}
314
337
  className="target-selection-outline"
315
338
  />
316
339
  </g>
317
340
 
318
- <g>
341
+ {/* Left handle */}
342
+ <g className={`selection-handle${(this._dragType === 'left' || this._dragType === 'scroll') ? ' selection-handle-dragging' : ''}`}>
343
+ <rect
344
+ x={pixelMinX - 15}
345
+ y={(elementHeight - 30)/2}
346
+ width={30}
347
+ height={30}
348
+ fill="transparent"
349
+ className="selection-bar-handle-hit"
350
+ onMouseDown={this.startLeftDrag}
351
+ onTouchStart={this.startLeftDrag}
352
+ />
319
353
  <path
320
354
  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"
321
355
  className="selection-bar-handle"
322
356
  transform={`translate(${pixelMinX},${(elementHeight - 15)/2})`}
323
357
  onMouseDown={this.startLeftDrag}
358
+ onTouchStart={this.startLeftDrag}
324
359
  />
325
360
  </g>
326
361
 
327
- <g>
362
+ {/* Right handle */}
363
+ <g className={`selection-handle${(this._dragType === 'right' || this._dragType === 'scroll') ? ' selection-handle-dragging' : ''}`}>
364
+ <rect
365
+ x={pixelMaxX - 15}
366
+ y={(elementHeight - 30)/2}
367
+ width={30}
368
+ height={30}
369
+ fill="transparent"
370
+ className="selection-bar-handle-hit"
371
+ onMouseDown={this.startRightDrag}
372
+ onTouchStart={this.startRightDrag}
373
+ />
328
374
  <path
329
375
  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"
330
376
  className="selection-bar-handle"
331
377
  transform={`translate(${pixelMaxX},${(elementHeight - 15)/2})`}
332
378
  onMouseDown={this.startRightDrag}
379
+ onTouchStart={this.startRightDrag}
333
380
  />
334
381
  </g>
382
+
335
383
  </svg>
336
384
 
337
385
  {
@@ -365,4 +413,4 @@ RangeGraph.propTypes = {
365
413
  markDates: PropTypes.bool,
366
414
  timeZone: PropTypes.string,
367
415
  verticalLines: CustomPropTypes.VerticalLines
368
- };
416
+ };
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import {calculatePrecision, calculateTimePrecision, formatX} from '../helpers/format';
4
- import placeGrid from '../helpers/place_grid';
4
+ import placeGrid, { placeTimeOnlyGrid, placeDateOnlyGrid } from '../helpers/place_grid';
5
5
  import {useEnumMap, useHasXEnum, usePrimarySize, useSelection} from '../state/hooks';
6
6
  import StateController from '../state/state_controller';
7
7
 
@@ -44,40 +44,153 @@ function XAxis({ showAxes, showGrid, stateController, bigLabels, xTickUnit, cloc
44
44
  const minLabel = formatX(minX, {...formatOptions, dates, precision }).toString();
45
45
  const maxLabel = formatX(maxX, {...formatOptions, dates, precision }).toString();
46
46
 
47
- let expectedLabelWidth = Math.max(minLabel.length, maxLabel.length) * 4;
47
+ let expectedLabelWidth = Math.max(minLabel.length, maxLabel.length);
48
48
  if (bigLabels) {
49
49
  expectedLabelWidth *= 2;
50
50
  }
51
51
 
52
- const labelPadding = 30; // space in between labels in the expected case
52
+ const labelPadding = 30;
53
53
 
54
- const ticks = placeGrid({
55
- min: minX,
56
- max: maxX,
57
- totalSize: elementWidth,
58
- precision,
59
- dates,
60
- formatter: formatXAxisLabel || formatX,
61
- expectedLabelSize: expectedLabelWidth,
62
- labelPadding,
63
- formatOptions
64
- });
54
+ let timeTicks = null;
55
+ let dateTicks = null;
56
+ let regularTicks = null;
57
+
58
+ if (dates) {
59
+ timeTicks = placeTimeOnlyGrid({
60
+ min: minX,
61
+ max: maxX,
62
+ totalSize: elementWidth,
63
+ precision,
64
+ expectedLabelSize: expectedLabelWidth,
65
+ labelPadding: labelPadding * 0.8,
66
+ formatter: formatXAxisLabel || formatX,
67
+ formatOptions
68
+ });
69
+
70
+ dateTicks = placeDateOnlyGrid({
71
+ min: minX,
72
+ max: maxX,
73
+ totalSize: elementWidth,
74
+ precision,
75
+ expectedLabelSize: expectedLabelWidth * 2,
76
+ labelPadding: labelPadding * 1.5,
77
+ formatter: formatXAxisLabel || formatX,
78
+ formatOptions
79
+ });
80
+ } else {
81
+ regularTicks = placeGrid({
82
+ min: minX,
83
+ max: maxX,
84
+ totalSize: elementWidth,
85
+ precision,
86
+ dates,
87
+ formatter: formatXAxisLabel || formatX,
88
+ expectedLabelSize: expectedLabelWidth,
89
+ labelPadding,
90
+ formatOptions
91
+ });
92
+ }
65
93
 
66
- const xAxisHeight = 20;
94
+ const xAxisHeight = dates ? 30 : 20;
67
95
 
68
96
  return (
69
- <svg className="axis x-axis" style={showAxes ? undefined : {marginBottom: -20}}>
97
+ <svg className={`axis x-axis${dates ? ' x-axis-dual' : ''}`} style={showAxes ? undefined : {marginBottom: -20}}>
70
98
  {
71
99
  showAxes &&
72
100
  <path d={`M-1,0 H${elementWidth}`} className="axis-line" />
73
101
  }
102
+
103
+ {/* Render time ticks in first row */}
74
104
  {
75
- showAxes &&
76
- <path d={`M-2,1 H${elementWidth + 1}`} className="axis-line-shadow" />
105
+ dates && timeTicks && timeTicks.map(({ pixelValue, label, size, position, skipGrid }, i) => {
106
+
107
+ const singleTick = timeTicks.length === 1;
108
+
109
+ if (isNaN(pixelValue)) {
110
+ return null;
111
+ }
112
+
113
+ const classes = ['axis-item', `axis-item-${size}`, `axis-item-${position}`];
114
+ if (bigLabels) {
115
+ classes.push('axis-item-big-labels');
116
+ }
117
+
118
+ return (
119
+ <g key={`time-${i}`} className={classes.join(' ')}>
120
+ {
121
+ showAxes &&
122
+ <path d={`M${pixelValue},1 v12`} className="axis-tick" />
123
+ }
124
+
125
+ {
126
+ showGrid && !skipGrid &&
127
+ <path d={`M${pixelValue},0 v-${elementHeight}`} />
128
+ }
129
+
130
+ {
131
+ showAxes &&
132
+ <text
133
+ x={(position === 'last' && !singleTick) ? pixelValue - 3 : pixelValue + 3}
134
+ y={12}
135
+ textAnchor={(position === 'last' && !singleTick) ? 'end' : 'start'}
136
+ className='x-axis-text x-axis-time-text'
137
+ >
138
+ {label}
139
+ </text>
140
+ }
141
+ </g>
142
+ );
143
+ })
77
144
  }
145
+
146
+ {/* Render date ticks in second row */}
147
+ {
148
+ dates && dateTicks && dateTicks.map(({ pixelValue, label, size, position, trueValue }, i) => {
149
+
150
+ if (isNaN(pixelValue)) {
151
+ return null;
152
+ }
153
+
154
+ const classes = ['axis-item', `axis-item-${size}`, `axis-item-${position}`];
155
+ if (bigLabels) {
156
+ classes.push('axis-item-big-labels');
157
+ }
158
+
159
+ let timezoneLabel = undefined;
160
+ if (timeZone) {
161
+ if (i === 0) {
162
+ timezoneLabel = timeZone.toLowerCase() === 'utc' ? 'UTC' : timeZone;
163
+ }
164
+ }
78
165
 
166
+ return (
167
+ <g key={`date-${i}`} className={classes.join(' ')}>
168
+ {
169
+ showAxes &&
170
+ <text
171
+ x={position === 'last' ? pixelValue - 3 : pixelValue + 3}
172
+ y={25}
173
+ textAnchor={position === 'last' ? 'end' : 'start'}
174
+ className='x-axis-text x-axis-date-text'
175
+ >
176
+ <tspan className='x-axis-date-label'>
177
+ {label}
178
+ </tspan>
179
+ {timezoneLabel && (
180
+ <tspan className='x-axis-timezone-label'>
181
+ {' '}({timezoneLabel})
182
+ </tspan>
183
+ )}
184
+ </text>
185
+ }
186
+ </g>
187
+ );
188
+ })
189
+ }
190
+
191
+ {/* Render regular ticks for non-date data */}
79
192
  {
80
- ticks.map(({ pixelValue, label, size, position, skipGrid }, i) => {
193
+ !dates && regularTicks && regularTicks.map(({ pixelValue, label, size, position, skipGrid }, i) => {
81
194
  if (isNaN(pixelValue)) {
82
195
  return null;
83
196
  }
@@ -91,7 +204,7 @@ function XAxis({ showAxes, showGrid, stateController, bigLabels, xTickUnit, cloc
91
204
  <g key={i} className={classes.join(' ')}>
92
205
  {
93
206
  showAxes &&
94
- <path d={`M${pixelValue},1 v6`} className="axis-tick" />
207
+ <path d={`M${pixelValue},1 v12`} className="axis-tick" />
95
208
  }
96
209
 
97
210
  {
@@ -101,7 +214,12 @@ function XAxis({ showAxes, showGrid, stateController, bigLabels, xTickUnit, cloc
101
214
 
102
215
  {
103
216
  showAxes &&
104
- <text x={pixelValue} y={xAxisHeight - 5}>
217
+ <text
218
+ x={position === 'last' ? pixelValue - 3 : pixelValue + 3}
219
+ y={xAxisHeight - 5}
220
+ textAnchor={position === 'last' ? 'end' : 'start'}
221
+ className='x-axis-text'
222
+ >
105
223
  {label}
106
224
  </text>
107
225
  }
@@ -135,11 +135,6 @@ function YAxis({ stateController, showAxes, showGrid, showSeriesKey, axis, sideI
135
135
  <path d={`M${side === 'left' ? Y_AXIS_WIDTH-1 : 1},3 V${elementHeight}`} className="axis-line" />
136
136
  }
137
137
 
138
- {
139
- showAxes &&
140
- <path d={`M${side === 'left' ? Y_AXIS_WIDTH-2 : 0},3 V${elementHeight + 1}`} className="axis-line-shadow" />
141
- }
142
-
143
138
  {
144
139
  ticks.map(({ pixelValue, label, size, skipGrid }, i) => {
145
140
  const edge = side === 'left' ? (sideIndex + 1) * Y_AXIS_WIDTH : -sideIndex*Y_AXIS_WIDTH;
package/src/grapher.jsx CHANGED
@@ -156,11 +156,12 @@ function Grapher(props) {
156
156
  const showingSidebar = useShowingSidebar(stateController);
157
157
 
158
158
  const showAxisColors = typeof props.showAxisColors === 'boolean' ? props.showAxisColors : (theme !== 'export');
159
- const showGrid = typeof props.showGrid === 'boolean' ? props.showGrid : (theme !== 'export');
159
+ const showGrid = typeof props.showGrid === 'boolean' ? props.showGrid : true;
160
+ const showAxes = typeof props.showAxes === 'boolean' ? props.showAxes : false;
160
161
 
161
162
  const commonYAxisProps = {
162
163
  stateController,
163
- showAxes: props.showAxes,
164
+ showAxes,
164
165
  showGrid,
165
166
  showSeriesKey: props.showSeriesKey,
166
167
  bodyHeight: props.bodyHeight,
@@ -251,7 +252,7 @@ function Grapher(props) {
251
252
 
252
253
  <XAxis
253
254
  showGrid={showGrid}
254
- showAxes={props.showAxes}
255
+ showAxes={showAxes}
255
256
  stateController={stateController}
256
257
  bigLabels={bigLabels}
257
258
  xTickUnit={props.xTickUnit}