DynamicMap of differently colored Curves

I am using the following MWE to simulate plotting data from an experiment.
When I stop and restart, I want the next curve (or points) to be colored differently.

import random
import asyncio
from functools import partial
import pandas as pd
import holoviews as hv
import bokeh
hv.extension('bokeh')
import panel as pn
from holoviews.streams import Buffer

def make_df(x=0.0, y=0.0):
    return pd.DataFrame(data={'x': x, 'y': y}, index=[0])

empty_df = pd.DataFrame(columns=make_df().columns)

buffer_length = 1000
buffer = Buffer(empty_df, length=buffer_length, index=False)

plot = hv.DynamicMap(partial(hv.Curve, kdims='x', vdims='y'), streams=[buffer]).opts(
    #color=hv.Cycle('Spectral'),
    #color=hv.Palette('Spectral'),
    #color=hv.dim('???'),   
)

scatterplot = hv.DynamicMap(partial(hv.Scatter, kdims='x', vdims='y'), streams=[buffer]).opts(
    color = 'k',
    marker='o',
    size=5
)

LABEL_START = 'Start'
LABEL_STOP = 'Stop'
button_run = pn.widgets.Button(name=LABEL_START)
button_reset_all_plots = pn.widgets.Button(name='Reset')

task = None

def reset_all_plots(self):
    buffer.clear()

button_reset_all_plots.on_click(reset_all_plots)

async def focus():
    seed = random.random()
    for i in range(10):
        datapoint_x = i
        datapoint_y = datapoint_x * seed
        datum = make_df(datapoint_x, datapoint_y)
        buffer.send(datum)
        await asyncio.sleep(0.1)  
    button_run.name = LABEL_START
    return


def run_focus(events):
    global task
    if button_run.name is LABEL_START:      
        button_run.name = LABEL_STOP
        task = asyncio.gather(focus())
    else:
        task.cancel()
        button_run.name = LABEL_START

button_run.on_click(run_focus)

plots = pn.Column(plot*scatterplot,  pn.Row(button_reset_all_plots, button_run))
plots

Using hv.cycle or hv.palette changes the color, but only initially, not upon each new run, since I am technically still plotting the same hv.curve. I don’t understand how to use hv.dim()+some dummy variable.

Any help would be appreciated

I have not resolved this yet, but am again interested in finding a solution.
I advanced a tiny bit and slightly changed the MWE

import random
import asyncio
from functools import partial
import pandas as pd
import holoviews as hv
import bokeh
hv.extension('bokeh')
import panel as pn
from holoviews.streams import Buffer

def make_df(x=0.0, y=0.0, z=0.0):
    return pd.DataFrame(data={'x': x, 'y': y, 'z': z}, index=[0])

empty_df = pd.DataFrame(columns=make_df().columns)

buffer_length = 1000
buffer = Buffer(empty_df, length=buffer_length, index=False)

plot = hv.DynamicMap(partial(hv.Curve, kdims='x', vdims=['y', 'z']), streams=[buffer]).opts(
    #color='z'#this never works with curve, should use "groupby" instead, but how?
)

scatterplot = hv.DynamicMap(partial(hv.Scatter, kdims='x', vdims=['y', 'z']), streams=[buffer]).opts(
    color = 'k',#changing this to 'z' makes the markers disappear
    marker='o',
    size=5
)

LABEL_START = 'Start'
LABEL_STOP = 'Stop'
button_run = pn.widgets.Button(name=LABEL_START)
button_reset_all_plots = pn.widgets.Button(name='Reset')

task = None

def reset_all_plots(self):
    buffer.clear()

button_reset_all_plots.on_click(reset_all_plots)

async def focus():
    seed = random.random()
    for i in range(10):
        datapoint_x = i
        datapoint_y = datapoint_x * seed
        datapoint_z = float(i)
        datum = make_df(datapoint_x, datapoint_y, datapoint_z)
        buffer.send(datum)
        await asyncio.sleep(0.1)  
    button_run.name = LABEL_START
    return


def run_focus(events):
    global task
    if button_run.name is LABEL_START:      
        button_run.name = LABEL_STOP
        task = asyncio.gather(focus())
    else:
        task.cancel()
        button_run.name = LABEL_START

button_run.on_click(run_focus)

plots = pn.Column(plot*scatterplot,  pn.Row(button_reset_all_plots, button_run))
plots

I added an additional vdim, and use that to color the hv.Scatter plot. But this only works when i call hv.Scatter directly for example after generating data using the button in the MWE

hv.Scatter(scatterplot.dframe(), kdims='x', vdims=['y','z']).opts(color='z')

I don’t know how to pass the option to hv.Scatter inside the hv.DynamicMap.
Also, trying the same with hv.Curve instead of hv.Scatter does not work at all. The error message says I should use groupby instead, but I don’t understand how.

not sure if this were easier if the partial wasn’t there, but as far as I know I need it to specify which columns I want to plot of a larger dataframe (see here)

I don’t really understand what is happening here at all.

When generating data at least once (by pressing the start button), then running scatterplot.opts(color='z') can be used to change the color of the dots. But if this done before generating any data, then the start button stops working.

On the other hand, if scatterplot.opts(color='z') is run after data is generated and the color is changed successfully, then once the reset button is pressed, the start buttons stops working again.

But when the color is changed back first to the standard black using scatterplot.opts(color='k'), then all buttons work normally again.

It seems that color='z' requires z to never be undefined.

import random
import asyncio
from functools import partial
import pandas as pd
import holoviews as hv
import panel as pn
import bokeh
hv.extension('bokeh')
from holoviews.streams import Buffer

def make_df(x=0.0, y=0.0, z=0.0):
    return pd.DataFrame(data={'x': x, 'y': y, 'z': z}, index=[0])

empty_df = pd.DataFrame(columns=make_df().columns)

buffer_length = 1000
buffer = Buffer(empty_df, length=buffer_length, index=False)

plot = hv.DynamicMap(partial(hv.Curve, kdims='x', vdims=['y', 'z']), streams=[buffer])
scatterplot = hv.DynamicMap(partial(hv.Scatter, kdims='x', vdims=['y', 'z']), streams=[buffer])

plot.opts(
    hv.opts.Curve(color='black', line_width=0.5
    )
)
scatterplot.opts(
    hv.opts.Scatter(color='k', marker='o', size=4
    )
)



LABEL_START = 'Start'
LABEL_STOP = 'Stop'
button_run = pn.widgets.Button(name=LABEL_START)
button_reset_all_plots = pn.widgets.Button(name='Reset')

task = None
counter_current = 0
counter_limit=19

def reset_all_plots(self):
    scatterplot.opts(hv.opts.Scatter(color=hv.dim('z'), cmap='category20', clim=(0,counter_limit)))                 
    buffer.clear()

button_reset_all_plots.on_click(reset_all_plots)

async def focus(counter_current):
    seed = random.random()
    for i in range(10):
        datapoint_x = i
        datapoint_y = datapoint_x * seed
        datapoint_z = float(counter_current)
        datum = make_df(datapoint_x, datapoint_y, datapoint_z)
        buffer.send(datum)
        await asyncio.sleep(0.1)           
    button_run.name = LABEL_START
    return


def run_focus(events):
    global task, counter_current
    if button_run.name is LABEL_START:      
        button_run.name = LABEL_STOP
        task = asyncio.gather(focus(counter_current), return_exceptions=True)
        counter_current = (counter_current + 1 ) % counter_limit
    else:
        task.cancel()
        button_run.name = LABEL_START

button_run.on_click(run_focus)
plots = pn.Column(plot*scatterplot,  pn.Row(button_reset_all_plots, button_run))
plots
#UI = pn.template.BootstrapTemplate(title='UI')
#UI.main.append(plots)
#UI.servable();

This does more or less what I want, but only barely.
The color is introduced only after pressing the reset button the first time.
If i try to apply the color via .opts before any data exists, then all future points plotted by Scatter become invisible.
I don’t understand why, if you do, please let me know, thanks.

If you want to persist a curve, what I would do is store a global list of Curves, i.e.

curves = []

def generate_curve(...):
    new_curve = hv.Curve(...).opts(color=hv.Cycle("Reds"))
    curves.append(new_curve)
    return hv.Overlay(curves)

Let me know if this works for you!