# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/plotting.core.ipynb (unless otherwise specified).

__all__ = ['barplot', 'histogram', 'heatmap', 'time_arc_plot', 'table_from_dataframe', 'stacked_hist',
           'categorical_2d_histogram', 'categorical_2d_histogram_with_gui', 'scatter_plot_with_gui', 'plots_as_matrix']

# Cell
import datetime
from typing import List, Union, Iterable, Tuple, Optional

import numpy as np
import pandas as pd
from pandas.api.types import is_numeric_dtype
import bokeh
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, TableColumn, DataTable, LinearColorMapper, ColorBar
from bokeh.models.widgets import HTMLTemplateFormatter
from bokeh.palettes import Viridis, viridis, Category20
from bokeh.transform import factor_cmap
from bokeh.layouts import gridplot
from bokeh import events
import panel as pn
import panel.widgets as pnw

from .utils import *

# Cell
def barplot(counts: Union[np.ndarray, List[np.ndarray]], values: Union[np.ndarray, List[np.ndarray]], bar_type: Union["horizontal", "vertical"] = "horizontal", linked_axis=True, width: int = 500, height: int = 500, **kwargs) -> bokeh.plotting.Figure:
    """Creates a figure with a barplot, were the counts is the bar height and values are the labels for the bars.
    Input can be a numpy array or a list of numpy array to create multiple plots.
    """
    if isinstance(counts, list) and isinstance(values, list):
        plot_list = []
        for counts_element, values_element in zip(counts, values):
            values_element = [str(entry) for entry in values_element]
            if bar_type == "horizontal":
                if len(plot_list) == 0:
                    p = figure(width=width, height=height, y_range=values_element)
                    p.hbar(y=values_element, left=0, right=counts_element, height=0.9)
                else:
                    if linked_axis:
                        p = figure(width=width, height=height, y_range=plot_list[0].y_range, x_range=plot_list[0].x_range)
                    else:
                        p = figure(width=width, height=height, y_range=values_element)
                    p.hbar(y=values_element, left=0, right=counts_element, height=0.9)
            elif bar_type == "vertical":
                if len(plot_list) == 0:
                    p = figure(width=width, height=height, x_range=values_element)
                    p.vbar(x=values_element, bottom=0, top=counts_element, width=0.9)
                else:
                    if linked_axis:
                        p = figure(width=width, height=height, x_range=plot_list[0].x_range, y_range=plot_list[0].y_range)
                    else:
                        p = figure(width=width, height=height, x_range=values_element)
                    p.vbar(x=values_element, bottom=0, top=counts_element, width=0.9)
            else:
                raise ValueError("hist_type has to be of 'horizontal' or 'vertical'")
            plot_list.append(p)
        return plot_list
    elif isinstance(counts, np.ndarray) and isinstance(values, np.ndarray):
        values = [str(entry) for entry in values]
        if bar_type == "horizontal":
            p = figure(width=width, height=height, y_range=values)
            p.hbar(y=values, left=0, right=counts, height=0.9)
        elif bar_type == "vertical":
            p = figure(width=width, height=height, x_range=values)
            p.vbar(x=values, bottom=0, top=counts, width=0.9)
        else:
            raise ValueError("hist_type has to be of 'horizontal' or 'vertical'")
        return p
    else:
        raise TypeError("counts and values need to be of the same type (list or np.ndarray)")

# Cell
def histogram(values: Union[np.ndarray, List[np.ndarray]], bins: int = 10, range: Tuple[int] = None, density: bool = False, remove_tools: bool = False, linked_axis=True, title="", x_label="", y_label="", orientation="horizontal", width: int = 500, height: int = 500) -> bokeh.plotting.Figure:
    "Creates a histogram."
    if isinstance(values, np.ndarray):
        if orientation == "vertical":
            x_label, y_label = y_label, x_label
        p = figure(width=width, height=height, title=title, x_axis_label=x_label, y_axis_label=y_label)
        counts, edges = np.histogram(values, bins=bins, range=range, density=density)
        if orientation == "horizontal":
            p.quad(top=counts, bottom=0, left=edges[:-1], right=edges[1:])
        elif orientation == "vertical":
            p.quad(top=edges[1:], bottom=edges[:-1], left=0, right=counts)
        if remove_tools:
            p.toolbar.logo = None
            p.toolbar_location = None
        return p
    elif isinstance(values, list):
        if not isinstance(title, list):
            title = [title]*len(values)
        if not isinstance(x_label, list):
            x_labels = [x_label]*len(values)
        if not isinstance(y_label, list):
            y_labels = [y_label]*len(values)
        if orientation == "vertical":
            x_labels, y_labels = y_labels, x_labels
        plot_list = []
        for values_element, plot_title, x_label, y_label in zip(values, title, x_labels, y_labels):
            if len(plot_list) == 0 or linked_axis == False:
                p = figure(width=width, height=height, title=plot_title, x_axis_label=x_label, y_axis_label=y_label)
            else:
                p = figure(width=width, height=height, x_range=plot_list[0].x_range, y_range=plot_list[0].y_range, title=plot_title, x_axis_label=x_label, y_axis_label=y_label)
            counts, edges = np.histogram(values_element, bins=bins, range=range, density=density)
            if orientation == "horizontal":
                p.quad(top=counts, bottom=0, left=edges[:-1], right=edges[1:])
            elif orientation == "vertical":
                p.quad(top=edges[1:], bottom=edges[:-1], left=0, right=counts)
            if remove_tools:
                p.toolbar.logo = None
                p.toolbar_location = None
            plot_list.append(p)
        return plot_list
    else:
        raise TypeError("values has to be of type list or np.ndarray")

# Cell
def heatmap(data: Union[pd.DataFrame, List[pd.DataFrame]], col_x: str, col_y: str, col_values: str, color_mapper: Optional[bokeh.models.ColorMapper] = None, normalize: str = "None", link_plots: bool = True, height: int = 500, width: int = 500) -> bokeh.plotting.Figure:
    def _heatmap(df: pd.DataFrame, col_x: str, col_y: str, col_values: str, color_mapper: Optional[bokeh.models.ColorMapper] = None, normalize: str = "None", height: int = 500, width: int = 500, x_range=None, y_range=None):
        df = df.copy()

        # normalize values
        if normalize == "Column":
            for name in df[col_x].unique():
                df.loc[(df[col_x] == name, col_values)] /= df[df[col_x] == name][col_values].sum()
        elif normalize == "Row":
            for name in df[col_y].unique():
                df.loc[(df[col_y] == name, col_values)] /= df[df[col_y] == name][col_values].sum()

        # ensure the x and y column are in a categorical format
        if df[col_x].dtype != str or df[col_y].dtype != str:
            df = df[[col_x, col_y, col_values]].copy()
            df[col_x] = df[col_x].astype(str)
            df[col_y] = df[col_y].astype(str)

        if x_range is not None and y_range is not None:
            p = figure(x_range=x_range, y_range=y_range, x_axis_location="above", tools="hover", toolbar_location=None, tooltips=[('', '@'+col_values)], width=width, height=height)
        elif x_range is not None and y_range is None:
            p = figure(x_range=x_range, y_range=sorted(df[col_y].unique()), x_axis_location="above", tools="hover", toolbar_location=None, tooltips=[('', '@'+col_values)], width=width, height=height)
        elif x_range is None and y_range is not None:
            p = figure(x_range=sorted(df[col_x].unique())[::-1], y_range=y_range, x_axis_location="above", tools="hover", toolbar_location=None, tooltips=[('', '@'+col_values)], width=width, height=height)
        else:
            p = figure(x_range=sorted(df[col_x].unique())[::-1], y_range=sorted(df[col_y].unique()), x_axis_location="above", tools="hover", toolbar_location=None, tooltips=[('', '@'+col_values)], width=width, height=height)

        p.grid.grid_line_color = None
        p.axis.axis_line_color = None
        p.axis.major_tick_line_color = None
        p.axis.major_label_standoff = 0

        p.rect(
            x=col_x, y=col_y, width=1, height=1, source=df,
            fill_color={'field': col_values, 'transform': color_mapper},
            line_color=None
        )
        return p

    def _create_colorbar_and_color_mapper(min_val, max_val, color_mapper):
        # create color bar
        if color_mapper is None:
            color_mapper = LinearColorMapper(palette=Viridis[256], low=min_val, high=max_val)

        color_bar = ColorBar(
            color_mapper=color_mapper, major_label_text_font_size="7px",
            label_standoff=6, border_line_color=None, location=(0, 0)
        )
        return color_bar, color_mapper

    # handel multiple inputs here
    if isinstance(data, list):
        plot_list = []
        if link_plots:
            min_value = min(df[col_values].min() for df in data)
            max_value = max(df[col_values].max() for df in data)
            color_bar, color_mapper = _create_colorbar_and_color_mapper(min_value, max_value, color_mapper)
        for df in data:
            if not link_plots:
                color_bar, color_mapper = _create_colorbar_and_color_mapper(df[col_values].min(), df[col_values].max(), None)
            temp_heatmap = _heatmap(df, col_x, col_y, col_values, color_mapper, normalize, height, width)
            if not link_plots:
                temp_heatmap.add_layout(color_bar, 'right')
            plot_list.append(temp_heatmap)
        if link_plots:
            c_bar_figure = figure(width=65, height=height, toolbar_location=None, min_border=0, outline_line_color=None)
            c_bar_figure.add_layout(color_bar, 'right')
            plot_list.append(c_bar_figure)
        return plot_list
    elif isinstance(data, pd.DataFrame):
        color_bar, color_mapper = _create_colorbar_and_color_mapper(data[col_values].min(), data[col_values].max(), color_mapper)
        p = _heatmap(data, col_x, col_y, col_values, color_mapper, normalize, height, width)
        p.add_layout(color_bar, 'right')
        return p
    else:
        raise TypeError("Data has to be of type list or pd.Dataframe")

# Cell
def time_arc_plot(start_dates: pd.core.series.Series, end_dates: Iterable[datetime.datetime], width: int = 500, height: int  = 300) -> bokeh.plotting.Figure:
    """Creates a plot of arcs where each ark spans from a start date to an end date."""
    radius = (end_dates-start_dates)/2
    x = start_dates + radius
    p = figure(x_axis_label="Date", x_axis_type='datetime', y_range=(0, radius.max()), x_range=(start_dates.min(), end_dates.max()), width=width, height=height, tools="")
    p.yaxis.major_tick_line_color = None
    p.yaxis.minor_tick_line_color = None
    p.yaxis.major_label_text_font_size = '0pt'
    p.toolbar.logo = None
    p.toolbar_location = None
    p.arc(x=x, y=0, radius=radius, start_angle=0, end_angle=np.pi)
    return p

# Cell
def table_from_dataframe(data: Union[pd.DataFrame, List[pd.DataFrame]], columns: List[str] = None, width: int = 500, height: int = None, index_position: int = None, **kwargs) -> pn.widgets.tables.DataFrame:
    if height is None:
        upper_bound = data.shape[0] if isinstance(data, pd.DataFrame) else data[0].shape[0]
        alternative_bound = data.shape[0] if isinstance(data, pd.DataFrame) else data[0].shape[0]
        height = 25*6 if upper_bound > 5 else 25*(alternative_bound+1)

    def _create_table(df, columns, width, height):
        if columns is None:
            columns = df.columns
        selection = df[columns]
        template = """<span href="#" data-toggle="tooltip" title="<%= value %>"><%= value %></span>"""
        table = pnw.DataFrame(selection, formatters={key: HTMLTemplateFormatter(template=template) for key in selection.columns}, selection=[0], width=width, height=height, **kwargs)
        return table

    if isinstance(data, pd.DataFrame):
        return _create_table(data, columns, width, height)
    elif isinstance(data, list):
        table_list = [_create_table(df, columns, width, height) for df in data]
        return table_list
    else:
        raise TypeError("data has to be of type list or pd.DataFrame")

# Cell
def stacked_hist(data: Union[list, pd.DataFrame], x_col: str, stack_col: str, x_label: str = "", link_plots=True, width: int = 500, height: int = 500):
    """If normalize is True the biggest col is set to 1 and all other cols are scaled accordingly."""
    def _stacked_hist(dataframe, x_col, stack_col, x_label, width, height, x_range=None, y_range=None):
        data = {"x": sorted(dataframe[x_col].unique().astype(str))}
        for stack_value in dataframe[stack_col].unique():
            x_values = dataframe.groupby(stack_col).get_group(stack_value).groupby(x_col).count()
            data[str(stack_value)] = [float(x_values[x_values.index == i][stack_col].values) if i in x_values.index else 0 for i in sorted(dataframe[x_col].unique())]
        colors =  Category20[dataframe[stack_col].nunique()] if (2 < dataframe[stack_col].nunique() < 21) else viridis(dataframe[stack_col].nunique())
        legend = [stack_col+": "+i for i in dataframe[stack_col].unique().astype(str).tolist()]

        if x_range is None and y_range is None:
            p = figure(x_range=sorted(dataframe[x_col].unique().astype(str)), height=height, width=width, toolbar_location=None, tools="", tooltips="$name: @$name")
        elif x_range is None and y_range is not None:
            p = figure(x_range=sorted(dataframe[x_col].unique().astype(str)), y_range=y_range, height=height, width=width, toolbar_location=None, tools="", tooltips="$name: @$name")
        elif x_range is not None and y_range is None:
            p = figure(x_range=x_range, height=height, width=width, toolbar_location=None, tools="", tooltips="$name: @$name")
        else:
            p = figure(x_range=x_range, y_range=y_range, height=height, width=width, toolbar_location=None, tools="", tooltips="$name: @$name")
        p.vbar_stack(sorted(dataframe[stack_col].unique().astype(str)), x="x", source=pd.DataFrame(data), color=colors, width=0.8, legend_label=legend)
        p.xaxis.axis_label = x_label
        p.yaxis.axis_label = "Counts"
        p.js_on_event(events.DoubleTap, toggle_legend_js(p))
        p.legend.click_policy="hide"
        return p

    if isinstance(data, pd.DataFrame):
        return _stacked_hist(data, x_col, stack_col, x_label, width, height)
    elif isinstance(data, list):
        plot_list = []
        for df in data:
            if link_plots and len(plot_list) > 0:
                plot_list.append(_stacked_hist(df, x_col, stack_col, x_label, width, height, plot_list[0].x_range, plot_list[0].y_range))
            else:
                plot_list.append(_stacked_hist(df, x_col, stack_col, x_label, width, height))
        return plot_list
    else:
        raise TypeError("data has to be of type list or pd.Dataframe")

# Cell
def categorical_2d_histogram(data: pd.DataFrame, category_col: str, hist_col: str, bins=10, range=None, normalize=False, precision=2, color_mapper=None, hist_col_is_categorical=False, width=500, height=500):
    """Creates a 2d histogram with the y axis being a category and the x axis the value each category is histogramed over."""
    def _create_colorbar_and_color_mapper(min_val, max_val, color_mapper):
        # create color bar
        if color_mapper is None:
            color_mapper = LinearColorMapper(palette=Viridis[256], low=min_val, high=max_val)

        color_bar = ColorBar(
            color_mapper=color_mapper, major_label_text_font_size="7px",
            label_standoff=6, border_line_color=None, location=(0, 0)
        )
        return color_bar, color_mapper

    def _categorical_2d_histogram(dataframe, category_col, hist_col, bins, range, normalize, precision, color_mapper, hist_col_is_categorical, width, height):
        data = {"x": np.array([]), "y": [], "z": np.array([])}
        hist_min = dataframe[hist_col].min() if range is None else range[0]
        hist_max = dataframe[hist_col].max() if range is None else range[1]
        if hist_max < hist_min:
            hist_max = hist_min*1.1
        for group in dataframe.groupby(category_col):
            if hist_col_is_categorical:
                bins = dataframe[hist_col].nunique()
                unique_values = dataframe[hist_col].unique()
                x, z = np.unique(group[1][hist_col], return_counts=True)
                data["y"] += [str(group[0])]*bins
                z = np.array([float(z[x == i]) if i in x else 0 for i in unique_values])
                if normalize:
                    z = z/z.sum()
                data["x"] = np.append(data["x"], unique_values.astype(str))
                data["z"] = np.append(data["z"],z)
            else:
                z,x = np.histogram(group[1][hist_col], bins=bins, range=(hist_min, hist_max))
                if normalize:
                    z = z/z.sum()
                data["y"] += [str(group[0])]*bins
                data["x"] = np.append(data["x"],x[:-1].round(precision).astype(str))
                data["z"] = np.append(data["z"],z)
        df = pd.DataFrame(data)

        color_bar, color_mapper = _create_colorbar_and_color_mapper(data["z"].min(), data["z"].max(), color_mapper)

        # create the 2d histogram
        p = figure(
            x_range=sorted(df["x"].unique().astype(str)), y_range=sorted(df["y"].unique().astype(str)),
            x_axis_location="above", tools="hover", toolbar_location=None, tooltips=[('', '@z')],
            width=width, height=height, x_axis_label=hist_col, y_axis_label=category_col
        )
        p.grid.grid_line_color = None
        p.axis.axis_line_color = None
        p.axis.major_tick_line_color = None
        p.axis.major_label_standoff = 0

        p.rect(
            x="x", y="y", width=1, height=1, source=df,
            fill_color={'field': "z", 'transform': color_mapper},
            line_color=None
        )

        p.add_layout(color_bar, 'right')

        return p

    if isinstance(data, pd.DataFrame):
        return _categorical_2d_histogram(data, category_col, hist_col, bins, range, normalize, precision, color_mapper, hist_col_is_categorical, width, height)
    elif isinstance(data, list):
        plot_list = [_categorical_2d_histogram(df, category_col, hist_col, bins, range, normalize, precision, color_mapper, hist_col_is_categorical, width, height) for df in data]
        return plot_list
    else:
        raise TypeError("data has to be of type ")

# Cell
def categorical_2d_histogram_with_gui(data: pd.DataFrame, category_cols=None, hist_cols=None, width=500, height=500):
    """Creates a categorical_2d_histogram for a dataframe, where each option (except width, height and color_mapper) of the categorical_2d_histogram can be set with gui elements.
    If the input is a list all dataframes need to have the same cols as the first dataframe in the list"""
    if category_cols is None and isinstance(data, pd.DataFrame):
        category_cols = data.columns.tolist()
    elif category_cols is None and isinstance(data, list):
        category_cols = data[0].columns.tolist()
    if hist_cols is None and isinstance(data, pd.DataFrame):
        hist_cols = [col_name for col_name in data.columns if is_numeric_dtype(data[col_name])]
    elif hist_cols is None and isinstance(data, list):
        hist_cols = [col_name for col_name in data[0].columns if is_numeric_dtype(data[0][col_name])]
    x_select = pnw.Select(name="X-Axis", options=hist_cols)
    y_select = pnw.Select(name="Y-Axis", options=category_cols)

    axis_selector = pn.Row(x_select, y_select, width=width)

    x_is_categorical = pnw.Checkbox(name="X is categorical", value=False)
    x_bins = pnw.IntInput(name="Bins", start=1, end=500, value=10, disabled=False)
    if isinstance(data, pd.DataFrame):
        x_range_start=data[x_select.value].min()
        x_range_end=data[x_select.value].max()
        x_range_end=x_range_end if x_range_end > x_range_start else x_range_end*1.1
        x_range_step=(x_range_end - x_range_start) / 50
    elif isinstance(data, list):
        x_range_start=min(df[x_select.value].min() for df in data)
        x_range_end=max(df[x_select.value].max() for df in data)
        x_range_end=x_range_end if x_range_end > x_range_start else x_range_end*1.1
        x_range_step=(x_range_end - x_range_start) / 50
    x_range = pnw.RangeSlider(name="X-Axis Range", start=x_range_start, end=x_range_end, step=x_range_step, disabled=False)

    x_axis_configuration = pn.Row(x_range, x_bins, width=width)

    normalize_rows = pnw.Checkbox(name="Normalize rows", value=False)
    x_precision = pnw.IntInput(name="Precision", start=0, end=10, value=2, disabled=False)

    additional_parameters = pn.Row(x_is_categorical, normalize_rows, x_precision, width=width)

    config_gui = pn.Column(axis_selector, additional_parameters, x_axis_configuration, align="center")

    @pn.depends(y_select.param.value, x_select.param.value, x_bins.param.value_throttled, x_range.param.value_throttled, x_is_categorical.param.value, normalize_rows.param.value, x_precision.param.value)
    def _plot(category_col, hist_col, bins, range, x_is_categorical, normalize_rows, precision):
        if x_is_categorical:
            x_range.disabled = True
            x_precision.disabled = True
            x_bins.disabled = True
        else:
            x_range.disabled = False
            x_precision.disabled = False
            x_bins.disabled = False

        if isinstance(data, pd.DataFrame):
            if data[hist_col].min() != x_range.start or data[hist_col].max() != x_range.end:
                x_range.start = data[hist_col].min()
                x_range.end = data[hist_col].max() if data[hist_col].max() > x_range.start else data[hist_col].max()*1.1
                x_range.value = (x_range.start, x_range.end)
                x_range.step = (x_range.end-x_range.start)/50
        elif isinstance(data, list):
            if min(df[hist_col].min() for df in data) != x_range.start or max(df[hist_col].max() for df in data) != x_range.end:
                x_range.start = min(df[hist_col].min() for df in data)
                x_range.end = max(df[hist_col].max() for df in data) if max(df[hist_col].max() for df in data) > x_range.start else max(df[hist_col].max() for df in data)*1.1
                x_range.value = (x_range.start, x_range.end)
                x_range.step = (x_range.end-x_range.start)/50
        range = x_range.value

        if isinstance(data, list):
            plot = pn.Row(*categorical_2d_histogram(data, category_col, hist_col, bins, range, normalize_rows, precision, color_mapper=None, hist_col_is_categorical=x_is_categorical, width=width, height=height))
        elif isinstance(data, pd.DataFrame):
            plot = pn.Row(categorical_2d_histogram(data, category_col, hist_col, bins, range, normalize_rows, precision, color_mapper=None, hist_col_is_categorical=x_is_categorical, width=width, height=height))
        return plot

    return pn.Column(config_gui, _plot)

# Cell
def scatter_plot_with_gui(dataframe: pd.DataFrame, x_cols: list = None, y_cols: list = None, color_cols: list=None, with_hist: bool=True, width=500, height=500):
    x_cols = list(dataframe.columns) if x_cols is None else x_cols
    y_cols = list(dataframe.columns) if y_cols is None else y_cols

    divider = 2

    if color_cols is not None:
        divider = 3
        color_select = pnw.Select(name="Color", options=color_cols, height=50, width=width//divider)

    x_select = pnw.Select(name="X-Axis", options=x_cols, height=50, width=width//divider)
    y_select = pnw.Select(name="Y-Axis", options=y_cols, height=50, width=width//divider)

    if color_cols is None:
        gui = pn.Column(pn.Row(x_select, y_select), None)
    else:
        gui = pn.Column(pn.Row(x_select, y_select, color_select), None)

    def plot(event):
        x_col = x_select.value
        y_col = y_select.value
        df_copy = dataframe.copy()
        if color_cols is not None:
            color_col = color_select.value
            df_copy[color_col] = df_copy[color_col].astype(str)
        source = ColumnDataSource(df_copy)

        if color_cols is not None:
            if 3 <= df_copy[color_col].nunique() <= 20:
                colors = Category20[df_copy[color_col].nunique()]
            else:
                colors = viridis(df_copy[color_col].nunique())
        else:
            colors="navy"
        tooltips = [(color_col, f"@{color_col}")] if color_cols is not None else None

        p = figure(width=width, height=height-50, x_axis_label=x_col, y_axis_label=y_col, tooltips=tooltips)
        # radius = min(df_copy[x_col].max()-df_copy[x_col].min(), df_copy[y_col].max()-df_copy[y_col].min())/75 * max(1, np.log(max(df_copy[x_col].max()-df_copy[x_col].min(), df_copy[y_col].max()-df_copy[y_col].min()))/20)
        if color_cols is not None:
            for index, entry in enumerate(df_copy[color_col].unique()):
                local_source = df_copy[df_copy[color_col] == entry]
                p.scatter(x=x_col, y=y_col, legend_label=entry, source=local_source, color=colors[index])
            p.js_on_event(events.DoubleTap, toggle_legend_js(p))
            p.legend.click_policy="hide"
        else:
            p.scatter(x=x_col, y=y_col, source=source)

        if with_hist:
            hist_x = histogram(df_copy[x_col].values, 30, remove_tools=True, height=height//5, width=width)
            hist_x.x_range = p.x_range
            hist_y = histogram(df_copy[y_col].values, 30, remove_tools=True, orientation="vertical", width=width//5, height=height//7*6)
            hist_y.y_range = p.y_range
            gui[-1] = gridplot([[hist_x, None], [p, hist_y]], merge_tools=True)
        else:
            gui[-1] = p
    plot(None)

    if color_cols is not None:
        color_select.param.watch(plot, "value")
    x_select.param.watch(plot, "value")
    y_select.param.watch(plot, "value")

    return gui

# Cell
def plots_as_matrix(plots, ncols, nrows, width=500, height=500):
    """Takes a list of plots and puts them into a matrix"""
    plot_matrix = pn.GridSpec(ncols=ncols, nrows=nrows, width=width, height=height)
    for index, plot in enumerate(plots):
        row_pos = index//ncols
        col_pos = index%ncols
        if row_pos >= nrows:
            break
        else:
            plot_matrix[row_pos, col_pos] = plot
    return plot_matrix