"""Output functions."""
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
tableau = {
-1: (1.00, 1.00, 1.00), 0: (0.12, 0.46, 0.70), 1: (0.68, 0.78, 0.91),
2: (1.00, 0.50, 0.05), 3: (1.00, 0.73, 0.47), 4: (0.17, 0.63, 0.17),
5: (0.59, 0.87, 0.54), 6: (0.84, 0.15, 0.16), 7: (1.00, 0.59, 0.59),
8: (0.58, 0.40, 0.74), 9: (0.77, 0.69, 0.83), 10: (0.55, 0.34, 0.29),
11: (0.77, 0.61, 0.58), 12: (0.89, 0.46, 0.76), 13: (0.96, 0.71, 0.82),
14: (0.50, 0.50, 0.50), 15: (0.78, 0.78, 0.78), 16: (0.73, 0.74, 0.13),
17: (0.86, 0.86, 0.55), 18: (0.09, 0.74, 0.81), 19: (0.62, 0.85, 0.89)
}
colorful = {
-1: (1.00, 1.00, 1.00), 0: (1.00, 0.00, 0.00), 1: (0.00, 1.00, 0.00),
2: (0.00, 0.00, 1.00), 3: (1.00, 1.00, 0.00), 4: (1.00, 0.00, 1.00),
5: (0.00, 1.00, 1.00), 6: (0.00, 0.00, 0.00), 7: (0.50, 0.00, 0.00),
8: (0.00, 0.50, 0.00), 9: (0.00, 0.00, 0.50), 10: (0.50, 0.50, 0.00),
11: (0.50, 0.00, 0.50), 12: (0.00, 0.50, 0.50), 13: (0.50, 0.50, 0.50),
14: (0.75, 0.00, 0.00), 15: (0.00, 0.75, 0.00), 16: (0.00, 0.00, 0.75),
17: (0.75, 0.75, 0.00), 18: (0.75, 0.00, 0.75), 19: (0.00, 0.75, 0.75),
20: (0.75, 0.75, 0.75), 21: (0.25, 0.00, 0.00), 22: (0.00, 0.25, 0.00),
23: (0.00, 0.00, 0.25), 24: (0.25, 0.25, 0.00), 25: (0.25, 0.00, 0.25),
26: (0.00, 0.25, 0.25), 27: (0.25, 0.25, 0.25), 28: (0.13, 0.00, 0.00),
29: (0.00, 0.13, 0.00), 30: (0.00, 0.00, 0.13), 31: (0.13, 0.13, 0.00),
32: (0.13, 0.00, 0.13), 33: (0.00, 0.13, 0.13), 34: (0.13, 0.13, 0.13),
35: (0.38, 0.00, 0.00), 36: (0.00, 0.38, 0.00), 37: (0.00, 0.00, 0.38),
38: (0.38, 0.38, 0.00), 39: (0.38, 0.00, 0.38), 40: (0.00, 0.38, 0.38),
41: (0.38, 0.38, 0.38), 42: (0.63, 0.00, 0.00), 43: (0.00, 0.63, 0.00),
44: (0.00, 0.00, 0.63), 45: (0.63, 0.63, 0.00), 46: (0.63, 0.00, 0.63),
47: (0.00, 0.63, 0.63), 48: (0.63, 0.63, 0.63), 49: (0.88, 0.00, 0.00),
50: (0.00, 0.88, 0.00), 51: (0.00, 0.00, 0.88), 52: (0.88, 0.88, 0.00),
53: (0.88, 0.00, 0.88), 54: (0.00, 0.88, 0.88), 55: (0.88, 0.88, 0.88)
}
colorblind = {
-1: (1.00, 1.00, 1.00), 0: (0.00, 0.42, 0.64), 1: (1.00, 0.50, 0.05),
2: (0.67, 0.67, 0.67), 3: (0.35, 0.35, 0.35), 4: (0.37, 0.62, 0.82),
5: (0.78, 0.32, 0.00), 6: (0.54, 0.54, 0.54), 7: (0.64, 0.78, 0.93),
8: (1.00, 0.74, 0.47), 9: (0.81, 0.81, 0.81)
}
def _all_numeric(L):
# Returns True iff all items in the list are numeric values
return all(isinstance(x, (int, float)) for x in L)
[docs]
def partition(c):
r"""Convert a coloring into its equivalent partition-based representation.
Negative color labels (signifying uncolored nodes/edges) are ignored.
Parameters
----------
c : dict
A dictionary with keys representing nodes or edges and values
representing their colors. Colors are identified by the integers
$0,1,2,\ldots$.
Returns
-------
list
A list in which each element is a list containing the nodes/edges
assigned to a particular color.
Examples
--------
>>> import networkx as nx
>>> import gcol
>>>
>>> G = nx.dodecahedral_graph()
>>> c = gcol.node_coloring(G)
>>> print(gcol.partition(c))
[[0, 2, 8, 18, 4, 13, 15], ..., [3, 9, 11, 7, 5, 16]]
>>>
>>> c = gcol.edge_coloring(G)
>>> print(gcol.partition(c))
[[(11, 12), (18, 19), (16, 17), ..., (2, 6), (4, 5)]]
Notes
-----
If all nodes in a color class are named by numerical values, the nodes are
sorted in ascending order. Otherwise, the nodes of each color class are
sorted by their string equivalents.
"""
if len(c) == 0:
return []
k = max(c.values()) + 1
S = [[] for i in range(k)]
for v in c:
if c[v] >= 0:
S[c[v]].append(v)
for i in range(k):
if _all_numeric(S[i]):
S[i].sort()
else:
try:
S[i] = sorted(S[i], key=str)
except TypeError:
print("Error, list of nodes contains incompatible types.")
return S
[docs]
def coloring_layout(G, c):
r"""Arrange the nodes of the graph in a circle.
Nodes of the same color are put next to each other. This method is designed
to be used with the ``pos`` argument in the drawing functions of NetworkX
(see example below).
Parameters
----------
G : NetworkX graph
The graph we want to visualize.
c : dict
A dictionary with keys representing nodes and values representing
their colors. Colors are identified by the integers $0,1,2,\ldots$.
Nodes with negative values are ignored.
Returns
-------
pos : dict
A dictionary of positions keyed by node.
Examples
--------
>>> import networkx as nx
>>> import matplotlib.pyplot as plt
>>> import gcol
>>>
>>> G = nx.dodecahedral_graph()
>>> c = gcol.node_coloring(G)
>>> nx.draw_networkx(
... G, pos=gcol.coloring_layout(G, c),
... node_color=gcol.get_node_colors(G, c)
... )
>>> plt.show()
See Also
--------
get_node_colors
multipartite_layout
"""
GCopy = nx.Graph()
P = partition(c)
for i in range(len(P)):
for v in P[i]:
GCopy.add_node(v)
for u, v in G.edges():
GCopy.add_edge(u, v)
return nx.circular_layout(GCopy)
[docs]
def multipartite_layout(G, c):
r"""Arrange the nodes of the graph into columns.
Nodes of the same color are put in the same column. This method is used
with the ``pos`` argument in the drawing functions of NetworkX (see
example below).
Parameters
----------
G : NetworkX graph
The graph we want to visualize.
c : dict
A dictionary with keys representing nodes and values representing
their colors. Colors are identified by the integers $0,1,2,\ldots$.
Nodes with negative color labels are ignored.
Returns
-------
pos : dict
A dictionary of positions keyed by node.
Examples
--------
>>> import networkx as nx
>>> import matplotlib.pyplot as plt
>>> import gcol
>>>
>>> G = nx.dodecahedral_graph()
>>> c = gcol.node_coloring(G)
>>> nx.draw_networkx(
... G, pos=gcol.multipartite_layout(G, c),
... node_color=gcol.get_node_colors(G, c)
... )
>>> plt.show()
See Also
--------
get_node_colors
coloring_layout
"""
GCopy = nx.Graph()
P = partition(c)
for i in range(len(P)):
for v in P[i]:
GCopy.add_node(v, layer=i)
for u, v in G.edges():
GCopy.add_edge(u, v)
return nx.multipartite_layout(GCopy, subset_key="layer")
[docs]
def get_node_colors(G, c, palette=None):
r"""Generate an RGB color for each node in the graph ``G``.
The RGB color of a node is determined by its color label in ``c`` and the
chosen palette. This method is designed to be used with the ``node_color``
argument in the drawing functions of NetworkX (see example below). If a
node is marked as uncolored (i.e., assigned a value of ``-1``, or is not
present in ``c``), it is painted white.
Parameters
----------
G : NetworkX graph
The graph we want to visualize.
c : dict
A dictionary with keys representing nodes and values representing their
colors. Colors are identified by the integers $0,1,2,\ldots$.
palette : None or dict, optional (default=None)
A dictionary that maps the integers $-1,0,1,\ldots$ to RGB values.
The in-built options are as follows
* ``gcol.tableau`` : A collection of 21 colors provided by Tableau.
* ``gcol.colorful`` : A collection of 57 bright colors that are
chosen to contrast each other as much as possible.
* ``gcol.colorblind`` : A collection of 11 colors, provided by
Tableau, that are intended to help colorblind users.
* If ``None``, then ``gcol.tableau`` is used.
Returns
-------
list
A sequence of RGB colors in node order.
Examples
--------
>>> import networkx as nx
>>> import matplotlib.pyplot as plt
>>> import gcol
>>>
>>> G = nx.dodecahedral_graph()
>>> c = gcol.node_coloring(G)
>>> nx.draw_networkx(
... G, pos=nx.spring_layout(G), node_color=gcol.get_node_colors(G, c)
... )
>>> plt.show()
Raises
------
ValueError
If ``c`` uses more colors than available in the palette.
Notes
-----
User generated palettes can also be passed into this method. In such cases
it is good practice to map the value ``-1`` to the color white.
Descriptions on how to specify valid colors can be found at [1]_.
See Also
--------
get_set_colors
get_edge_colors
References
----------
.. [1] Matplotlib: Specifying colors
<https://matplotlib.org/stable/users/explain/colors/colors.html>
"""
if palette is None:
palette = tableau
if len(c) == 0:
return [palette[-1] for v in G]
if max(c.values()) + 1 > len(palette) - 1:
raise ValueError(
"Error, insufficient colors are available in the chosen palette"
)
return [palette[c[v]] if v in c else palette[-1] for v in G]
[docs]
def get_edge_colors(G, c, palette=None):
r"""Generate an RGB color for each edge in the graph ``G``.
The RGB color of an edge is determined by its color label in ``c`` and the
chosen palette. This method is designed to be used with the ``edge_color``
argument in the drawing functions of NetworkX (see example below). If an
edge is marked as uncolored (i.e., assigned a value of ``-1`` , or not
present in ``c``), it is painted light grey.
Parameters
----------
G : NetworkX graph
The graph we want to visualize.
c : dict
A dictionary with keys representing edges and values representing
their colors. Colors are identified by the integers $0,1,2,\ldots$.
palette : None or dict, optional (default=None)
A dictionary that maps the integers $-1,0,1,\ldots$ to RGB values.
The in-built options are as follows
* ``gcol.tableau`` : A collection of 21 colors provided by Tableau.
* ``gcol.colorful`` : A collection of 57 bright colors that are
chosen to contrast each other as much as possible.
* ``gcol.colorblind`` : A collection of 11 colors, provided by
Tableau, that are intended to help colorblind users.
* If ``None``, then ``gcol.tableau`` is used.
Returns
-------
dict
list
A sequence of RGB colors in edge order.
Examples
--------
>>> import networkx as nx
>>> import matplotlib.pyplot as plt
>>> import gcol
>>>
>>> G = nx.dodecahedral_graph()
>>> c = gcol.edge_coloring(G)
>>> nx.draw_networkx(
... G, pos=nx.spring_layout(G), edge_color=gcol.get_edge_colors(G, c)
... )
>>> plt.show()
Raises
------
ValueError
If ``c`` uses more colors than available in the palette.
Notes
-----
User generated palettes can also be passed into this method. Descriptions
on how to specify valid colors can be found at [1]_.
See Also
--------
get_set_colors
get_node_colors
References
----------
.. [1] Matplotlib: Specifying colors
<https://matplotlib.org/stable/users/explain/colors/colors.html>
"""
if palette is None:
palette = tableau
if len(c) == 0:
return [palette[-1] for e in G.edges()]
if max(c.values()) + 1 > len(palette) - 1:
raise ValueError(
"Error, insufficient colors are available in the chosen palette"
)
return [
palette[c[e]] if e in c and c[e] >= 0
else (0.83, 0.83, 0.83)
for e in G.edges
]
[docs]
def get_set_colors(G, S, S_color="yellow", other_color="grey"):
r"""Generate an RGB color for each node based on if it is in ``S``.
By default, nodes in ``S`` are painted yellow and all others are painted
grey. This method is designed to be used with the ``node_color`` argument
in the drawing functions of NetworkX (see example below).
Parameters
----------
G : NetworkX graph
The graph we want to visualize.
S : list or set
A subset of ``G``'s nodes.
S_color : color, optional (default='yellow')
Desired color of the nodes in ``S``. Other options include ``'blue'``,
``'cyan'``, ``'green'``, ``'black'``, ``'magenta'``, ``'red'``,
``'white'``, and ``'yellow'``.
other_color : color, optional (default='grey')
Desired color of the nodes not in ``S``.
Returns
-------
list
A sequence of RGB colors, in node order
Examples
--------
>>> import networkx as nx
>>> import matplotlib.pyplot as plt
>>> import gcol
>>>
>>> G = nx.dodecahedral_graph()
>>> S = gcol.max_independent_set(G, it_limit=1000)
>>> nx.draw_networkx(
... G, pos=nx.spring_layout(G), node_color=gcol.get_set_colors(G, S)
... )
>>> plt.show()
See Also
--------
get_node_colors
get_edge_colors
Notes
-----
Descriptions on how to specify valid colors can be found at [1]_.
References
----------
.. [1] Matplotlib: Specifying colors
<https://matplotlib.org/stable/users/explain/colors/colors.html>
"""
X = set(S)
return [S_color if u in X else other_color for u in G]
[docs]
def draw_face_coloring(c, pos, external=False, palette=None):
r"""Draw the face coloring defined by ``c`` and ``pos``.
The RGB color of each face is determined by its color label in ``c`` and
the chosen palette. If a face is marked as uncolored (i.e., assigned a
value of ``-1``) it is painted white.
Parameters
----------
c : dict
A dictionary where keys represent the sequence of nodes occurring
in each face (polygon), and values represent the face's color.
Colors are identified by the integers $0,1,2,\ldots$. The first
element in ``c`` defines the external face of the embedding; the
remaining elements define the internal faces.
pos : dict
A dict specifying the (x,y) coordinates of each node in the embedding.
external : bool, optional (default=False)
If set to ``True``, the background (corresponding to the external face
of the embedding) is also colored, else it is left blank.
palette : None or dict, optional (default=None)
A dictionary that maps the integers $-1,0,1,\ldots$ to RGB values.
The in-built options are as follows
* ``gcol.tableau`` : A collection of 21 colors provided by Tableau.
* ``gcol.colorful`` : A collection of 57 bright colors that are
chosen to contrast each other as much as possible.
* ``gcol.colorblind`` : A collection of 11 colors, provided by
Tableau, that are intended to help colorblind users.
* If ``None``, then ``gcol.tableau`` is used.
Returns
-------
None
Examples
--------
>>> import networkx as nx
>>> import gcol
>>> import matplotlib.pyplot as plt
>>>
>>> # Construct a small planar graph with defined node positions
>>> G = nx.Graph()
>>> G.add_node(0, pos=(0,0))
>>> G.add_node(1, pos=(1,0))
>>> G.add_node(2, pos=(0,1))
>>> G.add_node(3, pos=(1,1))
>>> G.add_edges_from([(0,1),(0,2),(0,3),(1,3),(2,3)])
>>>
>>> # Color the faces of the embedding and draw to the screen
>>> pos = nx.get_node_attributes(G, "pos")
>>> c = face_coloring(G, pos)
>>> draw_face_coloring(c, pos)
>>> plt.show()
Raises
------
ValueError
If ``c`` uses more colors than available in the palette.
Notes
-----
User generated palettes can also be passed into this method. In such cases
it is good practice to map the value ``-1`` to the color white.
Descriptions on how to specify valid colors can be found at [1]_.
See Also
--------
get_set_colors
get_edge_colors
get_node_colors
References
----------
.. [1] Matplotlib: Specifying colors
<https://matplotlib.org/stable/users/explain/colors/colors.html>
"""
if palette is None:
palette = tableau
if len(c) == 0:
return []
if max(c.values()) + 1 > len(palette) - 1:
raise ValueError(
"Error, insufficient colors are available in the chosen palette"
)
fig, ax = plt.subplots()
ax.autoscale()
ax.tick_params(
axis="both",
which="both",
bottom=False,
left=False,
labelbottom=False,
labelleft=False,
)
faceList = list(c.keys())
if external:
ax.set_facecolor(palette[c[faceList[0]]])
for i in range(1, len(faceList)):
coords = [[pos[u][0], pos[u][1]] for u in faceList[i]]
p = Polygon(coords, facecolor=(palette[c[faceList[i]]]))
ax.add_patch(p)
# Alternative spellings of the above methods and globals
colouring_layout = coloring_layout
get_node_colours = get_node_colors
get_edge_colours = get_edge_colors
get_set_colours = get_set_colors
draw_face_colouring = draw_face_coloring
colourful = colorful
colourblind = colorblind