Hello, I’m trying to place vertical lines on a graph clicking on a segment of the graph, specifically I want to give the user the liberty to click two times and place the markers by clicking in some part of the graph, after placing them I want the program to give the difference between the X-axes. For example, if I place a marker at X:90 and at X:120, I want it to give me 30 as the difference. Also, to erase those lines I want to do it clicking them again, I don’t know if this is possible, anyways I’ll leave you an image that describes more specifically what I want to do:
In advance, thank you!
Hi @raxhacks welcome to teh forums.
To get you startet, you will have to know the coordinates of the click:
The you could generate the lines by using this:
Thank you so much, I’ll give it a try. This is my first time using Plotly and developing a web-based system, I hope I can make it work. Just a question, is it possible to apply the call-back function offline? Because my project is a local program that uses pyqt5, I’m embedding plotly in the available widgets in pyqt5. In advance, thank you.
Hi @raxhacks ,
ahh, I am sorry, I did not see that your question was tagged with plotly-python
. My answer refers to dash. What you are trying to achieve might be possible in plotly, but I did not try. Unfortunately I do not know anything about pyqt5
Ohh okay, no problem.
Hey @raxhacks,
Take a look here, this might be able to help you out.
The figures themselves are widgets and give callback data.
Hi thank you, I was trying to apply the same logic but it isn’t working. I don’t know why but the call-back functions are not triggered. I’m also using ipywidgets, but the on_click function doesn’t seem to work. I don’t know if it’s because I’m working with offline plots that are shown in a PyQt5 interface.
Can PyQt5 load websites into the layout? If so, you could host your dynamic charts via dash and possibly get the desired callback.
in response to your other topic, can you define what you mean by “offline”? You have no internet access at all?
What’s the difference between what you are running and running a dash app (locally)?
Hello, let me explain. I’m working on a local program in which I need to display the graphs in a PyQt widget. As far as I know about this topic, I’m pretty new at it, I need to embed the HTML code to the PyQt using the offline function available on plotly, otherwise, I could run the graph in the web browser. Something I didn’t know was that I could embed websites into a layout and still be offline (I don’t know if that was what you meant), I’ll make my research. In the other topic I started, I emphasized the offline mode cause reading Plotly’s on_click documentation it says that if I use Plotly.offline.plot the call-back functions were never triggered, but I’m not able to embed Plotly to a PyQt widget without it.
Sweet. Take a look and see what you find out.
Fig.show() is what makes the figs show in web browsers automatically.
Hi! Look what I got:
import sys
import threading
from PyQt5 import QtWidgets
from plotly import *
import dash
import dash_core_components as dcc
import dash_html_components as html
import sys
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWidgets import QApplication
import numpy as np
import json
from dash.dependencies import Input, Output, State
def run_dash(data, layout):
app = dash.Dash()
app.layout = html.Div(children=[
dcc.Graph(
id='example-graph',
figure={
'data': data,
'layout': layout
})
])
@app.callback(
Output("debug", "children"),
Input("fig", "clickData"),
)
def point_clicked(clickData):
print("a")
clicked=[]
clicked.append(clickData)
return json.dumps(clickData)
app.run_server(debug=False)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
web = QWebEngineView()
#web.load(QUrl("https://www.google.com"))
web.load(QUrl("http://127.0.0.1:8050"))
self.setCentralWidget(web)
if __name__ == '__main__':
data = [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
]
layout = {
'title': 'Dash Data Visualization'
}
threading.Thread(target=run_dash, args=(data, layout), daemon=True).start()
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
You can run it on your computer. It works, I was able to embed the plotly-dash graph to PyQt5 and see if the callbacks work. They aren’t. I don’t know why, but when I click the graph, it doesn’t do anything.
Run it in debug=True.
It will give you issues if you have them.
Most of the data is json encoded, so you shouldn’t need the json.dumps.
Okay, I tried it but it gives me this error: “signal only works in main thread of the main interpreter”, I don’t know if this has to do with the thread I created.
I’d host the dash app separately and then pass the url to the other app.
New update, I was able to display the graph with the debug=True, the only thing is that the call-backs are still not working.
Ok. That’s good.
I think clickData works specifically with points. Are you trying to click on a point?
Bro it took me 5 seconds reading some examples to see the problem I finally got it working, it was because of the ids at the callback function:
@app.callback(
Output("debug", "children"),
Input("fig", "clickData"),
)
This is my new test code:
import sys
import threading
from PyQt5 import QtWidgets
from plotly import *
import dash
import dash_core_components as dcc
import dash_html_components as html
import sys
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWidgets import QApplication
import numpy as np
import json
from dash.dependencies import Input, Output, State
def run_dash(data, layout):
app = dash.Dash()
app.layout = html.Div(children=[
dcc.Graph(
id='fig',
figure={
'data': data,
'layout': layout
}),dash.html.Div(id="debug"),
])
@app.callback(
Output("debug", "children"),
Input("fig", "clickData"),
)
def point_clicked(clickData):
print("a")
clicked=[]
clicked.append(clickData)
app.run_server(debug=True, use_reloader=False)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
web = QWebEngineView()
#web.load(QUrl("https://www.google.com"))
web.load(QUrl("http://127.0.0.1:8050"))
self.setCentralWidget(web)
if __name__ == '__main__':
data = [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
]
layout = {
'title': 'Dash Data Visualization'
}
threading.Thread(target=run_dash, args=(data, layout), daemon=True).start()
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
I was really stuck and didn’t know what to do. Thank you so much for your help!
You’re welcome man.
Glad you got it working.
Bro I’m sorry but one last question. I want this to work in several displayed graphs, so I change my code a little bit in order to test the different graph (in this case automatically), but whenever I click the graph, it says: "In the callback for output(s):
“debug1.children
Output 0 (debug1.children) is already in use.
Any given output can only have one callback that sets it.
To resolve this situation, try combining these into
one callback function, distinguishing the trigger
by using dash.callback_context
if necessary.”
Here’s the code:
import sys
from plotly.graph_objects import Figure, Scatter, FigureWidget
import threading
from PySide2 import QtCore
from PyQt5 import QtWidgets
from plotly import *
import dash
import dash_core_components as dcc
import dash_html_components as html
import sys
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWidgets import QApplication
import numpy as np
import json
from dash.dependencies import Input, Output, State
clicked=[]
def getLenClicks():
return len(clicked)
def run_dash(data, layout):
app = dash.Dash()
app.layout = html.Div(children=[
dcc.Graph(
id='fig',
figure={
'data': data,
'layout': layout
}),dash.html.Div(id="debug"),
])
@app.callback(
Output("debug", "children"),
Input("fig", "clickData"),
)
def point_clicked(clickData):
global clicked
lenClicks=getLenClicks()
if lenClicks==2:
clicked=[]
points=clickData.get('points')
x=points[0].get('x')
clicked.append(x)
print(clicked)
app.run_server(debug=False)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
web = QWebEngineView()
data = [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'scatter', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'scatter', 'name': u'Montréal'},
]
layout = {
'title': 'Dash Data Visualization'
}
fig=FigureWidget(data,layout)
self.qdask = QDash()
self.qdask.run(debug=True, use_reloader=False)
self.qdask.update_graph(fig)
web.load(QUrl("http://127.0.0.1:8050"))
self.setCentralWidget(web)
data = [
{'x': [3,5,6], 'y': [4, 1, 2], 'type': 'scatter', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'scatter', 'name': u'Montréal'},
]
layout = {
'title': 'Dash Data Visualization'
}
fig1=FigureWidget(data,layout)
self.qdask.update_graph(fig1)
web.reload()
#web.load(QUrl("https://www.google.com"))
class QDash(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._app = dash.Dash()
self._app.layout = html.Div()
def update_graph(self, df):
fig = df
self._app.layout = html.Div(children=[
dcc.Graph(
id='fig',
figure={
'data': fig.data,
'layout': fig.layout
}),dash.html.Div(id="debug1"),
])
@self._app.callback(
[Output("debug1", "children")],
Input("fig", "clickData")
)
def point_clicked(clickData):
global clicked
if len(clicked)==2:
clicked=[]
points=clickData.get('points')
x=points[0].get('x')
clicked.append(x)
print(clicked)
def run(self, **kwargs):
threading.Thread(target=self._app.run_server, kwargs=kwargs, daemon=True).start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
if I run the code without the fig1 it works perfectly, but if I change the graph, it throws this error, and the on_click call-back stops working. In my real program is a set of graphs, so I need this to work in all of them, and also, I changed my code to this because this is the way that worked for me in order to update the graphs in real time.