react-d3-graph
Advanced tools
Comparing version 2.4.1 to 2.5.0
# Change Log | ||
## [2.5.0](https://github.com/danielcaldas/react-d3-graph/tree/2.5.0) | ||
[Full Changelog](https://github.com/danielcaldas/react-d3-graph/compare/2.4.1...2.5.0) | ||
**Implemented enhancements:** | ||
- make node.size accept both height and width [\#336](https://github.com/danielcaldas/react-d3-graph/issues/336) | ||
**Fixed bugs:** | ||
- Passing an empty data.links array throws a warning [\#323](https://github.com/danielcaldas/react-d3-graph/issues/323) | ||
- renderLabel params are not working for single node [\#322](https://github.com/danielcaldas/react-d3-graph/issues/322) | ||
- The release version does not contain some fixes [\#314](https://github.com/danielcaldas/react-d3-graph/issues/314) | ||
**Closed issues:** | ||
- Docs missing collapsible sandbox example [\#337](https://github.com/danielcaldas/react-d3-graph/issues/337) | ||
- Multiple Edges between 2 nodes [\#335](https://github.com/danielcaldas/react-d3-graph/issues/335) | ||
- Ability to display node labels in different positions relative to the node center [\#299](https://github.com/danielcaldas/react-d3-graph/issues/299) | ||
**Merged pull requests:** | ||
- Added ability to configure a node's width and height separately [\#342](https://github.com/danielcaldas/react-d3-graph/pull/342) ([terahn](https://github.com/terahn)) | ||
- Bump websocket-extensions from 0.1.3 to 0.1.4 [\#331](https://github.com/danielcaldas/react-d3-graph/pull/331) ([dependabot[bot]](https://github.com/apps/dependabot)) | ||
- Misc improvements cleanup from previous PRs [\#327](https://github.com/danielcaldas/react-d3-graph/pull/327) ([danielcaldas](https://github.com/danielcaldas)) | ||
- Add GitHub Actions Workflow for library CI [\#326](https://github.com/danielcaldas/react-d3-graph/pull/326) ([danielcaldas](https://github.com/danielcaldas)) | ||
- Fix/misc documentation sandbox improvements [\#315](https://github.com/danielcaldas/react-d3-graph/pull/315) ([danielcaldas](https://github.com/danielcaldas)) | ||
- Feature/initial zoom [\#289](https://github.com/danielcaldas/react-d3-graph/pull/289) ([Morta1](https://github.com/Morta1)) | ||
- Reorganizing the computation of arrows and links for circle nodes [\#271](https://github.com/danielcaldas/react-d3-graph/pull/271) ([antoninklopp](https://github.com/antoninklopp)) | ||
## [2.4.1](https://github.com/danielcaldas/react-d3-graph/tree/2.4.1) | ||
@@ -4,0 +34,0 @@ |
@@ -15,2 +15,4 @@ "use strict"; | ||
var _graph2 = require("./graph.helper"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
@@ -24,2 +26,4 @@ | ||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } | ||
/** | ||
@@ -75,12 +79,2 @@ * Get the correct node opacity in order to properly make decisions based on context such as currently highlighted node. | ||
var type = link.type || config.link.type; | ||
var d = (0, _link.buildLinkPathDefinition)({ | ||
source: { | ||
x: x1, | ||
y: y1 | ||
}, | ||
target: { | ||
x: x2, | ||
y: y2 | ||
} | ||
}, type); | ||
var mainNodeParticipates = false; | ||
@@ -143,2 +137,13 @@ | ||
var normalizedNodeCoordinates = (0, _graph2.getNormalizedNodeCoordinates)({ | ||
source: { | ||
x: x1, | ||
y: y1 | ||
}, | ||
target: { | ||
x: x2, | ||
y: y2 | ||
} | ||
}, nodes, config, strokeWidth); | ||
var d = (0, _link.buildLinkPathDefinition)(normalizedNodeCoordinates, type); | ||
return { | ||
@@ -213,6 +218,23 @@ className: _graph["default"].LINK_CLASS_NAME, | ||
var nodeSize = node.size || config.node.size; | ||
var offset; | ||
var isSizeNumericValue = _typeof(nodeSize) !== "object"; | ||
if (isSizeNumericValue) { | ||
offset = nodeSize; | ||
} else if (labelPosition === "top" || labelPosition === "bottom") { | ||
offset = nodeSize.height; | ||
} else { | ||
nodeSize.width; | ||
} | ||
var fontSize = highlight ? config.node.highlightFontSize : config.node.fontSize; | ||
var dx = fontSize * t + nodeSize / 100 + 1.5; | ||
var dx = fontSize * t + offset / 100 + 1.5; | ||
var svg = node.svg || config.node.svg; | ||
var fontColor = node.fontColor || config.node.fontColor; | ||
var renderLabel = config.node.renderLabel; | ||
if (node.renderLabel !== undefined && typeof node.renderLabel === "boolean") { | ||
renderLabel = node.renderLabel; | ||
} | ||
return _objectSpread({}, node, { | ||
@@ -233,4 +255,7 @@ className: _graph["default"].NODE_CLASS_NAME, | ||
overrideGlobalViewGenerator: !node.viewGenerator && node.svg, | ||
renderLabel: node.renderLabel || config.node.renderLabel, | ||
size: nodeSize * t, | ||
renderLabel: renderLabel, | ||
size: isSizeNumericValue ? nodeSize * t : { | ||
height: nodeSize.height * t, | ||
width: nodeSize.width * t | ||
}, | ||
stroke: stroke, | ||
@@ -237,0 +262,0 @@ strokeWidth: strokeWidth * t, |
@@ -53,3 +53,3 @@ "use strict"; | ||
* @param {boolean} [collapsible=false] - <a id="collapsible" href="#collapsible">🔗</a> 🚅🚅🚅 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. <b>NOTE</b>: At this moment | ||
* To see an example of this behavior you can access <a href="https://danielcaldas.github.io/react-d3-graph/sandbox/index.html?data=marvel" target="_blank" title="sandbox collapsible example">this sandbox link</a> that has a specific set up to experiment this feature. <b>NOTE</b>: At this moment | ||
* nodes without connections (orphan nodes) are not rendered when this property is activated (see <a target="_blank" href="https://github.com/danielcaldas/react-d3-graph/issues/129">GitHub issue #129</a>). | ||
@@ -89,2 +89,3 @@ * </br> | ||
* the value the more the less highlighted nodes will be visible (related to <i>nodeHighlightBehavior</i>). | ||
* @param {number} [initialZoom=null] - <a id="max-zoom" href="#initial-zoom">🔗</a> initial zoom that can be set on the graph. | ||
* @param {number} [maxZoom=8] - <a id="max-zoom" href="#max-zoom">🔗</a> max zoom that can be performed against the graph. | ||
@@ -111,3 +112,3 @@ * @param {number} [minZoom=0.1] - <a id="min-zoom" href="#min-zoom">🔗</a> min zoom that can be performed against the graph. | ||
* @param {number} [d3.linkStrength=1] - <a id="d3-link-strength" href="#d3-link-strength">🔗</a> <a target="_blank" href="https://github.com/d3/d3-force#link_strength">see d3-force link.strength</a> | ||
* @param {number} [d3.disableLinkForce=false] - <a id="d3-disable-link-force" href="#d3-disable-link-force">🔗</a> ⚠️🧪EXPERIMENTAL🧪⚠️ it completely disables d3 force link and simulation to re-trigger so that one can obtain | ||
* @param {boolean} [d3.disableLinkForce=false] - <a id="d3-disable-link-force" href="#d3-disable-link-force">🔗</a> ⚠️🧪EXPERIMENTAL🧪⚠️ it completely disables d3 force link and simulation to re-trigger so that one can obtain | ||
* precise render of node positions as described by the author <a target="_blank" href="https://github.com/antoninklopp">@antoninklopp</a> in <a target="_blank" href="https://github.com/danielcaldas/react-d3-graph/pull/278">the Pull Request description</a>. | ||
@@ -158,3 +159,13 @@ * </br> | ||
* graph. | ||
* @param {number} [node.size=200] - <a id="node-size" href="#node-size">🔗</a> 🔍🔍🔍 defines the size of all nodes. | ||
* @param {number|Object} [node.size=200] - <a id="node-size" href="#node-size">🔗</a> 🔍🔍🔍 defines the size of all nodes. When set to a number, the node will have equal height and width.</br> | ||
* This can also be an object with a height and width property <b>when using custom nodes</b>. | ||
* ```javascript | ||
* size: 200 | ||
* // or | ||
* size: { | ||
* height: 200, | ||
* width: 300, | ||
* } | ||
* ``` | ||
* The actual node dimensions (in px) rendered on screen will be the size value divided by 10. For example, a node size of 200 will result in a node with a height and width of 20px. | ||
* @param {string} [node.strokeColor="none"] - <a id="node-stroke-color" href="#node-stroke-color">🔗</a> 🔍🔍🔍 this is the stroke color that will be applied to the node if no <b>strokeColor property</b> is found inside the node itself (yes <b>you can pass a property "strokeColor" inside the node and that stroke color will override this default one</b>). | ||
@@ -252,2 +263,3 @@ * @param {number} [node.strokeWidth=1.5] - <a id="node-stroke-width" href="#node-stroke-width">🔗</a> 🔍🔍🔍 the width of the all node strokes. | ||
minZoom: 0.1, | ||
initialZoom: null, | ||
nodeHighlightBehavior: false, | ||
@@ -254,0 +266,0 @@ panAndZoom: false, |
@@ -12,2 +12,3 @@ "use strict"; | ||
exports.updateNodeHighlightedValue = updateNodeHighlightedValue; | ||
exports.getNormalizedNodeCoordinates = getNormalizedNodeCoordinates; | ||
@@ -212,3 +213,3 @@ var _d3Force = require("d3-force"); | ||
* INVALID_LINKS - if links point to nonexistent nodes | ||
* INSUFFICIENT_LINKS - if no links are provided | ||
* INSUFFICIENT_LINKS - if no links are provided (not even empty Array) | ||
* @returns {undefined} | ||
@@ -224,3 +225,3 @@ * @memberof Graph/helper | ||
if (!data.links || !data.links.length) { | ||
if (!data.links) { | ||
(0, _utils.logWarning)("Graph", _err["default"].INSUFFICIENT_LINKS); | ||
@@ -493,2 +494,86 @@ data.links = []; | ||
}; | ||
} | ||
/** | ||
* Computes the normalized vector from a vector. | ||
* @param {Object} vector a 2D vector with x and y components | ||
* @param {number} vector.x x coordinate | ||
* @param {number} vector.y y coordinate | ||
* @returns {Object} normalized vector | ||
*/ | ||
function normalize(vector) { | ||
var norm = Math.sqrt(Math.pow(vector.x, 2) + Math.pow(vector.y, 2)); | ||
return { | ||
x: vector.x / norm, | ||
y: vector.y / norm | ||
}; | ||
} | ||
/** | ||
* Computes new node coordinates to make arrowheads point at nodes. | ||
* Arrow configuration is only available for circles. | ||
* @param {Object} node - the couple of nodes we need to compute new coordinates | ||
* @param {Object} node.source - node source | ||
* @param {Object} node.target - node target | ||
* @param {Object.<string, Object>} nodes - same as {@link #graphrenderer|nodes in renderGraph}. | ||
* @param {Object} config - same as {@link #graphrenderer|config in renderGraph}. | ||
* @param {number} strokeWidth width of the link stroke | ||
* @returns {Object} new nodes coordinates | ||
*/ | ||
function getNormalizedNodeCoordinates(_ref2, nodes, config, strokeWidth) { | ||
var _config$node, _config$node2; | ||
var _ref2$source = _ref2.source, | ||
source = _ref2$source === void 0 ? {} : _ref2$source, | ||
_ref2$target = _ref2.target, | ||
target = _ref2$target === void 0 ? {} : _ref2$target; | ||
if ((_config$node = config.node) === null || _config$node === void 0 ? void 0 : _config$node.viewGenerator) { | ||
return { | ||
source: source, | ||
target: target | ||
}; | ||
} | ||
var x1 = source.x, | ||
y1 = source.y; | ||
var x2 = target.x, | ||
y2 = target.y; | ||
switch ((_config$node2 = config.node) === null || _config$node2 === void 0 ? void 0 : _config$node2.symbolType) { | ||
case _graph2["default"].SYMBOLS.CIRCLE: | ||
{ | ||
var _nodes$source; | ||
var directionVector = normalize({ | ||
x: x2 - x1, | ||
y: y2 - y1 | ||
}); | ||
var strokeSize = strokeWidth * Math.min(config.link.markerWidth, config.link.markerHeight); | ||
var nodeSize = (nodes === null || nodes === void 0 ? void 0 : (_nodes$source = nodes[source]) === null || _nodes$source === void 0 ? void 0 : _nodes$source.size) || config.node.size; // cause this is a circle and A = pi * r^2 | ||
// we multiply by 0.95, because if we don't the link is not melting properly | ||
nodeSize = Math.sqrt(nodeSize / Math.PI) * 0.95; // points from the source, we move them not to begin in the circle but outside | ||
x1 += nodeSize * directionVector.x; | ||
y1 += nodeSize * directionVector.y; // points from the target, we move the by the size of the radius of the circle + the size of the arrow | ||
x2 -= (nodeSize + (config.directed ? strokeSize : 0)) * directionVector.x; | ||
y2 -= (nodeSize + (config.directed ? strokeSize : 0)) * directionVector.y; | ||
break; | ||
} | ||
} | ||
return { | ||
source: { | ||
x: x1, | ||
y: y1 | ||
}, | ||
target: { | ||
x: x2, | ||
y: y2 | ||
} | ||
}; | ||
} |
@@ -306,3 +306,12 @@ "use strict"; | ||
_defineProperty(_assertThisInitialized(_this), "_zoomConfig", function () { | ||
(0, _d3Selection.select)("#".concat(_this.state.id, "-").concat(_graph["default"].GRAPH_WRAPPER_ID)).call((0, _d3Zoom.zoom)().scaleExtent([_this.state.config.minZoom, _this.state.config.maxZoom]).on("zoom", _this._zoomed)).on("dblclick.zoom", null); | ||
var selector = (0, _d3Selection.select)("#".concat(_this.state.id, "-").concat(_graph["default"].GRAPH_WRAPPER_ID)); | ||
var zoomObject = (0, _d3Zoom.zoom)().scaleExtent([_this.state.config.minZoom, _this.state.config.maxZoom]).on("zoom", _this._zoomed); | ||
if (_this.state.config.initialZoom !== null) { | ||
zoomObject.scaleTo(selector, _this.state.config.initialZoom); | ||
} // avoid double click on graph to trigger zoom | ||
// for more details consult: https://github.com/danielcaldas/react-d3-graph/pull/202 | ||
selector.call(zoomObject).on("dblclick.zoom", null); | ||
}); | ||
@@ -309,0 +318,0 @@ |
@@ -26,2 +26,4 @@ "use strict"; | ||
var _marker2 = require("../marker/marker.helper"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
@@ -126,5 +128,7 @@ | ||
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; | ||
var _getMarkerSize = (0, _marker2.getMarkerSize)(config), | ||
small = _getMarkerSize.small, | ||
medium = _getMarkerSize.medium, | ||
large = _getMarkerSize.large; | ||
var markerProps = { | ||
@@ -131,0 +135,0 @@ markerWidth: config.link.markerWidth, |
@@ -6,2 +6,3 @@ "use strict"; | ||
}); | ||
exports.getMarkerSize = getMarkerSize; | ||
exports.getMarkerId = void 0; | ||
@@ -11,2 +12,6 @@ | ||
var _graph = _interopRequireDefault(require("../graph/graph.const")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
/** | ||
@@ -114,3 +119,33 @@ * @module Marker/helper | ||
var getMarkerId = _memoizedComputeMarkerId(); | ||
/** | ||
* Computes the three marker sizes | ||
* For supported shapes in {@link Graph/helper/getNormalizedNodeCoordinates}, the function should return 0, | ||
* to be able to control more accurately nodes and arrows sizes and positions in directional graphs. | ||
* @param {Object} config - the graph config object. | ||
* @returns {Object} size of markers | ||
*/ | ||
exports.getMarkerId = getMarkerId; | ||
exports.getMarkerId = getMarkerId; | ||
function getMarkerSize(config) { | ||
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; | ||
if (config.node && !config.node.viewGenerator) { | ||
switch (config.node.symbolType) { | ||
case _graph["default"].SYMBOLS.CIRCLE: | ||
small = 0; | ||
medium = 0; | ||
large = 0; | ||
break; | ||
} | ||
} | ||
return { | ||
small: small, | ||
medium: medium, | ||
large: large | ||
}; | ||
} |
@@ -12,8 +12,12 @@ "use strict"; | ||
var _node2 = _interopRequireDefault(require("./node.const")); | ||
var _utils = require("../../utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
function _extends() { _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; }; return _extends.apply(this, arguments); } | ||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } | ||
function _extends() { _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; }; return _extends.apply(this, arguments); } | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } | ||
@@ -144,2 +148,3 @@ | ||
var size = this.props.size; | ||
var isSizeNumericalValue = _typeof(size) !== "object"; | ||
var gtx = this.props.cx, | ||
@@ -151,4 +156,4 @@ gty = this.props.cy, | ||
if (this.props.svg || this.props.viewGenerator) { | ||
var height = size / 10; | ||
var width = size / 10; | ||
var height = isSizeNumericalValue ? size / 10 : size.height / 10; | ||
var width = isSizeNumericalValue ? size / 10 : size.width / 10; | ||
var tx = width / 2; | ||
@@ -189,2 +194,7 @@ var ty = height / 2; | ||
} else { | ||
if (!isSizeNumericalValue) { | ||
(0, _utils.logWarning)("node.size should be a number when not using custom nodes."); | ||
size = _node2["default"].DEFAULT_NODE_SIZE; | ||
} | ||
nodeProps.d = _node["default"].buildSvgSymbol(size, this.props.type); | ||
@@ -191,0 +201,0 @@ nodeProps.fill = this.props.fill; |
@@ -11,3 +11,3 @@ "use strict"; | ||
GRAPH_NO_ID_PROP: "id prop not defined! id property is mandatory and it should be unique.", | ||
INSUFFICIENT_LINKS: "you are passing invalid data to react-d3-graph. You must include a links array in the data object you're passing down to the <Graph> component.", | ||
INSUFFICIENT_LINKS: "you are passing invalid data to react-d3-graph. You must include a links array, even if empty, in the data object you're passing down to the <Graph> component.", | ||
INVALID_LINKS: "you provided a invalid links data structure. Links source and target attributes must point to an existent node", | ||
@@ -14,0 +14,0 @@ INSUFFICIENT_DATA: "you have not provided enough data for react-d3-graph to render something. You need to provide at least one node", |
{ | ||
"name": "react-d3-graph", | ||
"version": "2.4.1", | ||
"version": "2.5.0", | ||
"description": "React component to build interactive and configurable graphs with d3 effortlessly", | ||
@@ -5,0 +5,0 @@ "author": "Daniel Caldas", |
@@ -1,3 +0,5 @@ | ||
# react-d3-graph · [](https://travis-ci.org/danielcaldas/react-d3-graph) | ||
# react-d3-graph · [](https://github.com/danielcaldas/react-d3-graph/workflows/react-d3-graph/badge.svg) | ||
⚠️ __There has been some changes in the domain where I host the documentation and live examples, please use https://danielcaldas.github.io/react-d3-graph/docs instead of the old domain https://goodguydaniel.com/react-d3-graph. Sorry for any inconvenient__ ⚠️ | ||
[](https://www.npmjs.com/package/react-d3-graph) [](https://www.npmjs.com/package/react-d3-graph) | ||
@@ -8,3 +10,3 @@ [](https://www.npmjs.com/package/react-d3-graph) [](https://probot.github.io/) [](https://github.com/prettier/prettier) | ||
:book: [documentation](https://danielcaldas.github.io/react-d3-graph/docs/index.html) | ||
:book: [Documentation](https://danielcaldas.github.io/react-d3-graph/docs/index.html) | ||
@@ -25,10 +27,12 @@ ### _Interactive and configurable graphs with react and d3 effortlessly_ | ||
You can also load different datasets and configurations via URL query parameter, here are the links: | ||
You can also load different data sets and configurations via URL query parameter. Below is a table with all the data sets available in the live sandbox for you to interactively explore different kinds of integrations with the library. | ||
- [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. | ||
- [static dataset](https://goodguydaniel.com/react-d3-graph/sandbox/index.html?data=static) - small sample config statically positioned nodes. | ||
| Name | Link | Source | Description | | ||
| :---------- | :---------------------------------------------------------------------------------------------------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| small | [see it in action](https://danielcaldas.github.io/react-d3-graph/sandbox/index.html?data=small) | `sandbox/data/small` | This is a good example to get you started. It has only 4 nodes. It's good to discuss over integration details and it's also good to report issues that you might found in the library. It's much easier to debug over a tiny graph. | | ||
| custom-node | [see it in action](https://danielcaldas.github.io/react-d3-graph/sandbox/index.html?data=custom-node) | `sandbox/data/custom-node` | In this example you'll be able to see the power of the feature [node.viewGenerator](https://danielcaldas.github.io/react-d3-graph/docs/#node-view-generator) to create highly customizable nodes for you graph that go beyond the simple shapes that come out of the box with the library. | | ||
| marvel | [see it in action](https://danielcaldas.github.io/react-d3-graph/sandbox/index.html?data=marvel) | `sandbox/data/marvel` | In this thematic example you can see how several features such as: [nodeHighlightBehavior](https://danielcaldas.github.io/react-d3-graph/docs/#node-highlight-behavior), [custom SVGs for nodes](https://danielcaldas.github.io/react-d3-graph/docs/#node-svg), [collapsible](https://danielcaldas.github.io/react-d3-graph/docs/#collapsible) etc. come together on top of a directed graph that displays some characters from the Marvel Universe. | | ||
| static | [see it in action](https://danielcaldas.github.io/react-d3-graph/sandbox/index.html?data=static) | `sandbox/data/static` | If your goal is not to have nodes dancing around with the default [d3 forces](https://danielcaldas.github.io/react-d3-graph/docs/#config-d3) that the library provides, you can opt by making your nodes static and positioned them always in the same _(x, y)_ coordinates. To achieve this you can make use of [staticGraphWithDragAndDrop](https://danielcaldas.github.io/react-d3-graph/docs/#static-graph-with-drag-and-drop) or [staticGraph](https://danielcaldas.github.io/react-d3-graph/docs/#static-graph) | | ||
Do you want to visualize your own data set on the live sandbox? Just submit a PR! You're welcome 😁 | ||
Do you want to visualize your own data set on the live sandbox? Just submit a PR! You're welcome 😁. | ||
@@ -68,3 +72,6 @@ ## Documentation :book: | ||
nodes: [{ id: "Harry" }, { id: "Sally" }, { id: "Alice" }], | ||
links: [{ source: "Harry", target: "Sally" }, { source: "Harry", target: "Alice" }], | ||
links: [ | ||
{ source: "Harry", target: "Sally" }, | ||
{ source: "Harry", target: "Alice" }, | ||
], | ||
}; | ||
@@ -71,0 +78,0 @@ |
@@ -12,11 +12,11 @@ ## Release Process | ||
1. Update versioning in package.json | ||
1. npm run dist | ||
2. Update versioning in package.json | ||
3. npm run docs | ||
4. Small tweaks on documentation page (quicklinks) | ||
5. Replace current files in docs for the generated ones in gen-docs | ||
6. Generate CHANGELOG.md (github_changelog_generator -u GITHUB_USERNAME) | ||
7. git commit -m "Release x.x.x" | ||
8. Create release x.x.x in github | ||
9. git pull (origin master) | ||
10. npm publish | ||
1. npm run docs | ||
1. Small tweaks on documentation page (quicklinks) | ||
1. Replace current files in docs for the generated ones in gen-docs | ||
1. Generate CHANGELOG.md (github_changelog_generator -u GITHUB_USERNAME) | ||
1. git commit -m "Release x.x.x" | ||
1. Create release x.x.x in github | ||
1. git pull (origin master) | ||
1. npm publish |
@@ -10,2 +10,3 @@ /** | ||
import { getMarkerId } from "../marker/marker.helper"; | ||
import { getNormalizedNodeCoordinates } from "./graph.helper"; | ||
@@ -57,8 +58,8 @@ /** | ||
const { source, target } = link; | ||
const x1 = nodes?.[source]?.x || 0; | ||
const y1 = nodes?.[source]?.y || 0; | ||
const x2 = nodes?.[target]?.x || 0; | ||
const y2 = nodes?.[target]?.y || 0; | ||
let x1 = nodes?.[source]?.x || 0; | ||
let y1 = nodes?.[source]?.y || 0; | ||
let x2 = nodes?.[target]?.x || 0; | ||
let y2 = nodes?.[target]?.y || 0; | ||
const type = link.type || config.link.type; | ||
const d = buildLinkPathDefinition({ source: { x: x1, y: y1 }, target: { x: x2, y: y2 } }, type); | ||
@@ -126,2 +127,10 @@ let mainNodeParticipates = false; | ||
const normalizedNodeCoordinates = getNormalizedNodeCoordinates( | ||
{ source: { x: x1, y: y1 }, target: { x: x2, y: y2 } }, | ||
nodes, | ||
config, | ||
strokeWidth | ||
); | ||
const d = buildLinkPathDefinition(normalizedNodeCoordinates, type); | ||
return { | ||
@@ -162,4 +171,4 @@ className: CONST.LINK_CLASS_NAME, | ||
node.highlighted || | ||
(node.id === (highlightedLink && highlightedLink.source) || | ||
node.id === (highlightedLink && highlightedLink.target)); | ||
node.id === (highlightedLink && highlightedLink.source) || | ||
node.id === (highlightedLink && highlightedLink.target); | ||
const opacity = _getNodeOpacity(node, highlightedNode, highlightedLink, config); | ||
@@ -195,7 +204,24 @@ | ||
const nodeSize = node.size || config.node.size; | ||
let offset; | ||
const isSizeNumericValue = typeof nodeSize !== "object"; | ||
if (isSizeNumericValue) { | ||
offset = nodeSize; | ||
} else if (labelPosition === "top" || labelPosition === "bottom") { | ||
offset = nodeSize.height; | ||
} else { | ||
nodeSize.width; | ||
} | ||
const fontSize = highlight ? config.node.highlightFontSize : config.node.fontSize; | ||
const dx = fontSize * t + nodeSize / 100 + 1.5; | ||
const dx = fontSize * t + offset / 100 + 1.5; | ||
const svg = node.svg || config.node.svg; | ||
const fontColor = node.fontColor || config.node.fontColor; | ||
let renderLabel = config.node.renderLabel; | ||
if (node.renderLabel !== undefined && typeof node.renderLabel === "boolean") { | ||
renderLabel = node.renderLabel; | ||
} | ||
return { | ||
@@ -217,4 +243,4 @@ ...node, | ||
overrideGlobalViewGenerator: !node.viewGenerator && node.svg, | ||
renderLabel: node.renderLabel || config.node.renderLabel, | ||
size: nodeSize * t, | ||
renderLabel, | ||
size: isSizeNumericValue ? nodeSize * t : { height: nodeSize.height * t, width: nodeSize.width * t }, | ||
stroke, | ||
@@ -221,0 +247,0 @@ strokeWidth: strokeWidth * t, |
@@ -46,3 +46,3 @@ /** | ||
* @param {boolean} [collapsible=false] - <a id="collapsible" href="#collapsible">🔗</a> 🚅🚅🚅 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. <b>NOTE</b>: At this moment | ||
* To see an example of this behavior you can access <a href="https://danielcaldas.github.io/react-d3-graph/sandbox/index.html?data=marvel" target="_blank" title="sandbox collapsible example">this sandbox link</a> that has a specific set up to experiment this feature. <b>NOTE</b>: At this moment | ||
* nodes without connections (orphan nodes) are not rendered when this property is activated (see <a target="_blank" href="https://github.com/danielcaldas/react-d3-graph/issues/129">GitHub issue #129</a>). | ||
@@ -82,2 +82,3 @@ * </br> | ||
* the value the more the less highlighted nodes will be visible (related to <i>nodeHighlightBehavior</i>). | ||
* @param {number} [initialZoom=null] - <a id="max-zoom" href="#initial-zoom">🔗</a> initial zoom that can be set on the graph. | ||
* @param {number} [maxZoom=8] - <a id="max-zoom" href="#max-zoom">🔗</a> max zoom that can be performed against the graph. | ||
@@ -104,3 +105,3 @@ * @param {number} [minZoom=0.1] - <a id="min-zoom" href="#min-zoom">🔗</a> min zoom that can be performed against the graph. | ||
* @param {number} [d3.linkStrength=1] - <a id="d3-link-strength" href="#d3-link-strength">🔗</a> <a target="_blank" href="https://github.com/d3/d3-force#link_strength">see d3-force link.strength</a> | ||
* @param {number} [d3.disableLinkForce=false] - <a id="d3-disable-link-force" href="#d3-disable-link-force">🔗</a> ⚠️🧪EXPERIMENTAL🧪⚠️ it completely disables d3 force link and simulation to re-trigger so that one can obtain | ||
* @param {boolean} [d3.disableLinkForce=false] - <a id="d3-disable-link-force" href="#d3-disable-link-force">🔗</a> ⚠️🧪EXPERIMENTAL🧪⚠️ it completely disables d3 force link and simulation to re-trigger so that one can obtain | ||
* precise render of node positions as described by the author <a target="_blank" href="https://github.com/antoninklopp">@antoninklopp</a> in <a target="_blank" href="https://github.com/danielcaldas/react-d3-graph/pull/278">the Pull Request description</a>. | ||
@@ -151,3 +152,13 @@ * </br> | ||
* graph. | ||
* @param {number} [node.size=200] - <a id="node-size" href="#node-size">🔗</a> 🔍🔍🔍 defines the size of all nodes. | ||
* @param {number|Object} [node.size=200] - <a id="node-size" href="#node-size">🔗</a> 🔍🔍🔍 defines the size of all nodes. When set to a number, the node will have equal height and width.</br> | ||
* This can also be an object with a height and width property <b>when using custom nodes</b>. | ||
* ```javascript | ||
* size: 200 | ||
* // or | ||
* size: { | ||
* height: 200, | ||
* width: 300, | ||
* } | ||
* ``` | ||
* The actual node dimensions (in px) rendered on screen will be the size value divided by 10. For example, a node size of 200 will result in a node with a height and width of 20px. | ||
* @param {string} [node.strokeColor="none"] - <a id="node-stroke-color" href="#node-stroke-color">🔗</a> 🔍🔍🔍 this is the stroke color that will be applied to the node if no <b>strokeColor property</b> is found inside the node itself (yes <b>you can pass a property "strokeColor" inside the node and that stroke color will override this default one</b>). | ||
@@ -245,2 +256,3 @@ * @param {number} [node.strokeWidth=1.5] - <a id="node-stroke-width" href="#node-stroke-width">🔗</a> 🔍🔍🔍 the width of the all node strokes. | ||
minZoom: 0.1, | ||
initialZoom: null, | ||
nodeHighlightBehavior: false, | ||
@@ -247,0 +259,0 @@ panAndZoom: false, |
@@ -211,3 +211,3 @@ /** | ||
* INVALID_LINKS - if links point to nonexistent nodes | ||
* INSUFFICIENT_LINKS - if no links are provided | ||
* INSUFFICIENT_LINKS - if no links are provided (not even empty Array) | ||
* @returns {undefined} | ||
@@ -221,3 +221,3 @@ * @memberof Graph/helper | ||
if (!data.links || !data.links.length) { | ||
if (!data.links) { | ||
logWarning("Graph", ERRORS.INSUFFICIENT_LINKS); | ||
@@ -456,2 +456,58 @@ data.links = []; | ||
/** | ||
* Computes the normalized vector from a vector. | ||
* @param {Object} vector a 2D vector with x and y components | ||
* @param {number} vector.x x coordinate | ||
* @param {number} vector.y y coordinate | ||
* @returns {Object} normalized vector | ||
* @memberof Graph/helper | ||
*/ | ||
function normalize(vector) { | ||
const norm = Math.sqrt(Math.pow(vector.x, 2) + Math.pow(vector.y, 2)); | ||
return { x: vector.x / norm, y: vector.y / norm }; | ||
} | ||
/** | ||
* Computes new node coordinates to make arrowheads point at nodes. | ||
* Arrow configuration is only available for circles. | ||
* @param {Object} node - the couple of nodes we need to compute new coordinates | ||
* @param {Object} node.source - node source | ||
* @param {Object} node.target - node target | ||
* @param {Object.<string, Object>} nodes - same as {@link #graphrenderer|nodes in renderGraph}. | ||
* @param {Object} config - same as {@link #graphrenderer|config in renderGraph}. | ||
* @param {number} strokeWidth width of the link stroke | ||
* @returns {Object} new nodes coordinates | ||
* @memberof Graph/helper | ||
*/ | ||
function getNormalizedNodeCoordinates({ source = {}, target = {} }, nodes, config, strokeWidth) { | ||
if (config.node?.viewGenerator) { | ||
return { source, target }; | ||
} | ||
let { x: x1, y: y1 } = source; | ||
let { x: x2, y: y2 } = target; | ||
switch (config.node?.symbolType) { | ||
case CONST.SYMBOLS.CIRCLE: { | ||
const directionVector = normalize({ x: x2 - x1, y: y2 - y1 }); | ||
const strokeSize = strokeWidth * Math.min(config.link.markerWidth, config.link.markerHeight); | ||
let nodeSize = nodes?.[source]?.size || config.node.size; | ||
// cause this is a circle and A = pi * r^2 | ||
// we multiply by 0.95, because if we don't the link is not melting properly | ||
nodeSize = Math.sqrt(nodeSize / Math.PI) * 0.95; | ||
// points from the source, we move them not to begin in the circle but outside | ||
x1 += nodeSize * directionVector.x; | ||
y1 += nodeSize * directionVector.y; | ||
// points from the target, we move the by the size of the radius of the circle + the size of the arrow | ||
x2 -= (nodeSize + (config.directed ? strokeSize : 0)) * directionVector.x; | ||
y2 -= (nodeSize + (config.directed ? strokeSize : 0)) * directionVector.y; | ||
break; | ||
} | ||
} | ||
return { source: { x: x1, y: y1 }, target: { x: x2, y: y2 } }; | ||
} | ||
export { | ||
@@ -464,2 +520,3 @@ checkForGraphConfigChanges, | ||
updateNodeHighlightedValue, | ||
getNormalizedNodeCoordinates, | ||
}; |
@@ -280,9 +280,15 @@ import React from "react"; | ||
_zoomConfig = () => { | ||
d3Select(`#${this.state.id}-${CONST.GRAPH_WRAPPER_ID}`) | ||
.call( | ||
d3Zoom() | ||
.scaleExtent([this.state.config.minZoom, this.state.config.maxZoom]) | ||
.on("zoom", this._zoomed) | ||
) | ||
.on("dblclick.zoom", null); | ||
const selector = d3Select(`#${this.state.id}-${CONST.GRAPH_WRAPPER_ID}`); | ||
const zoomObject = d3Zoom() | ||
.scaleExtent([this.state.config.minZoom, this.state.config.maxZoom]) | ||
.on("zoom", this._zoomed); | ||
if (this.state.config.initialZoom !== null) { | ||
zoomObject.scaleTo(selector, this.state.config.initialZoom); | ||
} | ||
// avoid double click on graph to trigger zoom | ||
// for more details consult: https://github.com/danielcaldas/react-d3-graph/pull/202 | ||
selector.call(zoomObject).on("dblclick.zoom", null); | ||
}; | ||
@@ -289,0 +295,0 @@ |
@@ -9,3 +9,3 @@ /** | ||
import CONST from "./graph.const"; | ||
import { MARKERS, MARKER_SMALL_SIZE, MARKER_MEDIUM_OFFSET, MARKER_LARGE_OFFSET } from "../marker/marker.const"; | ||
import { MARKERS } from "../marker/marker.const"; | ||
@@ -18,2 +18,3 @@ import Link from "../link/Link"; | ||
import { isNodeVisible } from "./collapse.helper"; | ||
import { getMarkerSize } from "../marker/marker.helper"; | ||
@@ -110,6 +111,3 @@ /** | ||
const small = MARKER_SMALL_SIZE; | ||
const medium = small + (MARKER_MEDIUM_OFFSET * config.maxZoom) / 3; | ||
const large = small + (MARKER_LARGE_OFFSET * config.maxZoom) / 3; | ||
const { small, medium, large } = getMarkerSize(config); | ||
const markerProps = { | ||
@@ -116,0 +114,0 @@ markerWidth: config.link.markerWidth, |
@@ -6,3 +6,11 @@ /** | ||
*/ | ||
import { MARKERS, SIZES, HIGHLIGHTED } from "./marker.const"; | ||
import { | ||
MARKERS, | ||
SIZES, | ||
HIGHLIGHTED, | ||
MARKER_SMALL_SIZE, | ||
MARKER_MEDIUM_OFFSET, | ||
MARKER_LARGE_OFFSET, | ||
} from "./marker.const"; | ||
import CONST from "../graph/graph.const"; | ||
@@ -97,2 +105,28 @@ /** | ||
export { getMarkerId }; | ||
/** | ||
* Computes the three marker sizes | ||
* For supported shapes in {@link Graph/helper/getNormalizedNodeCoordinates}, the function should return 0, | ||
* to be able to control more accurately nodes and arrows sizes and positions in directional graphs. | ||
* @param {Object} config - the graph config object. | ||
* @returns {Object} size of markers | ||
* @memberof Marker/helper | ||
*/ | ||
function getMarkerSize(config) { | ||
let small = MARKER_SMALL_SIZE; | ||
let medium = small + (MARKER_MEDIUM_OFFSET * config.maxZoom) / 3; | ||
let large = small + (MARKER_LARGE_OFFSET * config.maxZoom) / 3; | ||
if (config.node && !config.node.viewGenerator) { | ||
switch (config.node.symbolType) { | ||
case CONST.SYMBOLS.CIRCLE: | ||
small = 0; | ||
medium = 0; | ||
large = 0; | ||
break; | ||
} | ||
} | ||
return { small, medium, large }; | ||
} | ||
export { getMarkerId, getMarkerSize }; |
@@ -68,2 +68,3 @@ /** | ||
* props to put text svg for label in correct spot. default case returns just dx and dy, without textAnchor and dominantBaseline | ||
* @memberof Node/helper | ||
*/ | ||
@@ -70,0 +71,0 @@ function getLabelPlacementProps(dx, labelPosition) { |
import React from "react"; | ||
import nodeHelper from "./node.helper"; | ||
import CONST from "./node.const"; | ||
import { logWarning } from "../../utils"; | ||
@@ -97,3 +99,4 @@ /** | ||
const size = this.props.size; | ||
let size = this.props.size; | ||
const isSizeNumericalValue = typeof size !== "object"; | ||
@@ -106,4 +109,4 @@ let gtx = this.props.cx, | ||
if (this.props.svg || this.props.viewGenerator) { | ||
const height = size / 10; | ||
const width = size / 10; | ||
const height = isSizeNumericalValue ? size / 10 : size.height / 10; | ||
const width = isSizeNumericalValue ? size / 10 : size.width / 10; | ||
const tx = width / 2; | ||
@@ -138,2 +141,6 @@ const ty = height / 2; | ||
} else { | ||
if (!isSizeNumericalValue) { | ||
logWarning("node.size should be a number when not using custom nodes."); | ||
size = CONST.DEFAULT_NODE_SIZE; | ||
} | ||
nodeProps.d = nodeHelper.buildSvgSymbol(size, this.props.type); | ||
@@ -140,0 +147,0 @@ nodeProps.fill = this.props.fill; |
@@ -5,3 +5,3 @@ /*eslint max-len: ["error", 200]*/ | ||
INSUFFICIENT_LINKS: | ||
"you are passing invalid data to react-d3-graph. You must include a links array in the data object you're passing down to the <Graph> component.", | ||
"you are passing invalid data to react-d3-graph. You must include a links array, even if empty, in the data object you're passing down to the <Graph> component.", | ||
INVALID_LINKS: | ||
@@ -8,0 +8,0 @@ "you provided a invalid links data structure. Links source and target attributes must point to an existent node", |
Sorry, the diff of this file is too big to display
444444
5772
170