P2CPs Using Other Visualization Libraries

This notebook first demonstrates how to visualize P2CPs with bokeh, plotly, and holoviews through formal hiveplotlib support.

We then show how to create a baseline JSON output from a toy P2CP instance.

Finally, we demonstrate how to visualize this toy P2CP in pure matplotlib. Although hiveplotlib of course supports matplotlib visualizations formally, this last example is meant to demonstrate wrangling the JSON data product to help users who need to translate the JSON output to an unsupported Python visualization library.

[1]:
import matplotlib.pyplot as plt
from hiveplotlib.datasets import example_p2cp
from hiveplotlib.utils import cartesian2polar, polar2cartesian
from hiveplotlib.viz import p2cp_legend, p2cp_viz

Matplotlib Baseline

We will use the following example throughout this notebook, which is a simplified version of the example covered in the Introduction to P2CPs notebook.

[2]:
p2cp = example_p2cp(num_points=2)

fig, ax = p2cp_viz(p2cp, node_kwargs={"s": 20, "alpha": 1}, alpha=1, lw=4)
p2cp_legend(p2cp, fig=fig, ax=ax, line_kwargs={"lw": 4})

ax.set_title("Hiveplotlib Starting Point", fontsize=20)
plt.show()
_images/p2cp_viz_outside_matplotlib_3_0.png

bokeh

hiveplotlib supports visualizations with a bokeh backend, mirroring the same function calls as the matplotlib-based functions. One need only switch the import from hiveplotlib.viz to hiveplotlib.viz.bokeh.

Note: the hiveplotlib.viz.bokeh module requires that hiveplotlib be installed with the bokeh package, which can be done by running:

$ pip install hiveplotlib[bokeh]
[3]:
from bokeh.io import output_notebook
from bokeh.plotting import show
from hiveplotlib.viz.bokeh import p2cp_legend, p2cp_viz

output_notebook()
Loading BokehJS ...
[4]:
fig = p2cp_viz(
    p2cp,
    axes_kwargs={"line_alpha": 1, "line_width": 2},
    axes_labels_fontsize="16px",
    line_width=3,
    line_alpha=1,
)
fig.title = "Hiveplotlib (Bokeh Backend)"
fig.title.text_font_size = "30px"
p2cp_legend(p2cp, fig=fig)
show(fig)

plotly

hiveplotlib supports visualizations with a plotly backend, mirroring the same function calls as the matplotlib-based functions. One need only switch the import from hiveplotlib.viz to hiveplotlib.viz.plotly.

Note: the hiveplotlib.viz.plotly module requires that hiveplotlib be installed with the plotly package, which can be done by running:

$ pip install hiveplotlib[plotly]
[5]:
import plotly.io
from hiveplotlib.viz.plotly import p2cp_legend, p2cp_viz

plotly.io.renderers.default = "plotly_mimetype+notebook"
[6]:
fig = p2cp_viz(p2cp, opacity=1, line_width=3)
p2cp_legend(p2cp, fig=fig)
fig.update_layout(title={"text": "Hiveplotlib (Plotly Backend)", "font": {"size": 30}})
fig

holoviews

hiveplotlib supports visualizations with a holoviews backend, mirroring the same function calls as the matplotlib-based functions. One need only switch the import from hiveplotlib.viz to hiveplotlib.viz.holoviews.

Note: the hiveplotlib.viz.holoviews module requires that hiveplotlib be installed with the holoviews package, which can be done by running:

$ pip install hiveplotlib[holoviews]

hiveplotlib supports holoviews plotting using both bokeh and matplotlib. We need only set the holoviews extension (and set any user-specified keyword arguments) accordingly, as demonstrated below.

Holoviews (Bokeh Backend)

[7]:
import holoviews as hv
from hiveplotlib.viz.holoviews import p2cp_legend, p2cp_viz

hv.extension("bokeh")
[8]:
fig = p2cp_viz(
    p2cp,
    node_kwargs={"size": 7},
    axes_kwargs={"line_alpha": 1, "line_width": 2},
    line_width=3,
    line_alpha=1,
)
fig = p2cp_legend(fig=fig)
fig = fig.opts(
    title="Hiveplotlib (Holoviews-Bokeh Backend)",
    fontsize={"title": 20},
)
fig
[8]:

Holoviews (Matplotlib Backend)

[9]:
import holoviews as hv

hv.extension("matplotlib")
[10]:
fig = p2cp_viz(
    p2cp,
    node_kwargs={"s": 50},
    axes_kwargs={"alpha": 1, "linewidth": 2},
    linewidth=2,
    alpha=1,
)
fig = p2cp_legend(fig=fig)
fig = fig.opts(
    title="Hiveplotlib (Holoviews-Matplotlib Backend)",
    fontsize={"title": 30},
)
fig
[10]:

Exporting P2CPs as JSON

To accommodate any library / language one might choose for the final visualization of their P2CP, we support exporting the relevant data as JSON via the hiveplotlib.P2CP.to_json() method.

To pull out the information of this P2CP instance, we simply need to call the P2CP.to_json() method.

[11]:
p2cp_json = p2cp.to_json()

What’s in the Output JSON

P2CP.to_json() creates the following JSON structure:

{
  "axes": {
    "<axis_id_0>": {
      "start": [axis_id_0_start_x (float), axis_id_0_start_y (float)],
      "end": [axis_id_0_end_x (float), axis_id_0_end_y (float)],
      "points: {
        "unique_id": [list, of, unique, point, ids],
        "x": [list, of, corresponding, point, x, values],
        "y": [list, of, corresponding, point, y, values]
      }
    },
       .
       .
       .
    "<axis_id_n>": {
      ...
    }
  }
  "edges": {
    "tag": {
      "ids": [point_0_id, ... , point_k_id],
      "edge_kwargs": {<matplotlib edge kwargs>},
      "curves": {
        "from_axis_id": {
          "to_axis_id": [[edge_0_x_0, edge_0_y_0], ... , [edge_0_x_j, edge_0_y_j],
                        [null, null],
                        [edge_1_x_0, edge_1_y_0], ... , [edge_1_x_j, edge_1_y_j],
                        [null, null],
                             .
                             .
                             .
                        [edge_k_x_0, edge_k_y_0], ... , [edge_k_x_j, edge_k_y_j]]
        }
      }
    }
  }
}

The highest-level separation is the top-level keys "axes" and "edges".

Axis and Point Information

"axes" contains the start and end points of each axis in Cartesian space, as well as the IDs and corresponding Cartesian coordinates of each point on a given axis.

For example, the information about axis "A" is under ["axes"]["A"], and the information for the points on axis "A" is under:

["axes"]["A"]["points"].

Edge Information

"edges" contains the discretized curve information for plotting edges.

Organized by nested dictionaries, the edges are split up first by which tag of points they correspond to, that is, the edges of points whose IDs are under:

["edges"][<tag>]["ids"]

are all stored within ["edges"][<tag>]["curves"].

Amongst that subset of curves, all the edges that go from axis "A" to axis "B" are stored under:

["edges"][<tag>]["curves"]["A"]["B"].

Note: all those curves going from one axis to another axis with the same tag are stored together in a single data structure. The breakpoint between each of these curves in JSON is a [null, null] value. (Recasting these null values to np.nan in Python allows for much faster, single-layer plotting in matplotlib, plotly, and holoviews.)

Any corresponding matplotlib kwargs generated along the way for a given tag of data are preserved under:

["edges"][<tag>]["edge_kwargs"].

Setting Edge Keyword Arguments to Generalize

Not every keyword argument setting with the various backends (e.g. matplotlib, bokeh) will result in the same output in every plotting tool.

Thus, if you have a particular plotting tool in mind:

Set your keyword arguments in the context of your plotting tool.

This will be particularly important for keyword arguments like size of nodes and thickness of lines.

For colors specifically, be sure to set your colors in a standardized way. Web colors should be preferred here. Conversion to hex codes and RGBA values are easy with the matplotlib.colors.to_hex() and matplotlib.colors.to_rgba() functions.

Replicating our Matplotlib Viz from JSON

Once we read the JSON back into Python as a dict, we can manually plot in matplotlib without touching the hiveplotlib.viz module.

[12]:
import json

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection

%matplotlib inline
[13]:
# read in JSON output that we exported from `P2CP` instance above
p2cp_dict = json.loads(p2cp_json)
[14]:
fig, ax = plt.subplots(figsize=(6, 6))

for axis in p2cp_dict["axes"]:
    # plot axis
    axis_start = p2cp_dict["axes"][axis]["start"]
    axis_end = p2cp_dict["axes"][axis]["end"]
    axis_range = np.row_stack([axis_start, axis_end])
    ax.plot(axis_range[:, 0], axis_range[:, 1], color="black")

    # plot points
    points = p2cp_dict["axes"][axis]["points"]
    ax.scatter(points["x"], points["y"], color="black", zorder=2)

    # add axis labels
    #  grab each endpoint, and move out in polar space to space away from axes
    rho, phi = cartesian2polar(x=axis_end[0], y=axis_end[1])
    new_x, new_y = polar2cartesian(rho=rho + 0.5, phi=phi)
    ax.text(s=axis, x=new_x, y=new_y, size=14)

# plot edges
for tag in p2cp_dict["edges"]:
    # only plot if there are curves generated
    if "curves" in p2cp_dict["edges"][tag]:
        for edge_from in p2cp_dict["edges"][tag]["curves"]:
            for edge_to in p2cp_dict["edges"][tag]["curves"][edge_from]:
                curves_to_plot = np.array(
                    p2cp_dict["edges"][tag]["curves"][edge_from][edge_to]
                ).astype(float)
                color = p2cp_dict["edges"][tag]["edge_kwargs"]["color"]
                # if there's no actual edges there, don't plot
                if curves_to_plot.size > 0:
                    split_arrays = np.split(
                        curves_to_plot, np.where(np.isnan(curves_to_plot[:, 0]))[0]
                    )
                    for_stacking = [
                        i[np.where(~np.isnan(i[:, 0]))[0], :]
                        for i in split_arrays
                        if i.shape[0] > 1
                    ]
                    stacked = np.stack(for_stacking, axis=0)
                    collection = LineCollection(stacked, lw=3, zorder=1, color=color)
                    ax.add_collection(collection)


ax.set_title(
    "P2CP Visualization from JSON\nUsing Matplotlib", x=0, y=1.2, size=20, ha="left"
)
ax.axis("off")
ax.axis("equal")
plt.show()
_images/p2cp_viz_outside_matplotlib_26_0.png