Skip to content

Commit 58a1ee2

Browse files
author
Chris Thomas
committed
Create ZoomHandler
Due to the way that the Highlight component handles the mouse events, it is pretty much required that it is one of the last children of the XYPlot. Since it renders a transparent `rect` over the entire `XYPlot` it currently disables any mouse events for controls lower in the dom tree. If the `Highlight` component is moved higher in the dom tree, then the `onMouseLeave` event will fire when a lower component is hovered over. The ZoomHandler delegates the handling of the mouse events to the XYPlot itself. The XYPlot now holds a collection of `Event` objects that will be passed to the children, where they can subscribe to the ones that they are interested in. When the appropriate Event in the XYPlot is fired, it will execute the callbacks for all listeners. This approach does not cause the `XYPlot` to re-render, and only the listeners that perform an operation in their callback will be re-rendered. Also created a `useStateWithGet` that wraps a `useState` call and also provides a memoized `getState` method. Since the `getState` method is only created once for the component, it will not trigger re-renders when listed in the dependencies of `useEffect` / `useCallback`. This drastically cuts down the number of event handlers that are subscribed / unsubscribed.
1 parent 8e56d05 commit 58a1ee2

File tree

9 files changed

+265
-125
lines changed

9 files changed

+265
-125
lines changed

.eslintrc

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"extends": [
33
"eslint:recommended",
44
"plugin:react/recommended",
5+
"plugin:react-hooks/recommended",
56
"plugin:jest/recommended",
67
"prettier",
78
"prettier/react"
@@ -19,8 +20,8 @@
1920
"**/dist/",
2021
"**/es/"
2122
],
22-
"settings":{
23-
"react":{
23+
"settings": {
24+
"react": {
2425
"version": "detect"
2526
}
2627
},
@@ -31,12 +32,19 @@
3132
},
3233
"rules": {
3334
"consistent-return": 0,
34-
"max-len": [1, 110, 4],
35-
"max-params": ["error", 6],
35+
"max-len": [
36+
1,
37+
110,
38+
4
39+
],
40+
"max-params": [
41+
"error",
42+
6
43+
],
3644
"object-curly-spacing": 0,
3745
"babel/object-curly-spacing": 2,
38-
"jest/require-top-level-describe":"error",
46+
"jest/require-top-level-describe": "error",
3947
"react/prop-types": "off",
4048
"prettier/prettier": "warn"
4149
}
42-
}
50+
}

packages/react-vis/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"enzyme-adapter-react-16": "^1.15.2",
7373
"enzyme-to-json": "^3.5.0",
7474
"eslint-plugin-jest": "^23.13.2",
75+
"eslint-plugin-react-hooks": "^4.0.4",
7576
"jest": "^25.5.4",
7677
"jsdom": "^9.9.1",
7778
"node-sass": "^4.9.3",

packages/react-vis/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export Treemap from 'treemap';
7474

7575
export ContentClipPath from './plot/content-clip-path';
7676

77+
export ZoomHandler from './plot/zoom-handler';
78+
7779
export {
7880
makeHeightFlexible,
7981
makeVisFlexible,

packages/react-vis/src/plot/highlight.js

Lines changed: 23 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ import {getAttributeScale} from 'utils/scales-utils';
66
import {getCombinedClassName} from 'utils/styling-utils';
77

88
function getLocs(evt) {
9-
// const xLoc = evt.type === 'touchstart' ? evt.pageX : evt.offsetX;
10-
// const yLoc = evt.type === 'touchstart' ? evt.pageY : evt.offsetY;
11-
const xLoc = evt.offsetX ?? evt.pageX;
12-
const yLoc = evt.offsetY ?? evt.pageY;
9+
const xLoc = evt.type === 'touchstart' ? evt.pageX : evt.offsetX;
10+
const yLoc = evt.type === 'touchstart' ? evt.pageY : evt.offsetY;
1311
return {xLoc, yLoc};
1412
}
1513

@@ -22,7 +20,7 @@ class Highlight extends AbstractSeries {
2220
startLocY: 0,
2321
dragArea: null
2422
};
25-
ref = React.createRef();
23+
2624
_getDrawArea(xLoc, yLoc) {
2725
const {startLocX, startLocY} = this.state;
2826
const {
@@ -118,69 +116,10 @@ class Highlight extends AbstractSeries {
118116
return {};
119117
}
120118

121-
_captureMouse = e => {
122-
console.log('capture', e.type, e.target, e);
123-
124-
document.addEventListener('mouseup', this.stopBrushing, {capture: true});
125-
document.addEventListener('mousemove', this.onBrush, {capture: true});
126-
// document.body.style['pointer-events'] = 'none';
127-
128-
if (this.ref.current) {
129-
this.ref.current.addEventListener('mouseleave', this._mouseLeave, {
130-
capture: true
131-
});
132-
}
133-
134-
e.preventDefault();
135-
e.stopPropagation();
136-
// this.startBrushing(e);
137-
};
138-
139-
_releaseMouse = e => {
140-
console.log('release', e.type, e.target, e);
141-
142-
document.removeEventListener('mouseup', this.stopBrushing, {capture: true});
143-
document.removeEventListener('mousemove', this.onBrush, {capture: true});
144-
145-
if (this.ref.current) {
146-
this.ref.current.removeEventListener('mouseleave', this._mouseLeave, {
147-
capture: true
148-
});
149-
}
150-
// document.body.style['pointer-events'] = 'auto';
151-
e.stopPropagation();
152-
};
153-
154-
_mouseLeave = e => {
155-
const {toElement} = e;
156-
if (toElement === document.documentElement) {
157-
this.stopBrushing(e);
158-
return;
159-
}
160-
if (toElement === this.ref.current.parentNode.parentNode) {
161-
this.stopBrushing(e);
162-
return;
163-
}
164-
console.log('didnt really leave', toElement, this.ref.current.parentNode);
165-
// this.ref.current.
166-
};
167-
168-
startBrushing = e => {
169-
// e.preventDefault();
170-
this._captureMouse(e);
119+
startBrushing(e) {
171120
const {onBrushStart, onDragStart, drag} = this.props;
172121
const {dragArea} = this.state;
173-
const {xLoc, yLoc} = getLocs(e);
174-
console.log(
175-
'start',
176-
xLoc,
177-
yLoc,
178-
e.type,
179-
e.offsetX,
180-
e.offsetY,
181-
e.pageX,
182-
e.pageY
183-
);
122+
const {xLoc, yLoc} = getLocs(e.nativeEvent);
184123

185124
const startArea = (dragging, resetDrag) => {
186125
const emptyBrush = {
@@ -214,23 +153,9 @@ class Highlight extends AbstractSeries {
214153
onDragStart(e);
215154
}
216155
}
217-
};
156+
}
218157

219-
stopBrushing = e => {
220-
if (e.toElement === document.documentElement) {
221-
console.log('is document');
222-
// return;
223-
}
224-
console.log(
225-
'stop',
226-
e.type,
227-
e.target,
228-
e.currentTarget,
229-
e.toElement,
230-
document,
231-
e
232-
);
233-
this._releaseMouse(e);
158+
stopBrushing() {
234159
const {brushing, dragging, brushArea} = this.state;
235160
// Quickly short-circuit if the user isn't brushing in our component
236161
if (!brushing && !dragging) {
@@ -258,18 +183,14 @@ class Highlight extends AbstractSeries {
258183
if (drag && onDragEnd) {
259184
onDragEnd(!isNulled ? this._convertAreaToCoordinates(brushArea) : null);
260185
}
261-
};
186+
}
262187

263-
onBrush = e => {
264-
e.preventDefault();
265-
e.stopPropagation();
188+
onBrush(e) {
266189
const {onBrush, onDrag, drag} = this.props;
267190
const {brushing, dragging} = this.state;
268-
const {xLoc, yLoc} = getLocs(e);
269-
// console.log('brush', xLoc, yLoc);
191+
const {xLoc, yLoc} = getLocs(e.nativeEvent);
270192
if (brushing) {
271193
const brushArea = this._getDrawArea(xLoc, yLoc);
272-
// console.log('brush area', brushArea);
273194
this.setState({brushArea});
274195

275196
if (onBrush) {
@@ -284,7 +205,7 @@ class Highlight extends AbstractSeries {
284205
onDrag(this._convertAreaToCoordinates(brushArea));
285206
}
286207
}
287-
};
208+
}
288209

289210
render() {
290211
const {
@@ -329,36 +250,26 @@ class Highlight extends AbstractSeries {
329250
className={getCombinedClassName(className, 'rv-highlight-container')}
330251
>
331252
<rect
332-
ref={this.ref}
333253
className="rv-mouse-target"
334254
fill="black"
335255
opacity="0"
336256
x="0"
337257
y="0"
338258
width={Math.max(touchWidth, 0)}
339259
height={Math.max(touchHeight, 0)}
340-
onMouseDownCapture={e => this.startBrushing(e.nativeEvent)}
341-
// onMouseMoveCapture={e => this.onBrush(e)}
342-
// onMouseUpCapture={e => this.stopBrushing(e)}
343-
// onMouseLeave={e => {
344-
// console.log(
345-
// 'mouse leave',
346-
// e.target,
347-
// e.currentTarget,
348-
// getLocs(e.nativeEvent)
349-
// );
350-
// // this._releaseMouse(e);
351-
// // this.stopBrushing(e);
352-
// }}
260+
onMouseDown={e => this.startBrushing(e)}
261+
onMouseMove={e => this.onBrush(e)}
262+
onMouseUp={e => this.stopBrushing(e)}
263+
onMouseLeave={e => this.stopBrushing(e)}
353264
// preventDefault() so that mouse event emulation does not happen
354-
// onTouchEnd={e => {
355-
// e.preventDefault();
356-
// this.stopBrushing(e);
357-
// }}
358-
// onTouchCancel={e => {
359-
// e.preventDefault();
360-
// this.stopBrushing(e);
361-
// }}
265+
onTouchEnd={e => {
266+
e.preventDefault();
267+
this.stopBrushing(e);
268+
}}
269+
onTouchCancel={e => {
270+
e.preventDefault();
271+
this.stopBrushing(e);
272+
}}
362273
onContextMenu={e => e.preventDefault()}
363274
onContextMenuCapture={e => e.preventDefault()}
364275
/>

packages/react-vis/src/plot/xy-plot.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import {
5050

5151
import CanvasWrapper from './series/canvas-wrapper';
5252

53+
import {Event} from '../utils/events';
54+
5355
const ATTRIBUTES = [
5456
'x',
5557
'y',
@@ -140,7 +142,14 @@ class XYPlot extends React.Component {
140142
const data = getStackedData(children, stackBy);
141143
this.state = {
142144
scaleMixins: this._getScaleMixins(data, props),
143-
data
145+
data,
146+
events: {
147+
mouseMove: new Event('move'),
148+
mouseDown: new Event('down'),
149+
mouseUp: new Event('up'),
150+
mouseLeave: new Event('leave'),
151+
mouseEnter: new Event('enter')
152+
}
144153
};
145154
}
146155

@@ -222,7 +231,8 @@ class XYPlot extends React.Component {
222231
...scaleMixins,
223232
...child.props,
224233
...XYPlotValues[index],
225-
...dataProps
234+
...dataProps,
235+
events: this.state.events
226236
});
227237
});
228238
}
@@ -348,6 +358,7 @@ class XYPlot extends React.Component {
348358
component.onParentMouseDown(event);
349359
}
350360
});
361+
this.state.events.mouseDown.fire(event);
351362
};
352363

353364
/**
@@ -367,6 +378,7 @@ class XYPlot extends React.Component {
367378
component.onParentMouseEnter(event);
368379
}
369380
});
381+
this.state.events.mouseEnter.fire(event);
370382
};
371383

372384
/**
@@ -386,6 +398,7 @@ class XYPlot extends React.Component {
386398
component.onParentMouseLeave(event);
387399
}
388400
});
401+
this.state.events.mouseLeave.fire(event);
389402
};
390403

391404
/**
@@ -405,6 +418,7 @@ class XYPlot extends React.Component {
405418
component.onParentMouseMove(event);
406419
}
407420
});
421+
this.state.events.mouseMove.fire(event);
408422
};
409423

410424
/**
@@ -424,6 +438,7 @@ class XYPlot extends React.Component {
424438
component.onParentMouseUp(event);
425439
}
426440
});
441+
this.state.events.mouseUp.fire(event);
427442
};
428443

429444
/**

0 commit comments

Comments
 (0)