Package: fastdot
fastdotA simple wrapper overpydotto make it more consistent, unsurprising, and pythonicAcknowledgement:fastdotis heavily influenced by work fromDavid Page, who built a system for drawing graphs based on a highly flexible data structure he designed.InstallWe suggest installing with conda:conda install -c fastai fastdot. You can alternatively install with pip:pip install fastdot; however, if you use this approach, you'll also need to install graphviz (e.g. usingapt,brew, etc).SynopsisStart with some data representing objects and connections between them (e.g. they wouldn't normally be just strings like in this example, but would be neural net layers, or users and products, or car trips, etc):layers1=['conv','conv','lin']layers2=['conv','lin']block1,block2=['block1','block2']conns=((block1,block2),(block1,layers2[-1]))Then map them directly to a visual respresentation:g=graph_items(seq_cluster(layers1,block1),seq_cluster(layers2,block2))g.add_items(*object_connections(conns))gSee thesymbolic graphsandobject graphssections below for a more complete example.fastdot overviewfastdotis a thin wrapper over the excellentpydotprogram (which is in turn a thin wrapper over the absolutely wonderfulGraphviz software), designed to make it more consistent, unsurprising, and pythonic. (An example of removingsurprise:pydot.Node('node')gives an obscure compilation exception, sincenodeis a keyword in the underlyinggraphvizprogram, whereasfastdot.Node('node')works just fine, due to auto-quoting.) In fact, you never need to provide names infastdot; you can create edges directly between objects.Here's a quick example of some of the main functionality:g=Dot()c=Cluster('cl',fillcolor='pink')a1,a2,b=c.add_items('a','a','b')c.add_items(a1.connect(a2),a2.connect(b))g.add_item(Node('Check tooltip',tooltip="I have a tooltip!"))g.add_item(c)gAs you see, graphs know how to show themselves in Jupyter notebooks directly and can be exported to HTML (it uses SVG behind the scenes). Tooltips appear in both notebooks and exported HTML pages. Nodes with the same label, by default, are set to the same color. Also, as shown above, you can just useadd_itemoradd_items, regardless of the type of item.Symbolic graphsfastdotis particularly designed to make it easier to create graphs symbolically - for instance, for Python dictionaries, PyTorch/TensorFlow models, and so forth. Here's a simple example with some mock neural network layers and sequential models. First, let's define our mock classes:@dataclass(frozen=True)classLayer:name:str;n_filters:int=1classLinear(Layer):passclassConv2d(Layer):pass@dataclass(frozen=True)classSequential:layers:list;name:strHere's our sequential blocks for our "model":block1=Sequential([Conv2d('conv',5),Linear('lin',3)],'block1')block2=Sequential([Conv2d('conv1',8),Conv2d('conv2',2),Linear('lin')],'block2')fastdotcan create all node properties directly from objects; you just have to define functions describing how to map the object's attributes to graph properties. These mappings go in thenode_defaultsandcluster_defaultsdictionaries (although by default labels are set usingstr(), so we don't need any special cluster defaults in this case):node_defaults['fillcolor']=lambdao:'greenyellow'ifisinstance(o,Linear)else'pink'cluster_defaults['label']=node_defaults['label']=attrgetter('name')node_defaults['tooltip']=strWith that in place, we can directly create nodes from our objects, for instance using the convenientseq_clusterfunction:c1=seq_cluster(block1.layers,block1)c2=seq_cluster(block2.layers,block2)e1,e2=c1.connect(c2),c1.connect(c2.last())graph_items(c1,c2,e1,e2)Note that in this example we didn't even need to create theDotobject separately -graph_itemscreates it directly from the graph items provided.Using object graphsIn the above example, we defined our edges directly betweenfastdotobjects. In practice, however, you'll most likely have your edges defined directly between python objects, for instance like this:conns=((block1,block2),(block1,block2.layers[-1]),)In this case, you'll want some way to connect your python objects to thefastdotgraph items that represent them. A mapping is stored automatically byfastdot, and is made available through theobject2graphfunction:g=graph_items(seq_cluster(block1.layers,block1),seq_cluster(block2.layers,block2))object2graph(block1.layers[-1])<pydot.Node at 0x7f013180c310>You can use this to graph your connections without needing access to the graph items:g.add_items(*[object2graph(a).connect(object2graph(b))fora,binconns])gThere's a helper function,object_connections, which creates these connections for you. So the above can be simplified to:g=graph_items(seq_cluster(block1.layers,block1),seq_cluster(block2.layers,block2))g.add_items(*object_connections(conns))g