# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: nxviz
#     language: python
#     name: nxviz
# ---

# # Panels and Faceting
#
# In this notebook, we will introduce how to make graph visualization panels!

# + tags=[]
# %config InlineBackend.figure_format = 'retina'
# %load_ext autoreload
# %autoreload 2

# + tags=[]
from random import choice

import matplotlib.pyplot as plt
import networkx as nx
import numpy as np

import nxviz as nv
# -

# ## Example graph
#
# Here's an example graph. It has both quantiative and qualitative data encoded on the nodes and edges.

# + tags=[]
categories = "abcdefghijk"
node_categories = "12345"

G = nx.erdos_renyi_graph(n=70, p=0.1)
for u, v in G.edges():
    G.edges[u, v]["group"] = choice(categories)
    G.edges[u, v]["edge_val"] = np.random.exponential()

for n in G.nodes():
    G.nodes[n]["category"] = choice(node_categories)
    G.nodes[n]["value"] = np.random.normal()
# -

# ## Non-panel'd version
#
# Let's see what happens if we just try to plot all nodes and all edges together.

# + tags=[]
nv.circos(
    G,
    group_by="category",
    node_color_by="category",
    edge_color_by="edge_val",
    edge_enc_kwargs={"alpha_scale": 5},
)
# -

# This isn't particularly useful.
# The edges are over-populated on the visualization.
# If there were structure in the graph that were interesting,
# we'd find it hard to elucidate.
# Here, we can rely on the principle of small multiples
# to design a more effective visualizations.

# ## `nxviz` faceting API
#
# Graph visualization panels are the solution here.
# We use categorical metadata on nodes or edges
# to facet our visualizations.
# (Faceting refers to creating subplots that contain a subset of the full dataset,
# so that one can optimize for visual clarity.)
#
# Because nodes are more easily arranged than edges,
# we can facet our graph out by edge categories into **panels**.
# As such, one subcategory of graph visualization panels
# is defined by a faceting of our graph by edge categories.
# Here are some examples.

# + tags=[]
from nxviz import annotate, facet

# from nxviz import hive_panel, arc_panel, circos_panel
from nxviz.utils import edge_table, node_table
# -

# ### Arc Panel

# + tags=[]
facet.arc_panel(
    G,
    edge_group_by="group",
    node_group_by="category",
    node_color_by="category",
    edge_color_by="edge_val",
)
# -

# ### Circos Panel

# + tags=[]
facet.circos_panel(
    G,
    edge_group_by="group",
    node_group_by="category",
    node_color_by="category",
    edge_color_by="edge_val",
)
# -

# ### Matrix Panel

facet.matrix_panel(
    G,
    edge_group_by="group",
    node_group_by="category",
    node_color_by="category",
    edge_color_by="edge_val",
)

# ### Hive Panel
#
# Hive panels are a special type of panel.
# Because hive plots alone can plot either 2 or 3 categories of nodes,
# it can't handle situations where there are more than 3 categories of nodes.
# Here is where a panel of hive plots comes in:
# each plot in the panel handles 3 of the categories that are present.
# As such, you will have ${K}\choose{3}$ plots to plot.
# For this reason, we don't recommend having more than 6 categories,
# otherwise you'll end up with a lot of plots to look at.
#

# + tags=[]
facet.hive_panel(G, node_group_by="category", node_color_by="category")
# -

# ## Custom panels
#
# While there are plot-specific faceting APIs,
# you can use the building blocks in there to compose your own.
#
# In this example, 
# we'll show you how to create a facet where we show
# only edges that are associated with a particular group of nodes.
# For this, the key function to use is `node_group_edges`,
# which yields graphs that contains edges attached to a particular group,
# as well as the group itself.

# + tags=[]
import inspect

from nxviz.facet import n_rows_cols, node_group_edges

print(inspect.getsource(node_group_edges))

# + tags=[]
node_group_by = "category"

graphs, groups = zip(*node_group_edges(G, node_group_by))
nrows, ncols = n_rows_cols(groups)
fig, axes = plt.subplots(figsize=(8, 8), nrows=3, ncols=3)
axes = list(axes.flatten())

for ax, G_sub, group in zip(axes, graphs, groups):
    plt.sca(ax)
    nv.circos(
        G_sub, group_by="category", sort_by="value", node_color_by="category"
    )
    annotate.circos_group(G_sub, group_by="category")
    ax.set_title(f"node group = {group}")


i = axes.index(ax)
for ax in axes[i + 1 :]:
    fig.delaxes(ax)

plt.tight_layout()
# -

# ## Development pattern
#
# The core of faceting in nxviz is to return an iterator of graphs
# that contain either
#
# 1. a subset of nodes,
# 2. a subset of edges,
# 3. or a subset of nodes and edges.
#
# Using one of the faceting functions as an example to illustrate:

# + tags=[]
import inspect

print(inspect.getsource(facet.edge_group))
# -

# The pattern is essentially to identify the exact groups that exist,
# iterate over these groups,
# and yield a graph that contains any one of the aforementioned three subsets
# alongside the group.
# That function pattern makes faceting consistent.

StarcoderdataJupyterScriptsDedupFiltered