paulbrodersen / netgraph

Publication-quality network visualisations in python
GNU General Public License v3.0
667 stars 40 forks source link

Combining interactive graphs with networkx subgraph functions? #80

Closed fdouglis closed 9 months ago

fdouglis commented 9 months ago

Hi, netgraph looks really nice. I just did something where I built up a networkX visual graph in pieces, by for instance specifying that certain nodes should have various shapes. I'm trying to understand how to do the same with an interactive graph so I can take everything I did and still drag things around. It would be nice if basically everyplace I have networkx.drawSOMETHING I could have netgraph.drawSOMETHING and have it understand the same commands, arguments, and data structures.

Does such a thing exist?

paulbrodersen commented 9 months ago

No, Netgraph is not emulating the NetworkX API in that regard (i.e. splitting the drawing into draw_nodes / draw_edges / draw_labels / draw_edge_labels), and this is a very deliberate design decision. The main reason is that the various elements of a good layout (node placement / edge routing / label placement) are highly interdependent: the edge routing algorithms need to know the node locations and shapes; the label placement needs to know both, node parameters and edge paths, to determine the locations with the most available whitespace to place the labels. NetworkX' drawing lacks all of the finer optimisations, and hence can allow its users to draw whatever whenever.

All that being said, other than the convenience of not having to learn a different API, what motivates this issue? Is there a specific problem that is preventing you from specifying the full layout right off the bat?

fdouglis commented 9 months ago

I had the impression that some of the options aren't specifyable as arrays but instead apply to the whole operation. so with networkX, if I want to draw a graph consisting of circles and squares for nodes, I thought I needed to call once for circles, and once for squares. Then I wanted to do the same but where I'd have an interactive canvas I could drag around, and saw no way to do it with netgraph.

If I can specify them both at once, great, I must have misread something in the documentation....

paulbrodersen commented 9 months ago

In Netgraph pretty much all arguments can be dictionaries. So in your case, you can provide a dictionary that maps node IDs to node shapes.

Figure_1

import matplotlib.pyplot as plt
import networkx as nx

from netgraph import InteractiveGraph

balanced_tree = nx.balanced_tree(3, 3)
node_shape = {node : "o" if node < 4 else "h" if (4 <= node <= 12) else "s" for node in balanced_tree}
instance = InteractiveGraph(balanced_tree, node_layout="radial", node_labels=True, node_shape=node_shape)
plt.show()

You can find more information on the various ways to style plot elements here

Does this solve your problem?

fdouglis commented 9 months ago

Thanks, that is indeed very useful. I made some progress using that, but hit an odd error when trying to pass a dict for node_label_fontdict ... I wanted to be able to specify a different node label color for different nodes, but it tried to treat the name of the node as an attribute and threw an exception . Do I correctly interpret that the per-node dict parameter doesn't apply to this one?

paulbrodersen commented 9 months ago

Yes, you are correct, this is one of the exceptions to "most parameters can also be dictionaries". However, you can still style node labels after the initial draw, as described here:

Figure_1

import matplotlib.pyplot as plt
from netgraph import Graph

fig, ax = plt.subplots()
g = Graph([(0, 1), (1, 2), (2, 0)], node_labels=True, edge_labels=True, ax=ax)
g.node_label_artists[0].set_color('blue')
g.node_label_artists[1].set_color('hotpink')
g.node_label_artists[2].set_color('green')
plt.show()
fdouglis commented 9 months ago

Thanks again! I had been looking just at the options to InteractiveGraph and didn't realize these were explained in the other section. I appreciate the pointer.