コード例 #1
0
 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))
コード例 #2
0
 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
コード例 #3
0
    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 = {}
コード例 #4
0
    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
コード例 #5
0
ファイル: xganttdepitem.py プロジェクト: zengjunfeng/projexui
    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)
コード例 #6
0
    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)
コード例 #7
0
    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'])
コード例 #8
0
ファイル: xnodelayout.py プロジェクト: zengjunfeng/projexui
    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
コード例 #9
0
    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)
コード例 #10
0
    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
コード例 #11
0
    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)
コード例 #12
0
    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)
コード例 #13
0
ファイル: xnodelayout.py プロジェクト: bitesofcode/projexui
 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