Skip to content

Commit c308e7c

Browse files
committed
add link.path_generator
1 parent a1bd350 commit c308e7c

File tree

14 files changed

+209
-24
lines changed

14 files changed

+209
-24
lines changed

README.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515

1616
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.
1717

18-
| Name | Link | Source | Description |
19-
| :----- | :---------------------------------------------------------------------------------------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20-
| 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. |
21-
| 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. |
22-
| 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. |
23-
| 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) |
18+
| Name | Link | Source | Description |
19+
| :---------- | :---------------------------------------------------------------------------------------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20+
| 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. |
21+
| 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. |
22+
| 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.pathGenerator](https://danielcaldas.github.io/react-d3-graph/docs/#link-path-generator) to create highly customizable links for you graph. |
23+
| 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. |
24+
| 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) |
2425

2526
Do you want to visualize your own data set on the live sandbox? Just submit a PR! You're welcome 😁.
2627

docs/DOCUMENTATION.md

+8
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,12 @@ const graph = {
561561
The stroke-linecap options are:- "butt"
562562
- "round"
563563
- "square" (optional, default `"butt"`)
564+
- `link.pathGenerator` **[Function][190]** <a id="link-path-generator" href="#link-path-generator">🔗</a> 🔍 function that receives path props and returns a JSX view.
565+
```js
566+
pathGenerator: ( props, { id, label, source, target, labelGenerator } ) =>
567+
<CustomLink id={id} props={props} label={label} labelGenerator={labelGenerator}
568+
source={source} target={target}/>,
569+
```
564570

565571
### Examples
566572

@@ -1438,9 +1444,11 @@ components.
14381444
},
14391445
...
14401446
}
1447+
14411448
```
14421449
14431450
```
1451+
14441452
- `linkCallbacks` **[Array][189]&lt;[Function][190]>** array of callbacks for used defined event handler for link interactions.
14451453
- `config` **[Object][188]** an object containing rd3g consumer defined configurations [config][200] for the graph.
14461454
- `highlightedNode` **[string][187]** this value contains a string that represents the some currently highlighted node.
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* eslint-disable valid-jsdoc */
2+
import React from "react";
3+
4+
/**
5+
* @param {Object} params component props to render.
6+
* @param {Object} params.props path props
7+
* @param {string} params.id path id
8+
* @param {string} params.label path label
9+
* @param {Function} params.labelGenerator path label generator
10+
* @param {Object} params.source source node
11+
* @param {Object} params.target target node
12+
*/
13+
function CustomLink(params) {
14+
const { id, props, labelGenerator, source, target } = params;
15+
const isReverse = target.x < source.x;
16+
let lineProps = props;
17+
if (isReverse) {
18+
const { markerEnd, d, ...rest } = props;
19+
const items = d.split(" ");
20+
const [sx, sy] = items[0].replace("M", "").split(",");
21+
const [tx, ty] = items[items.length - 1].split(",");
22+
const sOffset = { x: source.x - sx, y: source.y - sy };
23+
const tOffset = { x: target.x - tx, y: target.y - ty };
24+
const reverseD = `M${target.x - tOffset.x},${target.y - tOffset.y} ${source.x - sOffset.x},${source.y - sOffset.y}`;
25+
lineProps = { ...rest, markerStart: markerEnd, d: reverseD };
26+
}
27+
return (
28+
<>
29+
<path {...lineProps} id={id} />
30+
{labelGenerator && labelGenerator()}
31+
</>
32+
);
33+
}
34+
35+
export default CustomLink;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from "react";
2+
import CustomLink from "./CustomLink";
3+
4+
export default {
5+
automaticRearrangeAfterDropNode: false,
6+
collapsible: false,
7+
height: 400,
8+
highlightDegree: 1,
9+
highlightOpacity: 0.2,
10+
linkHighlightBehavior: true,
11+
maxZoom: 8,
12+
minZoom: 0.1,
13+
nodeHighlightBehavior: true,
14+
panAndZoom: false,
15+
staticGraph: false,
16+
width: 800,
17+
directed: true,
18+
node: {
19+
color: "#d3d3d3",
20+
fontColor: "black",
21+
fontSize: 12,
22+
fontWeight: "normal",
23+
highlightColor: "red",
24+
highlightFontSize: 12,
25+
highlightFontWeight: "bold",
26+
highlightStrokeColor: "SAME",
27+
highlightStrokeWidth: 1.5,
28+
labelProperty: "name",
29+
mouseCursor: "pointer",
30+
opacity: 1,
31+
renderLabel: true,
32+
size: 450,
33+
strokeColor: "none",
34+
strokeWidth: 1.5,
35+
svg: "",
36+
symbolType: "circle",
37+
},
38+
link: {
39+
color: "#d3d3d3",
40+
fontColor: "blue",
41+
fontSize: 10,
42+
highlightColor: "blue",
43+
highlightFontWeight: "bold",
44+
labelProperty: link => `from ${link.source} to ${link.target}`,
45+
opacity: 1,
46+
renderLabel: true,
47+
semanticStrokeWidth: false,
48+
strokeWidth: 4,
49+
pathGenerator: (props, { id, label, source, target, labelGenerator }) => (
50+
<CustomLink id={id} props={props} label={label} labelGenerator={labelGenerator} source={source} target={target} />
51+
),
52+
},
53+
d3: {
54+
gravity: -400,
55+
linkLength: 300,
56+
},
57+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module.exports = {
2+
links: [
3+
{
4+
source: 1,
5+
target: 2,
6+
},
7+
{
8+
source: 1,
9+
target: 3,
10+
},
11+
{
12+
source: 1,
13+
target: 4,
14+
},
15+
{
16+
source: 2,
17+
target: 3,
18+
},
19+
],
20+
nodes: [
21+
{
22+
id: 1,
23+
name: "Node 1",
24+
},
25+
{
26+
id: 2,
27+
name: "Node 2",
28+
},
29+
{
30+
id: 3,
31+
name: "Node 3",
32+
},
33+
{
34+
id: 4,
35+
name: "Node 4",
36+
},
37+
],
38+
};

src/components/graph/graph.builder.js

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ function buildLinkProps(link, nodes, links, config, linkCallbacks, highlightedNo
161161
strokeDashoffset,
162162
strokeLinecap,
163163
target,
164+
pathGenerator: link.pathGenerator || config.link.pathGenerator,
164165
onClickLink: linkCallbacks.onClickLink,
165166
onMouseOutLink: linkCallbacks.onMouseOutLink,
166167
onMouseOverLink: linkCallbacks.onMouseOverLink,

src/components/graph/graph.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -345,5 +345,6 @@ export default {
345345
strokeDasharray: 0,
346346
strokeDashoffset: 0,
347347
strokeLinecap: "butt",
348+
pathGenerator: null,
348349
},
349350
};

src/components/graph/graph.renderer.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ function _renderLinks(nodes, links, linksMatrix, config, linkCallbacks, highligh
5151
highlightedLink,
5252
transform
5353
);
54-
55-
return <Link key={key} id={key} {...props} />;
54+
const node = { source: nodes[props.source], target: nodes[props.target] };
55+
return <Link key={key} id={key} {...props} node={node} />;
5656
});
5757
}
5858

src/components/link/Link.jsx

+26-8
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export default class Link extends React.Component {
9292
lineProps.markerEnd = `url(#${this.props.markerId})`;
9393
}
9494

95-
const { label, id } = this.props;
95+
const { label, id, node } = this.props;
9696
const textProps = {
9797
dy: -1,
9898
style: {
@@ -102,17 +102,35 @@ export default class Link extends React.Component {
102102
},
103103
};
104104

105-
return (
106-
<g>
107-
<path {...lineProps} id={id} />
108-
{label && (
105+
const labelGenerator = label
106+
? () => (
109107
<text style={{ textAnchor: "middle" }} {...textProps}>
110108
<textPath href={`#${id}`} startOffset="50%">
111109
{label}
112110
</textPath>
113111
</text>
114-
)}
115-
</g>
116-
);
112+
)
113+
: () => null;
114+
115+
if (this.props.pathGenerator) {
116+
return (
117+
<>
118+
{this.props.pathGenerator(lineProps, {
119+
id,
120+
label,
121+
labelGenerator,
122+
source: node?.source,
123+
target: node?.target,
124+
})}
125+
</>
126+
);
127+
} else {
128+
return (
129+
<g>
130+
<path {...lineProps} id={id} />
131+
{labelGenerator()}
132+
</g>
133+
);
134+
}
117135
}
118136
}

src/components/marker/Marker.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default class Marker extends React.Component {
1717
refY="0"
1818
markerWidth={this.props.markerWidth}
1919
markerHeight={this.props.markerHeight}
20-
orient="auto"
20+
orient="auto-start-reverse"
2121
fill={this.props.fill}
2222
>
2323
<path d="M0,-5L10,0L0,5" />

test/graph/__snapshots__/graph.snapshot.spec.js.snap

+6-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = `
2121
id="marker-small"
2222
markerHeight={6}
2323
markerWidth={6}
24-
orient="auto"
24+
orient="auto-start-reverse"
2525
refX={0}
2626
refY="0"
2727
viewBox="0 -5 10 10"
@@ -36,7 +36,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = `
3636
id="marker-small-highlighted"
3737
markerHeight={6}
3838
markerWidth={6}
39-
orient="auto"
39+
orient="auto-start-reverse"
4040
refX={0}
4141
refY="0"
4242
viewBox="0 -5 10 10"
@@ -51,7 +51,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = `
5151
id="marker-medium"
5252
markerHeight={6}
5353
markerWidth={6}
54-
orient="auto"
54+
orient="auto-start-reverse"
5555
refX={0}
5656
refY="0"
5757
viewBox="0 -5 10 10"
@@ -66,7 +66,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = `
6666
id="marker-medium-highlighted"
6767
markerHeight={6}
6868
markerWidth={6}
69-
orient="auto"
69+
orient="auto-start-reverse"
7070
refX={0}
7171
refY="0"
7272
viewBox="0 -5 10 10"
@@ -81,7 +81,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = `
8181
id="marker-large"
8282
markerHeight={6}
8383
markerWidth={6}
84-
orient="auto"
84+
orient="auto-start-reverse"
8585
refX={0}
8686
refY="0"
8787
viewBox="0 -5 10 10"
@@ -96,7 +96,7 @@ exports[`Snapshot - Graph Component should match snapshot 1`] = `
9696
id="marker-large-highlighted"
9797
markerHeight={6}
9898
markerWidth={6}
99-
orient="auto"
99+
orient="auto-start-reverse"
100100
refX={0}
101101
refY="0"
102102
viewBox="0 -5 10 10"

test/link/__snapshots__/link.snapshot.spec.js.snap

+2
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ exports[`Snapshot - Link Component should match snapshot 1`] = `
2222
/>
2323
</g>
2424
`;
25+
26+
exports[`Snapshot - Link Component should match snapshot for pathGenerator 1`] = `null`;

test/link/link.snapshot.spec.js

+24
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,33 @@ describe("Snapshot - Link Component", () => {
2626
);
2727

2828
that.tree = that.link.toJSON();
29+
30+
const pathGenerator = () => () => "pathGenerator";
31+
that.linkForPathGenerator = renderer.create(
32+
<Link
33+
x1="2"
34+
y1="2"
35+
x2="4"
36+
y2="4"
37+
opacity="1"
38+
stroke="red"
39+
strokeWidth="2"
40+
onClickLink={that.callbackMock}
41+
strokeDasharray={0}
42+
strokeDashoffset={0}
43+
strokeLinecap="butt"
44+
node={{ source: { x: 2, y: 2 }, target: { x: 4, y: 4 } }}
45+
pathGenerator={pathGenerator}
46+
/>
47+
);
48+
49+
that.treeForPathGenerator = that.linkForPathGenerator.toJSON();
2950
});
3051

3152
test("should match snapshot", () => {
3253
expect(that.tree).toMatchSnapshot();
3354
});
55+
test("should match snapshot for pathGenerator", () => {
56+
expect(that.treeForPathGenerator).toMatchSnapshot();
57+
});
3458
});

test/marker/__snapshots__/marker.snapshot.spec.js.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ exports[`Snapshot - Marker Component should match snapshot 1`] = `
55
className="marker"
66
fill="green"
77
id="id"
8-
orient="auto"
8+
orient="auto-start-reverse"
99
refX="5"
1010
refY="0"
1111
viewBox="0 -5 10 10"

0 commit comments

Comments
 (0)