@ngroznykh/papirus 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/papirus.js +124 -20
- package/dist/papirus.js.map +1 -1
- package/dist/utils/SvgExporter.d.ts +10 -1
- package/dist/utils/SvgExporter.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/papirus.js
CHANGED
|
@@ -8104,6 +8104,7 @@ class SvgExporter {
|
|
|
8104
8104
|
const padding = options.padding ?? 20;
|
|
8105
8105
|
const includeBackground = options.includeBackground ?? true;
|
|
8106
8106
|
const backgroundColor = options.backgroundColor ?? "#ffffff";
|
|
8107
|
+
const edgeLabelOffset = options.edgeLabelOffset ?? 0;
|
|
8107
8108
|
const bounds = getContentBounds({
|
|
8108
8109
|
nodes: this.renderer.nodes.values(),
|
|
8109
8110
|
edges: this.renderer.edges.values(),
|
|
@@ -8123,10 +8124,15 @@ class SvgExporter {
|
|
|
8123
8124
|
this.renderer.nodes.values()
|
|
8124
8125
|
);
|
|
8125
8126
|
const parts = [];
|
|
8127
|
+
const renderedEdges = [];
|
|
8126
8128
|
parts.push(
|
|
8127
8129
|
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`
|
|
8128
8130
|
);
|
|
8129
|
-
|
|
8131
|
+
for (const edge of this.renderer.edges.values()) {
|
|
8132
|
+
if (edge.visible) {
|
|
8133
|
+
renderedEdges.push(this.renderEdge(edge, edgeLabelOffset));
|
|
8134
|
+
}
|
|
8135
|
+
}
|
|
8130
8136
|
if (includeBackground) {
|
|
8131
8137
|
parts.push(`<rect width="100%" height="100%" fill="${backgroundColor}"/>`);
|
|
8132
8138
|
}
|
|
@@ -8136,11 +8142,7 @@ class SvgExporter {
|
|
|
8136
8142
|
parts.push(this.renderGroup(group));
|
|
8137
8143
|
}
|
|
8138
8144
|
}
|
|
8139
|
-
|
|
8140
|
-
if (edge.visible) {
|
|
8141
|
-
parts.push(this.renderEdge(edge));
|
|
8142
|
-
}
|
|
8143
|
-
}
|
|
8145
|
+
parts.push(...renderedEdges);
|
|
8144
8146
|
for (const node of this.renderer.nodes.values()) {
|
|
8145
8147
|
if (node.visible) {
|
|
8146
8148
|
parts.push(this.renderNode(node));
|
|
@@ -8218,7 +8220,7 @@ class SvgExporter {
|
|
|
8218
8220
|
label
|
|
8219
8221
|
].join("");
|
|
8220
8222
|
}
|
|
8221
|
-
renderEdge(edge) {
|
|
8223
|
+
renderEdge(edge, edgeLabelOffset) {
|
|
8222
8224
|
const path = edge.path;
|
|
8223
8225
|
if (path.length < 2) {
|
|
8224
8226
|
return "";
|
|
@@ -8231,10 +8233,114 @@ class SvgExporter {
|
|
|
8231
8233
|
const dash = dashValues ? ` stroke-dasharray="${dashValues.join(" ")}"` : "";
|
|
8232
8234
|
const dashOffset = style.lineDashOffset !== void 0 ? ` stroke-dashoffset="${style.lineDashOffset}"` : "";
|
|
8233
8235
|
const d = this.buildPath(edge);
|
|
8234
|
-
const
|
|
8235
|
-
const
|
|
8236
|
-
|
|
8237
|
-
|
|
8236
|
+
const markerShapes = this.renderEdgeMarkers(edge, stroke);
|
|
8237
|
+
const label = edge.label ? this.renderTextLabel(edge.label.text, this.getEdgeLabelPoint(edge, edgeLabelOffset), edge.label.style) : "";
|
|
8238
|
+
return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" opacity="${opacity}" color="${stroke}"${dash}${dashOffset}/>${markerShapes}${label}`;
|
|
8239
|
+
}
|
|
8240
|
+
resolveMarkerConfig(edge, side) {
|
|
8241
|
+
const marker = side === "start" ? edge.startMarker : edge.endMarker;
|
|
8242
|
+
if (marker && marker.type !== "none") {
|
|
8243
|
+
return marker;
|
|
8244
|
+
}
|
|
8245
|
+
if (side === "end") {
|
|
8246
|
+
return edge.arrowType === "none" ? null : { type: "arrow" };
|
|
8247
|
+
}
|
|
8248
|
+
return edge.arrowType === "double" ? { type: "arrow" } : null;
|
|
8249
|
+
}
|
|
8250
|
+
renderEdgeMarkers(edge, edgeStroke) {
|
|
8251
|
+
const startMarker = this.resolveMarkerConfig(edge, "start");
|
|
8252
|
+
const endMarker = this.resolveMarkerConfig(edge, "end");
|
|
8253
|
+
const parts = [];
|
|
8254
|
+
if (endMarker) {
|
|
8255
|
+
const points = this.getMarkerPoints(edge, "end");
|
|
8256
|
+
if (points) {
|
|
8257
|
+
parts.push(this.renderMarkerShape(endMarker, points.from, points.to, edgeStroke));
|
|
8258
|
+
}
|
|
8259
|
+
}
|
|
8260
|
+
if (startMarker) {
|
|
8261
|
+
const points = this.getMarkerPoints(edge, "start");
|
|
8262
|
+
if (points) {
|
|
8263
|
+
parts.push(this.renderMarkerShape(startMarker, points.from, points.to, edgeStroke));
|
|
8264
|
+
}
|
|
8265
|
+
}
|
|
8266
|
+
return parts.join("");
|
|
8267
|
+
}
|
|
8268
|
+
getMarkerPoints(edge, position) {
|
|
8269
|
+
const path = edge.path;
|
|
8270
|
+
if (path.length < 2) {
|
|
8271
|
+
return null;
|
|
8272
|
+
}
|
|
8273
|
+
if (edge.type === "bezier" && path.length >= 4) {
|
|
8274
|
+
const epsilon = 1e-3;
|
|
8275
|
+
const isSame = (a, b) => Math.abs(a.x - b.x) < epsilon && Math.abs(a.y - b.y) < epsilon;
|
|
8276
|
+
if (position === "end") {
|
|
8277
|
+
const endIndex = path.length - 1;
|
|
8278
|
+
const endPoint = path[endIndex];
|
|
8279
|
+
let from = path[endIndex - 1];
|
|
8280
|
+
if (isSame(from, endPoint)) {
|
|
8281
|
+
from = path[endIndex - 2];
|
|
8282
|
+
if (isSame(from, endPoint)) {
|
|
8283
|
+
from = path[0];
|
|
8284
|
+
}
|
|
8285
|
+
}
|
|
8286
|
+
return { from, to: endPoint };
|
|
8287
|
+
}
|
|
8288
|
+
const start = path[0];
|
|
8289
|
+
let next = path[1];
|
|
8290
|
+
if (isSame(next, start)) {
|
|
8291
|
+
next = path[2];
|
|
8292
|
+
if (isSame(next, start)) {
|
|
8293
|
+
next = path[path.length - 1];
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
return { from: next, to: start };
|
|
8297
|
+
}
|
|
8298
|
+
if (position === "end") {
|
|
8299
|
+
return { from: path[path.length - 2], to: path[path.length - 1] };
|
|
8300
|
+
}
|
|
8301
|
+
return { from: path[1], to: path[0] };
|
|
8302
|
+
}
|
|
8303
|
+
renderMarkerShape(marker, from, to, edgeStroke) {
|
|
8304
|
+
const angle2 = Math.atan2(to.y - from.y, to.x - from.x);
|
|
8305
|
+
const size = marker.size ?? 12;
|
|
8306
|
+
const stroke = marker.strokeColor ?? edgeStroke;
|
|
8307
|
+
const fill = marker.fillColor ?? stroke;
|
|
8308
|
+
const fillOpacity = marker.fillOpacity ?? 1;
|
|
8309
|
+
switch (marker.type) {
|
|
8310
|
+
case "open": {
|
|
8311
|
+
const x1 = to.x - size * Math.cos(angle2 - ARROW_ANGLE);
|
|
8312
|
+
const y1 = to.y - size * Math.sin(angle2 - ARROW_ANGLE);
|
|
8313
|
+
const x2 = to.x - size * Math.cos(angle2 + ARROW_ANGLE);
|
|
8314
|
+
const y2 = to.y - size * Math.sin(angle2 + ARROW_ANGLE);
|
|
8315
|
+
return `<path d="M ${to.x} ${to.y} L ${x1} ${y1} M ${to.x} ${to.y} L ${x2} ${y2}" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>`;
|
|
8316
|
+
}
|
|
8317
|
+
case "diamond": {
|
|
8318
|
+
const halfLength = size / 2;
|
|
8319
|
+
const halfWidth = size * 0.3;
|
|
8320
|
+
const cos = Math.cos(angle2);
|
|
8321
|
+
const sin = Math.sin(angle2);
|
|
8322
|
+
const p1x = to.x - halfLength * cos + halfWidth * sin;
|
|
8323
|
+
const p1y = to.y - halfLength * sin - halfWidth * cos;
|
|
8324
|
+
const backX = to.x - size * cos;
|
|
8325
|
+
const backY = to.y - size * sin;
|
|
8326
|
+
const p2x = to.x - halfLength * cos - halfWidth * sin;
|
|
8327
|
+
const p2y = to.y - halfLength * sin + halfWidth * cos;
|
|
8328
|
+
return `<path d="M ${to.x} ${to.y} L ${p1x} ${p1y} L ${backX} ${backY} L ${p2x} ${p2y} Z" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
|
|
8329
|
+
}
|
|
8330
|
+
case "circle": {
|
|
8331
|
+
const cx = to.x - size * Math.cos(angle2);
|
|
8332
|
+
const cy = to.y - size * Math.sin(angle2);
|
|
8333
|
+
return `<circle cx="${cx}" cy="${cy}" r="${size}" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
|
|
8334
|
+
}
|
|
8335
|
+
case "arrow":
|
|
8336
|
+
default: {
|
|
8337
|
+
const x1 = to.x - size * Math.cos(angle2 - ARROW_ANGLE);
|
|
8338
|
+
const y1 = to.y - size * Math.sin(angle2 - ARROW_ANGLE);
|
|
8339
|
+
const x2 = to.x - size * Math.cos(angle2 + ARROW_ANGLE);
|
|
8340
|
+
const y2 = to.y - size * Math.sin(angle2 + ARROW_ANGLE);
|
|
8341
|
+
return `<path d="M ${to.x} ${to.y} L ${x1} ${y1} L ${x2} ${y2} Z" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8238
8344
|
}
|
|
8239
8345
|
buildPath(edge) {
|
|
8240
8346
|
const path = edge.path;
|
|
@@ -8284,6 +8390,13 @@ class SvgExporter {
|
|
|
8284
8390
|
}
|
|
8285
8391
|
return path[0];
|
|
8286
8392
|
}
|
|
8393
|
+
getEdgeLabelPoint(edge, edgeLabelOffset) {
|
|
8394
|
+
const midpoint = this.getPathMidpoint(edge);
|
|
8395
|
+
return {
|
|
8396
|
+
x: midpoint.x,
|
|
8397
|
+
y: midpoint.y + edgeLabelOffset
|
|
8398
|
+
};
|
|
8399
|
+
}
|
|
8287
8400
|
renderTextLabel(text, point, style = {}) {
|
|
8288
8401
|
const fill = style.color ?? "#000000";
|
|
8289
8402
|
const fontSize = style.fontSize ?? 14;
|
|
@@ -8295,15 +8408,6 @@ class SvgExporter {
|
|
|
8295
8408
|
text
|
|
8296
8409
|
)}</text>`;
|
|
8297
8410
|
}
|
|
8298
|
-
arrowDefs() {
|
|
8299
|
-
return [
|
|
8300
|
-
"<defs>",
|
|
8301
|
-
'<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">',
|
|
8302
|
-
'<path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor"/>',
|
|
8303
|
-
"</marker>",
|
|
8304
|
-
"</defs>"
|
|
8305
|
-
].join("");
|
|
8306
|
-
}
|
|
8307
8411
|
createEmptySvg(width, height, backgroundColor, includeBackground) {
|
|
8308
8412
|
return [
|
|
8309
8413
|
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,
|