How to enforce sub graphs to be on the same horizontal line in Graphviz - graphviz

I'm using graphviz (dot) to generate time expanded graph to use in my master thesis. However, my drawing results the following graph:
As you can see, the third line is a little bit shifted to the right.I want all the lines in the same horizontal line. Also i want the x.th node in each line to stay at the same horizontal line of other x.th nodes of lines. Changing the edge weights would not helped me.
I could not find anything related to this problem so far.
What i want to achieve is something like this:
Here is my code:
digraph G {
1[label="1"]
2[label="1"]
3[label="1"]
4[label="1"]
5[label="1"]
6[label="1"]
7[label="2"]
8[label="2"]
9[label="2"]
10[label="2"]
11[label="2"]
12[label="2"]
13[label="3"]
14[label="3"]
15[label="3"]
16[label="3"]
17[label="3"]
18[label="3"]
19[label="4"]
20[label="4"]
21[label="4"]
22[label="4"]
23[label="4"]
24[label="4"]
rankdir="LR";
node[width=0.15, height=0.15, shape=point];
edge[weight=500, label="1"];
1 -> 2 -> 3 -> 4 -> 5 -> 6 ;
7 -> 8 -> 9 -> 10 -> 11 -> 12 ;
13 -> 14 -> 15 -> 16 -> 17 -> 18 ;
19 -> 20 -> 21 -> 22 -> 23 -> 24 ;
edge[weight=3];
1 -> 9;
2 -> 9;
3 -> 11;
13 -> 22;
14 -> 22;
15 -> 24;
edge[weight=1];
1 -> 14;
2 -> 15;
3 -> 17;
7 -> 21;
8 -> 23;
9 -> 22;
}
Any help would be appreciated.

Instead of using weight try using the attribute constraint=false for all edges which go from one "line" to another.
Setting constraint to false makes graphviz to not consider those edges when laying out the nodes of the graph.
See also this very similar question & answer

As far as I know, the only way is to specify an explicit position for each individual node.
See: How to force node position (x and y) in graphviz

Related

Algorithm help :Given a num, can it finally be 1

Given a number, you can divide it or its contiguous part by 2 , or multiply it or its continuous part by 2. Can the number finally be 1?
For example : 13
3 is a part of 13, first we take 3 * 2 = 6, the num turn to be 16,
second we can operate the whole num 16, 16 / 2 = 8, the num is 8 now,
8/2 = 4, num is 4 now,
4/2 = 2, num is 2 now,
2/2 = 1, num is 1 now.
finally we can say 13 can turn into 1, and the path is 13->16->8->4->2->1, we can use a List to store the path.
Example :27
first we operate the whole num 27, 27 * 2 = 54;
then we take 4 as the part of 54, 4 / 2 = 2 , so the 4 is 2 now, num becomes 52;
operate 52, 52 / 2 = 26, num is 26 now;
operate 26, 26 / 2 = 13, num is 13 now;
we just analyzed 13, so 27 can turn into 1 finally.
How to analyze such problem? What's the main idea of solving such type problem?
Sorry about the confusing description, let's take a more complex example: 316
16 is a contiguous part, let 16 / 2 = 8 , so the num is 38 now,
then take 8 / 2 = 4 , the num is 34,
take 4 / 2 = 2, the num is 32,
now take the whole num 32 / 2 = 16,
16 / 2 = 8, num is 8,
8 / 2 = 4, num is 4,
4 / 2 = 2, num is 2,
finally 2 / 2 = 1.
We say, original num 316 can turn into 1 finally after above conversion.
And the contiguous part means, if the input num is 12345,
then 123, 234,345,12,2345 and so on, they are all contiguous parts.
Any continuous subset of num is fine,including head or tail is NOT necessary.
The question is :
How to judge such a num? And if the num can turn into 1, print the path.
Can you find the shortest way?
I got some hints from interviewer (The interview is over):
Most of numbers are eligible, that means nums which are NOT eligible, these characteristics are obvious.
Brute fore way's time complexity is too high, we should pruning timely. (Slide window + pruning ?)
Here is a simple and unoptimized breadth-first search.
def shortest_digit_path (n):
path_from = {n: None}
queue = [n]
count = 0
while True:
m = queue.pop(0)
count += 1
if 0 == count %1000:
print((count, m))
if m == 1:
break
x = str(m)
for i in range(len(x)):
for j in range(i+1, len(x) + 1):
y = x[0:i]
z = x[i:j]
w = x[j:]
if z[0] == '0':
continue # The continuous section is not a proper number.
# Try half of z
if z[-1] in ['2', '4', '6', '8']:
next_m = int(y + str(int(z)//2) + w)
if next_m not in path_from:
path_from[next_m] = m
queue.append(next_m)
# Try doubling z
next_m = int(y + str(int(z)*2) + w)
if next_m not in path_from:
path_from[next_m] = m
queue.append(next_m)
path = []
while m is not None:
path.append(m)
m = path_from[m]
return list(reversed(path))
After playing around with this for a bit, I came up with the following observations.
If the number ends in 0 or 5, there is no path to having any other digit at the end, and therefore you can't get to 1. (The above function will just run forever.
For anything else we can find a path just dealing with 1-2 digits at a time.
Here are the special cases for observation #2. Our first goal is to get to just 0, 1, and 5 as digits.
0: 0
1: 1
2: 2 -> 1
3: 3 -> 6 -> 12 -> 24 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
4: 4 -> 2 -> 1
5: 5
6: 6 -> 12 -> 24 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
7: 7 -> 14 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
8: 8 -> 4 -> 2 -> 1
9: 9 -> 18 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
And now from the start of the number we have to deal with the following cases that reduce the number of digits and get back to our desired form.
10: 10 -> 5
11: 11 -> 22 -> 24 -> 28 -> 56 -> 112 -> 16 -> 8 -> 4 -> 2 -> 1
15: 15 -> 110 -> 220 -> 240 -> 280 -> 560 -> 1120 -> 160 -> 80 -> 40 -> 20 -> 10 -> 5
50: 50 -> 25 -> 15 -> 110 -> 220 -> 240 -> 280 -> 560 -> 1120 -> 160 -> 80 -> 40 -> 20 -> 10 -> 5
51: 51 -> 52 -> 26 -> 16 -> 8 -> 4 -> 2 -> 1
55: 55 -> 510 -> 520 -> 260 -> 160 -> 80 -> 40 -> 20 -> 10 -> 5
With this set of rules we can first normalize the number to a standard form, then we can shorten it one digit at a time. This lets us essentially instantly come up with a path. Almost certainly not the shortest one, but definitely a path.
Writing that function is left as an exercise to the reader.
Now back to the shortest path. The algorithm for the breadth-first search can be made much faster if we start with a breadth-first search from both ends and meet in the middle. For this you'd need to also have a path_to that is initialized with {1: None}, a queue containing elements of the form (m, is_rising) and initialize it with [(1, True), (n: False)]. You'd then have to branch on is_rising and before entering values into path_from/path_to check for whether it is in path_to/path_from. If it is, you've met in the middle. Now work out both halves of the path and join them together.
The approach is tricker. But it will let you find the shortest path in the square root of the number of steps that the current approach takes.

GraphViz: trouble with grid layout using pos attribute

I'm trying to align this graph, whose purpose is to show the results of sorting.
I need a general solution.
The dot file is generated, so I can't use hand tweaking.
I've tried pos, pos with !, dot -n, dot -Kneato, dot -Kfdp, and other things.
Here is the source (laid out for human grokkability):
digraph x {
rankdir=LR
11 [pos="1,1"] 21 [pos="2,1"] 31 [pos="3,1"] 41 [pos="4,1"]
12 [pos="1,2"] 22 [pos="2,2"] 32 [pos="3,2"] 42 [pos="4,2"]
13 [pos="1,3"] 23 [pos="2,3"] 33 [pos="3,3"]
14 [pos="1,4"] 24 [pos="2,4"]
11:e -> 21:w 21:e -> 31:w 31:e -> 41:w
12:e -> 22:w 22:e -> 32:w 32:e -> 42:w
22:e -> 31:w
# 41:e -> 21:w
# 41:e -> 22:w
# 12:e -> 12:w
# 32:e -> 32:w
13:e -> 23:w 23:e -> 33:w
14:e -> 24:w
# 13:e -> 14:w
# 14:e -> 13:w
# 23:e -> 24:w
# 24:e -> 23:w
}
In the first rendering, you'll see essentially how it should look (except for the shuffled row order).
Later renderings, which will include cyclic edges, should retain this basic layout.
dot -Tpng -o test.png test.dot:
Now some renderings after uncommenting the cyclic edges.
dot -Tpng -o test.png test.dot:
dot -Kneato -Tpng -o test.png test.dot:
dot -Kfdp -Tpng -o test.png test.dot:
Not using pos as in my experience (confirmed by your attempts), it does not work well with the dot engine, and with neato and others I have never been able to produce something like a grid layout.
Three general steps to get closer with dot:
align the nodes vertically with rank = same
align the nodes horizontally using weight = 10 (or any other arbitrary number that works)
add invisible vertical struts as necessary (here 14 -> 12)
make sure that you point the edges following the established hierarchy, meaning explicitly using dir = back where appropriate
My shot at your situation:
digraph
{
rankdir = LR;
{rank = same; 14 -> 13 -> 12 -> 11[ style = invis ] }
{rank = same; 24 -> 23 -> 22 -> 21[ style = invis ] }
{rank = same; 33 -> 32 -> 31[ style = invis ] }
{rank = same; 42 -> 41[ style = invis ] }
14 -> 24[ weight = 10 ];
13 -> 23 -> 33[ weight = 10 ];
12 -> 22 -> 32 -> 42[ weight = 10 ];
11 -> 21 -> 31 -> 41[ weight = 10 ];
22:e -> 31:w;
12:e -> 12:w
32:e -> 32:w
21:se -> 41:sw[ dir = back ];
22:se -> 41:sw[ dir = back ];
14 -> 12 [ color = red ];
14:w -> 13:w
13:e -> 14:e[ dir = back ];
24:w -> 23:w
23:e -> 24:e[ dir = back ];
}
which yields

gvpack (graphviz) not preserving labels of the individual digraphs

There are four dot files (cluster_0, cluster_1, cluster_2, cluster_3) which need to be merged into a single dot file.
The code of the individual dot files is as follows.
digraph cluster_0{
sortv=1;
penwidth=0;pencolor=transparent;
label="1A";
rankdir=LR;
node [fontname = "times-roman-bold",fontsize=12];
edge [fontname = "times-roman-bold",fontsize=12,arrowsize=0.75];
1 [label="1:A"];
2 [label="2:C"];
3 [label="3:G"];
3 -> 1
1 -> 2
3 -> 2
2 -> 3
}
digraph cluster_1 {
sortv=2;
penwidth=0;pencolor=transparent;
label="1B";
node [fontname = "times-roman-bold",fontsize=12];
edge [fontname = "times-roman-bold",fontsize=12,arrowsize=0.75];
rankdir=LR;
11 [label="1:A"];
12 [label="2:C"];
13 [label="3:G"];
14 [label="4:T"];
14 -> 11
11 -> 12
12 -> 13
12 -> 14
13 -> 14
}
digraph cluster_2 {
rankdir=LR;
sortv=3;
penwidth=0;pencolor=transparent;
label="1C";
node [fontname = "times-roman-bold",fontsize=12];
edge [fontname = "times-roman-bold",fontsize=12,arrowsize=0.75];
21 [label="1:A"];
22 [label="2:C"];
23 [label="3:G"];
24 [label="4:T"];
25 [label="5:A"];
24 -> 21
21 -> 22
21 -> 25
25 -> 22
22 -> 23
23 -> 24
}
digraph cluster_3{
sortv=4;
penwidth=0;pencolor=transparent;
label="1D"
node [fontname = "times-roman-bold",fontsize=12];
edge [fontname = "times-roman-bold",fontsize=12,arrowsize=0.75];
rankdir=LR;
31 [label="1:A"];
32 [label="2:C"];
33 [label="3:G"];
31:c -> 31:w
32 -> 31 [dir=both,len=1.25];
33 -> 31 [dir=both,];
32 -> 32
33 -> 32 [dir=both,];
33 -> 33
}
To generate a merged single dot file ...I ran the following command.
neato -Gstart=5 cluster_0.dot cluster_1.dot cluster_2.dot cluster_3.dot | gvpack -array_u4 | neato -n2 -s -Teps -o merge.eps
merge.eps came perfectly well but the labels of the individual graphs went missing. How can we preserve the labels of the individual graphs in the output merge file ? Can anyone help to fix this ?
Here I am attaching the single individual file where the label is shown
I compiled the individual file with the following command.
neato -Gstart=5 cluster_0.dot -Teps -o cluster_0.eps
Also I am attaching the merge output file which removed the labels of the graphs.
This is not an answer but rather sharing experiences: As it turns out while experimenting with various options, it seems to be the neato engine that supresses the labels. If you use dot, you may not get the exact layout you want but you get the labels:
dot -Gstart=5 cluster_0.dot cluster_1.dot cluster_2.dot cluster_3.dot | gvpack -array_u4 | dot -n2 -s -T jpg -o merge.jpg
gives you
My personal preference would by to have one master file so.m4
digraph so
{
define(`digraph',`subgraph')
rankdir=LR;
include(cluster_0.dot)
include(cluster_1.dot)
include(cluster_2.dot)
include(cluster_3.dot)
}
that I process with
m4 so.m4 > so.dot
In the resulting so.dot file, I have all clusters combined, and I can go to the next step (here producing a JPG) with a simple
dot -T png -o so.png so.dot
The advantage of that is that you can use the master file for "global options". I think it is easier to maintain and less unwieldy than the command with all the pipes and options. But that's a matter of taste.
With the option rankdir=LR; this version is produced:
Have fun - happy to do more but I'd need to know more about your project.

Random position of nodes in hierarchical tree with alternate paths

I'm struggling with directed graph hierarchy tree visualization (distributed network in this case). Using graphviz: dot I have this tree:
digraph G {
node[width=0.14, height=0.14];
edge[weight=3, color = "0.000 0.000 0.0"];
1 -> 2;
1 -> 3;
edge[weight=2, color = "0.000 0.000 0.175"];
2 -> 4;
2 -> 5;
3 -> 6;
3 -> 7;
edge[weight=1, color = "0.000 0.000 0.825"];
3 -> 4;
3 -> 5;
2 -> 6;
2 -> 7;
}
Resulting in:
You can see that the primary paths (edges with higher weights) are crossed. The goal is to have the preferred path nodes as close as possible. I can do it by changing the order of nodes randomly so the output looks like this:
However I don't want to think about the ordering algorithm as I want to automate this on hundreds of nodes.
From documentation:
The weight of an edge provides another way to keep edges straight. An edge’s weight suggests some measure of an edge’s importance; thus, the heavier the weight, the closer together its nodes should be. dot causes edges with heavier weights to be drawn shorter and straighter.
But this is not the case as the nodes are drawn in random order. What am I doing wrong?
Probably not the answer you were looking for, but since dot doesn't seem to work with edge weights in this case:
You may just use constraint=false for the lightgray edges.
Could you live with a solution like this, which places invisible ranks and invisible edges to enforce the left-to-right ordering of your nodes? While it adds some white space to the left of the diagram, at least it neatly solves the random-ordering issue within each rank, and lends itself to being automated.
digraph G {
node[width=0.14, height=0.14];
{
rankdir="TB";
edge [style=invis];
rank1 [style=invis];
rank2 [style=invis];
rank3 [style=invis];
rank1 -> rank2 -> rank3 [style=invis];
}
edge[weight=3, color = "0.000 0.000 0.0"];
1 -> 2;
1 -> 3;
edge[weight=2, color = "0.000 0.000 0.175"];
2 -> 4;
2 -> 5;
3 -> 6;
3 -> 7;
{
edge [style=invis];
rank=same;
rank2 -> 2 -> 3;
}
edge[weight=1, color = "0.000 0.000 0.825"];
3 -> 4;
3 -> 5;
2 -> 6;
2 -> 7;
{
rank=same;
edge [style=invis];
rank3 -> 4 -> 5 -> 6 -> 7 ;
}
}

Finding the root value of a binary tree?

I have an array which stores the relations of values, which makes several trees something like:
So, in this case, my array would be (root, linked to)
(8,3)
(8,10)
(3,1)
(3,6)
(6,4)
(6,7)
(10,14)
(14,13)
And i'd like to set all the root values in the array to the main root in the tree (in all trees):
(8,3)
(8,1)
(8,6)
(8,4)
(8,7)
(8,10)
(8,14)
(8,13)
What algorithm should i investigate?
1) Make a list of all the unique first elements of the tuples.
2) Remove any that also appear as the second element of a tuple.
3) You'll be left with the root (8 here). Replace the first elements of all tuples with this value.
EDIT:
A more complicated approach that will work with multiple trees would be as follows.
First, convert to a parent lookup table:
1 -> 3
3 -> 8
4 -> 6
6 -> 3
7 -> 6
10 -> 8
13 -> 14
14 -> 10
Next, run "find parent with path compression" on each element:
1)
1 -> 3 -> 8
gives
1 -> 8
3 -> 8
4 -> 6
...
3)
3 -> 8
4)
4 -> 6 -> 3 -> 8
gives
1 -> 8
3 -> 8
4 -> 8
6 -> 8
7 -> 6
...
6)
6 -> 8 (already done)
7)
7 -> 6 -> 8
etc.
Result:
1 -> 8
3 -> 8
4 -> 8
6 -> 8
7 -> 8
...
Then convert this back to the tuple list:
(8,1)(8,3)(8,4)...
The find parent with path compression algorithm is as find_set would be for disjoint set forests, e.g.
int find_set(int x) const
{
Element& element = get_element(x);
int& parent = element.m_parent;
if(parent != x)
{
parent = find_set(parent);
}
return parent;
}
The key point is that path compression helps you avoid a lot of work. In the above, for example, when you do the lookup for 4, you store 6 -> 8, which makes later lookups referencing 6 faster.
So assume you have a list of tuples representing the points:
def find_root(ls):
child, parent, root = [], [], []
for node in ls:
parent.append(node[0])
child.append(node[1])
for dis in parent:
if (!child.count(dis)):
root.append(dis)
if len(root) > 1 : return -1 # failure, the tree is not formed well
for nodeIndex in xrange(len(ls)):
ls[nodeIndex] = (root[0], ls[nodeIndex][1])
return ls

Resources