Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Focus view on a node #107

Merged
merged 30 commits into from
Nov 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bc8cefc
feat: Focus graph on a node
LonelyPrincess Sep 7, 2018
bbcb9c1
feat: Node focus feature example added to sandbox
LonelyPrincess Sep 7, 2018
a1d9268
fix: Ensure that focusZoom is between minZoom and maxZoom
LonelyPrincess Sep 10, 2018
1fdda0d
Merge branch 'master' into feat/focus-on-node
LonelyPrincess Sep 22, 2018
2e9670f
fix: Avoid issue with id datatypes when searching for focused node
LonelyPrincess Sep 22, 2018
bcb1d98
refactor: Improve readability for focusZoom bounds
LonelyPrincess Sep 24, 2018
5103219
refactor: Remove statements in library code
LonelyPrincess Sep 24, 2018
5f6fcd2
revert: Removed blank lines in DOCUMENTATION.md
LonelyPrincess Sep 30, 2018
baf23b5
docs: Added more information on focusZoom property
LonelyPrincess Sep 30, 2018
5b44b36
feat: Customize focus animation length
LonelyPrincess Sep 30, 2018
7be4448
refactor: focusNodeId wrapped in data props
LonelyPrincess Sep 30, 2018
f7d9e69
refactor: focus transformation function moved to graphHelper
LonelyPrincess Sep 30, 2018
b70f016
Merge branch 'master' into feat/focus-on-node
LonelyPrincess Sep 30, 2018
eab8c6f
docs: Updated jsdoc for getCenterAndZoomTransformation
LonelyPrincess Sep 30, 2018
5614c5d
refactor: Renamed config param for focus animation duration
LonelyPrincess Sep 30, 2018
11026db
test: Graph snapshot updated
LonelyPrincess Sep 30, 2018
d596544
Merge branch 'master' into feat/focus-on-node
LonelyPrincess Oct 6, 2018
4cc8ef6
Merge branch 'master' into feat/focus-on-node
LonelyPrincess Oct 16, 2018
918b5cb
fix: Consider hidden links when obtaining matrix
LonelyPrincess Oct 16, 2018
963991b
Merge branch 'master' into feat/focus-on-node
LonelyPrincess Oct 17, 2018
948596c
fix: Reset transition duration after focus animation
LonelyPrincess Oct 22, 2018
701ea49
test: Graph snapshot updated with default transition duration
LonelyPrincess Oct 22, 2018
391b1e0
fix: Drag & drop in focused node
LonelyPrincess Oct 29, 2018
b76d853
refactor: Reset transition duration on drag start
LonelyPrincess Oct 29, 2018
63c92b1
style: Added blank line after variable declaration
LonelyPrincess Oct 29, 2018
aac9946
chore: Browser environment added to ESLint config
LonelyPrincess Oct 29, 2018
c3ed06d
refactor: getCenterAndZoomTransformation receives d3Node object
LonelyPrincess Oct 30, 2018
de9da5f
Merge branch 'master' into feat/focus-on-node
LonelyPrincess Oct 30, 2018
62f6754
fix: Node not centered when focusing with zoom
LonelyPrincess Nov 13, 2018
3e80a5e
refactor: Store focusedNodeId in Graph component state
LonelyPrincess Nov 13, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ module.exports = {
}
},
plugins: ['standard', 'promise', 'react', 'jest', 'cypress'],
env: {
browser: true
},
rules: {
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
Expand Down
2 changes: 2 additions & 0 deletions docs/DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -877,9 +877,11 @@ components.
},
...
}

LonelyPrincess marked this conversation as resolved.
Show resolved Hide resolved
```

```

* `linkCallbacks` **[Array][87]<[Function][83]>** array of callbacks for used defined event handler for link interactions.
* `config` **[Object][74]** an object containing rd3g consumer defined configurations [config][92] for the graph.
* `highlightedNode` **[string][78]** this value contains a string that represents the some currently highlighted node.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"start": "http-server ./sandbox/ -p 8888 -c-1",
"test:clean": "jest --no-cache --updateSnapshot --verbose --coverage --config jest.config.js",
"test:watch": "jest --verbose --coverage --watchAll --config jest.config.js",
"test": "jest --verbose --coverage --config jest.config.js"
"test": "jest --verbose --coverage --config jest.config.js",
"sandbox": "npm run dist:sandbox && npm run start"
},
"lint-staged": {
"*.{js,jsx,json,css,md}": [
Expand Down
13 changes: 11 additions & 2 deletions sandbox/Sandbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,15 @@ export default class Sandbox extends React.Component {

onClickGraph = () => console.info(`Clicked the graph`);

onClickNode = id => !this.state.config.collapsible && window.alert(`Clicked node ${id}`);
onClickNode = id => {
!this.state.config.collapsible && window.alert(`Clicked node ${id}`);
this.setState({
data: {
...this.state.data,
focusedNodeId: this.state.data.focusedNodeId !== id ? id : null
}
});
};

onRightClickNode = (event, id) => {
event.preventDefault();
Expand Down Expand Up @@ -299,7 +307,8 @@ export default class Sandbox extends React.Component {
// to true in the constructor we will provide nodes with initial positions
const data = {
nodes: this.decorateGraphNodesWithInitialPositioning(this.state.data.nodes),
links: this.state.data.links
links: this.state.data.links,
focusedNodeId: this.state.data.focusedNodeId
};

const graphProps = {
Expand Down
55 changes: 52 additions & 3 deletions src/components/graph/Graph.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,12 @@ export default class Graph extends React.Component {
* Handles d3 drag 'start' event.
* @returns {undefined}
*/
_onDragStart = () => this.pauseSimulation();
_onDragStart = () => {
this.pauseSimulation();
if (this.state.enableFocusAnimation) {
this.setState({ enableFocusAnimation: false });
}
};

/**
* Sets nodes and links highlighted value.
Expand Down Expand Up @@ -309,6 +314,7 @@ export default class Graph extends React.Component {
utils.throwErr(this.constructor.name, ERRORS.GRAPH_NO_ID_PROP);
}

this.focusAnimationTimeout = null;
this.state = graphHelper.initializeGraphState(this.props, this.state);
}

Expand Down Expand Up @@ -337,13 +343,21 @@ export default class Graph extends React.Component {

const transform = newConfig.panAndZoom !== this.state.config.panAndZoom ? 1 : this.state.transform;

const focusedNodeId = nextProps.data.focusedNodeId;
const d3FocusedNode = this.state.d3Nodes.find(node => `${node.id}` === `${focusedNodeId}`);
const focusTransformation = graphHelper.getCenterAndZoomTransformation(d3FocusedNode, this.state.config);
const enableFocusAnimation = this.props.data.focusedNodeId !== nextProps.data.focusedNodeId;

this.setState({
...state,
config,
configUpdated,
d3ConfigUpdated,
newGraphElements,
transform
transform,
focusedNodeId,
enableFocusAnimation,
focusTransformation
});
}

Expand Down Expand Up @@ -413,6 +427,10 @@ export default class Graph extends React.Component {
* @returns {undefined}
*/
onClickGraph = e => {
if (this.state.enableFocusAnimation) {
this.setState({ enableFocusAnimation: false });
}

// Only trigger the graph onClickHandler, if not clicked a node or link.
// toUpperCase() is added as a precaution, as the documentation says tagName should always
// return in UPPERCASE, but chrome returns lowercase
Expand All @@ -424,6 +442,35 @@ export default class Graph extends React.Component {
}
};

/**
* Obtain a set of properties which will be used to perform the focus and zoom animation if
* required. In case there's not a focus and zoom animation in progress, it should reset the
* transition duration to zero and clear transformation styles.
* @returns {Object} - Focus and zoom animation properties.
*/
_generateFocusAnimationProps = () => {
const { focusedNodeId } = this.state;

// In case an older animation was still not complete, clear previous timeout to ensure the new one is not cancelled
if (this.state.enableFocusAnimation) {
if (this.focusAnimationTimeout) {
clearTimeout(this.focusAnimationTimeout);
}

this.focusAnimationTimeout = setTimeout(
() => this.setState({ enableFocusAnimation: false }),
this.state.config.focusAnimationDuration * 1000
);
}

const transitionDuration = this.state.enableFocusAnimation ? this.state.config.focusAnimationDuration : 0;

return {
style: { transitionDuration: `${transitionDuration}s` },
transform: focusedNodeId ? this.state.focusTransformation : null
};
};

render() {
const { nodes, links, defs } = graphRenderer.buildGraph(
this.state.nodes,
Expand Down Expand Up @@ -452,11 +499,13 @@ export default class Graph extends React.Component {
width: this.state.config.width
};

const containerProps = this._generateFocusAnimationProps();

return (
<div id={`${this.state.id}-${CONST.GRAPH_WRAPPER_ID}`}>
<svg name={`svg-container-${this.state.id}`} style={svgStyle} onClick={this.onClickGraph}>
{defs}
<g id={`${this.state.id}-${CONST.GRAPH_CONTAINER_ID}`}>
<g id={`${this.state.id}-${CONST.GRAPH_CONTAINER_ID}`} {...containerProps}>
{links}
{nodes}
</g>
Expand Down
5 changes: 5 additions & 0 deletions src/components/graph/graph.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
* the value the more the less highlighted nodes will be visible (related to *nodeHighlightBehavior*).
* @param {number} [maxZoom=8] - max zoom that can be performed against the graph.
* @param {number} [minZoom=0.1] - min zoom that can be performed against the graph.
* @param {number} [focusZoom=1] - zoom that will be applied when the graph view is focused in a node. Its value must be between
* *minZoom* and *maxZoom*. If the specified *focusZoom* is out of this range, *minZoom* or *maxZoom* will be applied instead.
* @param {number} [focusAnimationDuration=0.75] - duration (in seconds) for the animation that takes place when focusing the graph on a node.
* @param {boolean} [panAndZoom=false] - 🚅🚅🚅 pan and zoom effect when performing zoom in the graph,
* a similar functionality may be consulted {@link https://bl.ocks.org/mbostock/2a39a768b1d4bc00a09650edef75ad39|here}.
* @param {boolean} [staticGraph=false] - when setting this value to true the graph will be completely static, thus
Expand Down Expand Up @@ -182,6 +185,8 @@ export default {
linkHighlightBehavior: false,
maxZoom: 8,
minZoom: 0.1,
focusZoom: 1,
focusAnimationDuration: 0.75,
nodeHighlightBehavior: false,
panAndZoom: false,
staticGraph: false,
Expand Down
35 changes: 33 additions & 2 deletions src/components/graph/graph.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function _initializeLinks(graphLinks, config) {
links[target] = {};
}

const value = l.value || 1;
const value = config.collapsible && l.isHidden ? 0 : l.value || 1;
LonelyPrincess marked this conversation as resolved.
Show resolved Hide resolved

links[source][target] = value;

Expand Down Expand Up @@ -475,6 +475,14 @@ function initializeGraphState({ data, id, config }, state) {
const formatedId = id.replace(/ /g, '_');
const simulation = _createForceSimulation(newConfig.width, newConfig.height, newConfig.d3 && newConfig.d3.gravity);

const { minZoom, maxZoom, focusZoom } = newConfig;

if (focusZoom > maxZoom) {
newConfig.focusZoom = maxZoom;
} else if (focusZoom < minZoom) {
newConfig.focusZoom = minZoom;
}

return {
id: formatedId,
config: newConfig,
Expand Down Expand Up @@ -521,11 +529,34 @@ function updateNodeHighlightedValue(nodes, links, config, id, value = false) {
};
}

/**
* Returns the transformation to apply in order to center the graph on the
* selected node.
* @param {Object} d3Node - node to focus the graph view on.
* @param {Object} config - same as {@link #buildGraph|config in buildGraph}.
* @returns {string} transform rule to apply.
LonelyPrincess marked this conversation as resolved.
Show resolved Hide resolved
* @memberof Graph/helper
*/
function getCenterAndZoomTransformation(d3Node, config) {
if (!d3Node) {
return;
}

const { width, height, focusZoom } = config;

return `
translate(${width / 2}, ${height / 2})
scale(${focusZoom})
translate(${-d3Node.x}, ${-d3Node.y})
`;
}

export {
buildLinkProps,
buildNodeProps,
checkForGraphConfigChanges,
checkForGraphElementsChanges,
initializeGraphState,
updateNodeHighlightedValue
updateNodeHighlightedValue,
getCenterAndZoomTransformation
};
6 changes: 6 additions & 0 deletions test/snapshot/graph/__snapshots__/graph.snapshot.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = `
</defs>
<g
id="graphId-graph-container-zoomable"
style={
Object {
"transitionDuration": "0s",
}
}
transform={null}
>
<path
className="link"
Expand Down