Hive Plots Using Other Visualization Libraries

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

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

Next, we use this JSON output to visualize the same toy example in JavaScript (specifically D3).

Finally, we demonstrate how to visualize this toy hive plot 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.

Matplotlib Baseline

We will use the following example throughout this notebook.

[1]:
import matplotlib.pyplot as plt
from hiveplotlib.datasets import example_hive_plot
from hiveplotlib.utils import cartesian2polar, polar2cartesian
from hiveplotlib.viz import hive_plot_viz
[2]:
hp = example_hive_plot()

fig, ax = hive_plot_viz(
    hp, figsize=(6, 6), axes_kwargs={"color": "black", "alpha": 1.0}, alpha=1
)
ax.set_title("Hiveplotlib Starting Point", y=1.2, fontsize=20)
plt.show()
_images/hive_plot_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 hive_plot_viz

output_notebook()
Loading BokehJS ...
[4]:
fig = hive_plot_viz(
    hp,
    node_kwargs={"size": 7},
    axes_kwargs={"line_alpha": 1, "line_width": 2},
    line_width=2,
    line_alpha=1,
)
fig.title = "Hiveplotlib (Bokeh Backend)"
fig.title.text_font_size = "30px"
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 hive_plot_viz

plotly.io.renderers.default = "plotly_mimetype+notebook"
[6]:
fig = hive_plot_viz(hp, opacity=1)
fig.update_layout(title="Hiveplotlib (Plotly Backend)", font={"size": 25})
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 hive_plot_viz

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

Holoviews (Matplotlib Backend)

[9]:
import holoviews as hv
from hiveplotlib.viz.holoviews import hive_plot_viz

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

Exporting Hive Plots as JSON

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

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

[11]:
hp_json = hp.to_json()

What’s in the Output JSON

Hiveplot.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)],
      "nodes: {
        "unique_id": [list, of, unique, node, ids],
        "x": [list, of, corresponding, node, x, values],
        "y": [list, of, corresponding, node, y, values]
      }
    },
       .
       .
       .
    "<axis_id_n>": {
      ...
    }
  }
  "edges": {
    "from_axis_id": {
      "to_axis_id": {
        "tag": {
          "ids": [
            [from_edge_0, to_edge_0], ... , [from_edge_k, to_edge_k]
          ],
          "curves": [
            [
              [edge_0_x_0, edge_0_y_0], ... , [edge_0_x_j, edge_0_y_j]
            ],
            [
              [edge_1_x_0, edge_1_y_0], ... , [edge_1_x_j, edge_1_y_j]
            ],
            .
            .
            .
            [
              [edge_k_x_0, edge_k_y_0], ... , [edge_k_x_j, edge_k_y_j]
            ]
          ],
          "edge_kwargs": {<edge kwargs>}
        }
      }
    }
  }
}

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

Axis and Node 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 node on a given axis.

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

["axes"]["A"]["nodes"].

Edge Information

"edges" contains the discretized curve information for plotting edges. Organized by nested dictionaries, the edges that go from axis "A" to axis "B" are stored under:

["edges"]["A"]["B"].

The underlying curve information will be nested one layer deeper under the “tag” of data being referenced. For more on tags, see the discussion on comparing subgroups in a single hive plot. Note, if you have not concerned yourself with tags up to this point, your tag will likely be "0".

All discretized curves that go from axis "A" to axis "B" are stored under:

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

where the \(n^{th}\) curve’s data is contained under:

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

One can also pull the corresponding node IDs from and to which each edge is being drawn under:

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

where, similarly, the the \(n^{th}\) pair of node IDs corresponding to the \(n^{th}\) curve is contained under:

["edges"]["A"]["B"][<tag>]["ids"][n].

Any corresponding edge kwargs generated along the way are preserved under:

["edges"]["A"]["B"][<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.

JavaScript (D3) Viz from JSON

Once we have a standardized format like JSON, we need not even restrict ourselves to Python.

We need only save the JSON string output to disk, then we can load the resulting .json file in any programming language.

The JSON output can be saved by running:

with open('my_hive_plot.json', 'w') as f:
    f.write(hp_json)

Below, we use code from the hiveplotlib-javascript repository to visualize the JSON output in JavaScript.

Note, below we plot the saved JSON file from the hiveplotlib-javascript repository, which is equivalent to our above example, but one could also pass the local file path (e.g. const filePath = "my_hive_plot.json") in the below JavaScript snippet if working locally.

[12]:
from IPython.display import HTML, display
[13]:
js_url = (
    "https://cdn.jsdelivr.net/gh/gjkoplik/hiveplotlib-javascript"
    "@latest/hive_plots_d3_viz.min.js"
)

hp_json_file_url = (
    "https://raw.githubusercontent.com/gjkoplik/"
    "hiveplotlib-javascript/main/example_hive_plot.json"
)

script = f"""
<div> <h1> Hive Plots in JavaScript (D3) </h1> </div>

<div id="my_js_hp"></div>

<script type="module">
    import visualizeHivePlot from "{js_url}";
    const filePath = "{hp_json_file_url}";
    visualizeHivePlot(filePath, [-5, 5], [-5, 5], 20, 0, 0, 0, 550, 550, my_js_hp);

</script>
"""

display(HTML(script))

Hive Plots in JavaScript (D3)

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.

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 a different Python visualization library.

[14]:
import json

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

%matplotlib inline
[15]:
# read in JSON output that we exported from `HivePlot` instance above
hp_dict = json.loads(hp_json)
[16]:
fig, ax = plt.subplots(figsize=(6, 6))

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

    # plot nodes
    nodes = hp_dict["axes"][axis]["nodes"]
    ax.scatter(nodes["x"], nodes["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 + 1.2, phi=phi)
    ax.text(s=axis, x=new_x, y=new_y, size=14)

# plot edges
for edge_from in hp_dict["edges"]:
    for edge_to in hp_dict["edges"][edge_from]:
        for tag in hp_dict["edges"][edge_from][edge_to]:
            # only plot if there are curves generated
            if "curves" in hp_dict["edges"][edge_from][edge_to][tag]:
                curves_to_plot = [
                    np.array(i).astype(float)
                    for i in hp_dict["edges"][edge_from][edge_to][tag]["curves"]
                ]
                color = hp_dict["edges"][edge_from][edge_to][tag]["edge_kwargs"][
                    "color"
                ]
                # if there's no actual edges there, don't plot
                if len(curves_to_plot) > 0:
                    collection = LineCollection(curves_to_plot, zorder=1, color=color)
                    ax.add_collection(collection)


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