Hej,
I am currently trying to assign markers to data based on user input through ipywidgets. The first user input step, defining offset, Step length, rest length etc. works so far. However, I want to add the option to manually adjust the marker position of Start and End per Stage. I figured out how to get the index of each Start End Marker, but whenever I press run interact, the marker location does not change as well as the marker disappears.
Am I missing something here or why is this happening?
from ipywidgets import Output, VBox, HBox, FloatSlider, Button, interact
import plotly.graph_objs as go
import pandas as pd
output = Output()
markers = {} # Initialize markers dictionary outside of set_markers function
np.random.seed(0)
df = pd.DataFrame({
'timeinsec': np.linspace(0, 1000, 100), # Time from 0 to 1000 seconds
"data": np.random.rand(100) # Random values
})
def set_markers(df, x_col, y_col, offset, step_length, rest_length, num_intervals, last_step_length, start_velocity, velocity_increment):
with output:
output.clear_output(wait=True)
fig = go.Figure()
fig.add_trace(go.Scatter(x=df[x_col], y=df[y_col], mode='markers', name='original', marker=dict(color='red', size=8, line=dict(width=1, color='DarkSlateGrey'))))
velocities = [start_velocity + i * velocity_increment for i in range(num_intervals)]
for i in range(num_intervals):
start = offset + i * (step_length + rest_length)
end = start + (last_step_length if i == num_intervals - 1 else step_length)
fig.add_shape(type="line", x0=start, y0=df[y_col].min(), x1=start, y1=df[y_col].max(), line=dict(color="blue", width=2))
fig.add_shape(type="line", x0=end, y0=df[y_col].min(), x1=end, y1=df[y_col].max(), line=dict(color="blue", width=2,))
markers[f"Start{i+1}"] = start # Create start marker positions
markers[f"End{i+1}"] = end # Create end marker positions
df.loc[(df[x_col] >= start) & (df[x_col] <= end), 'v'] = velocities[i]
# Create a new marker for the end position of each stage
fig.add_shape(type="line", x0=end, y0=df[y_col].min(), x1=end, y1=df[y_col].max(), line=dict(color="green", width=2))
# Calculate velocity for the last stage based on proportional length
if num_intervals > 1:
stage_per = (last_step_length * 100 / step_length) / 100
new_v = velocities[-2] + velocity_increment * stage_per
df.loc[(df[x_col] >= start) & (df[x_col] <= end), 'v'] = new_v
df.v.fillna(0,inplace = True)
fig.update_layout(title='Assign Stage:', xaxis_title=x_col, yaxis_title=y_col)
fig.show()
# Add sliders to adjust the position of the start and end markers for each stage
sliders_column_1 = VBox()
sliders_column_2 = VBox()
for i in range(num_intervals):
start_marker_pos = markers.get(f"Start{i+1}", 0) # Get initial position from markers dictionary
end_marker_pos = markers.get(f"End{i+1}", 0) # Get initial position from markers dictionary
start_marker_slider = FloatSlider(value=start_marker_pos, min=0, max=df[x_col].max(), description=f'Start {i+1}:')
sliders_column_1.children += (start_marker_slider,) # Add slider to the VBox
end_marker_slider = FloatSlider(value=end_marker_pos, min=0, max=df[x_col].max(), description=f'End {i+1}:')
sliders_column_2.children += (end_marker_slider,) # Add slider to the VBox
# Arrange sliders in two columns
sliders_box = HBox([sliders_column_1, sliders_column_2])
display(sliders_box)
def update_marker_positions(**kwargs):
with output:
output.clear_output(wait=True)
shapes = []
for i in range(num_intervals):
if f"Start{i+1}" in kwargs and f"End{i+1}" in kwargs:
start_marker_pos = kwargs[f"Start{i+1}"]
end_marker_pos = kwargs[f"End{i+1}"]
markers[f"Start{i+1}"] = start_marker_pos
markers[f"End{i+1}"] = end_marker_pos
shapes.append(dict(type="line", x0=start_marker_pos, y0=df[y_col].min(), x1=start_marker_pos, y1=df[y_col].max(), line=dict(color="blue", width=2)))
shapes.append(dict(type="line", x0=end_marker_pos, y0=df[y_col].min(), x1=end_marker_pos, y1=df[y_col].max(), line=dict(color="green", width=2)))
fig.update_layout(shapes=shapes)
fig.show()
interact_manual(update_marker_positions)
def interactive_line_plot(df, x_col, y_col):
offset = IntSlider(value=10, min=0, max=df[x_col].max(), step=1, description='Offset:')
step_length = IntSlider(value=240, min=1, max=500, step=1, description='Step Length:')
rest_length = IntSlider(value=30, min=1, max=500, step=1, description='Rest Length:')
num_intervals = IntSlider(value=5, min=1, max=10, step=1, description='Intervals:')
last_step_length = IntSlider(value=240, min=1, max=500, step=1, description=' t Last Step:')
start_velocity = FloatText(value=8.0, description='Start:')
velocity_increment = FloatText(value=1.0, description='Increment:')
update_plot_button = Button(description="Update Plot")
update_plot_button.on_click(lambda b: set_markers(df, x_col, y_col, offset.value, step_length.value, rest_length.value, num_intervals.value, last_step_length.value, start_velocity.value, velocity_increment.value))
control_box = VBox([offset, step_length, rest_length, num_intervals, last_step_length, start_velocity, velocity_increment, update_plot_button])
display(HBox([control_box, output]))
interactive_line_plot(df, "timeinsec", "data")
I wasn't seeing kwargs
passed into update_marker_positions()
passing in anything useful. (Maybe you had different luck?)
So every time you check if f"Start{i+1}" in kwargs and f"End{i+1}" in kwargs:
, it evaluates to False
and nothing happens.
This also means you cannot use start_marker_pos = kwargs[f"Start{i+1}"]
and end_marker_pos = kwargs[f"End{i+1}"
to get the updated values of the sliders.
I am actually not sure you need any conditional there at the line if f"Start{i+1}" in kwargs and f"End{i+1}" in kwargs:
before you update the assignments; however, I've kept it in to stick closer to your original code.
This below seems to work in my hands for now to give you a chance to refine the starts and ends for each interval. I basically updated how start_marker_pos = kwargs[f"Start{i+1}"]
and end_marker_pos = kwargs[f"End{i+1}"
get handled:
from ipywidgets import Output, VBox, HBox, FloatSlider, Button, interact, IntSlider, FloatText, interact_manual
import numpy as np
import plotly.graph_objs as go
import pandas as pd
output = Output()
markers = {} # Initialize markers dictionary outside of set_markers function
np.random.seed(0)
df = pd.DataFrame({
'timeinsec': np.linspace(0, 1000, 100), # Time from 0 to 1000 seconds
"data": np.random.rand(100) # Random values
})
def set_markers(df, x_col, y_col, offset, step_length, rest_length, num_intervals, last_step_length, start_velocity, velocity_increment):
with output:
output.clear_output(wait=True)
fig = go.Figure()
fig.add_trace(go.Scatter(x=df[x_col], y=df[y_col], mode='markers', name='original', marker=dict(color='red', size=8, line=dict(width=1, color='DarkSlateGrey'))))
velocities = [start_velocity + i * velocity_increment for i in range(num_intervals)]
for i in range(num_intervals):
start = offset + i * (step_length + rest_length)
end = start + (last_step_length if i == num_intervals - 1 else step_length)
fig.add_shape(type="line", x0=start, y0=df[y_col].min(), x1=start, y1=df[y_col].max(), line=dict(color="blue", width=2))
fig.add_shape(type="line", x0=end, y0=df[y_col].min(), x1=end, y1=df[y_col].max(), line=dict(color="blue", width=2,))
markers[f"Start{i+1}"] = start # Create start marker positions
markers[f"End{i+1}"] = end # Create end marker positions
df.loc[(df[x_col] >= start) & (df[x_col] <= end), 'v'] = velocities[i]
# Create a new marker for the end position of each stage
fig.add_shape(type="line", x0=end, y0=df[y_col].min(), x1=end, y1=df[y_col].max(), line=dict(color="green", width=2))
# Calculate velocity for the last stage based on proportional length
if num_intervals > 1:
stage_per = (last_step_length * 100 / step_length) / 100
new_v = velocities[-2] + velocity_increment * stage_per
df.loc[(df[x_col] >= start) & (df[x_col] <= end), 'v'] = new_v
df.v.fillna(0,inplace = True)
fig.update_layout(title='Assign Stage:', xaxis_title=x_col, yaxis_title=y_col)
fig.show()
# Add sliders to adjust the position of the start and end markers for each stage
sliders_column_1 = VBox()
sliders_column_2 = VBox()
for i in range(num_intervals):
start_marker_pos = markers.get(f"Start{i+1}", 0) # Get initial position from markers dictionary
end_marker_pos = markers.get(f"End{i+1}", 0) # Get initial position from markers dictionary
start_marker_slider = FloatSlider(value=start_marker_pos, min=0, max=df[x_col].max(), description=f'Start {i+1}:')
sliders_column_1.children += (start_marker_slider,) # Add slider to the VBox
end_marker_slider = FloatSlider(value=end_marker_pos, min=0, max=df[x_col].max(), description=f'End {i+1}:')
sliders_column_2.children += (end_marker_slider,) # Add slider to the VBox
# Arrange sliders in two columns
sliders_box = HBox([sliders_column_1, sliders_column_2])
display(sliders_box)
def update_marker_positions(**kwargs):
with output:
output.clear_output(wait=True)
shapes = []
for i in range(num_intervals):
if f"Start{i+1}" in markers and f"End{i+1}" in markers:
start_marker_pos = list(sliders_column_1.children)[i].value
end_marker_pos = list(sliders_column_2.children)[i].value
markers[f"Start{i+1}"] = start_marker_pos
markers[f"End{i+1}"] = end_marker_pos
shapes.append(dict(type="line", x0=start_marker_pos, y0=df[y_col].min(), x1=start_marker_pos, y1=df[y_col].max(), line=dict(color="blue", width=2)))
shapes.append(dict(type="line", x0=end_marker_pos, y0=df[y_col].min(), x1=end_marker_pos, y1=df[y_col].max(), line=dict(color="green", width=2)))
fig.update_layout(shapes=shapes)
fig.show()
interact_manual(update_marker_positions)
def interactive_line_plot(df, x_col, y_col):
offset = IntSlider(value=10, min=0, max=df[x_col].max(), step=1, description='Offset:')
step_length = IntSlider(value=240, min=1, max=500, step=1, description='Step Length:')
rest_length = IntSlider(value=30, min=1, max=500, step=1, description='Rest Length:')
num_intervals = IntSlider(value=5, min=1, max=10, step=1, description='Intervals:')
last_step_length = IntSlider(value=240, min=1, max=500, step=1, description=' t Last Step:')
start_velocity = FloatText(value=8.0, description='Start:')
velocity_increment = FloatText(value=1.0, description='Increment:')
update_plot_button = Button(description="Update Plot")
update_plot_button.on_click(lambda b: set_markers(df, x_col, y_col, offset.value, step_length.value, rest_length.value, num_intervals.value, last_step_length.value, start_velocity.value, velocity_increment.value))
control_box = VBox([offset, step_length, rest_length, num_intervals, last_step_length, start_velocity, velocity_increment, update_plot_button])
display(HBox([control_box, output]))
interactive_line_plot(df, "timeinsec", "data")