I am trying to build a dynamic list of color pickers. I am not sure about the bindings and seems like what I tried so far is not doing it
I have a minimal reproducible non working example which is showing what I would like to do. I would like that a change on any ColorPicker would update the following widget. My guess so far is that I am binding the Card instead of each individual components.
I am very new to Panel so I might have missed something very obvious. Like I donāt know if there is a handy tool to check all my bindings besides running and seeing if my app works or not.
import panel as pn
pn.extension(template="material")
# these 2 are functions that I bind in my real app, but does not seem to be the problem as this also fails
# the length of both lists is variable, here it's an example with 3, but can be anything.
base_colors = ["#0072b5"]*3
states = ["low", "medium", "high"]
def color_pickers(states, colors):
pickers = [
pn.widgets.ColorPicker(name=state, value=color)
for state, color in zip(states, colors)
]
card_pickers = pn.Card(*pickers, title="My colors:")
return card_pickers
def palette(colors):
list_colors = [color.value for color in colors]
return pn.pane.Markdown(f"{list_colors}")
interactive_color_pickers = pn.bind(color_pickers, states, base_colors)
interactive_palette = pn.bind(palette, interactive_color_pickers)
pn.Column(interactive_color_pickers, interactive_palette).servable(title="Minimal")
import panel as pn
pn.extension(template="material")
# these 2 are functions that I bind in my real app, but does not seem to be the problem as this also fails
BASE_COLOR = "#0072b5"
STATES = ["low", "medium", "high"]
def palette(*colors):
list_colors = [color for color in colors]
return pn.pane.Markdown(f"{list_colors}")
color_pickers = [pn.widgets.ColorPicker(name=state, value=BASE_COLOR) for state in STATES]
pn.Column(*color_pickers, pn.bind(palette, *color_pickers))
I am trying to see if this is working. I cannot have the states and colors as you did as in my case they are dynamically being generated and the size is not constant.
For more context, I pushed a branch of my current code here. The relevant part starts L287, the functions are not organised yet for that part as I am still fighting around.
import panel as pn
import param
pn.extension(template="material")
BASE_COLOR = "#0072b5"
class ColorsDisplay(param.Parameterized):
colors = param.List(default=[BASE_COLOR])
def __init__(self, **params):
self._color_pickers = pn.Column()
super().__init__(**params)
def _pick_colors(self, event):
"""
When color is picked from the color picker, add it to the list of selected colors.
"""
self.colors = [color_picker.value for color_picker in self._color_pickers]
@param.depends("colors", watch=True, on_init=True)
def _create_color_pickers(self):
current_colors = [color_picker.value for color_picker in self._color_pickers]
if self.colors == current_colors:
return
print("Updating color pickers...")
color_pickers = []
for color in self.colors:
color_picker = pn.widgets.ColorPicker(name="Color", value=color)
color_picker.param.watch(self._pick_colors, "value")
color_pickers.append(color_picker)
self._color_pickers[:] = color_pickers
@param.depends('colors')
def palette(self):
return pn.pane.Markdown(f"{self.colors}")
def view(self):
return pn.Column(self._color_pickers, self.palette)
colors_display = ColorsDisplay()
colors_display.view()
Hereās the functional way. I think param can group your parameters more effectively, and potentially scale (itās how panel / holoviews is built internally).
Initially pn.bind is easier to pick up and use, but after it could potentially get messy with watch=True is my understanding.
For some reason if I adapt your code, I donāt get the initial selection. So I need to show the MultiSelect and then if I select everything it works. It looks like I donāt manage to have the variable number of initial colors:
def base_colors(args):
# see my branch for details
return ['#fdb97d', '#c6c7e1', '#fca082']
# dummy binding, needed on my side
interactive_base_colors = pn.bind(base_colors, 3)
colors_select = pn.widgets.MultiSelect(
value=interactive_base_colors,
options=interactive_base_colors,
name="Colors",
visible=False
)
As dummy = pn.bind(..., watch=True) registers create_color_pickers as a callback and I believe passing dummy to pn.Column will again register create_color_pickers as a callback, itās likely itās going to be called twice. Thatās what Iāll try check later