Hi,
I have a go.Candlestick chart where I also let the user draw lines by using âfig.show(config={âmodeBarButtonsToAddâ:[âdrawlineâ]})â.
Is it possible for the user to copy/paste a line that has been drawn by the user so that we get two parallel lines? Then it would be possible for the user to grab one of the lines with the mouse and move it to visualize a trend channel in the chart.
Thanks
In Dash you could do this up to certain extend. I think I answered a similar question here, maybe youâll find how to to it via the forum search.
Thanks, yes, sorry, it was a follow-up question in topic 72224, but I unfortunately didnât manage to get that to work, but will give it another try.
I hope the Plotly developers some day will add button to copy/paste the selected shape, that you could add with âmodeBarButtonsToAddâ, would be perfect
As I said, thatâs not too complicated to do in dash. If I find the time Iâll write a MRE.
Basically in a callback you extract the line coordinates, add an amount delta x or delta y to each and create a new line with the new coordinates. Then you append the new line to the shapes list in the figure layout and return the updated figure.
From dash 2.9 you could use the partial update for that so you would not have to return the complete figure.
I doubt this will ever find its way as built-in functionality because itâs a pretty specific thing.
Here is a MRE for one possible approach. You could also use the relayoutData
as input, analyse what changed and forget about the update button.
import dash
from dash import Input, Output, html, dcc, State
import plotly.graph_objects as go
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
x0 += distance
x1 += distance
return {
'editable': False,
'xref': 'x',
'yref': 'y',
'layer': 'above',
'opacity': 1,
'line':
{
'color': 'red',
'width': 3,
'dash': 'dash'
},
'type': 'line',
'x0': x0,
'y0': y0,
'x1': x1,
'y1': y1,
}
# create figure
figure = go.Figure(
data=go.Scatter(x=[1, 2, 3], y=[1, 2, 3], mode='markers+lines'),
layout={
'newshape': {
'line': {
'color': 'red',
'width': 3,
},
}
}
)
# add some buttons
graph_config = {
'modeBarButtonsToAdd': [
'drawline',
'eraseshape'
]
}
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Div(
[
dcc.Input(
id='x_distance',
type='number',
value=0.3,
step=0.1,
),
html.Button('update line', id='btn')
],
style={'width': '10%'}
),
dcc.Graph(
id='graph',
figure=figure,
config=graph_config
)
]
)
@app.callback(
Output('graph', 'figure'),
Input('btn', 'n_clicks'),
State('x_distance', 'value'),
State('graph', 'figure'),
prevent_initial_call=True
)
def update(_, x_distance, current_figure):
# get master line
master_line = current_figure['layout']['shapes'][0]
# extract coordinates
coordinates = extract_coordinates(master_line)
# create slave line in defined distance
slave_line = create_slave(coordinates=coordinates, distance=x_distance)
# update figure
current_figure['layout']['shapes'] = [master_line, slave_line]
return current_figure
if __name__ == '__main__':
app.run(debug=True, port=8051)
mred shapes
Thanks so much!
I also figured out that if I want to keep other lines that I have drawn (instead of only the master_line and slave_line) I could change the row âcurrent_figure[âlayoutâ][âshapesâ] = [master_line, slave_line]â to this:
current_figure[âlayoutâ][âshapesâ] += [slave_line]
A follow-up question if you donât mind: Right now master_line is always set to current_figure[âlayoutâ][âshapesâ][0], but how can I change that so master_line is set to the currently selected line in the chart?
Hi @robin1,
this question came up quite often:
How to detect which shape is currently selected. There might be a way to do so in JS, but in python I donât think this is possible because this happens clientside.