drawPie() with customized borders - python

Is it possible to draw a pie shape with no border at the arc, but with borders at straight lines? I have attached a picture below:
Currently I have implemented this by first calling calling drawPie() with painter.setPen(QtCore.Qt.NoPen), and then later using QLineF to draw the lines separately based on the center and angles of the pie shape.
But the problem is that the line position does not sync with the pie shape if the angles are not multiples of 90. Attached another picture showing the problem.
Is there a simple/elegant way to do this?
Thanks!

Assuming your custom Pie is a subclassed QGraphicsRectItem, you could try something like this:
class CustomPie(QtGui.QGraphicsRectItem):
angle = 2000
def paint(self, painter, option, widget):
# Create the path to draw the lines
path = QtGui.QPainterPath()
path.moveTo(self.rect().width()/2, self.rect().height()/2)
path.lineTo(self.rect().width(), self.rect().height()/2)
path.arcMoveTo(self.rect(), self.angle/16) # arcMoveTo in degrees
path.lineTo(self.rect().width()/2, self.rect().height()/2)
# draw a pie with no Pen
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.lightGray))
painter.drawPie(self.rect(), 0, self.angle)
# Draw the path with a custom Pen
painter.setPen(QtGui.QPen(QtCore.Qt.black, 2))
painter.drawPath(path)
Here we override paint to draw a Pie and a path (actually quite similar to your own method).
You would have to override __init__ as well (angle as a class attribute is probably not what you want) but that's the idea.

Related

QGraphicsItem paint delay

What could be the possible reason for this? When i zoom in the QGraphicsView and move the QGraphicsItem, I get this weird result. It does update if I zoom or pan the View again or if I focus on other widgets. Im using PySide. And the painter function is this
def paint(self, painter, option, widget):
if self.isSelected():
brush = self.highlight_brush
pen = self.highlight_pen
else:
brush = self.dormant_brush
pen = self.dormant_pen
painter.setBrush(brush)
painter.setPen(pen)
painter.drawRect(0, 0, 100, 100)
Why does this happen even for this basic paint event? This problem is not seen if there is no Pen. If I increase the pen width, this issue is disturbingly visible.
I don't know the actual solution for this rendering artifacts. But, updating the view during mouseMoveEvent did fix the issue.
def mouseMoveEvent(self, event):
QGraphicsView.mouseMoveEvent(self, event)
if self.scene().selectedItems():
self.update()
The error you are seeing is probably because parts of what you are drawing are outside the bounding rectangle. My guess is you are using the same values to calculate the rectangle you are drawing as you are to calculate the bounding rectangle. Applying a pen then will make the drawn rectangle wider than the bounds and so will result in the smearing you are seeing.
I had the same problem. This is my solution:
As #Nathan Mooth said, the problem was that I was drawing outside of the boundingRect, so I just made my rounded rectangle(what I'm drawing in the paint() method) 10 units width and height less than the boundingRect:
# Setup Rect
frameRect = self.boundingRect()
frameRect.setWidth(self.boundingRect().width() - 10)
frameRect.setHeight(self.boundingRect().height() - 10)
This is how it was looking before(GIF):
This is how it looks now(GIF)
Note: I added color selection and changed the color of the drop shadow. So it looks a bit different.

QT5 QgraphicsScene: how to draw on th forground pixel by pixel

I am using PyQt5. I am making a program of robots moving in a maze.
For that, I use QGraphicsScene. I add objects like QRect to represent robots. The background is set via SetBackgroundBrush and loaded from a png image (black represents unpassable terrain):
def update_background(self):
qim = QImage(self.model.map.shape[1],self.model.map.shape[0], QImage.Format_RGB32)
for x in range(0, self.model.map.shape[1]):
for y in range(0, self.model.map.shape[0]):
qim.setPixel(x ,y, qRgb(self.model.map[y,x],self.model.map[y,x],self.model.map[y,x]))
pix = QPixmap(qim)
self.scene.setBackgroundBrush(QBrush(pix))
What I want to do now is to visualize the work of a pathfinding algorithm (I use A* for now). Like a red line that connects the robot with its destination bending over obstacles. This line is stored as a list of (X,Y) coords. I wanted to iterate over the list and paint pixel by pixel on the scene. However I don't know how to do that - there is no "drawPixel" method. Of course, I can add a hundred of small rectangles of 1x1 size. However I will have to redraw them if the route changes.
I thought about creating an image with paths and placing it in the FOREground and then adding. However I cannot make a transparent foreground. It was not a problem with background (because it is in the back). I considered using
theis function:
http://doc.qt.io/qt-5/qpixmap.html#setAlphaChannel
But it is deprecated. It refers to QPainter. I don't know what QPainter is and I am not sure I am heading in the right direction at all.
Please give advice!
So, the question is what is the correct and efficient way to draw routes built by robots?
RobotPathItem(QGraphicsItem):
def __init__(self, path):
super().__init__()
qpath = []
for xy in path:
qpath.append(QPoint(xy[0],xy[1]))
self.path = QPolygon(qpath)
if path:
print(path[0])
def paint(self, painter, option, qwidget = None):
painter.drawPoints(self.path)
def boundingRect(self):
return QRectF(0,0,520,520)
There's no drawPixel, but QPainter has a drawPoint or drawPoints (which would be a lot more efficient in this case, I think). You'll need to create a custom graphics item that contains your list of points and iterates through your list of QPointF values and draws them. When you add points to the list, be sure to recalculate the bounding rectangle. For example, if you had a RobotPathItem (derived from QGraphicsItem), your paint method might look something like:
RobotPathItem::paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen;
// ... set up your pen color, etc., here
painter->setPen (pen);
painter->drawPoints (points, points.count ());
}
This is assuming that "points" is a QList or QVector of QPointF.

Scaling positions of GraphicsItems on a GraphicsScene without changing other properties

I layout a bunch of nodes on a QGraphicsScene. The nodes are basic ellipses (QGraphicsEllipseItems). It works reasonably well.
However I would like to know how to size the ellipses. Currently I have a hard-coded radius to 80 units this works fine when there are the number of ellipses are few hundred, however when I have a few thousand ellipses it looks all wrong as they are too small for the scale of the scene.
Conversely when there are only a few 10s the scene being smaller the ellipses are way to large.
I am looking to find a formula that better balances the size of an ellipse, with the number of ellipses on the scene and the scale of the scene.
Also as I zoom in and out I would like the ellipses to remain appropriately sized.
Can anyone advise on how to best achieve a balanced arrangement?
The scene has a certain bounding rectangle that encloses all graphics items you put into it while the view has a certain size on the screen.
Between the scene and the view there is a transformation matrix (2x3 for scaling, rotate, shear and translation). You can get it by QGraphicsView.transform().
Now if you put more ellipses into your plot increasing the scene size but want to still see all of them, you must zoom out and accordingly the widths of the ellipses will shrink too.
You don't want that. Okay, so why not resizing them (according to the current scaling factor) everytime the scale changes. Well, this is probably not very efficient.
The better solution is to not change the scale of the view, but just scale the positions manually while keeping the zoom fixed. That way no properties of the items except their position has to be changed.
Example (using PySide and Python 3 but easily adjustable to PyQt and Python 2):
from PySide import QtGui, QtCore
import random
class MyGraphicsView(QtGui.QGraphicsView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def wheelEvent(self, event):
if event.delta() > 0:
scaling = 1.1
else:
scaling = 1 / 1.1
# reposition all items in scene
for item in self.scene().items():
r = item.rect()
item.setRect(QtCore.QRectF(r.x() * scaling, r.y() * scaling, r.width(), r.height()))
app = QtGui.QApplication([])
scene = QtGui.QGraphicsScene()
scene.setSceneRect(-200, -200, 400, 400)
for i in range(100):
rect = QtCore.QRectF(random.uniform(-180, 180), random.uniform(-180, 180), 10, 10)
scene.addEllipse(rect)
view = MyGraphicsView(scene)
view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
view.resize(400, 400)
view.show()
app.exec_()
With the mousewheel you can scale the positions and then it looks like this:
As for the balance of size and number of ellipses - well it's all your choice. There is no generic rule. I recommend to not make the ellipses larger than the distance between the ellipses or they will overlap. In general I work with scene coordinates that are 1:1 with pixel sizes of the view (as I did in the example above, 400 pixels width of the view, 400 units width of the scene rectangle). Then I can easily imagine what a size of an ellipse of 10 will be, that is 10 pixels. If I want more I use more and if I want less I use less. But there is no rule for it, it's up to what you want.

How does one draw a tilted ellipse in ImageDraw?

I am trying to draw a tilted ellipse in image draw. However, I am not sure how to define it, since while the scheme below would move the points, I think this would just squish the ellipse, not rotate it (also I think there is something slightly wrong with the transformation in any case). I am feeding the output of this function into the ellipse command and adding it to an existing picture, so any methods that would rotate the entire image are no good. OD is just a square offset to the coordinate center I am using.
def ellipsebound(major, minor, tilt=0, offset=0, angle=0):
#creates a bound for an ellispe, defined with tilt meaning to rotate the orthogonal axis and angle corresponds to rotating the ellipse position
angle = radians(angle)
tilt = radians(tilt)
box=(
1 + int(ceil((OD+offset*cos(angle)+(major*cos(tilt)+minor*sin(tilt)))/conv)),
1 + int(ceil((OD+offset*sin(angle)+(major*sin(tilt)-minor*cos(tilt)))/conv)),
int(ceil((2*OD-(OD-offset*cos(angle)-(major*cos(tilt)+minor*sin(tilt)))/conv))),
int(ceil((2*OD-(OD-offset*sin(angle)-(major*sin(tilt)-minor*cos(tilt)))/conv)))
) #create bounding box
return box
Does anyone know how to accomplish this?
It looks like the 'box' that is being used to draw the ellipse has no rotation associated with it. It is simply defined by the (left, top, right, bottom) extents.
One possible workaround (depending on what you need to do) is to draw the ellipse (sized correctly, but without the rotation) onto an intermediary image, use the image.rotate() method, and then paste it into your target image.
I hope that helps.

Negative coordinates in pygame

What would be the best way to use negative coordinates in pygame?
At the moment I have a surface that is 1.5 times the original surface then everything that needs to be drawn is shifted up by a certain amount (to ensure the negative coordinates become positive) and drawn.
Is there an easier/alternate way of doing this?
A simple solution is to write a linear mapping function from world coordinates to pygame screen coordinates
def coord(x,y):
"Convert world coordinates to pixel coordinates."
return 320+170*x, 400-170*y
and use it when drawing all world objects. Have a look here for a complete example.
There is no way to move the origin of a surface from 0,0.
Implement your own drawing class which transforms all the coordinates passed in into the space of the surface.
If it's similar to an RPG map situation, where you have world coordinates and screen coordinates:
use a function that translates world to local, and vice versa.
But I wasn't sure I'd you were looking for Rect's properties?
rect.bottomright = (width, height) # bottom right at window corner
If you want to use center coordinates to blit, vs top left being (0,0)
ship.rect.center = (20, 30) # don't need to translate by adding w/2 to topleft
See also: Rect.move_ip()

Categories