link to view changes axis range pyqtgraph - pyqtgraph

Assume that I have two views\plots created in pyqtgraph and then they are linked.
using lines
p2.setYLink('Plot1')
p2.setXLink('Plot1')
Question is that when we link the views, the ranges of both the views are made equal, which raises issue as one plot appears to be too much zoomed out or zoomed in.
We just want to link the views to zoom together but don't want the ranges to change as the plot looks changed.
Below is sample code that explains the issue visually
import sys
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
#QtGui.QApplication.setGraphicsSystem('raster')
try:
app = QtGui.QApplication(sys.argv)
except RuntimeError:
app = QtCore.QCoreApplication.instance()
x1 = [1,2,3,4,5]
y1 = x1
x2 = [10,20,30,40,50]
y2 = x2
win = pg.GraphicsWindow(title="pyqtgraph example: Linked Views")
win.resize(800,600)
win.addLabel("Linked Views", colspan=2)
win.nextRow()
p1 = win.addPlot(x=x1, y=y1, name="Plot1", title="Plot1")
p2 = win.addPlot(x=x2, y=y2, name="Plot2", title="Plot2: Y linked with Plot1")
p2_state = p2.vb.getState()
p1_state = p1.vb.getState()
p2.setLabel('bottom', "Label to test offset")
p2.setYLink('Plot1') ## test linking by name
p2.setXLink('Plot1')
app.exec_()

To restate the question: you want to have two views that can have different ranges and scales, but when you zoom with the mouse in one view, the other view will zoom by the same amount.
This is not the intended function of the setXLink/setYLink methods, so you will need to implement this by subclassing or monkey-patching the viewboxes. For example:
import pyqtgraph as pg
p1 = pg.plot([1,6,2,4,3])
p2 = pg.plot([30,50,10,70,20])
def scaleBy(*args, **kwds):
pg.ViewBox.scaleBy(p1.plotItem.vb, *args, **kwds)
pg.ViewBox.scaleBy(p2.plotItem.vb, *args, **kwds)
p1.plotItem.vb.scaleBy = scaleBy
p2.plotItem.vb.scaleBy = scaleBy
There is a problem, however, that you need know two things when scaling: how much to scale by (this is the same for both views, so not a problem), and around what point to scale (this is different between the views, so a bit more difficult to determine). The solution to this depends on your desired behavior.

Related

pyqtgraph: How to link two PlotWidget windows to show the same plot?

I am developing an orbital analysis tool using PyQT5 and pyqtgraph!
See: https://github.com/3fon3fonov/trifon
My tool has a plotting area with ~15 plots shown in different tab windows, which show different aspects of the data analysis.
The tool it self is assembled with the Designer-qt5, while the QGraphicView widgets are promoted to pyqtgraphs's PlotWidgets
For example in the gui.py I initialize the plots like this:
def initialize_plots(self):
global p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,pe
p1 = self.graphicsView_timeseries_RV
p2 = self.graphicsView_timeseries_RV_o_c
p3 = self.graphicsView_timeseries_phot
p4 = self.graphicsView_timeseries_phot_o_c
p5 = self.graphicsView_timeseries_activity
p6 = self.graphicsView_timeseries_correlations
# ...
pe = self.graphicsView_extra_plot
# ...
so p1-p6 in this case are different PlotWidget objects on which I add Items/Plot data, i.e. p1.plot(x,y), p1.addItem(), etc.
What I want is to link pe to any of p1-p6!. pe is an extra plot so the user can choose from those already available/created.
Thus the user can select which plot he/she wants to see next to the main plot.
Lets imagine that the ComboBox dropdown menu selects between p1-p6 objects, so
pe = p1, or later: pe = p4
for example.
Is there any way this to be done with PyQtgraph?
I really tried all kind things in the last two weeks and nothing seems to work.
I am aware of the
pe.setXLink(p1)
pe.setYLink(p2)
but this only links the axes not the plot object itself. It doesn't work for me.
I implemented something like that using Docks and a DockArea. I simply added several docks stacked below each other.
They can be shown using either by clicking on the labels or by using the raiseDock() method of each dock.
You can simply add the PlotWidget (or any other Widget) to the dock using the addWidget() method of each dock.
The labels can be hidden or locked if you don't want the user to be able to move the docks at runtime.
import sys
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui
from pyqtgraph.dockarea import DockArea, Dock
class Accel_GUI():
def __init__(self, window, dock_area):
self.testing = 0
self.pen = pg.mkPen(color='y')
"""Setup the UI"""
self.window = window
self.dock_area = dock_area
self.window.setCentralWidget(self.dock_area)
self.spec_dock = []
self.spec_dock.append(Dock("Spectro 1",
size=(1200, 600),
autoOrientation=False))
self.spec_dock.append(Dock("Spectro 2",
size=(1200, 600),
autoOrientation=False))
self.dock_area.addDock(self.spec_dock[0], "top")
self.dock_area.addDock(self.spec_dock[1], "below", self.spec_dock[0])
if __name__ == "__main__":
app = QtGui.QApplication.instance()
if app is None:
app = QtGui.QApplication(argv)
win = QtGui.QMainWindow()
area = DockArea()
pyqtplot = Accel_GUI(win, area)
win.show()
app.exec_()
There is also an example in the pyqtgraph library that shows how to work with docks.

How to add edge labels (interactive or permanent ones) for networkx graph in bokeh?

I would like to add label for edges in networkx graph using bokeh. How can I do this?
This question is similar to How to add permanent name labels (not interactive ones) on nodes for a networkx graph in bokeh? but different enough to warrant its own reply. As discussed in the other issue, this is currently a task that is probably harder than it should be to accomplish. I'd really encourage you to open a GitHub Issue to start a discussion of how this can be improved for users.
Here is complete example.
import networkx as nx
from bokeh.io import output_file, show
from bokeh.models import CustomJSTransform, LabelSet
from bokeh.models.graphs import from_networkx
from bokeh.plotting import figure
G=nx.barbell_graph(3,2)
p = figure(x_range=(-3,3), y_range=(-3,3))
p.grid.grid_line_color = None
r = from_networkx(G, nx.spring_layout, scale=3, center=(0,0))
r.node_renderer.glyph.size=15
r.edge_renderer.glyph.line_alpha=0.2
p.renderers.append(r)
This part is all fairly standard. To put labels on edges we must define transforms to extract the start and end coordinates from the layout provider. This code just averages the coordinates to put a label in the center of each edge (labelled by the start-end node numbers):
from bokeh.transform import transform
# add the labels to the edge renderer data source
source = r.edge_renderer.data_source
source.data['names'] = ["%d-%d" % (x, y) for (x,y) in zip(source.data['start'], source.data['end'])]
# create a transform that can extract and average the actual x,y positions
code = """
const result = new Float64Array(xs.length)
const coords = provider.get_edge_coordinates(source)[%s]
for (let i = 0; i < xs.length; i++) {
result[i] = (coords[i][0] + coords[i][1])/2
}
return result
"""
xcoord = CustomJSTransform(v_func=code % "0", args=dict(provider=r.layout_provider, source=source))
ycoord = CustomJSTransform(v_func=code % "1", args=dict(provider=r.layout_provider, source=source))
# Use the transforms to supply coords to a LabelSet
labels = LabelSet(x=transform('start', xcoord),
y=transform('start', ycoord),
text='names', text_font_size="12px",
x_offset=5, y_offset=5,
source=source, render_mode='canvas')
p.add_layout(labels)
show(p)
Edit 07/2022: Added missing var keyword to JavaScript part, would not show labels otherwise in current bokeh version.
I faced the same problem, I did check https://docs.bokeh.org/en/latest/docs/user_guide/styling.html and found it seems bokeh does not support well for the knowledge graph, including edge labels.

Most efficient way of displaying jpgs with bokeh? Image_rgba surprisingly slow

I've tried using the bokeh image_rgba method but found it to be very slow, I'm just displaying a 1000*500 px image and the html takes ~5 seconds to load (nothing is web based here, I have everything running/stored locally)
Again the code itself runs fast, itùs just displaying the image thqt is slow. I've been trying exqmples from the bokeh gallery and the speed is fine.
I'm thus wondering if there is anything I could do for the html to load faster? Is image_rgba the best way to go to display an image with bokeh?
This is the code I use:
pic = PIL.Image.open('/Users/blabla/eiffelTower.jpg')
self.imgArray = np.array(pic)
N1 = imgArray.shape[0]
N2 = imgArray.shape[1]
img = np.zeros((N1,prolongatedN2), dtype=np.uint32)
view = img.view(dtype=np.uint8).reshape((N1, N2, 4))
view[:N1,:N2,0] = self.imgArray[range(N1-1,-1,-1),:N2,0]
view[:N1,:N2,1] = self.imgArray[range(N1-1,-1,-1),:N2,1]
view[:N1,:N2,2] = self.imgArray[range(N1-1,-1,-1),:N2,2]
fig = bokeh.plotting.figure(plot_width = plot_width, plot_height=plot_height)
fig.image_rgba(image=[img], x=[0], y=[0],
dw=[plot_width], dh=[plot_height])
script, div = bokeh.embed.components(p.fig, INLINE)
output_file('testBokeh.html')
show(fig)
Again I'm quite surprised that displaying a locally stored 1000*500 pixels would be so slow.
FWIW, I do this, and it's very fast.
from __future__ import division
import numpy as np
from PIL import Image
from bokeh.plotting import figure, show, output_file
# Open image, and make sure it's RGB*A*
lena_img = Image.open('Lenna_rect.png').convert('RGBA')
xdim, ydim = lena_img.size
print("Dimensions: ({xdim}, {ydim})".format(**locals()))
# Create an array representation for the image `img`, and an 8-bit "4
# layer/RGBA" version of it `view`.
img = np.empty((ydim, xdim), dtype=np.uint32)
view = img.view(dtype=np.uint8).reshape((ydim, xdim, 4))
# Copy the RGBA image into view, flipping it so it comes right-side up
# with a lower-left origin
view[:,:,:] = np.flipud(np.asarray(lena_img))
# Display the 32-bit RGBA image
dim = max(xdim, ydim)
fig = figure(title="Lena",
x_range=(0,dim), y_range=(0,dim),
# Specifying xdim/ydim isn't quire right :-(
# width=xdim, height=ydim,
)
fig.image_rgba(image=[img], x=0, y=0, dw=xdim, dh=ydim)
output_file("lena.html", title="image example")
show(fig) # open a browser

Applying mapToGlobal to main window position

Here is some code that creates a single window and, just for fun, lets you click in the window to print self.pos() and self.mapToGlobal(self.pos()):
from PySide import QtGui, QtCore
class WinPosExplore(QtGui.QWidget):
def __init__(self):
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Plastique"))
QtGui.QWidget.__init__(self)
self.show()
def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
winPos=self.pos()
globalWinPos = self.mapToGlobal(winPos)
msg1="winPos=self.pos(): <{0}, {1}>\n".format(winPos.x(), winPos.y())
msg2="self.mapToGlobal(winPos): <{0}, {1}>\n\n".format(globalWinPos.x(), globalWinPos.y())
print msg1 + msg2
def main():
import sys
qtApp=QtGui.QApplication(sys.argv)
myWinPos=WinPosExplore()
sys.exit(qtApp.exec_())
if __name__=="__main__":
main()
Because I only am using a single top-level window, I would expect the two sets of coordinates to be the same. That is, I expect the program to realize that for the parent window of the application, mapToGlobal is just the identity function. But this is not what happens. I get different coordinates in ways that are not explicable (by me). For example, the following is one printout:
winPos=self.pos(): <86, 101>
self.mapToGlobal(winPos): <176, 225>
Can anyone explain what is happening?
Note, I realize this is a silly exercise to some degree (we don't technically ever need mapToGlobal for the main window, as pointed out at this post at qtforum). But I am still curious if anyone can make sense of this behavior in this corner case.
Docs:
QPoint QWidget::mapToGlobal ( const QPoint & pos ) const
Translates the widget coordinate pos to global screen coordinates. For
example, mapToGlobal(QPoint(0,0)) would give the global coordinates of
the top-left pixel of the widget.
The input to mapToGlobal is a point in widget coordinates, i.e. it's relative to the top-left of widget area.
To clarify, consider this image from docs:
mapToGlobal(self.pos()) will give you self.geometry().topLeft() + self.pos()
Note: mapToGlobal(QPoint(0, 0)) will not give same result as self.pos() either. This is because window decorations like title bar, etc. are not included in the widget area (see the difference between geometry().topLeft() and pos() in the image above).

pyqtgraph: add legend item for scatter plots

I'm using pyqtgraph and I'd like to add an item in the legend for scatter plots.
I've adapted the example code to demonstrate:
# -*- coding: utf-8 -*-
"""
Demonstrates basic use of LegendItem
"""
import initExample ## Add path to library (just for examples; you do not need this)
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
plt = pg.plot()
plt.setWindowTitle('pyqtgraph example: Legend')
plt.addLegend()
c1 = plt.plot([1,3,2,4], pen='r', name='red plot')
c2 = plt.plot([2,1,4,3], pen='g', fillLevel=0, fillBrush=(255,255,255,30), name='green plot')
c3 = plt.plot([4,3,2,1], pen=None, symbol='o', symbolPen='y', symbolBrush='r', name="point plot")
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
What I get as a result is:
How do I add an appropriate legend item?
The current release of pyqtgraph does not support this feature.
However, it is supported in the unstable branch. You can copy LegendItem.py from the unstable branch here: http://bazaar.launchpad.net/~luke-campagnola/pyqtgraph/inp/view/head:/pyqtgraph/graphicsItems/LegendItem.py

Resources