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()
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()
[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()