Repository: hpgem/nanomesh (language: python)

Issue:
Option to label contours individually / globally
Have an option on Mesher2D to label contours:

1: Individually (each label per `RegionMarker`)
2: Group them together (same label for all `RegionMarkers`)


Discussion / hints:
See #204, which adds an option to initialize region markers with the same or sequential labels. These need to be exposed somehow, so that it can be easily accessed in the notebook.

The alternative is to default to labeling every region, and doing the grouping afterwards by operating on the cell data.
This notebook shows one way of doing it by specifying one's own region markers:

https://nanomesh.readthedocs.io/en/latest/examples/How%20to%20generate%20a%20tetrahedral%20mesh%20from%20a%203D%20image.html#Using-region-markers

Fix patch:
diff --git a/docs/examples/examples_generate_a_2d_triangular_mesh.nblink b/docs/examples/examples_generate_a_2d_triangular_mesh.nblink
new file mode 100644
index 00000000..cf1adfd2
--- /dev/null
+++ b/docs/examples/examples_generate_a_2d_triangular_mesh.nblink
@@ -0,0 +1,1 @@
+{"path": "../../notebooks/examples/how_to_generate_a_triangle_mesh_from_a_2d_image.ipynb"}
diff --git a/nanomesh/__init__.py b/nanomesh/__init__.py
index 9170d7cc..68484e5d 100644
--- a/nanomesh/__init__.py
+++ b/nanomesh/__init__.py
@@ -7,7 +7,7 @@
 from .image2mesh import Mesher2D, Mesher3D, plane2mesh, volume2mesh
 from .mesh import LineMesh, TetraMesh, TriangleMesh
 from .mesh_container import MeshContainer
-from .region_markers import RegionMarker
+from .region_markers import RegionMarker, RegionMarkerList
 
 logging.basicConfig(format='%(message)s',
                     level=logging.INFO,
@@ -28,6 +28,7 @@
     'Plane',
     'plane2mesh',
     'RegionMarker',
+    'RegionMarkerList',
     'tetrahedralize',
     'TetraMesh',
     'TriangleMesh',
diff --git a/nanomesh/_tetgen_wrapper.py b/nanomesh/_tetgen_wrapper.py
index 0c25f57c..75cd2e39 100644
--- a/nanomesh/_tetgen_wrapper.py
+++ b/nanomesh/_tetgen_wrapper.py
@@ -3,10 +3,10 @@
 import os
 import tempfile
 from pathlib import Path
-from typing import TYPE_CHECKING, Any, List, Tuple
+from typing import TYPE_CHECKING, Any, Tuple
 
 from ._doc import doc
-from .region_markers import RegionMarker
+from .region_markers import RegionMarkerList
 
 if TYPE_CHECKING:
     from .mesh import TriangleMesh
@@ -15,7 +15,7 @@
 
 def write_smesh(filename: os.PathLike,
                 mesh: TriangleMesh,
-                region_markers: List[RegionMarker] = None):
+                region_markers: RegionMarkerList = None):
     """Save a mesh to a `.smesh` format (Tetgen). http://wias-
     berlin.de/software/tetgen/1.5/doc/manual/manual006.html#ff_smesh.
 
@@ -25,7 +25,7 @@ def write_smesh(filename: os.PathLike,
         Filename to save the data to.
     mesh : TriangleMesh
         Mesh data to be saved.
-    region_markers : List[RegionMarker], optional
+    region_markers : RegionMarkerList, optional
         Override region markers from input mesh.
     """
     if region_markers is None:
diff --git a/nanomesh/image2mesh/mesher2d/_helpers.py b/nanomesh/image2mesh/mesher2d/_helpers.py
index b1962eaf..9a670881 100644
--- a/nanomesh/image2mesh/mesher2d/_helpers.py
+++ b/nanomesh/image2mesh/mesher2d/_helpers.py
@@ -6,7 +6,7 @@
 from scipy.spatial.distance import cdist
 
 from nanomesh._doc import doc
-from nanomesh.region_markers import RegionMarker
+from nanomesh.region_markers import RegionMarker, RegionMarkerList
 
 if TYPE_CHECKING:
     from nanomesh.mesh import LineMesh
@@ -48,8 +48,8 @@ def pad(
     ValueError
         When the value of `side` is invalid.
     """
-    labels = [m.label for m in mesh.region_markers]
-    names = [m.name for m in mesh.region_markers]
+    labels = mesh.region_markers.labels
+    names = mesh.region_markers.names
 
     if (label in labels) and (name is None):
         name = [m.name for m in mesh.region_markers if m.label == label][0]
@@ -113,7 +113,8 @@ def pad(
     cells = np.vstack([mesh.cells, additional_segments])
 
     center = corners.mean(axis=0)
-    region_markers = mesh.region_markers + [RegionMarker(label, center, name)]
+    region_markers = RegionMarkerList(
+        (*mesh.region_markers, RegionMarker(label, center, name)))
 
     segment_markers = mesh.cell_data.get(
         'segment_markers',
diff --git a/nanomesh/image2mesh/mesher2d/_mesher.py b/nanomesh/image2mesh/mesher2d/_mesher.py
index 5f1a3bf8..2621cd0e 100644
--- a/nanomesh/image2mesh/mesher2d/_mesher.py
+++ b/nanomesh/image2mesh/mesher2d/_mesher.py
@@ -8,7 +8,7 @@
 from skimage import measure
 
 from nanomesh._doc import doc
-from nanomesh.region_markers import RegionMarker
+from nanomesh.region_markers import RegionMarker, RegionMarkerList
 
 from .._base import AbstractMesher
 from ._helpers import append_to_segment_markers, generate_segment_markers, pad
@@ -98,35 +98,25 @@ def _generate_background_region(polygons: List[Polygon],
     return RegionMarker(label=0, point=point, name='background')
 
 
-def _generate_regions(polygons: List[Polygon],
-                      same_label: bool = True) -> List[RegionMarker]:
+def _generate_regions(polygons: List[Polygon]) -> RegionMarkerList:
     """Generate regions for triangle.
 
     Parameters
     ----------
     polygons : List[Polygon]
         List of polygons.
-    same_label : bool, optional
-        If True, all labels equal 1.
-        If False, label regions sequentially from 1
 
     Returns
     -------
-    regions : List[RegionMarker]
+    regions : RegionMarkerList
         List of region markers describing each feature
     """
-    regions = []
+    regions = RegionMarkerList()
 
     for i, polygon in enumerate(polygons):
         point = polygon.find_point()
 
-        # in LineMesh format
-        if same_label:
-            regions.append(RegionMarker(label=1, point=point, name='feature'))
-        else:
-            label = i + 1
-            regions.append(
-                RegionMarker(label=label, point=point, name=f'feature{label}'))
+        regions.append(RegionMarker(label=1, point=point, name='feature'))
 
     return regions
 
@@ -172,6 +162,7 @@ def generate_contour(
         level: float = None,
         contour_precision: int = 1,
         max_contour_dist: int = 5,
+        group_regions: bool = True,
     ):
         """Generate contours using marching cubes algorithm.
 
@@ -190,6 +181,9 @@ def generate_contour(
         max_contour_dist : int, optional
             Divide long edges so that maximum distance between points does not
             exceed this value.
+        group_regions : bool, optional
+            If True, assign the same label to all features
+            If False, label regions sequentially
         """
         polygons = [
             Polygon(points)
@@ -207,11 +201,14 @@ def generate_contour(
         ]
         polygons = [polygon.remove_duplicate_points() for polygon in polygons]
 
-        regions = _generate_regions(polygons, same_label=True)
+        regions = _generate_regions(polygons)
         regions.append(_generate_background_region(polygons, self.image_bbox))
 
+        if not group_regions:
+            regions = regions.label_sequentially(1, fmt_name='feature{}')
+
         contour = _polygons_to_line_mesh(polygons, self.image_bbox)
-        contour.add_region_markers(regions)
+        contour.region_markers = regions
 
         self.polygons = polygons
         self.contour = contour
diff --git a/nanomesh/image2mesh/mesher3d/_helpers.py b/nanomesh/image2mesh/mesher3d/_helpers.py
index 67b24bc0..83bd8c4d 100644
--- a/nanomesh/image2mesh/mesher3d/_helpers.py
+++ b/nanomesh/image2mesh/mesher3d/_helpers.py
@@ -5,7 +5,7 @@
 import numpy as np
 
 from nanomesh._doc import doc
-from nanomesh.region_markers import RegionMarker
+from nanomesh.region_markers import RegionMarker, RegionMarkerList
 from nanomesh.utils import pairwise
 
 from ._bounding_box import BoundingBox
@@ -54,8 +54,8 @@ def pad(
     ValueError
         When the value of `side` is invalid.
     """
-    labels = [m.label for m in mesh.region_markers]
-    names = [m.name for m in mesh.region_markers]
+    labels = mesh.region_markers.labels
+    names = mesh.region_markers.names
 
     if (label in labels) and (name is None):
         name = [m.name for m in mesh.region_markers if m.label == label][0]
@@ -171,7 +171,8 @@ def pad(
     center = extra_coords.mean(axis=0)
     center[col] = (center[col] + edge_value) / 2
 
-    region_markers = mesh.region_markers + [RegionMarker(label, center, name)]
+    region_markers = RegionMarkerList(
+        (*mesh.region_markers, RegionMarker(label, center, name)))
 
     new_mesh = mesh.__class__(
         points=all_points,
diff --git a/nanomesh/image2mesh/mesher3d/_mesher.py b/nanomesh/image2mesh/mesher3d/_mesher.py
index 5e59f985..2231aea3 100644
--- a/nanomesh/image2mesh/mesher3d/_mesher.py
+++ b/nanomesh/image2mesh/mesher3d/_mesher.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from typing import TYPE_CHECKING, List, Union
+from typing import TYPE_CHECKING, Union
 
 import matplotlib.pyplot as plt
 import numpy as np
@@ -9,7 +9,7 @@
 from nanomesh import Volume, tetrahedralize, triangulate
 from nanomesh._doc import doc
 from nanomesh.mesh import TriangleMesh
-from nanomesh.region_markers import RegionMarker, RegionMarkerLike
+from nanomesh.region_markers import RegionMarker, RegionMarkerList
 
 from .._base import AbstractMesher
 from ._bounding_box import BoundingBox
@@ -44,8 +44,7 @@ def get_point_in_prop(
     return point
 
 
-def get_region_markers(vol: Union[Volume, np.ndarray],
-                       same_label: bool = True) -> List[RegionMarker]:
+def get_region_markers(vol: Union[Volume, np.ndarray]) -> RegionMarkerList:
     """Get region markers describing the featuers in the volume.
 
     The array will be labeled, and points inside the labeled region
@@ -65,7 +64,7 @@ def get_region_markers(vol: Union[Volume, np.ndarray],
         and the second the pixel coordinates somewhere in the center of the
         corresponding region.
     """
-    region_markers = []
+    region_markers = RegionMarkerList()
 
     if isinstance(vol, Volume):
         image = vol.image
@@ -83,14 +82,11 @@ def get_region_markers(vol: Union[Volume, np.ndarray],
 
         if label == 0:
             name = 'background'
-        elif same_label:
-            name = 'feature'
         else:
-            label = labels[i, j, k]
-            name = f'feature{label}'
+            name = 'feature'
 
-        region_marker = RegionMarker(label=label, point=point, name=name)
-        region_markers.append(region_marker)
+        region_markers.append(RegionMarker(label=label, point=point,
+                                           name=name))
 
     return region_markers
 
@@ -237,6 +233,7 @@ def __init__(self, image: np.ndarray):
     def generate_contour(
         self,
         level: float = None,
+        group_regions: bool = True,
     ):
         """Generate contours using marching cubes algorithm
         (:func:`skimage.measure.marching_cubes`).
@@ -254,6 +251,9 @@ def generate_contour(
             Contour value to search for isosurfaces (i.e. the threshold value).
             By default takes the average of the min and max value. Can be
             ignored if a binary image is passed to :class:`Mesher3D`.
+        group_regions : bool, optional
+            If True, assign the same label to all features
+            If False, label regions sequentially
         """
         from nanomesh.mesh import TriangleMesh
 
@@ -273,8 +273,12 @@ def generate_contour(
         else:
             segmented = self.image
 
-        region_markers = get_region_markers(segmented)
-        contour.add_region_markers(region_markers)
+        regions = get_region_markers(segmented)
+
+        if not group_regions:
+            regions = regions.label_sequentially(1, fmt_name='fasdf{}')
+
+        contour.region_markers = regions
 
         self.contour = contour
 
@@ -287,20 +291,18 @@ def pad_contour(self, **kwargs):
     def plot_contour(self, **kwargs):
         self.contour.plot_pyvista(**kwargs)
 
-    def set_region_markers(self, region_markers: List[RegionMarkerLike]):
+    def set_region_markers(self, region_markers: RegionMarkerList):
         """Sets custom region markers for tetrahedralization.
 
         This overwrites any contours generated by `.generate_contour()`.
 
         Parameters
         ----------
-        region_markers : List[RegionMarkerLike]
+        region_markers : RegionMarkerList
             List of `RegionMarker` objects.
         """
         self.contour.region_markers.clear()
-
-        for region_marker in region_markers:
-            self.contour.add_region_marker(region_marker)
+        self.contour.region_markers.extend(region_markers)
 
     @doc(tetrahedralize,
          prefix=('Tetrahedralize surface contour mesh using'
diff --git a/nanomesh/mesh/_base.py b/nanomesh/mesh/_base.py
index 34d0ecd7..7b7c52a3 100644
--- a/nanomesh/mesh/_base.py
+++ b/nanomesh/mesh/_base.py
@@ -1,14 +1,14 @@
 from __future__ import annotations
 
 from types import MappingProxyType
-from typing import Any, Dict, List, Sequence
+from typing import Any, Dict
 
 import meshio
 import numpy as np
 import pyvista as pv
 
 from .._doc import DocFormatterMeta, doc
-from ..region_markers import RegionMarker, RegionMarkerLike
+from ..region_markers import RegionMarkerList
 
 registry: Dict[str, Any] = {}
 
@@ -25,7 +25,7 @@ class GenericMesh(object, metaclass=DocFormatterMeta):
         Index array describing the cells of the mesh.
     fields : Dict[str, int]:
         Mapping from field names to labels
-    region_markers : List[RegionMarker], optional
+    region_markers : RegionMarkerList, optional
         List of region markers used for assigning labels to regions.
         Defaults to an empty list.
     **cell_data
@@ -38,7 +38,7 @@ def __init__(self,
                  points: np.ndarray,
                  cells: np.ndarray,
                  fields: Dict[str, int] = None,
-                 region_markers: List[RegionMarker] = None,
+                 region_markers: RegionMarkerList = None,
                  **cell_data):
         default_key = 'physical'
         if (not cell_data) or (default_key in cell_data):
@@ -51,7 +51,9 @@ def __init__(self,
         self.points = points
         self.cells = cells
         self.field_to_number = MappingProxyType(self.fields)
-        self.region_markers = [] if region_markers is None else region_markers
+        self.region_markers = RegionMarkerList()
+        if region_markers:
+            self.region_markers.extend(region_markers)
         self.cell_data = cell_data
 
     def __repr__(self, indent: int = 0):
@@ -79,33 +81,6 @@ def number_to_field(self):
             {v: k
              for k, v in self.field_to_number.items()})
 
-    def add_region_marker(self, region_marker: RegionMarkerLike):
-        """Add marker to list of :attr:`{classname}.region_markers`.
-
-        Parameters
-        ----------
-        region_marker : RegionMarkerLike
-            Either a `RegionMarker` object or `(label, point)` tuple,
-            where the label must be an `int` and the point a 2- or
-            3-element numpy array.
-        """
-        if not isinstance(region_marker, RegionMarker):
-            region_marker = RegionMarker(*region_marker)
-
-        self.region_markers.append(region_marker)
-
-    def add_region_markers(self, region_markers: Sequence[RegionMarkerLike]):
-        """Add markers to list of :attr:`{classname}.region_markers`.
-
-        Parameters
-        ----------
-        region_markers : List[RegionMarkerLike]
-            List of region markers passed to
-            :meth:`{classname}.add_region_marker`.
-        """
-        for region_marker in region_markers:
-            self.add_region_marker(region_marker)
-
     def to_meshio(self) -> 'meshio.Mesh':
         """Return instance of :func:`meshio.Mesh`."""
         cells = [
diff --git a/nanomesh/region_markers.py b/nanomesh/region_markers.py
index 12b87d41..6e37df3f 100644
--- a/nanomesh/region_markers.py
+++ b/nanomesh/region_markers.py
@@ -1,5 +1,10 @@
+from __future__ import annotations
+
+from collections import abc
+from copy import copy
 from dataclasses import dataclass
-from typing import Optional, Tuple, Union
+from functools import singledispatchmethod
+from typing import List, Optional, Tuple, Union
 
 import numpy.typing as npt
 
@@ -37,6 +42,135 @@ class RegionMarker:
     def __post_init__(self):
         self.point = tuple(self.point)
 
+    def update(self, **kwargs) -> RegionMarker:
+        new_marker = copy(self)
+        new_marker.__dict__.update(kwargs)
+        return new_marker
+
 
 RegionMarkerLike = Union[RegionMarker, Tuple[int, Tuple[float, ...],
                                              Optional[str]]]
+
+
+class RegionMarkerList(List[RegionMarker]):
+    """Collection of region markers.
+
+    Sub-classes :func:`list`.
+    """
+
+    def __repr__(self):
+        s = ',\n'.join(f'    {marker}' for marker in self)
+        return f'{self.__class__.__name__}(\n{s}\n)'
+
+    @singledispatchmethod
+    def relabel(self,
+                old: abc.Callable,
+                new: int,
+                name: str = None) -> RegionMarkerList:
+        """Relabel a sub-group of region markers.
+
+        Parameters
+        ----------
+        old : callable or list or int
+            The old label(s) to replace. The value can be of type:
+            - int, matches this exact label
+            - list, matches all labels in this list
+            - callable, returns True if label is a match
+        new : int
+            New label to assign
+        name : str, optional
+            Optional name to assign to the new region markers.
+
+        Returns
+        -------
+        RegionMarkerList
+            New list of region markers with updated labels.
+        """
+        if not name:
+            names = [m.name for m in self if old(m.label)]
+            if len(set(names)) == 1:
+                name = tuple(names)[0]
+
+        markers = (m.update(label=new, name=name) if old(m.label) else m
+                   for m in self)
+
+        return RegionMarkerList(markers)
+
+    @relabel.register
+    def _(self, old: abc.Sequence, new: int, *args, **kwargs):
+
+        def f(x):
+            return x in old
+
+        return self.relabel(f, new, *args, **kwargs)
+
+    @relabel.register
+    def _(self, old: int, new: int, *args, **kwargs):
+
+        def f(x):
+            return x == old
+
+        return self.relabel(f, new, *args, **kwargs)
+
+    @singledispatchmethod
+    def label_sequentially(self, old: abc.Callable, fmt_name: str = None):
+        """Re-label a set of regions sequentially.
+
+        `old` matches a sub-group of region markers and assigns new labels.
+        The new labels are incremented sequentially. E.g.:
+        [1,1,1,2] -> [3,4,5,6]
+
+        Parameters
+        ----------
+        old : callable or list or int
+            The old label(s) to replace. The value can be of type:
+            - int, matches this exact label
+            - list, matches all labels in this list
+            - callable, returns True if label is a match
+        fmt_name : str, optional
+            Optional format string for the label name, e.g. 'feature{}'. The
+            placeholder (`{}`) will be substituted by the new label.
+
+        Returns
+        -------
+        RegionMarkerList
+            New list of region markers with updated labels.
+        """
+        markers = [copy(marker) for marker in self]
+        i = max(self.labels) + 1
+        for marker in markers:
+            if not old(marker.label):
+                continue
+
+            marker.label = i
+            if fmt_name:
+                marker.name = fmt_name.format(i)
+
+            i += 1
+        return RegionMarkerList(markers)
+
+    @label_sequentially.register
+    def _(self, old: abc.Sequence, *args, **kwargs):
+
+        def f(x):
+            return x in old
+
+        return self.label_sequentially(f, *args, **kwargs)
+
+    @label_sequentially.register
+    def _(self, old: int, *args, **kwargs):
+
+        def f(x):
+            return x == old
+
+        return self.label_sequentially(f, *args, **kwargs)
+
+    @property
+    def labels(self) -> set:
+        """Return all unique region labels."""
+        return set(m.label for m in self)
+
+    @property
+    def names(self) -> set:
+        """Return all unique region names."""
+        return set(m.name for m in self)
diff --git a/notebooks/examples/how_to_generate_a_triangle_mesh_from_a_2d_image.ipynb b/notebooks/examples/how_to_generate_a_triangle_mesh_from_a_2d_image.ipynb
new file mode 100644
index 00000000..e97bcef0
--- /dev/null
+++ b/notebooks/examples/how_to_generate_a_triangle_mesh_from_a_2d_image.ipynb
@@ -0,0 +1,5377 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%load_ext autoreload\n",
+    "%autoreload 2\n",
+    "%matplotlib notebook"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Generate a 2D triangular mesh\n",
+    "\n",
+    "This notebook shows how to mesh a 2D image:\n",
+    "\n",
+    "1. Load and visualize a 2D data image\n",
+    "2. Generate a 2D triangle mesh\n",
+    "3. Visualize the mesh\n",
+    "4. Relabel the regions\n",
+    "5. Export the mesh to other formats"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Load and vizualize the data\n",
+    "\n",
+    "This example uses generated sample data from `nanomesh.data`.\n",
+    "\n",
+    "If you want to use your own data, any (2D) numpy array can be passed to into a [`Plane`](https://nanomesh.readthedocs.io/en/latest/nanomesh.plane.html#nanomesh.plane.Plane) object. Data stored as `.npy` can be loaded using `Plane.load()`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "/* Put everything inside the global mpl namespace */\n",
+       "/* global mpl */\n",
+       "window.mpl = {};\n",
+       "\n",
+       "mpl.get_websocket_type = function () {\n",
+       "    if (typeof WebSocket !== 'undefined') {\n",
+       "        return WebSocket;\n",
+       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
+       "        return MozWebSocket;\n",
+       "    } else {\n",
+       "        alert(\n",
+       "            'Your browser does not have WebSocket support. ' +\n",
+       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+       "                'Firefox 4 and 5 are also supported but you ' +\n",
+       "                'have to enable WebSockets in about:config.'\n",
+       "        );\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+       "    this.id = figure_id;\n",
+       "\n",
+       "    this.ws = websocket;\n",
+       "\n",
+       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
+       "\n",
+       "    if (!this.supports_binary) {\n",
+       "        var warnings = document.getElementById('mpl-warnings');\n",
+       "        if (warnings) {\n",
+       "            warnings.style.display = 'block';\n",
+       "            warnings.textContent =\n",
+       "                'This browser does not support binary websocket messages. ' +\n",
+       "                'Performance may be slow.';\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.imageObj = new Image();\n",
+       "\n",
+       "    this.context = undefined;\n",
+       "    this.message = undefined;\n",
+       "    this.canvas = undefined;\n",
+       "    this.rubberband_canvas = undefined;\n",
+       "    this.rubberband_context = undefined;\n",
+       "    this.format_dropdown = undefined;\n",
+       "\n",
+       "    this.image_mode = 'full';\n",
+       "\n",
+       "    this.root = document.createElement('div');\n",
+       "    this.root.setAttribute('style', 'display: inline-block');\n",
+       "    this._root_extra_style(this.root);\n",
+       "\n",
+       "    parent_element.appendChild(this.root);\n",
+       "\n",
+       "    this._init_header(this);\n",
+       "    this._init_canvas(this);\n",
+       "    this._init_toolbar(this);\n",
+       "\n",
+       "    var fig = this;\n",
+       "\n",
+       "    this.waiting = false;\n",
+       "\n",
+       "    this.ws.onopen = function () {\n",
+       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+       "        fig.send_message('send_image_mode', {});\n",
+       "        if (fig.ratio !== 1) {\n",
+       "            fig.send_message('set_device_pixel_ratio', {\n",
+       "                device_pixel_ratio: fig.ratio,\n",
+       "            });\n",
+       "        }\n",
+       "        fig.send_message('refresh', {});\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onload = function () {\n",
+       "        if (fig.image_mode === 'full') {\n",
+       "            // Full images could contain transparency (where diff images\n",
+       "            // almost always do), so we need to clear the canvas so that\n",
+       "            // there is no ghosting.\n",
+       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+       "        }\n",
+       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onunload = function () {\n",
+       "        fig.ws.close();\n",
+       "    };\n",
+       "\n",
+       "    this.ws.onmessage = this._make_on_message_function(this);\n",
+       "\n",
+       "    this.ondownload = ondownload;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_header = function () {\n",
+       "    var titlebar = document.createElement('div');\n",
+       "    titlebar.classList =\n",
+       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+       "    var titletext = document.createElement('div');\n",
+       "    titletext.classList = 'ui-dialog-title';\n",
+       "    titletext.setAttribute(\n",
+       "        'style',\n",
+       "        'width: 100%; text-align: center; padding: 3px;'\n",
+       "    );\n",
+       "    titlebar.appendChild(titletext);\n",
+       "    this.root.appendChild(titlebar);\n",
+       "    this.header = titletext;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._init_canvas = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+       "    canvas_div.setAttribute(\n",
+       "        'style',\n",
+       "        'border: 1px solid #ddd;' +\n",
+       "            'box-sizing: content-box;' +\n",
+       "            'clear: both;' +\n",
+       "            'min-height: 1px;' +\n",
+       "            'min-width: 1px;' +\n",
+       "            'outline: 0;' +\n",
+       "            'overflow: hidden;' +\n",
+       "            'position: relative;' +\n",
+       "            'resize: both;'\n",
+       "    );\n",
+       "\n",
+       "    function on_keyboard_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.key_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keydown',\n",
+       "        on_keyboard_event_closure('key_press')\n",
+       "    );\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keyup',\n",
+       "        on_keyboard_event_closure('key_release')\n",
+       "    );\n",
+       "\n",
+       "    this._canvas_extra_style(canvas_div);\n",
+       "    this.root.appendChild(canvas_div);\n",
+       "\n",
+       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
+       "    canvas.classList.add('mpl-canvas');\n",
+       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+       "\n",
+       "    this.context = canvas.getContext('2d');\n",
+       "\n",
+       "    var backingStore =\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        this.context.webkitBackingStorePixelRatio ||\n",
+       "        this.context.mozBackingStorePixelRatio ||\n",
+       "        this.context.msBackingStorePixelRatio ||\n",
+       "        this.context.oBackingStorePixelRatio ||\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        1;\n",
+       "\n",
+       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+       "\n",
+       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+       "        'canvas'\n",
+       "    ));\n",
+       "    rubberband_canvas.setAttribute(\n",
+       "        'style',\n",
+       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+       "    );\n",
+       "\n",
+       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+       "    if (this.ResizeObserver === undefined) {\n",
+       "        if (window.ResizeObserver !== undefined) {\n",
+       "            this.ResizeObserver = window.ResizeObserver;\n",
+       "        } else {\n",
+       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+       "            this.ResizeObserver = obs.ResizeObserver;\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+       "        var nentries = entries.length;\n",
+       "        for (var i = 0; i < nentries; i++) {\n",
+       "            var entry = entries[i];\n",
+       "            var width, height;\n",
+       "            if (entry.contentBoxSize) {\n",
+       "                if (entry.contentBoxSize instanceof Array) {\n",
+       "                    // Chrome 84 implements new version of spec.\n",
+       "                    width = entry.contentBoxSize[0].inlineSize;\n",
+       "                    height = entry.contentBoxSize[0].blockSize;\n",
+       "                } else {\n",
+       "                    // Firefox implements old version of spec.\n",
+       "                    width = entry.contentBoxSize.inlineSize;\n",
+       "                    height = entry.contentBoxSize.blockSize;\n",
+       "                }\n",
+       "            } else {\n",
+       "                // Chrome <84 implements even older version of spec.\n",
+       "                width = entry.contentRect.width;\n",
+       "                height = entry.contentRect.height;\n",
+       "            }\n",
+       "\n",
+       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
+       "            // the canvas container.\n",
+       "            if (entry.devicePixelContentBoxSize) {\n",
+       "                // Chrome 84 implements new version of spec.\n",
+       "                canvas.setAttribute(\n",
+       "                    'width',\n",
+       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
+       "                );\n",
+       "                canvas.setAttribute(\n",
+       "                    'height',\n",
+       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
+       "                );\n",
+       "            } else {\n",
+       "                canvas.setAttribute('width', width * fig.ratio);\n",
+       "                canvas.setAttribute('height', height * fig.ratio);\n",
+       "            }\n",
+       "            canvas.setAttribute(\n",
+       "                'style',\n",
+       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
+       "            );\n",
+       "\n",
+       "            rubberband_canvas.setAttribute('width', width);\n",
+       "            rubberband_canvas.setAttribute('height', height);\n",
+       "\n",
+       "            // And update the size in Python. We ignore the initial 0/0 size\n",
+       "            // that occurs as the element is placed into the DOM, which should\n",
+       "            // otherwise not happen due to the minimum size styling.\n",
+       "            if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+       "                fig.request_resize(width, height);\n",
+       "            }\n",
+       "        }\n",
+       "    });\n",
+       "    this.resizeObserverInstance.observe(canvas_div);\n",
+       "\n",
+       "    function on_mouse_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.mouse_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousedown',\n",
+       "        on_mouse_event_closure('button_press')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseup',\n",
+       "        on_mouse_event_closure('button_release')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'dblclick',\n",
+       "        on_mouse_event_closure('dblclick')\n",
+       "    );\n",
+       "    // Throttle sequential mouse events to 1 every 20ms.\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousemove',\n",
+       "        on_mouse_event_closure('motion_notify')\n",
+       "    );\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseenter',\n",
+       "        on_mouse_event_closure('figure_enter')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseleave',\n",
+       "        on_mouse_event_closure('figure_leave')\n",
+       "    );\n",
+       "\n",
+       "    canvas_div.addEventListener('wheel', function (event) {\n",
+       "        if (event.deltaY < 0) {\n",
+       "            event.step = 1;\n",
+       "        } else {\n",
+       "            event.step = -1;\n",
+       "        }\n",
+       "        on_mouse_event_closure('scroll')(event);\n",
+       "    });\n",
+       "\n",
+       "    canvas_div.appendChild(canvas);\n",
+       "    canvas_div.appendChild(rubberband_canvas);\n",
+       "\n",
+       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+       "    this.rubberband_context.strokeStyle = '#000000';\n",
+       "\n",
+       "    this._resize_canvas = function (width, height, forward) {\n",
+       "        if (forward) {\n",
+       "            canvas_div.style.width = width + 'px';\n",
+       "            canvas_div.style.height = height + 'px';\n",
+       "        }\n",
+       "    };\n",
+       "\n",
+       "    // Disable right mouse context menu.\n",
+       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+       "        event.preventDefault();\n",
+       "        return false;\n",
+       "    });\n",
+       "\n",
+       "    function set_focus() {\n",
+       "        canvas.focus();\n",
+       "        canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    window.setTimeout(set_focus, 100);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'mpl-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'mpl-button-group';\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'mpl-button-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
+       "        button.classList = 'mpl-widget';\n",
+       "        button.setAttribute('role', 'button');\n",
+       "        button.setAttribute('aria-disabled', 'false');\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "\n",
+       "        var icon_img = document.createElement('img');\n",
+       "        icon_img.src = '_images/' + image + '.png';\n",
+       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+       "        icon_img.alt = tooltip;\n",
+       "        button.appendChild(icon_img);\n",
+       "\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    var fmt_picker = document.createElement('select');\n",
+       "    fmt_picker.classList = 'mpl-widget';\n",
+       "    toolbar.appendChild(fmt_picker);\n",
+       "    this.format_dropdown = fmt_picker;\n",
+       "\n",
+       "    for (var ind in mpl.extensions) {\n",
+       "        var fmt = mpl.extensions[ind];\n",
+       "        var option = document.createElement('option');\n",
+       "        option.selected = fmt === mpl.default_extension;\n",
+       "        option.innerHTML = fmt;\n",
+       "        fmt_picker.appendChild(option);\n",
+       "    }\n",
+       "\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+       "    // which will in turn request a refresh of the image.\n",
+       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_message = function (type, properties) {\n",
+       "    properties['type'] = type;\n",
+       "    properties['figure_id'] = this.id;\n",
+       "    this.ws.send(JSON.stringify(properties));\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_draw_message = function () {\n",
+       "    if (!this.waiting) {\n",
+       "        this.waiting = true;\n",
+       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    var format_dropdown = fig.format_dropdown;\n",
+       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+       "    fig.ondownload(fig, format);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+       "    var size = msg['size'];\n",
+       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+       "        fig.send_message('refresh', {});\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+       "    var x0 = msg['x0'] / fig.ratio;\n",
+       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+       "    var x1 = msg['x1'] / fig.ratio;\n",
+       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+       "    x0 = Math.floor(x0) + 0.5;\n",
+       "    y0 = Math.floor(y0) + 0.5;\n",
+       "    x1 = Math.floor(x1) + 0.5;\n",
+       "    y1 = Math.floor(y1) + 0.5;\n",
+       "    var min_x = Math.min(x0, x1);\n",
+       "    var min_y = Math.min(y0, y1);\n",
+       "    var width = Math.abs(x1 - x0);\n",
+       "    var height = Math.abs(y1 - y0);\n",
+       "\n",
+       "    fig.rubberband_context.clearRect(\n",
+       "        0,\n",
+       "        0,\n",
+       "        fig.canvas.width / fig.ratio,\n",
+       "        fig.canvas.height / fig.ratio\n",
+       "    );\n",
+       "\n",
+       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+       "    // Updates the figure title.\n",
+       "    fig.header.textContent = msg['label'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+       "    fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+       "    fig.message.textContent = msg['message'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+       "    // Request the server to send over a new figure.\n",
+       "    fig.send_draw_message();\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+       "    fig.image_mode = msg['mode'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+       "    for (var key in msg) {\n",
+       "        if (!(key in fig.buttons)) {\n",
+       "            continue;\n",
+       "        }\n",
+       "        fig.buttons[key].disabled = !msg[key];\n",
+       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+       "    if (msg['mode'] === 'PAN') {\n",
+       "        fig.buttons['Pan'].classList.add('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    } else if (msg['mode'] === 'ZOOM') {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.add('active');\n",
+       "    } else {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Called whenever the canvas gets updated.\n",
+       "    this.send_message('ack', {});\n",
+       "};\n",
+       "\n",
+       "// A function to construct a web socket function for onmessage handling.\n",
+       "// Called in the figure constructor.\n",
+       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+       "    return function socket_on_message(evt) {\n",
+       "        if (evt.data instanceof Blob) {\n",
+       "            var img = evt.data;\n",
+       "            if (img.type !== 'image/png') {\n",
+       "                /* FIXME: We get \"Resource interpreted as Image but\n",
+       "                 * transferred with MIME type text/plain:\" errors on\n",
+       "                 * Chrome.  But how to set the MIME type?  It doesn't seem\n",
+       "                 * to be part of the websocket stream */\n",
+       "                img.type = 'image/png';\n",
+       "            }\n",
+       "\n",
+       "            /* Free the memory for the previous frames */\n",
+       "            if (fig.imageObj.src) {\n",
+       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
+       "                    fig.imageObj.src\n",
+       "                );\n",
+       "            }\n",
+       "\n",
+       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+       "                img\n",
+       "            );\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        } else if (\n",
+       "            typeof evt.data === 'string' &&\n",
+       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+       "        ) {\n",
+       "            fig.imageObj.src = evt.data;\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        var msg = JSON.parse(evt.data);\n",
+       "        var msg_type = msg['type'];\n",
+       "\n",
+       "        // Call the  \"handle_{type}\" callback, which takes\n",
+       "        // the figure and JSON message as its only arguments.\n",
+       "        try {\n",
+       "            var callback = fig['handle_' + msg_type];\n",
+       "        } catch (e) {\n",
+       "            console.log(\n",
+       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
+       "                msg\n",
+       "            );\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        if (callback) {\n",
+       "            try {\n",
+       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+       "                callback(fig, msg);\n",
+       "            } catch (e) {\n",
+       "                console.log(\n",
+       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+       "                    e,\n",
+       "                    e.stack,\n",
+       "                    msg\n",
+       "                );\n",
+       "            }\n",
+       "        }\n",
+       "    };\n",
+       "};\n",
+       "\n",
+       "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+       "mpl.findpos = function (e) {\n",
+       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+       "    var targ;\n",
+       "    if (!e) {\n",
+       "        e = window.event;\n",
+       "    }\n",
+       "    if (e.target) {\n",
+       "        targ = e.target;\n",
+       "    } else if (e.srcElement) {\n",
+       "        targ = e.srcElement;\n",
+       "    }\n",
+       "    if (targ.nodeType === 3) {\n",
+       "        // defeat Safari bug\n",
+       "        targ = targ.parentNode;\n",
+       "    }\n",
+       "\n",
+       "    // pageX,Y are the mouse positions relative to the document\n",
+       "    var boundingRect = targ.getBoundingClientRect();\n",
+       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+       "\n",
+       "    return { x: x, y: y };\n",
+       "};\n",
+       "\n",
+       "/*\n",
+       " * return a copy of an object with only non-object keys\n",
+       " * we need this to avoid circular references\n",
+       " * https://stackoverflow.com/a/24161582/3208463\n",
+       " */\n",
+       "function simpleKeys(original) {\n",
+       "    return Object.keys(original).reduce(function (obj, key) {\n",
+       "        if (typeof original[key] !== 'object') {\n",
+       "            obj[key] = original[key];\n",
+       "        }\n",
+       "        return obj;\n",
+       "    }, {});\n",
+       "}\n",
+       "\n",
+       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+       "    var canvas_pos = mpl.findpos(event);\n",
+       "\n",
+       "    if (name === 'button_press') {\n",
+       "        this.canvas.focus();\n",
+       "        this.canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    var x = canvas_pos.x * this.ratio;\n",
+       "    var y = canvas_pos.y * this.ratio;\n",
+       "\n",
+       "    this.send_message(name, {\n",
+       "        x: x,\n",
+       "        y: y,\n",
+       "        button: event.button,\n",
+       "        step: event.step,\n",
+       "        guiEvent: simpleKeys(event),\n",
+       "    });\n",
+       "\n",
+       "    /* This prevents the web browser from automatically changing to\n",
+       "     * the text insertion cursor when the button is pressed.  We want\n",
+       "     * to control all of the cursor setting manually through the\n",
+       "     * 'cursor' event from matplotlib */\n",
+       "    event.preventDefault();\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+       "    // Handle any extra behaviour associated with a key event\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.key_event = function (event, name) {\n",
+       "    // Prevent repeat events\n",
+       "    if (name === 'key_press') {\n",
+       "        if (event.key === this._key) {\n",
+       "            return;\n",
+       "        } else {\n",
+       "            this._key = event.key;\n",
+       "        }\n",
+       "    }\n",
+       "    if (name === 'key_release') {\n",
+       "        this._key = null;\n",
+       "    }\n",
+       "\n",
+       "    var value = '';\n",
+       "    if (event.ctrlKey && event.key !== 'Control') {\n",
+       "        value += 'ctrl+';\n",
+       "    }\n",
+       "    else if (event.altKey && event.key !== 'Alt') {\n",
+       "        value += 'alt+';\n",
+       "    }\n",
+       "    else if (event.shiftKey && event.key !== 'Shift') {\n",
+       "        value += 'shift+';\n",
+       "    }\n",
+       "\n",
+       "    value += 'k' + event.key;\n",
+       "\n",
+       "    this._key_event_extra(event, name);\n",
+       "\n",
+       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+       "    if (name === 'download') {\n",
+       "        this.handle_save(this, null);\n",
+       "    } else {\n",
+       "        this.send_message('toolbar_button', { name: name });\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+       "    this.message.textContent = tooltip;\n",
+       "};\n",
+       "\n",
+       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+       "// prettier-ignore\n",
+       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+       "\n",
+       "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+       "\n",
+       "mpl.default_extension = \"png\";/* global mpl */\n",
+       "\n",
+       "var comm_websocket_adapter = function (comm) {\n",
+       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
+       "    // object with the appropriate methods. Currently this is a non binary\n",
+       "    // socket, so there is still some room for performance tuning.\n",
+       "    var ws = {};\n",
+       "\n",
+       "    ws.binaryType = comm.kernel.ws.binaryType;\n",
+       "    ws.readyState = comm.kernel.ws.readyState;\n",
+       "    function updateReadyState(_event) {\n",
+       "        if (comm.kernel.ws) {\n",
+       "            ws.readyState = comm.kernel.ws.readyState;\n",
+       "        } else {\n",
+       "            ws.readyState = 3; // Closed state.\n",
+       "        }\n",
+       "    }\n",
+       "    comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+       "\n",
+       "    ws.close = function () {\n",
+       "        comm.close();\n",
+       "    };\n",
+       "    ws.send = function (m) {\n",
+       "        //console.log('sending', m);\n",
+       "        comm.send(m);\n",
+       "    };\n",
+       "    // Register the callback with on_msg.\n",
+       "    comm.on_msg(function (msg) {\n",
+       "        //console.log('receiving', msg['content']['data'], msg);\n",
+       "        var data = msg['content']['data'];\n",
+       "        if (data['blob'] !== undefined) {\n",
+       "            data = {\n",
+       "                data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+       "            };\n",
+       "        }\n",
+       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+       "        ws.onmessage(data);\n",
+       "    });\n",
+       "    return ws;\n",
+       "};\n",
+       "\n",
+       "mpl.mpl_figure_comm = function (comm, msg) {\n",
+       "    // This is the function which gets called when the mpl process\n",
+       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+       "\n",
+       "    var id = msg.content.data.id;\n",
+       "    // Get hold of the div created by the display call when the Comm\n",
+       "    // socket was opened in Python.\n",
+       "    var element = document.getElementById(id);\n",
+       "    var ws_proxy = comm_websocket_adapter(comm);\n",
+       "\n",
+       "    function ondownload(figure, _format) {\n",
+       "        window.open(figure.canvas.toDataURL());\n",
+       "    }\n",
+       "\n",
+       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+       "\n",
+       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+       "    // web socket which is closed, not our websocket->open comm proxy.\n",
+       "    ws_proxy.onopen();\n",
+       "\n",
+       "    fig.parent_element = element;\n",
+       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
+       "    if (!fig.cell_info) {\n",
+       "        console.error('Failed to find cell for figure', id, fig);\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.cell_info[0].output_area.element.on(\n",
+       "        'cleared',\n",
+       "        { fig: fig },\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+       "    var width = fig.canvas.width / fig.ratio;\n",
+       "    fig.cell_info[0].output_area.element.off(\n",
+       "        'cleared',\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+       "\n",
+       "    // Update the output cell to use the data from the current canvas.\n",
+       "    fig.push_to_output();\n",
+       "    var dataURL = fig.canvas.toDataURL();\n",
+       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+       "    // the notebook keyboard shortcuts fail.\n",
+       "    IPython.keyboard_manager.enable();\n",
+       "    fig.parent_element.innerHTML =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "    fig.close_ws(fig, msg);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+       "    fig.send_message('closing', msg);\n",
+       "    // fig.ws.close()\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+       "    // Turn the data on the canvas into data in the output cell.\n",
+       "    var width = this.canvas.width / this.ratio;\n",
+       "    var dataURL = this.canvas.toDataURL();\n",
+       "    this.cell_info[1]['text/html'] =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Tell IPython that the notebook contents must change.\n",
+       "    IPython.notebook.set_dirty(true);\n",
+       "    this.send_message('ack', {});\n",
+       "    var fig = this;\n",
+       "    // Wait a second, then push the new image to the DOM so\n",
+       "    // that it is saved nicely (might be nice to debounce this).\n",
+       "    setTimeout(function () {\n",
+       "        fig.push_to_output();\n",
+       "    }, 1000);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'btn-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'btn-group';\n",
+       "    var button;\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'btn-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        button = fig.buttons[name] = document.createElement('button');\n",
+       "        button.classList = 'btn btn-default';\n",
+       "        button.href = '#';\n",
+       "        button.title = name;\n",
+       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    // Add the status bar.\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message pull-right';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "\n",
+       "    // Add the close button to the window.\n",
+       "    var buttongrp = document.createElement('div');\n",
+       "    buttongrp.classList = 'btn-group inline pull-right';\n",
+       "    button = document.createElement('button');\n",
+       "    button.classList = 'btn btn-mini btn-primary';\n",
+       "    button.href = '#';\n",
+       "    button.title = 'Stop Interaction';\n",
+       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
+       "    button.addEventListener('click', function (_evt) {\n",
+       "        fig.handle_close(fig, {});\n",
+       "    });\n",
+       "    button.addEventListener(\n",
+       "        'mouseover',\n",
+       "        on_mouseover_closure('Stop Interaction')\n",
+       "    );\n",
+       "    buttongrp.appendChild(button);\n",
+       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+       "    var fig = event.data.fig;\n",
+       "    if (event.target !== this) {\n",
+       "        // Ignore bubbled events from children.\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.close_ws(fig, {});\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (el) {\n",
+       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+       "    // this is important to make the div 'focusable\n",
+       "    el.setAttribute('tabindex', 0);\n",
+       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
+       "    // off when our div gets focus\n",
+       "\n",
+       "    // location in version 3\n",
+       "    if (IPython.notebook.keyboard_manager) {\n",
+       "        IPython.notebook.keyboard_manager.register_events(el);\n",
+       "    } else {\n",
+       "        // location in version 2\n",
+       "        IPython.keyboard_manager.register_events(el);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+       "    // Check for shift+enter\n",
+       "    if (event.shiftKey && event.which === 13) {\n",
+       "        this.canvas_div.blur();\n",
+       "        // select the cell after this one\n",
+       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+       "        IPython.notebook.select(index + 1);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    fig.ondownload(fig, null);\n",
+       "};\n",
+       "\n",
+       "mpl.find_output_cell = function (html_output) {\n",
+       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+       "    // IPython event is triggered only after the cells have been serialised, which for\n",
+       "    // our purposes (turning an active figure into a static one), is too late.\n",
+       "    var cells = IPython.notebook.get_cells();\n",
+       "    var ncells = cells.length;\n",
+       "    for (var i = 0; i < ncells; i++) {\n",
+       "        var cell = cells[i];\n",
+       "        if (cell.cell_type === 'code') {\n",
+       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+       "                var data = cell.output_area.outputs[j];\n",
+       "                if (data.data) {\n",
+       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
+       "                    data = data.data;\n",
+       "                }\n",
+       "                if (data['text/html'] === html_output) {\n",
+       "                    return [cell, data, j];\n",
+       "                }\n",
+       "            }\n",
+       "        }\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "// Register the function which deals with the matplotlib target/channel.\n",
+       "// The kernel may be null if the page has been refreshed.\n",
+       "if (IPython.notebook.kernel !== null) {\n",
+       "    IPython.notebook.kernel.comm_manager.register_target(\n",
+       "        'matplotlib',\n",
+       "        mpl.mpl_figure_comm\n",
+       "    );\n",
+       "}\n"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Javascript object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAgAElEQVR4Xu3dMWoQUQCEYdLnAHa2NraCvZews08j2NikEW8giLVdrER7D5BLBLyBILZ6AJsQGH7Y/WphCpnE2e+9XS/ef//25Oefl7ev315/fPT09+Wnv49v3t29uXr+6vPt118vLj88+/HFn/v70Q8/H34/+P3o3wf/PtoHx9lHFwaggWvgG/gGvoFv4Bv4Bv65Br4BSDgJLwF3AuAExAmQEzAngCc7ATUADUAD0AA0AA1AA9AANAANwHMRqDuOjsAdgTsCdwTuCNwRuCPwsx2BE0ACSAAJIAEkgASQABJAAkgAveV0nLecCC/hJbyEl/ASXsL7v/ASQAJIAAkgASSABJAAEkACSAAJIAH0HUzfAfUdVN+BdYLgBOHIJwgEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkhICAkhISSEhJCcTEgMQAPQADQADUAD0AA0AA1AA/DGCZEToqOcELkD6A6gO4AecDzgeMDxgOMBxwPOyR5wDEAD0AA0AA1AA9AANAANQAPQSyCIH/EfhfjdYXKH6ch3mPRbv/X74R96J4AEkAASQAJIAAkgASSABJAAEkACSAB9BsZnYHwGhjASxiMLIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEhJCQkgICSEhJCcTEgPQADQADUAD0AA0AA1AA9AA9CHoOydERzkhcgfQHUB3AD3geMDxgOMBxwOOB5yTPeAYgAagAWgAGoAGoAFoABqABqCXQLwEgviPQvzuMLnDdOQ7TPqt3/rtQ9AEg2AQDIJBME4mGAagAWgAGoAGoAFoABqABqAB6CUNL2lcOcG532e83AF0B9AdQHcAPUB5gPIA5QHKA9TJHqAMQAPQADQADUAD0AA0AA1AA9BLIF4C8RKII4T7HSG4g+UOljtYD7+D5efHz0/580MACSABJIAEkAASQAJIAAkgASSABJAAEkD/F7D/C5jQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJNur6loAAAGzSURBVHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/fsHCABbS/RLZbUAAAAASUVORK5CYII=\" width=\"640\">"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<AxesSubplot:xlabel='x', ylabel='y'>"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from nanomesh import Plane\n",
+    "from nanomesh.data import binary_blobs2d\n",
+    "\n",
+    "data = binary_blobs2d(seed=12345)\n",
+    "\n",
+    "plane = Plane(data)\n",
+    "plane.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Generate mesh"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Meshes are generated using the `Mesher2D` class. Meshing consists of two steps:\n",
+    "\n",
+    "1. Contour finding (using the [`find_contours`](https://scikit-image.org/docs/dev/api/skimage.measure.html#skimage.measure.find_contours) function\n",
+    "2. Triangulation (using the [`triangle`](https://rufat.be/triangle/) library)\n",
+    "\n",
+    "Contour finding uses the [marching cubes algorithm](https://en.wikipedia.org/wiki/Marching_cubes) to wrap all the pores in a polygon. `max_contour_dist=5` splits up long edges in the contour, so that no two points are further than 5 pixels apart. `level` is directly passed to `find_contours` and specifies the level at which the contour is generated. In this case, we set it to the threshold value determined above."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "/* Put everything inside the global mpl namespace */\n",
+       "/* global mpl */\n",
+       "window.mpl = {};\n",
+       "\n",
+       "mpl.get_websocket_type = function () {\n",
+       "    if (typeof WebSocket !== 'undefined') {\n",
+       "        return WebSocket;\n",
+       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
+       "        return MozWebSocket;\n",
+       "    } else {\n",
+       "        alert(\n",
+       "            'Your browser does not have WebSocket support. ' +\n",
+       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+       "                'Firefox 4 and 5 are also supported but you ' +\n",
+       "                'have to enable WebSockets in about:config.'\n",
+       "        );\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+       "    this.id = figure_id;\n",
+       "\n",
+       "    this.ws = websocket;\n",
+       "\n",
+       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
+       "\n",
+       "    if (!this.supports_binary) {\n",
+       "        var warnings = document.getElementById('mpl-warnings');\n",
+       "        if (warnings) {\n",
+       "            warnings.style.display = 'block';\n",
+       "            warnings.textContent =\n",
+       "                'This browser does not support binary websocket messages. ' +\n",
+       "                'Performance may be slow.';\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.imageObj = new Image();\n",
+       "\n",
+       "    this.context = undefined;\n",
+       "    this.message = undefined;\n",
+       "    this.canvas = undefined;\n",
+       "    this.rubberband_canvas = undefined;\n",
+       "    this.rubberband_context = undefined;\n",
+       "    this.format_dropdown = undefined;\n",
+       "\n",
+       "    this.image_mode = 'full';\n",
+       "\n",
+       "    this.root = document.createElement('div');\n",
+       "    this.root.setAttribute('style', 'display: inline-block');\n",
+       "    this._root_extra_style(this.root);\n",
+       "\n",
+       "    parent_element.appendChild(this.root);\n",
+       "\n",
+       "    this._init_header(this);\n",
+       "    this._init_canvas(this);\n",
+       "    this._init_toolbar(this);\n",
+       "\n",
+       "    var fig = this;\n",
+       "\n",
+       "    this.waiting = false;\n",
+       "\n",
+       "    this.ws.onopen = function () {\n",
+       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+       "        fig.send_message('send_image_mode', {});\n",
+       "        if (fig.ratio !== 1) {\n",
+       "            fig.send_message('set_device_pixel_ratio', {\n",
+       "                device_pixel_ratio: fig.ratio,\n",
+       "            });\n",
+       "        }\n",
+       "        fig.send_message('refresh', {});\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onload = function () {\n",
+       "        if (fig.image_mode === 'full') {\n",
+       "            // Full images could contain transparency (where diff images\n",
+       "            // almost always do), so we need to clear the canvas so that\n",
+       "            // there is no ghosting.\n",
+       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+       "        }\n",
+       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onunload = function () {\n",
+       "        fig.ws.close();\n",
+       "    };\n",
+       "\n",
+       "    this.ws.onmessage = this._make_on_message_function(this);\n",
+       "\n",
+       "    this.ondownload = ondownload;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_header = function () {\n",
+       "    var titlebar = document.createElement('div');\n",
+       "    titlebar.classList =\n",
+       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+       "    var titletext = document.createElement('div');\n",
+       "    titletext.classList = 'ui-dialog-title';\n",
+       "    titletext.setAttribute(\n",
+       "        'style',\n",
+       "        'width: 100%; text-align: center; padding: 3px;'\n",
+       "    );\n",
+       "    titlebar.appendChild(titletext);\n",
+       "    this.root.appendChild(titlebar);\n",
+       "    this.header = titletext;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._init_canvas = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+       "    canvas_div.setAttribute(\n",
+       "        'style',\n",
+       "        'border: 1px solid #ddd;' +\n",
+       "            'box-sizing: content-box;' +\n",
+       "            'clear: both;' +\n",
+       "            'min-height: 1px;' +\n",
+       "            'min-width: 1px;' +\n",
+       "            'outline: 0;' +\n",
+       "            'overflow: hidden;' +\n",
+       "            'position: relative;' +\n",
+       "            'resize: both;'\n",
+       "    );\n",
+       "\n",
+       "    function on_keyboard_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.key_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keydown',\n",
+       "        on_keyboard_event_closure('key_press')\n",
+       "    );\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keyup',\n",
+       "        on_keyboard_event_closure('key_release')\n",
+       "    );\n",
+       "\n",
+       "    this._canvas_extra_style(canvas_div);\n",
+       "    this.root.appendChild(canvas_div);\n",
+       "\n",
+       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
+       "    canvas.classList.add('mpl-canvas');\n",
+       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+       "\n",
+       "    this.context = canvas.getContext('2d');\n",
+       "\n",
+       "    var backingStore =\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        this.context.webkitBackingStorePixelRatio ||\n",
+       "        this.context.mozBackingStorePixelRatio ||\n",
+       "        this.context.msBackingStorePixelRatio ||\n",
+       "        this.context.oBackingStorePixelRatio ||\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        1;\n",
+       "\n",
+       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+       "\n",
+       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+       "        'canvas'\n",
+       "    ));\n",
+       "    rubberband_canvas.setAttribute(\n",
+       "        'style',\n",
+       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+       "    );\n",
+       "\n",
+       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+       "    if (this.ResizeObserver === undefined) {\n",
+       "        if (window.ResizeObserver !== undefined) {\n",
+       "            this.ResizeObserver = window.ResizeObserver;\n",
+       "        } else {\n",
+       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+       "            this.ResizeObserver = obs.ResizeObserver;\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+       "        var nentries = entries.length;\n",
+       "        for (var i = 0; i < nentries; i++) {\n",
+       "            var entry = entries[i];\n",
+       "            var width, height;\n",
+       "            if (entry.contentBoxSize) {\n",
+       "                if (entry.contentBoxSize instanceof Array) {\n",
+       "                    // Chrome 84 implements new version of spec.\n",
+       "                    width = entry.contentBoxSize[0].inlineSize;\n",
+       "                    height = entry.contentBoxSize[0].blockSize;\n",
+       "                } else {\n",
+       "                    // Firefox implements old version of spec.\n",
+       "                    width = entry.contentBoxSize.inlineSize;\n",
+       "                    height = entry.contentBoxSize.blockSize;\n",
+       "                }\n",
+       "            } else {\n",
+       "                // Chrome <84 implements even older version of spec.\n",
+       "                width = entry.contentRect.width;\n",
+       "                height = entry.contentRect.height;\n",
+       "            }\n",
+       "\n",
+       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
+       "            // the canvas container.\n",
+       "            if (entry.devicePixelContentBoxSize) {\n",
+       "                // Chrome 84 implements new version of spec.\n",
+       "                canvas.setAttribute(\n",
+       "                    'width',\n",
+       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
+       "                );\n",
+       "                canvas.setAttribute(\n",
+       "                    'height',\n",
+       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
+       "                );\n",
+       "            } else {\n",
+       "                canvas.setAttribute('width', width * fig.ratio);\n",
+       "                canvas.setAttribute('height', height * fig.ratio);\n",
+       "            }\n",
+       "            canvas.setAttribute(\n",
+       "                'style',\n",
+       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
+       "            );\n",
+       "\n",
+       "            rubberband_canvas.setAttribute('width', width);\n",
+       "            rubberband_canvas.setAttribute('height', height);\n",
+       "\n",
+       "            // And update the size in Python. We ignore the initial 0/0 size\n",
+       "            // that occurs as the element is placed into the DOM, which should\n",
+       "            // otherwise not happen due to the minimum size styling.\n",
+       "            if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+       "                fig.request_resize(width, height);\n",
+       "            }\n",
+       "        }\n",
+       "    });\n",
+       "    this.resizeObserverInstance.observe(canvas_div);\n",
+       "\n",
+       "    function on_mouse_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.mouse_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousedown',\n",
+       "        on_mouse_event_closure('button_press')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseup',\n",
+       "        on_mouse_event_closure('button_release')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'dblclick',\n",
+       "        on_mouse_event_closure('dblclick')\n",
+       "    );\n",
+       "    // Throttle sequential mouse events to 1 every 20ms.\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousemove',\n",
+       "        on_mouse_event_closure('motion_notify')\n",
+       "    );\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseenter',\n",
+       "        on_mouse_event_closure('figure_enter')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseleave',\n",
+       "        on_mouse_event_closure('figure_leave')\n",
+       "    );\n",
+       "\n",
+       "    canvas_div.addEventListener('wheel', function (event) {\n",
+       "        if (event.deltaY < 0) {\n",
+       "            event.step = 1;\n",
+       "        } else {\n",
+       "            event.step = -1;\n",
+       "        }\n",
+       "        on_mouse_event_closure('scroll')(event);\n",
+       "    });\n",
+       "\n",
+       "    canvas_div.appendChild(canvas);\n",
+       "    canvas_div.appendChild(rubberband_canvas);\n",
+       "\n",
+       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+       "    this.rubberband_context.strokeStyle = '#000000';\n",
+       "\n",
+       "    this._resize_canvas = function (width, height, forward) {\n",
+       "        if (forward) {\n",
+       "            canvas_div.style.width = width + 'px';\n",
+       "            canvas_div.style.height = height + 'px';\n",
+       "        }\n",
+       "    };\n",
+       "\n",
+       "    // Disable right mouse context menu.\n",
+       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+       "        event.preventDefault();\n",
+       "        return false;\n",
+       "    });\n",
+       "\n",
+       "    function set_focus() {\n",
+       "        canvas.focus();\n",
+       "        canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    window.setTimeout(set_focus, 100);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'mpl-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'mpl-button-group';\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'mpl-button-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
+       "        button.classList = 'mpl-widget';\n",
+       "        button.setAttribute('role', 'button');\n",
+       "        button.setAttribute('aria-disabled', 'false');\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "\n",
+       "        var icon_img = document.createElement('img');\n",
+       "        icon_img.src = '_images/' + image + '.png';\n",
+       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+       "        icon_img.alt = tooltip;\n",
+       "        button.appendChild(icon_img);\n",
+       "\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    var fmt_picker = document.createElement('select');\n",
+       "    fmt_picker.classList = 'mpl-widget';\n",
+       "    toolbar.appendChild(fmt_picker);\n",
+       "    this.format_dropdown = fmt_picker;\n",
+       "\n",
+       "    for (var ind in mpl.extensions) {\n",
+       "        var fmt = mpl.extensions[ind];\n",
+       "        var option = document.createElement('option');\n",
+       "        option.selected = fmt === mpl.default_extension;\n",
+       "        option.innerHTML = fmt;\n",
+       "        fmt_picker.appendChild(option);\n",
+       "    }\n",
+       "\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+       "    // which will in turn request a refresh of the image.\n",
+       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_message = function (type, properties) {\n",
+       "    properties['type'] = type;\n",
+       "    properties['figure_id'] = this.id;\n",
+       "    this.ws.send(JSON.stringify(properties));\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_draw_message = function () {\n",
+       "    if (!this.waiting) {\n",
+       "        this.waiting = true;\n",
+       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    var format_dropdown = fig.format_dropdown;\n",
+       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+       "    fig.ondownload(fig, format);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+       "    var size = msg['size'];\n",
+       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+       "        fig.send_message('refresh', {});\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+       "    var x0 = msg['x0'] / fig.ratio;\n",
+       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+       "    var x1 = msg['x1'] / fig.ratio;\n",
+       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+       "    x0 = Math.floor(x0) + 0.5;\n",
+       "    y0 = Math.floor(y0) + 0.5;\n",
+       "    x1 = Math.floor(x1) + 0.5;\n",
+       "    y1 = Math.floor(y1) + 0.5;\n",
+       "    var min_x = Math.min(x0, x1);\n",
+       "    var min_y = Math.min(y0, y1);\n",
+       "    var width = Math.abs(x1 - x0);\n",
+       "    var height = Math.abs(y1 - y0);\n",
+       "\n",
+       "    fig.rubberband_context.clearRect(\n",
+       "        0,\n",
+       "        0,\n",
+       "        fig.canvas.width / fig.ratio,\n",
+       "        fig.canvas.height / fig.ratio\n",
+       "    );\n",
+       "\n",
+       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+       "    // Updates the figure title.\n",
+       "    fig.header.textContent = msg['label'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+       "    fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+       "    fig.message.textContent = msg['message'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+       "    // Request the server to send over a new figure.\n",
+       "    fig.send_draw_message();\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+       "    fig.image_mode = msg['mode'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+       "    for (var key in msg) {\n",
+       "        if (!(key in fig.buttons)) {\n",
+       "            continue;\n",
+       "        }\n",
+       "        fig.buttons[key].disabled = !msg[key];\n",
+       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+       "    if (msg['mode'] === 'PAN') {\n",
+       "        fig.buttons['Pan'].classList.add('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    } else if (msg['mode'] === 'ZOOM') {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.add('active');\n",
+       "    } else {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Called whenever the canvas gets updated.\n",
+       "    this.send_message('ack', {});\n",
+       "};\n",
+       "\n",
+       "// A function to construct a web socket function for onmessage handling.\n",
+       "// Called in the figure constructor.\n",
+       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+       "    return function socket_on_message(evt) {\n",
+       "        if (evt.data instanceof Blob) {\n",
+       "            var img = evt.data;\n",
+       "            if (img.type !== 'image/png') {\n",
+       "                /* FIXME: We get \"Resource interpreted as Image but\n",
+       "                 * transferred with MIME type text/plain:\" errors on\n",
+       "                 * Chrome.  But how to set the MIME type?  It doesn't seem\n",
+       "                 * to be part of the websocket stream */\n",
+       "                img.type = 'image/png';\n",
+       "            }\n",
+       "\n",
+       "            /* Free the memory for the previous frames */\n",
+       "            if (fig.imageObj.src) {\n",
+       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
+       "                    fig.imageObj.src\n",
+       "                );\n",
+       "            }\n",
+       "\n",
+       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+       "                img\n",
+       "            );\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        } else if (\n",
+       "            typeof evt.data === 'string' &&\n",
+       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+       "        ) {\n",
+       "            fig.imageObj.src = evt.data;\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        var msg = JSON.parse(evt.data);\n",
+       "        var msg_type = msg['type'];\n",
+       "\n",
+       "        // Call the  \"handle_{type}\" callback, which takes\n",
+       "        // the figure and JSON message as its only arguments.\n",
+       "        try {\n",
+       "            var callback = fig['handle_' + msg_type];\n",
+       "        } catch (e) {\n",
+       "            console.log(\n",
+       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
+       "                msg\n",
+       "            );\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        if (callback) {\n",
+       "            try {\n",
+       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+       "                callback(fig, msg);\n",
+       "            } catch (e) {\n",
+       "                console.log(\n",
+       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+       "                    e,\n",
+       "                    e.stack,\n",
+       "                    msg\n",
+       "                );\n",
+       "            }\n",
+       "        }\n",
+       "    };\n",
+       "};\n",
+       "\n",
+       "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+       "mpl.findpos = function (e) {\n",
+       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+       "    var targ;\n",
+       "    if (!e) {\n",
+       "        e = window.event;\n",
+       "    }\n",
+       "    if (e.target) {\n",
+       "        targ = e.target;\n",
+       "    } else if (e.srcElement) {\n",
+       "        targ = e.srcElement;\n",
+       "    }\n",
+       "    if (targ.nodeType === 3) {\n",
+       "        // defeat Safari bug\n",
+       "        targ = targ.parentNode;\n",
+       "    }\n",
+       "\n",
+       "    // pageX,Y are the mouse positions relative to the document\n",
+       "    var boundingRect = targ.getBoundingClientRect();\n",
+       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+       "\n",
+       "    return { x: x, y: y };\n",
+       "};\n",
+       "\n",
+       "/*\n",
+       " * return a copy of an object with only non-object keys\n",
+       " * we need this to avoid circular references\n",
+       " * https://stackoverflow.com/a/24161582/3208463\n",
+       " */\n",
+       "function simpleKeys(original) {\n",
+       "    return Object.keys(original).reduce(function (obj, key) {\n",
+       "        if (typeof original[key] !== 'object') {\n",
+       "            obj[key] = original[key];\n",
+       "        }\n",
+       "        return obj;\n",
+       "    }, {});\n",
+       "}\n",
+       "\n",
+       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+       "    var canvas_pos = mpl.findpos(event);\n",
+       "\n",
+       "    if (name === 'button_press') {\n",
+       "        this.canvas.focus();\n",
+       "        this.canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    var x = canvas_pos.x * this.ratio;\n",
+       "    var y = canvas_pos.y * this.ratio;\n",
+       "\n",
+       "    this.send_message(name, {\n",
+       "        x: x,\n",
+       "        y: y,\n",
+       "        button: event.button,\n",
+       "        step: event.step,\n",
+       "        guiEvent: simpleKeys(event),\n",
+       "    });\n",
+       "\n",
+       "    /* This prevents the web browser from automatically changing to\n",
+       "     * the text insertion cursor when the button is pressed.  We want\n",
+       "     * to control all of the cursor setting manually through the\n",
+       "     * 'cursor' event from matplotlib */\n",
+       "    event.preventDefault();\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+       "    // Handle any extra behaviour associated with a key event\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.key_event = function (event, name) {\n",
+       "    // Prevent repeat events\n",
+       "    if (name === 'key_press') {\n",
+       "        if (event.key === this._key) {\n",
+       "            return;\n",
+       "        } else {\n",
+       "            this._key = event.key;\n",
+       "        }\n",
+       "    }\n",
+       "    if (name === 'key_release') {\n",
+       "        this._key = null;\n",
+       "    }\n",
+       "\n",
+       "    var value = '';\n",
+       "    if (event.ctrlKey && event.key !== 'Control') {\n",
+       "        value += 'ctrl+';\n",
+       "    }\n",
+       "    else if (event.altKey && event.key !== 'Alt') {\n",
+       "        value += 'alt+';\n",
+       "    }\n",
+       "    else if (event.shiftKey && event.key !== 'Shift') {\n",
+       "        value += 'shift+';\n",
+       "    }\n",
+       "\n",
+       "    value += 'k' + event.key;\n",
+       "\n",
+       "    this._key_event_extra(event, name);\n",
+       "\n",
+       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+       "    if (name === 'download') {\n",
+       "        this.handle_save(this, null);\n",
+       "    } else {\n",
+       "        this.send_message('toolbar_button', { name: name });\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+       "    this.message.textContent = tooltip;\n",
+       "};\n",
+       "\n",
+       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+       "// prettier-ignore\n",
+       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+       "\n",
+       "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+       "\n",
+       "mpl.default_extension = \"png\";/* global mpl */\n",
+       "\n",
+       "var comm_websocket_adapter = function (comm) {\n",
+       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
+       "    // object with the appropriate methods. Currently this is a non binary\n",
+       "    // socket, so there is still some room for performance tuning.\n",
+       "    var ws = {};\n",
+       "\n",
+       "    ws.binaryType = comm.kernel.ws.binaryType;\n",
+       "    ws.readyState = comm.kernel.ws.readyState;\n",
+       "    function updateReadyState(_event) {\n",
+       "        if (comm.kernel.ws) {\n",
+       "            ws.readyState = comm.kernel.ws.readyState;\n",
+       "        } else {\n",
+       "            ws.readyState = 3; // Closed state.\n",
+       "        }\n",
+       "    }\n",
+       "    comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+       "\n",
+       "    ws.close = function () {\n",
+       "        comm.close();\n",
+       "    };\n",
+       "    ws.send = function (m) {\n",
+       "        //console.log('sending', m);\n",
+       "        comm.send(m);\n",
+       "    };\n",
+       "    // Register the callback with on_msg.\n",
+       "    comm.on_msg(function (msg) {\n",
+       "        //console.log('receiving', msg['content']['data'], msg);\n",
+       "        var data = msg['content']['data'];\n",
+       "        if (data['blob'] !== undefined) {\n",
+       "            data = {\n",
+       "                data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+       "            };\n",
+       "        }\n",
+       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+       "        ws.onmessage(data);\n",
+       "    });\n",
+       "    return ws;\n",
+       "};\n",
+       "\n",
+       "mpl.mpl_figure_comm = function (comm, msg) {\n",
+       "    // This is the function which gets called when the mpl process\n",
+       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+       "\n",
+       "    var id = msg.content.data.id;\n",
+       "    // Get hold of the div created by the display call when the Comm\n",
+       "    // socket was opened in Python.\n",
+       "    var element = document.getElementById(id);\n",
+       "    var ws_proxy = comm_websocket_adapter(comm);\n",
+       "\n",
+       "    function ondownload(figure, _format) {\n",
+       "        window.open(figure.canvas.toDataURL());\n",
+       "    }\n",
+       "\n",
+       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+       "\n",
+       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+       "    // web socket which is closed, not our websocket->open comm proxy.\n",
+       "    ws_proxy.onopen();\n",
+       "\n",
+       "    fig.parent_element = element;\n",
+       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
+       "    if (!fig.cell_info) {\n",
+       "        console.error('Failed to find cell for figure', id, fig);\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.cell_info[0].output_area.element.on(\n",
+       "        'cleared',\n",
+       "        { fig: fig },\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+       "    var width = fig.canvas.width / fig.ratio;\n",
+       "    fig.cell_info[0].output_area.element.off(\n",
+       "        'cleared',\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+       "\n",
+       "    // Update the output cell to use the data from the current canvas.\n",
+       "    fig.push_to_output();\n",
+       "    var dataURL = fig.canvas.toDataURL();\n",
+       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+       "    // the notebook keyboard shortcuts fail.\n",
+       "    IPython.keyboard_manager.enable();\n",
+       "    fig.parent_element.innerHTML =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "    fig.close_ws(fig, msg);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+       "    fig.send_message('closing', msg);\n",
+       "    // fig.ws.close()\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+       "    // Turn the data on the canvas into data in the output cell.\n",
+       "    var width = this.canvas.width / this.ratio;\n",
+       "    var dataURL = this.canvas.toDataURL();\n",
+       "    this.cell_info[1]['text/html'] =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Tell IPython that the notebook contents must change.\n",
+       "    IPython.notebook.set_dirty(true);\n",
+       "    this.send_message('ack', {});\n",
+       "    var fig = this;\n",
+       "    // Wait a second, then push the new image to the DOM so\n",
+       "    // that it is saved nicely (might be nice to debounce this).\n",
+       "    setTimeout(function () {\n",
+       "        fig.push_to_output();\n",
+       "    }, 1000);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'btn-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'btn-group';\n",
+       "    var button;\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'btn-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        button = fig.buttons[name] = document.createElement('button');\n",
+       "        button.classList = 'btn btn-default';\n",
+       "        button.href = '#';\n",
+       "        button.title = name;\n",
+       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    // Add the status bar.\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message pull-right';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "\n",
+       "    // Add the close button to the window.\n",
+       "    var buttongrp = document.createElement('div');\n",
+       "    buttongrp.classList = 'btn-group inline pull-right';\n",
+       "    button = document.createElement('button');\n",
+       "    button.classList = 'btn btn-mini btn-primary';\n",
+       "    button.href = '#';\n",
+       "    button.title = 'Stop Interaction';\n",
+       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
+       "    button.addEventListener('click', function (_evt) {\n",
+       "        fig.handle_close(fig, {});\n",
+       "    });\n",
+       "    button.addEventListener(\n",
+       "        'mouseover',\n",
+       "        on_mouseover_closure('Stop Interaction')\n",
+       "    );\n",
+       "    buttongrp.appendChild(button);\n",
+       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+       "    var fig = event.data.fig;\n",
+       "    if (event.target !== this) {\n",
+       "        // Ignore bubbled events from children.\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.close_ws(fig, {});\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (el) {\n",
+       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+       "    // this is important to make the div 'focusable\n",
+       "    el.setAttribute('tabindex', 0);\n",
+       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
+       "    // off when our div gets focus\n",
+       "\n",
+       "    // location in version 3\n",
+       "    if (IPython.notebook.keyboard_manager) {\n",
+       "        IPython.notebook.keyboard_manager.register_events(el);\n",
+       "    } else {\n",
+       "        // location in version 2\n",
+       "        IPython.keyboard_manager.register_events(el);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+       "    // Check for shift+enter\n",
+       "    if (event.shiftKey && event.which === 13) {\n",
+       "        this.canvas_div.blur();\n",
+       "        // select the cell after this one\n",
+       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+       "        IPython.notebook.select(index + 1);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    fig.ondownload(fig, null);\n",
+       "};\n",
+       "\n",
+       "mpl.find_output_cell = function (html_output) {\n",
+       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+       "    // IPython event is triggered only after the cells have been serialised, which for\n",
+       "    // our purposes (turning an active figure into a static one), is too late.\n",
+       "    var cells = IPython.notebook.get_cells();\n",
+       "    var ncells = cells.length;\n",
+       "    for (var i = 0; i < ncells; i++) {\n",
+       "        var cell = cells[i];\n",
+       "        if (cell.cell_type === 'code') {\n",
+       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+       "                var data = cell.output_area.outputs[j];\n",
+       "                if (data.data) {\n",
+       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
+       "                    data = data.data;\n",
+       "                }\n",
+       "                if (data['text/html'] === html_output) {\n",
+       "                    return [cell, data, j];\n",
+       "                }\n",
+       "            }\n",
+       "        }\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "// Register the function which deals with the matplotlib target/channel.\n",
+       "// The kernel may be null if the page has been refreshed.\n",
+       "if (IPython.notebook.kernel !== null) {\n",
+       "    IPython.notebook.kernel.comm_manager.register_target(\n",
+       "        'matplotlib',\n",
+       "        mpl.mpl_figure_comm\n",
+       "    );\n",
+       "}\n"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Javascript object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAgAElEQVR4Xu3dMWoWUQCFUQI21kHs3INoZ5/OZQT+NdhZiqWQVYiduAsbXUA2oC5BF2ATApcPZk4t3EJu4p3z3oxXT1//fv/p/ub27d8Xt89/ffn68sPPy5+7V9c3H7/fX749effmx7PP/tzfj374+fD7we9H/z7499E+OM4+ujIADVwD38A38A18A9/AN/DPNfANQMJJeAm4EwAnIE6AnIA5ATzZCagBaAAagAagAWgAGoAGoAFoAJ6LQN1xdATuCNwRuCNwR+COwB2Bn+0InAASQAJIAAkgASSABJAAEkAC6C2n47zlRHgJL+ElvISX8BLe/4WXABJAAkgACSABJIAEkAASQAJIAAmg72D6DqjvoPoOrBMEJwhHPkEggASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCQkgICSEhJITkZEJiABqABqABaAAagAagAWgAGoAXJ0ROiI5yQuQOoDuA7gB6wPGA4wHHA44HHA84J3vAMQANQAPQADQADUAD0AA0AA1AL4EgfsR/FOJ3h8kdpiPfYdJv/dbvx3/onQASQAJIAAkgASSABJAAEkACSAAJIAH0GRifgfEZGMJIGI8sjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSEgICSEhJISEkJxMSAxAA9AANAANQAPQADQADUAD0Ieg75wQHeWEyB1AdwDdAfSA4wHHA44HHA84HnBO9oBjABqABqABaAAagAagAWgAGoBeAvESCOI/CvG7w+QO05HvMOm3fuu3D0ETDIJBMAgGwTiZYBiABqABaAAagAagAWgAGoAGoJc0vKRx7QTnYZ/xcgfQHUB3AN0B9ADlAcoDlAcoD1Ane4AyAA1AA9AANAANQAPQADQADUAvgXgJxEsgjhAedoTgDpY7WO5gPf4Olp8fPz/lzw8BJIAEkAASQAJIAAkgASSABJAAEkACSAD9X8D+L2BCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARusGMgCQAAAGwSURBVFLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb9+8fmsNM4ap+kCIAAAAASUVORK5CYII=\" width=\"640\">"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<AxesSubplot:title={'center':'line mesh'}>"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from nanomesh import Mesher2D\n",
+    "\n",
+    "mesher = Mesher2D(plane)\n",
+    "mesher.generate_contour(max_contour_dist = 3)\n",
+    "\n",
+    "mesher.plot_contour()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The next step is to use the contours to initialize triangulation.\n",
+    "\n",
+    "Triangulation options can be specified through the `opts` keyword argument. This example uses `q30` to generate a quality mesh with angles > 30°, and `a100` to set a maximum triangle size of 100 pixels. For more options, see [here](https://rufat.be/triangle/API.html#triangle.triangulate)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mesh = mesher.triangulate(opts='q30a100')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Triangulation returns a `MeshContainer` dataclass that can be used for various operations, for example comparing it with the original image:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "/* Put everything inside the global mpl namespace */\n",
+       "/* global mpl */\n",
+       "window.mpl = {};\n",
+       "\n",
+       "mpl.get_websocket_type = function () {\n",
+       "    if (typeof WebSocket !== 'undefined') {\n",
+       "        return WebSocket;\n",
+       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
+       "        return MozWebSocket;\n",
+       "    } else {\n",
+       "        alert(\n",
+       "            'Your browser does not have WebSocket support. ' +\n",
+       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+       "                'Firefox 4 and 5 are also supported but you ' +\n",
+       "                'have to enable WebSockets in about:config.'\n",
+       "        );\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+       "    this.id = figure_id;\n",
+       "\n",
+       "    this.ws = websocket;\n",
+       "\n",
+       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
+       "\n",
+       "    if (!this.supports_binary) {\n",
+       "        var warnings = document.getElementById('mpl-warnings');\n",
+       "        if (warnings) {\n",
+       "            warnings.style.display = 'block';\n",
+       "            warnings.textContent =\n",
+       "                'This browser does not support binary websocket messages. ' +\n",
+       "                'Performance may be slow.';\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.imageObj = new Image();\n",
+       "\n",
+       "    this.context = undefined;\n",
+       "    this.message = undefined;\n",
+       "    this.canvas = undefined;\n",
+       "    this.rubberband_canvas = undefined;\n",
+       "    this.rubberband_context = undefined;\n",
+       "    this.format_dropdown = undefined;\n",
+       "\n",
+       "    this.image_mode = 'full';\n",
+       "\n",
+       "    this.root = document.createElement('div');\n",
+       "    this.root.setAttribute('style', 'display: inline-block');\n",
+       "    this._root_extra_style(this.root);\n",
+       "\n",
+       "    parent_element.appendChild(this.root);\n",
+       "\n",
+       "    this._init_header(this);\n",
+       "    this._init_canvas(this);\n",
+       "    this._init_toolbar(this);\n",
+       "\n",
+       "    var fig = this;\n",
+       "\n",
+       "    this.waiting = false;\n",
+       "\n",
+       "    this.ws.onopen = function () {\n",
+       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+       "        fig.send_message('send_image_mode', {});\n",
+       "        if (fig.ratio !== 1) {\n",
+       "            fig.send_message('set_device_pixel_ratio', {\n",
+       "                device_pixel_ratio: fig.ratio,\n",
+       "            });\n",
+       "        }\n",
+       "        fig.send_message('refresh', {});\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onload = function () {\n",
+       "        if (fig.image_mode === 'full') {\n",
+       "            // Full images could contain transparency (where diff images\n",
+       "            // almost always do), so we need to clear the canvas so that\n",
+       "            // there is no ghosting.\n",
+       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+       "        }\n",
+       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onunload = function () {\n",
+       "        fig.ws.close();\n",
+       "    };\n",
+       "\n",
+       "    this.ws.onmessage = this._make_on_message_function(this);\n",
+       "\n",
+       "    this.ondownload = ondownload;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_header = function () {\n",
+       "    var titlebar = document.createElement('div');\n",
+       "    titlebar.classList =\n",
+       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+       "    var titletext = document.createElement('div');\n",
+       "    titletext.classList = 'ui-dialog-title';\n",
+       "    titletext.setAttribute(\n",
+       "        'style',\n",
+       "        'width: 100%; text-align: center; padding: 3px;'\n",
+       "    );\n",
+       "    titlebar.appendChild(titletext);\n",
+       "    this.root.appendChild(titlebar);\n",
+       "    this.header = titletext;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._init_canvas = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+       "    canvas_div.setAttribute(\n",
+       "        'style',\n",
+       "        'border: 1px solid #ddd;' +\n",
+       "            'box-sizing: content-box;' +\n",
+       "            'clear: both;' +\n",
+       "            'min-height: 1px;' +\n",
+       "            'min-width: 1px;' +\n",
+       "            'outline: 0;' +\n",
+       "            'overflow: hidden;' +\n",
+       "            'position: relative;' +\n",
+       "            'resize: both;'\n",
+       "    );\n",
+       "\n",
+       "    function on_keyboard_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.key_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keydown',\n",
+       "        on_keyboard_event_closure('key_press')\n",
+       "    );\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keyup',\n",
+       "        on_keyboard_event_closure('key_release')\n",
+       "    );\n",
+       "\n",
+       "    this._canvas_extra_style(canvas_div);\n",
+       "    this.root.appendChild(canvas_div);\n",
+       "\n",
+       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
+       "    canvas.classList.add('mpl-canvas');\n",
+       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+       "\n",
+       "    this.context = canvas.getContext('2d');\n",
+       "\n",
+       "    var backingStore =\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        this.context.webkitBackingStorePixelRatio ||\n",
+       "        this.context.mozBackingStorePixelRatio ||\n",
+       "        this.context.msBackingStorePixelRatio ||\n",
+       "        this.context.oBackingStorePixelRatio ||\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        1;\n",
+       "\n",
+       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+       "\n",
+       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+       "        'canvas'\n",
+       "    ));\n",
+       "    rubberband_canvas.setAttribute(\n",
+       "        'style',\n",
+       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+       "    );\n",
+       "\n",
+       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+       "    if (this.ResizeObserver === undefined) {\n",
+       "        if (window.ResizeObserver !== undefined) {\n",
+       "            this.ResizeObserver = window.ResizeObserver;\n",
+       "        } else {\n",
+       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+       "            this.ResizeObserver = obs.ResizeObserver;\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+       "        var nentries = entries.length;\n",
+       "        for (var i = 0; i < nentries; i++) {\n",
+       "            var entry = entries[i];\n",
+       "            var width, height;\n",
+       "            if (entry.contentBoxSize) {\n",
+       "                if (entry.contentBoxSize instanceof Array) {\n",
+       "                    // Chrome 84 implements new version of spec.\n",
+       "                    width = entry.contentBoxSize[0].inlineSize;\n",
+       "                    height = entry.contentBoxSize[0].blockSize;\n",
+       "                } else {\n",
+       "                    // Firefox implements old version of spec.\n",
+       "                    width = entry.contentBoxSize.inlineSize;\n",
+       "                    height = entry.contentBoxSize.blockSize;\n",
+       "                }\n",
+       "            } else {\n",
+       "                // Chrome <84 implements even older version of spec.\n",
+       "                width = entry.contentRect.width;\n",
+       "                height = entry.contentRect.height;\n",
+       "            }\n",
+       "\n",
+       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
+       "            // the canvas container.\n",
+       "            if (entry.devicePixelContentBoxSize) {\n",
+       "                // Chrome 84 implements new version of spec.\n",
+       "                canvas.setAttribute(\n",
+       "                    'width',\n",
+       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
+       "                );\n",
+       "                canvas.setAttribute(\n",
+       "                    'height',\n",
+       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
+       "                );\n",
+       "            } else {\n",
+       "                canvas.setAttribute('width', width * fig.ratio);\n",
+       "                canvas.setAttribute('height', height * fig.ratio);\n",
+       "            }\n",
+       "            canvas.setAttribute(\n",
+       "                'style',\n",
+       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
+       "            );\n",
+       "\n",
+       "            rubberband_canvas.setAttribute('width', width);\n",
+       "            rubberband_canvas.setAttribute('height', height);\n",
+       "\n",
+       "            // And update the size in Python. We ignore the initial 0/0 size\n",
+       "            // that occurs as the element is placed into the DOM, which should\n",
+       "            // otherwise not happen due to the minimum size styling.\n",
+       "            if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+       "                fig.request_resize(width, height);\n",
+       "            }\n",
+       "        }\n",
+       "    });\n",
+       "    this.resizeObserverInstance.observe(canvas_div);\n",
+       "\n",
+       "    function on_mouse_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.mouse_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousedown',\n",
+       "        on_mouse_event_closure('button_press')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseup',\n",
+       "        on_mouse_event_closure('button_release')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'dblclick',\n",
+       "        on_mouse_event_closure('dblclick')\n",
+       "    );\n",
+       "    // Throttle sequential mouse events to 1 every 20ms.\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousemove',\n",
+       "        on_mouse_event_closure('motion_notify')\n",
+       "    );\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseenter',\n",
+       "        on_mouse_event_closure('figure_enter')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseleave',\n",
+       "        on_mouse_event_closure('figure_leave')\n",
+       "    );\n",
+       "\n",
+       "    canvas_div.addEventListener('wheel', function (event) {\n",
+       "        if (event.deltaY < 0) {\n",
+       "            event.step = 1;\n",
+       "        } else {\n",
+       "            event.step = -1;\n",
+       "        }\n",
+       "        on_mouse_event_closure('scroll')(event);\n",
+       "    });\n",
+       "\n",
+       "    canvas_div.appendChild(canvas);\n",
+       "    canvas_div.appendChild(rubberband_canvas);\n",
+       "\n",
+       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+       "    this.rubberband_context.strokeStyle = '#000000';\n",
+       "\n",
+       "    this._resize_canvas = function (width, height, forward) {\n",
+       "        if (forward) {\n",
+       "            canvas_div.style.width = width + 'px';\n",
+       "            canvas_div.style.height = height + 'px';\n",
+       "        }\n",
+       "    };\n",
+       "\n",
+       "    // Disable right mouse context menu.\n",
+       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+       "        event.preventDefault();\n",
+       "        return false;\n",
+       "    });\n",
+       "\n",
+       "    function set_focus() {\n",
+       "        canvas.focus();\n",
+       "        canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    window.setTimeout(set_focus, 100);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'mpl-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'mpl-button-group';\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'mpl-button-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
+       "        button.classList = 'mpl-widget';\n",
+       "        button.setAttribute('role', 'button');\n",
+       "        button.setAttribute('aria-disabled', 'false');\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "\n",
+       "        var icon_img = document.createElement('img');\n",
+       "        icon_img.src = '_images/' + image + '.png';\n",
+       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+       "        icon_img.alt = tooltip;\n",
+       "        button.appendChild(icon_img);\n",
+       "\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    var fmt_picker = document.createElement('select');\n",
+       "    fmt_picker.classList = 'mpl-widget';\n",
+       "    toolbar.appendChild(fmt_picker);\n",
+       "    this.format_dropdown = fmt_picker;\n",
+       "\n",
+       "    for (var ind in mpl.extensions) {\n",
+       "        var fmt = mpl.extensions[ind];\n",
+       "        var option = document.createElement('option');\n",
+       "        option.selected = fmt === mpl.default_extension;\n",
+       "        option.innerHTML = fmt;\n",
+       "        fmt_picker.appendChild(option);\n",
+       "    }\n",
+       "\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+       "    // which will in turn request a refresh of the image.\n",
+       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_message = function (type, properties) {\n",
+       "    properties['type'] = type;\n",
+       "    properties['figure_id'] = this.id;\n",
+       "    this.ws.send(JSON.stringify(properties));\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_draw_message = function () {\n",
+       "    if (!this.waiting) {\n",
+       "        this.waiting = true;\n",
+       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    var format_dropdown = fig.format_dropdown;\n",
+       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+       "    fig.ondownload(fig, format);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+       "    var size = msg['size'];\n",
+       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+       "        fig.send_message('refresh', {});\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+       "    var x0 = msg['x0'] / fig.ratio;\n",
+       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+       "    var x1 = msg['x1'] / fig.ratio;\n",
+       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+       "    x0 = Math.floor(x0) + 0.5;\n",
+       "    y0 = Math.floor(y0) + 0.5;\n",
+       "    x1 = Math.floor(x1) + 0.5;\n",
+       "    y1 = Math.floor(y1) + 0.5;\n",
+       "    var min_x = Math.min(x0, x1);\n",
+       "    var min_y = Math.min(y0, y1);\n",
+       "    var width = Math.abs(x1 - x0);\n",
+       "    var height = Math.abs(y1 - y0);\n",
+       "\n",
+       "    fig.rubberband_context.clearRect(\n",
+       "        0,\n",
+       "        0,\n",
+       "        fig.canvas.width / fig.ratio,\n",
+       "        fig.canvas.height / fig.ratio\n",
+       "    );\n",
+       "\n",
+       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+       "    // Updates the figure title.\n",
+       "    fig.header.textContent = msg['label'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+       "    fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+       "    fig.message.textContent = msg['message'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+       "    // Request the server to send over a new figure.\n",
+       "    fig.send_draw_message();\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+       "    fig.image_mode = msg['mode'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+       "    for (var key in msg) {\n",
+       "        if (!(key in fig.buttons)) {\n",
+       "            continue;\n",
+       "        }\n",
+       "        fig.buttons[key].disabled = !msg[key];\n",
+       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+       "    if (msg['mode'] === 'PAN') {\n",
+       "        fig.buttons['Pan'].classList.add('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    } else if (msg['mode'] === 'ZOOM') {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.add('active');\n",
+       "    } else {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Called whenever the canvas gets updated.\n",
+       "    this.send_message('ack', {});\n",
+       "};\n",
+       "\n",
+       "// A function to construct a web socket function for onmessage handling.\n",
+       "// Called in the figure constructor.\n",
+       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+       "    return function socket_on_message(evt) {\n",
+       "        if (evt.data instanceof Blob) {\n",
+       "            var img = evt.data;\n",
+       "            if (img.type !== 'image/png') {\n",
+       "                /* FIXME: We get \"Resource interpreted as Image but\n",
+       "                 * transferred with MIME type text/plain:\" errors on\n",
+       "                 * Chrome.  But how to set the MIME type?  It doesn't seem\n",
+       "                 * to be part of the websocket stream */\n",
+       "                img.type = 'image/png';\n",
+       "            }\n",
+       "\n",
+       "            /* Free the memory for the previous frames */\n",
+       "            if (fig.imageObj.src) {\n",
+       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
+       "                    fig.imageObj.src\n",
+       "                );\n",
+       "            }\n",
+       "\n",
+       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+       "                img\n",
+       "            );\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        } else if (\n",
+       "            typeof evt.data === 'string' &&\n",
+       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+       "        ) {\n",
+       "            fig.imageObj.src = evt.data;\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        var msg = JSON.parse(evt.data);\n",
+       "        var msg_type = msg['type'];\n",
+       "\n",
+       "        // Call the  \"handle_{type}\" callback, which takes\n",
+       "        // the figure and JSON message as its only arguments.\n",
+       "        try {\n",
+       "            var callback = fig['handle_' + msg_type];\n",
+       "        } catch (e) {\n",
+       "            console.log(\n",
+       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
+       "                msg\n",
+       "            );\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        if (callback) {\n",
+       "            try {\n",
+       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+       "                callback(fig, msg);\n",
+       "            } catch (e) {\n",
+       "                console.log(\n",
+       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+       "                    e,\n",
+       "                    e.stack,\n",
+       "                    msg\n",
+       "                );\n",
+       "            }\n",
+       "        }\n",
+       "    };\n",
+       "};\n",
+       "\n",
+       "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+       "mpl.findpos = function (e) {\n",
+       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+       "    var targ;\n",
+       "    if (!e) {\n",
+       "        e = window.event;\n",
+       "    }\n",
+       "    if (e.target) {\n",
+       "        targ = e.target;\n",
+       "    } else if (e.srcElement) {\n",
+       "        targ = e.srcElement;\n",
+       "    }\n",
+       "    if (targ.nodeType === 3) {\n",
+       "        // defeat Safari bug\n",
+       "        targ = targ.parentNode;\n",
+       "    }\n",
+       "\n",
+       "    // pageX,Y are the mouse positions relative to the document\n",
+       "    var boundingRect = targ.getBoundingClientRect();\n",
+       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+       "\n",
+       "    return { x: x, y: y };\n",
+       "};\n",
+       "\n",
+       "/*\n",
+       " * return a copy of an object with only non-object keys\n",
+       " * we need this to avoid circular references\n",
+       " * https://stackoverflow.com/a/24161582/3208463\n",
+       " */\n",
+       "function simpleKeys(original) {\n",
+       "    return Object.keys(original).reduce(function (obj, key) {\n",
+       "        if (typeof original[key] !== 'object') {\n",
+       "            obj[key] = original[key];\n",
+       "        }\n",
+       "        return obj;\n",
+       "    }, {});\n",
+       "}\n",
+       "\n",
+       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+       "    var canvas_pos = mpl.findpos(event);\n",
+       "\n",
+       "    if (name === 'button_press') {\n",
+       "        this.canvas.focus();\n",
+       "        this.canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    var x = canvas_pos.x * this.ratio;\n",
+       "    var y = canvas_pos.y * this.ratio;\n",
+       "\n",
+       "    this.send_message(name, {\n",
+       "        x: x,\n",
+       "        y: y,\n",
+       "        button: event.button,\n",
+       "        step: event.step,\n",
+       "        guiEvent: simpleKeys(event),\n",
+       "    });\n",
+       "\n",
+       "    /* This prevents the web browser from automatically changing to\n",
+       "     * the text insertion cursor when the button is pressed.  We want\n",
+       "     * to control all of the cursor setting manually through the\n",
+       "     * 'cursor' event from matplotlib */\n",
+       "    event.preventDefault();\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+       "    // Handle any extra behaviour associated with a key event\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.key_event = function (event, name) {\n",
+       "    // Prevent repeat events\n",
+       "    if (name === 'key_press') {\n",
+       "        if (event.key === this._key) {\n",
+       "            return;\n",
+       "        } else {\n",
+       "            this._key = event.key;\n",
+       "        }\n",
+       "    }\n",
+       "    if (name === 'key_release') {\n",
+       "        this._key = null;\n",
+       "    }\n",
+       "\n",
+       "    var value = '';\n",
+       "    if (event.ctrlKey && event.key !== 'Control') {\n",
+       "        value += 'ctrl+';\n",
+       "    }\n",
+       "    else if (event.altKey && event.key !== 'Alt') {\n",
+       "        value += 'alt+';\n",
+       "    }\n",
+       "    else if (event.shiftKey && event.key !== 'Shift') {\n",
+       "        value += 'shift+';\n",
+       "    }\n",
+       "\n",
+       "    value += 'k' + event.key;\n",
+       "\n",
+       "    this._key_event_extra(event, name);\n",
+       "\n",
+       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+       "    if (name === 'download') {\n",
+       "        this.handle_save(this, null);\n",
+       "    } else {\n",
+       "        this.send_message('toolbar_button', { name: name });\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+       "    this.message.textContent = tooltip;\n",
+       "};\n",
+       "\n",
+       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+       "// prettier-ignore\n",
+       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+       "\n",
+       "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+       "\n",
+       "mpl.default_extension = \"png\";/* global mpl */\n",
+       "\n",
+       "var comm_websocket_adapter = function (comm) {\n",
+       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
+       "    // object with the appropriate methods. Currently this is a non binary\n",
+       "    // socket, so there is still some room for performance tuning.\n",
+       "    var ws = {};\n",
+       "\n",
+       "    ws.binaryType = comm.kernel.ws.binaryType;\n",
+       "    ws.readyState = comm.kernel.ws.readyState;\n",
+       "    function updateReadyState(_event) {\n",
+       "        if (comm.kernel.ws) {\n",
+       "            ws.readyState = comm.kernel.ws.readyState;\n",
+       "        } else {\n",
+       "            ws.readyState = 3; // Closed state.\n",
+       "        }\n",
+       "    }\n",
+       "    comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+       "\n",
+       "    ws.close = function () {\n",
+       "        comm.close();\n",
+       "    };\n",
+       "    ws.send = function (m) {\n",
+       "        //console.log('sending', m);\n",
+       "        comm.send(m);\n",
+       "    };\n",
+       "    // Register the callback with on_msg.\n",
+       "    comm.on_msg(function (msg) {\n",
+       "        //console.log('receiving', msg['content']['data'], msg);\n",
+       "        var data = msg['content']['data'];\n",
+       "        if (data['blob'] !== undefined) {\n",
+       "            data = {\n",
+       "                data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+       "            };\n",
+       "        }\n",
+       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+       "        ws.onmessage(data);\n",
+       "    });\n",
+       "    return ws;\n",
+       "};\n",
+       "\n",
+       "mpl.mpl_figure_comm = function (comm, msg) {\n",
+       "    // This is the function which gets called when the mpl process\n",
+       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+       "\n",
+       "    var id = msg.content.data.id;\n",
+       "    // Get hold of the div created by the display call when the Comm\n",
+       "    // socket was opened in Python.\n",
+       "    var element = document.getElementById(id);\n",
+       "    var ws_proxy = comm_websocket_adapter(comm);\n",
+       "\n",
+       "    function ondownload(figure, _format) {\n",
+       "        window.open(figure.canvas.toDataURL());\n",
+       "    }\n",
+       "\n",
+       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+       "\n",
+       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+       "    // web socket which is closed, not our websocket->open comm proxy.\n",
+       "    ws_proxy.onopen();\n",
+       "\n",
+       "    fig.parent_element = element;\n",
+       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
+       "    if (!fig.cell_info) {\n",
+       "        console.error('Failed to find cell for figure', id, fig);\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.cell_info[0].output_area.element.on(\n",
+       "        'cleared',\n",
+       "        { fig: fig },\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+       "    var width = fig.canvas.width / fig.ratio;\n",
+       "    fig.cell_info[0].output_area.element.off(\n",
+       "        'cleared',\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+       "\n",
+       "    // Update the output cell to use the data from the current canvas.\n",
+       "    fig.push_to_output();\n",
+       "    var dataURL = fig.canvas.toDataURL();\n",
+       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+       "    // the notebook keyboard shortcuts fail.\n",
+       "    IPython.keyboard_manager.enable();\n",
+       "    fig.parent_element.innerHTML =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "    fig.close_ws(fig, msg);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+       "    fig.send_message('closing', msg);\n",
+       "    // fig.ws.close()\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+       "    // Turn the data on the canvas into data in the output cell.\n",
+       "    var width = this.canvas.width / this.ratio;\n",
+       "    var dataURL = this.canvas.toDataURL();\n",
+       "    this.cell_info[1]['text/html'] =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Tell IPython that the notebook contents must change.\n",
+       "    IPython.notebook.set_dirty(true);\n",
+       "    this.send_message('ack', {});\n",
+       "    var fig = this;\n",
+       "    // Wait a second, then push the new image to the DOM so\n",
+       "    // that it is saved nicely (might be nice to debounce this).\n",
+       "    setTimeout(function () {\n",
+       "        fig.push_to_output();\n",
+       "    }, 1000);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'btn-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'btn-group';\n",
+       "    var button;\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'btn-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        button = fig.buttons[name] = document.createElement('button');\n",
+       "        button.classList = 'btn btn-default';\n",
+       "        button.href = '#';\n",
+       "        button.title = name;\n",
+       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    // Add the status bar.\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message pull-right';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "\n",
+       "    // Add the close button to the window.\n",
+       "    var buttongrp = document.createElement('div');\n",
+       "    buttongrp.classList = 'btn-group inline pull-right';\n",
+       "    button = document.createElement('button');\n",
+       "    button.classList = 'btn btn-mini btn-primary';\n",
+       "    button.href = '#';\n",
+       "    button.title = 'Stop Interaction';\n",
+       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
+       "    button.addEventListener('click', function (_evt) {\n",
+       "        fig.handle_close(fig, {});\n",
+       "    });\n",
+       "    button.addEventListener(\n",
+       "        'mouseover',\n",
+       "        on_mouseover_closure('Stop Interaction')\n",
+       "    );\n",
+       "    buttongrp.appendChild(button);\n",
+       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+       "    var fig = event.data.fig;\n",
+       "    if (event.target !== this) {\n",
+       "        // Ignore bubbled events from children.\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.close_ws(fig, {});\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (el) {\n",
+       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+       "    // this is important to make the div 'focusable\n",
+       "    el.setAttribute('tabindex', 0);\n",
+       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
+       "    // off when our div gets focus\n",
+       "\n",
+       "    // location in version 3\n",
+       "    if (IPython.notebook.keyboard_manager) {\n",
+       "        IPython.notebook.keyboard_manager.register_events(el);\n",
+       "    } else {\n",
+       "        // location in version 2\n",
+       "        IPython.keyboard_manager.register_events(el);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+       "    // Check for shift+enter\n",
+       "    if (event.shiftKey && event.which === 13) {\n",
+       "        this.canvas_div.blur();\n",
+       "        // select the cell after this one\n",
+       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+       "        IPython.notebook.select(index + 1);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    fig.ondownload(fig, null);\n",
+       "};\n",
+       "\n",
+       "mpl.find_output_cell = function (html_output) {\n",
+       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+       "    // IPython event is triggered only after the cells have been serialised, which for\n",
+       "    // our purposes (turning an active figure into a static one), is too late.\n",
+       "    var cells = IPython.notebook.get_cells();\n",
+       "    var ncells = cells.length;\n",
+       "    for (var i = 0; i < ncells; i++) {\n",
+       "        var cell = cells[i];\n",
+       "        if (cell.cell_type === 'code') {\n",
+       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+       "                var data = cell.output_area.outputs[j];\n",
+       "                if (data.data) {\n",
+       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
+       "                    data = data.data;\n",
+       "                }\n",
+       "                if (data['text/html'] === html_output) {\n",
+       "                    return [cell, data, j];\n",
+       "                }\n",
+       "            }\n",
+       "        }\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "// Register the function which deals with the matplotlib target/channel.\n",
+       "// The kernel may be null if the page has been refreshed.\n",
+       "if (IPython.notebook.kernel !== null) {\n",
+       "    IPython.notebook.kernel.comm_manager.register_target(\n",
+       "        'matplotlib',\n",
+       "        mpl.mpl_figure_comm\n",
+       "    );\n",
+       "}\n"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Javascript object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAgAElEQVR4Xu3dsWlWYQCFYbOBC0gGsRYEF3ADKysrR0iT4ncFrQL/DHEWC4vMIGhvEwKHF+59bIVTyIme+3zfvd68+vfr26f7X7e339++v3548/rr9e769Hh59/D58vHP7y8Pjz9/+H1/Pvrh58PfD/5+9O+Dfx/tg+PsoxsD0MA18A18A9/AN/ANfAP/XAPfACSchJeAOwFwAuIEyAmYE8CTnYAagAagAWgAGoAGoAFoABqABuC5CNQdR0fgjsAdgTsCdwTuCNwR+NmOwAkgASSABJAAEkACSAAJIAEkgN5yOs5bToSX8BJewkt4CS/h/V94CSABJIAEkAASQAJIAAkgASSABJAA+g6m74D6DqrvwDpBcIJw5BMEAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkJISEkBASQkJITiYkBqABaAAagAagAWgAGoAGoAF454TICdFRTojcAXQH0B1ADzgecDzgeMDxgOMB52QPOAagAWgAGoAGoAFoABqABqAB6CUQxI/4j0L87jC5w3TkO0z6rd/6/fIPvRNAAkgACSABJIAEkAASQAJIAAkgASSAPgPjMzA+A0MYCeORhZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkJISEkhISQEJKTCYkBaAAagAagAWgAGoAGoAFoAPoQ9JMToqOcELkD6A6gO4AecDzgeMDxgOMBxwPOyR5wDEAD0AA0AA1AA9AANAANQAPQSyBeAkH8RyF+d5jcYTryHSb91m/99iFogkEwCAbBIBgnEwwD0AA0AA1AA9AANAANQAPQAPSShpc0Lk5wnvcZL3cA3QF0B9AdQA9QHqA8QHmA8gB1sgcoA9AANAANQAPQADQADUAD0AD0EoiXQLwE4gjheUcI7mC5g+UO1svvYPn58fNT/vwQQAJIAAkgASSABJAAEkACSAAJIAEkgATQ/wXs/wImdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbmIRMUMAAAGuSURBVN0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df/+AjLpTyBUoEepAAAAAElFTkSuQmCC\" width=\"640\">"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<AxesSubplot:title={'center':'triangle mesh'}>"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "plane.compare_with_mesh(mesh)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Region markers\n",
+    "\n",
+    "By default, regions are split up in the *background* and *features*. Feature regions are grouped by the label 1."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "RegionMarkerList(\n",
+       "    RegionMarker(label=1, point=(10.571428571428571, 3.2857142857142856), name='feature', constraint=0),\n",
+       "    RegionMarker(label=1, point=(9.333333333333334, 46.5), name='feature', constraint=0),\n",
+       "    RegionMarker(label=1, point=(15.214285714285714, 37.285714285714285), name='feature', constraint=0),\n",
+       "    RegionMarker(label=1, point=(28.5, 9.5), name='feature', constraint=0),\n",
+       "    RegionMarker(label=1, point=(34.5, 41.4), name='feature', constraint=0),\n",
+       "    RegionMarker(label=1, point=(44.42857142857143, 32.0), name='feature', constraint=0),\n",
+       "    RegionMarker(label=0, point=(24.5, 24.5), name='background', constraint=0)\n",
+       ")"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "mesher.contour.region_markers"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "To label regions sequentially, set `group_regions=False`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "/* Put everything inside the global mpl namespace */\n",
+       "/* global mpl */\n",
+       "window.mpl = {};\n",
+       "\n",
+       "mpl.get_websocket_type = function () {\n",
+       "    if (typeof WebSocket !== 'undefined') {\n",
+       "        return WebSocket;\n",
+       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
+       "        return MozWebSocket;\n",
+       "    } else {\n",
+       "        alert(\n",
+       "            'Your browser does not have WebSocket support. ' +\n",
+       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+       "                'Firefox 4 and 5 are also supported but you ' +\n",
+       "                'have to enable WebSockets in about:config.'\n",
+       "        );\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+       "    this.id = figure_id;\n",
+       "\n",
+       "    this.ws = websocket;\n",
+       "\n",
+       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
+       "\n",
+       "    if (!this.supports_binary) {\n",
+       "        var warnings = document.getElementById('mpl-warnings');\n",
+       "        if (warnings) {\n",
+       "            warnings.style.display = 'block';\n",
+       "            warnings.textContent =\n",
+       "                'This browser does not support binary websocket messages. ' +\n",
+       "                'Performance may be slow.';\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.imageObj = new Image();\n",
+       "\n",
+       "    this.context = undefined;\n",
+       "    this.message = undefined;\n",
+       "    this.canvas = undefined;\n",
+       "    this.rubberband_canvas = undefined;\n",
+       "    this.rubberband_context = undefined;\n",
+       "    this.format_dropdown = undefined;\n",
+       "\n",
+       "    this.image_mode = 'full';\n",
+       "\n",
+       "    this.root = document.createElement('div');\n",
+       "    this.root.setAttribute('style', 'display: inline-block');\n",
+       "    this._root_extra_style(this.root);\n",
+       "\n",
+       "    parent_element.appendChild(this.root);\n",
+       "\n",
+       "    this._init_header(this);\n",
+       "    this._init_canvas(this);\n",
+       "    this._init_toolbar(this);\n",
+       "\n",
+       "    var fig = this;\n",
+       "\n",
+       "    this.waiting = false;\n",
+       "\n",
+       "    this.ws.onopen = function () {\n",
+       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+       "        fig.send_message('send_image_mode', {});\n",
+       "        if (fig.ratio !== 1) {\n",
+       "            fig.send_message('set_device_pixel_ratio', {\n",
+       "                device_pixel_ratio: fig.ratio,\n",
+       "            });\n",
+       "        }\n",
+       "        fig.send_message('refresh', {});\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onload = function () {\n",
+       "        if (fig.image_mode === 'full') {\n",
+       "            // Full images could contain transparency (where diff images\n",
+       "            // almost always do), so we need to clear the canvas so that\n",
+       "            // there is no ghosting.\n",
+       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+       "        }\n",
+       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onunload = function () {\n",
+       "        fig.ws.close();\n",
+       "    };\n",
+       "\n",
+       "    this.ws.onmessage = this._make_on_message_function(this);\n",
+       "\n",
+       "    this.ondownload = ondownload;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_header = function () {\n",
+       "    var titlebar = document.createElement('div');\n",
+       "    titlebar.classList =\n",
+       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+       "    var titletext = document.createElement('div');\n",
+       "    titletext.classList = 'ui-dialog-title';\n",
+       "    titletext.setAttribute(\n",
+       "        'style',\n",
+       "        'width: 100%; text-align: center; padding: 3px;'\n",
+       "    );\n",
+       "    titlebar.appendChild(titletext);\n",
+       "    this.root.appendChild(titlebar);\n",
+       "    this.header = titletext;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._init_canvas = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+       "    canvas_div.setAttribute(\n",
+       "        'style',\n",
+       "        'border: 1px solid #ddd;' +\n",
+       "            'box-sizing: content-box;' +\n",
+       "            'clear: both;' +\n",
+       "            'min-height: 1px;' +\n",
+       "            'min-width: 1px;' +\n",
+       "            'outline: 0;' +\n",
+       "            'overflow: hidden;' +\n",
+       "            'position: relative;' +\n",
+       "            'resize: both;'\n",
+       "    );\n",
+       "\n",
+       "    function on_keyboard_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.key_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keydown',\n",
+       "        on_keyboard_event_closure('key_press')\n",
+       "    );\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keyup',\n",
+       "        on_keyboard_event_closure('key_release')\n",
+       "    );\n",
+       "\n",
+       "    this._canvas_extra_style(canvas_div);\n",
+       "    this.root.appendChild(canvas_div);\n",
+       "\n",
+       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
+       "    canvas.classList.add('mpl-canvas');\n",
+       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+       "\n",
+       "    this.context = canvas.getContext('2d');\n",
+       "\n",
+       "    var backingStore =\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        this.context.webkitBackingStorePixelRatio ||\n",
+       "        this.context.mozBackingStorePixelRatio ||\n",
+       "        this.context.msBackingStorePixelRatio ||\n",
+       "        this.context.oBackingStorePixelRatio ||\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        1;\n",
+       "\n",
+       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+       "\n",
+       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+       "        'canvas'\n",
+       "    ));\n",
+       "    rubberband_canvas.setAttribute(\n",
+       "        'style',\n",
+       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+       "    );\n",
+       "\n",
+       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+       "    if (this.ResizeObserver === undefined) {\n",
+       "        if (window.ResizeObserver !== undefined) {\n",
+       "            this.ResizeObserver = window.ResizeObserver;\n",
+       "        } else {\n",
+       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+       "            this.ResizeObserver = obs.ResizeObserver;\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+       "        var nentries = entries.length;\n",
+       "        for (var i = 0; i < nentries; i++) {\n",
+       "            var entry = entries[i];\n",
+       "            var width, height;\n",
+       "            if (entry.contentBoxSize) {\n",
+       "                if (entry.contentBoxSize instanceof Array) {\n",
+       "                    // Chrome 84 implements new version of spec.\n",
+       "                    width = entry.contentBoxSize[0].inlineSize;\n",
+       "                    height = entry.contentBoxSize[0].blockSize;\n",
+       "                } else {\n",
+       "                    // Firefox implements old version of spec.\n",
+       "                    width = entry.contentBoxSize.inlineSize;\n",
+       "                    height = entry.contentBoxSize.blockSize;\n",
+       "                }\n",
+       "            } else {\n",
+       "                // Chrome <84 implements even older version of spec.\n",
+       "                width = entry.contentRect.width;\n",
+       "                height = entry.contentRect.height;\n",
+       "            }\n",
+       "\n",
+       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
+       "            // the canvas container.\n",
+       "            if (entry.devicePixelContentBoxSize) {\n",
+       "                // Chrome 84 implements new version of spec.\n",
+       "                canvas.setAttribute(\n",
+       "                    'width',\n",
+       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
+       "                );\n",
+       "                canvas.setAttribute(\n",
+       "                    'height',\n",
+       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
+       "                );\n",
+       "            } else {\n",
+       "                canvas.setAttribute('width', width * fig.ratio);\n",
+       "                canvas.setAttribute('height', height * fig.ratio);\n",
+       "            }\n",
+       "            canvas.setAttribute(\n",
+       "                'style',\n",
+       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
+       "            );\n",
+       "\n",
+       "            rubberband_canvas.setAttribute('width', width);\n",
+       "            rubberband_canvas.setAttribute('height', height);\n",
+       "\n",
+       "            // And update the size in Python. We ignore the initial 0/0 size\n",
+       "            // that occurs as the element is placed into the DOM, which should\n",
+       "            // otherwise not happen due to the minimum size styling.\n",
+       "            if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+       "                fig.request_resize(width, height);\n",
+       "            }\n",
+       "        }\n",
+       "    });\n",
+       "    this.resizeObserverInstance.observe(canvas_div);\n",
+       "\n",
+       "    function on_mouse_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.mouse_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousedown',\n",
+       "        on_mouse_event_closure('button_press')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseup',\n",
+       "        on_mouse_event_closure('button_release')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'dblclick',\n",
+       "        on_mouse_event_closure('dblclick')\n",
+       "    );\n",
+       "    // Throttle sequential mouse events to 1 every 20ms.\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousemove',\n",
+       "        on_mouse_event_closure('motion_notify')\n",
+       "    );\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseenter',\n",
+       "        on_mouse_event_closure('figure_enter')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseleave',\n",
+       "        on_mouse_event_closure('figure_leave')\n",
+       "    );\n",
+       "\n",
+       "    canvas_div.addEventListener('wheel', function (event) {\n",
+       "        if (event.deltaY < 0) {\n",
+       "            event.step = 1;\n",
+       "        } else {\n",
+       "            event.step = -1;\n",
+       "        }\n",
+       "        on_mouse_event_closure('scroll')(event);\n",
+       "    });\n",
+       "\n",
+       "    canvas_div.appendChild(canvas);\n",
+       "    canvas_div.appendChild(rubberband_canvas);\n",
+       "\n",
+       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+       "    this.rubberband_context.strokeStyle = '#000000';\n",
+       "\n",
+       "    this._resize_canvas = function (width, height, forward) {\n",
+       "        if (forward) {\n",
+       "            canvas_div.style.width = width + 'px';\n",
+       "            canvas_div.style.height = height + 'px';\n",
+       "        }\n",
+       "    };\n",
+       "\n",
+       "    // Disable right mouse context menu.\n",
+       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+       "        event.preventDefault();\n",
+       "        return false;\n",
+       "    });\n",
+       "\n",
+       "    function set_focus() {\n",
+       "        canvas.focus();\n",
+       "        canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    window.setTimeout(set_focus, 100);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'mpl-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'mpl-button-group';\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'mpl-button-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
+       "        button.classList = 'mpl-widget';\n",
+       "        button.setAttribute('role', 'button');\n",
+       "        button.setAttribute('aria-disabled', 'false');\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "\n",
+       "        var icon_img = document.createElement('img');\n",
+       "        icon_img.src = '_images/' + image + '.png';\n",
+       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+       "        icon_img.alt = tooltip;\n",
+       "        button.appendChild(icon_img);\n",
+       "\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    var fmt_picker = document.createElement('select');\n",
+       "    fmt_picker.classList = 'mpl-widget';\n",
+       "    toolbar.appendChild(fmt_picker);\n",
+       "    this.format_dropdown = fmt_picker;\n",
+       "\n",
+       "    for (var ind in mpl.extensions) {\n",
+       "        var fmt = mpl.extensions[ind];\n",
+       "        var option = document.createElement('option');\n",
+       "        option.selected = fmt === mpl.default_extension;\n",
+       "        option.innerHTML = fmt;\n",
+       "        fmt_picker.appendChild(option);\n",
+       "    }\n",
+       "\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+       "    // which will in turn request a refresh of the image.\n",
+       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_message = function (type, properties) {\n",
+       "    properties['type'] = type;\n",
+       "    properties['figure_id'] = this.id;\n",
+       "    this.ws.send(JSON.stringify(properties));\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_draw_message = function () {\n",
+       "    if (!this.waiting) {\n",
+       "        this.waiting = true;\n",
+       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    var format_dropdown = fig.format_dropdown;\n",
+       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+       "    fig.ondownload(fig, format);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+       "    var size = msg['size'];\n",
+       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+       "        fig.send_message('refresh', {});\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+       "    var x0 = msg['x0'] / fig.ratio;\n",
+       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+       "    var x1 = msg['x1'] / fig.ratio;\n",
+       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+       "    x0 = Math.floor(x0) + 0.5;\n",
+       "    y0 = Math.floor(y0) + 0.5;\n",
+       "    x1 = Math.floor(x1) + 0.5;\n",
+       "    y1 = Math.floor(y1) + 0.5;\n",
+       "    var min_x = Math.min(x0, x1);\n",
+       "    var min_y = Math.min(y0, y1);\n",
+       "    var width = Math.abs(x1 - x0);\n",
+       "    var height = Math.abs(y1 - y0);\n",
+       "\n",
+       "    fig.rubberband_context.clearRect(\n",
+       "        0,\n",
+       "        0,\n",
+       "        fig.canvas.width / fig.ratio,\n",
+       "        fig.canvas.height / fig.ratio\n",
+       "    );\n",
+       "\n",
+       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+       "    // Updates the figure title.\n",
+       "    fig.header.textContent = msg['label'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+       "    fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+       "    fig.message.textContent = msg['message'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+       "    // Request the server to send over a new figure.\n",
+       "    fig.send_draw_message();\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+       "    fig.image_mode = msg['mode'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+       "    for (var key in msg) {\n",
+       "        if (!(key in fig.buttons)) {\n",
+       "            continue;\n",
+       "        }\n",
+       "        fig.buttons[key].disabled = !msg[key];\n",
+       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+       "    if (msg['mode'] === 'PAN') {\n",
+       "        fig.buttons['Pan'].classList.add('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    } else if (msg['mode'] === 'ZOOM') {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.add('active');\n",
+       "    } else {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Called whenever the canvas gets updated.\n",
+       "    this.send_message('ack', {});\n",
+       "};\n",
+       "\n",
+       "// A function to construct a web socket function for onmessage handling.\n",
+       "// Called in the figure constructor.\n",
+       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+       "    return function socket_on_message(evt) {\n",
+       "        if (evt.data instanceof Blob) {\n",
+       "            var img = evt.data;\n",
+       "            if (img.type !== 'image/png') {\n",
+       "                /* FIXME: We get \"Resource interpreted as Image but\n",
+       "                 * transferred with MIME type text/plain:\" errors on\n",
+       "                 * Chrome.  But how to set the MIME type?  It doesn't seem\n",
+       "                 * to be part of the websocket stream */\n",
+       "                img.type = 'image/png';\n",
+       "            }\n",
+       "\n",
+       "            /* Free the memory for the previous frames */\n",
+       "            if (fig.imageObj.src) {\n",
+       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
+       "                    fig.imageObj.src\n",
+       "                );\n",
+       "            }\n",
+       "\n",
+       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+       "                img\n",
+       "            );\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        } else if (\n",
+       "            typeof evt.data === 'string' &&\n",
+       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+       "        ) {\n",
+       "            fig.imageObj.src = evt.data;\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        var msg = JSON.parse(evt.data);\n",
+       "        var msg_type = msg['type'];\n",
+       "\n",
+       "        // Call the  \"handle_{type}\" callback, which takes\n",
+       "        // the figure and JSON message as its only arguments.\n",
+       "        try {\n",
+       "            var callback = fig['handle_' + msg_type];\n",
+       "        } catch (e) {\n",
+       "            console.log(\n",
+       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
+       "                msg\n",
+       "            );\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        if (callback) {\n",
+       "            try {\n",
+       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+       "                callback(fig, msg);\n",
+       "            } catch (e) {\n",
+       "                console.log(\n",
+       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+       "                    e,\n",
+       "                    e.stack,\n",
+       "                    msg\n",
+       "                );\n",
+       "            }\n",
+       "        }\n",
+       "    };\n",
+       "};\n",
+       "\n",
+       "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+       "mpl.findpos = function (e) {\n",
+       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+       "    var targ;\n",
+       "    if (!e) {\n",
+       "        e = window.event;\n",
+       "    }\n",
+       "    if (e.target) {\n",
+       "        targ = e.target;\n",
+       "    } else if (e.srcElement) {\n",
+       "        targ = e.srcElement;\n",
+       "    }\n",
+       "    if (targ.nodeType === 3) {\n",
+       "        // defeat Safari bug\n",
+       "        targ = targ.parentNode;\n",
+       "    }\n",
+       "\n",
+       "    // pageX,Y are the mouse positions relative to the document\n",
+       "    var boundingRect = targ.getBoundingClientRect();\n",
+       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+       "\n",
+       "    return { x: x, y: y };\n",
+       "};\n",
+       "\n",
+       "/*\n",
+       " * return a copy of an object with only non-object keys\n",
+       " * we need this to avoid circular references\n",
+       " * https://stackoverflow.com/a/24161582/3208463\n",
+       " */\n",
+       "function simpleKeys(original) {\n",
+       "    return Object.keys(original).reduce(function (obj, key) {\n",
+       "        if (typeof original[key] !== 'object') {\n",
+       "            obj[key] = original[key];\n",
+       "        }\n",
+       "        return obj;\n",
+       "    }, {});\n",
+       "}\n",
+       "\n",
+       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+       "    var canvas_pos = mpl.findpos(event);\n",
+       "\n",
+       "    if (name === 'button_press') {\n",
+       "        this.canvas.focus();\n",
+       "        this.canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    var x = canvas_pos.x * this.ratio;\n",
+       "    var y = canvas_pos.y * this.ratio;\n",
+       "\n",
+       "    this.send_message(name, {\n",
+       "        x: x,\n",
+       "        y: y,\n",
+       "        button: event.button,\n",
+       "        step: event.step,\n",
+       "        guiEvent: simpleKeys(event),\n",
+       "    });\n",
+       "\n",
+       "    /* This prevents the web browser from automatically changing to\n",
+       "     * the text insertion cursor when the button is pressed.  We want\n",
+       "     * to control all of the cursor setting manually through the\n",
+       "     * 'cursor' event from matplotlib */\n",
+       "    event.preventDefault();\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+       "    // Handle any extra behaviour associated with a key event\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.key_event = function (event, name) {\n",
+       "    // Prevent repeat events\n",
+       "    if (name === 'key_press') {\n",
+       "        if (event.key === this._key) {\n",
+       "            return;\n",
+       "        } else {\n",
+       "            this._key = event.key;\n",
+       "        }\n",
+       "    }\n",
+       "    if (name === 'key_release') {\n",
+       "        this._key = null;\n",
+       "    }\n",
+       "\n",
+       "    var value = '';\n",
+       "    if (event.ctrlKey && event.key !== 'Control') {\n",
+       "        value += 'ctrl+';\n",
+       "    }\n",
+       "    else if (event.altKey && event.key !== 'Alt') {\n",
+       "        value += 'alt+';\n",
+       "    }\n",
+       "    else if (event.shiftKey && event.key !== 'Shift') {\n",
+       "        value += 'shift+';\n",
+       "    }\n",
+       "\n",
+       "    value += 'k' + event.key;\n",
+       "\n",
+       "    this._key_event_extra(event, name);\n",
+       "\n",
+       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+       "    if (name === 'download') {\n",
+       "        this.handle_save(this, null);\n",
+       "    } else {\n",
+       "        this.send_message('toolbar_button', { name: name });\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+       "    this.message.textContent = tooltip;\n",
+       "};\n",
+       "\n",
+       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+       "// prettier-ignore\n",
+       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+       "\n",
+       "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+       "\n",
+       "mpl.default_extension = \"png\";/* global mpl */\n",
+       "\n",
+       "var comm_websocket_adapter = function (comm) {\n",
+       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
+       "    // object with the appropriate methods. Currently this is a non binary\n",
+       "    // socket, so there is still some room for performance tuning.\n",
+       "    var ws = {};\n",
+       "\n",
+       "    ws.binaryType = comm.kernel.ws.binaryType;\n",
+       "    ws.readyState = comm.kernel.ws.readyState;\n",
+       "    function updateReadyState(_event) {\n",
+       "        if (comm.kernel.ws) {\n",
+       "            ws.readyState = comm.kernel.ws.readyState;\n",
+       "        } else {\n",
+       "            ws.readyState = 3; // Closed state.\n",
+       "        }\n",
+       "    }\n",
+       "    comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+       "\n",
+       "    ws.close = function () {\n",
+       "        comm.close();\n",
+       "    };\n",
+       "    ws.send = function (m) {\n",
+       "        //console.log('sending', m);\n",
+       "        comm.send(m);\n",
+       "    };\n",
+       "    // Register the callback with on_msg.\n",
+       "    comm.on_msg(function (msg) {\n",
+       "        //console.log('receiving', msg['content']['data'], msg);\n",
+       "        var data = msg['content']['data'];\n",
+       "        if (data['blob'] !== undefined) {\n",
+       "            data = {\n",
+       "                data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+       "            };\n",
+       "        }\n",
+       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+       "        ws.onmessage(data);\n",
+       "    });\n",
+       "    return ws;\n",
+       "};\n",
+       "\n",
+       "mpl.mpl_figure_comm = function (comm, msg) {\n",
+       "    // This is the function which gets called when the mpl process\n",
+       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+       "\n",
+       "    var id = msg.content.data.id;\n",
+       "    // Get hold of the div created by the display call when the Comm\n",
+       "    // socket was opened in Python.\n",
+       "    var element = document.getElementById(id);\n",
+       "    var ws_proxy = comm_websocket_adapter(comm);\n",
+       "\n",
+       "    function ondownload(figure, _format) {\n",
+       "        window.open(figure.canvas.toDataURL());\n",
+       "    }\n",
+       "\n",
+       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+       "\n",
+       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+       "    // web socket which is closed, not our websocket->open comm proxy.\n",
+       "    ws_proxy.onopen();\n",
+       "\n",
+       "    fig.parent_element = element;\n",
+       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
+       "    if (!fig.cell_info) {\n",
+       "        console.error('Failed to find cell for figure', id, fig);\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.cell_info[0].output_area.element.on(\n",
+       "        'cleared',\n",
+       "        { fig: fig },\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+       "    var width = fig.canvas.width / fig.ratio;\n",
+       "    fig.cell_info[0].output_area.element.off(\n",
+       "        'cleared',\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+       "\n",
+       "    // Update the output cell to use the data from the current canvas.\n",
+       "    fig.push_to_output();\n",
+       "    var dataURL = fig.canvas.toDataURL();\n",
+       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+       "    // the notebook keyboard shortcuts fail.\n",
+       "    IPython.keyboard_manager.enable();\n",
+       "    fig.parent_element.innerHTML =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "    fig.close_ws(fig, msg);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+       "    fig.send_message('closing', msg);\n",
+       "    // fig.ws.close()\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+       "    // Turn the data on the canvas into data in the output cell.\n",
+       "    var width = this.canvas.width / this.ratio;\n",
+       "    var dataURL = this.canvas.toDataURL();\n",
+       "    this.cell_info[1]['text/html'] =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Tell IPython that the notebook contents must change.\n",
+       "    IPython.notebook.set_dirty(true);\n",
+       "    this.send_message('ack', {});\n",
+       "    var fig = this;\n",
+       "    // Wait a second, then push the new image to the DOM so\n",
+       "    // that it is saved nicely (might be nice to debounce this).\n",
+       "    setTimeout(function () {\n",
+       "        fig.push_to_output();\n",
+       "    }, 1000);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'btn-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'btn-group';\n",
+       "    var button;\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'btn-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        button = fig.buttons[name] = document.createElement('button');\n",
+       "        button.classList = 'btn btn-default';\n",
+       "        button.href = '#';\n",
+       "        button.title = name;\n",
+       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    // Add the status bar.\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message pull-right';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "\n",
+       "    // Add the close button to the window.\n",
+       "    var buttongrp = document.createElement('div');\n",
+       "    buttongrp.classList = 'btn-group inline pull-right';\n",
+       "    button = document.createElement('button');\n",
+       "    button.classList = 'btn btn-mini btn-primary';\n",
+       "    button.href = '#';\n",
+       "    button.title = 'Stop Interaction';\n",
+       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
+       "    button.addEventListener('click', function (_evt) {\n",
+       "        fig.handle_close(fig, {});\n",
+       "    });\n",
+       "    button.addEventListener(\n",
+       "        'mouseover',\n",
+       "        on_mouseover_closure('Stop Interaction')\n",
+       "    );\n",
+       "    buttongrp.appendChild(button);\n",
+       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+       "    var fig = event.data.fig;\n",
+       "    if (event.target !== this) {\n",
+       "        // Ignore bubbled events from children.\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.close_ws(fig, {});\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (el) {\n",
+       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+       "    // this is important to make the div 'focusable\n",
+       "    el.setAttribute('tabindex', 0);\n",
+       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
+       "    // off when our div gets focus\n",
+       "\n",
+       "    // location in version 3\n",
+       "    if (IPython.notebook.keyboard_manager) {\n",
+       "        IPython.notebook.keyboard_manager.register_events(el);\n",
+       "    } else {\n",
+       "        // location in version 2\n",
+       "        IPython.keyboard_manager.register_events(el);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+       "    // Check for shift+enter\n",
+       "    if (event.shiftKey && event.which === 13) {\n",
+       "        this.canvas_div.blur();\n",
+       "        // select the cell after this one\n",
+       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+       "        IPython.notebook.select(index + 1);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    fig.ondownload(fig, null);\n",
+       "};\n",
+       "\n",
+       "mpl.find_output_cell = function (html_output) {\n",
+       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+       "    // IPython event is triggered only after the cells have been serialised, which for\n",
+       "    // our purposes (turning an active figure into a static one), is too late.\n",
+       "    var cells = IPython.notebook.get_cells();\n",
+       "    var ncells = cells.length;\n",
+       "    for (var i = 0; i < ncells; i++) {\n",
+       "        var cell = cells[i];\n",
+       "        if (cell.cell_type === 'code') {\n",
+       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+       "                var data = cell.output_area.outputs[j];\n",
+       "                if (data.data) {\n",
+       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
+       "                    data = data.data;\n",
+       "                }\n",
+       "                if (data['text/html'] === html_output) {\n",
+       "                    return [cell, data, j];\n",
+       "                }\n",
+       "            }\n",
+       "        }\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "// Register the function which deals with the matplotlib target/channel.\n",
+       "// The kernel may be null if the page has been refreshed.\n",
+       "if (IPython.notebook.kernel !== null) {\n",
+       "    IPython.notebook.kernel.comm_manager.register_target(\n",
+       "        'matplotlib',\n",
+       "        mpl.mpl_figure_comm\n",
+       "    );\n",
+       "}\n"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Javascript object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAgAElEQVR4Xu3dMWoWUQCFUVKJuARb3YeC4B4sLW3sFCsX4KrSKfwiWAXJKkybwgXYhMDlg5lTC7eQm3jnvDfj1fc3z39c//p0e//h1ZOnvz9f315+3r28vLv5+vfts9cfv73/cv/nhT/396Mffj78fvD70b8P/n20D46zj64MQAPXwDfwDXwD38A38A38cw18A5BwEl4C7gTACYgTICdgTgBPdgJqABqABqABaAAagAagAWgAGoDnIlB3HB2BOwJ3BO4I3BG4I3BH4Gc7AieABJAAEkACSAAJIAEkgASQAHrL6ThvORFewkt4CS/hJbyE93/hJYAEkAASQAJIAAkgASSABJAAEkAC6DuYvgPqO6i+A+sEwQnCkU8QCCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQkBASQkJICAkhOZmQGIAGoAFoABqABqABaAAagAbgnRMiJ0RHOSFyB9AdQHcAPeB4wPGA4wHHA44HnJM94BiABqABaAAagAagAWgAGoAGoJdAED/iPwrxu8PkDtOR7zDpt37r9+M/9E4ACSABJIAEkAASQAJIAAkgASSABJAA+gyMz8D4DAxhJIxHFkYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASQkhISQEBJCQkhOJiQGoAFoABqABqABaAAagAagAehD0BcnREc5IXIH0B1AdwA94HjA8YDjAccDjgeckz3gGIAGoAFoABqABqABaAAagAagl0C8BIL4j0L87jC5w3TkO0z6rd/67UPQBINgEAyCQTBOJhgGoAFoABqABqABaAAagAagAeglDS9p3DjBedhnvNwBdAfQHUB3AD1AeYDyAOUBygPUyR6gDEAD0AA0AA1AA9AANAANQAPQSyBeAvESiCOEhx0huIPlDpY7WI+/g+Xnx89P+fNDAAkgASSABJAAEkACSAAJIAEkgASQABJA/xew/wuY0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UuoWO7b4AAAGuSURBVHX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v37B046qmYl29PgAAAAAElFTkSuQmCC\" width=\"640\">"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<AxesSubplot:title={'center':'line mesh'}>"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "mesher = Mesher2D(plane)\n",
+    "mesher.generate_contour(max_contour_dist = 3, group_regions=False)\n",
+    "\n",
+    "mesher.plot_contour()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Notice that each feature has been given a unique name:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "RegionMarkerList(\n",
+       "    RegionMarker(label=2, point=(10.571428571428571, 3.2857142857142856), name='feature2', constraint=0),\n",
+       "    RegionMarker(label=3, point=(9.333333333333334, 46.5), name='feature3', constraint=0),\n",
+       "    RegionMarker(label=4, point=(15.214285714285714, 37.285714285714285), name='feature4', constraint=0),\n",
+       "    RegionMarker(label=5, point=(28.5, 9.5), name='feature5', constraint=0),\n",
+       "    RegionMarker(label=6, point=(34.5, 41.4), name='feature6', constraint=0),\n",
+       "    RegionMarker(label=7, point=(44.42857142857143, 32.0), name='feature7', constraint=0),\n",
+       "    RegionMarker(label=0, point=(24.5, 24.5), name='background', constraint=0)\n",
+       ")"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "mesher.contour.region_markers"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "These labels will assigned to each triangle in the corresponding region after triangulation. These are stored in `mesh.cell_data` of the `MeshContainer`. This container stores a single set of points, and both the line segments (`LineMesh`) and triangles (`TriangleMesh`). To extract the triangle cells only, use `MeshContainer.get('triangle')`. this returns a class that is simpler to work with.\n",
+    "\n",
+    "The cell below shows how to use this to access the cell data for the triangle cells in the mesh."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'physical': array([0., 0., 2., 0., 0., 2., 0., 0., 2., 0., 0., 5., 0., 2., 0., 0., 0.,\n",
+       "        5., 5., 0., 5., 2., 0., 0., 0., 0., 0., 3., 0., 0., 3., 0., 3., 0.,\n",
+       "        4., 0., 0., 4., 3., 0., 0., 0., 4., 0., 0., 4., 4., 0., 0., 0., 5.,\n",
+       "        0., 0., 5., 5., 5., 5., 0., 0., 0., 0., 0., 0., 5., 7., 0., 7., 0.,\n",
+       "        0., 7., 0., 0., 0., 0., 6., 0., 0., 6., 0., 6., 0., 6., 6., 0., 6.,\n",
+       "        0., 7., 7., 0., 0., 6., 6., 0., 0., 0., 0., 0., 0., 0., 0., 0., 5.,\n",
+       "        0., 0., 3., 0., 0., 3., 3., 0., 0., 0., 0., 0., 0., 3., 5., 0., 0.,\n",
+       "        3., 3., 3., 0., 2., 5., 5., 0., 2., 0., 5., 5., 2., 2., 2., 4., 4.,\n",
+       "        0., 0., 5., 5., 0., 0., 0., 5., 5., 0., 0., 0., 0., 0., 0., 0., 0.,\n",
+       "        0., 0., 6., 6., 6., 0., 0., 6., 6., 6., 0., 0., 0., 6., 7., 6., 0.,\n",
+       "        0., 2., 2., 2., 0., 0., 0., 0., 0., 0., 6., 0., 6., 0., 0., 7., 7.,\n",
+       "        7., 7., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n",
+       "        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n",
+       "        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n",
+       "        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n",
+       "        0., 0., 0., 0., 0., 0., 0.])}"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "mesh = mesher.triangulate(opts='q30a100')\n",
+    "mesh.get('triangle').cell_data"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Field data\n",
+    "\n",
+    "Field data can be used to associate names with the values in the cell data. These are shown in the legend of mesh data (i.e. in the plots above). The field data is stored in the `.field_data` attribute. Because the data are somewhat difficult to use in this state, the properties `.field_to_number` and `.number_to_field` can be used to access the mapping per cell type."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "mappingproxy({'triangle': mappingproxy({2: 'feature2',\n",
+       "                            3: 'feature3',\n",
+       "                            4: 'feature4',\n",
+       "                            5: 'feature5',\n",
+       "                            6: 'feature6',\n",
+       "                            7: 'feature7',\n",
+       "                            0: 'background'})})"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "mesh.number_to_field"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "To update the values, you can update `.field_data` directory, or use `.set_field_data`. Note that field names are shared between cell types. For example, to relabel the cells data:\n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "mappingproxy({'triangle': mappingproxy({2: 'Pore2',\n",
+       "                            3: 'Pore3',\n",
+       "                            4: 'Pore4',\n",
+       "                            5: 'Pore5',\n",
+       "                            6: 'Pore6',\n",
+       "                            7: 'Pore7',\n",
+       "                            0: 'Silicon'})})"
+      ]
+     },
+     "execution_count": 11,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fields = {}\n",
+    "for k,v in mesh.number_to_field['triangle'].items():\n",
+    "    v = v.replace('background', 'Silicon')\n",
+    "    v = v.replace('feature', 'Pore')\n",
+    "    fields[k] = v\n",
+    "\n",
+    "mesh.set_field_data('triangle', fields)\n",
+    "mesh.number_to_field"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Plotting the mesh now shows the fields in the legend. Note that the fields are also saved when exported to a format that supports them (e.g. *gmsh*)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "/* Put everything inside the global mpl namespace */\n",
+       "/* global mpl */\n",
+       "window.mpl = {};\n",
+       "\n",
+       "mpl.get_websocket_type = function () {\n",
+       "    if (typeof WebSocket !== 'undefined') {\n",
+       "        return WebSocket;\n",
+       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
+       "        return MozWebSocket;\n",
+       "    } else {\n",
+       "        alert(\n",
+       "            'Your browser does not have WebSocket support. ' +\n",
+       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
+       "                'Firefox 4 and 5 are also supported but you ' +\n",
+       "                'have to enable WebSockets in about:config.'\n",
+       "        );\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
+       "    this.id = figure_id;\n",
+       "\n",
+       "    this.ws = websocket;\n",
+       "\n",
+       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
+       "\n",
+       "    if (!this.supports_binary) {\n",
+       "        var warnings = document.getElementById('mpl-warnings');\n",
+       "        if (warnings) {\n",
+       "            warnings.style.display = 'block';\n",
+       "            warnings.textContent =\n",
+       "                'This browser does not support binary websocket messages. ' +\n",
+       "                'Performance may be slow.';\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.imageObj = new Image();\n",
+       "\n",
+       "    this.context = undefined;\n",
+       "    this.message = undefined;\n",
+       "    this.canvas = undefined;\n",
+       "    this.rubberband_canvas = undefined;\n",
+       "    this.rubberband_context = undefined;\n",
+       "    this.format_dropdown = undefined;\n",
+       "\n",
+       "    this.image_mode = 'full';\n",
+       "\n",
+       "    this.root = document.createElement('div');\n",
+       "    this.root.setAttribute('style', 'display: inline-block');\n",
+       "    this._root_extra_style(this.root);\n",
+       "\n",
+       "    parent_element.appendChild(this.root);\n",
+       "\n",
+       "    this._init_header(this);\n",
+       "    this._init_canvas(this);\n",
+       "    this._init_toolbar(this);\n",
+       "\n",
+       "    var fig = this;\n",
+       "\n",
+       "    this.waiting = false;\n",
+       "\n",
+       "    this.ws.onopen = function () {\n",
+       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
+       "        fig.send_message('send_image_mode', {});\n",
+       "        if (fig.ratio !== 1) {\n",
+       "            fig.send_message('set_device_pixel_ratio', {\n",
+       "                device_pixel_ratio: fig.ratio,\n",
+       "            });\n",
+       "        }\n",
+       "        fig.send_message('refresh', {});\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onload = function () {\n",
+       "        if (fig.image_mode === 'full') {\n",
+       "            // Full images could contain transparency (where diff images\n",
+       "            // almost always do), so we need to clear the canvas so that\n",
+       "            // there is no ghosting.\n",
+       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
+       "        }\n",
+       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
+       "    };\n",
+       "\n",
+       "    this.imageObj.onunload = function () {\n",
+       "        fig.ws.close();\n",
+       "    };\n",
+       "\n",
+       "    this.ws.onmessage = this._make_on_message_function(this);\n",
+       "\n",
+       "    this.ondownload = ondownload;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_header = function () {\n",
+       "    var titlebar = document.createElement('div');\n",
+       "    titlebar.classList =\n",
+       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
+       "    var titletext = document.createElement('div');\n",
+       "    titletext.classList = 'ui-dialog-title';\n",
+       "    titletext.setAttribute(\n",
+       "        'style',\n",
+       "        'width: 100%; text-align: center; padding: 3px;'\n",
+       "    );\n",
+       "    titlebar.appendChild(titletext);\n",
+       "    this.root.appendChild(titlebar);\n",
+       "    this.header = titletext;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
+       "\n",
+       "mpl.figure.prototype._init_canvas = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
+       "    canvas_div.setAttribute(\n",
+       "        'style',\n",
+       "        'border: 1px solid #ddd;' +\n",
+       "            'box-sizing: content-box;' +\n",
+       "            'clear: both;' +\n",
+       "            'min-height: 1px;' +\n",
+       "            'min-width: 1px;' +\n",
+       "            'outline: 0;' +\n",
+       "            'overflow: hidden;' +\n",
+       "            'position: relative;' +\n",
+       "            'resize: both;'\n",
+       "    );\n",
+       "\n",
+       "    function on_keyboard_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.key_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keydown',\n",
+       "        on_keyboard_event_closure('key_press')\n",
+       "    );\n",
+       "    canvas_div.addEventListener(\n",
+       "        'keyup',\n",
+       "        on_keyboard_event_closure('key_release')\n",
+       "    );\n",
+       "\n",
+       "    this._canvas_extra_style(canvas_div);\n",
+       "    this.root.appendChild(canvas_div);\n",
+       "\n",
+       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
+       "    canvas.classList.add('mpl-canvas');\n",
+       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
+       "\n",
+       "    this.context = canvas.getContext('2d');\n",
+       "\n",
+       "    var backingStore =\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        this.context.webkitBackingStorePixelRatio ||\n",
+       "        this.context.mozBackingStorePixelRatio ||\n",
+       "        this.context.msBackingStorePixelRatio ||\n",
+       "        this.context.oBackingStorePixelRatio ||\n",
+       "        this.context.backingStorePixelRatio ||\n",
+       "        1;\n",
+       "\n",
+       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
+       "\n",
+       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
+       "        'canvas'\n",
+       "    ));\n",
+       "    rubberband_canvas.setAttribute(\n",
+       "        'style',\n",
+       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
+       "    );\n",
+       "\n",
+       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
+       "    if (this.ResizeObserver === undefined) {\n",
+       "        if (window.ResizeObserver !== undefined) {\n",
+       "            this.ResizeObserver = window.ResizeObserver;\n",
+       "        } else {\n",
+       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
+       "            this.ResizeObserver = obs.ResizeObserver;\n",
+       "        }\n",
+       "    }\n",
+       "\n",
+       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
+       "        var nentries = entries.length;\n",
+       "        for (var i = 0; i < nentries; i++) {\n",
+       "            var entry = entries[i];\n",
+       "            var width, height;\n",
+       "            if (entry.contentBoxSize) {\n",
+       "                if (entry.contentBoxSize instanceof Array) {\n",
+       "                    // Chrome 84 implements new version of spec.\n",
+       "                    width = entry.contentBoxSize[0].inlineSize;\n",
+       "                    height = entry.contentBoxSize[0].blockSize;\n",
+       "                } else {\n",
+       "                    // Firefox implements old version of spec.\n",
+       "                    width = entry.contentBoxSize.inlineSize;\n",
+       "                    height = entry.contentBoxSize.blockSize;\n",
+       "                }\n",
+       "            } else {\n",
+       "                // Chrome <84 implements even older version of spec.\n",
+       "                width = entry.contentRect.width;\n",
+       "                height = entry.contentRect.height;\n",
+       "            }\n",
+       "\n",
+       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
+       "            // the canvas container.\n",
+       "            if (entry.devicePixelContentBoxSize) {\n",
+       "                // Chrome 84 implements new version of spec.\n",
+       "                canvas.setAttribute(\n",
+       "                    'width',\n",
+       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
+       "                );\n",
+       "                canvas.setAttribute(\n",
+       "                    'height',\n",
+       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
+       "                );\n",
+       "            } else {\n",
+       "                canvas.setAttribute('width', width * fig.ratio);\n",
+       "                canvas.setAttribute('height', height * fig.ratio);\n",
+       "            }\n",
+       "            canvas.setAttribute(\n",
+       "                'style',\n",
+       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
+       "            );\n",
+       "\n",
+       "            rubberband_canvas.setAttribute('width', width);\n",
+       "            rubberband_canvas.setAttribute('height', height);\n",
+       "\n",
+       "            // And update the size in Python. We ignore the initial 0/0 size\n",
+       "            // that occurs as the element is placed into the DOM, which should\n",
+       "            // otherwise not happen due to the minimum size styling.\n",
+       "            if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
+       "                fig.request_resize(width, height);\n",
+       "            }\n",
+       "        }\n",
+       "    });\n",
+       "    this.resizeObserverInstance.observe(canvas_div);\n",
+       "\n",
+       "    function on_mouse_event_closure(name) {\n",
+       "        return function (event) {\n",
+       "            return fig.mouse_event(event, name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousedown',\n",
+       "        on_mouse_event_closure('button_press')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseup',\n",
+       "        on_mouse_event_closure('button_release')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'dblclick',\n",
+       "        on_mouse_event_closure('dblclick')\n",
+       "    );\n",
+       "    // Throttle sequential mouse events to 1 every 20ms.\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mousemove',\n",
+       "        on_mouse_event_closure('motion_notify')\n",
+       "    );\n",
+       "\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseenter',\n",
+       "        on_mouse_event_closure('figure_enter')\n",
+       "    );\n",
+       "    rubberband_canvas.addEventListener(\n",
+       "        'mouseleave',\n",
+       "        on_mouse_event_closure('figure_leave')\n",
+       "    );\n",
+       "\n",
+       "    canvas_div.addEventListener('wheel', function (event) {\n",
+       "        if (event.deltaY < 0) {\n",
+       "            event.step = 1;\n",
+       "        } else {\n",
+       "            event.step = -1;\n",
+       "        }\n",
+       "        on_mouse_event_closure('scroll')(event);\n",
+       "    });\n",
+       "\n",
+       "    canvas_div.appendChild(canvas);\n",
+       "    canvas_div.appendChild(rubberband_canvas);\n",
+       "\n",
+       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
+       "    this.rubberband_context.strokeStyle = '#000000';\n",
+       "\n",
+       "    this._resize_canvas = function (width, height, forward) {\n",
+       "        if (forward) {\n",
+       "            canvas_div.style.width = width + 'px';\n",
+       "            canvas_div.style.height = height + 'px';\n",
+       "        }\n",
+       "    };\n",
+       "\n",
+       "    // Disable right mouse context menu.\n",
+       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
+       "        event.preventDefault();\n",
+       "        return false;\n",
+       "    });\n",
+       "\n",
+       "    function set_focus() {\n",
+       "        canvas.focus();\n",
+       "        canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    window.setTimeout(set_focus, 100);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'mpl-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'mpl-button-group';\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'mpl-button-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
+       "        button.classList = 'mpl-widget';\n",
+       "        button.setAttribute('role', 'button');\n",
+       "        button.setAttribute('aria-disabled', 'false');\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "\n",
+       "        var icon_img = document.createElement('img');\n",
+       "        icon_img.src = '_images/' + image + '.png';\n",
+       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
+       "        icon_img.alt = tooltip;\n",
+       "        button.appendChild(icon_img);\n",
+       "\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    var fmt_picker = document.createElement('select');\n",
+       "    fmt_picker.classList = 'mpl-widget';\n",
+       "    toolbar.appendChild(fmt_picker);\n",
+       "    this.format_dropdown = fmt_picker;\n",
+       "\n",
+       "    for (var ind in mpl.extensions) {\n",
+       "        var fmt = mpl.extensions[ind];\n",
+       "        var option = document.createElement('option');\n",
+       "        option.selected = fmt === mpl.default_extension;\n",
+       "        option.innerHTML = fmt;\n",
+       "        fmt_picker.appendChild(option);\n",
+       "    }\n",
+       "\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
+       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
+       "    // which will in turn request a refresh of the image.\n",
+       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_message = function (type, properties) {\n",
+       "    properties['type'] = type;\n",
+       "    properties['figure_id'] = this.id;\n",
+       "    this.ws.send(JSON.stringify(properties));\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.send_draw_message = function () {\n",
+       "    if (!this.waiting) {\n",
+       "        this.waiting = true;\n",
+       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    var format_dropdown = fig.format_dropdown;\n",
+       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
+       "    fig.ondownload(fig, format);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
+       "    var size = msg['size'];\n",
+       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
+       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
+       "        fig.send_message('refresh', {});\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
+       "    var x0 = msg['x0'] / fig.ratio;\n",
+       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
+       "    var x1 = msg['x1'] / fig.ratio;\n",
+       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
+       "    x0 = Math.floor(x0) + 0.5;\n",
+       "    y0 = Math.floor(y0) + 0.5;\n",
+       "    x1 = Math.floor(x1) + 0.5;\n",
+       "    y1 = Math.floor(y1) + 0.5;\n",
+       "    var min_x = Math.min(x0, x1);\n",
+       "    var min_y = Math.min(y0, y1);\n",
+       "    var width = Math.abs(x1 - x0);\n",
+       "    var height = Math.abs(y1 - y0);\n",
+       "\n",
+       "    fig.rubberband_context.clearRect(\n",
+       "        0,\n",
+       "        0,\n",
+       "        fig.canvas.width / fig.ratio,\n",
+       "        fig.canvas.height / fig.ratio\n",
+       "    );\n",
+       "\n",
+       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
+       "    // Updates the figure title.\n",
+       "    fig.header.textContent = msg['label'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
+       "    fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
+       "    fig.message.textContent = msg['message'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
+       "    // Request the server to send over a new figure.\n",
+       "    fig.send_draw_message();\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
+       "    fig.image_mode = msg['mode'];\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
+       "    for (var key in msg) {\n",
+       "        if (!(key in fig.buttons)) {\n",
+       "            continue;\n",
+       "        }\n",
+       "        fig.buttons[key].disabled = !msg[key];\n",
+       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
+       "    if (msg['mode'] === 'PAN') {\n",
+       "        fig.buttons['Pan'].classList.add('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    } else if (msg['mode'] === 'ZOOM') {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.add('active');\n",
+       "    } else {\n",
+       "        fig.buttons['Pan'].classList.remove('active');\n",
+       "        fig.buttons['Zoom'].classList.remove('active');\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Called whenever the canvas gets updated.\n",
+       "    this.send_message('ack', {});\n",
+       "};\n",
+       "\n",
+       "// A function to construct a web socket function for onmessage handling.\n",
+       "// Called in the figure constructor.\n",
+       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
+       "    return function socket_on_message(evt) {\n",
+       "        if (evt.data instanceof Blob) {\n",
+       "            var img = evt.data;\n",
+       "            if (img.type !== 'image/png') {\n",
+       "                /* FIXME: We get \"Resource interpreted as Image but\n",
+       "                 * transferred with MIME type text/plain:\" errors on\n",
+       "                 * Chrome.  But how to set the MIME type?  It doesn't seem\n",
+       "                 * to be part of the websocket stream */\n",
+       "                img.type = 'image/png';\n",
+       "            }\n",
+       "\n",
+       "            /* Free the memory for the previous frames */\n",
+       "            if (fig.imageObj.src) {\n",
+       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
+       "                    fig.imageObj.src\n",
+       "                );\n",
+       "            }\n",
+       "\n",
+       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
+       "                img\n",
+       "            );\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        } else if (\n",
+       "            typeof evt.data === 'string' &&\n",
+       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
+       "        ) {\n",
+       "            fig.imageObj.src = evt.data;\n",
+       "            fig.updated_canvas_event();\n",
+       "            fig.waiting = false;\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        var msg = JSON.parse(evt.data);\n",
+       "        var msg_type = msg['type'];\n",
+       "\n",
+       "        // Call the  \"handle_{type}\" callback, which takes\n",
+       "        // the figure and JSON message as its only arguments.\n",
+       "        try {\n",
+       "            var callback = fig['handle_' + msg_type];\n",
+       "        } catch (e) {\n",
+       "            console.log(\n",
+       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
+       "                msg\n",
+       "            );\n",
+       "            return;\n",
+       "        }\n",
+       "\n",
+       "        if (callback) {\n",
+       "            try {\n",
+       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
+       "                callback(fig, msg);\n",
+       "            } catch (e) {\n",
+       "                console.log(\n",
+       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
+       "                    e,\n",
+       "                    e.stack,\n",
+       "                    msg\n",
+       "                );\n",
+       "            }\n",
+       "        }\n",
+       "    };\n",
+       "};\n",
+       "\n",
+       "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
+       "mpl.findpos = function (e) {\n",
+       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
+       "    var targ;\n",
+       "    if (!e) {\n",
+       "        e = window.event;\n",
+       "    }\n",
+       "    if (e.target) {\n",
+       "        targ = e.target;\n",
+       "    } else if (e.srcElement) {\n",
+       "        targ = e.srcElement;\n",
+       "    }\n",
+       "    if (targ.nodeType === 3) {\n",
+       "        // defeat Safari bug\n",
+       "        targ = targ.parentNode;\n",
+       "    }\n",
+       "\n",
+       "    // pageX,Y are the mouse positions relative to the document\n",
+       "    var boundingRect = targ.getBoundingClientRect();\n",
+       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
+       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
+       "\n",
+       "    return { x: x, y: y };\n",
+       "};\n",
+       "\n",
+       "/*\n",
+       " * return a copy of an object with only non-object keys\n",
+       " * we need this to avoid circular references\n",
+       " * https://stackoverflow.com/a/24161582/3208463\n",
+       " */\n",
+       "function simpleKeys(original) {\n",
+       "    return Object.keys(original).reduce(function (obj, key) {\n",
+       "        if (typeof original[key] !== 'object') {\n",
+       "            obj[key] = original[key];\n",
+       "        }\n",
+       "        return obj;\n",
+       "    }, {});\n",
+       "}\n",
+       "\n",
+       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
+       "    var canvas_pos = mpl.findpos(event);\n",
+       "\n",
+       "    if (name === 'button_press') {\n",
+       "        this.canvas.focus();\n",
+       "        this.canvas_div.focus();\n",
+       "    }\n",
+       "\n",
+       "    var x = canvas_pos.x * this.ratio;\n",
+       "    var y = canvas_pos.y * this.ratio;\n",
+       "\n",
+       "    this.send_message(name, {\n",
+       "        x: x,\n",
+       "        y: y,\n",
+       "        button: event.button,\n",
+       "        step: event.step,\n",
+       "        guiEvent: simpleKeys(event),\n",
+       "    });\n",
+       "\n",
+       "    /* This prevents the web browser from automatically changing to\n",
+       "     * the text insertion cursor when the button is pressed.  We want\n",
+       "     * to control all of the cursor setting manually through the\n",
+       "     * 'cursor' event from matplotlib */\n",
+       "    event.preventDefault();\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
+       "    // Handle any extra behaviour associated with a key event\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.key_event = function (event, name) {\n",
+       "    // Prevent repeat events\n",
+       "    if (name === 'key_press') {\n",
+       "        if (event.key === this._key) {\n",
+       "            return;\n",
+       "        } else {\n",
+       "            this._key = event.key;\n",
+       "        }\n",
+       "    }\n",
+       "    if (name === 'key_release') {\n",
+       "        this._key = null;\n",
+       "    }\n",
+       "\n",
+       "    var value = '';\n",
+       "    if (event.ctrlKey && event.key !== 'Control') {\n",
+       "        value += 'ctrl+';\n",
+       "    }\n",
+       "    else if (event.altKey && event.key !== 'Alt') {\n",
+       "        value += 'alt+';\n",
+       "    }\n",
+       "    else if (event.shiftKey && event.key !== 'Shift') {\n",
+       "        value += 'shift+';\n",
+       "    }\n",
+       "\n",
+       "    value += 'k' + event.key;\n",
+       "\n",
+       "    this._key_event_extra(event, name);\n",
+       "\n",
+       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
+       "    return false;\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
+       "    if (name === 'download') {\n",
+       "        this.handle_save(this, null);\n",
+       "    } else {\n",
+       "        this.send_message('toolbar_button', { name: name });\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
+       "    this.message.textContent = tooltip;\n",
+       "};\n",
+       "\n",
+       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
+       "// prettier-ignore\n",
+       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
+       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+       "\n",
+       "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
+       "\n",
+       "mpl.default_extension = \"png\";/* global mpl */\n",
+       "\n",
+       "var comm_websocket_adapter = function (comm) {\n",
+       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
+       "    // object with the appropriate methods. Currently this is a non binary\n",
+       "    // socket, so there is still some room for performance tuning.\n",
+       "    var ws = {};\n",
+       "\n",
+       "    ws.binaryType = comm.kernel.ws.binaryType;\n",
+       "    ws.readyState = comm.kernel.ws.readyState;\n",
+       "    function updateReadyState(_event) {\n",
+       "        if (comm.kernel.ws) {\n",
+       "            ws.readyState = comm.kernel.ws.readyState;\n",
+       "        } else {\n",
+       "            ws.readyState = 3; // Closed state.\n",
+       "        }\n",
+       "    }\n",
+       "    comm.kernel.ws.addEventListener('open', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('close', updateReadyState);\n",
+       "    comm.kernel.ws.addEventListener('error', updateReadyState);\n",
+       "\n",
+       "    ws.close = function () {\n",
+       "        comm.close();\n",
+       "    };\n",
+       "    ws.send = function (m) {\n",
+       "        //console.log('sending', m);\n",
+       "        comm.send(m);\n",
+       "    };\n",
+       "    // Register the callback with on_msg.\n",
+       "    comm.on_msg(function (msg) {\n",
+       "        //console.log('receiving', msg['content']['data'], msg);\n",
+       "        var data = msg['content']['data'];\n",
+       "        if (data['blob'] !== undefined) {\n",
+       "            data = {\n",
+       "                data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
+       "            };\n",
+       "        }\n",
+       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+       "        ws.onmessage(data);\n",
+       "    });\n",
+       "    return ws;\n",
+       "};\n",
+       "\n",
+       "mpl.mpl_figure_comm = function (comm, msg) {\n",
+       "    // This is the function which gets called when the mpl process\n",
+       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
+       "\n",
+       "    var id = msg.content.data.id;\n",
+       "    // Get hold of the div created by the display call when the Comm\n",
+       "    // socket was opened in Python.\n",
+       "    var element = document.getElementById(id);\n",
+       "    var ws_proxy = comm_websocket_adapter(comm);\n",
+       "\n",
+       "    function ondownload(figure, _format) {\n",
+       "        window.open(figure.canvas.toDataURL());\n",
+       "    }\n",
+       "\n",
+       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
+       "\n",
+       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
+       "    // web socket which is closed, not our websocket->open comm proxy.\n",
+       "    ws_proxy.onopen();\n",
+       "\n",
+       "    fig.parent_element = element;\n",
+       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
+       "    if (!fig.cell_info) {\n",
+       "        console.error('Failed to find cell for figure', id, fig);\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.cell_info[0].output_area.element.on(\n",
+       "        'cleared',\n",
+       "        { fig: fig },\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
+       "    var width = fig.canvas.width / fig.ratio;\n",
+       "    fig.cell_info[0].output_area.element.off(\n",
+       "        'cleared',\n",
+       "        fig._remove_fig_handler\n",
+       "    );\n",
+       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
+       "\n",
+       "    // Update the output cell to use the data from the current canvas.\n",
+       "    fig.push_to_output();\n",
+       "    var dataURL = fig.canvas.toDataURL();\n",
+       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
+       "    // the notebook keyboard shortcuts fail.\n",
+       "    IPython.keyboard_manager.enable();\n",
+       "    fig.parent_element.innerHTML =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "    fig.close_ws(fig, msg);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
+       "    fig.send_message('closing', msg);\n",
+       "    // fig.ws.close()\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
+       "    // Turn the data on the canvas into data in the output cell.\n",
+       "    var width = this.canvas.width / this.ratio;\n",
+       "    var dataURL = this.canvas.toDataURL();\n",
+       "    this.cell_info[1]['text/html'] =\n",
+       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.updated_canvas_event = function () {\n",
+       "    // Tell IPython that the notebook contents must change.\n",
+       "    IPython.notebook.set_dirty(true);\n",
+       "    this.send_message('ack', {});\n",
+       "    var fig = this;\n",
+       "    // Wait a second, then push the new image to the DOM so\n",
+       "    // that it is saved nicely (might be nice to debounce this).\n",
+       "    setTimeout(function () {\n",
+       "        fig.push_to_output();\n",
+       "    }, 1000);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._init_toolbar = function () {\n",
+       "    var fig = this;\n",
+       "\n",
+       "    var toolbar = document.createElement('div');\n",
+       "    toolbar.classList = 'btn-toolbar';\n",
+       "    this.root.appendChild(toolbar);\n",
+       "\n",
+       "    function on_click_closure(name) {\n",
+       "        return function (_event) {\n",
+       "            return fig.toolbar_button_onclick(name);\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    function on_mouseover_closure(tooltip) {\n",
+       "        return function (event) {\n",
+       "            if (!event.currentTarget.disabled) {\n",
+       "                return fig.toolbar_button_onmouseover(tooltip);\n",
+       "            }\n",
+       "        };\n",
+       "    }\n",
+       "\n",
+       "    fig.buttons = {};\n",
+       "    var buttonGroup = document.createElement('div');\n",
+       "    buttonGroup.classList = 'btn-group';\n",
+       "    var button;\n",
+       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
+       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
+       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
+       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
+       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
+       "\n",
+       "        if (!name) {\n",
+       "            /* Instead of a spacer, we start a new button group. */\n",
+       "            if (buttonGroup.hasChildNodes()) {\n",
+       "                toolbar.appendChild(buttonGroup);\n",
+       "            }\n",
+       "            buttonGroup = document.createElement('div');\n",
+       "            buttonGroup.classList = 'btn-group';\n",
+       "            continue;\n",
+       "        }\n",
+       "\n",
+       "        button = fig.buttons[name] = document.createElement('button');\n",
+       "        button.classList = 'btn btn-default';\n",
+       "        button.href = '#';\n",
+       "        button.title = name;\n",
+       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
+       "        button.addEventListener('click', on_click_closure(method_name));\n",
+       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
+       "        buttonGroup.appendChild(button);\n",
+       "    }\n",
+       "\n",
+       "    if (buttonGroup.hasChildNodes()) {\n",
+       "        toolbar.appendChild(buttonGroup);\n",
+       "    }\n",
+       "\n",
+       "    // Add the status bar.\n",
+       "    var status_bar = document.createElement('span');\n",
+       "    status_bar.classList = 'mpl-message pull-right';\n",
+       "    toolbar.appendChild(status_bar);\n",
+       "    this.message = status_bar;\n",
+       "\n",
+       "    // Add the close button to the window.\n",
+       "    var buttongrp = document.createElement('div');\n",
+       "    buttongrp.classList = 'btn-group inline pull-right';\n",
+       "    button = document.createElement('button');\n",
+       "    button.classList = 'btn btn-mini btn-primary';\n",
+       "    button.href = '#';\n",
+       "    button.title = 'Stop Interaction';\n",
+       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
+       "    button.addEventListener('click', function (_evt) {\n",
+       "        fig.handle_close(fig, {});\n",
+       "    });\n",
+       "    button.addEventListener(\n",
+       "        'mouseover',\n",
+       "        on_mouseover_closure('Stop Interaction')\n",
+       "    );\n",
+       "    buttongrp.appendChild(button);\n",
+       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
+       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
+       "    var fig = event.data.fig;\n",
+       "    if (event.target !== this) {\n",
+       "        // Ignore bubbled events from children.\n",
+       "        return;\n",
+       "    }\n",
+       "    fig.close_ws(fig, {});\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._root_extra_style = function (el) {\n",
+       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
+       "    // this is important to make the div 'focusable\n",
+       "    el.setAttribute('tabindex', 0);\n",
+       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
+       "    // off when our div gets focus\n",
+       "\n",
+       "    // location in version 3\n",
+       "    if (IPython.notebook.keyboard_manager) {\n",
+       "        IPython.notebook.keyboard_manager.register_events(el);\n",
+       "    } else {\n",
+       "        // location in version 2\n",
+       "        IPython.keyboard_manager.register_events(el);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
+       "    // Check for shift+enter\n",
+       "    if (event.shiftKey && event.which === 13) {\n",
+       "        this.canvas_div.blur();\n",
+       "        // select the cell after this one\n",
+       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
+       "        IPython.notebook.select(index + 1);\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
+       "    fig.ondownload(fig, null);\n",
+       "};\n",
+       "\n",
+       "mpl.find_output_cell = function (html_output) {\n",
+       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
+       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
+       "    // IPython event is triggered only after the cells have been serialised, which for\n",
+       "    // our purposes (turning an active figure into a static one), is too late.\n",
+       "    var cells = IPython.notebook.get_cells();\n",
+       "    var ncells = cells.length;\n",
+       "    for (var i = 0; i < ncells; i++) {\n",
+       "        var cell = cells[i];\n",
+       "        if (cell.cell_type === 'code') {\n",
+       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
+       "                var data = cell.output_area.outputs[j];\n",
+       "                if (data.data) {\n",
+       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
+       "                    data = data.data;\n",
+       "                }\n",
+       "                if (data['text/html'] === html_output) {\n",
+       "                    return [cell, data, j];\n",
+       "                }\n",
+       "            }\n",
+       "        }\n",
+       "    }\n",
+       "};\n",
+       "\n",
+       "// Register the function which deals with the matplotlib target/channel.\n",
+       "// The kernel may be null if the page has been refreshed.\n",
+       "if (IPython.notebook.kernel !== null) {\n",
+       "    IPython.notebook.kernel.comm_manager.register_target(\n",
+       "        'matplotlib',\n",
+       "        mpl.mpl_figure_comm\n",
+       "    );\n",
+       "}\n"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Javascript object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAgAElEQVR4Xu3dsYkWUQCFUf8GzEy2Fy1AzAW7UCNL2BUXxEhM7MECbEAQUwPbWEMLMFkWLh/MnFi4gdxd75z3Zrx8evL+5a8XX54/ffTq2d/Xt2+/P3539/Pm7ury+dvHD19vr/+8+fHbn/v70Q8/H34/+P3o3wf/PtoHx9lHFwPQwDXwDXwD38A38A18A/9cA98AJJyEl4A7AXAC4gTICZgTwJOdgBqABqABaAAagAagAWgAGoAG4LkI1B1HR+COwB2BOwJ3BO4I3BH42Y7ACSABJIAEkAASQAJIAAkgASSA3nI6zltOhJfwEl7CS3gJL+H9X3gJIAEkgASQABJAAkgACSABJIAEkAD6DqbvgPoOqu/AOkFwgnDkEwQCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASQkhISQEBJCQkhOJiQGoAFoABqABqABaAAagAagAXjnhMgJ0VFOiNwBdAfQHUAPOB5wPOB4wPGA4wHnZA84BqABaAAagAagAWgAGoAGoAHoJRDEj/iPQvzuMLnDdOQ7TPqt3/r98A+9E0ACSAAJIAEkgASQABJAAkgACSABJIA+A+MzMD4DQxgJ45GFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACQkhISSEhJAQkpMJiQFoABqABqABaAAagAagAWgA+hD0jROio5wQuQPoDqA7gB5wPOB4wPGA4wHHA87JHnAMQAPQADQADUAD0AA0AA1AA9BLIF4CQfxHIX53mNxhOvIdJv3Wb/32IWiCQTAIBsEgGCcTDAPQADQADUAD0AA0AA1AA9AA9JKGlzSunODc7zNe7gC6A+gOoDuAHqA8QHmA8gDlAepkD1AGoAFoABqABqABaAAagAagAeglEC+BeAnEEcL9jhDcwXIHyx2sh9/B8vPj56f8+SGABJAAEkACSAAJIAEkgASQABJAAkgACaD/C9j/BUzoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCVwrdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/hFAAkgACSABJIAEkAASQAJIAAkgASSABJAAEsC1wMgnjKUwEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASQEJH6EqhW/ePABJAAkgACSABJIAEkAASQAJIAAkgASSABJAArgVGPmEshZEAEkACSAAJIAEkgASQABJAAkgACSABJIAEkAASOkJXCt26fwSQABJAAkgACSABJIAEkAASQAJIAAkgASSABHAtMPIJYymMBJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEkNARulLo1v0jgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAa4GRTxhLYSSABJAAEkACSAAJIAEkgASQABJAAkgACSABJICEjtCVQrfuHwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgAVwLjHzCWAojASSABJAAEkACSAAJIAEkgASQABJAAkgACSABJHSErhS6df8IIAEkgASQABJAAkgACSABJIAEkAASQAJIAAngWmDkE8ZSGAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACSChI3Sl0K37RwAJIAEkgASQABJAAkgACSABJIAEkAASQAJIANcCI58wlsJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgACR2hK4Vu3T8CSAAJIAEkgASQABJAAkgACSABJIAEkAASQAK4Fhj5hLEURgJIAAkgASSABJAAEkACSAAJIAEkgASQABJAAkjoCF0pdOv+EUACSAAJIAEkgASQABJAAkgACSABJIAEkAASwLXAyCeMpTASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABJAQkfoSqFb948AEkACSAAJIAEkgASQABJAAkgACSABJIAEkACuBUY+YSyFkQASQAJIAAkgASSABJAAEkACSAAJIAEkgASQABI6QlcK3bp/BJAAEkACSAAJIAEkgASQABJAAkgACSABJIAEcC0w8gljKYwEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgASQ0BG6UujW/SOABJAAEkACSAAJIAEkgASQABJAAkgACSABJIBrgZFPGEthJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgISO0JVCt+4fASSABJAAEkACSAAJIAEkgASQABJAAkgACSABXAuMfMJYCiMBJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAEkdISuFLp1/wggASSABJAAEkACSAAJIAEkgASQABJAAkgACeBaYOQTxlIYCSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJIKEjdKXQrftHAAkgASSABJAAEkACSAAJIAEkgASQABJAAkgA1wIjnzCWwkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSAAJHaErhW7dPwJIAAkgASSABJAAEkACSAAJIAEkgASQABJAArgWGPmEsRRGAkgACSABJIAEkAASQAJIAAkgASSABJAAEkACSOgIXSl06/4RQAJIAAkgASSABJAAEkACSAAJIAEkgASQABLAtcDIJ4ylMBJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEkBCR+hKoVv3jwASQAJIAAkgASSABJAAEkACSAAJIAEkgASQAK4FRj5hLIWRABJAAkgACSABJIAEkAASQAJIAAkgASSABJAAEjpCV+MdWCAAAAGwSURBVArdun8EkAASQAJIAAkgASSABJAAEkACSAAJIAEkgARwLTDyCWMpjASQABJAAkgACSABJIAEkAASQAJIAAkgASSABJDQEbpS6Nb9I4AEkAASQAJIAAkgASSABJAAEkACSAAJIAEkgGuBkU8YS2EkgASQABJAAkgACSABJIAEkAASQAJIAAkgASSAhI7QlUK37h8BJIAEkAASQAJIAAkgASSABJAAEkACSAAJIAFcC4x8wlgKIwEkgASQABJAAkgACSABJIAEkAASQAJIAAkgASR0hK4UunX/CCABJIAEkAASQAJIAAkgASSABJAAEkACSAAJ4Fpg5BPGUhgJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkgoSN0pdCt+0cACSABJIAEkAASQAJIAAkgASSABJAAEkACSADXAiOfMJbCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJIAAkdoSuFbt0/AkgACSABJIAEkAASQAJIAAkgASSABJAAEkACuBYY+YSxFEYCSAAJIAEkgASQABJAAkgACSABJIAEkAASQAJI6AhdKXTr/v0DjI3sntE+YBwAAAAASUVORK5CYII=\" width=\"640\">"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "(<AxesSubplot:title={'center':'line mesh'}>,\n",
+       " <AxesSubplot:title={'center':'triangle mesh'}>)"
+      ]
+     },
+     "execution_count": 12,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "mesh.plot(lw=1, color_map={0: 'lightgray'}, legend='floating')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Interoperability\n",
+    "\n",
+    "The `MeshContainer` object can also be used to convert to various other library formats, such as:\n",
+    "\n",
+    "- [`trimesh.Trimesh`](https://trimsh.org/trimesh.base.html#trimesh.base.Trimesh)\n",
+    "- [`pyvista.UnstructuredGrid`](https://docs.pyvista.org/examples/00-load/create-unstructured-surface.html)\n",
+    "- [`meshio.Mesh`](https://docs.pyvista.org/examples/00-load/create-unstructured-surface.html)\n",
+    "\n",
+    "First, we must extract the triangle data:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "triangle_mesh = mesh.get('triangle')\n",
+    "\n",
+    "pv_mesh = triangle_mesh.to_pyvista_unstructured_grid()\n",
+    "trimesh_mesh = triangle_mesh.to_trimesh()\n",
+    "meshio_mesh = triangle_mesh.to_meshio()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "To save the data, use the `.write` method. This is essentially a very thin wrapper around `meshio`, equivalent to `meshio.write(...)`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #808000; text-decoration-color: #808000; font-weight: bold\">Warning:</span><span style=\"color: #808000; text-decoration-color: #808000\"> Appending zeros to replace the missing geometrical tag data.</span>\n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[1;33mWarning:\u001b[0m\u001b[33m Appending zeros to replace the missing geometrical tag data.\u001b[0m\n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "mesh.write('out.msh', file_format='gmsh22', binary=False)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "nanomesh",
+   "language": "python",
+   "name": "nanomesh"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.11"
+  },
+  "toc-autonumbering": false,
+  "toc-showcode": true,
+  "toc-showmarkdowntxt": false,
+  "toc-showtags": false
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}


Test patch:
diff --git a/notebooks/test_notebooks.PS1 b/notebooks/test_notebooks.PS1
index 461083dc..638a1f86 100644
--- a/notebooks/test_notebooks.PS1
+++ b/notebooks/test_notebooks.PS1
@@ -2,6 +2,7 @@ pytest --no-cov --nbmake `
    ".\other\make banner.ipynb" `
    ".\other\hello world.ipynb" `
    ".\examples\how_to_customize_regions_in_2d_mesh.ipynb" `
+   ".\how_to_generate_a_triangle_mesh_from_a_2d_image" `
    ".\examples\how_to_do_3d_multi-domain_meshing_using_simulated_data.ipynb" `
    ".\examples\how_to_generate_a_tetrahedral_mesh_from_3d_cells_image.ipynb" `
    ".\examples\how_to_generate_a_tetrahedral_mesh_from_a_3d_image.ipynb" `
diff --git a/notebooks/test_notebooks.sh b/notebooks/test_notebooks.sh
index 1730a10e..e93ec9a5 100644
--- a/notebooks/test_notebooks.sh
+++ b/notebooks/test_notebooks.sh
@@ -2,6 +2,7 @@ pytest --no-cov --nbmake \
    "./other/make banner.ipynb" \
    "./other/hello world.ipynb" \
    "./examples/how_to_customize_regions_in_2d_mesh.ipynb" \
+   "./examples/how_to_generate_a_triangle_mesh_from_a_2d_image" \
    "./examples/how_to_do_3d_multi-domain_meshing_using_simulated_data.ipynb" \
    "./examples/how_to_generate_a_tetrahedral_mesh_from_3d_cells_image.ipynb" \
    "./examples/how_to_generate_a_tetrahedral_mesh_from_a_3d_image.ipynb" \
diff --git a/tests/test_region_markers.py b/tests/test_region_markers.py
new file mode 100644
index 00000000..5ddd5dd6
--- /dev/null
+++ b/tests/test_region_markers.py
@@ -0,0 +1,63 @@
+import pytest
+
+from nanomesh import RegionMarker, RegionMarkerList
+
+
+class TestRegionMarkerList:
+
+    @pytest.fixture
+    def region_markers(self):
+        return RegionMarkerList((
+            RegionMarker(label=1, point=(1, 2), name='one'),
+            RegionMarker(label=1, point=(3, 4), name='one'),
+            RegionMarker(label=2, point=(5, 6), name='two'),
+            RegionMarker(label=2, point=(7, 8), name='two'),
+            RegionMarker(label=3, point=(9, 10), name='three'),
+        ))
+
+    def test_region_marker_list(self, region_markers):
+        assert isinstance(region_markers, RegionMarkerList)
+        assert all(isinstance(m, RegionMarker) for m in region_markers)
+
+    @pytest.mark.parametrize('old,new,expected', (
+        (1, 7, (7, 7, 2, 2, 3)),
+        (3, 2, (1, 1, 2, 2, 2)),
+        (0, 7, (1, 1, 2, 2, 3)),
+        ((1, 2), 7, (7, 7, 7, 7, 3)),
+        ((0, 2), 7, (1, 1, 7, 7, 3)),
+        (lambda x: x == 1, 7, (7, 7, 2, 2, 3)),
+        (lambda x: x > 1, 7, (1, 1, 7, 7, 7)),
+    ))
+    def test_relabel(self, old, new, expected, region_markers):
+        new_region_markers = region_markers.relabel(old, new)
+        labels = tuple(m.label for m in new_region_markers)
+        assert labels == expected
+
+    def test_relabel_name(self, region_markers):
+        new_region_markers = region_markers.relabel(1, 7)
+        assert new_region_markers.names == {'two', 'three', 'one'}
+
+    @pytest.mark.parametrize('old,expected', (
+        (1, (4, 5, 2, 2, 3)),
+        (3, (1, 1, 2, 2, 4)),
+        (0, (1, 1, 2, 2, 3)),
+        ((1, 2), (4, 5, 6, 7, 3)),
+        ((0, 2), (1, 1, 4, 5, 3)),
+        (lambda x: x == 1, (4, 5, 2, 2, 3)),
+        (lambda x: x > 1, (1, 1, 4, 5, 6)),
+    ))
+    def test_label_sequentially(self, old, expected, region_markers):
+        new_region_markers = region_markers.label_sequentially(old)
+        labels = tuple(m.label for m in new_region_markers)
+        assert labels == expected
+
+    def test_label_sequentially_name(self, region_markers):
+        new_region_markers = region_markers.label_sequentially(1,
+                                                               fmt_name='f{}')
+        assert new_region_markers.names == {'f4', 'f5', 'two', 'three'}
+
+    def test_labels_prop(self, region_markers):
+        assert region_markers.labels == {1, 2, 3}
+
+    def test_names_prop(self, region_markers):
+        assert region_markers.names == {'one', 'two', 'three'}
diff --git a/tests/test_tetgen.py b/tests/test_tetgen.py
index a6f4bc2b..da14a5ae 100644
--- a/tests/test_tetgen.py
+++ b/tests/test_tetgen.py
@@ -2,6 +2,7 @@
 import pytest
 from helpers import get_expected_if_it_exists
 
+from nanomesh import RegionMarker
 from nanomesh.mesh import TriangleMesh
 
 
@@ -86,8 +87,10 @@ def test_tetgen_volume2mesh(triangle_mesh):
     """Test 3D mesh generation."""
     expected_fn = 'expected_tetra_mesh.msh'
 
-    triangle_mesh.add_region_marker((10, np.array([0.5, 0.5, 0.5])))
-    triangle_mesh.add_region_marker((20, np.array([0.0, 2.0, 2.0])))
+    triangle_mesh.region_markers.append(
+        RegionMarker(10, np.array([0.5, 0.5, 0.5])))
+    triangle_mesh.region_markers.append(
+        RegionMarker(20, np.array([0.0, 2.0, 2.0])))
 
     mesh_container = triangle_mesh.tetrahedralize(opts='-pAq1.2')
 
diff --git a/tests/test_triangulate.py b/tests/test_triangulate.py
index b69c8706..943f188f 100644
--- a/tests/test_triangulate.py
+++ b/tests/test_triangulate.py
@@ -26,7 +26,7 @@ def test_triangulate_from_line_mesh(opts):
     ]
 
     line_mesh = LineMesh(points=points, cells=cells)
-    line_mesh.add_region_markers(region_markers)
+    line_mesh.region_markers.extend(region_markers)
 
     mesh = line_mesh.triangulate(opts=opts)