diff --git a/README.md b/README.md index dcb6b0dc1..637a4704d 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,13 @@ 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. -| Name | Link | Source | Description | -| :----- | :---------------------------------------------------------------------------------------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| small | [demo](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 | [demo](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 | [demo](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 | [demo](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) | +| Name | Link | Source | Description | +| :---------- | :---------------------------------------------------------------------------------------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| small | [demo](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 | [demo](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. | +| custom_link | [demo](https://danielcaldas.github.io/react-d3-graph/sandbox/index.html?data=custom-link) | `sandbox/data/custom-link` | In this example you'll be able to see the power of the feature [link.viewGenerator](https://danielcaldas.github.io/react-d3-graph/docs/#link-view-generator) to create highly customizable links for you graph. | +| marvel | [demo](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 | [demo](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 😁. diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 8f700d59f..4180b58aa 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -561,6 +561,18 @@ const graph = { The stroke-linecap options are:- "butt" - "round" - "square" (optional, default `"butt"`) + - `link.viewGenerator` **[Function][190]** 🔗 🔍 function that receives parameters ( link label, source node, target node, link options ) and returns a JSX view. + ```js + viewGenerator: (props, options) => ( + ), + ``` ### Examples @@ -1438,9 +1450,11 @@ components. }, ... } + ``` ``` + - `linkCallbacks` **[Array][189]<[Function][190]>** array of callbacks for used defined event handler for link interactions. - `config` **[Object][188]** an object containing rd3g consumer defined configurations [config][200] for the graph. - `highlightedNode` **[string][187]** this value contains a string that represents the some currently highlighted node. diff --git a/sandbox/data/custom-link/CustomLink.jsx b/sandbox/data/custom-link/CustomLink.jsx new file mode 100644 index 000000000..cb098feff --- /dev/null +++ b/sandbox/data/custom-link/CustomLink.jsx @@ -0,0 +1,41 @@ +/* eslint-disable valid-jsdoc */ +import React from "react"; + +/** + * @param {Object} params component props to render. + * @param {string} params.label path label + * @param {Object} params.source source node + * @param {Object} params.target target node + * @param {string} params.id path id + * @param {Object} params.lineProps line props + * @param {Object} params.textProps text props + */ +function CustomLink(params) { + const { label, source, target, id, lineProps, textProps } = params; + const isReverse = target.x < source.x; + let fixedLineProps = lineProps; + if (isReverse) { + const { markerEnd, d, ...rest } = lineProps; + const items = d.split(" "); + const [sx, sy] = items[0].replace("M", "").split(","); + const [tx, ty] = items[items.length - 1].split(","); + const sOffset = { x: source.x - sx, y: source.y - sy }; + const tOffset = { x: target.x - tx, y: target.y - ty }; + const reverseD = `M${target.x - tOffset.x},${target.y - tOffset.y} ${source.x - sOffset.x},${source.y - sOffset.y}`; + fixedLineProps = { ...rest, markerStart: markerEnd, d: reverseD }; + } + return ( + + + {label && ( + + + {label} + + + )} + + ); +} + +export default CustomLink; diff --git a/sandbox/data/custom-link/custom-link.config.js b/sandbox/data/custom-link/custom-link.config.js new file mode 100644 index 000000000..05dea76e5 --- /dev/null +++ b/sandbox/data/custom-link/custom-link.config.js @@ -0,0 +1,64 @@ +import React from "react"; +import CustomLink from "./CustomLink"; + +export default { + automaticRearrangeAfterDropNode: false, + collapsible: false, + height: 400, + highlightDegree: 1, + highlightOpacity: 0.2, + linkHighlightBehavior: true, + maxZoom: 8, + minZoom: 0.1, + nodeHighlightBehavior: true, + panAndZoom: false, + staticGraph: false, + width: 800, + directed: true, + node: { + color: "#d3d3d3", + fontColor: "black", + fontSize: 12, + fontWeight: "normal", + highlightColor: "red", + highlightFontSize: 12, + highlightFontWeight: "bold", + highlightStrokeColor: "SAME", + highlightStrokeWidth: 1.5, + labelProperty: "name", + mouseCursor: "pointer", + opacity: 1, + renderLabel: true, + size: 450, + strokeColor: "none", + strokeWidth: 1.5, + svg: "", + symbolType: "circle", + }, + link: { + color: "#d3d3d3", + fontColor: "blue", + fontSize: 10, + highlightColor: "blue", + highlightFontWeight: "bold", + labelProperty: link => `from ${link.source} to ${link.target}`, + opacity: 1, + renderLabel: true, + semanticStrokeWidth: false, + strokeWidth: 4, + viewGenerator: (props, options) => ( + + ), + }, + d3: { + gravity: -400, + linkLength: 300, + }, +}; diff --git a/sandbox/data/custom-link/custom-link.data.js b/sandbox/data/custom-link/custom-link.data.js new file mode 100644 index 000000000..81147a05b --- /dev/null +++ b/sandbox/data/custom-link/custom-link.data.js @@ -0,0 +1,38 @@ +module.exports = { + links: [ + { + source: 1, + target: 2, + }, + { + source: 1, + target: 3, + }, + { + source: 1, + target: 4, + }, + { + source: 2, + target: 3, + }, + ], + nodes: [ + { + id: 1, + name: "Node 1", + }, + { + id: 2, + name: "Node 2", + }, + { + id: 3, + name: "Node 3", + }, + { + id: 4, + name: "Node 4", + }, + ], +}; diff --git a/src/components/graph/graph.builder.js b/src/components/graph/graph.builder.js index 73734e62e..5c477edff 100644 --- a/src/components/graph/graph.builder.js +++ b/src/components/graph/graph.builder.js @@ -161,6 +161,7 @@ function buildLinkProps(link, nodes, links, config, linkCallbacks, highlightedNo strokeDashoffset, strokeLinecap, target, + viewGenerator: link.viewGenerator || config.link.viewGenerator, onClickLink: linkCallbacks.onClickLink, onMouseOutLink: linkCallbacks.onMouseOutLink, onMouseOverLink: linkCallbacks.onMouseOverLink, diff --git a/src/components/graph/graph.config.js b/src/components/graph/graph.config.js index 7c9f9d219..0535fde8a 100644 --- a/src/components/graph/graph.config.js +++ b/src/components/graph/graph.config.js @@ -345,5 +345,6 @@ export default { strokeDasharray: 0, strokeDashoffset: 0, strokeLinecap: "butt", + viewGenerator: null, }, }; diff --git a/src/components/graph/graph.renderer.jsx b/src/components/graph/graph.renderer.jsx index 6c82e6173..5cb6a06a7 100644 --- a/src/components/graph/graph.renderer.jsx +++ b/src/components/graph/graph.renderer.jsx @@ -51,8 +51,8 @@ function _renderLinks(nodes, links, linksMatrix, config, linkCallbacks, highligh highlightedLink, transform ); - - return ; + const node = { source: nodes[props.source], target: nodes[props.target] }; + return ; }); } diff --git a/src/components/link/Link.jsx b/src/components/link/Link.jsx index bf6e87ee0..9c5f5bea1 100644 --- a/src/components/link/Link.jsx +++ b/src/components/link/Link.jsx @@ -35,7 +35,8 @@ import React from "react"; * onClickLink={onClickLink} * onRightClickLink={onRightClickLink} * onMouseOverLink={onMouseOverLink} - * onMouseOutLink={onMouseOutLink} /> + * onMouseOutLink={onMouseOutLink} + * node={{source:Node,target:Node}} // for ViewGenerator /> */ export default class Link extends React.Component { /** @@ -92,7 +93,7 @@ export default class Link extends React.Component { lineProps.markerEnd = `url(#${this.props.markerId})`; } - const { label, id } = this.props; + const { label, id, node } = this.props; const textProps = { dy: -1, style: { @@ -102,17 +103,24 @@ export default class Link extends React.Component { }, }; - return ( - - - {label && ( - - - {label} - - - )} - - ); + if (this.props.viewGenerator) { + return this.props.viewGenerator( + { source: node?.source, target: node?.target, label }, + { id, lineProps, textProps } + ); + } else { + return ( + + + {label && ( + + + {label} + + + )} + + ); + } } } diff --git a/src/components/marker/Marker.jsx b/src/components/marker/Marker.jsx index 82a7007ed..e21cea034 100644 --- a/src/components/marker/Marker.jsx +++ b/src/components/marker/Marker.jsx @@ -17,7 +17,7 @@ export default class Marker extends React.Component { refY="0" markerWidth={this.props.markerWidth} markerHeight={this.props.markerHeight} - orient="auto" + orient="auto-start-reverse" fill={this.props.fill} > diff --git a/test/graph/__snapshots__/graph.snapshot.spec.js.snap b/test/graph/__snapshots__/graph.snapshot.spec.js.snap index 850e2e556..892d1fc5e 100644 --- a/test/graph/__snapshots__/graph.snapshot.spec.js.snap +++ b/test/graph/__snapshots__/graph.snapshot.spec.js.snap @@ -21,7 +21,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = ` id="marker-small" markerHeight={6} markerWidth={6} - orient="auto" + orient="auto-start-reverse" refX={0} refY="0" viewBox="0 -5 10 10" @@ -36,7 +36,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = ` id="marker-small-highlighted" markerHeight={6} markerWidth={6} - orient="auto" + orient="auto-start-reverse" refX={0} refY="0" viewBox="0 -5 10 10" @@ -51,7 +51,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = ` id="marker-medium" markerHeight={6} markerWidth={6} - orient="auto" + orient="auto-start-reverse" refX={0} refY="0" viewBox="0 -5 10 10" @@ -66,7 +66,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = ` id="marker-medium-highlighted" markerHeight={6} markerWidth={6} - orient="auto" + orient="auto-start-reverse" refX={0} refY="0" viewBox="0 -5 10 10" @@ -81,7 +81,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = ` id="marker-large" markerHeight={6} markerWidth={6} - orient="auto" + orient="auto-start-reverse" refX={0} refY="0" viewBox="0 -5 10 10" @@ -96,7 +96,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = ` id="marker-large-highlighted" markerHeight={6} markerWidth={6} - orient="auto" + orient="auto-start-reverse" refX={0} refY="0" viewBox="0 -5 10 10" diff --git a/test/link/__snapshots__/link.snapshot.spec.js.snap b/test/link/__snapshots__/link.snapshot.spec.js.snap index 1b7dc3fd1..6e67c0481 100644 --- a/test/link/__snapshots__/link.snapshot.spec.js.snap +++ b/test/link/__snapshots__/link.snapshot.spec.js.snap @@ -22,3 +22,5 @@ exports[`Snapshot - Link Component should match snapshot 1`] = ` /> `; + +exports[`Snapshot - Link Component should match snapshot for viewGenerator 1`] = `null`; diff --git a/test/link/link.snapshot.spec.js b/test/link/link.snapshot.spec.js index 1dc1143c3..c6893d2ea 100644 --- a/test/link/link.snapshot.spec.js +++ b/test/link/link.snapshot.spec.js @@ -26,9 +26,33 @@ describe("Snapshot - Link Component", () => { ); that.tree = that.link.toJSON(); + + const viewGenerator = () => () => "viewGenerator"; + that.linkForViewGenerator = renderer.create( + + ); + + that.treeForViewGenerator = that.linkForViewGenerator.toJSON(); }); test("should match snapshot", () => { expect(that.tree).toMatchSnapshot(); }); + test("should match snapshot for viewGenerator", () => { + expect(that.treeForViewGenerator).toMatchSnapshot(); + }); }); diff --git a/test/marker/__snapshots__/marker.snapshot.spec.js.snap b/test/marker/__snapshots__/marker.snapshot.spec.js.snap index c20fe399b..35795b6af 100644 --- a/test/marker/__snapshots__/marker.snapshot.spec.js.snap +++ b/test/marker/__snapshots__/marker.snapshot.spec.js.snap @@ -5,7 +5,7 @@ exports[`Snapshot - Marker Component should match snapshot 1`] = ` className="marker" fill="green" id="id" - orient="auto" + orient="auto-start-reverse" refX="5" refY="0" viewBox="0 -5 10 10"