Given this demo graph, it renders (vaguely) well in the default engine, and awfully in Neato:
import graphviz
a = [
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 18.442211], [0, 1.5577889]],
]
e = [
[0, 7.8787879, 15.353535, 0, 0, 31.212121],
[0, 0, 0, 0, 0, 0],
[11.392405, 0, 0, 22.025316, 46.582278, 0],
]
f = [88.607595, 12.121212, 64.646465, 37.974684, 84.97551, 67.23009]
w = [45.555556, 0, 0, 60, 150, 100]
Cuntreat = (100, 250, 80, 200, 150, 130)
Ctreat = (650, 200)
Cfresh = 1
plants = range(len(Cuntreat))
treatments = range(len(Ctreat))
graph = graphviz.Digraph(
name='treatment_flow', format='svg', engine='neato',
graph_attr={
'rankdir': 'LR',
'overlap': 'false', 'splines': 'true',
},
)
graph.node(name='fresh', label='Freshwater')
graph.node(name='waste', label='Wastewater')
for plant in plants:
graph.node(
name=str(plant),
shape='Mrecord',
label=
'{'
'{'
'<fresh_in> fresh|'
'<untreat_in> untreated|'
'<treat_in> treated'
'}|'
r'Plant \N|'
'{'
'<waste_out> waste|'
'<untreat_out> untreated|'
+ '|'.join(
f'<treat_{treatment}_out> treatment {treatment}'
for treatment in treatments
) +
'}'
'}'
)
for i, a_slice in enumerate(a):
for j, a_row in enumerate(a_slice):
for treatment, (contam, flow) in enumerate(zip(Ctreat, a_row)):
if flow > 0:
graph.edge(
tail_name=f'{i}:treat_{treatment}_out',
head_name=f'{j}:treat_in',
label=f'{flow:.1f} ({contam*flow:.1f})',
)
for i, (e_row, contam) in enumerate(zip(e, Cuntreat)):
for j, flow in enumerate(e_row):
if flow > 0:
graph.edge(
tail_name=f'{i}:untreat_out',
head_name=f'{j}:untreat_in',
label=f'{flow:.1f} ({contam*flow:.1f})',
)
for j, flow in enumerate(f):
if flow > 0:
graph.edge(
tail_name='fresh',
head_name=f'{j}:fresh_in',
label=f'{flow:.1f} ({Cfresh*flow:.1f})',
)
for i, (flow, contam) in enumerate(zip(w, Cuntreat)):
if flow > 0:
graph.edge(
tail_name=f'{i}:waste_out',
head_name='waste',
label=f'{flow:.1f} ({contam*flow:.1f})',
)
graph.view()
with output
digraph treatment_flow {
graph [overlap=false rankdir=LR splines=true]
fresh [label=Freshwater]
waste [label=Wastewater]
0 [label="{{<fresh_in> fresh|<untreat_in> untreated|<treat_in> treated}|Plant \N|{<waste_out> waste|<untreat_out> untreated|<treat_0_out> treatment 0|<treat_1_out> treatment 1}}" shape=Mrecord]
1 [label="{{<fresh_in> fresh|<untreat_in> untreated|<treat_in> treated}|Plant \N|{<waste_out> waste|<untreat_out> untreated|<treat_0_out> treatment 0|<treat_1_out> treatment 1}}" shape=Mrecord]
2 [label="{{<fresh_in> fresh|<untreat_in> untreated|<treat_in> treated}|Plant \N|{<waste_out> waste|<untreat_out> untreated|<treat_0_out> treatment 0|<treat_1_out> treatment 1}}" shape=Mrecord]
3 [label="{{<fresh_in> fresh|<untreat_in> untreated|<treat_in> treated}|Plant \N|{<waste_out> waste|<untreat_out> untreated|<treat_0_out> treatment 0|<treat_1_out> treatment 1}}" shape=Mrecord]
4 [label="{{<fresh_in> fresh|<untreat_in> untreated|<treat_in> treated}|Plant \N|{<waste_out> waste|<untreat_out> untreated|<treat_0_out> treatment 0|<treat_1_out> treatment 1}}" shape=Mrecord]
5 [label="{{<fresh_in> fresh|<untreat_in> untreated|<treat_in> treated}|Plant \N|{<waste_out> waste|<untreat_out> untreated|<treat_0_out> treatment 0|<treat_1_out> treatment 1}}" shape=Mrecord]
1:treat_1_out -> 4:treat_in [label="18.4 (3688.4)"]
1:treat_1_out -> 5:treat_in [label="1.6 (311.6)"]
0:untreat_out -> 1:untreat_in [label="7.9 (787.9)"]
0:untreat_out -> 2:untreat_in [label="15.4 (1535.4)"]
0:untreat_out -> 5:untreat_in [label="31.2 (3121.2)"]
2:untreat_out -> 0:untreat_in [label="11.4 (911.4)"]
2:untreat_out -> 3:untreat_in [label="22.0 (1762.0)"]
2:untreat_out -> 4:untreat_in [label="46.6 (3726.6)"]
fresh -> 0:fresh_in [label="88.6 (88.6)"]
fresh -> 1:fresh_in [label="12.1 (12.1)"]
fresh -> 2:fresh_in [label="64.6 (64.6)"]
fresh -> 3:fresh_in [label="38.0 (38.0)"]
fresh -> 4:fresh_in [label="85.0 (85.0)"]
fresh -> 5:fresh_in [label="67.2 (67.2)"]
0:waste_out -> waste [label="45.6 (4555.6)"]
3:waste_out -> waste [label="60.0 (12000.0)"]
4:waste_out -> waste [label="150.0 (22500.0)"]
5:waste_out -> waste [label="100.0 (13000.0)"]
}
Among the specific problems I need to fix:
- Edge heads and tails should not enter ports at nonsensical directions
- At least some effort should be made to avoid edge-node overlap
- Node placement needn't be as it's shown here. For example, moving plant 3 to the right would probably help avoid overlap.
I'm sure there isn't a magic bullet for all of these, but surely Neato should be able to do better?



I can't reproduce the symptom. (I mean, it's bad, but not as bad.)
I'm using version 9:
Layout should improve a bit if we display smaller plants, and especially if there's fewer of them.
With the
dotlayout engine, the usual way to fix a train wreck is to mess with[constraint = false];
sometimes in combination with an edge that does impose a layout constraint but which is
[style = invis].Here is one example of that.
Switch to dot.
Force the rank of selected nodes. Also, some edges won't affect rank.
It will still be a bit cluttered, but now one more lever is available for shuffling the clutter around.