xgi-org / xgi

CompleX Group Interactions (XGI) is a Python package for higher-order networks.
https://xgi.readthedocs.io
Other
172 stars 27 forks source link

Issues with specifying edge colors #478

Closed nwlandry closed 9 months ago

nwlandry commented 9 months ago

I am attempting to specify my own colors for each edge size in a hypergraph, but (a) it is turning out to be very cumbersome and (b) it doesn't work. Here is my minimal "working" example:

import xgi
links = [[1, 2], [1, 3], [5, 6], [1, 7]]
triangles = [[3, 5, 7], [2, 7, 1], [6, 10, 15]]
squares = [[7, 8, 9, 10]]
pentagons = [[1, 11, 12, 13, 14]]
edges = links + triangles + squares + pentagons

H = xgi.Hypergraph(edges)

link_color = "#000000"
triangle_color = "#648FFF"
square_color = "#785EF0"
pentagon_color = "#DC267F"

num_dyads = xgi.num_edges_order(H, 1)
dyad_colors = np.empty(num_dyads, dtype=str)
edge_colors = np.empty(H.num_edges - num_dyads, dtype=str)

idx_dyad = 0
idx_edge = 0

for s in enumerate(H.edges.size.asnumpy()):
    if s == 2:
        dyad_colors[idx_dyad] = link_color
        idx_dyad += 1
    elif s == 3:
        edge_colors[idx_edge] = triangle_color
        idx_edge += 1
    elif s == 4:
        edge_colors[idx_edge] = square_color
        idx_edge += 1
    elif s == 5:
        edge_colors[idx_edge] = pentagon_color
        idx_edge += 1

xgi.draw(H)

First, I get an error when I specify a color for all the edges + dyads because the drawing function is agnostic to the edge IDs vs. the dyad IDs and complains when the number of colors does not match the number of edges sans dyads. This seems somewhat clunky. Second, I get the error "ValueError: Invalud input format for colors." This is not an error when I set the edge_fc to be a single hex value.

maximelucas commented 9 months ago

I quickly tried this and I think your code for some reason only keeps the first letter of colors when storing them in say dyad_colors, is that possible? Then it's passed as input to the draw function which does not recognise "#" as a color. Let me know!

nwlandry commented 9 months ago

Mmmmm. you're right - that's kinda weird.

nwlandry commented 9 months ago

Fixed now, so I think that I'll close this issue.

maximelucas commented 9 months ago

For the record, here are three ways to do this. After defining the colours:

import xgi
links = [[1, 2], [1, 3], [5, 6], [1, 7]]
triangles = [[3, 5, 7], [2, 7, 1], [6, 10, 15]]
squares = [[7, 8, 9, 10]]
pentagons = [[1, 11, 12, 13, 14]]
edges = links + triangles + squares + pentagons

H = xgi.Hypergraph(edges)

link_color = "#000000"
triangle_color = "#648FFF"
square_color = "#785EF0"
pentagon_color = "#DC267F"
colors = [link_color, triangle_color, square_color, pentagon_color]

Option 1: input colors that are lists/arrays with the right number of elements in the right order

edge_color = [colors[i-2] for i in H.edges.filterby("order", 1, "gt").size.aslist()]

xgi.draw_hyperedges(H, dyad_color=link_color, edge_fc=edge_color)

Option 2: a dictionary where keys are hyperedge ids and values are colours:

color_dict = {idx : colors[i-2] for idx, i in H.edges.size.asdict().items()}

xgi.draw_hyperedges(H, dyad_color=color_dict, edge_fc=color_dict)

Option 3: just create a cmap that has the colours you want. This works because the edges are already plotted with colors corresponding to their size. This option also allows to plot a corresponding colorbar:

from matplotlib.colors import ListedColormap
cmap = ListedColormap(colors[1:])

xgi.draw_hyperedges(H, dyad_color=link_color, edge_fc_cmap=cmap)

This might be worth adding in a recipe.