Skip to content
Advertisement

Fill color in single cells in a networkx graph

I’ve build a graph with networkx, that looks like this: Graph

I want to fill every singel cell with a specified color. The Graph was drawn by nx.draw_networkx_edges() (returns a LineCollection). I found a similar question here (Fill area between lines), but the solution in the comments, doesn’t worked for me.

I’ve also used plt.fill_between with a simpler graph and manually set the values:

plt.fill_between([1, 2], [2, 2], color='yellow')
plt.fill_between([1.75, 2, 3], [1.25, 2, 2], color='purple')
plt.fill_between([0, 1, 1.25], [2, 2, 1.25], color='red')
plt.fill_between([0.75, 1.25, 1.75, 2.25], [0.75, 1.25, 1.25, 0.75], color='blue')
plt.fill_between([2, 2.25, 3], [0, 0.75, 1], color='pink')
plt.fill_between([0, 0.75, 1], [1, 0.75, 0], color='green')

And it turns out pretty good (result), but the problem with that is, that the filling depends on the order when the cells get filled and that would make the algorithm for it way to complicated, I guess.

Does anyone knows a better and simpler solution?

Edit: I tried to convert the Graph into a Voronoi-Diagram to try the solution of @JohanC, but the runtime is pretty long and the solution for larger graphs isn’t exact. For the calculation of the centroids I used this Center of Polygon

def find_Centroid(v):
    sum_A = 0
    sum_x = 0
    sum_y = 0
    for i in range(len(v)):
        next = i+1 if i != len(v)-1 else 0
        sum_A += v[i][0]*v[next][1] - v[next][0]*v[i][1]
        sum_x += (v[i][0] + v[next][0]) * (v[i][0]*v[next][1] - v[next][0]*v[i][1])
        sum_y += (v[i][1] + v[next][1]) * (v[i][0]*v[next][1] - v[next][0]*v[i][1])
    A = 1/2 * sum_A
    Cx = 1/(6*A) * sum_x
    Cy = 1/(6*A) * sum_y

    return Cx, Cy

# Get all cells of Graph (I think that takes most of the time)
cycle = nx.minimum_cycle_basis(SVG)

centroids = list()

# calculate all centroids of the cells
for c in cycle:
    subG = SVG.subgraph(c)
    sortedCycle = sortGraphNodes(subG)
    centroid = find_Centroid(sortedCycle)
    SVG.add_node((centroid[0], centroid[1]))
    centroids.append(centroid)

vor = Voronoi(centroids)
voronoi_plot_2d(vor)
plt.show()

Result small graph Result large graph

Advertisement

Answer

Using the first code block from the question that shows filling the simpler graph, I constructed an example network. The edges are listed below:

edges = [((1, 2), (2, 2)),
         ((1, 2), (0, 2)),
         ((1, 2), (1.25, 1.25)),
         ((2, 2), (1.75, 1.25)),
         ((2, 2), (3, 2)),
         ((1.75, 1.25), (1.25, 1.25)),
         ((1.75, 1.25), (2.25, 0.75)),
         ((3, 2), (3, 1)),
         ((0, 2), (0, 1)),
         ((1.25, 1.25), (0.75, 0.75)),
         ((0.75, 0.75), (0, 1)),
         ((0.75, 0.75), (1, 0)),
         ((2.25, 0.75), (2, 0)),
         ((2.25, 0.75), (3, 1)),
         ((2, 0), (1, 0)),
         ((2, 0), (3, 0)),
         ((3, 1), (3, 0)),
         ((0, 1), (0, 0)),
         ((1, 0), (0, 0))]

With this network, we do not need to use any Voronoi diagram (although very pleasing to they eye) to fill the cells of the network.

The basis for the solution is to use the minimum cycle basis iterator for the network, and then correct each cycle for following actual edges in the network (see documentation for minimum cycle basis “nodes are not necessarily returned in a order by which they appear in the cycle”).

The solution becomes the following, assuming edges from above:

import matplotlib.pyplot as plt
import networkx as nx

G = nx.Graph()
for edge in edges:
    G.add_edge(edge[0], edge[1])

pos = {x: x for x in G.nodes}
options = {
    "node_size": 10,
    "node_color": "lime",
    "edgecolors": "black",
    "linewidths": 1,
    "width": 1,
    "with_labels": False,
}
nx.draw_networkx(G, pos, **options)

# Fill all cells of graph
for cycle in nx.minimum_cycle_basis(G):
    full_cycle = cycle.copy()
    cycle_path = [full_cycle.pop(0)]
    while len(cycle_path) < len(cycle):
        for nb in G.neighbors(cycle_path[-1]):
            if nb in full_cycle:
                idx = full_cycle.index(nb)
                cycle_path.append(full_cycle.pop(idx))
                break
    plt.fill(*zip(*cycle_path))
plt.show()

The resulting graph looks like this: Simpler graph with filled cells

This algorithm scales better than the Voronoi / centroid approach listed in the edit to the question, but suffers from the same inefficiencies for large networks (O(m^2n), according to the reference in the documentation for minimum cycle basis).

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement