1

I am trying to design a very specific type of graph with certain colors and shapes but I am unable to tell if this is achievable with Graphviz.

My goal is to draw a directed graph where each node:

  • is composed of 3 smaller boxes aligned horizontally (striped rectangle or record, rounded)
  • contains 3 labels accordingly
  • is filled with a gradient
  • has a black square node located on top of the middle box

Example (all black boxes have been placed manually) :

enter image description here

I have made it to the third requirement but I am struggling implementing the last one. How would you place square nodes (rect, square, box, etc) on top of the first set of nodes (colored rectangles) so as they perfectly fit the dimensions of the middle boxes they are meant to cover ? Is that even possible with Graphviz ?

Example code (Python):

from graphviz import Digraph

# Dictionary storing relations between nodes
d = {0: set([1, 2, 3]), 
     1: set([4, 5, 6]), 
     2: set([7, 8, 9, 10]), 
     3: set([0]), 
     4: set([]), 
     5: set([]), 
     6: set([]), 
     7: set([]), 
     8: set([0]), 
     9: set([]), 
     10: set([])} 

# List of node labels (3 labels per node)
P = [('S', 'M', 'S'),
     ('M', 'S', 'L'),
     ('M', 'S', 'S'),
     ('M', 'S', 'M'),
     ('S', 'L', 'L'),
     ('S', 'L', 'M'),
     ('S', 'L', 'X'),
     ('S', 'S', 'S'),
     ('S', 'S', 'M'),
     ('S', 'S', 'L'),
     ('S', 'S', 'X')]

# Dictionary storing the colors corresponding to each label
c = {'S':'olivedrab1', 
     'M':'mediumturquoise', 
     'L':'deepskyblue', 
     'X':'palevioletred1'}

# Create a "directed graph" with general node and edge attributes
G = Digraph(node_attr={'shape':'record',  
                       'style':'rounded, filled',
                       'color':'white', 
                       'height':'0.1',
                       'fontcolor':'white'},
           
            edge_attr={'color':'grey', 
                       'arrowhead':'vee'} 
           )
       
G.attr('graph', bgcolor='transparent')

# 1st pass: create all nodes (0 to 10)
for k in d:
    l1, l2, l3 = P[k]
    
    # set specific attribute to each node (label & colors)
    G.attr('node', label='{} | {} | {}'.format(l1, ' ', l2), fillcolor='{}:{}'.format(c[l1], c[l2]))
    G.node(str(k))

# 2nd pass: create edges between nodes
for k in d:
    l1, l2, l3 = P[k]
    for i in d[k]:
        if i in d:
            G.edge(str(k), str(i))

# Then, how to overlap black square nodes ?
solub
  • 1,291
  • 17
  • 40
  • 1
    Why can’t the black ‘node’ simply be the centre box? – DisappointedByUnaccountableMod Jul 08 '21 at 21:39
  • When applying a gradient on the main node, all three inner boxes are colored accordingly. It is not possible to stop the gradient between the end of the 1st box and the beginning of the 3rd one. Hence the necessity to put another node on top. – solub Jul 08 '21 at 21:45
  • OK so that’s a bit X-Y problemy; your real question is ‘how do I make a 3-box node with the edge boxes gradient filled and the centre box solid black?’ Looks like it might be possible using HTML nodes like this: https://graphviz.org/Gallery/gradient/table.html but pygraphviz may not help you with this. BTW please make the code in your question a [mre] so anyone can run it _without having to add anything_ and get output like you see.. – DisappointedByUnaccountableMod Jul 08 '21 at 22:00
  • Now you’ve added the import, yes ;-) – DisappointedByUnaccountableMod Jul 08 '21 at 22:10

1 Answers1

0

I don't think it's possible in dot/graphviz to force nodes to superimpose one node on another; I guess that would sort of defeat the whole point of rendering a graph visually.

This code uses HTML(-like) shapes for the nodes; these describe a three-cell table with gradient colours on the left and right side, and solid black with white text in the centre.

import graphviz

# Dictionary storing relations between nodes
d = {0: set([1, 2, 3]), 
     1: set([4, 5, 6]), 
     2: set([7, 8, 9, 10]), 
     3: set([0]), 
     4: set([]), 
     5: set([]), 
     6: set([]), 
     7: set([]), 
     8: set([0]), 
     9: set([]), 
     10: set([])} 

# List of node labels (3 labels per node)
P = [('S', 'M', 'S'),
     ('M', 'S', 'L'),
     ('M', 'S', 'S'),
     ('M', 'S', 'M'),
     ('S', 'L', 'L'),
     ('S', 'L', 'M'),
     ('S', 'L', 'X'),
     ('S', 'S', 'S'),
     ('S', 'S', 'M'),
     ('S', 'S', 'L'),
     ('S', 'S', 'X')]

# Dictionary storing the colors corresponding to each label
c = {'S':'olivedrab1:grey', 
     'M':'mediumturquoise:gray', 
     'L':'deepskyblue:gray', 
     'X':'palevioletred1:gray'}

# Create a "directed graph" with general node and edge attributes
G = graphviz.Digraph(
            format='svg'
            ,edge_attr={'color':'grey', 
                       'arrowhead':'vee'} 
           )
       
G.attr('graph', bgcolor='transparent')

# 1st pass: create all nodes (0 to 10)
for k in d:
    l1, l2, l3 = P[k]
    
    G.node(name=str(k),shape='plain',label=f'''<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" BGCOLOR="gray">
  <TR>
    <TD BGCOLOR="{c[l1]}" GRADIENTANGLE="45">{l1}</TD>
    <TD BGCOLOR="black"><FONT COLOR="white">{l2}</FONT></TD>
    <TD BGCOLOR="{c[l3]}" GRADIENTANGLE="135">{l3}</TD>
  </TR>
</TABLE>
>
'''
)

# 2nd pass: create edges between nodes
for k in d:
    l1, l2, l3 = P[k]
    for i in d[k]:
        if i in d:
            G.edge(str(k), str(i))

G.view()

Result:

enter image description here

Notes:

  • the node gets the shape of the HTML when shape is set to 'plain'
  • The colour gradient is specified in the cell <TD> as BGCOLOR=“colour1:colour2”
  • The angle of the gradient is also specified in the cell <TD> as e.g.GRADIENTANGLE=“45”

More info about HTML nodes here: http://graphviz.org/doc/info/shapes.html#gradientangle