Open iainhallam opened 10 months ago
Sorry for derailing this a bit, but for anyone looking for a quick workaround, you can style edges with filter: drop-shadow(...)
to symbolize very basic line crossings, like so:
.flowchart-link {
filter:
drop-shadow(0px 2px 0px black)
drop-shadow(2px 0px 0px black)
drop-shadow(0px -2px 0px black)
drop-shadow(-2px 0px 0px black)
}
The color of the shadow of course needs to be the same as the background, which in this case is black.
But note that is a very bad workaround, adding css filters to a graph with already like 15 edges (at least for me) hurts performance when the graph gets animated for example. It's just not a good idea in general but the best "easy" fix I could come up for now. And if you need more space between the lines at the crossings, you have to add even more shadows, like so for 4 pixels for example:
.flowchart-link {
filter:
drop-shadow(0px 2px 0px black)
drop-shadow(2px 0px 0px black)
drop-shadow(0px -2px 0px black)
drop-shadow(-2px 0px 0px black)
drop-shadow(0px 4px 0px black)
drop-shadow(4px 0px 0px black)
drop-shadow(0px -4px 0px black)
drop-shadow(-4px 0px 0px black)
}
But making it thicker obviously leads to problems like these:
And obviously this approach is utterly useless for graphs on backgrounds that are anything but a solid color.
Because this is not a long-term solution, I'm currently working on a way to detect line crossings from the output SVG and then changing those paths to get "actual" crossings - should be possible, technically...
Sorry again but ahh, I did it! Obviously still a hack, but a way better one.
Assume the following graph and look at all these crosssings:
%%{init:{"flowchart": { "defaultRenderer": "elk" }}}%%
graph TD
Amy --- Bob
Amy --- Christine
Amy --- Daniel
Amy --- Elizabeth
Amy --- Frank
Amy --- Gloria
Bob --- Christine
Bob --- Daniel
Bob --- Elizabeth
Bob --- Frank
Bob --- Gloria
Christine --- Daniel
Christine --- Elizabeth
Christine --- Frank
Christine --- Gloria
Daniel --- Elizabeth
Daniel --- Frank
Daniel --- Gloria
Elizabeth --- Frank
Elizabeth --- Gloria
Frank --- Gloria
Then render the graph definition and run the SVG string through my very badly written function addCrossings
before adding it to the DOM, like so:
import mermaid from "mermaid";
import SVGPathCommander from "svg-path-commander";
import { DOMParser, XMLSerializer } from "xmldom";
const getIntersectionOfSegments = (
[{ x: xA, y: yA }, { x: xB, y: yB }],
[{ x: xC, y: yC }, { x: xD, y: yD }]
) => {
const divisor = (((xA - xB) * (yC - yD)) - ((yA - yB) * (xC - xD)));
const firstNormalizedIntersection = (
(((xA - xC) * (yC - yD)) - ((yA - yC) * (xC - xD))) /
divisor
);
const secondNormalizedIntersecion = -(
(((xA - xB) * (yA - yC)) - ((yA - yB) * (xA - xC))) /
divisor
);
if (
firstNormalizedIntersection >= 0 &&
firstNormalizedIntersection <= 1 &&
secondNormalizedIntersecion >= 0 &&
secondNormalizedIntersecion <= 1
) {
const intersectionX = (xA + (firstNormalizedIntersection * (xB - xA)));
const intersectionY = (yA + (firstNormalizedIntersection * (yB - yA)));
return {
x: intersectionX,
y: intersectionY
};
}
return null;
};
const parser = new DOMParser();
const serializer = new XMLSerializer();
const padding = 5;
const addCrossings = (svgString) => {
const svgDocument = parser.parseFromString(svgString, "text/xml");
const edgeNodes = [
...[...svgDocument.childNodes[0].childNodes]
.find((childNode) => childNode.tagName === "g" && childNode.getAttribute("class") === "edges edgePath").childNodes
]
.filter(({ tagName }) => tagName === "path");
for (const [index, edgeNode1] of edgeNodes.entries()) {
const pathString1 = edgeNode1.getAttribute("d");
const path1 = new SVGPathCommander(pathString1);
for (const edgeNode2 of edgeNodes.slice(index + 1)) {
const pathString2 = edgeNode2.getAttribute("d");
const path2 = new SVGPathCommander(pathString2);
const {
segments: points1
} = path1;
const {
segments: points2
} = path2;
const paddedPoints1 = [];
for (const [point1Index, point1] of points1.slice(0, -1).entries()) {
const [
command1,
x1,
y1
] = point1;
const [
command2,
x2,
y2
] = points1[point1Index + 1];
const orientation1 = x1 === x2 ? "vertical" : "horizontal";
const direction1 = orientation1 === "vertical"
? (
y1 <= y2
? "down"
: "up"
)
: (
x1 <= x2
? "right"
: "left"
);
paddedPoints1.push(point1);
for (const [point2Index, point2] of points2.slice(0, -1).entries()) {
const [
command3,
x3,
y3
] = point2;
const [
command4,
x4,
y4
] = points2[point2Index + 1];
const intersection = getIntersectionOfSegments(
[
{
x: x1,
y: y1
},
{
x: x2,
y: y2
}
],
[
{
x: x3,
y: y3
},
{
x: x4,
y: y4
}
]
);
if (intersection) {
paddedPoints1.push(
[
"L",
intersection.x -
(
orientation1 === "horizontal"
? padding * (direction1 === "left" ? -1 : 1)
: 0
),
intersection.y -
(
orientation1 === "vertical"
? padding * (direction1 === "up" ? -1 : 1)
: 0
)
],
[
"M",
intersection.x +
(
orientation1 === "horizontal"
? padding * (direction1 === "left" ? -1 : 1)
: 0
),
intersection.y +
(
orientation1 === "vertical"
? padding * (direction1 === "up" ? -1 : 1)
: 0
)
]
);
}
}
}
paddedPoints1.push(points1.at(-1));
path1.segments = paddedPoints1;
edgeNode1.setAttribute("d", path1.toString());
}
}
return serializer.serializeToString(svgDocument);
};
const graphDefinition = `...`;
const { svg: renderedSvgString } = await mermaid.render("graph", graphDefinition);
const svgStringWithCrossings = addCrossings(renderedSvgString);
This was done with the elk renderer in mind because I use it, so it probably needs some tinkering to make it more generic, but feel free to yank this code and do something better with it.
@nnmrts This is very nice looking, thanks. We will look and see how we might be able to incorporate. @ashishjain0512 @knsv
If it's the only way, please incorporate this drop-shadow concept.
OTOH if there's a way to do a line-cross "bump" like this (snippet of diagram from #334 -- specifically referring to the bump in the upper right portion of the diagram here)
...that would be my preference...
Description
334 was closed by a bot, but it's a key feature for serious diagramming work. Lines that cross should be able to be visually indicated, ideally in a user-selected choice of things like little arcs, or a break in the rear-most line.
Steps to reproduce
Create any diagram with crossing lines. See the slightly misleading crosses.
Screenshots
No response
Code Sample
No response
Setup
Suggested Solutions
A config option for the diagram on how to show crossing lines.
Additional Context
No response