def rebuild( self, gridRect ): """ Rebuilds the tracker item. """ scene = self.scene() if ( not scene ): return self.setVisible(gridRect.contains(self.pos())) self.setZValue(100) path = QPainterPath() path.moveTo(0, 0) path.lineTo(0, gridRect.height()) tip = '' tip_point = None self._ellipses = [] items = scene.collidingItems(self) self._basePath = QPainterPath(path) for item in items: item_path = item.path() found = None for y in range(int(gridRect.top()), int(gridRect.bottom())): point = QPointF(self.pos().x(), y) if ( item_path.contains(point) ): found = QPointF(0, y - self.pos().y()) break if ( found ): path.addEllipse(found, 6, 6) self._ellipses.append(found) # update the value information value = scene.valueAt(self.mapToScene(found)) tip_point = self.mapToScene(found) hruler = scene.horizontalRuler() vruler = scene.verticalRuler() x_value = hruler.formatValue(value[0]) y_value = vruler.formatValue(value[1]) tip = '<b>x:</b> %s<br/><b>y:</b> %s' % (x_value, y_value) self.setPath(path) self.setVisible(True) # show the popup widget if ( tip ): anchor = XPopupWidget.Anchor.RightCenter widget = self.scene().chartWidget() tip_point = widget.mapToGlobal(widget.mapFromScene(tip_point)) XPopupWidget.showToolTip(tip, anchor = anchor, parent = widget, point = tip_point, foreground = QColor('blue'), background = QColor(148, 148, 255))
def pointAt(self, axes, axis_values): """ Returns the point that best represents this graph information. :param axes | [<XChartAxis>, ..] axis_values | {<str> axisName, <variant> value :return <QPointF> """ point = QPointF() rect = self._buildData.get('axis_rect') if not rect: return point x_range = rect.right() - rect.left() y_range = rect.bottom() - rect.top() for axis in axes: if not axis.name() in axis_values: continue perc = axis.percentAt(axis_values[axis.name()]) if axis.orientation() == Qt.Vertical: point.setY(rect.bottom() - perc * y_range) else: point.setX(rect.left() + perc * x_range) return point
def __init__(self): super(XChartWidgetItem, self).__init__() self.setAcceptHoverEvents(True) # set default information self._chartType = None self._pieCenter = QPointF(0, 0) self._subpaths = [] self._keyColors = {} self._ellipses = [] self._keyToolTips = {} self._showPointsInLine = True self._shaded = True self._dragData = {} self._radius = 6 self._title = '' self._color = self.randomColor() self._alternateColor = self._color.lighter(140) self._points = [] self._barSize = 30 self._orientation = Qt.Horizontal self._pieAxis = Qt.YAxis self._pointRadius = 6 self._horizontalOffset = 0 self._verticalOffset = 0 self._hoveredPath = None self._dirty = False self._buildData = {}
def rebuild(self): """ Rebuilds the dependency path for this item. """ scene = self.scene() if (not scene): return sourcePos = self.sourceItem().viewItem().pos() sourceRect = self.sourceItem().viewItem().rect() targetPos = self.targetItem().viewItem().pos() targetRect = self.targetItem().viewItem().rect() cellWidth = scene.ganttWidget().cellWidth() startX = sourcePos.x() + sourceRect.width() - (cellWidth / 2.0) startY = sourcePos.y() + (sourceRect.height() / 2.0) endX = targetPos.x() - 2 endY = targetPos.y() + (targetRect.height() / 2.0) path = QPainterPath() path.moveTo(startX, startY) path.lineTo(startX, endY) path.lineTo(endX, endY) a = QPointF(endX - 10, endY - 3) b = QPointF(endX, endY) c = QPointF(endX - 10, endY + 3) self._polygon = QPolygonF([a, b, c, a]) path.addPolygon(self._polygon) self.setPath(path)
def mapFromChart(self, x, y): """ Maps a chart point to a pixel position within the grid based on the rulers. :param x | <variant> y | <variant> :return <QPointF> """ grid = self.gridRect() hruler = self.horizontalRuler() vruler = self.verticalRuler() xperc = hruler.percentAt(x) yperc = vruler.percentAt(y) xoffset = grid.width() * xperc yoffset = grid.height() * yperc xpos = grid.left() + xoffset ypos = grid.bottom() - yoffset return QPointF(xpos, ypos)
def rebuild(self): """ Rebuilds the data for this scene to draw with. """ global XChartWidgetItem if (XChartWidgetItem is None): from projexui.widgets.xchartwidget.xchartwidgetitem \ import XChartWidgetItem self._buildData = {} # build the grid location x = 8 y = 8 w = self.sceneRect().width() h = self.sceneRect().height() hpad = self.horizontalPadding() vpad = self.verticalPadding() hmax = self.horizontalRuler().maxNotchSize(Qt.Horizontal) left_offset = hpad + self.verticalRuler().maxNotchSize(Qt.Vertical) right_offset = left_offset + hpad top_offset = vpad bottom_offset = top_offset + vpad + hmax left = x + left_offset right = w - right_offset top = y + top_offset bottom = h - bottom_offset rect = QRectF() rect.setLeft(left) rect.setRight(right) rect.setBottom(bottom) rect.setTop(top) self._buildData['grid_rect'] = rect # rebuild the ruler data self.rebuildGrid() self._dirty = False # rebuild all the items padding = self.horizontalPadding() + self.verticalPadding() grid = self.sceneRect() filt = lambda x: isinstance(x, XChartWidgetItem) items = filter(filt, self.items()) height = float(grid.height()) if height == 0: ratio = 1 else: ratio = grid.width() / height count = len(items) if (not count): return if (ratio >= 1): radius = (grid.height() - padding * 2) / 2.0 x = rect.center().x() y = rect.center().y() dx = radius * 2.5 dy = 0 else: radius = (grid.width() - padding * 2) / 2.0 x = rect.center().x() y = rect.center().y() dx = 0 dy = radius * 2.5 for item in items: item.setPieCenter(QPointF(x, y)) item.setRadius(radius) item.rebuild() x += dx y += dy if (self._trackerItem and self._trackerItem()): self._trackerItem().rebuild(self._buildData['grid_rect'])
def layout(self, scene, nodes, center=None, padX=None, padY=None, direction=None, animationGroup=None): """ Lays out the nodes for this scene based on a block layering algorithm. :param scene | <XNodeScene> nodes | [<XNode>, ..] center | <QPointF> || None padX | <int> || None padY | <int> || None direction | <Qt.Direction> animationGroup | <QAnimationGroup> || None :return {<XNode>: <QRectF>, ..} | new rects per affected node """ nodes = filter(lambda x: x is not None and x.isVisible(), nodes) # make sure we have at least 1 node, otherwise, it is already laid out if not nodes or len(nodes) == 1: return {} # calculate the default padding based on the scene if padX == None: if direction == Qt.Vertical: padX = 2 * scene.cellWidth() else: padX = 4 * scene.cellWidth() if padY == None: if direction == Qt.Vertical: padY = 4 * scene.cellHeight() else: padY = 2 * scene.cellWidth() # step 1: create a mapping of the connections connection_map = self.connectionMap(scene, nodes) # step 2: organize the nodes into layers based on their connection chain layers = self.generateLayers(scene, nodes, connection_map) # step 3: calculate the total dimensions for the layout bounds = QRectF() # step 3.1: compare the nodes together that have common connections layer_widths = [] layer_heights = [] node_heights = {} node_widths = {} for layer_index, layer in enumerate(layers): layer_w = 0 layer_h = 0 layer_node_w = [] layer_node_h = [] for node in layer: rect = node.rect() layer_node_w.append(rect.width()) layer_node_h.append(rect.height()) if direction == Qt.Vertical: layer_w += rect.width() layer_h = max(rect.height(), layer_h) else: layer_w = max(rect.width(), layer_w) layer_h += rect.height() # update the bounding area if direction == Qt.Vertical: layer_w += padX * 1 - len(layer) bounds.setWidth(max(layer_w, bounds.width())) bounds.setHeight(bounds.height() + layer_h) else: layer_h += padY * 1 - len(layer) bounds.setWidth(bounds.width() + layer_w) bounds.setHeight(max(layer_h, bounds.height())) node_widths[layer_index] = layer_node_w node_heights[layer_index] = layer_node_h layer_widths.append(layer_w) layer_heights.append(layer_h) if not center: center = scene.sceneRect().center() w = bounds.width() h = bounds.height() bounds.setX(center.x() - bounds.width() / 2.0) bounds.setY(center.y() - bounds.height() / 2.0) bounds.setWidth(w) bounds.setHeight(h) # step 4: assign positions for each node by layer processed_nodes = {} layer_grps = [(i, layer) for i, layer in enumerate(layers)] layer_grps.sort(key=lambda x: len(x[1])) used_rects = [n.sceneRect() for n in scene.nodes() if not n in nodes] for layer_index, layer in reversed(layer_grps): layer_width = layer_widths[layer_index] layer_height = layer_heights[layer_index] # determine the starting point for this layer if direction == Qt.Vertical: offset = layer_index * padY + sum(layer_heights[:layer_index]) point = QPointF(bounds.x(), offset + bounds.y()) else: offset = layer_index * padX + sum(layer_widths[:layer_index]) point = QPointF(offset + bounds.x(), bounds.y()) # assign node positions based on existing connections for node_index, node in enumerate(layer): max_, min_ = (None, None) inputs, outputs = connection_map[node] for connected_node in inputs + outputs: if not connected_node in processed_nodes: continue npos = processed_nodes[connected_node] nrect = connected_node.rect() rect = QRectF(npos.x(), npos.y(), nrect.width(), nrect.height()) if direction == Qt.Vertical: if min_ is None: min_ = rect.left() min_ = min(rect.left(), min_) max_ = max(rect.right(), max_) else: if min_ is None: min_ = rect.top() min_ = min(rect.top(), min_) max_ = max(rect.bottom(), max_) if direction == Qt.Vertical: off_x = 0 off_y = (layer_height - node.rect().height()) / 2.0 start_x = (bounds.width() - layer_width) start_y = 0 else: off_x = (layer_width - node.rect().width()) / 2.0 off_y = 0 start_x = 0 start_y = (bounds.height() - layer_height) # align against existing nodes point_x = -1 point_y = -1 offset = 0 before = True found_point = True new_rect = QRectF() while found_point: if not None in (min_, max_): if direction == Qt.Vertical: off_x = (max_ - min_) / 2.0 - node.rect().width() / 2.0 if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = min_ + off_x point_y = point.y() + off_y else: off_y = (max_ - min_) / 2.0 - node.rect().height() / 2.0 if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = min_ + off_y # otherwise, align based on its position in the layer else: if direction == Qt.Vertical: off_x = sum(node_widths[layer_index][:node_index]) off_x += node_index * padX off_x += start_x if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = point.x() + off_x point_y = point.y() + off_y else: off_y = sum(node_heights[layer_index][:node_index]) off_y += node_index * padY off_y += start_y if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = point.y() + off_y # determine if we've already used this point before before = not before found_point = False orect = node.rect() new_rect = QRectF(point_x, point_y, orect.width(), orect.height()) for used_rect in used_rects: if used_rect.intersects(new_rect): found_point = True break used_rects.append(new_rect) if not animationGroup: node.setPos(point_x, point_y) else: anim = XNodeAnimation(node, 'setPos') anim.setStartValue(node.pos()) anim.setEndValue(QPointF(point_x, point_y)) animationGroup.addAnimation(anim) processed_nodes[node] = QPointF(point_x, point_y) if self._testing: QApplication.processEvents() time.sleep(0.5) return processed_nodes
def setPos(self, *args): super(XChartWidgetItem, self).setPos(*args) if (self._horizontalOffset or self._verticalOffset): offset = QPointF(self._horizontalOffset, self._verticalOffset) super(XChartWidgetItem, self).setPos(self.pos() + offset)
def rebuild(self): """ Rebuilds the item based on the current points. """ scene = self.scene() if not scene: return self._subpaths = [] grid = scene.gridRect() typ = self.chartType() hruler = scene.horizontalRuler() vruler = scene.verticalRuler() path = QPainterPath() area = QPainterPath() self._buildData.clear() self._buildData['path_area'] = area self.setPos(0, 0) # draw a line item if typ == XChartScene.Type.Line: first = True pos = None home = None self._ellipses = [] points = self.points() if (self.orientation() == Qt.Horizontal): points.sort(hruler.compareValues, key=lambda x: x[0]) else: points.sort(vruler.compareValues, key=lambda y: y[1]) points.reverse() for x, y in self.points(): pos = scene.mapFromChart(x, y) if first: home = QPointF(pos.x(), grid.bottom()) area.moveTo(home) area.lineTo(pos) path.moveTo(pos) self._ellipses.append(pos) first = False else: path.lineTo(pos) area.lineTo(pos) self._ellipses.append(pos) if pos and home: area.lineTo(pos.x(), grid.bottom()) area.lineTo(home) # draw a bar item elif typ == XChartScene.Type.Bar: barsize = self.barSize() horiz = self.orientation() == Qt.Horizontal for x, y in self.points(): pos = scene.mapFromChart(x, y) subpath = QPainterPath() if horiz: r = min(grid.bottom() - pos.y(), 8) subpath.moveTo(pos.x() - barsize / 2.0, grid.bottom()) subpath.lineTo(pos.x() - barsize / 2.0, pos.y() + r) subpath.quadTo(pos.x() - barsize / 2.0, pos.y(), pos.x() - barsize / 2.0 + r, pos.y()) subpath.lineTo(pos.x() + barsize / 2.0 - r, pos.y()) subpath.quadTo(pos.x() + barsize / 2.0, pos.y(), pos.x() + barsize / 2.0, pos.y() + r) subpath.lineTo(pos.x() + barsize / 2.0, grid.bottom()) subpath.lineTo(pos.x() - barsize / 2.0, grid.bottom()) else: subpath.moveTo(grid.left(), pos.y() - barsize / 2.0) subpath.lineTo(pos.x(), pos.y() - barsize / 2.0) subpath.lineTo(pos.x(), pos.y() + barsize / 2.0) subpath.lineTo(grid.left(), pos.y() + barsize / 2.0) subpath.lineTo(grid.left(), pos.y() - barsize / 2.0) path.addPath(subpath) self._subpaths.append((x, y, subpath)) # draw a pie chart elif typ == XChartScene.Type.Pie: if self.orientation() == Qt.Horizontal: key_index = 0 value_index = 1 value_ruler = self.verticalRuler() else: key_index = 1 value_index = 0 value_ruler = self.horizontalRuler() pie_values = {} for point in self.points(): key = point[key_index] value = point[value_index] pie_values.setdefault(key, []) pie_values[key].append(value) for key, values in pie_values.items(): pie_values[key] = value_ruler.calcTotal(values) total = max(1, value_ruler.calcTotal(pie_values.values())) # calculate drawing parameters center = self.pieCenter() radius = self.radius() diameter = radius * 2 angle = 0 bound = QRectF(-radius, -radius, diameter, diameter) for key, value in sorted(pie_values.items(), key=lambda x: x[1]): # calculate the percentage perc = float(value) / total # calculate the angle as the perc * 360 item_angle = perc * 360 self.setPos(center) sub_path = QPainterPath() sub_path.arcTo(bound, angle, item_angle) sub_path.lineTo(0, 0) path.addPath(sub_path) self._subpaths.append((key, value, sub_path)) angle += item_angle self.setPath(path) self._dirty = False
def paint(self, painter, option, widget): """ Draws this item with the inputed painter. :param painter | <QPainter> rect | <QRect> """ if self._dirty: self.rebuild() scene = self.scene() if not scene: return grid = scene.gridRect() typ = self.chartType() # draw the line chart if typ == XChartScene.Type.Line: painter.setRenderHint(painter.Antialiasing) # draw the path area area = self._buildData.get('path_area') if area and self.isShaded(): clr = QColor(self.color()) clr.setAlpha(120) painter.setPen(Qt.NoPen) painter.setBrush(clr) painter.drawPath(area) # draw the line data pen = QPen(self.color()) pen.setWidth(2) painter.setPen(pen) painter.setBrush(Qt.NoBrush) painter.drawPath(self.path()) if (self.showPointsInLine()): palette = QApplication.palette() pen = QPen(palette.color(palette.Base)) pen.setWidth(2) painter.setBrush(self.color()) painter.setPen(pen) for point in self._ellipses: painter.drawEllipse(point, self.pointRadius(), self.pointRadius()) # draw a bar chart elif typ == XChartScene.Type.Bar: painter.setRenderHint(painter.Antialiasing) pen = QPen(self.color()) pen.setWidth(1) painter.setPen(pen) for key, value, sub_path in self._subpaths: gradient = QLinearGradient() clr = QColor(self.color()) if (sub_path != self._hoveredPath): clr.setAlpha(130) gradient.setColorAt(0.0, clr.lighter(140)) gradient.setColorAt(0.1, clr.lighter(120)) gradient.setColorAt(0.25, clr.lighter(110)) gradient.setColorAt(1.0, clr.lighter(105)) if (self.orientation() == Qt.Horizontal): gradient.setStart(0, sub_path.boundingRect().top()) gradient.setFinalStop(0, sub_path.boundingRect().bottom()) else: gradient.setStart(sub_path.boundingRect().left(), 0) gradient.setFinalStop(sub_path.boundingRect().right(), 0) painter.setBrush(gradient) painter.drawPath(sub_path) # draw a simple pie chart (calculated by scene) elif typ == XChartScene.Type.Pie: painter.setRenderHint(painter.Antialiasing) center = self.pieCenter() radius = self.radius() for key, value, sub_path in self._subpaths: clr = self.keyColor(key) gradient = QRadialGradient(QPointF(0, 0), radius) a = QColor(clr.lighter(140)) b = QColor(clr.lighter(110)) a.setAlpha(40) b.setAlpha(80) # look for mouse over if (sub_path == self._hoveredPath): a.setAlpha(100) b.setAlpha(200) gradient.setColorAt(0, a) gradient.setColorAt(1, b) pen = QPen(clr) pen.setWidth(1) painter.setBrush(gradient) painter.setPen(pen) painter.drawPath(sub_path)
def calculateDatasets(self, scene, axes, datasets): """ Builds the datasets for this renderer. Each renderer will need to subclass and implemenent this method, otherwise, no data will be shown in the chart. :param scene | <XChartScene> axes | [< datasets | [<XChartDataset>, ..] """ items = self.calculateDatasetItems(scene, datasets) if not items: scene.clear() return xaxis = scene.chart().horizontalAxis() yaxis = scene.chart().verticalAxis() data_axis = None all_values = [] # determine if we're mapping data aginst the sets themselves, in # which case, create a pie chart of the dataset vs. its if isinstance(xaxis, XDatasetAxis): per_dataset = False data_axis = yaxis total = 1 elif isinstance(yaxis, XDatasetAxis): per_dataset = False total = 1 data_axis = xaxis else: per_dataset = True total = len(items) if not per_dataset: all_values = [dataset.sum(data_axis) \ for dataset in datasets] # generate the build information rect = self.buildData('grid_rect') rect.setX(rect.x() + 10) rect.setY(rect.y() + 10) rect.setWidth(rect.width() - 20) rect.setHeight(rect.height() - 20) if rect.width() > rect.height(): radius = min(rect.width() / 2.0, rect.height() / 2.0) x = rect.left() y = rect.top() + radius deltax = min(radius * 2, rect.width() / float(total + 1)) deltay = 0 else: radius = min(rect.height() / 2.0, rect.width() / 2.0) x = rect.left() + radius y = rect.top() deltax = 0 deltay = min(radius * 2, rect.height() / float(total + 1)) # initialize the first pie chart angle = 0 cx = x + deltax cy = y + deltay x += deltax y += deltay self.setBuildData('center', QPointF(cx, cy)) self.setBuildData('radius', radius) for dataset in datasets: item = items.get(dataset) if not item: continue item.setBuildData('center', QPointF(cx, cy)) item.setBuildData('radius', radius) subpaths = [] bound = QRectF(cx - radius, cy - radius, radius * 2, radius * 2) path = QPainterPath() if per_dataset: data_values = dataset.values(yaxis.name()) andle = 0 for value in dataset.values(): perc = yaxis.percentOfTotal(value.get(yaxis.name()), data_values) # calculate the angle as the perc item_angle = perc * 360 subpath = QPainterPath() subpath.moveTo(cx, cy) subpath.arcTo(bound, angle, item_angle) subpath.lineTo(cx, cy) path.addPath(subpath) subpaths.append((value.get(xaxis.name()), subpath)) angle += item_angle cx = x + deltax cy = y + deltay x += deltax y += deltay else: value = dataset.sum(data_axis) perc = data_axis.percentOfTotal(value, all_values) # calculate the angle as the perc item_angle = perc * 360 subpath = QPainterPath() subpath.moveTo(cx, cy) subpath.arcTo(bound, angle, item_angle) subpath.lineTo(cx, cy) path.addPath(subpath) subpaths.append((value, subpath)) angle += item_angle item.setPath(path) item.setBuildData('subpaths', subpaths)
def layout(self, scene, nodes, center=None, padX=None, padY=None, direction=None, animationGroup=None): """ Lays out the nodes for this scene based on a block layering algorithm. :param scene | <XNodeScene> nodes | [<XNode>, ..] center | <QPointF> || None padX | <int> || None padY | <int> || None direction | <Qt.Direction> animationGroup | <QAnimationGroup> || None :return {<XNode>: <QRectF>, ..} | new rects per affected node """ nodes = filter(lambda x: x is not None and x.isVisible(), nodes) # make sure we have at least 1 node, otherwise, it is already laid out if not nodes or len(nodes) == 1: return {} # calculate the default padding based on the scene if padX == None: if direction == Qt.Vertical: padX = 2 * scene.cellWidth() else: padX = 4 * scene.cellWidth() if padY == None: if direction == Qt.Vertical: padY = 4 * scene.cellHeight() else: padY = 2 * scene.cellWidth() # step 1: create a mapping of the connections connection_map = self.connectionMap(scene, nodes) # step 2: organize the nodes into layers based on their connection chain layers = self.generateLayers(scene, nodes, connection_map) # step 3: calculate the total dimensions for the layout bounds = QRectF() # step 3.1: compare the nodes together that have common connections layer_widths = [] layer_heights = [] node_heights = {} node_widths = {} for layer_index, layer in enumerate(layers): layer_w = 0 layer_h = 0 layer_node_w = [] layer_node_h = [] for node in layer: rect = node.rect() layer_node_w.append(rect.width()) layer_node_h.append(rect.height()) if direction == Qt.Vertical: layer_w += rect.width() layer_h = max(rect.height(), layer_h) else: layer_w = max(rect.width(), layer_w) layer_h += rect.height() # update the bounding area if direction == Qt.Vertical: layer_w += padX * 1 - len(layer) bounds.setWidth(max(layer_w, bounds.width())) bounds.setHeight(bounds.height() + layer_h) else: layer_h += padY * 1 - len(layer) bounds.setWidth(bounds.width() + layer_w) bounds.setHeight(max(layer_h, bounds.height())) node_widths[layer_index] = layer_node_w node_heights[layer_index] = layer_node_h layer_widths.append(layer_w) layer_heights.append(layer_h) if not center: center = scene.sceneRect().center() w = bounds.width() h = bounds.height() bounds.setX(center.x() - bounds.width() / 2.0) bounds.setY(center.y() - bounds.height() / 2.0) bounds.setWidth(w) bounds.setHeight(h) # step 4: assign positions for each node by layer processed_nodes = {} layer_grps = [(i, layer) for i, layer in enumerate(layers)] layer_grps.sort(key=lambda x: len(x[1])) used_rects = [n.sceneRect() for n in scene.nodes() if not n in nodes] for layer_index, layer in reversed(layer_grps): layer_width = layer_widths[layer_index] layer_height = layer_heights[layer_index] # determine the starting point for this layer if direction == Qt.Vertical: offset = layer_index * padY + sum(layer_heights[:layer_index]) point = QPointF(bounds.x(), offset + bounds.y()) else: offset = layer_index * padX + sum(layer_widths[:layer_index]) point = QPointF(offset + bounds.x(), bounds.y()) # assign node positions based on existing connections for node_index, node in enumerate(layer): max_, min_ = (None, None) inputs, outputs = connection_map[node] for connected_node in inputs + outputs: if not connected_node in processed_nodes: continue npos = processed_nodes[connected_node] nrect = connected_node.rect() rect = QRectF(npos.x(), npos.y(), nrect.width(), nrect.height()) if direction == Qt.Vertical: if min_ is None: min_ = rect.left() min_ = min(rect.left(), min_) max_ = max(rect.right(), max_) else: if min_ is None: min_ = rect.top() min_ = min(rect.top(), min_) max_ = max(rect.bottom(), max_) if direction == Qt.Vertical: off_x = 0 off_y = (layer_height - node.rect().height()) / 2.0 start_x = (bounds.width() - layer_width) start_y = 0 else: off_x = (layer_width - node.rect().width()) / 2.0 off_y = 0 start_x = 0 start_y = (bounds.height() - layer_height) # align against existing nodes point_x = -1 point_y = -1 offset = 0 before = True found_point = True new_rect = QRectF() while found_point: if not None in (min_, max_): if direction == Qt.Vertical: off_x = (max_ - min_)/2.0 - node.rect().width()/2.0 if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = min_ + off_x point_y = point.y() + off_y else: off_y = (max_ - min_)/2.0 - node.rect().height()/2.0 if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = min_ + off_y # otherwise, align based on its position in the layer else: if direction == Qt.Vertical: off_x = sum(node_widths[layer_index][:node_index]) off_x += node_index * padX off_x += start_x if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = point.x() + off_x point_y = point.y() + off_y else: off_y = sum(node_heights[layer_index][:node_index]) off_y += node_index * padY off_y += start_y if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = point.y() + off_y # determine if we've already used this point before before = not before found_point = False orect = node.rect() new_rect = QRectF(point_x, point_y, orect.width(), orect.height()) for used_rect in used_rects: if used_rect.intersects(new_rect): found_point = True break used_rects.append(new_rect) if not animationGroup: node.setPos(point_x, point_y) else: anim = XNodeAnimation(node, 'setPos') anim.setStartValue(node.pos()) anim.setEndValue(QPointF(point_x, point_y)) animationGroup.addAnimation(anim) processed_nodes[node] = QPointF(point_x, point_y) if self._testing: QApplication.processEvents() time.sleep(0.5) return processed_nodes