How to tilt the datetime string displayed on the pyqtgraph X axis - python

I've solved the problem of pyqtgraph displaying datetime string on the X-axis, but many datetime string will cause overlap.
How to tilt the datetime string displayed on the pyqtgraph X axis to avoid overlapping across the display,I am deeply indebted to you for this help
self.topFiller = QtWidgets.QWidget()
self.topFiller.setMinimumSize(2000, 9000)
graphicsView = QtWidgets.QGraphicsView(self.topFiller)
y1=[100,2,8,5,9,6,1,3,9,11,13,1,1,2,8,5,9,6,1,3,9,11,13,1]
x1=["2019-7-1\r\n11:11:11","2019-7-1\r\n12:11:11","2019-7-1\r\n13:11:11","2019-7-1\r\n14:11:11","2019-7-1\r\n15:11:11","2019-7-1\r\n16:11:11","2019-7-1\r\n11:11:11","2019-7-1\r\n12:11:11","2019-7-1\r\n13:11:11","2019-7-1\r\n14:11:11","2019-7-1\r\n15:11:11","2019-7-1\r\n16:11:11","2019-7-1\r\n11:11:11","2019-7-1\r\n12:11:11","2019-7-1\r\n13:11:11","2019-7-1\r\n14:11:11","2019-7-1\r\n15:11:11","2019-7-1\r\n16:11:11","2019-7-1\r\n11:11:11","2019-7-1\r\n12:11:11","2019-7-1\r\n13:11:11","2019-7-1\r\n14:11:11","2019-7-1\r\n15:11:11","2019-7-1\r\n16:11:11"]
xdict1=dict(enumerate(x1))
stringaxis1 = pg.AxisItem(orientation='bottom')
stringaxis1.setTicks([xdict1.items()])
pw = pg.PlotWidget(graphicsView, left="rate", bottom="time", title="g1/"+str(i)+" in rate",axisItems={'bottom': stringaxis1})
curvein=pw.plot(x=list(xdict1.keys()),y=y1)
pw.getAxis("bottom").setLabel( color='#0000ff')
pw.setXRange(0,10)

You could make your own subclass of AxisItem and reimplement its painting method. I just copied it from the source and modified the part that refers to the painting of the ticks. The trick is to "save" the state of the painter and rotate it before drawing the text, then "restore" it. Since rotation is based on the source position of the painter, I also had to translate to the textRect position and swap its width and height.
It might need some small polishing to ensure that the text is correctly aligned, but it should work, and it also sets the minimum height of the axis item based on the tick text.
class MyAxisItem(pg.AxisItem):
def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
p.setRenderHint(p.Antialiasing, False)
p.setRenderHint(p.TextAntialiasing, True)
## draw long line along axis
pen, p1, p2 = axisSpec
p.setPen(pen)
p.drawLine(p1, p2)
p.translate(0.5,0) ## resolves some damn pixel ambiguity
## draw ticks
for pen, p1, p2 in tickSpecs:
p.setPen(pen)
p.drawLine(p1, p2)
## Draw all text
if self.tickFont is not None:
p.setFont(self.tickFont)
p.setPen(self.pen())
for rect, flags, text in textSpecs:
# this is the important part
p.save()
p.translate(rect.x(), rect.y())
p.rotate(-90)
p.drawText(-rect.width(), rect.height(), rect.width(), rect.height(), flags, text)
# restoring the painter is *required*!!!
p.restore()
stringaxis1 = MyAxisItem(orientation='bottom')
stringaxis1.setTicks([xdict1.items()])
# find the maximum width required by the tick texts and add a small margin
fm = QtGui.QFontMetrics(stringaxis1.font())
minHeight = max(fm.boundingRect(QtCore.QRect(), QtCore.Qt.AlignLeft, t).width() for t in xdict1.values())
stringaxis1.setHeight(minHeight + fm.width(' '))

Related

PyQt5 QGraphicsSimpleTextItem Y position

Here is the simple code that draws letters using PyQt5.
setPos is 0, 0 but letters not at the top of the window.
Horizontally letters not at the window edge too.
What is wrong?
Thank you
from PyQt5 import QtWidgets, QtGui, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtGui import QBrush, QColor
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initWindow()
def initWindow(self):
self.setGeometry(200,200,1000,400)
self.show()
self.scene = QtWidgets.QGraphicsScene()
self.graphics = QtWidgets.QGraphicsView(self.scene, self)
self.graphics.setGeometry(0, 0, 1000, 400)
self.scene.setSceneRect(0, 0, 1000, 400)
self.graphics.setHorizontalScrollBarPolicy(1)
self.graphics.setVerticalScrollBarPolicy(1)
self.graphics.setFrameStyle(0)
text = QtWidgets.QGraphicsSimpleTextItem()
_font = QtGui.QFont()
_font.setPixelSize(200)
_font.setBold(False)
text.setFont(_font)
text.setText('DDD')
text.setBrush(QBrush(QColor(0,0,0)))
text.setPos(0, 0)
self.scene.addItem(text)
self.graphics.show()
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec())
As the QGraphicsSimpleTextItem documentation explains, the positioning of the text is based on the font "bounding rectangle":
Each character symbol of a font uses a "baseline" for reference, an "ascent" (how much the font goes from the baseline to the top) and a descent (how much it goes down for letters like lowercase "g" or "q"). Also, most characters do not start exactly on the "left" 0-point, but there is always some margin of "horizontal margin".
If you want to position it exactly at the top-left, you'll need to use QFontMetrics, which provides specific metrics information about a font; also, since we're dealing with a QGraphicsScene, QFontMetricsF is more indicated, as it returns floating point precision. The most important function for this is tightBoundingRect().
If I add the following to your code, just after adding the text item:
outRect = self.scene.addRect(text.boundingRect())
outRect.setPen(QtCore.Qt.red)
fm = QtGui.QFontMetricsF(_font)
boundingRect = fm.tightBoundingRect(text.text())
self.scene.addRect(boundingRect.translated(0, text.boundingRect().bottom() - fm.descent()))
The result is clear (I used a different string to better show the differences):
The red rectangle indicates the actual bounding rectangle of the item (which has the same size of QFontMetrics(_font).boundingRect(QtCore.QRect(), QtCore.Qt.AlignCenter, text.text()) would return.
The black rectangle shows the "tight" bounding rectangle, which is the smallest possible rectangle for that string.
The tight rectangle (as the one provided by QFontMetrics.boundingRect) uses the baseline as the 0-point for the coordinates, so it will always have a negative y position and probably (but it depends on the font and the style) an x greater than 0.
Finally, to get your text item placed with the characters aligned on the top left corner of the scene, you'll need to compute its position based on that tight rectangle:
text.setPos(-boundingRect.left(), -(fm.ascent() + boundingRect.top()))
The left has to be negative to compensate the horizontal text positioning, while the negative vertical position is computed by adding the ascent to the boundingRect top (which is negative in turn).
Keep in mind, though, that the bounding rectangle will still be the bigger red rectangle shown before (obviously translated to the new position):
So, while the text appears aligned on the top left, the item bounding rect top-left corner is actually outside the scene rectangle, and its bottom-left corner exceedes its visual representation.
This is important, as it has to do with item collision detection and mouse button interaction.
To avoid that, you'd need to subclass QGraphicsSimpleTextItem and override boundingRect(), but keep in mind that positioning (expecially vertical) should always be based on the baseline (the ascent), otherwise you'll probably get unexpected or odd behavior if the text changes.

Mayavi - Mouse hovering interaction & plot same scene from different perspectives

i am currently struggling to see through how to interact in an appropriate way with a mayavi rendered scene.
I have a lidar point cloud which gets plotted by the function points3d(), now i have set in addition a bounding box around a car in between the point cloud, and i would like to change the color of the points inside the box as soon as i hover with my mouse over the bounding box.
Can you tell me how I can just select the points inside the bbox and change their color?
And my second question is, how can i show the same scene of the pointcloud in a 3d view and a bird view, concurrently?
Thank you very much :]
I have found a solution regarding the color problem - I don't know if it is best practice. But i still need help for determining the points inside the bounding box. I would also like to create a gui which enables the user to modify the size and orientation of the bounding box. [but that is an other topic]
import numpy as np
from mayavi.mlab import draw, points3d
from tvtk.api import tvtk
# Primitives
N = 3000 # Number of points
ones = np.ones(N) #np.hstack((np.tile(np.array([1]), int(N/2)).T, np.tile(np.array([4000]), int(N/2)).T))
scalars = ones #np.arange(N) # Key point: set an integer for each point
# Define color table (including alpha), which must be uint8 and [0,255]
colors = np.vstack((np.tile(np.array([[255],[255],[0]]), int(N/2)).T, np.tile(np.array([[0],[0],[255]]), int(N/2)).T))
# Define coordinates and points
x, y, z = (np.random.random((N, 3))*255).astype(np.uint8).T # Assign x, y, z values to match color
pts = points3d(x, y, z, scale_factor=10) # Create points
#pts.glyph.color_mode = 'color_by_vector' # Color by scalar
# Set look-up table and redraw
#pts.module_manager.scalar_lut_manager.lut.table = colors
pts.glyph.scale_mode = 'scale_by_vector'
sc=tvtk.UnsignedCharArray()
sc.from_array(colors)
pts.mlab_source.dataset.point_data.scalars = sc
draw()

Show grid lines over image in pyqtgraph

I'm drawing an image in pyqtgraph, and I'd like to be able to see the grid lines. But the grid lines are always drawn underneath the image, so any black areas of the image obscure the grid. Here's a fairly minimal example:
import matplotlib # necessary for interactive plots in pyqtgraph
import pyqtgraph as pg
import numpy as np
n = 100000
sigma_y = 1e-3
sigma_x = 1e-3
x0 = np.matrix([np.random.normal(0, sigma_x, n), np.random.normal(0, sigma_y, n)])
bins = 30
histogram, x_edges, y_edges = np.histogram2d(np.asarray(x0)[0], np.asarray(x0)[1], bins)
x_range = x_edges[-1] - x_edges[0]
y_range = y_edges[-1] - y_edges[0]
imv = pg.ImageView(view=pg.PlotItem())
imv.show()
imv.setPredefinedGradient('thermal')
imv.getView().showGrid(True, True)
imv.setImage(histogram, pos=(x_edges[0], y_edges[0]), scale=(x_range / bins, y_range / bins))
Here's what I see (after zooming out a little). You can see that the black area of the image obscures the grid lines.
EDIT: it's possible in the GUI to change the black colour to transparent (not my first choice, but an OK workaround for now), so you can see the grid below the image. That works OK but I can't figure out how to do it in code. How do I get the lookup table out of the ImageView to modify it?
Here is what I did.
glw = pyqtgraph.GraphicsLayoutWidget()
pw = glw.addPlot(0, 0)
# Fix Axes ticks and grid
for key in pw.axes:
ax = pw.getAxis(key)
# Set the grid opacity
if grid_is_visible:
ax.setGrid(grid_opacity * 255)
else:
ax.setGrid(False)
# Fix Z value making the grid on top of the image
ax.setZValue(1)
This did cause some other issue, I think. It may have been the context menu or it had to do with panning and zooming, because of how Qt was signaling the events. One axes got the event priority and prevented the event from propagating for the other axes to pan and zoom. I submitted a pull request to pyqtgraph, so that might not be an issue anymore. I can't remember what caused the problem though. It may work just fine for you. I was doing a lot of other things like changing the background color and changing the viewbox background color which caused some small issues.
As a note I also changed the image z value. You shouldn't have to though.
imv.setZValue(1)

PyQt QGraphicsEllipseItem rotate offset

New to PyQt and I'm having an issue rotating a QGraphicsEllipseItem. I want the ellipse to rotate around the center of the ellipse instead of the corner of the QRectF used to define the ellipse. My code looks like this (sorry, the computer I am coding it on, doesn't have internet access, so I am copying the relevant parts here by hand):
self.scene = QtGui.QGraphicsScene()
self.ui.graphicsView.setScene(self.scene)
pen = QtGui.QPen(QColor(Qt.yellow))
# Draw first Ellipse
# This code correctly places a yellow ellipse centered at the scene 500,500 point
ellipse1 = QtGui.QGraphicsEllipseItem(0,0,100,10)
ellipse1.setPen(pen)
self.scene.addItem(ellipse1)
ellipse1.setPos(500, 500)
ellipse1.translate(-50, -5)
# Now, try to draw a rotated ellipse
# This code rotates the ellipse about the 0,0 location of the rectangle
# which is the scene 450, 495 point, not the center of the ellipse
ellipse2 = QtGui.QGraphicsEllipseItem(0,0,100,10)
ellipse2.setPen(pen)
self.scene.addItem(ellipse2)
ellipse2.setPos(500, 500)
ellipse2.translate(-50, -5)
ellipse2.rotate(45.0)
OK, that is basically what I expected. Since QGraphicsEllipseItem is derived from QGraphicsItem, I tried to set the transform origin point for ellipse2 before the rotation:
ellipse2 = QtGui.QGraphicsEllipseItem(0,0,100,10)
ellipse2.setPen(pen)
self.scene.addItem(ellipse2)
ellipse2.setPos(500, 500)
ellipse2.translate(-50, -5)
ellipse2.setTransformOriginPoint(450, 495)
ellipse2.rotate(45.0)
This results in the error "AttributeError: 'QGraphicsEllipseItem' object has no attribute 'setTransformOriginPoint'
Obviously, I'm doing something wrong or making an incorrect assumption about QGraphicsEllipseItem. Some sites hint that I may need to use a bounding rectangle in order to do the rotation, but I don't understand how to do that.
If someone could show me the correct way to rotate an ellipse about its center in pyqt, I would greatly appreciate it!!!
Ok, so after many weeks I was able to find my own answer although I don't really really understand why it works. My standard method of programming by brail. Anyway, the code should look like this:
transform = QtGui.QTransform()
ellipse = QtGui.QGraphicsEllipseItem(0,0,100,10)
ellipse.setPen(pen)
ellipse.setPos(500, 500)
transform.rotate(-45.0) # rotate the negative of the angle desired
transform.translate((-50, -5) # center of the ellipse
ellipse.setTansform(transform)
self.scene.addItem(ellipse)
So this successfully places the center of the rotated ellipse at the point 500,500. I'm not sure why you would take the negative of the angle you want to rotate, but it seems to work. If anyone can explain why it works this, I would appreciate it.
I got the same problem and spent two whole days to solve it.This is my solution:
First of all you should define the coordinates(x,y) of the point around which the ellipse should rotate, this way:
ellipse.setTransformOriginPoint(QPointF(?, ?))
then you can use the setRotation() to rotate ellipse
the whole code can be seen below:
__author__ = 'shahryar_slg'
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MainWindow(QDialog):
def __init__(self):
super(QDialog, self).__init__()
self.view = QGraphicsView()
self.scene = QGraphicsScene()
self.layout = QGridLayout()
self.layout.addWidget(self.view, 0, 0)
self.view.setScene(self.scene)
self.setLayout(self.layout)
self.ellipse = QGraphicsEllipseItem(10, 20, 100, 60)
self.ellipse.setTransformOriginPoint(QPointF(100/2+10, 60/2+20))
self.ellipse.setRotation(-60)
self.scene.addItem(self.ellipse)
# I created another ellipse on the same position to show you
# that the first one is rotated around it's center:
self.ellipse2 = QGraphicsEllipseItem(20, 20, 100, 40)
self.scene.addItem(self.ellipse2)
self.update()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Pay attention to the way I've calculated the center of the ellipse.

How to scroll a tkinter canvas to an absolute position?

I'm using Python and tkinter. I have a Canvas widget that will display just one image. Most times the image will be larger than the canvas dimensions, but sometimes it will be smaller. Let's just focus on the first case (image larger than canvas).
I want to scroll the canvas to an absolute position that I have already calculated (in pixels). How can I do that?
After trying for around half hour, I got another solution that seems better:
self.canvas.xview_moveto(float(scroll_x+1)/img_width)
self.canvas.yview_moveto(float(scroll_y+1)/img_height)
img_width and img_height are the dimensions of the image. In other words, they are the full scrollable area.
scroll_x and scroll_y are the coordinates of the desired top-left corner.
+1 is a magic value to make it work precisely (but should be applied only if scroll_x/y is non-negative)
Note that the current widget dimension is not needed, only the dimension of the contents.
This solution works very well even if the image is smaller than the widget size (and thus the scroll_x/y can be negative).
EDIT: improved version:
offset_x = +1 if scroll_x >= 0 else 0
offset_y = +1 if scroll_y >= 0 else 0
self.canvas.xview_moveto(float(scroll_x + offset_x)/new_width)
self.canvas.yview_moveto(float(scroll_y + offset_y)/new_height)
This is what I have already done:
# Little hack to scroll by 1-pixel increments.
oldincx = self.canvas["xscrollincrement"]
oldincy = self.canvas["yscrollincrement"]
self.canvas["xscrollincrement"] = 1
self.canvas["yscrollincrement"] = 1
self.canvas.xview_moveto(0.0)
self.canvas.yview_moveto(0.0)
self.canvas.xview_scroll(int(scroll_x)+1, UNITS)
self.canvas.yview_scroll(int(scroll_y)+1, UNITS)
self.canvas["xscrollincrement"] = oldincx
self.canvas["yscrollincrement"] = oldincy
But... As you can see... it's very hackish and ugly. To much workaround for something that should be simple. (plus that magic +1 I was required to add, or it would be off-by-one)
Does anyone else have another better and cleaner solution?
In tkinter you can get the width and height of a PhotoImagefile. You can just call it when you use canvas.create_image
imgrender = PhotoImage(file="something.png")
##Other canvas and scrollbar codes here...
canvas.create_image((imgrender.width()/2),(imgrender.height()/2), image=imgrender)
## The top left corner coordinates is (width/2 , height/2)
I found this solution by using self.canvas.scan_dragto(x, y)
Edit: I develop an interface which can scroll, zoom, and rotate an image. Let's extract from my interface the code.
When I want to save the current position of image I use this:
# 1) Save image position
x0canvas = -self.canvas.canvasx(0)
y0canvas = -self.canvas.canvasy(0)
x0, y0 = self.canvas.coords(text)
ximg = x0
yimg = y0
# 2) Restore image position (for example: after a load)
self.text = canvas.create_text(ximg, yimg, anchor='nw', text='')
self.xyscroll(x0canvas, y0canvas)
# Rotate and zoom image
image = Image.open('fileImg.jpg')
..
imageMod = image.resize(new_size)
if rotate != 0:
imageMod = imageMod.rotate(rotate)
imagetk = ImageTk.PhotoImage(imageMod)
imageid = canvas.create_image(canvas.coords(text), anchor='nw', image=imagetk)

Categories

Resources