From a user point-of-view, one way to do this would be to allow ports on edges. This, in turn, would be cleaner if edges could be explicitly named.
From a developer point-of-view, I have no idea how much work this would require.
In the interim, I think this matches the "Extended Argumentation Frameworks" web searching leads to:
Greetings. Nice idea but difficult to implement. (For reference compare the glorious hack we use
to support cluster edges by clipping ordinary edges to the boundary of a cluster instead of fully
supporting higher order edges.) The assumption that edges connect node pairs is baked in many
places in the code. Not that this is definitive but early in the project I tried rewriting cgraph
to support higher order objects (e.g. hyperedges, edges connecting arbitrary objects, maybe
nodes containing other objects) and eventually gave up.
If the idea is mainly more convenient syntax, with graphs still represented the conventional way
under the hood, this might be a reasonable project.
One could imagine allowing the graph specification to set the endpoint to be X% of the way along the edge.
One workaround that I've considered was to read out edge positions from a rendered SVG, and to add custom edges to that SVG, completely going around graphviz.
I'm wondering, this seems like graphviz could, at least in theory, do that itself, and reuse its routing algorithms for routing edges between edges as the last phase in its layout pipeline. @truenorth I wanted to ask you, does this make sense or does graphviz not appear to have appropriate routing algorithms that could make this approach practical?
Some example graphs to demonstrate what I mean can be found here 1, here 2 and here 3.
I'm not sure. In principle, there is an API to the spline router that can fit a spline that connects endpoints inside/on a simple polygon. The polygon is made by a lot of special purpose code in dotgen, that is not too flexible. Some complexity arises from the various features of dot, and from the complications of keeping edges away from unrelated notes and non-crossing nearby edges. It would benefit from taking a fresh look at the problem, like this work that I insist on bringing up every few months despite the improbability of anyone else taking up this cause.
So yes definitely just continue with anything that gets the job done, at this point.
@truenorth is there a tracking issue for integrating repulsive curves into graphviz? (I couldn't find one and I'm not sure If I'm using gitlab search properly)
Of course, even if there's an implementation, integrating it would probably still be difficult (I've sent you an email, maybe that approach could help secure funding for something like this)
I'm wondering, have you ever reached out to the authors? It looks like in Figure 26 they have already considered integrating repulsive curves into graphviz.
I'm also wondering how well their approach would perform when there are many edges. The current edge routing algorithms scale really well to reasonably large graphs for me, it seems like repulsive curves would not be able to do that, but maybe I'm completely wrong here.
[The following does NOT take into account Stephen's API or repulsive curves. It is just a set of kludges that
define two ports per edge (for all edges). One near the tail and one near the head
new edges that can terminate in old nodes and/or new edge ports
]
An "augmented" input file for diagram 4.3.1 referenced above:
/**************************************************************Edge attribute (defines 1 port):PORT*(unique for this edge):ID(unique to graph):location(tail/head/TBD)Graph comment (defines 1 new edge):EDGE:tailid:headid:attribute1:value1:attribute2:value2:...**************************************************************/digraph e2e { rankdir=LR ranksep=1.3 // rank2rank, inches splines=ortho // works well (do not use ports) //splines=polyline // ortho looks better edge[arrowhead=vee] node[label=""] {node[group=Ga] start [label="\N"] na1 na2 } {node[group=Gb] nb1 nb2} {node[group=Gc] nc2 nc3} {node[group=Gd] nd3 nd4} {node[group=Ge] ne4} // not sure if this helps/hinders {rank=same na1 nb1} {rank=same na2 nb2 nc2} {rank=same nc3 nd3} {rank=same nd4 ne4} start -> na1 [PORT1="p1:nearH"] na1 ->na2 nb1->nb2 [PORT1="p2:nearT" PORT2="p3:nearH"] nc2->nc3 [PORT1="p4:nearH"] nd3->nd4 [PORTXX="p5:nearH"] start -> nb1 start->ne4 nb1->nc2 nc2->nd3 nd3->ne4 nd3->nb1[constraint=false] comment=" new edges defined in graph-level comment - grossEDGE:p2:p1:style:dashed:color:darkgrey EDGE:p4:p3:style:dotted:color:red EDGE:p5:p4:style:dotted:color:red EDGE:p3:p5:style:dotted"}
Giving:
And the result when splines=true
When I said kluge, I wasn't kidding. Note the PORT* attributes to name the ports and the graph-level comment to define new edges! (yuck)
There is a tiny pre-processing program (GVPR) and a post-processor (GVPR again), plus a multi-step pipeline.
But it does allow edges-to-edges (more or less, kind-of)
p.s. 4.3.2 is wickedly hard to layout (just the nodes and the "traditional" edges). I'd be tempted to use pikchr
@steveroush you might enjoy the following technique (by the guy that put graphviz on the web via visjs) here
digraph { start -> A [dir=none] A -> x1 start -> b [dir=none] b -> x2 x2 -> B [dir=none] B -> x3 B -> A [style=dashed] A [shape=point, xlabel="A"] b [shape=point, xlabel="b"] B [shape=point, xlabel="B"] x1 [shape=circle, label=""] x2 [shape=circle, label=""] x3 [shape=circle, label=""] { rank=same start A x1 } { rank=same b x2 B x3 }}
Of course, it is already known that one can simulate edges between edges by having a single edge consist of 2 edges and an invisible node, however, he uses the xlabel attribute to add a pretty label to such a simulated edge which, I think, is pretty neat and not obvious.
Of course, it's not optimal, but it's something.
Edit: @steveroush thank you for sharing the "group" attribute trick. I think, this is pretty cool, and completely new to me. And thank you for giving it a go. I'm currently still pursuing the SVG-post-processing approach, I'm able to extract edge locations and their center position using a bezierjs-style library. I will have to see how that goes.