I have the following program that plots a line chart that develops each second with a new line segment at the end using dcc.Interval as well as a button using dcc.Input that duplicates a line that has been drawn in the chart by the user (thanks AIMPED!). The problem is that when I click “duplicate line” the line gets duplicated and visible in the chart for a second and then when dcc.Interval is triggered again all shapes disappears from the chart. How can I keep all shapes?
import dash
from dash import Input, Output, html, dcc, State
import plotly.graph_objects as go
from random import randrange
def extract_coordinates(line: dict) -> list:
return [line.get(c) for c in ['x0', 'y0', 'x1', 'y1']]
def create_slave(coordinates: list, distance: float) -> dict:
x0, y0, x1, y1 = coordinates
y0 += distance
y1 += distance
return {'editable': True, 'xref': 'x', 'yref': 'y', 'layer': 'above', 'opacity': 1, 'line': {'color': 'green', 'width': 3, 'dash': 'solid'}, 'type': 'line', 'x0': x0, 'y0': y0, 'x1': x1, 'y1': y1}
x_pos = [0]
y_pos = [0]
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Div(
[
dcc.Input(
id='y_distance',
type='number',
value=1.0,
step=0.1,
),
html.Button('duplicate line', id='btn')
],
style={'width': '10%'}
),
dcc.Graph(
id='graph',
config={'modeBarButtonsToAdd': ['drawline','eraseshape']}
),
dcc.Interval(
id = 'graph-update',
interval = 1000,
n_intervals = 0
)
]
)
@app.callback(
Output('graph', 'figure'),
Input('graph-update', 'n_intervals')
)
def update(n):
global x_pos
global y_pos
x_pos += [x_pos[len(x_pos)-1]+1]
y_pos += [y_pos[len(y_pos)-1]+randrange(-1,2)]
fig = go.Figure(
data=go.Scatter(x=x_pos, y=y_pos, mode='markers+lines'),
layout = {'newshape': {'line': {'color': 'red', 'width': 3}}})
fig.update_layout(dragmode='drawline')
fig.update_layout(uirevision=True)
return fig
@app.callback(
Output('graph', 'figure', allow_duplicate=True),
Input('btn', 'n_clicks'),
State('y_distance', 'value'),
State('graph', 'figure'),
prevent_initial_call=True
)
def update(_, y_distance, current_figure):
# get master line = the last line drawn
master_line = current_figure['layout']['shapes'][len(current_figure['layout']['shapes'])-1]
# extract coordinates
coordinates = extract_coordinates(master_line)
# create slave line in defined distance
slave_line = create_slave(coordinates=coordinates, distance=y_distance)
# update figure
current_figure['layout']['shapes'] += [slave_line]
return current_figure
if __name__ == '__main__':
app.run(debug=True, port=8051)