示例#1
0
def update_legends(ly):

    q_x = config.Quantity(inp_x.value)
    q_y = config.Quantity(inp_y.value)

    p = ly.children[0].children[1]

    #title = "{} vs {}".format(q_x.label, q_y.label)
    xhover = (q_x.label, "@x {}".format(q_x.unit_str))
    yhover = (q_y.label, "@y {}".format(q_y.unit_str))

    q_clr = config.Quantity(inp_clr.value)
    hover.tooltips = [
        ("name", "@name"),
        xhover,
        yhover,
        (q_clr.label, "@color {}".format(q_clr.unit_str)),
    ]

    p.xaxis.axis_label = q_x.axis_label
    p.yaxis.axis_label = q_y.axis_label
    p.title.text = q_clr.axis_label

    url = "detail?name=@name&table=top_mofs"
    tap.callback = bmd.OpenURL(url=url)
def update_legends(ly):
    """Update figure legends."""

    q_x = config.quantities[inp_x.value]
    q_y = config.quantities[inp_y.value]
    p = ly.children[0].children[1]

    #title = "{} vs {}".format(q_x["label"], q_y["label"])
    xlabel = '{} [{}]'.format(q_x['label'], q_x['unit'])
    ylabel = '{} [{}]'.format(q_y['label'], q_y['unit'])
    xhover = (q_x['label'], '@x {}'.format(q_x['unit']))
    yhover = (q_y['label'], '@y {}'.format(q_y['unit']))

    q_clr = config.quantities[inp_clr.value]
    if 'unit' not in list(q_clr.keys()):
        clr_label = q_clr['label']
        clr_val = '@color'
    else:
        clr_val = '@color {}'.format(q_clr['unit'])
        clr_label = '{} [{}]'.format(q_clr['label'], q_clr['unit'])
    hover.tooltips = [
        ('name', '@name'),
        xhover,
        yhover,
        (q_clr['label'], clr_val),
    ]

    if inp_clr.value == 'bond_type':
        clr_label = 'Bond type'
        hover.tooltips = [
            ('name', '@name'),
            xhover,
            yhover,
            ('Bond type', '@color'),
        ]
    else:
        q_clr = config.quantities[inp_clr.value]
        clr_label = '{} [{}]'.format(q_clr['label'], q_clr['unit'])
        hover.tooltips = [
            ('name', '@name'),
            xhover,
            yhover,
            (q_clr['label'], '@color {}'.format(q_clr['unit'])),
        ]

    p.xaxis.axis_label = xlabel
    p.yaxis.axis_label = ylabel
    p.title.text = clr_label

    url = 'detail?name=@name'
    tap.callback = bmd.OpenURL(url=url)
示例#3
0
def update_legends(ly):

    q_x = quantities[inp_x.value]
    q_y = quantities[inp_y.value]
    p = ly.children[0].children[1]

    #title = "{} vs {}".format(q_x["label"], q_y["label"])
    xlabel = "{} [{}]".format(q_x["label"], q_x["unit"])
    ylabel = "{} [{}]".format(q_y["label"], q_y["unit"])
    xhover = (q_x["label"], "@x {}".format(q_x["unit"]))
    yhover = (q_y["label"], "@y {}".format(q_y["unit"]))

    q_clr = quantities[inp_clr.value]
    if 'unit' not in q_clr.keys():
        clr_label = q_clr["label"]
        clr_val = "@color"
    else:
        clr_val = "@color {}".format(q_clr['unit'])
        clr_label = "{} [{}]".format(q_clr["label"], q_clr["unit"])
    hover.tooltips = [
        ("name", "@name"),
        xhover,
        yhover,
        (q_clr["label"], clr_val),
    ]

    if inp_clr.value == 'bond_type':
        clr_label = "Bond type"
        hover.tooltips = [
            ("name", "@name"),
            xhover,
            yhover,
            ("Bond type", "@color"),
        ]
    else:
        q_clr = quantities[inp_clr.value]
        clr_label = "{} [{}]".format(q_clr["label"], q_clr["unit"])
        hover.tooltips = [
            ("name", "@name"),
            xhover,
            yhover,
            (q_clr["label"], "@color {}".format(q_clr["unit"])),
        ]

    p.xaxis.axis_label = xlabel
    p.yaxis.axis_label = ylabel
    p.title.text = clr_label

    url = "detail?name=@name"
    tap.callback = bmd.OpenURL(url=url)
示例#4
0
def get_plot(inp_x, inp_y, inp_clr):
    """Returns a Bokeh plot of the input values, and a message with the number of COFs found."""
    q_list = [quantities[label] for label in [inp_x, inp_y, inp_clr]]
    db_nodes_dict = get_db_nodes_dict(
    )  # TODO: try to move outside, to load it once!
    results = get_figure_values(
        db_nodes_dict,
        q_list)  #returns [inp_x_value, inp_y_value, inp_clr_value, cif.label]

    # prepare data for plotting
    nresults = len(results)
    msg = "{} MOFs found.<br> <b>Click on any point for details!</b>".format(
        nresults)

    label, x, y, clrs = zip(*results)
    x = list(map(float, x))
    y = list(map(float, y))
    clrs = list(map(float, clrs))
    mat_id = label

    data = {'x': x, 'y': y, 'color': clrs, 'mat_id': mat_id}

    # create bokeh plot
    source = bmd.ColumnDataSource(data=data)

    hover = bmd.HoverTool(tooltips=[])
    tap = bmd.TapTool()
    p_new = bpl.figure(
        plot_height=600,
        plot_width=600,
        toolbar_location='above',
        tools=[
            'pan',
            'wheel_zoom',
            'box_zoom',
            'save',
            'reset',
            hover,
            tap,
        ],
        active_drag='box_zoom',
        output_backend='webgl',
        title='',  # trick: title is used as the colorbar label
        title_location='right',
        x_axis_type=q_list[0]['scale'],
        y_axis_type=q_list[1]['scale'],
    )
    p_new.title.align = 'center'
    p_new.title.text_font_size = '10pt'
    p_new.title.text_font_style = 'italic'
    update_legends(p_new, q_list, hover)
    tap.callback = bmd.OpenURL(url="detail_pyrenemofs?mat_id=@mat_id")

    cmap = bmd.LinearColorMapper(palette=Plasma256,
                                 low=min(clrs),
                                 high=max(clrs))
    fill_color = {'field': 'color', 'transform': cmap}
    p_new.circle('x', 'y', size=10, source=source, fill_color=fill_color)
    cbar = bmd.ColorBar(color_mapper=cmap, location=(0, 0))
    p_new.add_layout(cbar, 'right')

    return p_new, msg
示例#5
0
def get_plot(appl):  #pylint: disable=too-many-locals
    """Make the bokeh plot for a given application.

    1. read important metrics from applications.yml
    2. read metadata for thes metricy in quantities.yml
    3. query the AiiDA database for values
    4. rank materials according to their performance
    5. generate the plot and return a message stating the number of found materials
    """

    # Query for results
    q_list = tuple([quantities[label] for label in [appl['x'], appl['y']]])
    results_wnone = get_data_aiida(
        q_list)  #[[id_material, name_material, class_material, xval, yval]]

    # Clean the query from None values projections
    results = []
    for l in results_wnone:
        if None not in l:
            results.append(l)

    # Prepare data for plotting
    nresults = len(results)
    if not results:
        results = [['none', 'none', 'none', 0, 0]]
        msg = "No materials found"
    else:
        msg = "{} materials shown".format(nresults)

    mat_id, name, class_mat, x, y = zip(*results)

    x = list(map(float, x))
    y = list(map(float, y))
    rank = rank_materials(x, y, appl['wx'], appl['wy'])

    # Setup plot
    hover = bmd.HoverTool()
    hover.tooltips = [
        ("name", "@name"),
        ("MAT_ID", "@mat_id"),
        (q_list[0]["label"], "@x {}".format(q_list[0]["unit"])),
        (q_list[1]["label"], "@y {}".format(q_list[1]["unit"])),
        ('ranking', '@color'),
    ]

    tap = bmd.TapTool()
    p = bpl.figure(
        plot_height=600,
        plot_width=600,
        toolbar_location="right",  # choose: above, below, right, left
        tools=[
            'pan',
            'wheel_zoom',
            'box_zoom',
            'save',
            'reset',
            hover,
            tap,
        ],
        active_drag='box_zoom',
        output_backend='webgl',
        x_axis_type=q_list[0]['scale'],
        y_axis_type=q_list[1]['scale'],
        x_axis_label="{} [{}]".format(q_list[0]["label"], q_list[0]["unit"]),
        y_axis_label="{} [{}]".format(q_list[1]["label"], q_list[1]["unit"]),
    )

    tap.callback = bmd.OpenURL(url="details?mat_id=@mat_id")

    cmap = bmd.LinearColorMapper(palette=myRdYlGn)
    fill_color = {'field': 'color', 'transform': cmap}

    source = bmd.ColumnDataSource(
        data={
            'mat_id': mat_id,
            'name': name,
            'class_mat': class_mat,
            'x': x,
            'y': y,
            'color': rank,
        })
    p.circle(x='x',
             y='y',
             size=10,
             source=source,
             fill_color=fill_color,
             muted_alpha=0.2)

    #cbar = bmd.ColorBar(color_mapper=cmap, location=(0, 0))
    #p.add_layout(cbar, 'right') #show colorbar

    return p, msg
示例#6
0
def plot_isotherms(mat_id):  #pylint: disable=too-many-locals
    """Plot figure with all isotherms."""

    nodes_dict = get_isotherm_nodes(mat_id)

    tooltips = [
        ("Molecule", "@legend_label"),
        ("Uptake (mol/kg)", "@q_avg"),
    ]
    hover = bmd.HoverTool(tooltips=tooltips)
    tap = bmd.TapTool()
    tap.callback = bmd.OpenURL(url=get_provenance_url(uuid="@uuid"))
    TOOLS = ["pan", "wheel_zoom", "box_zoom", "reset", "save", hover, tap]

    p1 = figure(tools=TOOLS, height=350, width=450)
    p1.xaxis.axis_label = 'Pressure (bar)'
    p1.yaxis.axis_label = 'Uptake (mol/kg)'

    for gas, gas_dict in gasses.items():
        if gas not in nodes_dict:
            continue

        for node in nodes_dict[gas]:
            try:  # avoid to fail if there are problems with one dict
                isot_out = node.get_dict()

                if isot_out['is_porous']:
                    p = isot_out['isotherm']["pressure"]  #(bar)
                    q_avg = isot_out['isotherm']["loading_absolute_average"]  #(mol/kg)
                    q_dev = isot_out['isotherm']["loading_absolute_dev"]  #(mol/kg)
                    q_upper = np.array(q_avg) + np.array(q_dev)
                    q_lower = np.array(q_avg) - np.array(q_dev)
                    h_avg = isot_out['isotherm']["enthalpy_of_adsorption_average"]  #(kJ/mol)
                    h_dev = isot_out['isotherm']["enthalpy_of_adsorption_dev"]  #(kJ/mol)
                    # TRICK: use the enthalpy from widom (energy-RT) which is more accurate that the one at 0.001 bar
                    # (and which also is NaN for weakly interacting systems)
                    h_avg[0] = isot_out['adsorption_energy_widom_average'] - isot_out['temperature'] / 120.027
                    h_dev[0] = isot_out['adsorption_energy_widom_dev']
                    h_upper = np.array(h_avg) + np.array(h_dev)
                    h_lower = np.array(h_avg) - np.array(h_dev)
                else:
                    p = [0, 100]
                    q_avg = q_upper = q_lower = h_avg = h_upper = h_lower = [0, 0]

                legend_label = "{} ({}K)".format(gas_dict['legend'], int(isot_out['temperature']))

                data = dict(p=p,
                            q_avg=q_avg,
                            q_upper=q_upper,
                            q_lower=q_lower,
                            h_avg=h_avg,
                            h_upper=h_upper,
                            h_lower=h_lower,
                            uuid=[str(node.uuid) for _ in q_avg],
                            legend_label=[legend_label] * len(p))
                source = bmd.ColumnDataSource(data=data)

                p1.line(x='p',
                        y='q_avg',
                        source=source,
                        line_color=gas_dict['color'],
                        line_width=2,
                        legend_label=legend_label)
                p1.circle(x='p', y='q_avg', source=source, color=gas_dict['color'], size=5, legend_label=legend_label)
                p1.add_layout(bmd.Whisker(source=source, base="p", upper="q_upper", lower="q_lower"))
            except (KeyError, TypeError):
                continue

    p1.legend.location = "bottom_right"

    fig = p1

    return fig
示例#7
0
    def compare(self,
                summary_catalogue,
                plot_name=None,
                summary_type='median',
                level=68.,
                params_to_plot=None,
                overwrite=False,
                rows=None,
                interactive=False):

        # Match IDs in the two catalogs
        indx1, indx2 = match_ID(self.hdulist[1].data['ID'],
                                summary_catalogue.hdulist[1].data['ID'],
                                ignore_string=self.ignore_string)

        # Check whether the IDs of the two catalogues match
        #if 'ID' in self.hdulist[1].data:
        #    for i, ID in enumerate(self.hdulist[1].data['ID']):
        #        if not ID == summary_catalogue.hdulist['POSTERIOR PDF'].data['ID'][i]:
        #            raise Exception("The object IDs of the `mock` and `summary` catalogues do not match!")

        # The summary statistics can be only 'mean' or 'median'
        if summary_type not in ('mean', 'median'):
            raise ValueError(
                "`summary_type` con only be set to `mean` or `median`")

        if plot_name is None:
            plot_name = "BEAGLE_mock_retrieved_params.pdf"

        # Check if the plot already exists
        if plot_exists(plot_name) and not overwrite:
            logging.warning('The plot `' + plot_name +
                            '` already exists. \n Exiting the function.')
            return

        # By default you plot all parameters
        if params_to_plot is None:
            params_to_plot = list()
            for key, value in six.iteritems(self.adjust_params):
                params_to_plot.append(key)

        # Do you consider only some rows in the catalogue?
        #if rows is None:
        #    rows = np.arange(len(self.hdulist[1].data.field(0)))

        _n = int(np.ceil(np.sqrt(len(params_to_plot))))

        ######################################################################
        # If interactive is `True`, you draw an interactive plot instead of
        # plotting into a pdf file
        ######################################################################
        if interactive:

            # Size (in pixels) of each Bokeh figure
            size = 400

            # Name of the output html file created by Bokeh
            name = prepare_plot_saving(os.path.splitext(plot_name)[0] +
                                       '.html',
                                       overwrite=overwrite)

            # Tell Bokeh to save the plot into an output html file
            bk_plt.output_file(name)

            # create a column data source for the plots to share, and fill the
            # dictionary with all the data that will then be used in the Bokeh
            # plot
            data = dict()
            data['ID'] = self.hdulist[1].data['ID'][indx1]
            for param in params_to_plot:

                # Store the "true" parameter
                key = param + '_true'
                data[key] = self.data[param][indx1]

                # and the retrieved one
                key = param + '_retrieved'
                _col = param + '_' + summary_type
                data[key] = summary_catalogue.hdulist['POSTERIOR PDF'].data[
                    _col][indx2]

                # Get the errors
                _col = param + '_' + '{:.2f}'.format(level)
                tmp = summary_catalogue.hdulist['POSTERIOR PDF'].data[_col][
                    indx2]

                # Store the errors in a way that is easily usable by the Bokeh
                # `multi_line` glyph
                err_xs = list()
                err_ys = list()
                key = param + '_true'
                for i, _x in enumerate(data[key]):
                    err_xs.append((_x, _x))
                    err_ys.append((tmp[i, 0], tmp[i, 1]))

                key = param + '_err_xs'
                data[key] = err_xs
                key = param + '_err_ys'
                data[key] = err_ys
                #data[key] = 0.5*(tmp[:,1]-tmp[:,0])

            strID = [
                str(ID) +
                '_BEAGLE_input_for_Pierre.fits_snr_PS_CLEAR_PRISM_BEAGLE_marginal_SED_spec.pdf'
                for ID in data['ID']
            ]

            data['strID'] = strID

            # Build the `ColumnDataSource` (a sort of dictionary) that will be
            # used by Bokeh to extract the data
            source = bk_mdl.ColumnDataSource(data=data)

            # Define the "tools" that will be included in the interactive Bokeh plot
            tools = 'wheel_zoom,pan,reset,resize,hover,tap'

            figs = list()
            for param in params_to_plot:
                # create a new plot and add a renderer
                fig = bk_plt.figure(tools=tools,
                                    width=size,
                                    height=size,
                                    title=None)

                # Plot the x and y data points as circles
                x = param + '_true'
                y = param + '_retrieved'
                error_low = param + '_err_xs'
                error_up = param + '_err_ys'
                fig.circle(x, y, source=source)

                # Plot the errorbars
                fig.multi_line(xs=error_low,
                               ys=error_up,
                               source=source,
                               alpha=0.6)

                # Plot the 1-to-1 relation
                _max = np.amax(np.ravel([data[x], data[y]]))
                _min = np.amin(np.ravel([data[x], data[y]]))
                fig.line([_min, _max], [_min, _max], color='red')

                # Label the x- and y-axis
                label = self.adjust_params[param]['label_plain']

                xlabel = label + " (true)"
                fig.xaxis.axis_label = xlabel

                ylabel = label
                fig.yaxis.axis_label = ylabel

                # Append the newly created figure to the `fgs` list of Bokeh figures
                figs.append(fig)

            # Arrange the different figures in a matrix-list
            grid_figs = list()

            for i in range(0, _n):
                _tmp = list()
                for j in range(_n):
                    if i * _n + j >= len(figs):
                        break
                    else:
                        _tmp.append(figs[i * _n + j])
                grid_figs.append(_tmp)

            # Use the matrix-list to create a grid of Bokeh figures
            p = bk_plt.gridplot(grid_figs)

            # Here we customize the behaviour of some tools
            hover = p.select(dict(type=bk_mdl.HoverTool))

            hover.tooltips = [
                ("(x,y)", "($x, $y)"),
                ('ID', '@ID'),
            ]

            path = os.path.join(os.getcwd(), '@strID')
            url = "file://" + path
            print("url: ", url)
            taptool = p.select(type=bk_mdl.TapTool)
            taptool.callback = bk_mdl.OpenURL(url=url)

            bk_plt.show(p)

            return

        ######################################################################
        # Below is a standard plot written to a pdf file
        ######################################################################

        fig, axs = plt.subplots(_n, _n)
        fig.subplots_adjust(left=0.08, bottom=0.08)
        fontsize = 8

        axs = np.ravel(axs)

        for i, param in enumerate(params_to_plot):
            # Extract the row corresponding to `param` from the mock catalogue
            # (this array contains the "true") values of the parameters

            if "extName" in self.adjust_params[param]:
                extName = self.adjust_params[param]["extName"]
            else:
                extName = "POSTERIOR PDF"

            if "colName" in self.adjust_params[param]:
                colName = self.adjust_params[param]["colName"]
            else:
                colName = param

            true_param = self.hdulist[extName].data[colName][indx1]

            _col = colName + '_' + summary_type

            retrieved_param = summary_catalogue.hdulist[extName].data[_col][
                indx2]

            _col = colName + '_' + '{:.2f}'.format(level)
            error = summary_catalogue.hdulist[extName].data[_col][indx2]

            ax = axs[i]

            # Set the x- and y-axis labels
            label = self.adjust_params[param]['label']
            xlabel = label + " (true)"
            ylabel = label

            ax.set_xlabel(xlabel)
            ax.set_ylabel(ylabel)

            # Check if we need to compute the log10 of the param or not
            x = true_param
            y = retrieved_param
            yerr_u = error[:, 1] - y
            yerr_d = y - error[:, 0]

            is_log = False
            if 'log' in self.adjust_params[param]:
                if self.adjust_params[param]['log']:
                    is_log = True
                    #x = np.log10(x)
                    #y = np.log10(y)
                    #yerr_d = np.log10(yerr_d)
                    #yerr_u = np.log10(yerr_u)

            ax.errorbar(x,
                        y,
                        yerr=[yerr_d, yerr_u],
                        ls="",
                        marker='o',
                        ms=3,
                        mew=0,
                        elinewidth=0.6,
                        capsize=3,
                        alpha=0.5)

            for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] +
                         ax.get_xticklabels() + ax.get_yticklabels()):
                item.set_fontsize(fontsize)

            # Make the axes in log-scale
            if is_log:
                ax.set_xscale('log')
                ax.set_yscale('log')
            else:
                set_plot_ticks(ax, n_x=4, n_y=4)

            # Make axes of same aspect ratio
            plt.axis('equal')

            # Set same axis range for x- and y-axis, and use the (optional)
            # user-provided value
            xx = ax.get_xlim()
            yy = ax.get_ylim()
            ll = [min([xx[0], yy[0]]), max(xx[1], yy[1])]
            if 'axis_range' in self.adjust_params[param]:
                rr = self.adjust_params[param]['axis_range']
                ll = [max(ll[0], rr[0]), min(ll[1], rr[1])]
                ax.set_autoscale_on(False)

            ax.set_xlim(ll)
            ax.set_ylim(ll)

            # Plot the 1-to-1 relations
            ax.plot(ax.get_xlim(),
                    ax.get_ylim(),
                    ls="-",
                    color="black",
                    c=".3")

        # Make the unused axes invisibles
        for ax in axs[len(params_to_plot):]:
            ax.axis('off')

        name = prepare_plot_saving(plot_name, overwrite=overwrite)

        plt.tight_layout()

        fig.savefig(name,
                    dpi=None,
                    facecolor='w',
                    edgecolor='w',
                    orientation='portrait',
                    papertype='a4',
                    format="pdf",
                    transparent=False,
                    bbox_inches="tight",
                    pad_inches=0.1)

        plt.close(fig)
示例#8
0
def get_plot(inp_x, inp_y, inp_clr):
    """Returns a Bokeh plot of the input values, and a message with the number of COFs found."""
    q_list = [config.quantities[label] for label in [inp_x, inp_y, inp_clr]]
    results_wnone = get_data_aiida(q_list)  #returns ***

    # dump None lists that make bokeh crash
    results = []
    for l in results_wnone:
        if None not in l:
            results.append(l)

    # prepare data for plotting
    nresults = len(results)
    if not results:
        results = [['x', 'x', 'x', 0, 0, 0]]
        msg = "No matching COFs found."
    else:
        msg = "{} COFs found.<br> <b>Click on any point for details!</b>".format(nresults)

    mat_id, mat_name, mat_class, x, y, clrs = zip(*results)  # returned ***
    x = list(map(float, x))
    y = list(map(float, y))
    clrs = list(map(float, clrs))

    data = {'x': x, 'y': y, 'color': clrs, 'mat_id': mat_id, 'mat_name': mat_name, 'mat_class': mat_class}

    # create bokeh plot
    source = bmd.ColumnDataSource(data=data)

    hover = bmd.HoverTool(tooltips=[])
    tap = bmd.TapTool()
    p_new = bpl.figure(
        plot_height=600,
        plot_width=600,
        toolbar_location='above',
        tools=[
            'pan',
            'wheel_zoom',
            'box_zoom',
            'save',
            'reset',
            hover,
            tap,
        ],
        active_drag='box_zoom',
        output_backend='webgl',
        title='',  # trick: title is used as the colorbar label
        title_location='right',
        x_axis_type=q_list[0]['scale'],
        y_axis_type=q_list[1]['scale'],
    )
    p_new.title.align = 'center'
    p_new.title.text_font_size = '10pt'
    p_new.title.text_font_style = 'italic'
    update_legends(p_new, q_list, hover)
    tap.callback = bmd.OpenURL(url="detail?mat_id=@mat_id")

    # Plot vertical line for comparison with amine-based technology (PE=1MJ/kg)
    if inp_y == 'CO2 parasitic energy (coal)':
        hline = bmd.Span(location=1, dimension='width', line_dash='dashed', line_color='grey', line_width=3)
        p_new.add_layout(hline)
        hline_descr = bmd.Label(x=30, y=1, x_units='screen', text_color='grey', text='amine-based sep.')
        p_new.add_layout(hline_descr)

    cmap = bmd.LinearColorMapper(palette=Plasma256, low=min(clrs), high=max(clrs))
    fill_color = {'field': 'color', 'transform': cmap}
    p_new.circle('x', 'y', size=10, source=source, fill_color=fill_color)
    cbar = bmd.ColorBar(color_mapper=cmap, location=(0, 0))
    p_new.add_layout(cbar, 'right')

    return p_new, msg
示例#9
0
def get_plot(inp_x, inp_y, inp_clr):
    """Creates holoviews plot"""
    data, msg = prepare_data(inp_x, inp_y, inp_clr)
    source = bmd.ColumnDataSource(data=data)

    # hovering
    hover = get_hover(inp_x, inp_y, inp_clr)

    # tap
    tap = bmd.TapTool()
    tap.callback = bmd.OpenURL(url=explore_url + "/details/@uuid")

    # plot
    points = hv.Points(
        source.data,
        kdims=['x', 'y'],
        vdims=['color', 'name', 'uuid'],
    )
    filtered = points.apply(filter_points,
                            streams=[hv.streams.RangeXY(source=points)])

    p_shaded = datashade(
        filtered,
        width=plot_px,
        height=plot_px,
        cmap=Plasma256,
        aggregator=ds.mean('color')  # we want color of mean value under pixel
    )
    p_hover = filtered.apply(hover_points)

    update_fn = functools.partial(update_legends,
                                  inp_x=inp_x,
                                  inp_y=inp_y,
                                  inp_clr=inp_clr)
    hv_plot = (dynspread(p_shaded) * p_hover).opts(
        hv.opts.Points(
            tools=[
                tap,
                'pan',
                'box_zoom',
                'save',
                'reset',
                hover,
            ],
            active_tools=['wheel_zoom'],
            #active_scroll='box_zoom',
            #active_drag='box_zoom',
            alpha=0.2,
            hover_alpha=0.5,
            size=10,
            width=plot_px,
            height=plot_px,
            color='color',
            cmap=Plasma256,
            colorbar=True,
            show_grid=True,
        ),
        hv.opts(toolbar='above', finalize_hooks=[update_fn]),
    )
    #     output_backend='webgl',

    p_new = hv_renderer.get_plot(hv_plot).state

    return p_new, msg