Reputation: 75
I want to plot an image, draw freehand over the image, then be able to press a custom button so that freehand drawing is now in a different colour. I cannot figure out how to make the button press change the line colour though.
The code I have tried is here below. I've tried using all four button methods described in the documentation, but none of them have any effect when pressed.
Furthermore, I can't find anywhere in the documentation how to access the lines that have been drawn over the image once finished (other than having to save the image manually using the GUI which I want to avoid).
# Imports
import plotly
import plotly.graph_objects as go
import plotly.express as px
# Show image
img = cv2.imread(fpath)
fig = px.imshow(img)
# Enable freehand drawing on mouse drag
fig.update_layout(overwrite=True,
dragmode='drawopenpath',
newshape_line_color='cyan',
modebar_add=['drawopenpath',"eraseshape"])
# Add two buttons, 'r' and 'b' which attempt to update newshape_line_color...
fig.update_layout(
updatemenus=[
dict(
type="buttons",
direction="right",
active=0,
showactive=True,
x=0.57,
y=1.2,
buttons=list([
{
'label':"r",
'method':"relayout",
'args':[{'newshape_line_color':'red'}],
},
dict(label="b",
method="restyle",
args=[{"newshape_line_color": 'blue'}]),
]),
)
])
# Show figure
config = dict({'scrollZoom': True})
fig.show(config = config)
Any help would be greatly appreciated!
Upvotes: 1
Views: 919
Reputation: 18714
If I understand correctly, you want all of the drawn lines to change color when the button is selected.
I've got two solutions for you.
The first doesn't do exactly what you're asking for, but it's entirely in Python. Instead of changing the last line drawn, all subsequent lines are drawn with the selected color.
The second does do what you're looking for but requires JS.
Here's the updated updatemenus
. You were pretty close, actually. I've created two color buttons: red and green.
fig.update_layout(
updatemenus = list([
dict(type = "buttons",
direction = "right",
active = 0,
showactive = True,
x = 0.57,
y = 1.2,
buttons = list([ # change future colors
dict(label = "Make Me Red",
method = "relayout",
args = [{'newshape.line.color': 'red'}]
),
dict(label = "Make Me Green",
method = "relayout",
args = [{'newshape.line.color': 'green'}]
)
])
)
])
)
When you select the color button, all lines drawn after will be in the color selected.
Here's the entire chunk of code used to make this:
from skimage import io
import plotly.graph_objects as go
import plotly.express as px
# Show image
img = io.imread('https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Crab_Nebula.jpg/240px-Crab_Nebula.jpg')
fig = px.imshow(img)
# Enable freehand drawing on mouse drag
fig.update_layout(overwrite=True,
dragmode='drawopenpath',
newshape_line_color='cyan',
modebar_add=['drawopenpath',"eraseshape"])
fig.add_shape(dict(editable = True, type = "line",
line = dict(color = "white"),
layer = 'above',
x0 = 0, x1 = 200.0000001,
y0 = 0, y1 = 200.0000001))
# Add two buttons, 'r' and 'b' which attempt to update newshape_line_color...
fig.update_layout(
updatemenus = list([
dict(type = "buttons",
direction = "right",
active = 0,
showactive = True,
x = 0.57,
y = 1.2,
buttons = list([ # change future colors
dict(label = "Make Me Red",
method = "relayout",
args = [{'newshape.line.color': 'red'}]
),
dict(label = "Make Me Green",
method = "relayout",
args = [{'newshape.line.color': 'green'}]
)
])
)
])
)
# Show figure
config = dict({'scrollZoom': True})
fig.show(config = config)
You'll use everything up to fig.show()
then you'll use the following. Yes, it creates an external file, but it will immediately open in your browser, as well.
This piggybacks off of your buttons. When green is clicked now, it will change all of the lines, not just what's drawn next. There are two events here, one for each color.
In the JS, you'll notice two for
loops in each event. These serve very different purposes. Because there doesn't seem to be a built-in event to do this for me, the first loop changes the actual attributes of the plot. However, that won't be visible immediately. So the second loop changes what you actually see at that moment.
This requires the Plotly io
package. You had called import plotly
, so you could just change pio
to plotly.io
instead of calling it though.
import plotly.io as pio
pio.write_html(fig, file = 'index2.html', auto_open = True,
config = config, include_plotlyjs = 'cdn', include_mathjax = 'cdn',
post_script = "setTimeout(function() {" +
"btns = document.querySelectorAll('g.updatemenu-button');" +
"btns[0].addEventListener('click', function() {" +
"ch = document.getElementById('thisCh');" +
"shapes = ch.layout.shapes; /* update the plot attributes */" +
"for(i = 0; i < shapes.length; i++) {" +
"shapes[i].line.color = 'red';" +
"} /* update the current appearance immediately */" +
"chart = document.querySelectorAll('g.shapelayer')[2];" +
"for(i = 0; i < chart.children.length; i++) {" +
"chart.children[i].style.stroke = 'red';" +
"}" +
"});" +
"btns[1].addEventListener('click', function() {" +
"ch = document.getElementById('thisCh');" +
"shapes = ch.layout.shapes; /* update the plot attributes */" +
"for(i = 0; i < shapes.length; i++) {" +
"shapes[i].line.color = 'green';" +
"} /* update the current appearance immediately */" +
"chart = document.querySelectorAll('g.shapelayer')[2];" +
"for(i = 0; i < chart.children.length; i++) {" +
"chart.children[i].style.stroke = 'green';" +
"}" +
"});" +
"}, 200)", full_html = True, div_id = "thisCh")
If I've misunderstood what you're looking for or if you have any questions, let me know. Oh, and if you go with the second solution but wanted many colors, I can make it color dynamic, I didn't do that with only two colors that I used for this demonstration.
Upvotes: 1