How do I use Bokeh models in Panel?

It seems to only show the repr

from bokeh.models.ui import SVGIcon, TablerIcon
import panel as pn
pn.extension()

pn.panel(TablerIcon("heart", size="1.2em"))

Hi @ahuang11,

Seems like it’s not supported, I tried something like

from bokeh.models import SVGIcon, TablerIcon
import panel as pn

pn.extension()

bokeh_pane = pn.pane.Bokeh()
bokeh_pane
from bokeh.models import Div
bokeh_pane.object = Div(text='<h2>This text replaced the pie chart</h2>')

The above works but then when I try with the TablerIcon (I have no idea if it is right to try and add it to the object) but I get the following error which may help:

from bokeh.models.ui import SVGIcon, TablerIcon
import panel as pn

pn.extension()

bokeh_pane = pn.pane.Bokeh()
bokeh_pane

bokeh_pane.object = TablerIcon("heart", size="1.2em")

ValueError: failed to validate Row(id=‘a660a70d-921b-4de2-88b6-99f3ecb98f06’, …).children: expected an element of List(Instance(UIElement)), got seq with invalid items [TablerIcon(id=‘71588166-442a-454b-bb42-eddf2cc1b5a3’, …)]

1 Like

Yeah I was wondering how it gets rendered in Button.

1 Like

have you tried wrapping it into a bokeh column?

1 Like

Good idea! I suppose it doesn’t work.

from bokeh.models.ui import SVGIcon, TablerIcon
from bokeh.layouts import Column
import panel as pn
pn.extension()

pn.panel(Column(TablerIcon("heart", size="1.2em")))

ValueError: failed to validate Column(id='eaaf0083-4d13-4a00-9710-365da722ab15', ...).children: expected an element of List(Instance(UIElement)), got seq with invalid items [TablerIcon(id='1d34a545-4d6e-4b9d-add9-841d092211df', ...)

I came across this message in the bokeh docs

from bokeh.models.ui import SVGIcon, TablerIcon
from bokeh.models import Button
import panel as pn
pn.extension()

icon = list()
icon = TablerIcon("heart",size="1.2em")
button = Button(label="Foo", button_type="success", icon=icon)

pn.panel(button).show()

image

Thanks for investigating; it’s also possible to simply do
pn.widgets.Button(icon='heart') or even pn.widgets.Toggle(icon='heart')

However, I don’t want it as a button. An alternative is an pn.pane.Image of the tabler icon URL.

1 Like

Seems like it should be very similar to
https://panel.holoviz.org/reference/indicators/TooltipIcon.html

I see. So I suppose it needs a panel model to support it:

import param
from panel.reactive import ReactiveHTML

class ChatReaction(ReactiveHTML):

    value = param.String(default="", doc="""
        The alt text for the icon.""")

    icon = param.String(default=None, doc="""
        An icon to use when inactive. An icon name which
        is loaded from https://tabler-icons.io.""")

    active_icon = param.String(default=None, doc="""
        An icon to use when active. An icon name which
        is loaded from https://tabler-icons.io.
        If this is not set, the '-filled' suffix is
        added to `icon`.""")

    active = param.Boolean(default=False, doc="""
        Whether the reaction is active.""")

    icon_size = param.String(default="10px", doc="""
        The size of the icon.""")

    _icon_url = param.Parameter(doc="The current displayed icon.")

    _template = """
        <div>
            <img id="icon" alt="${value}" src="${_icon_url}"
                style="width: ${icon_size}; height: ${icon_size}; cursor: pointer;"
                onclick="${script('update_layout')}">
            </img>
        </div>
    """

    _scripts = {
        "render": "self.update_layout()",
        "update_layout": """
            const tabler_base_url = "https://tabler-icons.io/static/tabler-icons/icons-png/";
            data.active = !data.active;
            if (data.active && data.active_icon) {
                icon.src = `${tabler_base_url}${data.active_icon}.png`;
            } else if (data.active && !data.active_icon) {
                icon.src = `${tabler_base_url}${data.icon}-filled.png`;
            } else {
                icon.src = `${tabler_base_url}${data.icon}.png`;
            }
        """
    }

    def __init__(self, value: str, **params):
        super().__init__(value=value, **params)

import panel as pn
pn.extension()

ChatReaction(value="dislike", icon="thumb-up", active_icon="thumb-down", styles={"background": "white"})

image

image

1 Like

I like this implementation better;


class ChatReactionIcons(ReactiveHTML):

    value = param.List(doc="The selected reactions.")

    options = param.Dict(default={"favorite": "heart"}, doc="""
        A key-value pair of reaction values and their corresponding tabler icon names
        found on https://tabler-icons.io.""")

    icon_size = param.String(default="15px", doc="""
        The size of each icon.""")
    
    icon_toggle_mapping = param.Dict(default={}, doc="""
        The mapping of tabler icon names to their corresponding toggled icon names;
        if not set, the toggled icon name will default to its "filled" version.""")

    _icon_base_url = param.String("https://tabler-icons.io/static/tabler-icons/icons-png/")

    _template = """
        <div id="reactions" class="reactions">
            {% for reaction, icon_name in options.items() %}
            <img id="icon-{{ reaction }}" alt="{{ reaction }}"
                {% if reaction in value %}
                src="${_icon_base_url}{{ icon_name }}-filled.png"
                {% else %}
                src="${_icon_base_url}{{ icon_name }}.png"
                {% endif %}
                style="width: {{ icon_size }}; height: {{ icon_size }}; cursor: pointer;"
                onclick="${script('update_value')}">
            </img>
            {% endfor %}
        </div>
    """

    _scripts = {
        "update_value": """
            const reaction = event.target.alt;
            const iconName = data.options[reaction];
            if (data.value.includes(reaction)) {
                data.value = data.value.filter(r => r !== reaction);
                event.target.src = data._icon_base_url + iconName + ".png";
            } else {
                data.value = [...data.value, reaction];
                if (iconName in data.icon_toggled_names) {
                    event.target.src = data._icon_base_url + data.icon_toggled_names[iconName] + ".png";
                } else {
                    event.target.src = data._icon_base_url + iconName + "-filled.png";
                }
            }
        """
    }