Source code for xrview.core.panel

import abc

from bokeh.document import Document
from bokeh.io import export_png, export_svgs
from bokeh.layouts import column, gridplot, row
from bokeh.models import Spacer


class BasePanel(object):
    """ Base class for all panels. """

    def __init__(self):

        self.layout = None
        self.doc = None

        self.handlers = None
        self.glyph_map = None
        self.figure_map = None
        self.figures = None

        # TODO: improve
        self.added_figures = []
        self.added_overlays = []
        self.added_overlay_figures = []
        self.added_annotations = []
        self.added_annotation_figures = []
        self.modifiers = []

    @abc.abstractmethod
    def make_layout(self):
        """ Make the layout. """

    @abc.abstractmethod
    def show(self, *args, **kwargs):
        """ Show the layout. """

    def _export(self, func, backend, filename):
        """ Export. """
        backends = []
        for f in self.figures:
            if hasattr(f, "output_backend"):
                backends.append(f.output_backend)
                f.output_backend = backend
        func(self.layout, filename=filename)
        for f in self.figures:
            if hasattr(f, "output_backend"):
                f.output_backend = backends.pop(0)

    def export(self, filename, mode="auto"):
        """ Export the layout as as png or svg file.

        Parameters
        ----------
        filename : str
            The path of the exported file.

        mode : 'auto', 'png' or 'svg', default 'auto'
            Whether to export as png or svg. Note that multi-figure layouts
            will be split into individual files for each figure in the svg
            mode. 'auto' will try to determine the mode automatically from
            the file extension.
        """
        if self.layout is None:
            self.make_layout()

        if mode == "auto":
            mode = filename.split(".")[-1]
            if mode not in ("png", "svg"):
                raise ValueError(
                    "Could not determine mode from file extension"
                )

        if mode == "png":
            # TODO: TEST
            for c in self.layout.children:
                if hasattr(c, "toolbar_location"):
                    c.toolbar_location = None
            self._export(export_png, "canvas", filename)
            # TODO: TEST
            for c in self.layout.children:
                if hasattr(c, "toolbar_location"):
                    c.toolbar_location = self.toolbar_location
        elif mode == "svg":
            self._export(export_svgs, "svg", filename)
        else:
            raise ValueError("Unrecognized mode")

    def make_doc(self):
        """ Make the document. """
        self.doc = Document()
        self.doc.theme = self.theme
        self.doc.add_root(row(self.layout))

    def copy(self, with_data=False):
        """ Create a copy of this instance.

        Parameters
        ----------
        with_data : bool, default False
            If true, also copy the data.

        Returns
        -------
        new : xrview.core.panel.BasePanel
            The copied object.
        """
        from copy import copy

        new = self.__new__(type(self))
        new.__dict__ = {
            k: (copy(v) if (k != "data" or with_data) else v)
            for k, v in self.__dict__.items()
        }

        return new


class GridPlot(BasePanel):
    """ Base class for grid plots. """

    def __init__(self, panels, ncols=1, toolbar_location="above"):
        """ Constructor. """
        self.panels = panels
        self.ncols = ncols
        self.toolbar_location = toolbar_location
        self.make_layout()

    def make_layout(self):
        """ Make the layout. """
        self.figures = []
        for p in self.panels:
            if p.layout is None:
                p.make_layout()
            # TODO: TEST
            for c in p.layout.children:
                if hasattr(c, "toolbar_location"):
                    c.toolbar_location = None
            self.figures += p.figures

        self.layout = gridplot(
            [p.layout for p in self.panels],
            ncols=self.ncols,
            toolbar_location=self.toolbar_location,
        )

        return self.layout


class SpacerPanel(BasePanel):
    """ Base class for spacers. """

    def __init__(self):
        """ Constructor. """
        self.figures = [Spacer()]
        self.make_layout()

    def make_layout(self):
        """ Make the layout. """
        self.layout = column(*self.figures)
        return self.layout