Compare commits

...

9 Commits

Author SHA1 Message Date
Knut Sveidqvist
8277579259 #6097 Handling hourglass and lightningBolt 2024-12-03 10:49:43 +01:00
Knut Sveidqvist
8336d1cf2d #6097 Next shapes, learn-right and card 2024-12-03 10:23:02 +01:00
Knut Sveidqvist
3c93e4640a #6097 Next batch of shapes 2024-11-30 13:42:50 +01:00
Knut Sveidqvist
0c28593ea5 #6097 Implemented calcIntersect for the first round of shapes 2024-11-30 10:54:18 +01:00
Knut Sveidqvist
33d8b1a78d #6097 Added scaffolding for the new function in all shapes 2024-11-30 09:49:54 +01:00
Knut Sveidqvist
1e3ea13323 Fix for when last point is on the intersection 2024-11-29 09:07:47 +01:00
Knut Sveidqvist
4c8c48cde9 Generic solution for intersection of shapes with elk 2024-11-28 14:31:54 +01:00
Knut Sveidqvist
c8e50276e8 Added changeset 2024-11-27 15:54:05 +01:00
Knut Sveidqvist
1e6419a63f #6088 Updated offset calculations 2024-11-27 15:52:24 +01:00
68 changed files with 1338 additions and 621 deletions

View File

@@ -0,0 +1,5 @@
---
'@mermaid-js/layout-elk': patch
---
fix: Updated offset calculations for diamond shape when handling intersections

View File

@@ -900,6 +900,153 @@ flowchart LR
n7@{ shape: rect}
n8@{ shape: rect}
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('6088-1: should handle diamond shape intersections', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
subgraph S2
subgraph s1["APA"]
D{"Use the editor"}
end
D -- Mermaid js --> I{"fa:fa-code Text"}
D --> I
D --> I
end
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('6088-2: should handle diamond shape intersections', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
a
subgraph s0["APA"]
subgraph s8["APA"]
subgraph s1["APA"]
D{"X"}
E[Q]
end
subgraph s3["BAPA"]
F[Q]
I
end
D --> I
D --> I
D --> I
I{"X"}
end
end
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('6088-3: should handle diamond shape intersections', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
a
D{"Use the editor"}
D -- Mermaid js --> I{"fa:fa-code Text"}
D-->I
D-->I
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('6088-4: should handle diamond shape intersections', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
n3["Option 2"]
n4["fa:fa-car Option 3"]
end
subgraph s2["Untitled subgraph"]
n5["Evaluate"]
n6["Option 1"]
n7["Option 2"]
n8["fa:fa-car Option 3"]
end
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
n1 -- One --> n2
n1 -- Two --> n3
n1 -- Three --> n4
n5 -- One --> n6
n5 -- Two --> n7
n5 -- Three --> n8
n1@{ shape: diam}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
n5@{ shape: diam}
n6@{ shape: rect}
n7@{ shape: rect}
n8@{ shape: rect}
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('6088-5: should handle diamond shape intersections', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
A{A} --> B & C
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('6088-6: should handle diamond shape intersections', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
A{A} --> B & C
subgraph "subbe"
A
end
`,
{ flowchart: { titleTopMargin: 0 } }
);

View File

@@ -88,33 +88,84 @@
</head>
<body>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
n3["Option 2"]
n4["fa:fa-car Option 3"]
end
n1 -- One --> n2
n1 -- Two --> n3
n1 -- Three --> n4
n5
n1@{ shape: diam}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
%% subgraph s1["Untitled subgraph"]
C{"Evaluate"}
%% end
B --> C
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart LR
%% A ==> B
%% A2 --> B2
D --> I((I the Circle))
D --> I
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart LR
subgraph S2
subgraph s1["APA"]
D{"Use the editor"}
end
D -- Mermaid js --> I(("fa:fa-code Text"))
D --> I
D --> E --> I
end
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart LR
a
subgraph s0["APA"]
subgraph s8["APA"]
subgraph s1["APA"]
D{"X"}
E[Q]
end
subgraph s3["BAPA"]
F[Q]
I
end
D --> I
D --> I
D --> I
I{"X"}
end
end
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart LR
a
D{"Use the editor"}
D -- Mermaid js --> I{"fa:fa-code Text"}
D-->I
D-->I
</pre>
<pre id="diagram4" class="mermaid">
---
@@ -155,7 +206,7 @@ flowchart LR
n8@{ shape: rect}
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -171,7 +222,7 @@ flowchart LR
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -180,7 +231,19 @@ flowchart LR
A{A} --> B & C
</pre
>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart LR
A{A} --> B & C
subgraph "subbe"
A
end
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
@@ -198,7 +261,7 @@ flowchart LR
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
kanban:
@@ -217,81 +280,81 @@ kanban
task3[💻 Develop login feature]@{ ticket: 103 }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
flowchart LR
nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
kanban
id2[In progress]
docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
</pre>
<pre id="diagram4" class="mermaid2">
<pre id="diagram4" class="mermaid">
---
config:
kanban:

View File

@@ -0,0 +1,102 @@
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: 'Arial';
background-color: #333;
}
</style>
</head>
<body>
<div id="diagram"></div>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';
mermaid.registerLayoutLoaders(layouts);
mermaid.parseError = function (err, hash) {
console.error('Mermaid error: ', err);
};
mermaid.initialize({
startOnLoad: false,
//look: 'handdrawn',
// layout: 'fixed',
theme: 'dark',
//layout: 'elk',
fontFamily: 'Kalam',
logLevel: 1,
});
let shape = 'card';
// let simplified = true;
let simplified = false;
let algorithm = 'elk';
// let algorithm = 'dagre';
let code = `---
config:
layout: ${algorithm}
---
flowchart TD
A["Abrakadabra"] --> C["C"] & D["I am a circle"] & n4["Untitled Node"]
D@{ shape: diamond}
B["Bombrakadombra"] --> D & C & D
C --> E["E"] & B
D --> E & A
n4 --> C
A@{ shape: ${shape}}
B@{ shape: ${shape}}
C@{ shape: ${shape}}
D@{ shape: ${shape}}
E@{ shape: ${shape}}
n4@{ shape: ${shape}}
`;
if (simplified) {
code = `---
config:
layout: ${algorithm}
---
flowchart LR
A["Abrakadabra"] --> C["C"] & C & C & C & C
%% A["Abrakadabra"] --> C
A@{ shape: ${shape}}
C@{ shape: ${shape}}
`;
}
console.log(code);
const { svg } = await mermaid.render('the-id-of-the-svg', code, undefined, undefined);
const elem = document.querySelector('#diagram');
elem.innerHTML = svg;
</script>
</body>
</html>

View File

@@ -4,7 +4,8 @@ import type { InternalHelpers, LayoutData, RenderOptions, SVG, SVGGroup } from '
import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
type Node = LayoutData['nodes'][number];
// Used to calculate distances in order to avoid floating number rounding issues when comparing floating numbers
const epsilon = 0.0001;
interface LabelData {
width: number;
height: number;
@@ -17,7 +18,16 @@ interface NodeWithVertex extends Omit<Node, 'domId'> {
labelData?: LabelData;
domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
}
interface Point {
x: number;
y: number;
}
function distance(p1?: Point, p2?: Point): number {
if (!p1 || !p2) {
return 0;
}
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}
export const render = async (
data4Layout: LayoutData,
svg: SVG,
@@ -60,6 +70,7 @@ export const render = async (
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
const boundingBox = childNodeEl.node()!.getBBox();
child.domId = childNodeEl;
child.calcIntersect = node.calcIntersect;
child.width = boundingBox.width;
child.height = boundingBox.height;
} else {
@@ -459,305 +470,6 @@ export const render = async (
}
}
function intersectLine(
p1: { y: number; x: number },
p2: { y: number; x: number },
q1: { x: any; y: any },
q2: { x: any; y: any }
) {
log.debug('UIO intersectLine', p1, p2, q1, q2);
// Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
// p7 and p473.
// let a1, a2, b1, b2, c1, c2;
// let r1, r2, r3, r4;
// let denom, offset, num;
// let x, y;
// Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
// b1 y + c1 = 0.
const a1 = p2.y - p1.y;
const b1 = p1.x - p2.x;
const c1 = p2.x * p1.y - p1.x * p2.y;
// Compute r3 and r4.
const r3 = a1 * q1.x + b1 * q1.y + c1;
const r4 = a1 * q2.x + b1 * q2.y + c1;
const epsilon = 1e-6;
// Check signs of r3 and r4. If both point 3 and point 4 lie on
// same side of line 1, the line segments do not intersect.
if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
return /*DON'T_INTERSECT*/;
}
// Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
const a2 = q2.y - q1.y;
const b2 = q1.x - q2.x;
const c2 = q2.x * q1.y - q1.x * q2.y;
// Compute r1 and r2
const r1 = a2 * p1.x + b2 * p1.y + c2;
const r2 = a2 * p2.x + b2 * p2.y + c2;
// Check signs of r1 and r2. If both point 1 and point 2 lie
// on same side of second line segment, the line segments do
// not intersect.
if (Math.abs(r1) < epsilon && Math.abs(r2) < epsilon && sameSign(r1, r2)) {
return /*DON'T_INTERSECT*/;
}
// Line segments intersect: compute intersection point.
const denom = a1 * b2 - a2 * b1;
if (denom === 0) {
return /*COLLINEAR*/;
}
const offset = Math.abs(denom / 2);
// The denom/2 is to get rounding instead of truncating. It
// is added or subtracted to the numerator, depending upon the
// sign of the numerator.
let num = b1 * c2 - b2 * c1;
const x = num < 0 ? (num - offset) / denom : (num + offset) / denom;
num = a2 * c1 - a1 * c2;
const y = num < 0 ? (num - offset) / denom : (num + offset) / denom;
return { x: x, y: y };
}
function sameSign(r1: number, r2: number) {
return r1 * r2 > 0;
}
const diamondIntersection = (
bounds: { x: any; y: any; width: any; height: any },
outsidePoint: { x: number; y: number },
insidePoint: any
) => {
const x1 = bounds.x;
const y1 = bounds.y;
const w = bounds.width; //+ bounds.padding;
const h = bounds.height; // + bounds.padding;
const polyPoints = [
{ x: x1, y: y1 - h / 2 },
{ x: x1 + w / 2, y: y1 },
{ x: x1, y: y1 + h / 2 },
{ x: x1 - w / 2, y: y1 },
];
log.debug(
`APA16 diamondIntersection calc abc89:
outsidePoint: ${JSON.stringify(outsidePoint)}
insidePoint : ${JSON.stringify(insidePoint)}
node-bounds : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`,
JSON.stringify(polyPoints)
);
const intersections = [];
let minX = Number.POSITIVE_INFINITY;
let minY = Number.POSITIVE_INFINITY;
polyPoints.forEach(function (entry) {
minX = Math.min(minX, entry.x);
minY = Math.min(minY, entry.y);
});
const left = x1 - w / 2 - minX;
const top = y1 - h / 2 - minY;
for (let i = 0; i < polyPoints.length; i++) {
const p1 = polyPoints[i];
const p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
const intersect = intersectLine(
bounds,
outsidePoint,
{ x: left + p1.x, y: top + p1.y },
{ x: left + p2.x, y: top + p2.y }
);
if (intersect) {
intersections.push(intersect);
}
}
if (!intersections.length) {
return bounds;
}
log.debug('UIO intersections', intersections);
if (intersections.length > 1) {
// More intersections, find the one nearest to edge end point
intersections.sort(function (p, q) {
const pdx = p.x - outsidePoint.x;
const pdy = p.y - outsidePoint.y;
const distp = Math.sqrt(pdx * pdx + pdy * pdy);
const qdx = q.x - outsidePoint.x;
const qdy = q.y - outsidePoint.y;
const distq = Math.sqrt(qdx * qdx + qdy * qdy);
return distp < distq ? -1 : distp === distq ? 0 : 1;
});
}
return intersections[0];
};
const intersection = (
node: { x: any; y: any; width: number; height: number },
outsidePoint: { x: number; y: number },
insidePoint: { x: number; y: number }
) => {
log.debug(`intersection calc abc89:
outsidePoint: ${JSON.stringify(outsidePoint)}
insidePoint : ${JSON.stringify(insidePoint)}
node : x:${node.x} y:${node.y} w:${node.width} h:${node.height}`);
const x = node.x;
const y = node.y;
const dx = Math.abs(x - insidePoint.x);
// const dy = Math.abs(y - insidePoint.y);
const w = node.width / 2;
let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
const h = node.height / 2;
const Q = Math.abs(outsidePoint.y - insidePoint.y);
const R = Math.abs(outsidePoint.x - insidePoint.x);
if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
// Intersection is top or bottom of rect.
const q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
r = (R * q) / Q;
const res = {
x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r,
y: insidePoint.y < outsidePoint.y ? insidePoint.y + Q - q : insidePoint.y - Q + q,
};
if (r === 0) {
res.x = outsidePoint.x;
res.y = outsidePoint.y;
}
if (R === 0) {
res.x = outsidePoint.x;
}
if (Q === 0) {
res.y = outsidePoint.y;
}
log.debug(`abc89 topp/bott calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res); // cspell: disable-line
return res;
} else {
// Intersection onn sides of rect
if (insidePoint.x < outsidePoint.x) {
r = outsidePoint.x - w - x;
} else {
// r = outsidePoint.x - w - x;
r = x - w - outsidePoint.x;
}
const q = (Q * r) / R;
// OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x + dx - w;
// OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
// let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
log.debug(`sides calc abc89, Q ${Q}, q ${q}, R ${R}, r ${r}`, { _x, _y });
if (r === 0) {
_x = outsidePoint.x;
_y = outsidePoint.y;
}
if (R === 0) {
_x = outsidePoint.x;
}
if (Q === 0) {
_y = outsidePoint.y;
}
return { x: _x, y: _y };
}
};
const outsideNode = (
node: { x: any; y: any; width: number; height: number },
point: { x: number; y: number }
) => {
const x = node.x;
const y = node.y;
const dx = Math.abs(point.x - x);
const dy = Math.abs(point.y - y);
const w = node.width / 2;
const h = node.height / 2;
if (dx >= w || dy >= h) {
return true;
}
return false;
};
/**
* This function will page a path and node where the last point(s) in the path is inside the node
* and return an update path ending by the border of the node.
*/
const cutPathAtIntersect = (
_points: any[],
bounds: { x: any; y: any; width: any; height: any; padding: any },
isDiamond: boolean
) => {
log.debug('UIO cutPathAtIntersect Points:', _points, 'node:', bounds, 'isDiamond', isDiamond);
const points: any[] = [];
let lastPointOutside = _points[0];
let isInside = false;
_points.forEach((point: any) => {
// const node = clusterDb[edge.toCluster].node;
log.debug(' checking point', point, bounds);
// check if point is inside the boundary rect
if (!outsideNode(bounds, point) && !isInside) {
// First point inside the rect found
// Calc the intersection coord between the point anf the last point outside the rect
let inter;
if (isDiamond) {
const inter2 = diamondIntersection(bounds, lastPointOutside, point);
const distance = Math.sqrt(
(lastPointOutside.x - inter2.x) ** 2 + (lastPointOutside.y - inter2.y) ** 2
);
if (distance > 1) {
inter = inter2;
}
}
if (!inter) {
inter = intersection(bounds, lastPointOutside, point);
}
// Check case where the intersection is the same as the last point
let pointPresent = false;
points.forEach((p) => {
pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y);
});
// if (!pointPresent) {
if (!points.some((e) => e.x === inter.x && e.y === inter.y)) {
points.push(inter);
} else {
log.debug('abc88 no intersect', inter, points);
}
// points.push(inter);
isInside = true;
} else {
// Outside
log.debug('abc88 outside', point, lastPointOutside, points);
lastPointOutside = point;
// points.push(point);
if (!isInside) {
points.push(point);
}
}
});
return points;
};
// @ts-ignore - ELK is not typed
const elk = new ELK();
const element = svg.select('g');
@@ -880,9 +592,10 @@ export const render = async (
setIncludeChildrenPolicy(target, ancestorId);
}
});
// const copy = JSON.parse(JSON.stringify({ ...elkGraph }));
// console.log('APA13 layout before', copy);
const g = await elk.layout(elkGraph);
// console.log('APA13 layout', JSON.parse(JSON.stringify(g)));
// debugger;
await drawNodes(0, 0, g.children, svg, subGraphsEl, 0);
g.edges?.map(
@@ -906,7 +619,7 @@ export const render = async (
const offset = calcOffset(sourceId, targetId, parentLookupDb);
log.debug(
'offset',
'APA18 offset',
offset,
sourceId,
' ==> ',
@@ -969,49 +682,58 @@ export const render = async (
startNode.innerHTML
);
}
if (startNode.shape === 'diamond' || startNode.shape === 'diam') {
edge.points.unshift({
x: startNode.x + startNode.width / 2 + offset.x,
y: startNode.y + startNode.height / 2 + offset.y,
});
}
if (endNode.shape === 'diamond' || endNode.shape === 'diam') {
const x = endNode.x + endNode.width / 2 + offset.x;
// Add a point at the center of the diamond
if (
Math.abs(edge.points[edge.points.length - 1].y - endNode.y - offset.y) > 0.01 ||
Math.abs(edge.points[edge.points.length - 1].x - x) > 0.001
) {
edge.points.push({
x: endNode.x + endNode.width / 2 + offset.x,
y: endNode.y + endNode.height / 2 + offset.y,
});
if (startNode.calcIntersect) {
// console.log(
// 'APA13 calculating start intersection start node',
// startNode.id,
// startNode.x,
// startNode.y,
// 'w:',
// startNode.width,
// 'h:',
// startNode.height,
// '\nPos',
// edge.points[0]
// );
const intersection = startNode.calcIntersect(
{
x: startNode.offset.posX + startNode.width / 2,
y: startNode.offset.posY + startNode.height / 2,
width: startNode.width,
height: startNode.height,
},
edge.points[0]
);
if (distance(intersection, edge.points[0]) > epsilon) {
edge.points.unshift(intersection);
}
}
if (endNode.calcIntersect) {
const intersection = endNode.calcIntersect(
{
x: endNode.offset.posX + endNode.width / 2,
y: endNode.offset.posY + endNode.height / 2,
width: endNode.width,
height: endNode.height,
},
edge.points[edge.points.length - 1]
);
// if (edge.id === 'L_n4_C_10_0') {
// console.log('APA14 lineData', edge.points, 'intersection:', intersection);
// console.log(
// 'APA14! calculating end intersection\ndistance:',
// distance(intersection, edge.points[edge.points.length - 1])
// );
// }
edge.points = cutPathAtIntersect(
edge.points.reverse(),
{
x: startNode.x + startNode.width / 2 + offset.x,
y: startNode.y + startNode.height / 2 + offset.y,
width: sw,
height: startNode.height,
padding: startNode.padding,
},
startNode.shape === 'diamond' || startNode.shape === 'diam'
).reverse();
edge.points = cutPathAtIntersect(
edge.points,
{
x: endNode.x + ew / 2 + endNode.offset.x,
y: endNode.y + endNode.height / 2 + endNode.offset.y,
width: ew,
height: endNode.height,
padding: endNode.padding,
},
endNode.shape === 'diamond' || endNode.shape === 'diam'
);
if (distance(intersection, edge.points[edge.points.length - 1]) > epsilon) {
// console.log('APA13! distance ok\nintersection:', intersection);
edge.points.push(intersection);
// console.log('APA13! distance ok\npoints:', edge.points);
}
}
const paths = insertEdge(
edgesEl,
@@ -1022,7 +744,6 @@ export const render = async (
endNode,
data4Layout.diagramId
);
log.info('APA12 edge points after insert', JSON.stringify(edge.points));
edge.x = edge.labels[0].x + offset.x + edge.labels[0].width / 2;
edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2;

View File

@@ -5,7 +5,8 @@ import { createText } from '../createText.js';
import utils from '../../utils.js';
import { getLineFunctionsWithOffset } from '../../utils/lineWithOffset.js';
import { getSubGraphTitleMargins } from '../../utils/subGraphTitleMargins.js';
import { curveBasis, line, select } from 'd3';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { curveBasis, curveLinear, line, select } from 'd3';
import rough from 'roughjs';
import createLabel from './createLabel.js';
import { addEdgeMarkers } from './edgeMarker.ts';
@@ -335,6 +336,40 @@ const cutPathAtIntersect = (_points, boundaryNode) => {
return points;
};
const adjustForArrowHeads = function (lineData, size = 5, shouldLog = false) {
const newLineData = [...lineData];
const lastPoint = lineData[lineData.length - 1];
const secondLastPoint = lineData[lineData.length - 2];
const distanceBetweenLastPoints = Math.sqrt(
(lastPoint.x - secondLastPoint.x) ** 2 + (lastPoint.y - secondLastPoint.y) ** 2
);
if (shouldLog) {
log.debug('APA14 distanceBetweenLastPoints', distanceBetweenLastPoints);
}
if (distanceBetweenLastPoints < size) {
// Calculate the direction vector from the last point to the second last point
const directionX = secondLastPoint.x - lastPoint.x;
const directionY = secondLastPoint.y - lastPoint.y;
// Normalize the direction vector
const magnitude = Math.sqrt(directionX ** 2 + directionY ** 2);
const normalizedX = directionX / magnitude;
const normalizedY = directionY / magnitude;
// Calculate the new position for the second last point
const adjustedSecondLastPoint = {
x: lastPoint.x + normalizedX * size,
y: lastPoint.y + normalizedY * size,
};
// Replace the second last point in the new line data
newLineData[newLineData.length - 2] = adjustedSecondLastPoint;
}
return newLineData;
};
function extractCornerPoints(points) {
const cornerPoints = [];
const cornerPointPositions = [];
@@ -422,6 +457,109 @@ const fixCorners = function (lineData) {
return newLineData;
};
export const generateRoundedPath = (points, radius, endPosition) => {
if (points.length < 2) {
return '';
}
// console.trace('here', points);
const path = [];
const startPoint = points[0];
path.push(`M ${startPoint.x},${startPoint.y}`);
for (let i = 1; i < points.length - 1; i++) {
const currPoint = points[i];
const nextPoint = points[i + 1];
const prevPoint = points[i - 1];
// Calculate vectors
const v1 = { x: currPoint.x - prevPoint.x, y: currPoint.y - prevPoint.y };
const v2 = { x: nextPoint.x - currPoint.x, y: nextPoint.y - currPoint.y };
// Normalize vectors
const v1Length = Math.hypot(v1.x, v1.y);
const v2Length = Math.hypot(v2.x, v2.y);
const v1Normalized = { x: v1.x / v1Length, y: v1.y / v1Length };
const v2Normalized = { x: v2.x / v2Length, y: v2.y / v2Length };
// Calculate tangent points
const tangentLength = Math.min(radius, v1Length / 2, v2Length / 2);
const tangent1 = {
x: currPoint.x - v1Normalized.x * tangentLength,
y: currPoint.y - v1Normalized.y * tangentLength,
};
const tangent2 = {
x: currPoint.x + v2Normalized.x * tangentLength,
y: currPoint.y + v2Normalized.y * tangentLength,
};
if (endPosition) {
const { bottomY, leftX, rightX, topY } = endPosition;
if (startPoint.pos === 'b' && tangent1.y > topY) {
tangent1.y = topY;
tangent2.y = topY;
currPoint.y = topY;
}
if (startPoint.pos === 't' && tangent1.y < bottomY) {
tangent1.y = bottomY;
tangent2.y = bottomY;
currPoint.y = bottomY;
}
if (startPoint.pos === 'l' && tangent1.x < rightX) {
tangent1.x = rightX;
tangent2.x = rightX;
currPoint.x = rightX;
}
if (startPoint.pos === 'r' && tangent1.x > leftX) {
tangent1.x = leftX;
tangent2.x = leftX;
currPoint.x = leftX;
}
if (tangent2.x && tangent2.y && tangent1.x && tangent1.y) {
path.push(
`L ${tangent1.x},${tangent1.y}`,
`Q ${currPoint.x},${currPoint.y} ${tangent2.x},${tangent2.y}`
);
}
} else {
if (tangent2.x && tangent2.y && tangent1.x && tangent1.y) {
path.push(
`L ${tangent1.x},${tangent1.y}`,
`Q ${currPoint.x},${currPoint.y} ${tangent2.x},${tangent2.y}`
);
}
}
}
// Last point
const lastPoint = points[points.length - 1];
if (endPosition) {
if (startPoint.pos === 'b') {
if (endPosition?.topY && points[1].y > endPosition?.topY && points[2].y > endPosition?.topY) {
points[1].y = endPosition?.topY;
points[2].y = endPosition?.topY;
}
path.push(`L ${lastPoint.x},${endPosition.topY}`);
}
if (startPoint.pos === 't') {
if (points[1].y < endPosition.bottomY) {
points[1].y = endPosition.bottomY;
points[2].y = endPosition.bottomY;
}
path.push(`L ${lastPoint.x},${endPosition.bottomY}`);
}
if (startPoint.pos === 'l') {
path.push(`L ${endPosition.rightX},${lastPoint.y}`);
}
if (startPoint.pos === 'r') {
path.push(`L ${endPosition.leftX},${lastPoint.y}`);
}
} else {
path.push(`L ${lastPoint.x},${lastPoint.y}`);
}
return path.join(' ');
};
export const insertEdge = function (elem, edge, clusterDb, diagramType, startNode, endNode, id) {
const { handDrawnSeed } = getConfig();
let points = edge.points;
@@ -462,14 +600,21 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
}
let lineData = points.filter((p) => !Number.isNaN(p.y));
lineData = adjustForArrowHeads(lineData, 4, edge.id === 'L_n4_C_10_0');
lineData = fixCorners(lineData);
// if (edge.id === 'L_n4_C_10_0') {
// console.log('APA14 lineData', lineData);
// }
// lineData = adjustForArrowHeads(lineData);
let curve = curveBasis;
// let curve = curveLinear;
if (edge.curve) {
curve = edge.curve;
}
const { x, y } = getLineFunctionsWithOffset(edge);
const lineFunction = line().x(x).y(y).curve(curve);
// const lineFunction = line().curve(curve);
let strokeClasses;
switch (edge.thickness) {
@@ -500,6 +645,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
}
let svgPath;
let linePath = lineFunction(lineData);
// let linePath = generateRoundedPath(lineData, 5);
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
if (edge.look === 'handDrawn') {
const rc = rough.svg(elem);
@@ -531,21 +677,12 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
// DEBUG code, DO NOT REMOVE
// adds a red circle at each edge coordinate
// cornerPoints.forEach((point) => {
// points.forEach((point) => {
// elem
// .append('circle')
// .style('stroke', 'blue')
// .style('fill', 'blue')
// .attr('r', 3)
// .attr('cx', point.x)
// .attr('cy', point.y);
// });
// lineData.forEach((point) => {
// elem
// .append('circle')
// .style('stroke', 'blue')
// .style('fill', 'blue')
// .attr('r', 3)
// .style('stroke', 'red')
// .style('fill', 'red')
// .attr('r', 1)
// .attr('cx', point.x)
// .attr('cy', point.y);
// });

View File

@@ -2,64 +2,87 @@
* Returns the point at which two lines, p and q, intersect or returns undefined if they do not intersect.
*/
function intersectLine(p1, p2, q1, q2) {
// Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
// p7 and p473.
{
// Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
// p7 and p473.
var a1, a2, b1, b2, c1, c2;
var r1, r2, r3, r4;
var denom, offset, num;
var x, y;
// Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
// b1 y + c1 = 0.
const a1 = p2.y - p1.y;
const b1 = p1.x - p2.x;
const c1 = p2.x * p1.y - p1.x * p2.y;
// Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
// b1 y + c1 = 0.
a1 = p2.y - p1.y;
b1 = p1.x - p2.x;
c1 = p2.x * p1.y - p1.x * p2.y;
// Compute r3 and r4.
const r3 = a1 * q1.x + b1 * q1.y + c1;
const r4 = a1 * q2.x + b1 * q2.y + c1;
// Compute r3 and r4.
r3 = a1 * q1.x + b1 * q1.y + c1;
r4 = a1 * q2.x + b1 * q2.y + c1;
const epsilon = 1e-6;
// Check signs of r3 and r4. If both point 3 and point 4 lie on
// same side of line 1, the line segments do not intersect.
if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
return /*DON'T_INTERSECT*/;
// Check signs of r3 and r4. If both point 3 and point 4 lie on
// same side of line 1, the line segments do not intersect.
if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
return /*DON'T_INTERSECT*/;
}
// Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
const a2 = q2.y - q1.y;
const b2 = q1.x - q2.x;
const c2 = q2.x * q1.y - q1.x * q2.y;
// Compute r1 and r2
const r1 = a2 * p1.x + b2 * p1.y + c2;
const r2 = a2 * p2.x + b2 * p2.y + c2;
// Check signs of r1 and r2. If both point 1 and point 2 lie
// on same side of second line segment, the line segments do
// not intersect.
if (Math.abs(r1) < epsilon && Math.abs(r2) < epsilon && sameSign(r1, r2)) {
return /*DON'T_INTERSECT*/;
}
// Line segments intersect: compute intersection point.
const denom = a1 * b2 - a2 * b1;
if (denom === 0) {
return /*COLLINEAR*/;
}
const offset = Math.abs(denom / 2);
// The denom/2 is to get rounding instead of truncating. It
// is added or subtracted to the numerator, depending upon the
// sign of the numerator.
let num = b1 * c2 - b2 * c1;
const x = num < 0 ? (num - offset) / denom : (num + offset) / denom;
num = a2 * c1 - a1 * c2;
const y = num < 0 ? (num - offset) / denom : (num + offset) / denom;
// console.log(
// 'APA13 intersectLine intersection',
// '\np1: (',
// p1.x,
// p1.y,
// ')',
// '\np2: (',
// p2.x,
// p2.y,
// ')',
// '\nq1: (',
// q1.x,
// q1.y,
// ')',
// '\np1: (',
// q2.x,
// q2.y,
// ')',
// 'offset:',
// offset,
// '\nintersection: (',
// x,
// y,
// ')'
// );
return { x: x, y: y };
}
// Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
a2 = q2.y - q1.y;
b2 = q1.x - q2.x;
c2 = q2.x * q1.y - q1.x * q2.y;
// Compute r1 and r2
r1 = a2 * p1.x + b2 * p1.y + c2;
r2 = a2 * p2.x + b2 * p2.y + c2;
// Check signs of r1 and r2. If both point 1 and point 2 lie
// on same side of second line segment, the line segments do
// not intersect.
if (r1 !== 0 && r2 !== 0 && sameSign(r1, r2)) {
return /*DON'T_INTERSECT*/;
}
// Line segments intersect: compute intersection point.
denom = a1 * b2 - a2 * b1;
if (denom === 0) {
return /*COLLINEAR*/;
}
offset = Math.abs(denom / 2);
// The denom/2 is to get rounding instead of truncating. It
// is added or subtracted to the numerator, depending upon the
// sign of the numerator.
num = b1 * c2 - b2 * c1;
x = num < 0 ? (num - offset) / denom : (num + offset) / denom;
num = a2 * c1 - a1 * c2;
y = num < 0 ? (num - offset) / denom : (num + offset) / denom;
return { x: x, y: y };
}
function sameSign(r1, r2) {

View File

@@ -7,7 +7,7 @@ import intersectLine from './intersect-line.js';
function intersectPolygon(node, polyPoints, point) {
let x1 = node.x;
let y1 = node.y;
// console.trace('APA14 intersectPolygon', x1, y1, polyPoints, point);
let intersections = [];
let minX = Number.POSITIVE_INFINITY;
@@ -24,7 +24,7 @@ function intersectPolygon(node, polyPoints, point) {
let left = x1 - node.width / 2 - minX;
let top = y1 - node.height / 2 - minY;
// console.log('APA13 intersectPolygon2 ', left, y1);
for (let i = 0; i < polyPoints.length; i++) {
let p1 = polyPoints[i];
let p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
@@ -34,7 +34,9 @@ function intersectPolygon(node, polyPoints, point) {
{ x: left + p1.x, y: top + p1.y },
{ x: left + p2.x, y: top + p2.y }
);
// console.log('APA13 intersectPolygon3 ', intersect);
if (intersect) {
// console.log('APA13 intersectPolygon4 ', intersect);
intersections.push(intersect);
}
}
@@ -42,6 +44,7 @@ function intersectPolygon(node, polyPoints, point) {
if (!intersections.length) {
return node;
}
// console.log('APA12 intersectPolygon5 ');
if (intersections.length > 1) {
// More intersections, find the one nearest to edge end point
@@ -54,6 +57,8 @@ function intersectPolygon(node, polyPoints, point) {
let qdy = q.y - point.y;
let distq = Math.sqrt(qdx * qdx + qdy * qdy);
// console.log('APA12 intersectPolygon6 ');
return distp < distq ? -1 : distp === distq ? 0 : 1;
});
}

View File

@@ -6,6 +6,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { handleUndefinedAttr } from '../../../utils.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export function anchor<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles } = styles2String(node);
@@ -37,6 +38,11 @@ export function anchor<T extends SVGGraphicsElement>(parent: D3Selection<T>, nod
updateNodeBounds(node, circleElem);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('Circle intersect', node, radius, point);
return intersect.circle(node, radius, point);

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function generateArcPoints(
x1: number,
@@ -70,7 +71,15 @@ function generateArcPoints(
return points;
}
function getPoints(w: number, h: number, rx: number, ry: number) {
return [
{ x: w / 2, y: -h / 2 },
{ x: -w / 2, y: -h / 2 },
...generateArcPoints(-w / 2, -h / 2, -w / 2, h / 2, rx, ry, false),
{ x: w / 2, y: h / 2 },
...generateArcPoints(w / 2, h / 2, w / 2, -h / 2, rx, ry, true),
];
}
export async function bowTieRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
@@ -84,13 +93,7 @@ export async function bowTieRect<T extends SVGGraphicsElement>(parent: D3Selecti
// let shape: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>;
const { cssStyles } = node;
const points = [
{ x: w / 2, y: -h / 2 },
{ x: -w / 2, y: -h / 2 },
...generateArcPoints(-w / 2, -h / 2, -w / 2, h / 2, rx, ry, false),
{ x: w / 2, y: h / 2 },
...generateArcPoints(w / 2, h / 2, w / 2, -h / 2, rx, ry, true),
];
const points = getPoints(w, h, rx, ry);
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
@@ -118,6 +121,16 @@ export async function bowTieRect<T extends SVGGraphicsElement>(parent: D3Selecti
updateNodeBounds(node, bowTieRectShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const h = bounds.height;
const ry = h / 2;
const rx = ry / (2.5 + h / 50);
const points = getPoints(w, h, rx, ry);
return intersect.polygon(bounds, points, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;

View File

@@ -3,17 +3,25 @@ import intersect from '../intersect/index.js';
import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import { createPathFromPoints } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
// const createPathFromPoints = (points: { x: number; y: number }[]): string => {
// const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`);
// pointStrings.push('Z');
// return pointStrings.join(' ');
// };
function getPoints(w: number, h: number, padding: number) {
const left = 0;
const right = w;
const top = -h;
const bottom = 0;
return [
{ x: left + padding, y: top },
{ x: right, y: top },
{ x: right, y: bottom },
{ x: left, y: bottom },
{ x: left, y: top + padding },
{ x: left + padding, y: top },
];
}
export async function card<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
@@ -22,18 +30,8 @@ export async function card<T extends SVGGraphicsElement>(parent: D3Selection<T>,
const h = bbox.height + node.padding;
const padding = 12;
const w = bbox.width + node.padding + padding;
const left = 0;
const right = w;
const top = -h;
const bottom = 0;
const points = [
{ x: left + padding, y: top },
{ x: right, y: top },
{ x: right, y: bottom },
{ x: left, y: bottom },
{ x: left, y: top + padding },
{ x: left + padding, y: top },
];
const points = getPoints(w, h, padding);
let polygon: D3Selection<SVGGElement> | Awaited<ReturnType<typeof insertPolygonShape>>;
const { cssStyles } = node;
@@ -62,6 +60,17 @@ export async function card<T extends SVGGraphicsElement>(parent: D3Selection<T>,
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const h = bounds.height;
const padding = 12;
const w = bounds.width;
const points = getPoints(w, h, padding);
const res = intersect.polygon(bounds, points, point);
return { x: res.x - 0.5, y: res.y - 0.5 };
};
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};

View File

@@ -4,7 +4,15 @@ import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { createPathFromPoints, getNodeClasses } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function getPoints(s: number) {
return [
{ x: 0, y: s / 2 },
{ x: s / 2, y: 0 },
{ x: 0, y: -s / 2 },
{ x: -s / 2, y: 0 },
];
}
export function choice<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { nodeStyles } = styles2String(node);
node.label = '';
@@ -16,12 +24,7 @@ export function choice<T extends SVGGraphicsElement>(parent: D3Selection<T>, nod
const s = Math.max(28, node.width ?? 0);
const points = [
{ x: 0, y: s / 2 },
{ x: s / 2, y: 0 },
{ x: 0, y: -s / 2 },
{ x: -s / 2, y: 0 },
];
const points = getPoints(s);
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
@@ -47,6 +50,13 @@ export function choice<T extends SVGGraphicsElement>(parent: D3Selection<T>, nod
node.width = 28;
node.height = 28;
node.calcIntersect = function (bounds: Bounds, point: Point) {
const s = Math.max(28, bounds.width ?? 0);
const points = getPoints(s);
return intersect.circle(bounds, points, point);
};
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};

View File

@@ -6,6 +6,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export async function circle<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -35,7 +36,10 @@ export async function circle<T extends SVGGraphicsElement>(parent: D3Selection<T
}
updateNodeBounds(node, circleElem);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('Circle intersect', node, radius, point);
return intersect.circle(node, radius, point);

View File

@@ -9,6 +9,7 @@ import intersect from '../intersect/index.js';
import { textHelper } from '../../../diagrams/class/shapeUtil.js';
import { evaluate } from '../../../diagrams/common/common.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const config = getConfig();
@@ -199,6 +200,9 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
}
updateNodeBounds(node, rect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
return intersect.rect(bounds, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function createLine(r: number) {
const xAxis45 = Math.cos(Math.PI / 4); // cosine of 45 degrees
@@ -57,6 +58,11 @@ export function crossedCircle<T extends SVGGraphicsElement>(parent: D3Selection<
updateNodeBounds(node, crossedCircle);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const radius = Math.max(30, bounds?.width ?? 0);
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('crossedCircle intersect', node, { radius, point });
const pos = intersect.circle(node, radius, point);

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function generateCirclePoints(
centerX: number,
@@ -35,6 +36,21 @@ function generateCirclePoints(
return points;
}
function getRectPoints(w: number, h: number, radius: number) {
return [
{ x: w / 2, y: -h / 2 - radius },
{ x: -w / 2, y: -h / 2 - radius },
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: -w / 2 - radius, y: -radius },
...generateCirclePoints(w / 2 + w * 0.1, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + w * 0.1, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
{ x: -w / 2, y: h / 2 + radius },
{ x: w / 2, y: h / 2 + radius },
];
}
export async function curlyBraceLeft<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
@@ -57,18 +73,7 @@ export async function curlyBraceLeft<T extends SVGGraphicsElement>(
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
];
const rectPoints = [
{ x: w / 2, y: -h / 2 - radius },
{ x: -w / 2, y: -h / 2 - radius },
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: -w / 2 - radius, y: -radius },
...generateCirclePoints(w / 2 + w * 0.1, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + w * 0.1, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
{ x: -w / 2, y: h / 2 + radius },
{ x: w / 2, y: h / 2 + radius },
];
const rectPoints = getRectPoints(w, h, radius);
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
@@ -105,6 +110,15 @@ export async function curlyBraceLeft<T extends SVGGraphicsElement>(
updateNodeBounds(node, curlyBraceLeftShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const h = bounds.height;
const radius = Math.max(5, h * 0.1);
const rectPoints = getRectPoints(w, h, radius);
return intersect.polygon(bounds, rectPoints, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, rectPoints, point);

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function generateCirclePoints(
centerX: number,
@@ -35,6 +36,21 @@ function generateCirclePoints(
return points;
}
function getRectPoints(w: number, h: number, radius: number) {
return [
{ x: -w / 2, y: -h / 2 - radius },
{ x: w / 2, y: -h / 2 - radius },
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: w / 2 + radius, y: -radius },
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: w / 2 + radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
{ x: w / 2, y: h / 2 + radius },
{ x: -w / 2, y: h / 2 + radius },
];
}
export async function curlyBraceRight<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
@@ -57,18 +73,7 @@ export async function curlyBraceRight<T extends SVGGraphicsElement>(
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
];
const rectPoints = [
{ x: -w / 2, y: -h / 2 - radius },
{ x: w / 2, y: -h / 2 - radius },
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: w / 2 + radius, y: -radius },
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: w / 2 + radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
{ x: w / 2, y: h / 2 + radius },
{ x: -w / 2, y: h / 2 + radius },
];
const rectPoints = getRectPoints(w, h, radius);
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
@@ -105,6 +110,15 @@ export async function curlyBraceRight<T extends SVGGraphicsElement>(
updateNodeBounds(node, curlyBraceRightShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const h = bounds.height;
const radius = Math.max(5, h * 0.1);
const rectPoints = getRectPoints(w, h, radius);
return intersect.polygon(bounds, rectPoints, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, rectPoints, point);

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function generateCirclePoints(
centerX: number,
@@ -35,6 +36,25 @@ function generateCirclePoints(
return points;
}
const getRectPoints = (w: number, h: number, radius: number) => [
{ x: w / 2, y: -h / 2 - radius },
{ x: -w / 2, y: -h / 2 - radius },
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: -w / 2 - radius, y: -radius },
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
{ x: -w / 2, y: h / 2 + radius },
{ x: w / 2 - radius - radius / 2, y: h / 2 + radius },
...generateCirclePoints(-w / 2 + radius + radius / 2, -h / 2, radius, 20, -90, -180),
{ x: w / 2 - radius / 2, y: radius },
...generateCirclePoints(-w / 2 - radius / 2, -radius, radius, 20, 0, 90),
...generateCirclePoints(-w / 2 - radius / 2, radius, radius, 20, -90, 0),
{ x: w / 2 - radius / 2, y: -radius },
...generateCirclePoints(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270),
];
export async function curlyBraces<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
@@ -66,24 +86,7 @@ export async function curlyBraces<T extends SVGGraphicsElement>(
...generateCirclePoints(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270),
];
const rectPoints = [
{ x: w / 2, y: -h / 2 - radius },
{ x: -w / 2, y: -h / 2 - radius },
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
{ x: -w / 2 - radius, y: -radius },
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
{ x: -w / 2 - radius, y: h / 2 },
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
{ x: -w / 2, y: h / 2 + radius },
{ x: w / 2 - radius - radius / 2, y: h / 2 + radius },
...generateCirclePoints(-w / 2 + radius + radius / 2, -h / 2, radius, 20, -90, -180),
{ x: w / 2 - radius / 2, y: radius },
...generateCirclePoints(-w / 2 - radius / 2, -radius, radius, 20, 0, 90),
...generateCirclePoints(-w / 2 - radius / 2, radius, radius, 20, -90, 0),
{ x: w / 2 - radius / 2, y: -radius },
...generateCirclePoints(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270),
];
const rectPoints = getRectPoints(w, h, radius);
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
@@ -124,6 +127,15 @@ export async function curlyBraces<T extends SVGGraphicsElement>(
updateNodeBounds(node, curlyBracesShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const h = bounds.height;
const radius = Math.max(5, h * 0.1);
const rectPoints = getRectPoints(w, h, radius);
return intersect.polygon(bounds, rectPoints, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, rectPoints, point);

View File

@@ -10,6 +10,16 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
const getTrapezoidPoints = (rw: number, tw: number, totalHeight: number, radius: number) => [
{ x: rw, y: 0 },
{ x: tw, y: 0 },
{ x: 0, y: totalHeight / 2 },
{ x: tw, y: totalHeight },
{ x: rw, y: totalHeight },
...generateCirclePoints(-rw, -totalHeight / 2, radius, 50, 270, 90),
];
export async function curvedTrapezoid<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -39,14 +49,7 @@ export async function curvedTrapezoid<T extends SVGGraphicsElement>(
const rw = totalWidth - radius;
const tw = totalHeight / 4;
const points = [
{ x: rw, y: 0 },
{ x: tw, y: 0 },
{ x: 0, y: totalHeight / 2 },
{ x: tw, y: totalHeight },
{ x: rw, y: totalHeight },
...generateCirclePoints(-rw, -totalHeight / 2, radius, 50, 270, 90),
];
const points = getTrapezoidPoints(rw, tw, totalHeight, radius);
const pathData = createPathFromPoints(points);
const shapeNode = rc.path(pathData, options);
@@ -66,6 +69,20 @@ export async function curvedTrapezoid<T extends SVGGraphicsElement>(
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const h = bounds.height;
const radius = h / 2;
const totalWidth = w,
totalHeight = h;
const rw = totalWidth - radius;
const tw = totalHeight / 4;
const points = getTrapezoidPoints(rw, tw, totalHeight, radius);
return intersect.polygon(bounds, points, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export const createCylinderPathD = (
x: number,
@@ -96,22 +97,24 @@ export async function cylinder<T extends SVGGraphicsElement>(parent: D3Selection
`translate(${-(bbox.width / 2) - (bbox.x - (bbox.left ?? 0))}, ${-(bbox.height / 2) + (node.padding ?? 0) / 1.5 - (bbox.y - (bbox.top ?? 0))})`
);
node.intersect = function (point) {
const pos = intersect.rect(node, point);
const x = pos.x - (node.x ?? 0);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const rx = w / 2;
const ry = rx / (2.5 + w / 50);
const h = bounds.height;
const pos = intersect.rect(bounds, point);
const x = pos.x - (bounds.x ?? 0);
if (
rx != 0 &&
(Math.abs(x) < (node.width ?? 0) / 2 ||
(Math.abs(x) == (node.width ?? 0) / 2 &&
Math.abs(pos.y - (node.y ?? 0)) > (node.height ?? 0) / 2 - ry))
(Math.abs(x) < (w ?? 0) / 2 ||
(Math.abs(x) == (w ?? 0) / 2 && Math.abs(pos.y - (bounds.y ?? 0)) > (h ?? 0) / 2 - ry))
) {
let y = ry * ry * (1 - (x * x) / (rx * rx));
if (y > 0) {
y = Math.sqrt(y);
}
y = ry - y;
if (point.y - (node.y ?? 0) > 0) {
if (point.y - (bounds.y ?? 0) > 0) {
y = -y;
}
@@ -121,5 +124,14 @@ export async function cylinder<T extends SVGGraphicsElement>(parent: D3Selection
return pos;
};
node.intersect = function (point: Point) {
return this.calcIntersect
? this.calcIntersect(
{ x: node.x ?? 0, y: node.y ?? 0, width: node.width ?? 0, height: node.height ?? 0 },
point
)
: { x: 0, y: 0 };
};
return shapeSvg;
}

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function dividedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -62,6 +63,10 @@ export async function dividedRectangle<T extends SVGGraphicsElement>(
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
return intersect.rect(bounds, point);
};
node.intersect = function (point) {
const pos = intersect.rect(node, point);
return pos;

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export const createCylinderPathD = (
x: number,
@@ -91,29 +92,36 @@ export async function cylinder<T extends SVGGraphicsElement>(parent: D3Selection
updateNodeBounds(node, cylinder);
node.intersect = function (point) {
const pos = intersect.rect(node, point);
const x = pos.x - (node.x ?? 0);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const pos = intersect.rect(bounds, point);
const x = pos.x - (bounds.x ?? 0);
if (
rx != 0 &&
(Math.abs(x) < (node.width ?? 0) / 2 ||
(Math.abs(x) == (node.width ?? 0) / 2 &&
Math.abs(pos.y - (node.y ?? 0)) > (node.height ?? 0) / 2 - ry))
(Math.abs(x) < (bounds.width ?? 0) / 2 ||
(Math.abs(x) == (bounds.width ?? 0) / 2 &&
Math.abs(pos.y - (bounds.y ?? 0)) > (bounds.height ?? 0) / 2 - ry))
) {
let y = ry * ry * (1 - (x * x) / (rx * rx));
if (y != 0) {
y = Math.sqrt(y);
}
y = ry - y;
if (point.y - (node.y ?? 0) > 0) {
if (point.y - (bounds.y ?? 0) > 0) {
y = -y;
}
pos.y += y;
}
};
return pos;
node.intersect = function (point) {
return this.calcIntersect
? this.calcIntersect(
{ x: node.x ?? 0, y: node.y ?? 0, width: node.width ?? 0, height: node.height ?? 0 },
point
)
: { x: 0, y: 0 };
};
return shapeSvg;

View File

@@ -6,6 +6,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export async function doublecircle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -62,6 +63,11 @@ export async function doublecircle<T extends SVGGraphicsElement>(
updateNodeBounds(node, circleGroup);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('DoubleCircle intersect', node, outerRadius, point);
return intersect.circle(node, outerRadius, point);

View File

@@ -6,6 +6,7 @@ import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export async function drawRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -62,6 +63,10 @@ export async function drawRect<T extends SVGGraphicsElement>(
updateNodeBounds(node, rect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
return intersect.rect(bounds, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -5,6 +5,7 @@ import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export function filledCircle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -46,6 +47,11 @@ export function filledCircle<T extends SVGGraphicsElement>(
updateNodeBounds(node, filledCircle);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('filledCircle intersect', node, { radius, point });
const pos = intersect.circle(node, radius, point);

View File

@@ -6,6 +6,15 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { createPathFromPoints } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function getPoints(tw: number, h: number) {
return [
{ x: 0, y: -h },
{ x: tw, y: -h },
{ x: tw / 2, y: 0 },
];
}
export async function flippedTriangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -19,11 +28,7 @@ export async function flippedTriangle<T extends SVGGraphicsElement>(
const h = w + bbox.height;
const tw = w + bbox.height;
const points = [
{ x: 0, y: -h },
{ x: tw, y: -h },
{ x: tw / 2, y: 0 },
];
const points = getPoints(tw, h);
const { cssStyles } = node;
@@ -59,6 +64,16 @@ export async function flippedTriangle<T extends SVGGraphicsElement>(
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))}, ${-h / 2 + (node.padding ?? 0) / 2 + (bbox.y - (bbox.top ?? 0))})`
);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const w = bounds.width;
const h = bounds.height;
const tw = w + bounds.height;
const points = getPoints(tw, h);
return intersect.polygon(node, points, point);
};
node.intersect = function (point) {
log.info('Triangle intersect', node, points, point);
return intersect.polygon(node, points, point);

View File

@@ -4,6 +4,7 @@ import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export function forkJoin<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -59,6 +60,10 @@ export function forkJoin<T extends SVGGraphicsElement>(
node.width += padding / 2 || 0;
node.height += padding / 2 || 0;
}
node.calcIntersect = function (bounds: Bounds, point: Point) {
return intersect.rect(bounds, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -11,6 +11,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function halfRoundedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -63,6 +64,12 @@ export async function halfRoundedRectangle<T extends SVGGraphicsElement>(
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('Pill intersect', node, { radius, point });
const pos = intersect.polygon(node, points, point);

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export const createHexagonPathD = (
x: number,
@@ -72,6 +73,12 @@ export async function hexagon<T extends SVGGraphicsElement>(parent: D3Selection<
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};

View File

@@ -5,6 +5,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function hourglass<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -52,6 +53,18 @@ export async function hourglass<T extends SVGGraphicsElement>(parent: D3Selectio
// label.attr('transform', `translate(${-bbox.width / 2}, ${(h/2)})`); // To transform text below hourglass shape
node.calcIntersect = function (bounds: Bounds, point: Point) {
const { width: w, height: h } = bounds;
const points = [
{ x: 0, y: 0 },
{ x: w, y: 0 },
{ x: 0, y: h },
{ x: w, y: h },
];
const res = intersect.polygon(bounds, points, point);
return { x: res.x - 0.5, y: res.y - 0.5 };
};
node.intersect = function (point) {
log.info('Pill intersect', node, { points });
const pos = intersect.polygon(node, points, point);

View File

@@ -6,6 +6,7 @@ import intersect from '../intersect/index.js';
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function icon<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -97,6 +98,12 @@ export async function icon<T extends SVGGraphicsElement>(
updateNodeBounds(node, outerShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('iconSquare intersect', node, point);
if (!node.label) {

View File

@@ -6,6 +6,7 @@ import intersect from '../intersect/index.js';
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function iconCircle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -94,6 +95,12 @@ export async function iconCircle<T extends SVGGraphicsElement>(
updateNodeBounds(node, outerShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('iconSquare intersect', node, point);
const pos = intersect.rect(node, point);

View File

@@ -7,6 +7,7 @@ import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShap
import { createRoundedRectPathD } from './roundedRectPath.js';
import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function iconRounded<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -104,6 +105,12 @@ export async function iconRounded<T extends SVGGraphicsElement>(
updateNodeBounds(node, outerShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('iconSquare intersect', node, point);
if (!node.label) {

View File

@@ -7,6 +7,7 @@ import { createRoundedRectPathD } from './roundedRectPath.js';
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function iconSquare<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -104,6 +105,12 @@ export async function iconSquare<T extends SVGGraphicsElement>(
updateNodeBounds(node, outerShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('iconSquare intersect', node, point);
if (!node.label) {

View File

@@ -5,6 +5,7 @@ import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function imageSquare<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -108,6 +109,12 @@ export async function imageSquare<T extends SVGGraphicsElement>(
updateNodeBounds(node, outerShape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('iconSquare intersect', node, point);
if (!node.label) {

View File

@@ -5,21 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
// export const createInvertedTrapezoidPathD = (
// x: number,
// y: number,
// width: number,
// height: number
// ): string => {
// return [
// `M${x + height / 6},${y}`,
// `L${x + width - height / 6},${y}`,
// `L${x + width + (2 * height) / 6},${y - height}`,
// `L${x - (2 * height) / 6},${y - height}`,
// 'Z',
// ].join(' ');
// };
import type { Bounds, Point } from '../../../types.js';
export async function inv_trapezoid<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -70,6 +56,12 @@ export async function inv_trapezoid<T extends SVGGraphicsElement>(
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};

View File

@@ -5,6 +5,7 @@ import { createRoundedRectPathD } from './roundedRectPath.js';
import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
const colorFromPriority = (priority: NonNullable<KanbanNode['priority']>) => {
switch (priority) {
@@ -155,6 +156,12 @@ export async function kanbanItem<T extends SVGGraphicsElement>(
updateNodeBounds(kanbanNode, rect);
kanbanNode.height = totalHeight;
kanbanNode.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
kanbanNode.intersect = function (point) {
return intersect.rect(kanbanNode, point);
};

View File

@@ -3,6 +3,7 @@ import { drawRect } from './drawRect.js';
import { labelHelper, updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function roundedRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -48,8 +49,12 @@ export async function labelRect<T extends SVGGraphicsElement>(parent: D3Selectio
// }
updateNodeBounds(node, rect);
// node.width = 1;
// node.height = 1;
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function lean_left<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -50,6 +51,12 @@ export async function lean_left<T extends SVGGraphicsElement>(parent: D3Selectio
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function lean_right<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -49,8 +50,59 @@ export async function lean_right<T extends SVGGraphicsElement>(parent: D3Selecti
updateNodeBounds(node, polygon);
node.intersect = function (point) {
return intersect.polygon(node, points, point);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const h = bounds.height;
const dx = h / 2;
const z = w - h;
// (w = dx+z+dx)
const points = [
{ x: -dx, y: 0 },
{ x: z, y: 0 },
{ x: z + dx, y: -h },
{ x: 0, y: -h },
];
const res = intersect.polygon(bounds, points, point);
// if (node.id === 'C') {
// console.log(
// 'APA14!',
// bounds.x,
// bounds.x,
// bounds.width,
// '\nw:',
// w,
// points,
// '\nExternal point: ',
// '(',
// point.x,
// point.y,
// ')\nIntersection:',
// res
// );
// }
return { x: res.x - 0.5, y: res.y - 0.5 };
};
node.intersect = function (point: Point) {
const res = intersect.polygon(node, points, point);
// if (node.id === 'C') {
// console.log(
// 'APA14!!',
// node.x,
// node.y,
// '\nw:',
// node.width,
// points,
// '\nExternal point: ',
// '(',
// point.x,
// point.y,
// ')\nIntersection:',
// res
// );
// }
return res;
};
return shapeSvg;

View File

@@ -6,7 +6,17 @@ import rough from 'roughjs';
import intersect from '../intersect/index.js';
import { createPathFromPoints } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function getPoints(width: number, height: number, gapX: number, gapY: number) {
return [
{ x: width, y: 0 },
{ x: 0, y: height / 2 + gapY / 2 },
{ x: width - 4 * gapX, y: height / 2 + gapY / 2 },
{ x: 0, y: height },
{ x: width, y: height / 2 - gapY / 2 },
{ x: 4 * gapX, y: height / 2 - gapY / 2 },
];
}
export function lightningBolt<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.label = '';
@@ -55,11 +65,21 @@ export function lightningBolt<T extends SVGGraphicsElement>(parent: D3Selection<
updateNodeBounds(node, lightningBolt);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const { width: w, height: h } = bounds;
const gapX = Math.max(5, w * 0.1);
const gapY = Math.max(5, h * 0.1);
const p = getPoints(w, h, gapX, gapY);
const res = intersect.polygon(bounds, p, point);
return { x: res.x - 0.5, y: res.y - 0.5 };
};
node.intersect = function (point) {
log.info('lightningBolt intersect', node, point);
const pos = intersect.polygon(node, points, point);
const res = intersect.polygon(node, points, point);
return pos;
return res;
};
return shapeSvg;

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export const createCylinderPathD = (
x: number,
@@ -110,6 +111,12 @@ export async function linedCylinder<T extends SVGGraphicsElement>(
`translate(${-(bbox.width / 2) - (bbox.x - (bbox.left ?? 0))}, ${-(bbox.height / 2) + ry - (bbox.y - (bbox.top ?? 0))})`
);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.rect(node, point);
const x = pos.x - (node.x ?? 0);

View File

@@ -9,6 +9,7 @@ import type { Node } from '../../types.js';
import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function linedWaveEdgedRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -74,6 +75,13 @@ export async function linedWaveEdgedRect<T extends SVGGraphicsElement>(
);
updateNodeBounds(node, waveEdgeRect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;

View File

@@ -4,6 +4,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function multiRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -74,6 +75,12 @@ export async function multiRect<T extends SVGGraphicsElement>(parent: D3Selectio
updateNodeBounds(node, multiRect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, outerPathPoints, point);
return pos;

View File

@@ -10,6 +10,7 @@ import type { Node } from '../../types.js';
import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function multiWaveEdgedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -99,6 +100,12 @@ export async function multiWaveEdgedRectangle<T extends SVGGraphicsElement>(
updateNodeBounds(node, shape);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, outerPathPoints, point);
return pos;

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import { getConfig } from '../../../config.js';
import type { Bounds, Point } from '../../../types.js';
export async function note<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -52,6 +53,12 @@ export async function note<T extends SVGGraphicsElement>(
updateNodeBounds(node, rect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -1,4 +1,3 @@
import { log } from '../../../logger.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.js';
@@ -6,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export const createDecisionBoxPathD = (x: number, y: number, size: number): string => {
return [
@@ -59,17 +59,41 @@ export async function question<T extends SVGGraphicsElement>(parent: D3Selection
}
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const s = bounds.width;
node.intersect = function (point) {
log.debug(
'APA12 Intersect called SPLIT\npoint:',
point,
'\nnode:\n',
node,
'\nres:',
intersect.polygon(node, points, point)
);
return intersect.polygon(node, points, point);
// console.log(
// 'APA10\nbounds width:',
// bounds.width,
// '\nbounds height:',
// bounds.height,
// 'point:',
// point.x,
// point.y,
// '\nw:',
// w,
// '\nh',
// h,
// '\ns',
// s
// );
// Define polygon points
const points = [
{ x: s / 2, y: 0 },
{ x: s, y: -s / 2 },
{ x: s / 2, y: -s },
{ x: 0, y: -s / 2 },
];
// Calculate the intersection point
const res = intersect.polygon(bounds, points, point);
return { x: res.x - 0.5, y: res.y - 0.5 }; // Adjusted result
};
node.intersect = function (point: Point) {
return this.calcIntersect ? this.calcIntersect(node as Bounds, point) : { x: 0, y: 0 };
};
return shapeSvg;

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function rect_left_inv_arrow<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -60,6 +61,12 @@ export async function rect_left_inv_arrow<T extends SVGGraphicsElement>(
);
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};

View File

@@ -10,6 +10,7 @@ import { getConfig } from '../../../diagram-api/diagramAPI.js';
import { createRoundedRectPathD } from './roundedRectPath.js';
import { log } from '../../../logger.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function rectWithTitle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -150,6 +151,12 @@ export async function rectWithTitle<T extends SVGGraphicsElement>(
}
updateNodeBounds(node, rect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -5,6 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export async function shadedProcess<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -63,6 +64,12 @@ export async function shadedProcess<T extends SVGGraphicsElement>(
updateNodeBounds(node, rect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function slopedRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -54,6 +55,12 @@ export async function slopedRect<T extends SVGGraphicsElement>(parent: D3Selecti
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;

View File

@@ -6,6 +6,7 @@ import rough from 'roughjs';
import { createRoundedRectPathD } from './roundedRectPath.js';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export const createStadiumPathD = (
x: number,
@@ -88,6 +89,12 @@ export async function stadium<T extends SVGGraphicsElement>(parent: D3Selection<
updateNodeBounds(node, rect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -4,6 +4,7 @@ import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export function stateEnd<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -54,6 +55,12 @@ export function stateEnd<T extends SVGGraphicsElement>(
updateNodeBounds(node, circle);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.circle(node, 7, point);
};

View File

@@ -4,6 +4,7 @@ import intersect from '../intersect/index.js';
import { solidStateFill } from './handDrawnShapeStyles.js';
import { updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export function stateStart<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -33,6 +34,12 @@ export function stateStart<T extends SVGGraphicsElement>(
updateNodeBounds(node, circle);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.circle(node, 7, point);
};

View File

@@ -6,6 +6,7 @@ import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export const createSubroutinePathD = (
x: number,
@@ -79,6 +80,12 @@ export async function subroutine<T extends SVGGraphicsElement>(parent: D3Selecti
updateNodeBounds(node, el);
}
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};

View File

@@ -4,6 +4,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function taggedRect<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -60,6 +61,12 @@ export async function taggedRect<T extends SVGGraphicsElement>(parent: D3Selecti
updateNodeBounds(node, taggedRect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, rectPoints, point);

View File

@@ -10,6 +10,7 @@ import type { Node } from '../../types.js';
import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function taggedWaveEdgedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -96,6 +97,13 @@ export async function taggedWaveEdgedRectangle<T extends SVGGraphicsElement>(
);
updateNodeBounds(node, waveEdgeRect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;

View File

@@ -3,6 +3,7 @@ import intersect from '../intersect/index.js';
import type { Node } from '../../types.js';
import { styles2String } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function text<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -29,6 +30,12 @@ export async function text<T extends SVGGraphicsElement>(parent: D3Selection<T>,
updateNodeBounds(node, rect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.rect(node, point);
};

View File

@@ -5,6 +5,7 @@ import rough from 'roughjs';
import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
export const createCylinderPathD = (
x: number,
@@ -113,6 +114,12 @@ export async function tiltedCylinder<T extends SVGGraphicsElement>(
updateNodeBounds(node, cylinder);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.rect(node, point);
const y = pos.y - (node.y ?? 0);

View File

@@ -5,21 +5,7 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
// export const createTrapezoidPathD = (
// x: number,
// y: number,
// width: number,
// height: number
// ): string => {
// return [
// `M${x - (2 * height) / 6},${y}`,
// `L${x + width + (2 * height) / 6},${y}`,
// `L${x + width - height / 6},${y - height}`,
// `L${x + height / 6},${y - height}`,
// 'Z',
// ].join(' ');
// };
import type { Bounds, Point } from '../../../types.js';
export async function trapezoid<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -65,6 +51,12 @@ export async function trapezoid<T extends SVGGraphicsElement>(parent: D3Selectio
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
return intersect.polygon(node, points, point);
};

View File

@@ -4,6 +4,7 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function trapezoidalPentagon<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -52,6 +53,12 @@ export async function trapezoidalPentagon<T extends SVGGraphicsElement>(
updateNodeBounds(node, polygon);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;

View File

@@ -8,6 +8,7 @@ import { createPathFromPoints } from './util.js';
import { evaluate } from '../../../diagrams/common/common.js';
import { getConfig } from '../../../diagram-api/diagramAPI.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function triangle<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -59,6 +60,12 @@ export async function triangle<T extends SVGGraphicsElement>(parent: D3Selection
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))}, ${h / 2 - (bbox.height + (node.padding ?? 0) / (useHtmlLabels ? 2 : 1) - (bbox.y - (bbox.top ?? 0)))})`
);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
log.info('Triangle intersect', node, points, point);
return intersect.polygon(node, points, point);

View File

@@ -10,6 +10,7 @@ import type { Node } from '../../types.js';
import rough from 'roughjs';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
export async function waveEdgedRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -74,6 +75,13 @@ export async function waveEdgedRectangle<T extends SVGGraphicsElement>(
);
updateNodeBounds(node, waveEdgeRect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
// TODO: Implement intersect for this shape
const radius = bounds.width / 2;
return intersect.circle(bounds, radius, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;

View File

@@ -10,7 +10,16 @@ import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function getPoints(w: number, finalH: number, waveAmplitude: number) {
return [
{ x: -w / 2, y: finalH / 2 },
...generateFullSineWavePoints(-w / 2, finalH / 2, w / 2, finalH / 2, waveAmplitude, 1),
{ x: w / 2, y: -finalH / 2 },
...generateFullSineWavePoints(w / 2, -finalH / 2, -w / 2, -finalH / 2, waveAmplitude, -1),
];
}
export async function waveRectangle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
@@ -52,12 +61,7 @@ export async function waveRectangle<T extends SVGGraphicsElement>(
options.fillStyle = 'solid';
}
const points = [
{ x: -w / 2, y: finalH / 2 },
...generateFullSineWavePoints(-w / 2, finalH / 2, w / 2, finalH / 2, waveAmplitude, 1),
{ x: w / 2, y: -finalH / 2 },
...generateFullSineWavePoints(w / 2, -finalH / 2, -w / 2, -finalH / 2, waveAmplitude, -1),
];
const points = getPoints(w, finalH, waveAmplitude);
const waveRectPath = createPathFromPoints(points);
const waveRectNode = rc.path(waveRectPath, options);
@@ -75,6 +79,18 @@ export async function waveRectangle<T extends SVGGraphicsElement>(
}
updateNodeBounds(node, waveRect);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const h = bounds.height;
const waveAmplitude = Math.min(h * 0.2, h / 4);
const finalH = h + waveAmplitude * 2;
const points = getPoints(w, finalH, waveAmplitude);
return intersect.polygon(node, points, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;

View File

@@ -4,6 +4,16 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import intersect from '../intersect/index.js';
import type { D3Selection } from '../../../types.js';
import type { Bounds, Point } from '../../../types.js';
function getOutPathPoints(x: number, y: number, w: number, h: number, rectOffset: number) {
return [
{ x: x - rectOffset, y: y - rectOffset },
{ x: x - rectOffset, y: y + h },
{ x: x + w, y: y + h },
{ x: x + w, y: y - rectOffset },
];
}
export async function windowPane<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
@@ -20,12 +30,7 @@ export async function windowPane<T extends SVGGraphicsElement>(parent: D3Selecti
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const outerPathPoints = [
{ x: x - rectOffset, y: y - rectOffset },
{ x: x - rectOffset, y: y + h },
{ x: x + w, y: y + h },
{ x: x + w, y: y - rectOffset },
];
const outerPathPoints = getOutPathPoints(x, y, w, h, rectOffset);
const path = `M${x - rectOffset},${y - rectOffset} L${x + w},${y - rectOffset} L${x + w},${y + h} L${x - rectOffset},${y + h} L${x - rectOffset},${y - rectOffset}
M${x - rectOffset},${y} L${x + w},${y}
@@ -58,6 +63,17 @@ export async function windowPane<T extends SVGGraphicsElement>(parent: D3Selecti
updateNodeBounds(node, windowPane);
node.calcIntersect = function (bounds: Bounds, point: Point) {
const w = bounds.width;
const h = bounds.height;
const rectOffset = 5;
const x = -w / 2;
const y = -h / 2;
const outerPathPoints = getOutPathPoints(x, y, w, h, rectOffset);
return intersect.polygon(node, outerPathPoints, point);
};
node.intersect = function (point) {
const pos = intersect.polygon(node, outerPathPoints, point);
return pos;

View File

@@ -2,6 +2,7 @@ export type MarkdownWordType = 'normal' | 'strong' | 'em';
import type { MermaidConfig } from '../config.type.js';
import type { ClusterShapeID } from './rendering-elements/clusters.js';
import type { ShapeID } from './rendering-elements/shapes.js';
import type { Bounds, Point } from '../types.js';
export interface MarkdownWord {
content: string;
type: MarkdownWordType;
@@ -43,6 +44,7 @@ interface BaseNode {
height?: number;
// Specific properties for State Diagram nodes TODO remove and use generic properties
intersect?: (point: any) => any;
calcIntersect?: (bounds: Bounds, point: Point) => any;
// Non-generic properties
rx?: number; // Used for rounded corners in Rect, Ellipse, etc.Maybe it to specialized RectNode, EllipseNode, etc.

View File

@@ -18,6 +18,12 @@ export interface Point {
x: number;
y: number;
}
export interface Bounds {
x: number;
y: number;
width: number;
height: number;
}
export interface TextDimensionConfig {
fontSize?: number;

View File

@@ -3,7 +3,7 @@ import type { EdgeData, Point } from '../types.js';
// We need to draw the lines a bit shorter to avoid drawing
// under any transparent markers.
// The offsets are calculated from the markers' dimensions.
const markerOffsets = {
export const markerOffsets = {
aggregation: 18,
extension: 18,
composition: 18,
@@ -104,7 +104,6 @@ export const getLineFunctionsWithOffset = (
adjustment *= DIRECTION === 'right' ? -1 : 1;
offset += adjustment;
}
return pointTransformer(d).x + offset;
},
y: function (