New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

react-d3-graph

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-d3-graph - npm Package Compare versions

Comparing version 2.0.0-rc1 to 2.0.0-rc2

documentation.yml

38

CHANGELOG.md
# Change Log
## [2.0.0-rc2](https://github.com/danielcaldas/react-d3-graph/tree/2.0.0-rc2)
[Full Changelog](https://github.com/danielcaldas/react-d3-graph/compare/2.0.0-rc1...2.0.0-rc2)
**Implemented enhancements:**
* Allow nodes to override strokeColor [\#122](https://github.com/danielcaldas/react-d3-graph/issues/122)
**Fixed bugs:**
* Custom onNodeClick handler not triggering on collapsible nodes [\#136](https://github.com/danielcaldas/react-d3-graph/issues/136)
* 🐛 Global `viewGenerator` not been applied to the nodes [\#130](https://github.com/danielcaldas/react-d3-graph/issues/130)
**Closed issues:**
* graph constantly re-rendering even when app is idle? [\#145](https://github.com/danielcaldas/react-d3-graph/issues/145)
* Allow users to pass in a function in node.labelProperty [\#133](https://github.com/danielcaldas/react-d3-graph/issues/133)
* Drop yarn support for development \(stick to npm only\) [\#127](https://github.com/danielcaldas/react-d3-graph/issues/127)
* Link mouse cursor property [\#119](https://github.com/danielcaldas/react-d3-graph/issues/119)
* Center graph on a specific node [\#102](https://github.com/danielcaldas/react-d3-graph/issues/102)
* Links with directional arrow [\#88](https://github.com/danielcaldas/react-d3-graph/issues/88)
**Merged pull requests:**
* Fix/right clicks [\#140](https://github.com/danielcaldas/react-d3-graph/pull/140) ([danielcaldas](https://github.com/danielcaldas))
* Refactor/clean link component [\#139](https://github.com/danielcaldas/react-d3-graph/pull/139) ([danielcaldas](https://github.com/danielcaldas))
* fix: Trigger custom click handler in collapsible nodes [\#137](https://github.com/danielcaldas/react-d3-graph/pull/137) ([LonelyPrincess](https://github.com/LonelyPrincess))
* Add Support to pass a function to node.labelProperty [\#135](https://github.com/danielcaldas/react-d3-graph/pull/135) ([dgautsch](https://github.com/dgautsch))
* Support Development on Windows Machines [\#134](https://github.com/danielcaldas/react-d3-graph/pull/134) ([dgautsch](https://github.com/dgautsch))
* Feature/directional graph [\#132](https://github.com/danielcaldas/react-d3-graph/pull/132) ([danielcaldas](https://github.com/danielcaldas))
* Global `viewGenerator` included in default config object [\#131](https://github.com/danielcaldas/react-d3-graph/pull/131) ([LonelyPrincess](https://github.com/LonelyPrincess))
* Remove Yarn [\#128](https://github.com/danielcaldas/react-d3-graph/pull/128) ([sasalx](https://github.com/sasalx))
* Feature/right clicking [\#124](https://github.com/danielcaldas/react-d3-graph/pull/124) ([ghardin137](https://github.com/ghardin137))
* Allow nodes to override strokeColor [\#123](https://github.com/danielcaldas/react-d3-graph/pull/123) ([Andras-Simon](https://github.com/Andras-Simon))
* fix: \#119 Add mouseCursor prop to \<Link\> [\#120](https://github.com/danielcaldas/react-d3-graph/pull/120) ([kaungmyatlwin](https://github.com/kaungmyatlwin))
* Add onClick handler to the canvas, for use in eg. unselecting nodes [\#113](https://github.com/danielcaldas/react-d3-graph/pull/113) ([smilykoch](https://github.com/smilykoch))
* Focus view on a node [\#107](https://github.com/danielcaldas/react-d3-graph/pull/107) ([LonelyPrincess](https://github.com/LonelyPrincess))
## [2.0.0-rc1](https://github.com/danielcaldas/react-d3-graph/tree/2.0.0-rc1)

@@ -4,0 +42,0 @@

56

lib/components/graph/graph.config.js

@@ -45,6 +45,11 @@ 'use strict';

* rearrange all nodes positions based on new position of dragged node (note: **staticGraph** should be false).
* @param {boolean} [collapsible=false] - 🚅🚅🚅 Allow leaf neighbours nodes to be collapsed (folded), this will allow users to clear the way out and focus on the parts of the graph that really matter.
* To see an example of this behavior you can access this sandbox link that has a specific set up to experiment this feature.
* @param {boolean} [collapsible=false] - 🚅🚅🚅 Allow leaf neighbors nodes to be collapsed (folded), this will allow users to clear the way out and focus on the parts of the graph that really matter.
* To see an example of this behavior you can access this sandbox link that has a specific set up to experiment this feature. **NOTE**: At this moment
* nodes without connections (orphan nodes) are not rendered when this property is activated (see [react-d3-graph/issues/#129](https://github.com/danielcaldas/react-d3-graph/issues/129)).
* <br/>
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-collapsible.gif?raw=true" width="820" height="480"/>
* @param {boolean} [directed=false] - This property makes react-d3-graph handle your graph as a directed graph. It will
* out of the box provide the look and feel of a directed graph and add directional semantic to links. You can see a sample in the image below.
* <br/>
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-directed.gif?raw=true" width="820" height="480"/>
* @param {number} [height=400] - the height of the (svg) area where the graph will be rendered.

@@ -63,2 +68,18 @@ * @param {boolean} [nodeHighlightBehavior=false] - 🚅🚅🚅 when user mouse hovers a node that node and adjacent common

* @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.
* **NOTE:** This animation is not trigger by default. In order to trigger it you need to pass down to `react-d3-graph` the
* node that you want to focus via prop `focusedNodeId` along side with nodes and links:
*
* ```javascript
* const data = {
* nodes: this.state.data.nodes,
* links: this.state.data.links,
* focusedNodeId: 'nodeIdToTriggerZoomAnimation'
* };
* ```
*
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-zoom-animation.gif?raw=true" width="820" height="480"/>
*
* @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,

@@ -97,5 +118,15 @@ * a similar functionality may be consulted {@link https://bl.ocks.org/mbostock/2a39a768b1d4bc00a09650edef75ad39|here}.

* @param {number} [node.highlightStrokeWidth=1.5] - strokeWidth in highlighted state.
* @param {string} [node.labelProperty='id'] - this is the node property that will be used in runtime to
* @param {string|Function} [node.labelProperty='id'] - this is the node property that will be used in runtime to
* fetch the label content. You just need to add some property (e.g. firstName) to the node payload and then set
* node.labelProperty to be **'firstName'**.
* node.labelProperty to be **'firstName'**. **This can also be a function!**, if you pass a function here it will be called
* to obtain the `label` value on the fly, as a client you will receive all the node information that you passed down into react-d3-graph,
* so the signature of the function would be:
* ```javascript
* function myCustomLabelBuilder(node) {
* // do stuff to get the final result...
* return 'label string';
* }
* ```
* Then you just need to make sure that you pass this function in the config in `config.node.labelProperty`.
* <br/>
* @param {string} [node.mouseCursor='pointer'] - {@link https://developer.mozilla.org/en/docs/Web/CSS/cursor?v=control|cursor}

@@ -107,3 +138,3 @@ * property for when some node is mouse hovered.

* @param {number} [node.size=200] - 🔍🔍🔍 defines the size of all nodes.
* @param {string} [node.strokeColor='none'] - color for the stroke of each node.
* @param {string} [node.strokeColor='none'] - 🔍🔍🔍 this is the stroke color that will be applied to the node if no **strokeColor property** is found inside the node itself (yes **you can pass a property 'strokeColor' inside the node and that stroke color will override this default one** ).
* @param {number} [node.strokeWidth=1.5] - the width of the all node strokes.

@@ -125,3 +156,3 @@ * @param {string} [node.svg=''] - 🔍🔍🔍 render custom svg for nodes in alternative to **node.symbolType**. This svg can

* **[note]** react-d3-graph will map this values to [d3 symbols](https://github.com/d3/d3-shape#symbols)
* @param {Function} [node.viewGenerator=undefined] - 🔍🔍🔍 function that receives a node and returns a JSX view.
* @param {Function} [node.viewGenerator=null] - 🔍🔍🔍 function that receives a node and returns a JSX view.
* <br/>

@@ -132,2 +163,6 @@ * @param {Object} link link object is explained in the next section. ⬇️

* (from version 1.3.0 this property can be configured at link level).
* @param {string} [link.highlightColor='#d3d3d3'] - links' color in highlight state.
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-bend.gif?raw=true" width="820" height="480"/>
* @param {string} [link.mouseCursor='pointer'] - {@link https://developer.mozilla.org/en/docs/Web/CSS/cursor?v=control|cursor}
* property for when link is mouse hovered.
* @param {number} [link.opacity=1] - the default opacity value for links.

@@ -145,3 +180,2 @@ * @param {boolean} [link.semanticStrokeWidth=false] - when set to true all links will have

* ```
* @param {string} [link.highlightColor='#d3d3d3'] - links' color in highlight state.
* @param {string} [link.type='STRAIGHT'] - the type of line to draw, available types at this point are:

@@ -152,3 +186,2 @@ * - "STRAIGHT" <small>(default)</small> - a straight line.

* <br/>
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-bend.gif?raw=true" width="820" height="480"/>
*

@@ -174,2 +207,3 @@ * @example

collapsible: false,
directed: false,
height: 400,

@@ -181,2 +215,4 @@ highlightDegree: 1,

minZoom: 0.1,
focusZoom: 1,
focusAnimationDuration: 0.75,
nodeHighlightBehavior: false,

@@ -210,3 +246,4 @@ panAndZoom: false,

svg: '',
symbolType: 'circle'
symbolType: 'circle',
viewGenerator: null
},

@@ -216,2 +253,3 @@ link: {

highlightColor: '#d3d3d3',
mouseCursor: 'pointer',
opacity: 1,

@@ -218,0 +256,0 @@ semanticStrokeWidth: false,

189

lib/components/graph/graph.helper.js

@@ -6,11 +6,10 @@ 'use strict';

});
exports.updateNodeHighlightedValue = exports.toggleNodeConnection = exports.initializeGraphState = exports.getNodeCardinality = exports.getLeafNodeConnections = exports.disconnectLeafNodeConnections = exports.checkForGraphElementsChanges = exports.checkForGraphConfigChanges = exports.buildNodeProps = exports.buildLinkProps = undefined;
exports.getCenterAndZoomTransformation = exports.updateNodeHighlightedValue = exports.initializeGraphState = exports.checkForGraphElementsChanges = exports.checkForGraphConfigChanges = exports.buildNodeProps = exports.buildLinkProps = undefined;
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; }; /*eslint-disable max-lines*/
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; }; /**
* @module Graph/helper
* @description
* Offers a series of methods that isolate logic of Graph component and also from Graph rendering methods.
*/
/**
* @module Graph/helper
* @description
* Offers a series of methods that isolate logic of Graph component and also from Graph rendering methods.
*/
/**
* @typedef {Object} Link

@@ -53,2 +52,4 @@ * @property {string} source - the node id of the source in the link.

var _marker = require('../marker/marker.helper');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -109,2 +110,3 @@

* @param {Array.<Link>} graphLinks - an array of all graph links.
* @param {Object} config - the graph config.
* @returns {Object.<string, Object>} an object containing a matrix of connections of the graph, for each nodeId,

@@ -114,3 +116,3 @@ * there is an object that maps adjacent nodes ids (string) and their values (number).

*/
function _initializeLinks(graphLinks) {
function _initializeLinks(graphLinks, config) {
return graphLinks.reduce(function (links, l) {

@@ -128,5 +130,10 @@ var source = l.source.id || l.source;

// TODO: If the graph is directed this should be adapted
links[source][target] = links[target][source] = l.value || 1;
var value = config.collapsible && l.isHidden ? 0 : l.value || 1;
links[source][target] = value;
if (!config.directed) {
links[target][source] = value;
}
return links;

@@ -174,6 +181,11 @@ }, {});

* @param {Array.<Object>} d3Links - all d3Links.
* @param {Object} config - same as {@link #buildGraph|config in buildGraph}.
* @param {Object} state - Graph component current state (same format as returned object on this function).
* @returns {Object} a d3Link.
* @memberof Graph/helper
*/
function _mapDataLinkToD3Link(link, index) {
var d3Links = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
var config = arguments[3];
var state = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};

@@ -183,3 +195,11 @@ var d3Link = d3Links[index];

if (d3Link) {
return d3Link;
var toggledDirected = state.config && state.config.directed && config.directed !== state.config.directed;
// every time we toggle directed config all links should be visible again
if (toggledDirected) {
return _extends({}, d3Link, { isHidden: false });
}
// every time we disable collapsible (collapsible is false) all links should be visible again
return config.collapsible ? d3Link : _extends({}, d3Link, { isHidden: false });
}

@@ -208,3 +228,2 @@

* @param {Object} data - Same as {@link #initializeGraphState|data in initializeGraphState}.
* @memberof Graph/helper
* @throws can throw the following error msg:

@@ -214,2 +233,3 @@ * INSUFFICIENT_DATA - msg if no nodes are provided

* @returns {undefined}
* @memberof Graph/helper
*/

@@ -305,15 +325,16 @@ function _validateGraphData(data) {

var markerId = config.directed ? (0, _marker.getMarkerId)(highlight, transform, config) : null;
return {
markerId: markerId,
d: d,
source: source,
target: target,
x1: x1,
y1: y1,
x2: x2,
y2: y2,
strokeWidth: strokeWidth,
stroke: stroke,
mouseCursor: config.link.mouseCursor,
className: _graph3.default.LINK_CLASS_NAME,
opacity: opacity,
onClickLink: linkCallbacks.onClickLink,
onRightClickLink: linkCallbacks.onRightClickLink,
onMouseOverLink: linkCallbacks.onMouseOverLink,

@@ -349,3 +370,3 @@ onMouseOutLink: linkCallbacks.onMouseOutLink

var stroke = config.node.strokeColor;
var stroke = node.strokeColor || config.node.strokeColor;

@@ -356,2 +377,8 @@ if (highlight && config.node.highlightStrokeColor !== _graph3.default.KEYWORDS.SAME) {

var label = node[config.node.labelProperty] || node.id;
if (typeof config.node.labelProperty === 'function') {
label = config.node.labelProperty(node);
}
var t = 1 / transform;

@@ -376,4 +403,5 @@ var nodeSize = node.size || config.node.size;

id: node.id,
label: node[config.node.labelProperty] || node.id,
label: label,
onClickNode: nodeCallbacks.onClickNode,
onRightClickNode: nodeCallbacks.onRightClickNode,
onMouseOverNode: nodeCallbacks.onMouseOverNode,

@@ -407,2 +435,3 @@ onMouseOut: nodeCallbacks.onMouseOut,

* some node or link or was updated).
* @memberof Graph/helper
*/

@@ -447,6 +476,7 @@ function checkForGraphElementsChanges(nextProps, currentState) {

* - d3ConfigUpdated - specific flag that indicates changes in d3 configurations.
* @memberof Graph/helper
*/
function checkForGraphConfigChanges(nextProps, currentState) {
var newConfig = nextProps.config || {};
var configUpdated = newConfig && !_utils2.default.isObjectEmpty(newConfig) && !_utils2.default.isDeepEqual(newConfig, currentState.config);
var configUpdated = newConfig && !_utils2.default.isEmptyObject(newConfig) && !_utils2.default.isDeepEqual(newConfig, currentState.config);
var d3ConfigUpdated = newConfig && newConfig.d3 && !_utils2.default.isDeepEqual(newConfig.d3, currentState.config.d3);

@@ -482,3 +512,3 @@

links: data.links.map(function (l, index) {
return _mapDataLinkToD3Link(l, index, state && state.d3Links);
return _mapDataLinkToD3Link(l, index, state && state.d3Links, config, state);
})

@@ -499,3 +529,3 @@ };

var nodes = _initializeNodes(graph.nodes);
var links = _initializeLinks(graph.links); // matrix of graph connections
var links = _initializeLinks(graph.links, newConfig); // matrix of graph connections
var _graph = graph,

@@ -508,2 +538,13 @@ d3Nodes = _graph.nodes,

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

@@ -558,95 +599,22 @@ id: formatedId,

/**
* This function disconnects all the connections from leaf -> parent.
* @param {string} targetNodeId - The id of the node from which to disconnect the leaf nodes
* @param {Object.<string, number>} originalConnections - An object containing a matrix of connections of the nodes.
* @param {Array} d3Links - An array containing all the d3 links.
* @returns {Object.<string, number>} - Contains the new links and d3Links.
* 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.
* @memberof Graph/helper
*/
function disconnectLeafNodeConnections(targetNodeId, originalConnections, d3Links) {
var leafNodesToToggle = getLeafNodeConnections(targetNodeId, originalConnections);
var toggledLeafNodes = Object.keys(leafNodesToToggle).reduce(function (newLeafNodeConnections, leafNodeId) {
// toggle connections from Leaf node to Parent node
newLeafNodeConnections[leafNodeId] = toggleNodeConnection(targetNodeId, originalConnections[leafNodeId]);
function getCenterAndZoomTransformation(d3Node, config) {
if (!d3Node) {
return;
}
return newLeafNodeConnections;
}, {});
var width = config.width,
height = config.height,
focusZoom = config.focusZoom;
var toggledLeafNodesList = Object.keys(toggledLeafNodes);
var toggledD3Links = d3Links.reduce(function (allD3Links, currentD3Link) {
var source = currentD3Link.source,
target = currentD3Link.target;
var isLeafNode = toggledLeafNodesList.some(function (leafNodeId) {
return leafNodeId === '' + source.id || leafNodeId === '' + target.id;
});
isLeafNode ? allD3Links.push(_extends({}, currentD3Link, { isHidden: !currentD3Link.isHidden })) : allD3Links.push(currentD3Link);
return allD3Links;
}, []);
return {
d3Links: toggledD3Links,
links: _extends({}, originalConnections, toggledLeafNodes)
};
return '\n translate(' + width / 2 + ', ' + height / 2 + ')\n scale(' + focusZoom + ')\n translate(' + -d3Node.x + ', ' + -d3Node.y + ')\n ';
}
/**
* This function toggles the value for a given node connection (1 -> 0 and vice-versa).
* @param {string} targetNodeId - The id of the node which to toggle
* @param {Object.<string, number>} allConnections - An object containing a matrix of connections of the node
* where we want to toggle the connection (destinations/targets).
* @returns {Object.<string, number>} - Contains the new connections with the target node toggled.
* @memberof Graph/helper
*/
function toggleNodeConnection(targetNodeId, allConnections) {
var newConnection = _defineProperty({}, targetNodeId, allConnections[targetNodeId] === 1 ? 0 : 1);
return _extends({}, allConnections, newConnection);
}
/**
* Based on a starting node (ID) and all the current connections between all the nodes.
* Find the leaf node connections of that starting node.
* @param {string} startingNodeId - The id of the node where the "search" should be started.
* @param {Object.<string, number>} currentConnections - An object containing a matrix of connections of the nodes.
* @returns {Object.<string, number>} - Contains the connections to leaf nodes based on the given starting node.
* @memberof Graph/helper
*/
function getLeafNodeConnections(startingNodeId, currentConnections) {
var startingNodeConnections = currentConnections[startingNodeId];
var startingNodeConnectionsList = Object.keys(startingNodeConnections);
return startingNodeConnectionsList.reduce(function (allLeafNodes, candidateLeafId) {
var candidateLeafConnections = currentConnections[candidateLeafId];
var candidateLeafConnectionList = Object.keys(candidateLeafConnections);
var isLeafNode = candidateLeafConnectionList.length === 1;
if (isLeafNode) {
allLeafNodes[candidateLeafId] = candidateLeafConnections;
}
return allLeafNodes;
}, {});
}
/**
* Given a node and the connections matrix, give the cardinality of the node.
*
* i.e.: Taking into account the node is connected to nothing, it amounts to 0.
* Being connected to three nodes, it amounts to 3.
* @param {string} nodeId - The id of the node to get the cardinality of
* @param {Object.<string, number>} linksMatrix - An object containing a matrix of connections of the nodes.
* @returns {number} - Contains the cardinality of the asked node.
* @memberof Graph/helper
*/
function getNodeCardinality(nodeId, linksMatrix) {
var nodeConnectivityList = Object.values(linksMatrix[nodeId] || []);
return nodeConnectivityList.reduce(function (cardinality, nodeConnectivity) {
return cardinality + nodeConnectivity;
}, 0);
}
exports.buildLinkProps = buildLinkProps;

@@ -656,7 +624,4 @@ exports.buildNodeProps = buildNodeProps;

exports.checkForGraphElementsChanges = checkForGraphElementsChanges;
exports.disconnectLeafNodeConnections = disconnectLeafNodeConnections;
exports.getLeafNodeConnections = getLeafNodeConnections;
exports.getNodeCardinality = getNodeCardinality;
exports.initializeGraphState = initializeGraphState;
exports.toggleNodeConnection = toggleNodeConnection;
exports.updateNodeHighlightedValue = updateNodeHighlightedValue;
exports.updateNodeHighlightedValue = updateNodeHighlightedValue;
exports.getCenterAndZoomTransformation = getCenterAndZoomTransformation;

@@ -35,10 +35,14 @@ 'use strict';

var _graph5 = require('./graph.renderer');
var _collapse = require('./collapse.helper');
var graphRenderer = _interopRequireWildcard(_graph5);
var collapseHelper = _interopRequireWildcard(_collapse);
var _graph6 = require('./graph.helper');
var _graph5 = require('./graph.helper');
var graphHelper = _interopRequireWildcard(_graph6);
var graphHelper = _interopRequireWildcard(_graph5);
var _graph6 = require('./graph.renderer');
var graphRenderer = _interopRequireWildcard(_graph6);
var _utils = require('../../utils');

@@ -94,2 +98,6 @@

* // graph event callbacks
* const onClickGraph = function() {
* window.alert('Clicked the graph background');
* };
*
* const onClickNode = function(nodeId) {

@@ -99,2 +107,6 @@ * window.alert('Clicked node ${nodeId}');

*
* const onRightClickNode = function(event, nodeId) {
* window.alert('Right clicked node ${nodeId}');
* };
*
* const onMouseOverNode = function(nodeId) {

@@ -112,2 +124,6 @@ * window.alert(`Mouse over node ${nodeId}`);

*
* const onRightClickLink = function(event, source, target) {
* window.alert('Right clicked link between ${source} and ${target}');
* };
*
* const onMouseOverLink = function(source, target) {

@@ -125,4 +141,7 @@ * window.alert(`Mouse over in link between ${source} and ${target}`);

* config={myConfig}
* onClickGraph={onClickGraph}
* onClickNode={onClickNode}
* onRightClickNode={onRightClickNode}
* onClickLink={onClickLink}
* onRightClickLink={onRightClickLink}
* onMouseOverNode={onMouseOverNode}

@@ -192,2 +211,3 @@ * onMouseOutNode={onMouseOutNode}

* @param {Object} state - new state to pass on.
* @param {Function} [cb] - optional callback to fed in to {@link setState()|https://reactjs.org/docs/react-component.html#setstate}.
* @returns {undefined}

@@ -291,3 +311,6 @@ */

_this._onDragStart = function () {
return _this.pauseSimulation();
_this.pauseSimulation();
if (_this.state.enableFocusAnimation) {
_this.setState({ enableFocusAnimation: false });
}
};

@@ -302,3 +325,4 @@

var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return _this.setState(state);
var cb = arguments[1];
return cb ? _this.setState(state, cb) : _this.setState(state);
};

@@ -377,10 +401,53 @@

if (_this.state.config.collapsible) {
var disconnectedLeafNodesPartialState = graphHelper.disconnectLeafNodeConnections(clickedNodeId, _this.state.links, _this.state.d3Links);
var leafConnections = collapseHelper.getTargetLeafConnections(clickedNodeId, _this.state.links, _this.state.config);
var links = collapseHelper.toggleLinksMatrixConnections(_this.state.links, leafConnections, _this.state.config);
var d3Links = collapseHelper.toggleLinksConnections(_this.state.d3Links, links);
_this._tick(_extends({}, disconnectedLeafNodesPartialState));
_this._tick({
links: links,
d3Links: d3Links
}, function () {
return _this.props.onClickNode && _this.props.onClickNode(clickedNodeId);
});
} else {
_this.props.onClickNode && _this.props.onClickNode(clickedNodeId);
}
};
_this.props.onClickNode && _this.props.onClickNode(clickedNodeId);
_this.onClickGraph = function (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
if (e.target.tagName.toUpperCase() === 'SVG' && e.target.attributes.name.value === 'svg-container-' + _this.state.id) {
_this.props.onClickGraph && _this.props.onClickGraph();
}
};
_this._generateFocusAnimationProps = function () {
var focusedNodeId = _this.state.focusedNodeId;
// 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(function () {
return _this.setState({ enableFocusAnimation: false });
}, _this.state.config.focusAnimationDuration * 1000);
}
var transitionDuration = _this.state.enableFocusAnimation ? _this.state.config.focusAnimationDuration : 0;
return {
style: { transitionDuration: transitionDuration + 's' },
transform: focusedNodeId ? _this.state.focusTransformation : null
};
};
if (!_this.props.id) {

@@ -390,2 +457,3 @@ _utils2.default.throwErr(_this.constructor.name, _err2.default.GRAPH_NO_ID_PROP);

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

@@ -428,2 +496,9 @@ return _this;

var focusedNodeId = nextProps.data.focusedNodeId;
var d3FocusedNode = this.state.d3Nodes.find(function (node) {
return '' + node.id === '' + focusedNodeId;
});
var focusTransformation = graphHelper.getCenterAndZoomTransformation(d3FocusedNode, this.state.config);
var enableFocusAnimation = this.props.data.focusedNodeId !== nextProps.data.focusedNodeId;
this.setState(_extends({}, state, {

@@ -434,3 +509,6 @@ config: config,

newGraphElements: newGraphElements,
transform: transform
transform: transform,
focusedNodeId: focusedNodeId,
enableFocusAnimation: enableFocusAnimation,
focusTransformation: focusTransformation
}));

@@ -477,2 +555,17 @@ }

/**
* Calls the callback passed to the component.
* @param {Object} e - The event of onClick handler.
* @returns {undefined}
*/
/**
* 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.
*/
}, {

@@ -483,2 +576,3 @@ key: 'render',

onClickNode: this.onClickNode,
onRightClickNode: this.props.onRightClickNode,
onMouseOverNode: this.onMouseOverNode,

@@ -488,2 +582,3 @@ onMouseOut: this.onMouseOutNode

onClickLink: this.props.onClickLink,
onRightClickLink: this.props.onRightClickLink,
onMouseOverLink: this.onMouseOverLink,

@@ -493,3 +588,4 @@ onMouseOutLink: this.onMouseOutLink

nodes = _graphRenderer$buildG.nodes,
links = _graphRenderer$buildG.links;
links = _graphRenderer$buildG.links,
defs = _graphRenderer$buildG.defs;

@@ -501,2 +597,4 @@ var svgStyle = {

var containerProps = this._generateFocusAnimationProps();
return _react2.default.createElement(

@@ -507,6 +605,7 @@ 'div',

'svg',
{ style: svgStyle },
{ name: 'svg-container-' + this.state.id, style: svgStyle, onClick: this.onClickGraph },
defs,
_react2.default.createElement(
'g',
{ id: this.state.id + '-' + _graph2.default.GRAPH_CONTAINER_ID },
_extends({ id: this.state.id + '-' + _graph2.default.GRAPH_CONTAINER_ID }, containerProps),
links,

@@ -513,0 +612,0 @@ nodes

@@ -23,2 +23,4 @@ 'use strict';

var _marker = require('../marker/marker.const');
var _Link = require('../link/Link');

@@ -32,4 +34,10 @@

var _Marker = require('../marker/Marker');
var _Marker2 = _interopRequireDefault(_Marker);
var _graph3 = require('./graph.helper');
var _collapse = require('./collapse.helper');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -47,10 +55,16 @@

* @param {number} transform - value that indicates the amount of zoom transformation.
* @returns {Object[]} returns the generated array of Link components.
* @memberof Graph/renderer
* @returns {Array.<Object>} returns the generated array of Link components.
* @memberof Graph/helper
*/
function _buildLinks(nodes, links, linksMatrix, config, linkCallbacks, highlightedNode, highlightedLink, transform) {
return links.filter(function (_ref) {
var isHidden = _ref.isHidden;
return !isHidden;
}).map(function (link) {
var outLinks = links;
if (config.collapsible) {
outLinks = outLinks.filter(function (_ref) {
var isHidden = _ref.isHidden;
return !isHidden;
});
}
return outLinks.map(function (link) {
var source = link.source,

@@ -80,4 +94,4 @@ target = link.target;

* @param {Object.<string, Object>} linksMatrix - the matrix of connections of the graph
* @returns {Object} returns the generated array of nodes components
* @memberof Graph/renderer
* @returns {Array.<Object>} returns the generated array of node components
* @memberof Graph/helper
*/

@@ -89,3 +103,3 @@ function _buildNodes(nodes, nodeCallbacks, config, highlightedNode, highlightedLink, transform, linksMatrix) {

outNodes = outNodes.filter(function (nodeId) {
return (0, _graph3.getNodeCardinality)(nodeId, linksMatrix) > 0;
return (0, _collapse.isNodeVisible)(nodeId, linksMatrix);
});

@@ -102,2 +116,44 @@ }

/**
* Builds graph defs (for now markers, but we could also have gradients for instance).
* NOTE: defs are static svg graphical objects, thus we only need to render them once, the result
* is cached on the 1st call and from there we simply return the cached jsx.
* @returns {Function} memoized build definitions function.
* @memberof Graph/helper
*/
function _buildDefs() {
var cachedDefs = void 0;
return function (config) {
if (cachedDefs) {
return cachedDefs;
}
var small = _marker.MARKER_SMALL_SIZE;
var medium = small + _marker.MARKER_MEDIUM_OFFSET * config.maxZoom / 3;
var large = small + _marker.MARKER_LARGE_OFFSET * config.maxZoom / 3;
cachedDefs = _react2.default.createElement(
'defs',
null,
_react2.default.createElement(_Marker2.default, { id: _marker.MARKERS.MARKER_S, refX: small, fill: config.link.color }),
_react2.default.createElement(_Marker2.default, { id: _marker.MARKERS.MARKER_SH, refX: small, fill: config.link.highlightColor }),
_react2.default.createElement(_Marker2.default, { id: _marker.MARKERS.MARKER_M, refX: medium, fill: config.link.color }),
_react2.default.createElement(_Marker2.default, { id: _marker.MARKERS.MARKER_MH, refX: medium, fill: config.link.highlightColor }),
_react2.default.createElement(_Marker2.default, { id: _marker.MARKERS.MARKER_L, refX: large, fill: config.link.color }),
_react2.default.createElement(_Marker2.default, { id: _marker.MARKERS.MARKER_LH, refX: large, fill: config.link.highlightColor })
);
return cachedDefs;
};
}
/**
* Memoized reference for _buildDefs.
* @param {Object} config - an object containing rd3g consumer defined configurations {@link #config config} for the graph.
* @returns {Object} graph reusable objects [defs](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs).
* @memberof Graph/helper
*/
var _memoizedBuildDefs = _buildDefs();
/**
* Method that actually is exported an consumed by Graph component in order to build all Nodes and Link

@@ -146,3 +202,4 @@ * components.

nodes: _buildNodes(nodes, nodeCallbacks, config, highlightedNode, highlightedLink, transform, linksMatrix),
links: _buildLinks(nodes, links, linksMatrix, config, linkCallbacks, highlightedNode, highlightedLink, transform)
links: _buildLinks(nodes, links, linksMatrix, config, linkCallbacks, highlightedNode, highlightedLink, transform),
defs: _memoizedBuildDefs(config)
};

@@ -149,0 +206,0 @@ }

@@ -58,3 +58,3 @@ 'use strict';

* *CURVE_SMOOTH* type inspired by {@link http://bl.ocks.org/mbostock/1153292|mbostock - Mobile Patent Suits}.
* @param {string} type type of curve to get radius strategy from.
* @param {string} [type=LINE_TYPES.STRAIGHT] type of curve to get radius strategy from.
* @returns {Function} a function that calculates a radius

@@ -65,3 +65,3 @@ * to match curve type expectation. Fallback is the straight line.

function getRadiusStrategy(type) {
return RADIUS_STRATEGIES[type];
return RADIUS_STRATEGIES[type] || RADIUS_STRATEGIES[_link.LINE_TYPES.STRAIGHT];
}

@@ -68,0 +68,0 @@

@@ -28,2 +28,6 @@ 'use strict';

*
* const onRightClickLink = function(source, target) {
* window.alert(`Right clicked link between ${source} and ${target}`);
* };
*
* const onMouseOverLink = function(source, target) {

@@ -41,6 +45,3 @@ * window.alert(`Mouse over in link between ${source} and ${target}`);

* target='idTargetNode'
* x1=22
* y1=22
* x2=22
* y2=22
* markerId='marker-small'
* strokeWidth=1.5

@@ -50,3 +51,5 @@ * stroke='green'

* opacity=1
* mouseCursor='pointer'
* onClickLink={onClickLink}
* onRightClickLink={onRightClickLink}
* onMouseOverLink={onMouseOverLink}

@@ -71,2 +74,4 @@ * onMouseOutLink={onMouseOutLink} />

return _this.props.onClickLink && _this.props.onClickLink(_this.props.source, _this.props.target);
}, _this.handleOnRightClickLink = function (event) {
return _this.props.onRightClickLink && _this.props.onRightClickLink(event, _this.props.source, _this.props.target);
}, _this.handleOnMouseOverLink = function () {

@@ -85,2 +90,9 @@ return _this.props.onMouseOverLink && _this.props.onMouseOverLink(_this.props.source, _this.props.target);

/**
* Handle link right click event.
* @param {Object} event - native event.
* @returns {undefined}
*/
/**
* Handle mouse over link event.

@@ -104,3 +116,4 @@ * @returns {undefined}

opacity: this.props.opacity,
fill: 'none'
fill: 'none',
cursor: this.props.mouseCursor
};

@@ -112,11 +125,12 @@

onClick: this.handleOnClickLink,
onContextMenu: this.handleOnRightClickLink,
onMouseOut: this.handleOnMouseOutLink,
onMouseOver: this.handleOnMouseOverLink,
style: lineStyle,
x1: this.props.x1,
x2: this.props.x2,
y1: this.props.y1,
y2: this.props.y2
style: lineStyle
};
if (this.props.markerId) {
lineProps.markerEnd = 'url(#' + this.props.markerId + ')';
}
return _react2.default.createElement('path', lineProps);

@@ -123,0 +137,0 @@ }

@@ -38,2 +38,6 @@ 'use strict';

*
* const onRightClickNode = function(nodeId) {
* window.alert('Right clicked node', nodeId);
* }
*
* const onMouseOverNode = function(nodeId) {

@@ -67,2 +71,3 @@ * window.alert('Mouse over node', nodeId);

* onClickNode={onClickNode}
* onRightClickNode={onRightClickNode}
* onMouseOverNode={onMouseOverNode}

@@ -87,2 +92,4 @@ * onMouseOutNode={onMouseOutNode} />

return _this.props.onClickNode && _this.props.onClickNode(_this.props.id);
}, _this.handleOnRightClickNode = function (event) {
return _this.props.onRightClickNode && _this.props.onRightClickNode(event, _this.props.id);
}, _this.handleOnMouseOverNode = function () {

@@ -101,2 +108,9 @@ return _this.props.onMouseOverNode && _this.props.onMouseOverNode(_this.props.id);

/**
* Handle right click on the node.
* @param {Object} event - native event.
* @returns {undefined}
*/
/**
* Handle mouse over node event.

@@ -119,2 +133,3 @@ * @returns {undefined}

onClick: this.handleOnClickNode,
onContextMenu: this.handleOnRightClickNode,
onMouseOut: this.handleOnMouseOutNode,

@@ -121,0 +136,0 @@ onMouseOver: this.handleOnMouseOverNode,

@@ -28,3 +28,3 @@ 'use strict';

function _isPropertyNestedObject(o, k) {
return o.hasOwnProperty(k) && _typeof(o[k]) === 'object' && o[k] !== null && !isObjectEmpty(o[k]);
return o.hasOwnProperty(k) && _typeof(o[k]) === 'object' && o[k] !== null && !isEmptyObject(o[k]);
}

@@ -49,3 +49,3 @@

if (isObjectEmpty(o1) && !isObjectEmpty(o2) || !isObjectEmpty(o1) && isObjectEmpty(o2)) {
if (isEmptyObject(o1) && !isEmptyObject(o2) || !isEmptyObject(o1) && isEmptyObject(o2)) {
return false;

@@ -74,3 +74,3 @@ }

} else {
var r = isObjectEmpty(o1[k]) && isObjectEmpty(o2[k]) || o2.hasOwnProperty(k) && o2[k] === o1[k];
var r = isEmptyObject(o1[k]) && isEmptyObject(o2[k]) || o2.hasOwnProperty(k) && o2[k] === o1[k];

@@ -109,3 +109,3 @@ diffs.push(r);

*/
function isObjectEmpty(o) {
function isEmptyObject(o) {
return !!o && (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === 'object' && !Object.keys(o).length;

@@ -133,3 +133,3 @@ }

if (Object.keys(o1 || {}).length === 0) {
return o2 && !isObjectEmpty(o2) ? o2 : {};
return o2 && !isEmptyObject(o2) ? o2 : {};
}

@@ -202,2 +202,3 @@

* @returns {Object} the object resultant from the anti picking operation.
* @memberof utils
*/

@@ -229,3 +230,3 @@ function antiPick(o) {

isDeepEqual: isDeepEqual,
isObjectEmpty: isObjectEmpty,
isEmptyObject: isEmptyObject,
merge: merge,

@@ -232,0 +233,0 @@ pick: pick,

{
"name": "react-d3-graph",
"version": "2.0.0-rc1",
"version": "2.0.0-rc2",
"description": "React component to build interactive and configurable graphs with d3 effortlessly",

@@ -10,3 +10,3 @@ "author": "Daniel Caldas",

"check:light": "npm run lint && npm run test",
"dev": "NODE_ENV=dev webpack-dev-server --mode=development --content-base sandbox --config webpack.config.js --inline --hot --port 3002",
"dev": "cross-env NODE_ENV=dev webpack-dev-server --mode=development --content-base sandbox --config webpack.config.js --inline --hot --port 3002",
"dist:rd3g": "rm -rf dist/ && webpack --config webpack.config.dist.js -p --display-modules --optimize-minimize",

@@ -17,4 +17,4 @@ "dist:sandbox": "webpack --config webpack.config.js -p",

"docs:lint": "node_modules/documentation/bin/documentation.js lint src/**/*.js",
"docs:watch": "node_modules/documentation/bin/documentation.js build src/**/*.js -f html -o gen-docs --watch",
"docs": "npm run docs:lint && node_modules/documentation/bin/documentation.js build src/**/*.js -f html -o gen-docs && node_modules/documentation/bin/documentation.js build src/**/*.js -f md > gen-docs/DOCUMENTATION.md",
"docs:watch": "node_modules/documentation/bin/documentation.js --config documentation.yml build src/**/*.js -f html -o gen-docs --watch",
"docs": "npm run docs:lint && node_modules/documentation/bin/documentation.js --config documentation.yml build src/**/*.js -f html -o gen-docs && node_modules/documentation/bin/documentation.js build src/**/*.js -f md > gen-docs/DOCUMENTATION.md",
"functional:local": "export CYPRESS_SANDBOX_URL=http://localhost:3002 && cypress open",

@@ -30,3 +30,4 @@ "functional:remote": "export CYPRESS_SANDBOX_URL=https://danielcaldas.github.io/react-d3-graph/sandbox/index.html && cypress open",

"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"
},

@@ -58,2 +59,3 @@ "lint-staged": {

"babel-preset-stage-0": "6.24.1",
"cross-env": "^5.2.0",
"css-loader": "0.28.7",

@@ -65,2 +67,3 @@ "cypress": "2.1.0",

"eslint-config-recommended": "2.0.0",
"eslint-plugin-cypress": "2.0.1",
"eslint-plugin-jest": "21.18.0",

@@ -67,0 +70,0 @@ "eslint-plugin-promise": "3.7.0",

@@ -9,2 +9,4 @@ # react-d3-graph &middot; [![Build Status](https://travis-ci.org/danielcaldas/react-d3-graph.svg?branch=master&style=flat-square)](https://travis-ci.org/danielcaldas/react-d3-graph)

[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/DanielCaldas321)
:book: [documentation](https://danielcaldas.github.io/react-d3-graph/docs/index.html)

@@ -22,4 +24,5 @@

* [small dataset](https://goodguydaniel.com/react-d3-graph/sandbox/index.html?data=small)
* [custom node dataset](https://goodguydaniel.com/react-d3-graph/sandbox/index.html?data=custom-node)
* [small dataset](https://goodguydaniel.com/react-d3-graph/sandbox/index.html?data=small) - small example.
* [custom node dataset](https://goodguydaniel.com/react-d3-graph/sandbox/index.html?data=custom-node) - sample config with custom views.
* [marvel dataset!](https://goodguydaniel.com/react-d3-graph/sandbox/index.html?data=marvel) - sample config with directed collapsible graph and custom svg nodes.

@@ -38,3 +41,2 @@ Do you want to visualize your own data set on the live sandbox? Just submit a PR! You're welcome 😁

npm install react-d3-graph // using npm
yarn add react-d3-graph // using yarn
```

@@ -71,2 +73,6 @@

// graph event callbacks
const onClickGraph = function() {
window.alert(`Clicked the graph background`);
};
const onClickNode = function(nodeId) {

@@ -76,2 +82,6 @@ window.alert(`Clicked node ${nodeId}`);

const onRightClickNode = function(event, nodeId) {
window.alert(`Right clicked node ${nodeId}`);
};
const onMouseOverNode = function(nodeId) {

@@ -89,2 +99,6 @@ window.alert(`Mouse over node ${nodeId}`);

const onRightClickLink = function(event, source, target) {
window.alert(`Right clicked link between ${source} and ${target}`);
};
const onMouseOverLink = function(source, target) {

@@ -103,3 +117,6 @@ window.alert(`Mouse over in link between ${source} and ${target}`);

onClickNode={onClickNode}
onRightClickNode={onRightClickNode}
onClickGraph={onClickGraph}
onClickLink={onClickLink}
onRightClickLink={onRightClickLink}
onMouseOverNode={onMouseOverNode}

@@ -121,4 +138,10 @@ onMouseOutNode={onMouseOutNode}

## Donation
Using _react-d3-graph_ and want to help the project grow with new features or simply want to say thank you? You can always buy me a cup of coffee ☕☕☕
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/DanielCaldas321)
## Alternatives (Not what you where looking for?)
Well if you scrolled this far maybe _react-d3-graph_ does not fulfill all your requirements 😭, but don't worry I got you covered! There are a lot of different and good alternatives out there, [here is a list with a few alternatives](http://anvaka.github.io/graph-drawing-libraries/#!/all#%2Fall). Btw, not in the previous list but also a valid alternative built by uber [uber/react-vis-force](https://github.com/uber/react-vis-force).

@@ -40,6 +40,11 @@ /**

* rearrange all nodes positions based on new position of dragged node (note: **staticGraph** should be false).
* @param {boolean} [collapsible=false] - 🚅🚅🚅 Allow leaf neighbours nodes to be collapsed (folded), this will allow users to clear the way out and focus on the parts of the graph that really matter.
* To see an example of this behavior you can access this sandbox link that has a specific set up to experiment this feature.
* @param {boolean} [collapsible=false] - 🚅🚅🚅 Allow leaf neighbors nodes to be collapsed (folded), this will allow users to clear the way out and focus on the parts of the graph that really matter.
* To see an example of this behavior you can access this sandbox link that has a specific set up to experiment this feature. **NOTE**: At this moment
* nodes without connections (orphan nodes) are not rendered when this property is activated (see [react-d3-graph/issues/#129](https://github.com/danielcaldas/react-d3-graph/issues/129)).
* <br/>
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-collapsible.gif?raw=true" width="820" height="480"/>
* @param {boolean} [directed=false] - This property makes react-d3-graph handle your graph as a directed graph. It will
* out of the box provide the look and feel of a directed graph and add directional semantic to links. You can see a sample in the image below.
* <br/>
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-directed.gif?raw=true" width="820" height="480"/>
* @param {number} [height=400] - the height of the (svg) area where the graph will be rendered.

@@ -58,2 +63,18 @@ * @param {boolean} [nodeHighlightBehavior=false] - 🚅🚅🚅 when user mouse hovers a node that node and adjacent common

* @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.
* **NOTE:** This animation is not trigger by default. In order to trigger it you need to pass down to `react-d3-graph` the
* node that you want to focus via prop `focusedNodeId` along side with nodes and links:
*
* ```javascript
* const data = {
* nodes: this.state.data.nodes,
* links: this.state.data.links,
* focusedNodeId: 'nodeIdToTriggerZoomAnimation'
* };
* ```
*
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-zoom-animation.gif?raw=true" width="820" height="480"/>
*
* @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,

@@ -92,5 +113,15 @@ * a similar functionality may be consulted {@link https://bl.ocks.org/mbostock/2a39a768b1d4bc00a09650edef75ad39|here}.

* @param {number} [node.highlightStrokeWidth=1.5] - strokeWidth in highlighted state.
* @param {string} [node.labelProperty='id'] - this is the node property that will be used in runtime to
* @param {string|Function} [node.labelProperty='id'] - this is the node property that will be used in runtime to
* fetch the label content. You just need to add some property (e.g. firstName) to the node payload and then set
* node.labelProperty to be **'firstName'**.
* node.labelProperty to be **'firstName'**. **This can also be a function!**, if you pass a function here it will be called
* to obtain the `label` value on the fly, as a client you will receive all the node information that you passed down into react-d3-graph,
* so the signature of the function would be:
* ```javascript
* function myCustomLabelBuilder(node) {
* // do stuff to get the final result...
* return 'label string';
* }
* ```
* Then you just need to make sure that you pass this function in the config in `config.node.labelProperty`.
* <br/>
* @param {string} [node.mouseCursor='pointer'] - {@link https://developer.mozilla.org/en/docs/Web/CSS/cursor?v=control|cursor}

@@ -102,3 +133,3 @@ * property for when some node is mouse hovered.

* @param {number} [node.size=200] - 🔍🔍🔍 defines the size of all nodes.
* @param {string} [node.strokeColor='none'] - color for the stroke of each node.
* @param {string} [node.strokeColor='none'] - 🔍🔍🔍 this is the stroke color that will be applied to the node if no **strokeColor property** is found inside the node itself (yes **you can pass a property 'strokeColor' inside the node and that stroke color will override this default one** ).
* @param {number} [node.strokeWidth=1.5] - the width of the all node strokes.

@@ -120,3 +151,3 @@ * @param {string} [node.svg=''] - 🔍🔍🔍 render custom svg for nodes in alternative to **node.symbolType**. This svg can

* **[note]** react-d3-graph will map this values to [d3 symbols](https://github.com/d3/d3-shape#symbols)
* @param {Function} [node.viewGenerator=undefined] - 🔍🔍🔍 function that receives a node and returns a JSX view.
* @param {Function} [node.viewGenerator=null] - 🔍🔍🔍 function that receives a node and returns a JSX view.
* <br/>

@@ -127,2 +158,6 @@ * @param {Object} link link object is explained in the next section. ⬇️

* (from version 1.3.0 this property can be configured at link level).
* @param {string} [link.highlightColor='#d3d3d3'] - links' color in highlight state.
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-bend.gif?raw=true" width="820" height="480"/>
* @param {string} [link.mouseCursor='pointer'] - {@link https://developer.mozilla.org/en/docs/Web/CSS/cursor?v=control|cursor}
* property for when link is mouse hovered.
* @param {number} [link.opacity=1] - the default opacity value for links.

@@ -140,3 +175,2 @@ * @param {boolean} [link.semanticStrokeWidth=false] - when set to true all links will have

* ```
* @param {string} [link.highlightColor='#d3d3d3'] - links' color in highlight state.
* @param {string} [link.type='STRAIGHT'] - the type of line to draw, available types at this point are:

@@ -147,3 +181,2 @@ * - "STRAIGHT" <small>(default)</small> - a straight line.

* <br/>
* <img src="https://github.com/danielcaldas/react-d3-graph/blob/master/docs/rd3g-bend.gif?raw=true" width="820" height="480"/>
*

@@ -169,2 +202,3 @@ * @example

collapsible: false,
directed: false,
height: 400,

@@ -176,2 +210,4 @@ highlightDegree: 1,

minZoom: 0.1,
focusZoom: 1,
focusAnimationDuration: 0.75,
nodeHighlightBehavior: false,

@@ -205,3 +241,4 @@ panAndZoom: false,

svg: '',
symbolType: 'circle'
symbolType: 'circle',
viewGenerator: null
},

@@ -211,2 +248,3 @@ link: {

highlightColor: '#d3d3d3',
mouseCursor: 'pointer',
opacity: 1,

@@ -213,0 +251,0 @@ semanticStrokeWidth: false,

@@ -1,2 +0,1 @@

/*eslint-disable max-lines*/
/**

@@ -36,2 +35,3 @@ * @module Graph/helper

import { buildLinkPathDefinition } from '../link/link.helper';
import { getMarkerId } from '../marker/marker.helper';

@@ -98,2 +98,3 @@ const NODE_PROPS_WHITELIST = ['id', 'highlighted', 'x', 'y', 'index', 'vy', 'vx'];

* @param {Array.<Link>} graphLinks - an array of all graph links.
* @param {Object} config - the graph config.
* @returns {Object.<string, Object>} an object containing a matrix of connections of the graph, for each nodeId,

@@ -103,3 +104,3 @@ * there is an object that maps adjacent nodes ids (string) and their values (number).

*/
function _initializeLinks(graphLinks) {
function _initializeLinks(graphLinks, config) {
return graphLinks.reduce((links, l) => {

@@ -117,5 +118,10 @@ const source = l.source.id || l.source;

// TODO: If the graph is directed this should be adapted
links[source][target] = links[target][source] = l.value || 1;
const value = config.collapsible && l.isHidden ? 0 : l.value || 1;
links[source][target] = value;
if (!config.directed) {
links[target][source] = value;
}
return links;

@@ -163,10 +169,20 @@ }, {});

* @param {Array.<Object>} d3Links - all d3Links.
* @param {Object} config - same as {@link #buildGraph|config in buildGraph}.
* @param {Object} state - Graph component current state (same format as returned object on this function).
* @returns {Object} a d3Link.
* @memberof Graph/helper
*/
function _mapDataLinkToD3Link(link, index, d3Links = []) {
function _mapDataLinkToD3Link(link, index, d3Links = [], config, state = {}) {
const d3Link = d3Links[index];
if (d3Link) {
return d3Link;
const toggledDirected = state.config && state.config.directed && config.directed !== state.config.directed;
// every time we toggle directed config all links should be visible again
if (toggledDirected) {
return { ...d3Link, isHidden: false };
}
// every time we disable collapsible (collapsible is false) all links should be visible again
return config.collapsible ? d3Link : { ...d3Link, isHidden: false };
}

@@ -282,15 +298,16 @@

const markerId = config.directed ? getMarkerId(highlight, transform, config) : null;
return {
markerId,
d,
source,
target,
x1,
y1,
x2,
y2,
strokeWidth,
stroke,
mouseCursor: config.link.mouseCursor,
className: CONST.LINK_CLASS_NAME,
opacity,
onClickLink: linkCallbacks.onClickLink,
onRightClickLink: linkCallbacks.onRightClickLink,
onMouseOverLink: linkCallbacks.onMouseOverLink,

@@ -324,3 +341,3 @@ onMouseOutLink: linkCallbacks.onMouseOutLink

let stroke = config.node.strokeColor;
let stroke = node.strokeColor || config.node.strokeColor;

@@ -331,2 +348,8 @@ if (highlight && config.node.highlightStrokeColor !== CONST.KEYWORDS.SAME) {

let label = node[config.node.labelProperty] || node.id;
if (typeof config.node.labelProperty === 'function') {
label = config.node.labelProperty(node);
}
const t = 1 / transform;

@@ -352,4 +375,5 @@ const nodeSize = node.size || config.node.size;

id: node.id,
label: node[config.node.labelProperty] || node.id,
label,
onClickNode: nodeCallbacks.onClickNode,
onRightClickNode: nodeCallbacks.onRightClickNode,
onMouseOverNode: nodeCallbacks.onMouseOverNode,

@@ -418,3 +442,3 @@ onMouseOut: nodeCallbacks.onMouseOut,

const configUpdated =
newConfig && !utils.isObjectEmpty(newConfig) && !utils.isDeepEqual(newConfig, currentState.config);
newConfig && !utils.isEmptyObject(newConfig) && !utils.isDeepEqual(newConfig, currentState.config);
const d3ConfigUpdated = newConfig && newConfig.d3 && !utils.isDeepEqual(newConfig.d3, currentState.config.d3);

@@ -448,3 +472,3 @@

),
links: data.links.map((l, index) => _mapDataLinkToD3Link(l, index, state && state.d3Links))
links: data.links.map((l, index) => _mapDataLinkToD3Link(l, index, state && state.d3Links, config, state))
};

@@ -460,3 +484,3 @@ } else {

let nodes = _initializeNodes(graph.nodes);
let links = _initializeLinks(graph.links); // matrix of graph connections
let links = _initializeLinks(graph.links, newConfig); // matrix of graph connections
const { nodes: d3Nodes, links: d3Links } = graph;

@@ -466,2 +490,10 @@ const formatedId = id.replace(/ /g, '_');

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

@@ -514,98 +546,23 @@ id: formatedId,

/**
* This function disconnects all the connections from leaf -> parent.
* @param {string} targetNodeId - The id of the node from which to disconnect the leaf nodes
* @param {Object.<string, number>} originalConnections - An object containing a matrix of connections of the nodes.
* @param {Array} d3Links - An array containing all the d3 links.
* @returns {Object.<string, number>} - Contains the new links and d3Links.
* 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.
* @memberof Graph/helper
*/
function disconnectLeafNodeConnections(targetNodeId, originalConnections, d3Links) {
const leafNodesToToggle = getLeafNodeConnections(targetNodeId, originalConnections);
const toggledLeafNodes = Object.keys(leafNodesToToggle).reduce((newLeafNodeConnections, leafNodeId) => {
// toggle connections from Leaf node to Parent node
newLeafNodeConnections[leafNodeId] = toggleNodeConnection(targetNodeId, originalConnections[leafNodeId]);
function getCenterAndZoomTransformation(d3Node, config) {
if (!d3Node) {
return;
}
return newLeafNodeConnections;
}, {});
const { width, height, focusZoom } = config;
const toggledLeafNodesList = Object.keys(toggledLeafNodes);
const toggledD3Links = d3Links.reduce((allD3Links, currentD3Link) => {
const { source, target } = currentD3Link;
const isLeafNode = toggledLeafNodesList.some(
leafNodeId => leafNodeId === `${source.id}` || leafNodeId === `${target.id}`
);
isLeafNode
? allD3Links.push({ ...currentD3Link, isHidden: !currentD3Link.isHidden })
: allD3Links.push(currentD3Link);
return allD3Links;
}, []);
return {
d3Links: toggledD3Links,
links: {
...originalConnections,
...toggledLeafNodes
}
};
return `
translate(${width / 2}, ${height / 2})
scale(${focusZoom})
translate(${-d3Node.x}, ${-d3Node.y})
`;
}
/**
* This function toggles the value for a given node connection (1 -> 0 and vice-versa).
* @param {string} targetNodeId - The id of the node which to toggle
* @param {Object.<string, number>} allConnections - An object containing a matrix of connections of the node
* where we want to toggle the connection (destinations/targets).
* @returns {Object.<string, number>} - Contains the new connections with the target node toggled.
* @memberof Graph/helper
*/
function toggleNodeConnection(targetNodeId, allConnections) {
const newConnection = {
[targetNodeId]: allConnections[targetNodeId] === 1 ? 0 : 1
};
return { ...allConnections, ...newConnection };
}
/**
* Based on a starting node (ID) and all the current connections between all the nodes.
* Find the leaf node connections of that starting node.
* @param {string} startingNodeId - The id of the node where the "search" should be started.
* @param {Object.<string, number>} currentConnections - An object containing a matrix of connections of the nodes.
* @returns {Object.<string, number>} - Contains the connections to leaf nodes based on the given starting node.
* @memberof Graph/helper
*/
function getLeafNodeConnections(startingNodeId, currentConnections) {
const startingNodeConnections = currentConnections[startingNodeId];
const startingNodeConnectionsList = Object.keys(startingNodeConnections);
return startingNodeConnectionsList.reduce((allLeafNodes, candidateLeafId) => {
const candidateLeafConnections = currentConnections[candidateLeafId];
const candidateLeafConnectionList = Object.keys(candidateLeafConnections);
const isLeafNode = candidateLeafConnectionList.length === 1;
if (isLeafNode) {
allLeafNodes[candidateLeafId] = candidateLeafConnections;
}
return allLeafNodes;
}, {});
}
/**
* Given a node and the connections matrix, give the cardinality of the node.
*
* i.e.: Taking into account the node is connected to nothing, it amounts to 0.
* Being connected to three nodes, it amounts to 3.
* @param {string} nodeId - The id of the node to get the cardinality of
* @param {Object.<string, number>} linksMatrix - An object containing a matrix of connections of the nodes.
* @returns {number} - Contains the cardinality of the asked node.
* @memberof Graph/helper
*/
function getNodeCardinality(nodeId, linksMatrix) {
const nodeConnectivityList = Object.values(linksMatrix[nodeId] || []);
return nodeConnectivityList.reduce((cardinality, nodeConnectivity) => cardinality + nodeConnectivity, 0);
}
export {

@@ -616,8 +573,5 @@ buildLinkProps,

checkForGraphElementsChanges,
disconnectLeafNodeConnections,
getLeafNodeConnections,
getNodeCardinality,
initializeGraphState,
toggleNodeConnection,
updateNodeHighlightedValue
updateNodeHighlightedValue,
getCenterAndZoomTransformation
};

@@ -12,4 +12,5 @@ import React from 'react';

import * as collapseHelper from './collapse.helper';
import * as graphHelper from './graph.helper';
import * as graphRenderer from './graph.renderer';
import * as graphHelper from './graph.helper';
import utils from '../../utils';

@@ -53,2 +54,6 @@

* // graph event callbacks
* const onClickGraph = function() {
* window.alert('Clicked the graph background');
* };
*
* const onClickNode = function(nodeId) {

@@ -58,2 +63,6 @@ * window.alert('Clicked node ${nodeId}');

*
* const onRightClickNode = function(event, nodeId) {
* window.alert('Right clicked node ${nodeId}');
* };
*
* const onMouseOverNode = function(nodeId) {

@@ -71,2 +80,6 @@ * window.alert(`Mouse over node ${nodeId}`);

*
* const onRightClickLink = function(event, source, target) {
* window.alert('Right clicked link between ${source} and ${target}');
* };
*
* const onMouseOverLink = function(source, target) {

@@ -84,4 +97,7 @@ * window.alert(`Mouse over in link between ${source} and ${target}`);

* config={myConfig}
* onClickGraph={onClickGraph}
* onClickNode={onClickNode}
* onRightClickNode={onRightClickNode}
* onClickLink={onClickLink}
* onRightClickLink={onRightClickLink}
* onMouseOverNode={onMouseOverNode}

@@ -157,3 +173,8 @@ * onMouseOutNode={onMouseOutNode}

*/
_onDragStart = () => this.pauseSimulation();
_onDragStart = () => {
this.pauseSimulation();
if (this.state.enableFocusAnimation) {
this.setState({ enableFocusAnimation: false });
}
};

@@ -175,5 +196,6 @@ /**

* @param {Object} state - new state to pass on.
* @param {Function} [cb] - optional callback to fed in to {@link setState()|https://reactjs.org/docs/react-component.html#setstate}.
* @returns {undefined}
*/
_tick = (state = {}) => this.setState(state);
_tick = (state = {}, cb) => (cb ? this.setState(state, cb) : this.setState(state));

@@ -302,2 +324,3 @@ /**

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

@@ -331,2 +354,7 @@ }

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({

@@ -338,3 +366,6 @@ ...state,

newGraphElements,
transform
transform,
focusedNodeId,
enableFocusAnimation,
focusTransformation
});

@@ -379,19 +410,82 @@ }

if (this.state.config.collapsible) {
const disconnectedLeafNodesPartialState = graphHelper.disconnectLeafNodeConnections(
const leafConnections = collapseHelper.getTargetLeafConnections(
clickedNodeId,
this.state.links,
this.state.d3Links
this.state.config
);
const links = collapseHelper.toggleLinksMatrixConnections(
this.state.links,
leafConnections,
this.state.config
);
const d3Links = collapseHelper.toggleLinksConnections(this.state.d3Links, links);
this._tick({ ...disconnectedLeafNodesPartialState });
this._tick(
{
links,
d3Links
},
() => this.props.onClickNode && this.props.onClickNode(clickedNodeId)
);
} else {
this.props.onClickNode && this.props.onClickNode(clickedNodeId);
}
};
this.props.onClickNode && this.props.onClickNode(clickedNodeId);
/**
* Calls the callback passed to the component.
* @param {Object} e - The event of onClick handler.
* @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
if (
e.target.tagName.toUpperCase() === 'SVG' &&
e.target.attributes.name.value === `svg-container-${this.state.id}`
) {
this.props.onClickGraph && this.props.onClickGraph();
}
};
/**
* 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 } = graphRenderer.buildGraph(
const { nodes, links, defs } = graphRenderer.buildGraph(
this.state.nodes,
{
onClickNode: this.onClickNode,
onRightClickNode: this.props.onRightClickNode,
onMouseOverNode: this.onMouseOverNode,

@@ -404,2 +498,3 @@ onMouseOut: this.onMouseOutNode

onClickLink: this.props.onClickLink,
onRightClickLink: this.props.onRightClickLink,
onMouseOverLink: this.onMouseOverLink,

@@ -419,6 +514,9 @@ onMouseOutLink: this.onMouseOutLink

const containerProps = this._generateFocusAnimationProps();
return (
<div id={`${this.state.id}-${CONST.GRAPH_WRAPPER_ID}`}>
<svg style={svgStyle}>
<g id={`${this.state.id}-${CONST.GRAPH_CONTAINER_ID}`}>
<svg name={`svg-container-${this.state.id}`} style={svgStyle} onClick={this.onClickGraph}>
{defs}
<g id={`${this.state.id}-${CONST.GRAPH_CONTAINER_ID}`} {...containerProps}>
{links}

@@ -425,0 +523,0 @@ {nodes}

@@ -9,6 +9,9 @@ /**

import CONST from './graph.const';
import { MARKERS, MARKER_SMALL_SIZE, MARKER_MEDIUM_OFFSET, MARKER_LARGE_OFFSET } from '../marker/marker.const';
import Link from '../link/Link';
import Node from '../node/Node';
import { buildLinkProps, buildNodeProps, getNodeCardinality } from './graph.helper';
import Marker from '../marker/Marker';
import { buildLinkProps, buildNodeProps } from './graph.helper';
import { isNodeVisible } from './collapse.helper';

@@ -25,7 +28,13 @@ /**

* @param {number} transform - value that indicates the amount of zoom transformation.
* @returns {Object[]} returns the generated array of Link components.
* @memberof Graph/renderer
* @returns {Array.<Object>} returns the generated array of Link components.
* @memberof Graph/helper
*/
function _buildLinks(nodes, links, linksMatrix, config, linkCallbacks, highlightedNode, highlightedLink, transform) {
return links.filter(({ isHidden }) => !isHidden).map(link => {
let outLinks = links;
if (config.collapsible) {
outLinks = outLinks.filter(({ isHidden }) => !isHidden);
}
return outLinks.map(link => {
const { source, target } = link;

@@ -62,4 +71,4 @@ // FIXME: solve this source data inconsistency later

* @param {Object.<string, Object>} linksMatrix - the matrix of connections of the graph
* @returns {Object} returns the generated array of nodes components
* @memberof Graph/renderer
* @returns {Array.<Object>} returns the generated array of node components
* @memberof Graph/helper
*/

@@ -70,3 +79,3 @@ function _buildNodes(nodes, nodeCallbacks, config, highlightedNode, highlightedLink, transform, linksMatrix) {

if (config.collapsible) {
outNodes = outNodes.filter(nodeId => getNodeCardinality(nodeId, linksMatrix) > 0);
outNodes = outNodes.filter(nodeId => isNodeVisible(nodeId, linksMatrix));
}

@@ -89,2 +98,44 @@

/**
* Builds graph defs (for now markers, but we could also have gradients for instance).
* NOTE: defs are static svg graphical objects, thus we only need to render them once, the result
* is cached on the 1st call and from there we simply return the cached jsx.
* @returns {Function} memoized build definitions function.
* @memberof Graph/helper
*/
function _buildDefs() {
let cachedDefs;
return config => {
if (cachedDefs) {
return cachedDefs;
}
const small = MARKER_SMALL_SIZE;
const medium = small + MARKER_MEDIUM_OFFSET * config.maxZoom / 3;
const large = small + MARKER_LARGE_OFFSET * config.maxZoom / 3;
cachedDefs = (
<defs>
<Marker id={MARKERS.MARKER_S} refX={small} fill={config.link.color} />
<Marker id={MARKERS.MARKER_SH} refX={small} fill={config.link.highlightColor} />
<Marker id={MARKERS.MARKER_M} refX={medium} fill={config.link.color} />
<Marker id={MARKERS.MARKER_MH} refX={medium} fill={config.link.highlightColor} />
<Marker id={MARKERS.MARKER_L} refX={large} fill={config.link.color} />
<Marker id={MARKERS.MARKER_LH} refX={large} fill={config.link.highlightColor} />
</defs>
);
return cachedDefs;
};
}
/**
* Memoized reference for _buildDefs.
* @param {Object} config - an object containing rd3g consumer defined configurations {@link #config config} for the graph.
* @returns {Object} graph reusable objects [defs](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs).
* @memberof Graph/helper
*/
const _memoizedBuildDefs = _buildDefs();
/**
* Method that actually is exported an consumed by Graph component in order to build all Nodes and Link

@@ -152,3 +203,4 @@ * components.

transform
)
),
defs: _memoizedBuildDefs(config)
};

@@ -155,0 +207,0 @@ }

@@ -51,3 +51,3 @@ /**

* *CURVE_SMOOTH* type inspired by {@link http://bl.ocks.org/mbostock/1153292|mbostock - Mobile Patent Suits}.
* @param {string} type type of curve to get radius strategy from.
* @param {string} [type=LINE_TYPES.STRAIGHT] type of curve to get radius strategy from.
* @returns {Function} a function that calculates a radius

@@ -58,3 +58,3 @@ * to match curve type expectation. Fallback is the straight line.

function getRadiusStrategy(type) {
return RADIUS_STRATEGIES[type];
return RADIUS_STRATEGIES[type] || RADIUS_STRATEGIES[LINE_TYPES.STRAIGHT];
}

@@ -61,0 +61,0 @@

@@ -10,2 +10,6 @@ import React from 'react';

*
* const onRightClickLink = function(source, target) {
* window.alert(`Right clicked link between ${source} and ${target}`);
* };
*
* const onMouseOverLink = function(source, target) {

@@ -23,6 +27,3 @@ * window.alert(`Mouse over in link between ${source} and ${target}`);

* target='idTargetNode'
* x1=22
* y1=22
* x2=22
* y2=22
* markerId='marker-small'
* strokeWidth=1.5

@@ -32,3 +33,5 @@ * stroke='green'

* opacity=1
* mouseCursor='pointer'
* onClickLink={onClickLink}
* onRightClickLink={onRightClickLink}
* onMouseOverLink={onMouseOverLink}

@@ -45,2 +48,10 @@ * onMouseOutLink={onMouseOutLink} />

/**
* Handle link right click event.
* @param {Object} event - native event.
* @returns {undefined}
*/
handleOnRightClickLink = event =>
this.props.onRightClickLink && this.props.onRightClickLink(event, this.props.source, this.props.target);
/**
* Handle mouse over link event.

@@ -64,3 +75,4 @@ * @returns {undefined}

opacity: this.props.opacity,
fill: 'none'
fill: 'none',
cursor: this.props.mouseCursor
};

@@ -72,13 +84,14 @@

onClick: this.handleOnClickLink,
onContextMenu: this.handleOnRightClickLink,
onMouseOut: this.handleOnMouseOutLink,
onMouseOver: this.handleOnMouseOverLink,
style: lineStyle,
x1: this.props.x1,
x2: this.props.x2,
y1: this.props.y1,
y2: this.props.y2
style: lineStyle
};
if (this.props.markerId) {
lineProps.markerEnd = `url(#${this.props.markerId})`;
}
return <path {...lineProps} />;
}
}

@@ -14,2 +14,6 @@ import React from 'react';

*
* const onRightClickNode = function(nodeId) {
* window.alert('Right clicked node', nodeId);
* }
*
* const onMouseOverNode = function(nodeId) {

@@ -43,2 +47,3 @@ * window.alert('Mouse over node', nodeId);

* onClickNode={onClickNode}
* onRightClickNode={onRightClickNode}
* onMouseOverNode={onMouseOverNode}

@@ -55,2 +60,9 @@ * onMouseOutNode={onMouseOutNode} />

/**
* Handle right click on the node.
* @param {Object} event - native event.
* @returns {undefined}
*/
handleOnRightClickNode = event => this.props.onRightClickNode && this.props.onRightClickNode(event, this.props.id);
/**
* Handle mouse over node event.

@@ -71,2 +83,3 @@ * @returns {undefined}

onClick: this.handleOnClickNode,
onContextMenu: this.handleOnRightClickNode,
onMouseOut: this.handleOnMouseOutNode,

@@ -73,0 +86,0 @@ onMouseOver: this.handleOnMouseOverNode,

@@ -20,3 +20,3 @@ /**

function _isPropertyNestedObject(o, k) {
return o.hasOwnProperty(k) && typeof o[k] === 'object' && o[k] !== null && !isObjectEmpty(o[k]);
return o.hasOwnProperty(k) && typeof o[k] === 'object' && o[k] !== null && !isEmptyObject(o[k]);
}

@@ -39,3 +39,3 @@

if ((isObjectEmpty(o1) && !isObjectEmpty(o2)) || (!isObjectEmpty(o1) && isObjectEmpty(o2))) {
if ((isEmptyObject(o1) && !isEmptyObject(o2)) || (!isEmptyObject(o1) && isEmptyObject(o2))) {
return false;

@@ -57,3 +57,3 @@ }

} else {
const r = (isObjectEmpty(o1[k]) && isObjectEmpty(o2[k])) || (o2.hasOwnProperty(k) && o2[k] === o1[k]);
const r = (isEmptyObject(o1[k]) && isEmptyObject(o2[k])) || (o2.hasOwnProperty(k) && o2[k] === o1[k]);

@@ -78,3 +78,3 @@ diffs.push(r);

*/
function isObjectEmpty(o) {
function isEmptyObject(o) {
return !!o && typeof o === 'object' && !Object.keys(o).length;

@@ -97,3 +97,3 @@ }

if (Object.keys(o1 || {}).length === 0) {
return o2 && !isObjectEmpty(o2) ? o2 : {};
return o2 && !isEmptyObject(o2) ? o2 : {};
}

@@ -163,3 +163,3 @@

isDeepEqual,
isObjectEmpty,
isEmptyObject,
merge,

@@ -166,0 +166,0 @@ pick,

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc