react-d3-graph
Advanced tools
Comparing version 0.0.2 to 0.1.0
@@ -119,3 +119,3 @@ 'use strict'; | ||
color: '#d3d3d3', | ||
fontSize: 10, | ||
fontSize: 8, | ||
fontWeight: 'normal', | ||
@@ -126,3 +126,3 @@ labelProperty: 'id', | ||
renderLabel: true, | ||
size: 200, | ||
size: 80, | ||
strokeColor: 'none', | ||
@@ -132,3 +132,3 @@ strokeWidth: 1.5, | ||
highlightColor: 'SAME', | ||
highlightFontSize: 10, | ||
highlightFontSize: 8, | ||
highlightFontWeight: 'normal', | ||
@@ -135,0 +135,0 @@ highlightStrokeColor: 'SAME', |
@@ -239,12 +239,15 @@ 'use strict'; | ||
graphLinks.forEach(function (l) { | ||
if (!links[l.source]) { | ||
links[l.source] = {}; | ||
var source = l.source.id || l.source; | ||
var target = l.target.id || l.target; | ||
if (!links[source]) { | ||
links[source] = {}; | ||
} | ||
if (!links[l.target]) { | ||
links[l.target] = {}; | ||
if (!links[target]) { | ||
links[target] = {}; | ||
} | ||
// @todo: If the graph is directed this should be adapted | ||
links[l.source][l.target] = links[l.target][l.source] = l.value || 1; | ||
// @TODO: If the graph is directed this should be adapted | ||
links[source][target] = links[target][source] = l.value || 1; | ||
}); | ||
@@ -266,3 +269,3 @@ | ||
var nodes = {}; | ||
var indexMapping = {}; | ||
var nodeIndexMapping = {}; | ||
var index = 0; | ||
@@ -276,3 +279,3 @@ | ||
nodes[n.id.toString()] = n; | ||
indexMapping[index] = n.id; | ||
nodeIndexMapping[index] = n.id; | ||
@@ -284,3 +287,3 @@ index++; | ||
nodes: nodes, | ||
indexMapping: indexMapping | ||
nodeIndexMapping: nodeIndexMapping | ||
}; | ||
@@ -287,0 +290,0 @@ } |
@@ -7,2 +7,4 @@ 'use strict'; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
@@ -54,2 +56,4 @@ | ||
* @example | ||
* import { Graph } from 'react-d3-graph'; | ||
* | ||
* // Graph payload (with minimalist structure) | ||
@@ -110,36 +114,192 @@ * const data = { | ||
/** | ||
* This method resets all nodes fixed positions by deleting the properties fx (fixed x) | ||
* and fy (fixed y). Next a simulation is triggered in order to force nodes to go back | ||
* to their original positions (or at least new positions according to the d3 force parameters). | ||
* @return {undefined} | ||
*/ | ||
_createClass(Graph, [{ | ||
key: '_validateGraphData', | ||
/** | ||
* Handles mouse over node event. | ||
* @param {number} index - index of the mouse hovered node. | ||
* @return {undefined} | ||
*/ | ||
/** | ||
* Some integraty validations on links and nodes structure. | ||
* @param {Object} data | ||
*/ | ||
/** | ||
* Handler for 'zoom' event within zoom config. | ||
* @return {Object} returns the transformed elements within the svg graph area. | ||
*/ | ||
/** | ||
* This method resets all nodes fixed positions by deleting the properties fx (fixed x) | ||
* and fy (fixed y). Next a simulation is triggered in order to force nodes to go back | ||
* to their original positions (or at least new positions according to the d3 force parameters). | ||
*/ | ||
/** | ||
* The tick function simply calls React set state in order to update component and render nodes | ||
* along time as d3 calculates new node positioning. | ||
*/ | ||
/** | ||
* Handles mouse over node event. | ||
* @param {number} index - index of the mouse hovered node. | ||
* @return {undefined} | ||
*/ | ||
/** | ||
* Handles d3 drag 'start' event. | ||
*/ | ||
/** | ||
* Handler for 'zoom' event within zoom config. | ||
* @return {Object} returns the transformed elements within the svg graph area. | ||
*/ | ||
/** | ||
* Handles d3 drag 'end' event. | ||
*/ | ||
/** | ||
* The tick function simply calls React set state in order to update component and render nodes | ||
* along time as d3 calculates new node positioning. | ||
*/ | ||
/** | ||
* Handles d3 drag 'start' event. | ||
*/ | ||
/** | ||
* Handles d3 drag 'end' event. | ||
*/ | ||
value: function _validateGraphData(data) { | ||
var _this2 = this; | ||
data.links.forEach(function (l) { | ||
if (!data.nodes.find(function (n) { | ||
return n.id === l.source; | ||
})) { | ||
_utils2.default.throwErr(_this2.constructor.name, _err2.default.INVALID_LINKS + ' - ' + l.source + ' is not a valid node id'); | ||
} | ||
if (!data.nodes.find(function (n) { | ||
return n.id === l.target; | ||
})) { | ||
_utils2.default.throwErr(_this2.constructor.name, _err2.default.INVALID_LINKS + ' - ' + l.target + ' is not a valid node id'); | ||
} | ||
}); | ||
} | ||
/** | ||
* Incapsulates common procedures to initialize graph. | ||
* @param {Object} data | ||
* @param {Array.<Object>} data.nodes - nodes of the graph to be created. | ||
* @param {Array.<Object>} data.links - links that connect data.nodes. | ||
* @returns {Object} | ||
*/ | ||
/** | ||
* Calls d3 simulation.restart().<br/> | ||
* {@link https://github.com/d3/d3-force#simulation_restart} | ||
*/ | ||
/** | ||
* Calls d3 simulation.stop().<br/> | ||
* {@link https://github.com/d3/d3-force#simulation_stop} | ||
*/ | ||
/** | ||
* Handles mouse out node event. | ||
* @param {number} index - index of the mouse hovered node. | ||
* @return {undefined} | ||
*/ | ||
/** | ||
* Configures zoom upon graph with default or user provided values.<br/> | ||
* {@link https://github.com/d3/d3-zoom#zoom} | ||
* @return {undefined} | ||
*/ | ||
/** | ||
* Sets nodes and links highlighted value. | ||
* @param {number} index - the index of the node to highlight (and its adjacent). | ||
* @param {boolean} value - the highlight value to be set (true or false). | ||
* @return {undefined} | ||
*/ | ||
/** | ||
* Handles d3 'drag' event. | ||
* @param {Object} _ - event. | ||
* @param {number} index - index of the node that is being dragged. | ||
* @return {undefined} | ||
*/ | ||
}, { | ||
key: '_initializeGraphState', | ||
value: function _initializeGraphState(data) { | ||
var _this3 = this; | ||
this._validateGraphData(data); | ||
var graph = void 0; | ||
if (this.state && this.state.nodes && this.state.links && this.state.nodeIndexMapping) { | ||
// absorve existent positining | ||
graph = { | ||
nodes: data.nodes.map(function (n) { | ||
return Object.assign({}, n, _this3.state.nodes[n.id]); | ||
}), | ||
links: {} | ||
}; | ||
} else { | ||
graph = { | ||
nodes: data.nodes.map(function (n) { | ||
return Object.assign({}, n); | ||
}), | ||
links: {} | ||
}; | ||
} | ||
graph.links = data.links.map(function (l) { | ||
return Object.assign({}, l); | ||
}); | ||
var config = Object.assign({}, _utils2.default.merge(_config2.default, this.props.config || {})); | ||
var _GraphHelper$initiali = _helper2.default.initializeNodes(graph.nodes), | ||
nodes = _GraphHelper$initiali.nodes, | ||
nodeIndexMapping = _GraphHelper$initiali.nodeIndexMapping; | ||
var links = _helper2.default.initializeLinks(graph.links); // Matrix of graph connections | ||
var _graph = graph, | ||
d3Nodes = _graph.nodes, | ||
d3Links = _graph.links; | ||
var id = this.props.id.replace(/ /g, '_'); | ||
var simulation = _helper2.default.createForceSimulation(config.width, config.height); | ||
return { | ||
id: id, | ||
config: config, | ||
nodeIndexMapping: nodeIndexMapping, | ||
links: links, | ||
d3Links: d3Links, | ||
nodes: nodes, | ||
d3Nodes: d3Nodes, | ||
nodeHighlighted: false, | ||
simulation: simulation, | ||
newGraphElements: false, | ||
configUpdated: false | ||
}; | ||
} | ||
/** | ||
* Sets d3 tick function and configures other d3 stuff such as forces and drag events. | ||
*/ | ||
}, { | ||
key: '_graphForcesConfig', | ||
value: function _graphForcesConfig() { | ||
this.state.simulation.nodes(this.state.d3Nodes).on('tick', this._tick); | ||
var forceLink = d3.forceLink(this.state.d3Links).id(function (l) { | ||
return l.id; | ||
}).distance(_const2.default.LINK_IDEAL_DISTANCE).strength(1); | ||
this.state.simulation.force(_const2.default.LINK_CLASS_NAME, forceLink); | ||
var customNodeDrag = d3.drag().on('start', this._onDragStart).on('drag', this._onDragMove).on('end', this._onDragEnd); | ||
d3.select('#' + this.state.id + '-' + _const2.default.GRAPH_WRAPPER_ID).selectAll('.node').call(customNodeDrag); | ||
} | ||
}]); | ||
function Graph(props) { | ||
@@ -151,3 +311,3 @@ _classCallCheck(this, Graph); | ||
_this._onDragEnd = function () { | ||
return !_this.state.config.staticGraph && _this.state.config.automaticRearrangeAfterDropNode && _this.simulation.alphaTarget(0.05).restart(); | ||
return !_this.state.config.staticGraph && _this.state.config.automaticRearrangeAfterDropNode && _this.state.simulation.alphaTarget(0.05).restart(); | ||
}; | ||
@@ -158,3 +318,3 @@ | ||
// This is where d3 and react bind | ||
var draggedNode = _this.state.nodes[_this.indexMapping[index]]; | ||
var draggedNode = _this.state.nodes[_this.state.nodeIndexMapping[index]]; | ||
@@ -173,3 +333,3 @@ draggedNode.x += d3.event.dx; | ||
_this._onDragStart = function () { | ||
return !_this.state.config.staticGraph && _this.simulation.stop(); | ||
return _this.pauseSimulation(); | ||
}; | ||
@@ -187,3 +347,3 @@ | ||
_this.setState(_this.state || {}); | ||
_this._tick(); | ||
}; | ||
@@ -196,7 +356,7 @@ | ||
_this._zoomConfig = function () { | ||
return d3.select('#' + _this.id + '-' + _const2.default.GRAPH_WRAPPER_ID).call(d3.zoom().scaleExtent([_this.state.config.minZoom, _this.state.config.maxZoom]).on('zoom', _this._zoomed)); | ||
return d3.select('#' + _this.state.id + '-' + _const2.default.GRAPH_WRAPPER_ID).call(d3.zoom().scaleExtent([_this.state.config.minZoom, _this.state.config.maxZoom]).on('zoom', _this._zoomed)); | ||
}; | ||
_this._zoomed = function () { | ||
return d3.selectAll('#' + _this.id + '-' + _const2.default.GRAPH_CONTAINER_ID).attr('transform', d3.event.transform); | ||
return d3.selectAll('#' + _this.state.id + '-' + _const2.default.GRAPH_CONTAINER_ID).attr('transform', d3.event.transform); | ||
}; | ||
@@ -217,3 +377,3 @@ | ||
_this.pauseSimulation = function () { | ||
return !_this.state.config.staticGraph && _this.simulation.stop(); | ||
return !_this.state.config.staticGraph && _this.state.simulation.stop(); | ||
}; | ||
@@ -232,6 +392,6 @@ | ||
// @todo: hardcoded alpha target | ||
_this.simulation.alphaTarget(0.08).restart(); | ||
// @TODO: hardcoded alpha target | ||
_this.state.simulation.alphaTarget(0.08).restart(); | ||
_this.setState(_this.state || {}); | ||
_this._tick(); | ||
} | ||
@@ -241,86 +401,50 @@ }; | ||
_this.restartSimulation = function () { | ||
return !_this.state.config.staticGraph && _this.simulation.restart(); | ||
return !_this.state.config.staticGraph && _this.state.simulation.restart(); | ||
}; | ||
if (!_this.props.id) { | ||
throw _utils2.default.throwErr(_this.constructor.name, _err2.default.GRAPH_NO_ID_PROP); | ||
_utils2.default.throwErr(_this.constructor.name, _err2.default.GRAPH_NO_ID_PROP); | ||
} | ||
var graph = _this.props.data || {}; | ||
var config = _utils2.default.merge(_config2.default, _this.props.config || {}); | ||
var _GraphHelper$initiali = _helper2.default.initializeNodes(graph.nodes), | ||
nodes = _GraphHelper$initiali.nodes, | ||
indexMapping = _GraphHelper$initiali.indexMapping; | ||
var links = _helper2.default.initializeLinks(graph.links); // Matrix of graph connections | ||
_this.id = _this.props.id.replace(/ /g, '_'); | ||
_this.indexMapping = indexMapping; | ||
_this.simulation = _helper2.default.createForceSimulation(config.width, config.height); | ||
// Disposable once component is mounted | ||
_this.links = graph.links; | ||
_this.nodes = graph.nodes; | ||
_this.state = { | ||
config: config, | ||
links: links, | ||
nodes: nodes, | ||
nodeHighlighted: false | ||
}; | ||
_this.state = _this._initializeGraphState(_this.props.data); | ||
return _this; | ||
} | ||
/** | ||
* Calls d3 simulation.restart().<br/> | ||
* {@link https://github.com/d3/d3-force#simulation_restart} | ||
*/ | ||
_createClass(Graph, [{ | ||
key: 'componentWillReceiveProps', | ||
value: function componentWillReceiveProps(nextProps) { | ||
var newGraphElements = nextProps.data.nodes.length !== this.state.d3Nodes.length || nextProps.data.links.length !== this.state.d3Links.length; | ||
if (newGraphElements && nextProps.config.staticGraph) { | ||
_utils2.default.throwErr(this.constructor.name, _err2.default.STATIC_GRAPH_DATA_UPDATE); | ||
} | ||
/** | ||
* Calls d3 simulation.stop().<br/> | ||
* {@link https://github.com/d3/d3-force#simulation_stop} | ||
*/ | ||
var configUpdated = !_utils2.default.isDeepEqual(nextProps.config, this.state.config); | ||
var state = newGraphElements ? this._initializeGraphState(nextProps.data) : this.state; | ||
var config = _utils2.default.merge(_config2.default, nextProps.config || {}); | ||
// In order to properly update graph data we need to pause eventual d3 ongoing animations | ||
newGraphElements && this.pauseSimulation(); | ||
/** | ||
* Handles mouse out node event. | ||
* @param {number} index - index of the mouse hovered node. | ||
* @return {undefined} | ||
*/ | ||
this.setState(_extends({}, state, { | ||
config: config, | ||
newGraphElements: newGraphElements, | ||
configUpdated: configUpdated | ||
})); | ||
} | ||
}, { | ||
key: 'componentDidUpdate', | ||
value: function componentDidUpdate() { | ||
// If the property staticGraph was activated we want to stop possible ongoing simulation | ||
this.state.config.staticGraph && this.state.simulation.stop(); | ||
if (!this.state.config.staticGraph && this.state.newGraphElements) { | ||
this._graphForcesConfig(); | ||
this.restartSimulation(); | ||
this.state.newGraphElements = false; | ||
} | ||
/** | ||
* Configures zoom upon graph with default or user provided values.<br/> | ||
* {@link https://github.com/d3/d3-zoom#zoom} | ||
* @return {undefined} | ||
*/ | ||
/** | ||
* Sets nodes and links highlighted value. | ||
* @param {number} index - the index of the node to highlight (and its adjacent). | ||
* @param {boolean} value - the highlight value to be set (true or false). | ||
* @return {undefined} | ||
*/ | ||
/** | ||
* Handles d3 'drag' event. | ||
* @param {Object} _ - event. | ||
* @param {number} index - index of the node that is being dragged. | ||
* @return {undefined} | ||
*/ | ||
_createClass(Graph, [{ | ||
key: 'componentWillReceiveProps', | ||
value: function componentWillReceiveProps(nextProps) { | ||
var config = _utils2.default.merge(_config2.default, nextProps.config || {}); | ||
if (!_utils2.default.isEqual(this.state.config, config)) { | ||
this.setState({ | ||
config: config | ||
}); | ||
if (this.state.configUpdated) { | ||
this._zoomConfig(); | ||
this.state.configUpdated = false; | ||
} | ||
@@ -332,13 +456,3 @@ } | ||
if (!this.state.config.staticGraph) { | ||
this.simulation.nodes(this.nodes).on('tick', this._tick); | ||
var forceLink = d3.forceLink(this.links).id(function (l) { | ||
return l.id; | ||
}).distance(_const2.default.LINK_IDEAL_DISTANCE).strength(1); | ||
this.simulation.force(_const2.default.LINK_CLASS_NAME, forceLink); | ||
var customNodeDrag = d3.drag().on('start', this._onDragStart).on('drag', this._onDragMove).on('end', this._onDragEnd); | ||
d3.select('#' + this.id + '-' + _const2.default.GRAPH_WRAPPER_ID).selectAll('.node').call(customNodeDrag); | ||
this._graphForcesConfig(); | ||
} | ||
@@ -348,16 +462,4 @@ | ||
this._zoomConfig(); | ||
Reflect.deleteProperty(this, 'nodes'); | ||
Reflect.deleteProperty(this, 'links'); | ||
} | ||
}, { | ||
key: 'componentDidUpdate', | ||
value: function componentDidUpdate() { | ||
// If some zoom config changed we want to apply possible new values for maxZoom and minZoom | ||
this._zoomConfig(); | ||
// If the property staticGraph was activated we want to stop possible ongoing simulation | ||
this.state.config.staticGraph && this.simulation.stop(); | ||
} | ||
}, { | ||
key: 'render', | ||
@@ -380,3 +482,3 @@ value: function render() { | ||
'div', | ||
{ id: this.id + '-' + _const2.default.GRAPH_WRAPPER_ID }, | ||
{ id: this.state.id + '-' + _const2.default.GRAPH_WRAPPER_ID }, | ||
_react2.default.createElement( | ||
@@ -387,3 +489,3 @@ 'svg', | ||
'g', | ||
{ id: this.id + '-' + _const2.default.GRAPH_CONTAINER_ID }, | ||
{ id: this.state.id + '-' + _const2.default.GRAPH_CONTAINER_ID }, | ||
links, | ||
@@ -390,0 +492,0 @@ nodes |
@@ -24,3 +24,3 @@ 'use strict'; | ||
* @param {string} typeName - the string that specifies the symbol type (should be one of {@link #node-symbol-type|node.symbolType}). | ||
* @return {Object} concrete instance of d3 symbol. | ||
* @return {Object} concrete instance of d3 symbol (defaults to circle). | ||
* @memberof Node/helper | ||
@@ -50,3 +50,3 @@ */ | ||
default: | ||
return d3.symbolTriangle; | ||
return d3.symbolCircle; | ||
} | ||
@@ -53,0 +53,0 @@ } |
@@ -1,2 +0,2 @@ | ||
'use strict'; | ||
"use strict"; | ||
@@ -7,3 +7,7 @@ Object.defineProperty(exports, "__esModule", { | ||
exports.default = { | ||
GRAPH_NO_ID_PROP: 'id prop not defined! id property is mandatory and it should be unique.' | ||
GRAPH_NO_ID_PROP: "id prop not defined! id property is mandatory and it should be unique.", | ||
STATIC_GRAPH_DATA_UPDATE: "a static graph cannot receive new data (nodes or links).\ | ||
Make sure config.staticGraph is set to true if you want to update graph data", | ||
INVALID_LINKS: "you provided a invalid links data structure.\ | ||
Links source and target attributes must point to an existent node" | ||
}; |
@@ -16,3 +16,3 @@ 'use strict'; | ||
// This variable assures that recursive methods such as merge and isEqual do not fall on | ||
// This variable assures that recursive methods such as merge and isDeepEqual do not fall on | ||
// circular JSON structure evaluation. | ||
@@ -40,3 +40,3 @@ var MAX_DEPTH = 5; | ||
*/ | ||
function isEqual(o1, o2) { | ||
function isDeepEqual(o1, o2) { | ||
var _depth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
@@ -65,3 +65,3 @@ | ||
if (nestedO && _depth < MAX_DEPTH) { | ||
diffs.push(isEqual(o1[k], o2[k], _depth + 1)); | ||
diffs.push(isDeepEqual(o1[k], o2[k], _depth + 1)); | ||
} else { | ||
@@ -122,2 +122,3 @@ var r = isObjectEmpty(o1[k]) && isObjectEmpty(o2[k]) || o2.hasOwnProperty(k) && o2[k] === o1[k]; | ||
// @TODO: Support for arrays | ||
var o = {}; | ||
@@ -173,3 +174,3 @@ | ||
exports.default = { | ||
isEqual: isEqual, | ||
isDeepEqual: isDeepEqual, | ||
isObjectEmpty: isObjectEmpty, | ||
@@ -176,0 +177,0 @@ merge: merge, |
{ | ||
"name": "react-d3-graph", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"description": "React component to build interactive and configurable graphs with d3 effortlessly", | ||
@@ -8,2 +8,3 @@ "author": "Daniel Caldas", | ||
"scripts": { | ||
"check": "npm run lint && npm run test", | ||
"dev": "node_modules/.bin/webpack-dev-server -d --content-base sandbox --inline --hot --port 3002", | ||
@@ -10,0 +11,0 @@ "dist": "node_modules/.bin/npm-run-all --parallel dist:*", |
@@ -18,2 +18,8 @@ # react-d3-graph · [](https://travis-ci.com/danielcaldas/react-d3-graph) | ||
## Install | ||
```bash | ||
npm install react-d3-graph // using npm | ||
yarn add react-d3-graph // using yarn | ||
``` | ||
## Usage sample | ||
@@ -81,20 +87,4 @@ Graph component is the main component for react-d3-graph components, its interface allows its user | ||
## TODOs | ||
This consists in a list of ideas for further developments: | ||
- Expose a graph property **background-color** that is applied to the svg graph container; | ||
- Expose d3-force values as configurable such as **alphaTarget** simulation value; | ||
- Improve opacity/highlightBehavior strategy maybe use a global *background: rgba(...)* value and then set a higher | ||
value on selected nodes; | ||
- At the moment highlightBehavior is highlighting the mouse hovered node, its 1st degree connections and their 1st | ||
degree connections. Make **highlightBehaviorDegree** which consists in a *step value* on the depth that we wish to highlight; | ||
- Semantic node size. Have a property value in each node that then is used along side config.nodeSize property | ||
to calculate effective node size in run time; | ||
- Improve semanticStrokeWidth calculation; | ||
- On Graph instantiation do a check on all config properties. If there is a "bad property" (name or value) throw | ||
a custom error (property error checking); | ||
- Path highlight - highlight a certain set of links and nodes (use case: highlight shortest path between two given nodes); | ||
- Link mouseover with highlight behavior highlights the intervenient nodes. | ||
## Contributions | ||
Contributions are welcome fell free to submit new features or simply grab something from | ||
the above TODO list. |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
323379
17
1259
0
89