Skip to content

Commit 1ff3c8c

Browse files
committed
feat(examples): add bezier-js integration example
1 parent 47bf303 commit 1ff3c8c

File tree

7 files changed

+300
-0
lines changed

7 files changed

+300
-0
lines changed

examples/bezier-js/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build/
2+
dist/
3+
node_modules/

examples/bezier-js/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# JointJS List Demo
2+
3+
## Setup
4+
5+
Use Yarn to run this demo.
6+
7+
You need to build *JointJS* first. Navigate to the root folder and run:
8+
```bash
9+
yarn install
10+
yarn run build
11+
```
12+
13+
Navigate to this directory, then run:
14+
```bash
15+
yarn start
16+
```
17+
18+
## License
19+
20+
The *JointJS* library is licensed under the [Mozilla Public License 2.0](https://github.com/clientIO/joint/blob/master/LICENSE).
21+
22+
Copyright © 2013-2025 client IO

examples/bezier-js/css/bezier.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#paper-container {
2+
position: absolute;
3+
right: 0;
4+
top: 0;
5+
left: 0;
6+
bottom: 0;
7+
overflow: scroll;
8+
}

examples/bezier-js/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8"/>
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6+
<meta name="description" content="The JointJS Bezier-JS example demo serves as a template to help bring your idea to life in no time."/>
7+
<title>Bezier JS Example | JointJS</title>
8+
</head>
9+
<body>
10+
<div id="paper-container"></div>
11+
<script src="dist/bundle.js"></script>
12+
</body>
13+
</html>

examples/bezier-js/package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@joint/demo-bezier-js",
3+
"version": "4.1.3",
4+
"main": "src/index.js",
5+
"homepage": "https://jointjs.com",
6+
"author": {
7+
"name": "client IO",
8+
"url": "https://client.io"
9+
},
10+
"license": "MPL-2.0",
11+
"private": true,
12+
"installConfig": {
13+
"hoistingLimits": "workspaces"
14+
},
15+
"scripts": {
16+
"start": "webpack-dev-server",
17+
"tsc": "tsc"
18+
},
19+
"dependencies": {
20+
"@joint/core": "workspace:^",
21+
"bezier-js": "^6.1.4"
22+
},
23+
"devDependencies": {
24+
"css-loader": "3.5.3",
25+
"style-loader": "1.2.1",
26+
"webpack": "^5.61.0",
27+
"webpack-cli": "^4.8.0",
28+
"webpack-dev-server": "^4.2.1"
29+
},
30+
"volta": {
31+
"node": "16.18.1",
32+
"npm": "8.19.2",
33+
"yarn": "3.4.1"
34+
}
35+
}

examples/bezier-js/src/index.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { dia, shapes, util, g, linkTools } from "@joint/core";
2+
import { Bezier } from "bezier-js";
3+
4+
import "../css/bezier.css";
5+
6+
class BezierLinkView extends dia.LinkView {
7+
updateDOMSubtreeAttributes() {
8+
const method = this.model.get("outline") ? outlinePath : offsetPath;
9+
10+
const thickness1 = 20;
11+
const thickness2 = 30;
12+
const thickness3 = 10;
13+
const fillOpacity = 0.3;
14+
const roundDecimals = 0;
15+
const strokeWidth = 2;
16+
const useFill = method !== offsetPath;
17+
18+
const { line1, line2, line3 } = this.selectors;
19+
20+
const path1 = this.getConnection();
21+
path1.round(roundDecimals);
22+
line1.setAttribute("stroke", "red");
23+
line1.setAttribute("stroke-width", strokeWidth);
24+
line1.setAttribute("fill", useFill ? "red" : "none");
25+
line1.setAttribute("fill-opacity", fillOpacity);
26+
line1.setAttribute("fill-rule", "nonzero");
27+
line1.setAttribute("d", method(path1, thickness1));
28+
29+
const path2 = new g.Path(offsetPath(path1, thickness1 + thickness2));
30+
path2.round(roundDecimals);
31+
line2.setAttribute("stroke", "blue");
32+
line2.setAttribute("stroke-width", strokeWidth);
33+
line2.setAttribute("fill", useFill ? "blue" : "none");
34+
line2.setAttribute("fill-opacity", fillOpacity);
35+
line2.setAttribute("fill-rule", "nonzero");
36+
line2.setAttribute("d", method(path2, thickness2));
37+
38+
const path3 = new g.Path(offsetPath(path1, -(thickness1 + thickness3)));
39+
path3.round(roundDecimals);
40+
line3.setAttribute("stroke", "green");
41+
line3.setAttribute("stroke-width", strokeWidth);
42+
line3.setAttribute("fill", useFill ? "green" : "none");
43+
line3.setAttribute("fill-opacity", fillOpacity);
44+
line3.setAttribute("fill-rule", "nonzero");
45+
line3.setAttribute("d", method(path3, thickness3));
46+
}
47+
}
48+
49+
const graph = new dia.Graph({}, { cellNamespace: shapes });
50+
const paper = new dia.Paper({
51+
width: "100%",
52+
height: "100%",
53+
model: graph,
54+
overflow: true,
55+
cellViewNamespace: shapes,
56+
linkView: BezierLinkView,
57+
});
58+
59+
document.getElementById("paper-container").appendChild(paper.el);
60+
61+
const link1 = new dia.Link({
62+
type: "bezier",
63+
source: { x: 40, y: 100 },
64+
target: { x: 740, y: 100 },
65+
vertices: [{ x: 401, y: 208 }],
66+
// vertices: [{ x: 422, y: 417 }], // rounding issues
67+
// vertices: [{ x: 204, y: 63 }], // can not create offset
68+
connector: { name: "smooth" },
69+
markup: util.svg`
70+
<path @selector="line1" />
71+
<path @selector="line2" />
72+
<path @selector="line3" />
73+
`,
74+
});
75+
76+
const link2 = link1.clone().translate(0, 300).set("outline", true);
77+
graph.resetCells([link1, link2]);
78+
79+
function offsetPath(path, offset) {
80+
const offsetBezierCurves = pathToBezierCurves(path)
81+
.map((bezier) => {
82+
const polyBezier = bezier.offset(offset);
83+
return polyBezier.map((b) => {
84+
if (isNaN(b.points[0].x)) {
85+
console.warn("Unable to create offset", bezier);
86+
return bezier;
87+
}
88+
return b;
89+
});
90+
})
91+
.flat();
92+
let d = "";
93+
for (let i = 0; i < offsetBezierCurves.length; i++) {
94+
d += offsetBezierCurves[i].toSVG();
95+
}
96+
return d;
97+
}
98+
99+
function outlinePath(path, o) {
100+
const outlines = pathToBezierCurves(path).map((curve1) => {
101+
let curves;
102+
try {
103+
curves = curve1.outline(o).curves;
104+
} catch (e) {
105+
console.warn("Caught exception in bezier-js", curve1);
106+
return curve1;
107+
}
108+
return curves.map((curve2) => {
109+
if (isNaN(curve2.points[0].x)) {
110+
console.warn("Unable to create outline", curve1);
111+
return curve1;
112+
}
113+
return curve2;
114+
});
115+
});
116+
let d = "M 0 0";
117+
for (let i = 0; i < outlines.length; i++) {
118+
const outline = outlines[i];
119+
for (let j = 0; j < outline.length; j++) {
120+
let segmentPath = outline[j].toSVG();
121+
if (j > 0) {
122+
// Remove the first moveTo command
123+
let index = segmentPath.search(/[C,Q]/);
124+
if (index > 0) {
125+
segmentPath = segmentPath.slice(index);
126+
}
127+
}
128+
d += segmentPath;
129+
}
130+
}
131+
d += "Z";
132+
return d;
133+
}
134+
135+
function pathToBezierCurves(path) {
136+
const segments = path.segments;
137+
const bezierCurves = [];
138+
for (let i = 0; i < segments.length; i++) {
139+
const curve = segments[i];
140+
// Note: JointJS path use only absolute commands
141+
// it's safe to ignore all
142+
if (curve.type === "M") continue;
143+
const {
144+
start,
145+
end,
146+
controlPoint1 = start,
147+
controlPoint2 = end,
148+
} = curve;
149+
const bezier = new Bezier(
150+
start.x,
151+
start.y,
152+
controlPoint1.x,
153+
controlPoint1.y,
154+
controlPoint2.x,
155+
controlPoint2.y,
156+
end.x,
157+
end.y
158+
);
159+
bezierCurves.push(bezier);
160+
}
161+
return bezierCurves;
162+
}
163+
164+
// Interactions
165+
166+
graph.getLinks().forEach((link) => {
167+
const tools = [
168+
new linkTools.Vertices({
169+
vertexAdding: true,
170+
redundancyRemoval: false,
171+
}),
172+
new linkTools.SourceArrowhead(),
173+
new linkTools.TargetArrowhead(),
174+
];
175+
176+
link.findView(paper).addTools(
177+
new dia.ToolsView({
178+
tools: tools,
179+
})
180+
);
181+
});
182+
183+
// Failing examples from bezier-js
184+
185+
// const b1 = new Bezier(40, 100, 159, 323, 277, 545, 394, 545);
186+
// console.log(b1.outline(50));
187+
// console.log(b1.outlineshapes(50));
188+
189+
// const b2 = new Bezier(100, 100, 100, 100, 200, 200, 200, 200);
190+
// console.log(b2.outline(50).curves);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const path = require('path');
2+
3+
module.exports = {
4+
resolve: {
5+
extensions: ['.js']
6+
},
7+
entry: './src/index.js',
8+
output: {
9+
filename: 'bundle.js',
10+
path: path.resolve(__dirname, 'dist'),
11+
publicPath: '/dist/'
12+
},
13+
mode: 'development',
14+
module: {
15+
rules: [
16+
{
17+
test: /\.css$/,
18+
sideEffects: true,
19+
use: ['style-loader', 'css-loader'],
20+
}
21+
]
22+
},
23+
devServer: {
24+
static: {
25+
directory: __dirname,
26+
},
27+
compress: true
28+
},
29+
};

0 commit comments

Comments
 (0)