Source code for dna_features_viewer.GraphicRecord.BokehPlottableMixin

try:
    from bokeh.plotting import figure, ColumnDataSource
    from bokeh.models import Range1d, HoverTool

    BOKEH_AVAILABLE = True
except ImportError:
    BOKEH_AVAILABLE = False

try:
    import pandas as pd

    PANDAS_AVAILABLE = True
except ImportError:
    PANDAS_AVAILABLE = False

import matplotlib.pyplot as plt


class BokehPlottableMixin:
    def bokeh_feature_patch(
        self,
        start,
        end,
        strand,
        figure_width=5,
        width=0.4,
        level=0,
        arrow_width_inches=0.05,
        **kwargs
    ):
        """Return a dict with points coordinates of a Bokeh Feature arrow.

        Parameters
        ----------

        start, end, strand

        """
        hw = width / 2.0
        x1, x2 = (start, end) if (strand >= 0) else (end, start)
        bp_per_width = figure_width / self.sequence_length
        delta = arrow_width_inches / bp_per_width
        if strand >= 0:
            head_base = max(x1, x2 - delta)
        else:
            head_base = min(x1, x2 + delta)
        result = dict(
            xs=[x1, x1, head_base, x2, head_base, x1],
            ys=[e + level for e in [-hw, hw, hw, 0, -hw, -hw]],
        )
        result.update(kwargs)
        return result

    def plot_with_bokeh(self, figure_width=5, figure_height="auto"):
        """Plot the graphic record using Bokeh.

        Examples
        --------

        >>>


        """
        if not BOKEH_AVAILABLE:
            raise ImportError("``plot_with_bokeh`` requires Bokeh installed.")
        if not PANDAS_AVAILABLE:
            raise ImportError("``plot_with_bokeh`` requires Pandas installed.")

        # FIRST PLOT WITH MATPLOTLIB AND GATHER INFOS ON THE PLOT
        ax, (features_levels, plot_data) = self.plot(figure_width=figure_width)
        width, height = [int(100 * e) for e in ax.figure.get_size_inches()]
        plt.close(ax.figure)
        if figure_height == "auto":
            height = int(0.5 * height)
        else:
            height = 100 * figure_height
        max_y = max(
            [data["annotation_y"] for f, data in plot_data.items()]
            + list(features_levels.values())
        )

        # BUILD THE PLOT ()
        hover = HoverTool(tooltips="@hover_html")
        plot = figure(
            plot_width=width,
            plot_height=height,
            tools=[hover, "xpan,xwheel_zoom,reset,tap"],
            x_range=Range1d(0, self.sequence_length),
            y_range=Range1d(-1, max_y + 1),
        )
        plot.patches(
            xs="xs",
            ys="ys",
            color="color",
            line_color="#000000",
            source=ColumnDataSource(
                pd.DataFrame.from_records(
                    [
                        self.bokeh_feature_patch(
                            feature.start,
                            feature.end,
                            feature.strand,
                            figure_width=figure_width,
                            level=level,
                            color=feature.color,
                            label=feature.label,
                            hover_html=(
                                feature.html
                                if feature.html is not None
                                else feature.label
                            ),
                        )
                        for feature, level in features_levels.items()
                    ]
                )
            ),
        )

        if plot_data != {}:
            plot.text(
                x="x",
                y="y",
                text="text",
                text_align="center",
                text_font_size="12px",
                text_font="arial",
                text_font_style="normal",
                source=ColumnDataSource(
                    pd.DataFrame.from_records(
                        [
                            dict(
                                x=feature.x_center,
                                y=pdata["annotation_y"],
                                text=feature.label,
                                color=feature.color,
                            )
                            for feature, pdata in plot_data.items()
                        ]
                    )
                ),
            )
            plot.segment(
                x0="x0",
                x1="x1",
                y0="y0",
                y1="y1",
                line_width=0.5,
                color="#000000",
                source=ColumnDataSource(
                    pd.DataFrame.from_records(
                        [
                            dict(
                                x0=feature.x_center,
                                x1=feature.x_center,
                                y0=pdata["annotation_y"],
                                y1=pdata["feature_y"],
                            )
                            for feature, pdata in plot_data.items()
                        ]
                    )
                ),
            )

        plot.yaxis.visible = False
        plot.outline_line_color = None
        plot.grid.grid_line_color = None
        plot.toolbar.logo = None

        return plot