import random from copy import deepcopy from typing import List from typing import Optional from typing import Union import easygraph as eg __all__ = [ "draw_SHS_center", "draw_SHS_center_kk", "draw_kamada_kawai", "draw_hypergraph", "draw_dynamic_hypergraph", "draw_easygraph_nodes", "draw_easygraph_edges", "draw_louvain_com", "draw_lpa_com", "draw_gm_com", "draw_ego_graph", ] from easygraph.functions.drawing.defaults import default_hypergraph_strength from easygraph.functions.drawing.defaults import default_hypergraph_style from easygraph.functions.drawing.defaults import default_size from easygraph.functions.drawing.layout import force_layout from easygraph.functions.drawing.utils import draw_circle_edge from easygraph.functions.drawing.utils import draw_vertex def draw_hypergraph( hg: "eg.Hypergraph", e_style: str = "circle", v_label: Optional[List[str]] = None, v_size: Union[float, list] = 1.0, v_color: Union[str, list] = "r", v_line_width: Union[str, list] = 1.0, e_color: Union[str, list] = "gray", e_fill_color: Union[str, list] = "whitesmoke", e_line_width: Union[str, list] = 1.0, font_size: float = 1.0, font_family: str = "sans-serif", push_v_strength: float = 1.0, push_e_strength: float = 1.0, pull_e_strength: float = 1.0, pull_center_strength: float = 1.0, ): r"""Draw the hypergraph structure. Args: ``hg`` (``eg.Hypergraph``): The EasyGraph's hypergraph object. ``e_style`` (``str``): The style of hyperedges. The available styles are only ``'circle'``. Defaults to ``'circle'``. ``v_label`` (``list``): The labels of vertices. Defaults to ``None``. ``v_size`` (``float`` or ``list``): The size of vertices. Defaults to ``1.0``. ``v_color`` (``str`` or ``list``): The `color `_ of vertices. Defaults to ``'r'``. ``v_line_width`` (``float`` or ``list``): The line width of vertices. Defaults to ``1.0``. ``e_color`` (``str`` or ``list``): The `color `_ of hyperedges. Defaults to ``'gray'``. ``e_fill_color`` (``str`` or ``list``): The fill `color `_ of hyperedges. Defaults to ``'whitesmoke'``. ``e_line_width`` (``float`` or ``list``): The line width of hyperedges. Defaults to ``1.0``. ``font_size`` (``float``): The font size of labels. Defaults to ``1.0``. ``font_family`` (``str``): The font family of labels. Defaults to ``'sans-serif'``. ``push_v_strength`` (``float``): The strength of pushing vertices. Defaults to ``1.0``. ``push_e_strength`` (``float``): The strength of pushing hyperedges. Defaults to ``1.0``. ``pull_e_strength`` (``float``): The strength of pulling hyperedges. Defaults to ``1.0``. ``pull_center_strength`` (``float``): The strength of pulling vertices to the center. Defaults to ``1.0``. """ import matplotlib.pyplot as plt assert isinstance( hg, eg.Hypergraph ), "The input object must be a EasyGraph's hypergraph object." assert e_style in ["circle"], "e_style must be 'circle'" assert hg.num_e > 0, "g must be a non-empty structure" fig, ax = plt.subplots(figsize=(6, 6)) num_v, e_list = hg.num_v, deepcopy(hg.e[0]) # default configures v_color, e_color, e_fill_color = default_hypergraph_style( hg.num_v, hg.num_e, v_color, e_color, e_fill_color ) v_size, v_line_width, e_line_width, font_size = default_size( num_v, e_list, v_size, v_line_width, e_line_width ) ( push_v_strength, push_e_strength, pull_e_strength, pull_center_strength, ) = default_hypergraph_strength( num_v, e_list, push_v_strength, push_e_strength, pull_e_strength, pull_center_strength, ) # layout v_coor = force_layout( num_v, e_list, push_v_strength, push_e_strength, pull_e_strength, pull_center_strength, ) if e_style == "circle": draw_circle_edge( ax, v_coor, v_size, e_list, e_color, e_fill_color, e_line_width, ) else: raise ValueError("e_style must be 'circle'") draw_vertex( ax, v_coor, v_label, font_size, font_family, v_size, v_color, e_color, v_line_width, ) plt.xlim((0, 1.0)) plt.ylim((0, 1.0)) plt.axis("off") fig.tight_layout() plt.show() def _draw_single_dynamic_hypergraph( hg: "eg.Hypergraph", ax, title_font_size=4, group_name: str = "main", e_style: str = "circle", v_label: Optional[List[str]] = None, v_size: Union[float, list] = 2.0, v_color: Union[str, list] = "r", v_line_width: Union[str, list] = 1.0, e_color: Union[str, list] = "gray", e_fill_color: Union[str, list] = "whitesmoke", e_line_width: Union[str, list] = 1.0, font_size: float = 1.0, font_family: str = "sans-serif", push_v_strength: float = 1.0, push_e_strength: float = 1.0, pull_e_strength: float = 1.0, pull_center_strength: float = 1.0, ): import matplotlib.pyplot as plt assert isinstance( hg, eg.Hypergraph ), "The input object must be a EasyGraph's hypergraph object." assert e_style in ["circle"], "e_style must be 'circle'" assert hg.num_e > 0, "g must be a non-empty structure" num_v, e_list = hg.num_v, deepcopy(hg.e_of_group(group_name)[0]) # default configures v_color, e_color, e_fill_color = default_hypergraph_style( hg.num_v, hg.num_e, v_color, e_color, e_fill_color ) v_size, v_line_width, e_line_width, font_size = default_size( num_v, e_list, v_size, v_line_width, e_line_width, font_size ) ( push_v_strength, push_e_strength, pull_e_strength, pull_center_strength, ) = default_hypergraph_strength( num_v, e_list, push_v_strength, push_e_strength, pull_e_strength, pull_center_strength, ) # layout v_coor = force_layout( num_v, e_list, push_v_strength, push_e_strength, pull_e_strength, pull_center_strength, ) if e_style == "circle": draw_circle_edge( ax, v_coor, v_size, e_list, e_color, e_fill_color, e_line_width, ) else: raise ValueError("e_style must be 'circle'") draw_vertex( ax, v_coor, v_label, font_size, font_family, v_size, v_color, v_color, v_line_width, ) plt.title(group_name, fontsize=title_font_size) plt.xlim((0, 1.0)) plt.ylim((0, 1.0)) plt.axis("off") def draw_dynamic_hypergraph( G, group_name_list=None, column_size=None, save_path=None, title_font_size=4, e_style: str = "circle", v_label: Optional[List[str]] = None, v_size: Union[float, list] = 2.0, v_color: Union[str, list] = "r", v_line_width: Union[str, list] = 1.0, e_color: Union[str, list] = "gray", e_fill_color: Union[str, list] = "whitesmoke", e_line_width: Union[str, list] = 1.0, font_size: float = 1.0, font_family: str = "sans-serif", push_v_strength: float = 1.0, push_e_strength: float = 1.0, pull_e_strength: float = 1.0, pull_center_strength: float = 1.0, ): """ Parameters ---------- G eg.Hypergraph group_name_list The groups to visualize column_size The number of subplots placed in each row save_path path to save visualization title_font_size The font size of tilte of each subplot """ import math import matplotlib.pyplot as plt # if group_name_list == None: # group_name_list = G.group_names COLUMN_SIZE = 3 if column_size == None else column_size ROW_SIZE = math.ceil(len(group_name_list) / COLUMN_SIZE) fig = plt.figure() sub = 1 for gn in group_name_list: if sub > len(group_name_list): break tmp_ax = fig.add_subplot(ROW_SIZE, COLUMN_SIZE, sub) _draw_single_dynamic_hypergraph( G, ax=tmp_ax, group_name=gn, title_font_size=title_font_size, e_style=e_style, v_label=v_label, v_size=v_size, v_color=v_color, v_line_width=v_line_width, e_color=e_color, e_fill_color=e_fill_color, e_line_width=e_line_width, font_size=font_size, font_family=font_family, push_v_strength=push_v_strength, push_e_strength=push_e_strength, pull_e_strength=pull_e_strength, pull_center_strength=pull_center_strength, ) sub += 1 fig.tight_layout() if save_path is not None: plt.savefig(save_path) plt.show() def draw_easygraph_nodes( G, pos, nodelist=None, node_size=300, node_color="#1f78b4", node_shape="o", alpha=None, cmap=None, vmin=None, vmax=None, ax=None, linewidths=None, edgecolors=None, label=None, margins=None, ): """Draw the nodes of the graph G. This draws only the nodes of the graph G. Parameters ---------- G : graph A EasyGraph graph pos : dictionary A dictionary with nodes as keys and positions as values. Positions should be sequences of length 2. ax : Matplotlib Axes object, optional Draw the graph in the specified Matplotlib axes. nodelist : list (default list(G)) Draw only specified nodes node_size : scalar or array (default=300) Size of nodes. If an array it must be the same length as nodelist. node_color : color or array of colors (default='#1f78b4') Node color. Can be a single color or a sequence of colors with the same length as nodelist. Color can be string or rgb (or rgba) tuple of floats from 0-1. If numeric values are specified they will be mapped to colors using the cmap and vmin,vmax parameters. See matplotlib.scatter for more details. node_shape : string (default='o') The shape of the node. Specification is as matplotlib.scatter marker, one of 'so^>v>> G = eg.dodecahedral_graph() >>> nodes = eg.draw_easygraph_nodes(G, pos=eg.spring_layout(G)) """ from collections.abc import Iterable import matplotlib as mpl import matplotlib.collections # call as mpl.collections import matplotlib.pyplot as plt import numpy as np if ax is None: ax = plt.gca() if nodelist is None: nodelist = list(G) if len(nodelist) == 0: # empty nodelist, no drawing return mpl.collections.PathCollection(None) try: xy = np.asarray([pos[v] for v in nodelist]) except KeyError as err: raise eg.EasygraphError(f"Node {err} has no position.") from err if isinstance(alpha, Iterable): node_color = apply_alpha(node_color, alpha, nodelist, cmap, vmin, vmax) alpha = None node_collection = ax.scatter( xy[:, 0], xy[:, 1], s=node_size, c=node_color, marker=node_shape, cmap=cmap, vmin=vmin, vmax=vmax, alpha=alpha, linewidths=linewidths, edgecolors=edgecolors, label=label, ) ax.tick_params( axis="both", which="both", bottom=False, left=False, labelbottom=False, labelleft=False, ) if margins is not None: if isinstance(margins, Iterable): ax.margins(*margins) else: ax.margins(margins) node_collection.set_zorder(2) return node_collection def draw_easygraph_edges( G, pos, edgelist=None, width=1.0, edge_color="k", style="solid", alpha=None, arrowstyle=None, arrowsize=10, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=None, label=None, node_size=300, nodelist=None, node_shape="o", connectionstyle="arc3", min_source_margin=0, min_target_margin=0, ): r"""Draw the edges of the graph G. This draws only the edges of the graph G. Parameters ---------- G : graph A easygraph graph pos : dictionary A dictionary with nodes as keys and positions as values. Positions should be sequences of length 2. edgelist : collection of edge tuples (default=G.edges()) Draw only specified edges width : float or array of floats (default=1.0) Line width of edges edge_color : color or array of colors (default='k') Edge color. Can be a single color or a sequence of colors with the same length as edgelist. Color can be string or rgb (or rgba) tuple of floats from 0-1. If numeric values are specified they will be mapped to colors using the edge_cmap and edge_vmin,edge_vmax parameters. style : string or array of strings (default='solid') Edge line style e.g.: '-', '--', '-.', ':' or words like 'solid' or 'dashed'. Can be a single style or a sequence of styles with the same length as the edge list. If less styles than edges are given the styles will cycle. If more styles than edges are given the styles will be used sequentially and not be exhausted. Also, `(offset, onoffseq)` tuples can be used as style instead of a strings. (See `matplotlib.patches.FancyArrowPatch`: `linestyle`) alpha : float or array of floats (default=None) The edge transparency. This can be a single alpha value, in which case it will be applied to all specified edges. Otherwise, if it is an array, the elements of alpha will be applied to the colors in order (cycling through alpha multiple times if necessary). edge_cmap : Matplotlib colormap, optional Colormap for mapping intensities of edges edge_vmin,edge_vmax : floats, optional Minimum and maximum for edge colormap scaling ax : Matplotlib Axes object, optional Draw the graph in the specified Matplotlib axes. arrows : bool or None, optional (default=None) If `None`, directed graphs draw arrowheads with `~matplotlib.patches.FancyArrowPatch`, while undirected graphs draw edges via `~matplotlib.collections.LineCollection` for speed. If `True`, draw arrowheads with FancyArrowPatches (bendable and stylish). If `False`, draw edges using LineCollection (linear and fast). Note: Arrowheads will be the same color as edges. arrowstyle : str (default='-\|>' for directed graphs) For directed graphs and `arrows==True` defaults to '-\|>', For undirected graphs default to '-'. See `matplotlib.patches.ArrowStyle` for more options. arrowsize : int (default=10) For directed graphs, choose the size of the arrow head's length and width. See `matplotlib.patches.FancyArrowPatch` for attribute `mutation_scale` for more info. connectionstyle : string (default="arc3") Pass the connectionstyle parameter to create curved arc of rounding radius rad. For example, connectionstyle='arc3,rad=0.2'. See `matplotlib.patches.ConnectionStyle` and `matplotlib.patches.FancyArrowPatch` for more info. node_size : scalar or array (default=300) Size of nodes. Though the nodes are not drawn with this function, the node size is used in determining edge positioning. nodelist : list, optional (default=G.nodes()) This provides the node order for the `node_size` array (if it is an array). node_shape : string (default='o') The marker used for nodes, used in determining edge positioning. Specification is as a `matplotlib.markers` marker, e.g. one of 'so^>vv