How to change button color

Hi, everyone.
Could you tell me my easy question?
I want to change button color when click the button and finish treatment finish.
I think this question is easy for anyone. But I don’t know how to change it.

My simple code

from bokeh.models.annotations import Title
import hvplot.pandas  # noqa
import numpy as np
import pandas as pd
import panel as pn
import param

INDICES = ["v1", "v2", "v3", "v4"]

#------------------------------------------------------------------------------------
# Definition ReactiveTable Class
#------------------------------------------------------------------------------------
class ReactiveTable(pn.viewable.Viewer):
    table = param.DataFrame()  # x,y table
    count = param.Integer()    # number of items
    random = param.Action(lambda x: x.param.trigger("random"), label="Random")
    x = param.ObjectSelector(INDICES[0], objects=INDICES, label="x")  # selector x
    y = param.ObjectSelector(INDICES[1], objects=INDICES, label="y")  # selector y
    table_widget = param.Parameter()  # to get table_widget.selection to work in depends

    def __init__(self, **params):
        super().__init__(**params)
        self.table_widget = pn.Param(self.param.table)[0]

    # Definition click random button
    @param.depends("random", watch=True, on_init=True)
    def _fill_table_with_random_data(self):
        size = np.random.randint(5, 15)
        nums = np.random.randint(5, 15)                      # <--- 2021/12/06  Change nums of items 
        self.count = nums                                    # <--- 2021/12/06  Display nums of items
        INDICES = [ str('v'+str(i)) for i in range(nums) ]   # <--- 2021/12/06  Make a new indices list
        self.table = pd.DataFrame(
            {i: np.random.randint(1, 100, size) for i in INDICES}
        ).T
        self.param.x.objects = INDICES   # change selector x lists
        self.param.y.objects = INDICES   # change selector y lists

    # Definition click table widget
    @param.depends("table_widget.selection", watch=True)
    def _set_x_with_table_selection(self):
        OBJECT = (self.param.x.objects)
        if self.table_widget.selection:
            self.x = OBJECT[self.table_widget.selection[0]]
            if hasattr(self, "tabs") : self.tabs.active = 0  # if it is not initial , self has tabs objects. so change active tab=0 
        else:
            self.x = OBJECT[0]
    
        # self.count += 1 # <--- 2021/12/06 Comment out

    # Definition select x value
    @param.depends("x", watch=True)
    def _set_table_selection_with_x(self):
        OBJECT = (self.param.x.objects)
        self.table_widget.selection = [OBJECT.index(self.x)]

    # Definition select x,y value or click random button
    @param.depends("x", "y", "random")
    def plot(self):
        return self.table.T.hvplot.scatter(
            x=self.x, y=self.y, color="red", grid=True, xlim=(0, 100), ylim=(0, 100) 
        ).opts(title=str(self.table[0][0]))

    # Definition update data table
    @param.depends("table")
    def table_list(self):
        return pn.pane.DataFrame(self.table, sizing_mode="fixed")

    # Definition panel layout
    def __panel__(self):
        # Layout
        graph_layout = pn.Row(
            pn.Column(
                pn.pane.Markdown("## Update table"),
                self.param.x,
                self.param.y,
                self.param.random,
            ),
            pn.panel(self.plot, sizing_mode="fixed"),
        )
        list_layout = pn.Column(self.table_widget, self.param.count, self.param.random)

        self.tabs = pn.Tabs(
            ("Graph", graph_layout),
            ("List", list_layout),
            active=self.tabs.active if hasattr(self, "tabs") else 0,
        )

        return pn.template.FastListTemplate(
            site="Panel",
            main=self.tabs,
            title="Panel Sample",
            theme="dark",
        )

# run app
if __name__ == "__main__":
    app = ReactiveTable()
    app.show(port=5007)
elif __name__.startswith("bokeh"):
    app = ReactiveTable()
    app.servable()

Screen shot 1

When I click the random button. when graph values change, I want to change random button color.

Screen shot 2

this is the point that I want to change button color.

Screen shot 3

I checked panel document. But I couldn’t apply my button.
Button — Panel 0.12.6 documentation (holoviz.org)

I’m sorry bothering for you. But could you tell me that?

In your app you are currently asking Panel to display the Parameter self.param.random. Since it’s an Action it will render as a button. This is very nice, straightforward and what you want in most cases. However sometimes like in your case you want to graphically customize how your components behave. You need to create to button yourself to get a handle on it, so you’re able to manipulate it yourself. One way to create a button synced to the Parameter is to the from_param method that the widgets have: self.button = pn.widgets.Button.from_param(self.param.button). Note that I could have passed to from_param all the arguments that the Button constructor accepts (like button_type='primary').

Once you have a handle on the button object, you can replace self.param.random by this object in your pn.Column, and manipulate self.button as you like in some other places of your code, by executing self.button.button_type = 'primary' (choose your favorite color!).

You’ll have to make sure that you’re not calling self.button before the app is created which happens in __panel__ and pretty late. So for example, if you create self.button in __panel__ and refer to it in either __init__, a method called by __init__ or in a callback set on_init, you will get an error because __panel__ has not yet been called.

Let us know if you can figure out how to rearrange your app!

2 Likes

I see you’re using the FastListTemplate which currently has an open whishlist item/issue that is to improve the button colors which are not respected as with the basic and the other templates.

I looks to me like a good opportunity for a contribution to Panel, by doing the research @Marc said was needed in the issue linked above.

2 Likes

Hi @kazux68k

You can control the button color in different ways. One way is the below using the css_classes argument to apply a bit of CSS.

import panel as pn
from panel.widgets.button import BUTTON_TYPES
pn.extension(sizing_mode="stretch_width")

ACCENT="#00758F"

buttons = [
    pn.widgets.Button(name=button_type.upper(), button_type=button_type, height=50, css_classes=["color"])
    for button_type in BUTTON_TYPES
]

def style(color, apply, selector=".bk-root .bk.color .bk-btn"):
    if not apply:
        return ""

    result = f"""<style>
{selector} {{
    --accent-fill-rest: {color};
    --accent-fill-hover: {color};
    --accent-fill-active: {color};
    --accent-foreground-rest: {color};
}}
</style>"""
    print(result)
    return pn.pane.HTML(result, height=0, width=0, margin=0, sizing_mode="fixed")

color = pn.widgets.ColorPicker(value="#8f1a00", name="Color")
apply = pn.widgets.Checkbox(value=True, name="Apply")
istyle=pn.bind(style, color=color, apply=apply)

pn.template.FastListTemplate(
    site="Awesome Panel", title="Fast Buttons with color", sidebar=[apply, color, istyle], main=[pn.Column(*buttons)],
    accent_base_color=ACCENT, header_background=ACCENT
).servable()

You can take the resulting style string (without the <style> tags) and just append it to pn.config.raw_css in your app.

1 Like

Background

The button_type is a concept from the Bootstrap design systen. Fast is another design system that does not have a button_type. Instead it has an accent. See the Fast Button.

So when I designed the Fast templates I choose to use button_type to mimic the accent of Fast. Thus they only have one color currently.

Feel free to post a Feature Request with a suggestion for change.

The vision is to one day have separate Fast widgets implementing the Fast design system truly. Some attempts have started.

1 Like

@maximlt @Marc
Thanks a lot for giving me much advice. I modify my code according to your advice. And then,
I did it as I want to. instead of Button widget , I use Toggle widget.

##Slide 1 default screen shot

##Slide 2 click Toggle button ( Toggle button button_type changed from default to primary.

##Slide 3 use css in order to change color

my simple code

from typing import Any
from bokeh.models.annotations import Title
import hvplot.pandas  # noqa
import numpy as np
import pandas as pd
import panel as pn
import param

INDICES = ["v1", "v2", "v3", "v4"]

#------------------------------------------------------------------------------------
# Definition ReactiveTable Class
#------------------------------------------------------------------------------------
class ReactiveTable(pn.viewable.Viewer):
    table = param.DataFrame()  # x,y table
    count = param.Integer()    # number of items
    # random = param.Action(lambda x: x.param.trigger("random"), label="Random")
    x = param.ObjectSelector(INDICES[0], objects=INDICES, label="x")  # selector x
    y = param.ObjectSelector(INDICES[1], objects=INDICES, label="y")  # selector y
    table_widget = param.Parameter()  # to get table_widget.selection to work in depends
    Toggle_tf = param.Boolean() # <--- 20211220 add Toggle_tf param

    color = pn.widgets.ColorPicker(value="#8f1a00", name="Color")
    apply = pn.widgets.Checkbox(value=False, name="Apply")

    def __init__(self, **params):
        super().__init__(**params)
        self.table_widget = pn.Param(self.param.table)[0]
        self.Toggle = pn.widgets.Toggle.from_param(self.param.Toggle_tf,
                        name='Push Toggle', button_type='default', css_classes=["color"]) # <<-- 20211220 add button_widget
        self.istyle=pn.bind(self.style, color=self.color, apply=self.apply)

    def change_data(self) :
        size = np.random.randint(5, 15)
        nums = np.random.randint(5, 15)                      # <--- 2021/12/06  Change nums of items 
        self.count = nums                                    # <--- 2021/12/06  Display nums of items
        INDICES = [ str('v'+str(i)) for i in range(nums) ]   # <--- 2021/12/06  Make a new indices list
        self.table = pd.DataFrame(
        {i: np.random.randint(1, 100, size) for i in INDICES}
        ).T
        self.param.x.objects = INDICES   # change selector x lists
        self.param.y.objects = INDICES   # change selector y lists

    # Definition click random button
    #@param.depends("random", watch=True, on_init=True)
    @param.depends('Toggle_tf',watch=True, on_init=True)
    def _fill_table_with_random_data(self):
        if hasattr(self, "tabs") : 
            if self.Toggle_tf == True :       
                self.change_data()
                self.Toggle.name = 'Finish'
                self.Toggle.button_type = 'primary'
            else :
                self.Toggle.name = 'Push Toggle'
                self.Toggle.button_type = 'default'
        else :
            self.change_data()

    # Definition click table widget
    @param.depends("table_widget.selection", watch=True)
    def _set_x_with_table_selection(self):
        OBJECT = (self.param.x.objects)
        if self.table_widget.selection:
            self.x = OBJECT[self.table_widget.selection[0]]
            if hasattr(self, "tabs") : self.tabs.active = 0  # if it is not initial , self has tabs objects. so change active tab=0 
        else:
            self.x = OBJECT[0]
    
        # self.count += 1 # <--- 2021/12/06 Comment out

    # Definition select x value
    @param.depends("x", watch=True)
    def _set_table_selection_with_x(self):
        OBJECT = (self.param.x.objects)
        self.table_widget.selection = [OBJECT.index(self.x)]

    # Definition select x,y value or click random button
#    @param.depends("x", "y", "random")
    @param.depends("x", "y", "Toggle_tf")

    def plot(self):
        return self.table.T.hvplot.scatter(
            x=self.x, y=self.y, color="red", grid=True, xlim=(0, 100), ylim=(0, 100) 
        ).opts(title=str(self.table[0][0]))

    # Definition update data table
    @param.depends("table")
    def table_list(self):
        return pn.pane.DataFrame(self.table, sizing_mode="fixed")

    def style(self, color, apply, selector=".bk-root .bk.color .bk-btn"):
        if not apply:
            return ""

        result = f"""<style>
    {selector} {{
        --accent-fill-rest: {color};
        --accent-fill-hover: {color};
        --accent-fill-active: {color};
        --accent-foreground-rest: {color};
    }}
    </style>"""
        print(result)
        return pn.pane.HTML(result, height=0, width=0, margin=0, sizing_mode="fixed")


    # Definition panel layout
    def __panel__(self):
        # Layout
        graph_layout = pn.Row(
            pn.Column(
                pn.pane.Markdown("## Update table"),
                self.param.x,
                self.param.y,
                #self.param.random,
                self.Toggle,
                self.color,
                self.apply,
                self.istyle,
            ),
            pn.panel(self.plot, sizing_mode="fixed"),
        )
        list_layout = pn.Column(self.table_widget, self.param.count) # , self.param.random)

        self.tabs = pn.Tabs(
            ("Graph", graph_layout),
            ("List", list_layout),
            active=self.tabs.active if hasattr(self, "tabs") else 0,
        )

        return pn.template.FastListTemplate(
            site="Panel",
            main=self.tabs,
            title="Panel Sample",
            theme="dark",
        )


# run app
if __name__ == "__main__":
    app = ReactiveTable()
    app.show(port=5007)
elif __name__.startswith("bokeh"):
    app = ReactiveTable()
    app.servable()

Great thanks!!

3 Likes