def __init__(self): QMainWindow.__init__(self) view = MapGraphicsView(tileSource=MapTileSourceHere()) self.setCentralWidget(view) view.scene().setCenter(31.789339, 41.450508) view.setOptimizationFlag(QGraphicsView.DontSavePainterState, True) view.setRenderHint(QPainter.Antialiasing, True) view.setRenderHint(QPainter.SmoothPixmapTransform, True) pointItem = view.scene().addCircle(31.789339, 44.860767, 3.0) pointItem.setBrush(Qt.black) pointItem.setToolTip('31.789339, 41.450508') pointItem.setFlag(QGraphicsItem.ItemIsSelectable, True) pointItem = view.scene().addCircle(31.789339, 44.860767, 5.0) pointItem.setBrush(Qt.green) pointItem.setPen(QPen(Qt.NoPen)) pointItem.setToolTip('%f, %f' % (31.789339, 44.860767)) pointItem.setFlag(QGraphicsItem.ItemIsSelectable, True) lineItem = view.scene().addLine(32.859741, 39.933365, 31.789339, 41.450508) lineItem.setPen(QPen(QBrush(Qt.blue), 3.0)) polylineItem = view.scene().addPolyline('41.450508', '31.789339') polylineItem.setPen(QPen(QBrush(Qt.red), 3.0)) pix = QPixmap(24, 24) pix.fill(Qt.red) pixmapItem = view.scene().addPixmap(31.789339, 44.860767, pix) pixmapItem.setOffset(-12, -12)
def set_color(self, color): if color != self._color: self._color = color self.colorChanged.emit(self._color) pixmap = QPixmap(self.iconSize()) pixmap.fill(color) self.setIcon(QIcon(pixmap))
def set_color(self, color): if color != self._color: self._color = color self.colorChanged.emit(self._color) pixmap = QPixmap(self.iconSize()) pixmap.fill(color) self.setIcon(QIcon(pixmap))
def makeForegroundIcon(name, foregroundColour, ext="svg", returnType="icon"): """ Open {name}.{ext} icon and change the colour to `foregroundColour`. Parameters ---------- name : str Name of icon file (without extension) foregroundColour : str or QColor Colour to make the icon ext : str Icon file extension. Default is "svg" returnType : {"icon", "pixmap"} Whether to return a QIcon or a QPixmap. Default is "icon" """ if returnType not in ['icon', 'pixmap']: raise ValueError(f"Invalid icon return type {returnType}") if isinstance(foregroundColour, str): foregroundColour = QColor(foregroundColour) file = getIconPath(name, ext=ext) px0 = QPixmap(file) px1 = QPixmap(px0.size()) px1.fill(foregroundColour) px1.setMask(px0.createMaskFromColor(Qt.transparent)) if returnType == 'icon': return QIcon(px1) else: return px1
def findAscent(self, font): dummy = "E" white = QColor(Qt.white) fm = self.fontmetrics(font) pm = QPixmap(fm.width(dummy), fm.height()) pm.fill(white) p = QPainter(pm) p.setFont(font) p.drawText(0, 0, pm.width(), pm.height(), 0, dummy) p.end() img = pm.toImage() w = pm.width() linebytes = w * 4 for row in range(img.height()): if PYSIDE2: line = bytes(img.scanLine(row)) else: line = img.scanLine(row).asstring(linebytes) for col in range(w): color = struct.unpack("I", line[col * 4:(col + 1) * 4])[0] if color != white.rgb(): return fm.ascent() - row + 1 return fm.ascent()
def filename(self, new_file): """ The filename of the image to be displayed. This file can be either relative to the ``.ui`` file or absolute. If the path does not exist, a shape of ``.null_color`` will be displayed instead. Parameters ------- new_file : str The filename to be used """ # Expand user (~ or ~user) and environment variables. pixmap = None self._file = new_file abs_path = os.path.expanduser(os.path.expandvars(self._file)) # Find the absolute path relative to UI if not os.path.isabs(abs_path): try: # Based on the QApplication if is_pydm_app(): abs_path = QApplication.instance().get_path(abs_path) # Based on the QtDesigner elif is_qt_designer(): # pragma: no cover p = self.get_designer_window() if p is not None: ui_dir = p.absoluteDir().absolutePath() abs_path = os.path.join(ui_dir, abs_path) except Exception: logger.exception("Unable to find full filepath for %s", self._file) # Check that the path exists if os.path.isfile(abs_path): if self._movie is not None: self._movie.stop() self._movie.deleteLater() self._movie = None if not abs_path.endswith(".gif"): pixmap = QPixmap(abs_path) else: self._movie = QMovie(abs_path, parent=self) self._movie.setCacheMode(QMovie.CacheAll) self._movie.frameChanged.connect(self.movie_frame_changed) if self._movie.frameCount() > 1: self._movie.finished.connect(self.movie_finished) self._movie.start() # Return a blank image if we don't have a valid path else: # Warn the user loudly if their file does not exist, but avoid # doing this in Designer as this spams the user as they are typing if not is_qt_designer(): # pragma: no cover logger.error("Image file %r does not exist", abs_path) pixmap = QPixmap(self.sizeHint()) pixmap.fill(self.null_color) # Update the display if pixmap is not None: self._pixmap = pixmap self.update()
def update(self, value): """Reimplement LineEditWidget method""" LineEditWidget.update(self, value) color = text_to_qcolor(value) if color.isValid(): bitmap = QPixmap(16, 16) bitmap.fill(color) icon = QIcon(bitmap) else: icon = get_icon("not_found") self.button.setIcon(icon)
def square_pixmap(size): """Create a white/black hollow square pixmap. For use as labels cursor.""" pixmap = QPixmap(QSize(size, size)) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) painter.setPen(Qt.white) painter.drawRect(0, 0, size - 1, size - 1) painter.setPen(Qt.black) painter.drawRect(1, 1, size - 3, size - 3) painter.end() return pixmap
def _to_qimage(self): t = self._to_qgraphicstextitem() rect = t.boundingRect() height = rect.height() width = rect.width() pixmap = QPixmap(width, height) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) t.paint(painter, QStyleOptionGraphicsItem(), None) painter.end() return pixmap.toImage()
def circle_pixmap(size: int): """Create a white/black hollow circle pixmap. For use as labels cursor.""" size = max(int(size), 1) pixmap = QPixmap(QSize(size, size)) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) painter.setPen(Qt.white) painter.drawEllipse(0, 0, size - 1, size - 1) painter.setPen(Qt.black) painter.drawEllipse(1, 1, size - 3, size - 3) painter.end() return pixmap
def update_layer_item(self, plot=None, *args, **kwargs): if plot is None: return layer = plot._layer pixmap = QPixmap(10, 10) pixmap.fill(plot.pen.color()) icon = QIcon(pixmap) layer_item = self.get_layer_item(layer) if layer_item is not None: layer_item.setIcon(0, icon) layer_item.setCheckState(0, Qt.Checked if plot.checked else Qt.Unchecked)
def drawColorBar(self, painter, colorMap, interval, scaleMap, orientation, rect): """ Draw a color bar into a rectangle :param QPainter painter: Painter :param qwt.color_map.QwtColorMap colorMap: Color map :param qwt.interval.QwtInterval interval: Value range :param qwt.scalemap.QwtScaleMap scaleMap: Scale map :param Qt.Orientation orientation: Orientation :param QRectF rect: Target rectangle """ colorTable = [] if colorMap.format() == QwtColorMap.Indexed: colorTable = colorMap.colorTable(interval) c = QColor() devRect = rect.toAlignedRect() pixmap = QPixmap(devRect.size()) pixmap.fill(Qt.transparent) pmPainter = QPainter(pixmap) pmPainter.translate(-devRect.x(), -devRect.y()) if orientation == Qt.Horizontal: sMap = QwtScaleMap(scaleMap) sMap.setPaintInterval(rect.left(), rect.right()) for x in range(devRect.left(), devRect.right() + 1): value = sMap.invTransform(x) if colorMap.format() == QwtColorMap.RGB: c.setRgba(colorMap.rgb(interval, value)) else: c = colorTable[colorMap.colorIndex(interval, value)] pmPainter.setPen(c) pmPainter.drawLine( QLineF(x, devRect.top(), x, devRect.bottom())) else: sMap = QwtScaleMap(scaleMap) sMap.setPaintInterval(rect.bottom(), rect.top()) for y in range(devRect.top(), devRect.bottom() + 1): value = sMap.invTransform(y) if colorMap.format() == QwtColorMap.RGB: c.setRgba(colorMap.rgb(interval, value)) else: c = colorTable[colorMap.colorIndex(interval, value)] pmPainter.setPen(c) pmPainter.drawLine( QLineF(devRect.left(), y, devRect.right(), y)) pmPainter.end() self.drawPixmap(painter, rect, pixmap)
def toPixmap(self, *args): """ Convert the graphic to a `QPixmap` All pixels of the pixmap get initialized by `Qt.transparent` before the graphic is scaled and rendered on it. The size of the pixmap is the default size ( ceiled to integers ) of the graphic. :return: The graphic as pixmap in default size .. seealso:: :py:meth:`defaultSize()`, :py:meth:`toImage()`, :py:meth:`render()` """ if len(args) == 0: if self.isNull(): return QPixmap() sz = self.defaultSize() w = np.ceil(sz.width()) h = np.ceil(sz.height()) pixmap = QPixmap(w, h) pixmap.fill(Qt.transparent) r = QRectF(0.0, 0.0, sz.width(), sz.height()) painter = QPainter(pixmap) self.render(painter, r, Qt.KeepAspectRatio) painter.end() return pixmap elif len(args) in (1, 2): size = args[0] aspectRatioMode = Qt.IgnoreAspectRatio if len(args) == 2: aspectRatioMode = args[-1] pixmap = QPixmap(size) pixmap.fill(Qt.transparent) r = QRect(0, 0, size.width(), size.height()) painter = QPainter(pixmap) self.render(painter, r, aspectRatioMode) painter.end() return pixmap
def drag_with_pixmap(list_widget: QListWidget) -> QDrag: """Create a QDrag object with a pixmap of the currently select list item. This method is useful when you have a QListWidget that displays custom widgets for each QListWidgetItem instance in the list (usually by calling ``QListWidget.setItemWidget(item, widget)``). When used in a ``QListWidget.startDrag`` method, this function creates a QDrag object that shows an image of the item being dragged (rather than an empty rectangle). Parameters ---------- list_widget : QListWidget The QListWidget for which to create a QDrag object. Returns ------- QDrag A QDrag instance with a pixmap of the currently selected item. Examples -------- >>> class QListWidget: ... def startDrag(self, supportedActions): ... drag = drag_with_pixmap(self) ... drag.exec_(supportedActions, Qt.MoveAction) """ drag = QDrag(list_widget) drag.setMimeData(list_widget.mimeData(list_widget.selectedItems())) size = list_widget.viewport().visibleRegion().boundingRect().size() pixmap = QPixmap(size) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) for index in list_widget.selectedIndexes(): rect = list_widget.visualRect(index) painter.drawPixmap(rect, list_widget.viewport().grab(rect)) painter.end() drag.setPixmap(pixmap) drag.setHotSpot(list_widget.viewport().mapFromGlobal(QCursor.pos())) return drag
def create_transparent_pixmap(source: QPixmap, opacity: float) -> QPixmap: ''' Creates a semi transparent pixmap from the given pixmap Source. The Opacity parameter defines the opacity from completely transparent (0.0) to completely opaque (1.0) Parameters ---------- source : QPixmap opacity : qreal Returns ------- value : QPixmap ''' transparent_pixmap = QPixmap(source.size()) transparent_pixmap.fill(Qt.transparent) painter = QPainter(transparent_pixmap) painter.setOpacity(opacity) painter.drawPixmap(0, 0, source) return transparent_pixmap
def data(self, index, role): if role in (Qt.DisplayRole, Qt.EditRole): try: return self.item_list[index.row()][0] except IndexError: return None elif role == Qt.DecorationRole: # preflight if not self.display_item_colors: return # get display color try: color = QColor(*self.item_list[index.row()][1]) except IndexError: color = QColor(*iColor["rgba_gray_0"]) # create pixmap/icon pixmap = QPixmap( getFontSize(QApplication) * 2, getFontSize(QApplication) * 0.5) pixmap.fill(color) icon = QIcon(pixmap) return icon
def pixmap(self, size, mode, state): pm = QPixmap(size) pm.fill(Qt.transparent) self.paint(QPainter(pm), QRect(QPoint(0, 0), size), mode, state) return pm
def crosshair_pixmap(): """Create a cross cursor with white/black hollow square pixmap in the middle. For use as points cursor.""" size = 25 pixmap = QPixmap(QSize(size, size)) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) # Base measures width = 1 center = 3 # Must be odd! rect_size = center + 2 * width square = rect_size + width * 4 pen = QPen(Qt.white, 1) pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) painter.setPen(pen) # # Horizontal rectangle painter.drawRect(0, (size - rect_size) // 2, size - 1, rect_size - 1) # Vertical rectangle painter.drawRect((size - rect_size) // 2, 0, rect_size - 1, size - 1) # Square painter.drawRect( (size - square) // 2, (size - square) // 2, square - 1, square - 1 ) pen = QPen(Qt.black, 2) pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) painter.setPen(pen) # # Square painter.drawRect( (size - square) // 2 + 2, (size - square) // 2 + 2, square - 4, square - 4, ) pen = QPen(Qt.black, 3) pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) painter.setPen(pen) # # # Horizontal lines mid_vpoint = QPoint(2, size // 2) painter.drawLine( mid_vpoint, QPoint(((size - center) // 2) - center + 1, size // 2) ) mid_vpoint = QPoint(size - 3, size // 2) painter.drawLine( mid_vpoint, QPoint(((size - center) // 2) + center + 1, size // 2) ) # # # Vertical lines mid_hpoint = QPoint(size // 2, 2) painter.drawLine( QPoint(size // 2, ((size - center) // 2) - center + 1), mid_hpoint ) mid_hpoint = QPoint(size // 2, size - 3) painter.drawLine( QPoint(size // 2, ((size - center) // 2) + center + 1), mid_hpoint ) painter.end() return pixmap
class MapGraphicsScene(QGraphicsScene): """Graphics scene for showing a slippy map. """ sigZoomChanged = Signal(int) def __init__(self, tileSource, parent=None): """Constructor. Args: tileSource(MapTileSource): Source for loading the tiles. parent(QObject): Parent object, default `None` """ QGraphicsScene.__init__(self, parent=parent) self._zoom = 15 self._tileSource = tileSource self._tileSource.setParent(self) self._tileSource.tileReceived.connect(self.setTilePixmap) tdim = self._tileSource.tileSize() self._emptyTile = QPixmap(tdim, tdim) self._emptyTile.fill(Qt.lightGray) self._tilesRect = QRect() self._tilePixmaps = {} self._tileInDownload = list() self.setSceneRect(0.0, 0.0, 400, 300) self.sceneRectChanged.connect(self.onSceneRectChanged) @Slot() def close(self): self._tileSource.close() def setTileSource(self, newTileSource): self._tileSource.tileReceived.disconnect(self.setTilePixmap) self._tileSource.close() self._tilePixmaps.clear() self._tileInDownload = list() self._tileSource = newTileSource self._tileSource.setParent(self) self._tileSource.tileReceived.connect(self.setTilePixmap) self.requestTiles() self.invalidate() self.update() @Slot(QRectF) def onSceneRectChanged(self, rect): """Callback for the changing of the visible rect. Evaluate the visible tiles and request to load the new tiles. Args: rect(QRectF): Current visible area. """ tdim = self._tileSource.tileSize() center = rect.center() ct = self.tileFromPos(center.x(), center.y()) tx = ct.x() ty = ct.y() width = rect.width() height = rect.height() # top left corner of the center tile xp = int(width / 2.0 - (tx - floor(tx)) * tdim) yp = int(height / 2.0 - (ty - floor(ty)) * tdim) # first tile vertical and horizontal xs = tx - (xp + tdim - 1) / tdim ys = ty - (yp + tdim - 1) / tdim # last tile vertical and horizontal xe = (width - xp - 1) / tdim - xs + 1 + tx ye = (height - yp - 1) / tdim - ys + 1 + ty # define the rect of visible tiles self._tilesRect = QRect(xs, ys, xe, ye) # Request the loading of new tiles (if needed) self.requestTiles() self.invalidate() self.update() def drawBackground(self, painter, rect): """Draw the background tiles. If a tile is not available, draw a gray rectangle. Args: painter(QPainter): Painter for drawing. rect(QRectF): Current visible area. """ tilesRect = self._tilesRect left = tilesRect.left() top = tilesRect.top() numXtiles = tilesRect.width() numYtiles = tilesRect.height() tdim = self._tileSource.tileSize() pixRect = QRectF(0.0, 0.0, tdim, tdim) emptyTilePix = self._emptyTile tilePixmaps = self._tilePixmaps for x in iterRange(numXtiles + 1): for y in iterRange(numYtiles + 1): tp = (x + left, y + top) box = self.tileRect(tp[0], tp[1]) # Use default gray image if tile image is missing pix = tilePixmaps.get(tp, emptyTilePix) painter.drawPixmap(box, pix, pixRect) def zoomTo(self, pos, zoomlevel): """Zoom to a specific zoom level. If the level is out of range, the zoom action is ignored. clear the current tile cache, evaluate the new center and update the position of all the items. Args: zoomlevel(int): New zoom level. """ tileSource = self._tileSource if zoomlevel > tileSource.maxZoom() or zoomlevel < tileSource.minZoom( ): return # Get the coordinates of the center using the position in pixels of the mouse pos_corr = self.views()[0].mapToScene(pos) coord = self.lonLatFromPos(pos_corr.x(), pos_corr.y()) # Set the new zoom level self._zoom = zoomlevel # Clear cache and abort active requests self._tileSource.abortAllRequests() self._tilePixmaps.clear() # Re-center map so that the point on which it was zoomed is in the same position self.setCenter(coord[0], coord[1]) pos_corr = self.views()[0].mapToScene(pos) center = self.sceneRect().center() self.translate(center.x() - pos_corr.x(), center.y() - pos_corr.y()) self.sigZoomChanged.emit(zoomlevel) def zoomIn(self, pos=None): """Increments the zoom level Args: pos (QPointF): Center position, Latitude and Longitude. Default None for the current center position. """ if pos is None: pos = self.sceneRect().center() self.zoomTo(pos, self._zoom + 1) def zoomOut(self, pos=None): """Decrements the zoom level Args: pos (QPointF): Center position, Latitude and Longitude. Default None for the current center position. """ if pos is None: pos = self.sceneRect().center() self.zoomTo(pos, self._zoom - 1) def zoom(self): return self._zoom @Slot(int, int, int, QPixmap) def setTilePixmap(self, x, y, zoom, pixmap): """Set the image of the tile. Args: x(int): X coordinate of the tile. y(int): Y coordinate of the tile. zoom(int): Zoom coordinate of the tile. pixmap(QPixmap): Image for the tile. """ if self._zoom == zoom: self._tilePixmaps[(x, y)] = pixmap self.update() def requestTiles(self): """Request the loading of tiles. Check the loaded tiles and requests only the missing tiles. """ tilesRect = self._tilesRect tilePixmaps = self._tilePixmaps numXtiles = tilesRect.width() numYtiles = tilesRect.height() left = tilesRect.left() top = tilesRect.top() tileSource = self._tileSource zoom = self._zoom # Request load of new tiles for x in iterRange(numXtiles): for y in iterRange(numYtiles): tp = (left + x, top + y) # Request tile only if missing if tp not in tilePixmaps: pix = tileSource.requestTile(tp[0], tp[1], zoom) if pix is not None: tilePixmaps[tp] = pix self.update() def tileRect(self, tx, ty): """Area for a specific tile. Args: tx(int): X coordinate of the tile. ty(int): Y coordinate of the tile. Returns: QRectF, the area of the tile. """ tdim = self._tileSource.tileSize() return QRectF(tx * tdim, ty * tdim, tdim, tdim) def setSize(self, width, height): """Set the size of the visible area in pixels. Update the scene rect. Args: width(int): Width of the visible area. height(int): Height of the visible area. """ rect = QRectF(self.sceneRect().topLeft(), QSizeF(width, height)) self.setSceneRect(rect) def setCenter(self, lon, lat): """Move the center of the visible area to new coordinates. Update the scene rect. Args: lon(float): New longitude of the center. lat(float): New latitude of the center. """ rect = QRectF(self.sceneRect()) pos = self.posFromLonLat(lon, lat) rect.moveCenter(QPointF(pos[0], pos[1])) self.setSceneRect(rect) def center(self): centerPos = self.sceneRect().center() centerCoord = self.lonLatFromPos(centerPos.x(), centerPos.y()) return QPointF(centerCoord[0], centerCoord[1]) def translate(self, dx, dy): """Translate the visible area by dx, dy pixels. Update the scene rect. Args: dx(int): Increments for the center x coord in pixels. dy(int): Increments for the center y coord in pixels. """ self.setSceneRect(self.sceneRect().translated(dx, dy)) def posFromLonLat(self, lon, lat): """Position in scene coordinate of the WGS84 coordinates. Convert from WGS84 reference system to scene reference system. Args: lon(float or numpy.ndarray): Longitude value or values. lat(float or numpy.ndarray): Latitude value or values. Returns: tuple: (x, y) with the positions of the input coordinates. """ return posFromLonLat(lon, lat, self._zoom, self._tileSource.tileSize()) def lonLatFromPos(self, x, y): """Position in WGS84 coordinate of the scene coordinates. Convert from scene reference system to WGS84 reference system. Args: x(float, int or numpy.ndarray): X value or values. y(float, int or numpy.ndarray): Y value or values. Returns: tuple: (lon, lat) with the coordinates of the input positions. """ return lonLatFromPos(x, y, self._zoom, self._tileSource.tileSize()) def tileFromPos(self, x, y): """Tile in the selected position. Args: x(float, int): X value for position. y(float, int): Y value for position. Returns: QPointF with the coordinates of the tile. """ tdim = float(self._tileSource.tileSize()) return QPointF(x / tdim, y / tdim) def addCircle(self, longitude, latitude, radius): """Add a new circle to the graphics scene. Args: longitude(float): Longitude of the center of the circle. latitude(float): Latitude of the center of the circle. radius(float): Longitude of the center of the circle. Returns: MapGraphicsCircleItem added to the scene. """ item = MapGraphicsCircleItem(longitude, latitude, radius) self.addItem(item) return item def addLine(self, lon0, lat0, lon1, lat1): """Add a newline) to the graphics scene. Args: lon0(float): Longitude of the start point. lat0(float): Latitude of the start point. lon1(float): Longitude of the end point. lat1(float): Latitude of the end point. Returns: MapGraphicsLineItem added to the scene. """ item = MapGraphicsLineItem(lon0, lat0, lon1, lat1) self.addItem(item) return item def addRect(self, lon0, lat0, lon1, lat1): """Add a newline) to the graphics scene. Args: lon0(float): Longitude of the top left point. lat0(float): Latitude of the top left point. lon1(float): Longitude of the bottom right point. lat1(float): Latitude of the bottom right point. Returns: MapGraphicsLineItem added to the scene. """ item = MapGraphicsRectItem(lon0, lat0, lon1, lat1) self.addItem(item) return item def addPolyline(self, longitudes, latitudes): """Add a new circle (point) to the graphics scene. Args: longitudes(iterable): Longitudes of all the points of the polyline. latitudes(iterable): Latitudes of all the points of the polyline. Returns: MapGraphicsPolylineItem added to the scene. """ item = MapGraphicsPolylineItem(longitudes, latitudes) self.addItem(item) return item def addPixmap(self, longitude, latitude, pixmap): """Add a new circle (point) to the graphics scene. Args: longitude(float): Longitude of the origin of the pixmap. latitude(float): Latitude of the center of the pixmap. pixmap(QPixmap): Pixmap. Returns: MapGraphicsPixmapItem added to the scene. Note: Use `MapGraphicsPixmapItem.setOffset(off)` to translate by `off` pixels the pixmap respect the origin coordinates. """ item = MapGraphicsPixmapItem(longitude, latitude, pixmap) self.addItem(item) return item def addText(self, longitude, latitude, text): """Add a test item to the graphics scene. Args: longitude(float): Longitude of the origin of the text latitude(float): Latitude of the origin of the text Returns: MapGraphicsTextItem added to the scene. """ item = MapGraphicsTextItem(longitude, latitude, text) self.addItem(item) return item def addLegend(self, pos=QPointF(10.0, 10.0)): legend = MapLegendItem(pos=pos) self.addItem(legend) return legend def addScale(self, **kwargs): """Add a scale bar with text on the right bottom of the map Keyword Args: textPen: QPen to use for drawing the text. Default 'black'. barBrush: QBrush to use for drawing the scale bar. Default (190, 190, 190, 160) barPen: QPen to use for drawing the scale bar border. Default (190, 190, 190, 240) barBrushHover: QBrush to use for drawing the scale bar when the mouse is over it. Default (110, 110, 110, 255). barPenHover: QPen to use for drawing the scale bar borderwhen the mouse is over it. Default (90, 90, 90, 255). Note: Almost all the argumnets accepted by the functions.makeBrush() and functions.makePen() are accepted. """ scaleItem = MapScaleItem(**kwargs) self.addItem(scaleItem) return scaleItem def addLinesGroup(self, longitudes, latitudes): item = MapGraphicsLinesGroupItem(longitudes, latitudes) self.addItem(item) return item
def __init__(self): QMainWindow.__init__(self) view = MapGraphicsView(tileSource=MapTileSourceHere()) self.setCentralWidget(view) view.scene().setCenter(10.065990, 44.861041) view.setOptimizationFlag(QGraphicsView.DontSavePainterState, True) view.setRenderHint(QPainter.Antialiasing, True) view.setRenderHint(QPainter.SmoothPixmapTransform, True) pointItem = view.scene().addCircle(10.068640, 44.860767, 3.0) pointItem.setBrush(Qt.black) pointItem.setToolTip('10.068640, 44.860767') pointItem.setFlag(QGraphicsItem.ItemIsSelectable, True) lats = list() lons = list() for p in POINTS: pointItem = view.scene().addCircle(p[1], p[0], 5.0) pointItem.setBrush(Qt.green) pointItem.setPen(QPen(Qt.NoPen)) pointItem.setToolTip('%f, %f' % (p[1], p[0])) pointItem.setFlag(QGraphicsItem.ItemIsSelectable, True) lons.append(p[1]) lats.append(p[0]) lineItem = view.scene().addLine(10.191037, 44.832810, 10.201736, 44.837632) lineItem.setPen(QPen(QBrush(Qt.blue), 3.0)) polylineItem = view.scene().addPolyline(lons, lats) polylineItem.setPen(QPen(QBrush(Qt.red), 3.0)) pix = QPixmap(24, 24) pix.fill(Qt.red) pixmapItem = view.scene().addPixmap(10.090598, 44.857893, pix) pixmapItem.setOffset(-12, -12) pointItemPixmapOrigin = view.scene().addCircle(10.090598, 44.857893, 3.0) pointItemPixmapOrigin.setBrush(Qt.black) pointItemWithChild = view.scene().addCircle(10.083103, 44.858014, 3.0) pointItemWithChild.setBrush(Qt.blue) pointItemWithChild.setPen(QPen(Qt.NoPen)) textItem = QGraphicsSimpleTextItem('Annotation\nfor blue point', parent=pointItemWithChild) textItem.setBrush(QBrush(QColor(Qt.blue))) textItem.setPos(-5, 3) lats_2 = list() lons_2 = list() for p in POINTS_2: lons_2.append(p[1]) lats_2.append(p[0]) linesGroupItem = view.scene().addLinesGroup(lons_2, lats_2) linesGroupItem.setLineStyle(POINTS_2_COLORS, width=POINTS_2_SIZES) legendItem = view.scene().addLegend() legendItem.addPoint('Point 1', '#FF0000', border=None) legendItem.addRect('Rect 2', '#00FF00', border=None) legendItem.addPoint('Circle 3', '#0000FF', border=None) legendItem.addRect('Sphere 4', '#00FFFF', border=None) legendItem.addPoint('Polygon 5', '#FF00FF', border=None) scaleItem = view.scene().addScale(anchor=Qt.BottomRightCorner)
def create_high_dpi_drop_indicator_pixmap( self, size: QSizeF, area: DockWidgetArea, mode: OverlayMode) -> QPixmap: ''' Create high dpi drop indicator pixmap Parameters ---------- size : QSizeF area : DockWidgetArea mode : OverlayMode Returns ------- value : QPixmap ''' border_color = self.icon_color(IconColor.frame_color) background_color = self.icon_color(IconColor.window_background_color) window = self.public.window() # QT version compatibility (TODO necessary for qtpy?) device_pixel_ratio = (window.devicePixelRatioF() if hasattr(window, 'devicePixelRatioF') else window.devicePixelRatio()) pixmap_size = QSizeF(size * device_pixel_ratio) pm = QPixmap(pixmap_size.toSize()) pm.fill(QColor(0, 0, 0, 0)) p = QPainter(pm) pen = p.pen() shadow_rect = QRectF(pm.rect()) base_rect = QRectF() base_rect.setSize(shadow_rect.size() * 0.7) base_rect.moveCenter(shadow_rect.center()) # Fill shadow_color = self.icon_color(IconColor.shadow_color) if shadow_color.alpha() == 255: shadow_color.setAlpha(64) p.fillRect(shadow_rect, shadow_color) # Drop area rect. p.save() area_rect = QRectF() area_line = QLineF() non_area_rect = QRectF() if area == DockWidgetArea.top: area_rect = QRectF(base_rect.x(), base_rect.y(), base_rect.width(), base_rect.height()*.5) non_area_rect = QRectF(base_rect.x(), shadow_rect.height()*.5, base_rect.width(), base_rect.height()*.5) area_line = QLineF(area_rect.bottomLeft(), area_rect.bottomRight()) elif area == DockWidgetArea.right: area_rect = QRectF(shadow_rect.width()*.5, base_rect.y(), base_rect.width()*.5, base_rect.height()) non_area_rect = QRectF(base_rect.x(), base_rect.y(), base_rect.width()*.5, base_rect.height()) area_line = QLineF(area_rect.topLeft(), area_rect.bottomLeft()) elif area == DockWidgetArea.bottom: area_rect = QRectF(base_rect.x(), shadow_rect.height()*.5, base_rect.width(), base_rect.height()*.5) non_area_rect = QRectF(base_rect.x(), base_rect.y(), base_rect.width(), base_rect.height()*.5) area_line = QLineF(area_rect.topLeft(), area_rect.topRight()) elif area == DockWidgetArea.left: area_rect = QRectF(base_rect.x(), base_rect.y(), base_rect.width()*.5, base_rect.height()) non_area_rect = QRectF(shadow_rect.width()*.5, base_rect.y(), base_rect.width()*.5, base_rect.height()) area_line = QLineF(area_rect.topRight(), area_rect.bottomRight()) baseSize = base_rect.size() if (OverlayMode.container == mode and area != DockWidgetArea.center): base_rect = area_rect p.fillRect(base_rect, background_color) if area_rect.isValid(): pen = p.pen() pen.setColor(border_color) Color = self.icon_color(IconColor.overlay_color) if Color.alpha() == 255: Color.setAlpha(64) p.setBrush(Color) p.setPen(Qt.NoPen) p.drawRect(area_rect) pen = p.pen() pen.setWidth(1) pen.setColor(border_color) pen.setStyle(Qt.DashLine) p.setPen(pen) p.drawLine(area_line) p.restore() p.save() # Draw outer border pen = p.pen() pen.setColor(border_color) pen.setWidth(1) p.setBrush(Qt.NoBrush) p.setPen(pen) p.drawRect(base_rect) # draw window title bar p.setBrush(border_color) frame_rect = QRectF(base_rect.topLeft(), QSizeF(base_rect.width(), baseSize.height()/10)) p.drawRect(frame_rect) p.restore() # Draw arrow for outer container drop indicators if (OverlayMode.container == mode and area != DockWidgetArea.center): arrow_rect = QRectF() arrow_rect.setSize(baseSize) arrow_rect.setWidth(arrow_rect.width()/4.6) arrow_rect.setHeight(arrow_rect.height()/2) arrow_rect.moveCenter(QPointF(0, 0)) arrow = QPolygonF() arrow.append(arrow_rect.topLeft()) arrow.append(QPointF(arrow_rect.right(), arrow_rect.center().y())) arrow.append(arrow_rect.bottomLeft()) p.setPen(Qt.NoPen) p.setBrush(self.icon_color(IconColor.arrow_color)) p.setRenderHint(QPainter.Antialiasing, True) p.translate(non_area_rect.center().x(), non_area_rect.center().y()) if area == DockWidgetArea.top: p.rotate(-90) elif area == DockWidgetArea.right: ... elif area == DockWidgetArea.bottom: p.rotate(90) elif area == DockWidgetArea.left: p.rotate(180) p.drawPolygon(arrow) pm.setDevicePixelRatio(device_pixel_ratio) return pm
def color_icon(name: str, size: int = 20) -> QIcon: """Get color block as QIcon by name.""" color_block = QPixmap(QSize(size, size)) color_block.fill(color_qt(name)) return QIcon(color_block)
def pixmap(self, size, mode, state): pm = QPixmap(size) pm.fill(Qt.transparent) self.paint(QPainter(pm), QRect(QPoint(0, 0), size), mode, state) return pm
class PyDMDrawingImage(PyDMDrawing): """ Renders an image given by the ``filename`` property. This class inherits from PyDMDrawing. Parameters ---------- parent : QWidget The parent widget for the Label init_channel : str, optional The channel to be used by the widget. Attributes ---------- null_color : Qt.Color QColor to fill the image if the filename is not found. """ null_color = Qt.gray def __init__(self, parent=None, init_channel=None, filename=""): super(PyDMDrawingImage, self).__init__(parent, init_channel) hint = super(PyDMDrawingImage, self).sizeHint() self._pixmap = QPixmap(hint) self._pixmap.fill(self.null_color) self._aspect_ratio_mode = Qt.KeepAspectRatio self._movie = None # Make sure we don't set a non-existant file if filename: self.filename = filename # But we always have an internal value to reference else: self._file = filename if is_qt_designer(): # pragma: no cover designer_window = self.get_designer_window() if designer_window is not None: designer_window.fileNameChanged.connect( self.designer_form_saved) def get_designer_window(self): # pragma: no cover # Internal function to find the designer window that owns this widget. p = self.parent() while p is not None: if isinstance(p, QDesignerFormWindowInterface): return p p = p.parent() return None @Slot(str) def designer_form_saved(self, filename): # pragma: no cover self.filename = self._file @Property(str) def filename(self): """ The filename of the image to be displayed. This can be an absolute or relative path to the display file. Returns ------- str The filename configured. """ return self._file @filename.setter def filename(self, new_file): """ The filename of the image to be displayed. This file can be either relative to the ``.ui`` file or absolute. If the path does not exist, a shape of ``.null_color`` will be displayed instead. Parameters ------- new_file : str The filename to be used """ # Expand user (~ or ~user) and environment variables. pixmap = None self._file = new_file abs_path = os.path.expanduser(os.path.expandvars(self._file)) # Find the absolute path relative to UI if not os.path.isabs(abs_path): try: # Based on the QApplication if is_pydm_app(): abs_path = QApplication.instance().get_path(abs_path) # Based on the QtDesigner elif is_qt_designer(): # pragma: no cover p = self.get_designer_window() if p is not None: ui_dir = p.absoluteDir().absolutePath() abs_path = os.path.join(ui_dir, abs_path) except Exception: logger.exception("Unable to find full filepath for %s", self._file) # Check that the path exists if os.path.isfile(abs_path): if self._movie is not None: self._movie.stop() self._movie.deleteLater() self._movie = None if not abs_path.endswith(".gif"): pixmap = QPixmap(abs_path) else: self._movie = QMovie(abs_path, parent=self) self._movie.setCacheMode(QMovie.CacheAll) self._movie.frameChanged.connect(self.movie_frame_changed) if self._movie.frameCount() > 1: self._movie.finished.connect(self.movie_finished) self._movie.start() # Return a blank image if we don't have a valid path else: # Warn the user loudly if their file does not exist, but avoid # doing this in Designer as this spams the user as they are typing if not is_qt_designer(): # pragma: no cover logger.error("Image file %r does not exist", abs_path) pixmap = QPixmap(self.sizeHint()) pixmap.fill(self.null_color) # Update the display if pixmap is not None: self._pixmap = pixmap self.update() def sizeHint(self): if self._pixmap.size().isEmpty(): return super(PyDMDrawingImage, self).sizeHint() return self._pixmap.size() @Property(Qt.AspectRatioMode) def aspectRatioMode(self): """ PyQT Property for aspect ratio mode to be used when rendering the image Returns ------- int Index at Qt.AspectRatioMode enum """ return self._aspect_ratio_mode @aspectRatioMode.setter def aspectRatioMode(self, new_mode): """ PyQT Property for aspect ratio mode to be used when rendering the image Parameters ---------- new_mode : int Index at Qt.AspectRatioMode enum """ if new_mode != self._aspect_ratio_mode: self._aspect_ratio_mode = new_mode self.update() def draw_item(self, painter): """ Draws the image after setting up the canvas with a call to ```PyDMDrawing.draw_item```. """ super(PyDMDrawingImage, self).draw_item(painter) x, y, w, h = self.get_bounds(maxsize=True, force_no_pen=True) if not isinstance(self._pixmap, QMovie): _scaled = self._pixmap.scaled(w, h, self._aspect_ratio_mode, Qt.SmoothTransformation) # Make sure the image is centered if smaller than the widget itself if w > _scaled.width(): logger.debug("Centering image horizontally ...") x += (w - _scaled.width()) / 2 if h > _scaled.height(): logger.debug("Centering image vertically ...") y += (h - _scaled.height()) / 2 painter.drawPixmap(x, y, _scaled) def movie_frame_changed(self, frame_no): """ Callback executed when a new frame is available at the QMovie. Parameters ---------- frame_no : int The new frame index Returns ------- None """ if self._movie is None: return curr_pixmap = self._movie.currentPixmap() self._pixmap = curr_pixmap self.update() def movie_finished(self): """ Callback executed when the movie is finished. Returns ------- None """ if self._movie is None: return self._movie.start()
def graph2icon(g: Graph, width: int, node_mode: bool, show_label: bool, monochrome: bool, *, except_node: Optional[int] = None, engine: str = "", pos: Optional[_Pos] = None) -> QIcon: """Draw a generalized chain graph.""" if engine: pos = engine_picker(g, engine, node_mode) if pos is None: raise ValueError("no engine selected") if not pos: pixmap = QPixmap(width, width) pixmap.fill(Qt.transparent) return QIcon(pixmap) width_bound = -float('inf') for x, y in pos.values(): if abs(x) > width_bound: width_bound = x if abs(y) > width_bound: width_bound = y width_bound *= 2.5 image = QImage(QSize(int(width_bound), int(width_bound)), QImage.Format_ARGB32_Premultiplied) image.fill(Qt.transparent) painter = QPainter(image) painter.translate(image.width() / 2, image.height() / 2) pen = QPen() r = int(width_bound / 50) pen.setWidth(r) painter.setPen(pen) _font.setPixelSize(r * 6) painter.setFont(_font) # Draw edges if node_mode: for l1, l2 in g.edges: if except_node in {l1, l2}: pen.setColor(Qt.gray) else: pen.setColor(Qt.black) painter.setPen(pen) painter.drawLine(QPointF(pos[l1][0], -pos[l1][1]), QPointF(pos[l2][0], -pos[l2][1])) else: color = color_qt('dark-gray') if monochrome else LINK_COLOR color.setAlpha(150) painter.setBrush(QBrush(color)) for link in g.vertices: if link == except_node: pen.setColor(Qt.gray) else: pen.setColor(Qt.black) painter.setPen(pen) painter.drawPolygon(*convex_hull([(pos[n][0], -pos[n][1]) for n, edge in edges_view(g) if link in edge], as_qpoint=True)) # Draw vertices for k, (x, y) in pos.items(): if node_mode: color = color_num(len(list(g.neighbors(k))) - 1) if k == except_node: color.setAlpha(150) else: if monochrome: color = Qt.black elif except_node in dict(edges_view(g))[k]: color = color_qt('green') else: color = color_qt('blue') pen.setColor(color) painter.setPen(pen) painter.setBrush(QBrush(color)) point = QPointF(x, -y) painter.drawEllipse(point, r, r) if show_label: pen.setColor(Qt.darkMagenta) painter.setPen(pen) painter.drawText(point, str(k)) painter.end() return QIcon(QPixmap.fromImage(image).scaledToWidth(width))
def __init__(self, parent, data=None, readonly=False, bg_value=None, bg_gradient='blue-red', minvalue=None, maxvalue=None, digits=None): QWidget.__init__(self, parent) assert bg_gradient in gradient_map if data is not None and np.isscalar(data): readonly = True self.readonly = readonly # prepare internal views and models self.model_axes = AxesArrayModel(parent=self, readonly=readonly) self.view_axes = AxesView(parent=self, model=self.model_axes) self.model_hlabels = LabelsArrayModel(parent=self, readonly=readonly) self.view_hlabels = LabelsView(parent=self, model=self.model_hlabels, hpos=RIGHT, vpos=TOP) self.model_vlabels = LabelsArrayModel(parent=self, readonly=readonly) self.view_vlabels = LabelsView(parent=self, model=self.model_vlabels, hpos=LEFT, vpos=BOTTOM) self.model_data = DataArrayModel(parent=self, readonly=readonly, minvalue=minvalue, maxvalue=maxvalue) self.view_data = DataView(parent=self, model=self.model_data) # in case data is None self.data_adapter = None # Create vertical and horizontal scrollbars self.vscrollbar = ScrollBar(self, self.view_data.verticalScrollBar()) self.hscrollbar = ScrollBar(self, self.view_data.horizontalScrollBar()) # Synchronize resizing self.view_axes.horizontalHeader().sectionResized.connect( self.view_vlabels.updateSectionWidth) self.view_axes.verticalHeader().sectionResized.connect( self.view_hlabels.updateSectionHeight) self.view_hlabels.horizontalHeader().sectionResized.connect( self.view_data.updateSectionWidth) self.view_vlabels.verticalHeader().sectionResized.connect( self.view_data.updateSectionHeight) # Synchronize auto-resizing self.view_axes.horizontalHeader().sectionHandleDoubleClicked.connect( self.resize_axes_column_to_contents) self.view_hlabels.horizontalHeader( ).sectionHandleDoubleClicked.connect( self.resize_hlabels_column_to_contents) self.view_axes.verticalHeader().sectionHandleDoubleClicked.connect( self.resize_axes_row_to_contents) self.view_vlabels.verticalHeader().sectionHandleDoubleClicked.connect( self.resize_vlabels_row_to_contents) # synchronize specific methods self.view_axes.allSelected.connect(self.view_data.selectAll) self.view_data.signal_copy.connect(self.copy) self.view_data.signal_excel.connect(self.to_excel) self.view_data.signal_paste.connect(self.paste) self.view_data.signal_plot.connect(self.plot) # propagate changes (add new items in the QUndoStack attribute of MappingEditor) self.model_data.newChanges.connect(self.data_changed) # Synchronize scrolling # data <--> hlabels self.view_data.horizontalScrollBar().valueChanged.connect( self.view_hlabels.horizontalScrollBar().setValue) self.view_hlabels.horizontalScrollBar().valueChanged.connect( self.view_data.horizontalScrollBar().setValue) # data <--> vlabels self.view_data.verticalScrollBar().valueChanged.connect( self.view_vlabels.verticalScrollBar().setValue) self.view_vlabels.verticalScrollBar().valueChanged.connect( self.view_data.verticalScrollBar().setValue) # Synchronize selecting columns(rows) via hor.(vert.) header of x(y)labels view self.view_hlabels.horizontalHeader().sectionPressed.connect( self.view_data.selectColumn) self.view_hlabels.horizontalHeader().sectionEntered.connect( self.view_data.selectNewColumn) self.view_vlabels.verticalHeader().sectionPressed.connect( self.view_data.selectRow) self.view_vlabels.verticalHeader().sectionEntered.connect( self.view_data.selectNewRow) # following lines are required to keep usual selection color # when selecting rows/columns via headers of label views. # Otherwise, selected rows/columns appear in grey. self.view_data.setStyleSheet("""QTableView { selection-background-color: palette(highlight); selection-color: white; }""") # set external borders array_frame = QFrame(self) array_frame.setFrameStyle(QFrame.StyledPanel) # remove borders of internal tables self.view_axes.setFrameStyle(QFrame.NoFrame) self.view_hlabels.setFrameStyle(QFrame.NoFrame) self.view_vlabels.setFrameStyle(QFrame.NoFrame) self.view_data.setFrameStyle(QFrame.NoFrame) # Set layout of table views: # [ axes ][hlabels]|V| # [vlabels][ data ]|s| # | H. scrollbar | array_layout = QGridLayout() array_layout.addWidget(self.view_axes, 0, 0) array_layout.addWidget(self.view_hlabels, 0, 1) array_layout.addWidget(self.view_vlabels, 1, 0) self.view_data.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) array_layout.addWidget(self.view_data, 1, 1) array_layout.addWidget(self.vscrollbar, 0, 2, 2, 1) array_layout.addWidget(self.hscrollbar, 2, 0, 1, 2) array_layout.setSpacing(0) array_layout.setContentsMargins(0, 0, 0, 0) array_frame.setLayout(array_layout) # Set filters and buttons layout self.filters_layout = QHBoxLayout() self.btn_layout = QHBoxLayout() self.btn_layout.setAlignment(Qt.AlignLeft) label = QLabel("Digits") self.btn_layout.addWidget(label) spin = QSpinBox(self) spin.valueChanged.connect(self.digits_changed) self.digits_spinbox = spin self.btn_layout.addWidget(spin) self.digits = 0 scientific = QCheckBox(_('Scientific')) scientific.stateChanged.connect(self.scientific_changed) self.scientific_checkbox = scientific self.btn_layout.addWidget(scientific) self.use_scientific = False gradient_chooser = QComboBox() gradient_chooser.setMaximumSize(120, 20) gradient_chooser.setIconSize(QSize(100, 20)) pixmap = QPixmap(100, 15) pixmap.fill(Qt.white) gradient_chooser.addItem(QIcon(pixmap), " ") pixmap.fill(Qt.transparent) painter = QPainter(pixmap) for name, gradient in available_gradients[1:]: qgradient = gradient.as_qgradient() # * fill with white because gradient can be transparent and if we do not "start from whilte", it skews the # colors. # * 1 and 13 instead of 0 and 15 to have a transparent border around/between the gradients painter.fillRect(0, 1, 100, 13, Qt.white) painter.fillRect(0, 1, 100, 13, qgradient) gradient_chooser.addItem(QIcon(pixmap), name, gradient) # without this, we can crash python :) del painter, pixmap # select default gradient # requires Qt5+ # gradient_chooser.setCurrentText(bg_gradient) gradient_chooser.setCurrentIndex( gradient_chooser.findText(bg_gradient)) gradient_chooser.currentIndexChanged.connect(self.gradient_changed) self.btn_layout.addWidget(gradient_chooser) self.gradient_chooser = gradient_chooser # Set widget layout layout = QVBoxLayout() layout.addLayout(self.filters_layout) layout.addWidget(array_frame) layout.addLayout(self.btn_layout) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) # set gradient self.model_data.set_bg_gradient(gradient_map[bg_gradient]) # set data if data is not None: self.set_data(data, bg_value=bg_value, digits=digits) # See http://doc.qt.io/qt-4.8/qt-draganddrop-fridgemagnets-dragwidget-cpp.html for an example self.setAcceptDrops(True)
class MapGraphicsScene(QGraphicsScene): """Graphics scene for showing a slippy map. """ sigZoomChanged = Signal(int) def __init__(self, tileSource, parent=None): """Constructor. Args: tileSource(MapTileSource): Source for loading the tiles. parent(QObject): Parent object, default `None` """ QGraphicsScene.__init__(self, parent=parent) self._zoom = 15 self._tileSource = tileSource self._tileSource.setParent(self) self._tileSource.tileReceived.connect(self.setTilePixmap) tdim = self._tileSource.tileSize() self._emptyTile = QPixmap(tdim, tdim) self._emptyTile.fill(Qt.lightGray) self._tilesRect = QRect() self._tilePixmaps = {} self._tileInDownload = list() self.setSceneRect(0.0, 0.0, 400, 300) self.sceneRectChanged.connect(self.onSceneRectChanged) @Slot() def close(self): self._tileSource.close() def setTileSource(self, newTileSource): self._tileSource.tileReceived.disconnect(self.setTilePixmap) self._tileSource.close() self._tilePixmaps.clear() self._tileInDownload = list() self._tileSource = newTileSource self._tileSource.setParent(self) self._tileSource.tileReceived.connect(self.setTilePixmap) self.requestTiles() self.invalidate() self.update() @Slot(QRectF) def onSceneRectChanged(self, rect): """Callback for the changing of the visible rect. Evaluate the visible tiles and request to load the new tiles. Args: rect(QRectF): Current visible area. """ tdim = self._tileSource.tileSize() center = rect.center() ct = self.tileFromPos(center.x(), center.y()) tx = ct.x() ty = ct.y() width = rect.width() height = rect.height() # top left corner of the center tile xp = int(width / 2.0 - (tx - floor(tx)) * tdim) yp = int(height / 2.0 - (ty - floor(ty)) * tdim) # first tile vertical and horizontal xs = tx - (xp + tdim - 1) / tdim ys = ty - (yp + tdim - 1) / tdim # last tile vertical and horizontal xe = (width - xp - 1) / tdim - xs + 1 + tx ye = (height - yp - 1) / tdim - ys + 1 + ty # define the rect of visible tiles self._tilesRect = QRect(xs, ys, xe, ye) # Request the loading of new tiles (if needed) self.requestTiles() self.invalidate() self.update() def drawBackground(self, painter, rect): """Draw the background tiles. If a tile is not available, draw a gray rectangle. Args: painter(QPainter): Painter for drawing. rect(QRectF): Current visible area. """ tilesRect = self._tilesRect left = tilesRect.left() top = tilesRect.top() numXtiles = tilesRect.width() numYtiles = tilesRect.height() tdim = self._tileSource.tileSize() pixRect = QRectF(0.0, 0.0, tdim, tdim) emptyTilePix = self._emptyTile tilePixmaps = self._tilePixmaps for x in iterRange(numXtiles+1): for y in iterRange(numYtiles+1): tp = (x + left, y + top) box = self.tileRect(tp[0], tp[1]) # Use default gray image if tile image is missing pix = tilePixmaps.get(tp, emptyTilePix) painter.drawPixmap(box, pix, pixRect) def zoomTo(self, pos, zoomlevel): """Zoom to a specific zoom level. If the level is out of range, the zoom action is ignored. clear the current tile cache, evaluate the new center and update the position of all the items. Args: zoomlevel(int): New zoom level. """ tileSource = self._tileSource if zoomlevel > tileSource.maxZoom() or zoomlevel < tileSource.minZoom(): return # Get the coordinates of the center using the position in pixels of the mouse pos_corr = self.views()[0].mapToScene(pos) coord = self.lonLatFromPos(pos_corr.x(), pos_corr.y()) # Set the new zoom level self._zoom = zoomlevel # Clear cache and abort active requests self._tileSource.abortAllRequests() self._tilePixmaps.clear() # Re-center map so that the point on which it was zoomed is in the same position self.setCenter(coord[0], coord[1]) pos_corr = self.views()[0].mapToScene(pos) center = self.sceneRect().center() self.translate(center.x() - pos_corr.x(), center.y() - pos_corr.y()) self.sigZoomChanged.emit(zoomlevel) def zoomIn(self, pos=None): """Increments the zoom level Args: pos (QPointF): Center position, Latitude and Longitude. Default None for the current center position. """ if pos is None: pos = self.sceneRect().center() self.zoomTo(pos, self._zoom + 1) def zoomOut(self, pos=None): """Decrements the zoom level Args: pos (QPointF): Center position, Latitude and Longitude. Default None for the current center position. """ if pos is None: pos = self.sceneRect().center() self.zoomTo(pos, self._zoom - 1) def zoom(self): return self._zoom @Slot(int, int, int, QPixmap) def setTilePixmap(self, x, y, zoom, pixmap): """Set the image of the tile. Args: x(int): X coordinate of the tile. y(int): Y coordinate of the tile. zoom(int): Zoom coordinate of the tile. pixmap(QPixmap): Image for the tile. """ if self._zoom == zoom: self._tilePixmaps[(x, y)] = pixmap self.update() def requestTiles(self): """Request the loading of tiles. Check the loaded tiles and requests only the missing tiles. """ tilesRect = self._tilesRect tilePixmaps = self._tilePixmaps numXtiles = tilesRect.width() numYtiles = tilesRect.height() left = tilesRect.left() top = tilesRect.top() tileSource = self._tileSource zoom = self._zoom # Request load of new tiles for x in iterRange(numXtiles): for y in iterRange(numYtiles): tp = (left + x, top + y) # Request tile only if missing if tp not in tilePixmaps: pix = tileSource.requestTile(tp[0], tp[1], zoom) if pix is not None: tilePixmaps[tp] = pix self.update() def tileRect(self, tx, ty): """Area for a specific tile. Args: tx(int): X coordinate of the tile. ty(int): Y coordinate of the tile. Returns: QRectF, the area of the tile. """ tdim = self._tileSource.tileSize() return QRectF(tx * tdim, ty * tdim, tdim, tdim) def setSize(self, width, height): """Set the size of the visible area in pixels. Update the scene rect. Args: width(int): Width of the visible area. height(int): Height of the visible area. """ rect = QRectF(self.sceneRect().topLeft(), QSizeF(width, height)) self.setSceneRect(rect) def setCenter(self, lon, lat): """Move the center of the visible area to new coordinates. Update the scene rect. Args: lon(float): New longitude of the center. lat(float): New latitude of the center. """ rect = QRectF(self.sceneRect()) pos = self.posFromLonLat(lon, lat) rect.moveCenter(QPointF(pos[0], pos[1])) self.setSceneRect(rect) def center(self): centerPos = self.sceneRect().center() centerCoord = self.lonLatFromPos(centerPos.x(), centerPos.y()) return QPointF(centerCoord[0], centerCoord[1]) def translate(self, dx, dy): """Translate the visible area by dx, dy pixels. Update the scene rect. Args: dx(int): Increments for the center x coord in pixels. dy(int): Increments for the center y coord in pixels. """ self.setSceneRect(self.sceneRect().translated(dx, dy)) def posFromLonLat(self, lon, lat): """Position in scene coordinate of the WGS84 coordinates. Convert from WGS84 reference system to scene reference system. Args: lon(float or numpy.ndarray): Longitude value or values. lat(float or numpy.ndarray): Latitude value or values. Returns: tuple: (x, y) with the positions of the input coordinates. """ return posFromLonLat(lon, lat, self._zoom, self._tileSource.tileSize()) def lonLatFromPos(self, x, y): """Position in WGS84 coordinate of the scene coordinates. Convert from scene reference system to WGS84 reference system. Args: x(float, int or numpy.ndarray): X value or values. y(float, int or numpy.ndarray): Y value or values. Returns: tuple: (lon, lat) with the coordinates of the input positions. """ return lonLatFromPos(x, y, self._zoom, self._tileSource.tileSize()) def tileFromPos(self, x, y): """Tile in the selected position. Args: x(float, int): X value for position. y(float, int): Y value for position. Returns: QPointF with the coordinates of the tile. """ tdim = float(self._tileSource.tileSize()) return QPointF(x / tdim, y / tdim) def addCircle(self, longitude, latitude, radius): """Add a new circle to the graphics scene. Args: longitude(float): Longitude of the center of the circle. latitude(float): Latitude of the center of the circle. radius(float): Longitude of the center of the circle. Returns: MapGraphicsCircleItem added to the scene. """ item = MapGraphicsCircleItem(longitude, latitude, radius) self.addItem(item) return item def addLine(self, lon0, lat0, lon1, lat1): """Add a newline) to the graphics scene. Args: lon0(float): Longitude of the start point. lat0(float): Latitude of the start point. lon1(float): Longitude of the end point. lat1(float): Latitude of the end point. Returns: MapGraphicsLineItem added to the scene. """ item = MapGraphicsLineItem(lon0, lat0, lon1, lat1) self.addItem(item) return item def addRect(self, lon0, lat0, lon1, lat1): """Add a newline) to the graphics scene. Args: lon0(float): Longitude of the top left point. lat0(float): Latitude of the top left point. lon1(float): Longitude of the bottom right point. lat1(float): Latitude of the bottom right point. Returns: MapGraphicsLineItem added to the scene. """ item = MapGraphicsRectItem(lon0, lat0, lon1, lat1) self.addItem(item) return item def addPolyline(self, longitudes, latitudes): """Add a new circle (point) to the graphics scene. Args: longitudes(iterable): Longitudes of all the points of the polyline. latitudes(iterable): Latitudes of all the points of the polyline. Returns: MapGraphicsPolylineItem added to the scene. """ item = MapGraphicsPolylineItem(longitudes, latitudes) self.addItem(item) return item def addPixmap(self, longitude, latitude, pixmap): """Add a new circle (point) to the graphics scene. Args: longitude(float): Longitude of the origin of the pixmap. latitude(float): Latitude of the center of the pixmap. pixmap(QPixmap): Pixmap. Returns: MapGraphicsPixmapItem added to the scene. Note: Use `MapGraphicsPixmapItem.setOffset(off)` to translate by `off` pixels the pixmap respect the origin coordinates. """ item = MapGraphicsPixmapItem(longitude, latitude, pixmap) self.addItem(item) return item def addText(self, longitude, latitude, text): """Add a test item to the graphics scene. Args: longitude(float): Longitude of the origin of the text latitude(float): Latitude of the origin of the text Returns: MapGraphicsTextItem added to the scene. """ item = MapGraphicsTextItem(longitude, latitude, text) self.addItem(item) return item def addLegend(self, pos=QPointF(10.0, 10.0)): legend = MapLegendItem(pos=pos) self.addItem(legend) return legend def addScale(self, **kwargs): """Add a scale bar with text on the right bottom of the map Keyword Args: textPen: QPen to use for drawing the text. Default 'black'. barBrush: QBrush to use for drawing the scale bar. Default (190, 190, 190, 160) barPen: QPen to use for drawing the scale bar border. Default (190, 190, 190, 240) barBrushHover: QBrush to use for drawing the scale bar when the mouse is over it. Default (110, 110, 110, 255). barPenHover: QPen to use for drawing the scale bar borderwhen the mouse is over it. Default (90, 90, 90, 255). Note: Almost all the argumnets accepted by the functions.makeBrush() and functions.makePen() are accepted. """ scaleItem = MapScaleItem(**kwargs) self.addItem(scaleItem) return scaleItem def addLinesGroup(self, longitudes, latitudes): item = MapGraphicsLinesGroupItem(longitudes, latitudes) self.addItem(item) return item
def __init__(self): QMainWindow.__init__(self) view = MapGraphicsView(tileSource=MapTileSourceHere()) self.setCentralWidget(view) view.scene().setCenter(10.065990, 44.861041, zoom=13) view.setOptimizationFlag(QGraphicsView.DontSavePainterState, True) view.setRenderHint(QPainter.Antialiasing, True) view.setRenderHint(QPainter.SmoothPixmapTransform, True) pointItem = view.scene().addCircle(10.068640, 44.860767, 3.0) pointItem.setBrush(Qt.black) pointItem.setToolTip('10.068640, 44.860767') pointItem.setFlag(QGraphicsItem.ItemIsSelectable, True) lats = list() lons = list() for p in POINTS: pointItem = view.scene().addCircle(p[1], p[0], 5.0) pointItem.setBrush(Qt.green) pointItem.setPen(QPen(Qt.NoPen)) pointItem.setToolTip('%f, %f' % (p[1], p[0])) pointItem.setFlag(QGraphicsItem.ItemIsSelectable, True) lons.append(p[1]) lats.append(p[0]) lineItem = view.scene().addLine(10.191037, 44.832810, 10.201736, 44.837632) lineItem.setPen(QPen(QBrush(Qt.blue), 3.0)) polylineItem = view.scene().addPolyline(lons, lats) polylineItem.setPen(QPen(QBrush(Qt.red), 3.0)) pix = QPixmap(24, 24) pix.fill(Qt.red) pixmapItem = view.scene().addPixmap(10.090598, 44.857893, pix) pixmapItem.setOffset(-12, -12) pointItemPixmapOrigin = view.scene().addCircle(10.090598, 44.857893, 3.0) pointItemPixmapOrigin.setBrush(Qt.black) # Pixmap with both corners geo-referenced geo_pixmap = QPixmap(36, 36) geo_pixmap.fill(Qt.blue) geo_pixmap_item = view.scene().addGeoPixmap(10.090598, 44.8709, 10.092, 44.873, geo_pixmap) geo_pixmap_item.setLabel("GeoPixmapItem") geo_pixmap_item.showLabel() # Blue Point with an HTML label blue_point = view.scene().addCircle(10.083103, 44.868014, 3.0) blue_point.setBrush(Qt.blue) blue_point.setPen(QPen(Qt.NoPen)) blue_point.setLabel("<div style='background-color: #ffff00;'>" + "Label for Blue Point" + "</div>", html=True) blue_point.showLabel() # Location Pin pin_item = view.scene().addPin(10.06, 44.84) pin_item.setLabel( "<div style='background-color: #00ff00;'><I>Pin Item</I></div>", html=True) pin_item.showLabel() # Pixmap with all four corners geo-referenced lon0r = 10.06 lat0r = 44.83 lon1r = 10.110000000000001 lat1r = 44.743397459621555 lon2r = 9.936794919243113 lat2r = 44.64339745962155 lon3r = 9.886794919243112 lat3r = 44.73 clr = QColor(0, 255, 0, 100) pix = QPixmap(100, 100) pix.fill(clr) view.scene().addGeoPixmapCorners(lon0r, lat0r, lon1r, lat1r, lon2r, lat2r, lon3r, lat3r, pix) lats_2 = list() lons_2 = list() for p in POINTS_2: lons_2.append(p[1]) lats_2.append(p[0]) linesGroupItem = view.scene().addLinesGroup(lons_2, lats_2) linesGroupItem.setLineStyle(POINTS_2_COLORS, width=POINTS_2_SIZES) legendItem = view.scene().addLegend() legendItem.addPoint('Point 1', '#FF0000', border=None) legendItem.addRect('Rect 2', '#00FF00', border=None) legendItem.addPoint('Circle 3', '#0000FF', border=None) legendItem.addRect('Sphere 4', '#00FFFF', border=None) legendItem.addPoint('Polygon 5', '#FF00FF', border=None) navItem = view.scene().addNavItem(anchor=Qt.TopRightCorner) scaleItem = view.scene().addScale(anchor=Qt.BottomRightCorner)