Skip to content

Commit c29ad80

Browse files
authored
Implementing center node on click functionality (#381)
1 parent 8d398df commit c29ad80

File tree

3 files changed

+76
-10
lines changed

3 files changed

+76
-10
lines changed

src/Node/index.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ type NodeProps = {
2929
onNodeMouseOver: NodeEventHandler;
3030
onNodeMouseOut: NodeEventHandler;
3131
subscriptions: object;
32+
centerNode: (hierarchyPointNode: HierarchyPointNode<TreeNodeDatum>) => void;
3233
};
3334

3435
type NodeState = {
3536
transform: string;
3637
initialStyle: { opacity: number };
38+
wasClicked: boolean;
3739
};
3840

3941
export default class Node extends React.Component<NodeProps, NodeState> {
@@ -49,13 +51,18 @@ export default class Node extends React.Component<NodeProps, NodeState> {
4951
initialStyle: {
5052
opacity: 0,
5153
},
54+
wasClicked: false,
5255
};
5356

5457
componentDidMount() {
5558
this.commitTransform();
5659
}
5760

5861
componentDidUpdate() {
62+
if (this.state.wasClicked) {
63+
this.props.centerNode(this.props.hierarchyPointNode);
64+
this.setState({ wasClicked: false });
65+
}
5966
this.commitTransform();
6067
}
6168

@@ -119,22 +126,27 @@ export default class Node extends React.Component<NodeProps, NodeState> {
119126
// TODO: needs tests
120127
renderNodeElement = () => {
121128
const { data, hierarchyPointNode, renderCustomNodeElement } = this.props;
122-
const renderNode = typeof renderCustomNodeElement === 'function' ? renderCustomNodeElement : DefaultNodeElement;
129+
const renderNode =
130+
typeof renderCustomNodeElement === 'function' ? renderCustomNodeElement : DefaultNodeElement;
123131
const nodeProps = {
124-
hierarchyPointNode: hierarchyPointNode,
125-
nodeDatum: data,
126-
toggleNode: this.handleNodeToggle,
127-
onNodeClick: this.handleOnClick,
128-
onNodeMouseOver: this.handleOnMouseOver,
129-
onNodeMouseOut: this.handleOnMouseOut,
132+
hierarchyPointNode: hierarchyPointNode,
133+
nodeDatum: data,
134+
toggleNode: this.handleNodeToggle,
135+
onNodeClick: this.handleOnClick,
136+
onNodeMouseOver: this.handleOnMouseOver,
137+
onNodeMouseOut: this.handleOnMouseOut,
130138
};
131139

132-
return renderNode(nodeProps)
140+
return renderNode(nodeProps);
133141
};
134142

135-
handleNodeToggle = () => this.props.onNodeToggle(this.props.data.__rd3t.id);
143+
handleNodeToggle = () => {
144+
this.setState({ wasClicked: true });
145+
this.props.onNodeToggle(this.props.data.__rd3t.id);
146+
};
136147

137148
handleOnClick = evt => {
149+
this.setState({ wasClicked: true });
138150
this.props.onNodeClick(this.props.hierarchyPointNode, evt);
139151
};
140152

src/Tree/index.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
5151
renderCustomNodeElement: undefined,
5252
enableLegacyTransitions: false,
5353
hasInteractiveNodes: false,
54+
dimensions: undefined,
5455
};
5556

5657
state: TreeState = {
@@ -151,7 +152,12 @@ class Tree extends React.Component<TreeProps, TreeState> {
151152
.scaleExtent(zoomable ? [scaleExtent.min, scaleExtent.max] : [zoom, zoom])
152153
// TODO: break this out into a separate zoom handler fn, rather than inlining it.
153154
.filter(() => {
154-
if (hasInteractiveNodes) return event.target.classList.contains(this.svgInstanceRef) || event.target.classList.contains(this.gInstanceRef) || event.shiftKey;
155+
if (hasInteractiveNodes)
156+
return (
157+
event.target.classList.contains(this.svgInstanceRef) ||
158+
event.target.classList.contains(this.gInstanceRef) ||
159+
event.shiftKey
160+
);
155161
return true;
156162
})
157163
.on('zoom', () => {
@@ -377,6 +383,43 @@ class Tree extends React.Component<TreeProps, TreeState> {
377383
}
378384
};
379385

386+
/**
387+
* Takes a hierarchy point node and centers the node on the screen
388+
* if the dimensions parameter is passed to the tree.
389+
*
390+
* This code is adapted from Rob Schmuecker's centerNode method.
391+
* Link: http://www.robschmuecker.com/d3-js-drag-and-drop-zoomable-tree/
392+
*
393+
* @param hierarchyPointNode
394+
*/
395+
centerNode = (hierarchyPointNode: HierarchyPointNode<TreeNodeDatum>) => {
396+
// if the dimensions are given
397+
if (this.props.dimensions) {
398+
const g = select(`.${this.gInstanceRef}`);
399+
const svg = select(`.${this.svgInstanceRef}`);
400+
const scale = this.state.d3.scale;
401+
402+
let x, y;
403+
// if the orientation is horizontal, calculate the variables inverted (x->y, y->x)
404+
if (this.props.orientation === 'horizontal') {
405+
y = -hierarchyPointNode.x * scale + this.props.dimensions.width / 2;
406+
x = -hierarchyPointNode.y * scale + this.props.dimensions.height / 2;
407+
} else {
408+
// else, calculate the variables normally (x->x, y->y)
409+
x = -hierarchyPointNode.x * scale + this.props.dimensions.width / 2;
410+
y = -hierarchyPointNode.y * scale + this.props.dimensions.height / 2;
411+
}
412+
//@ts-ignore
413+
g.transition()
414+
.duration(800)
415+
.attr('transform', 'translate(' + x + ',' + y + ')scale(' + scale + ')');
416+
// Sets the viewport to the new center so that it does not jump back to original
417+
// coordinates when dragged/zoomed
418+
//@ts-ignore
419+
svg.call(d3zoom().transform, zoomIdentity.translate(x, y).scale(this.props.zoom));
420+
}
421+
};
422+
380423
/**
381424
* Generates tree elements (`nodes` and `links`) by
382425
* grabbing the rootNode from `this.state.data[0]`.
@@ -525,6 +568,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
525568
onNodeMouseOver={this.handleOnNodeMouseOverCb}
526569
onNodeMouseOut={this.handleOnNodeMouseOutCb}
527570
subscriptions={subscriptions}
571+
centerNode={this.centerNode}
528572
/>
529573
);
530574
})}

src/Tree/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,14 @@ export interface TreeProps {
285285
* {@link Tree.defaultProps.hasInteractiveNodes | Default value}
286286
*/
287287
hasInteractiveNodes?: boolean;
288+
289+
/**
290+
* Gives the user the ability to pass dimensions of the tree container to center a node on click
291+
*
292+
* If dimensions are given: node will center on click. If not, node will not center on click.
293+
*/
294+
dimensions?: {
295+
height: number;
296+
width: number;
297+
};
288298
}

0 commit comments

Comments
 (0)