Source code for gtda.plotting.persistence_diagrams

"""Persistent-homology–related plotting functions and classes."""
# License: GNU AGPLv3

import numpy as np
import plotly.graph_objs as gobj


[docs]def plot_diagram(diagram, homology_dimensions=None, plotly_params=None): """Plot a single persistence diagram. Parameters ---------- diagram : ndarray of shape (n_points, 3) The persistence diagram to plot, where the third dimension along axis 1 contains homology dimensions, and the first two contain (birth, death) pairs to be used as coordinates in the two-dimensional plot. homology_dimensions : list of int or None, optional, default: ``None`` Homology dimensions which will appear on the plot. If ``None``, all homology dimensions which appear in `diagram` will be plotted. plotly_params : dict or None, optional, default: ``None`` Custom parameters to configure the plotly figure. Allowed keys are ``"traces"`` and ``"layout"``, and the corresponding values should be dictionaries containing keyword arguments as would be fed to the :meth:`update_traces` and :meth:`update_layout` methods of :class:`plotly.graph_objects.Figure`. Returns ------- fig : :class:`plotly.graph_objects.Figure` object Figure representing the persistence diagram. """ # TODO: increase the marker size if homology_dimensions is None: homology_dimensions = np.unique(diagram[:, 2]) diagram = diagram[diagram[:, 0] != diagram[:, 1]] diagram_no_dims = diagram[:, :2] posinfinite_mask = np.isposinf(diagram_no_dims) neginfinite_mask = np.isneginf(diagram_no_dims) max_val = np.max(np.where(posinfinite_mask, -np.inf, diagram_no_dims)) min_val = np.min(np.where(neginfinite_mask, np.inf, diagram_no_dims)) parameter_range = max_val - min_val extra_space_factor = 0.02 has_posinfinite_death = np.any(posinfinite_mask[:, 1]) if has_posinfinite_death: posinfinity_val = max_val + 0.1 * parameter_range extra_space_factor += 0.1 extra_space = extra_space_factor * parameter_range min_val_display = min_val - extra_space max_val_display = max_val + extra_space fig = gobj.Figure() fig.add_trace(gobj.Scatter( x=[min_val_display, max_val_display], y=[min_val_display, max_val_display], mode="lines", line={"dash": "dash", "width": 1, "color": "black"}, showlegend=False, hoverinfo="none" )) for dim in homology_dimensions: name = f"H{int(dim)}" if dim != np.inf else "Any homology dimension" subdiagram = diagram[diagram[:, 2] == dim] unique, inverse, counts = np.unique( subdiagram, axis=0, return_inverse=True, return_counts=True ) hovertext = [ f"{tuple(unique[unique_row_index][:2])}" + ( f", multiplicity: {counts[unique_row_index]}" if counts[unique_row_index] > 1 else "" ) for unique_row_index in inverse ] y = subdiagram[:, 1] if has_posinfinite_death: y[np.isposinf(y)] = posinfinity_val fig.add_trace(gobj.Scatter( x=subdiagram[:, 0], y=y, mode="markers", hoverinfo="text", hovertext=hovertext, name=name )) fig.update_layout( width=500, height=500, xaxis1={ "title": "Birth", "side": "bottom", "type": "linear", "range": [min_val_display, max_val_display], "autorange": False, "ticks": "outside", "showline": True, "zeroline": True, "linewidth": 1, "linecolor": "black", "mirror": False, "showexponent": "all", "exponentformat": "e" }, yaxis1={ "title": "Death", "side": "left", "type": "linear", "range": [min_val_display, max_val_display], "autorange": False, "scaleanchor": "x", "scaleratio": 1, "ticks": "outside", "showline": True, "zeroline": True, "linewidth": 1, "linecolor": "black", "mirror": False, "showexponent": "all", "exponentformat": "e" }, plot_bgcolor="white" ) # Add a horizontal dashed line for points with infinite death if has_posinfinite_death: fig.add_trace(gobj.Scatter( x=[min_val_display, max_val_display], y=[posinfinity_val, posinfinity_val], mode="lines", line={"dash": "dash", "width": 0.5, "color": "black"}, showlegend=True, name=u"\u221E", hoverinfo="none" )) # Update traces and layout according to user input if plotly_params: fig.update_traces(plotly_params.get("traces", None)) fig.update_layout(plotly_params.get("layout", None)) return fig