Beispiel #1
0
    def generateDrawSpecs(self, p):
        """
        Calls tickValues() and tickStrings() to determine where and how ticks should
        be drawn, then generates from this a set of drawing commands to be
        interpreted by drawPicture().
        """

        # bounds = self.boundingRect()

        self.fullBoundingRect = QtCore.QRectF(0, 0,
                                              self._bounds["radial"][1] * 2,
                                              self._bounds["radial"][1] * 2)
        self.fullBoundingRect.moveCenter(Point(0, 0))

        bounds = self.mapRectFromParent(self.geometry())

        linkedView = self.linkedView()
        tickBounds = linkedView.mapRectToItem(self, linkedView.boundingRect())

        span = (bounds.bottomLeft(), bounds.topRight())
        tickStart = 0
        tickStop = max(bounds.width(), bounds.height())
        tickDir = 0
        axis = 0
        # print tickStart, tickStop, span

        ## determine size of this item in pixels
        points = list(map(self.mapToDevice, span))
        if None in points:
            return
        lengthInPixels = Point(points[1] - points[0]).length()
        if lengthInPixels == 0:
            return

        # Determine major / minor / subminor axis ticks
        if self._tickLevels is None:
            rng = self.linkedView().viewRange()
            if self.pOrientation is "radial":
                tickLevels = self.tickValues(
                    self.range[0],
                    np.sqrt(max(np.abs(rng[0]))**2 + max(np.abs(rng[1]))**2),
                    lengthInPixels)
            else:
                # todo undo hardcoded angles
                tickLevels = self.tickValues(self._bounds["azimuthal"][0],
                                             self._bounds["azimuthal"][1],
                                             lengthInPixels)
            tickStrings = None
        else:
            ## parse self.tickLevels into the formats returned by tickLevels() and tickStrings()
            tickLevels = []
            tickStrings = []
            for level in self._tickLevels:
                values = []
                strings = []
                tickLevels.append((None, values))
                tickStrings.append(strings)
                for val, strn in level:
                    values.append(val)
                    strings.append(strn)

        # Not needed for radial plots
        xScale = 1
        offset = 0

        if self.pOrientation is "radial":
            rng = self.linkedView().viewRange()
            xRange = [0] + [abs(x * xScale - offset) for x in self.range]
            xMin = self._bounds["radial"][0]
            xMax = np.sqrt(max(np.abs(rng[0]))**2 + max(np.abs(rng[1]))**2)
            xMax = max(xMax, self._bounds["radial"][0])
        else:
            xRange = [x * xScale - offset for x in self.range]
            xMin = self._bounds["azimuthal"][0]
            xMax = self._bounds["azimuthal"][1]

        tickPositions = []  # remembers positions of previously drawn ticks

        ## compute coordinates to draw ticks
        ## draw three different intervals, long ticks first
        tickSpecs = []
        for i in range(len(tickLevels)):
            tickPositions.append([])
            ticks = tickLevels[i][1]

            ## length of tick
            tickLength = self.style['tickLength'] / ((i * 0.5) + 1.0)

            lineAlpha = 255 / (i + 3)
            if self.grid is not False:
                lineAlpha *= self.grid / 255. * np.clip(
                    (0.05 * lengthInPixels / (len(ticks) + 1)), 0., 1.)

            for v in ticks:
                ## determine actual position to draw this tick
                x = (v * xScale) - offset
                if x < xMin or x >= xMax:  ## last check to make sure no out-of-bounds ticks are drawn
                    tickPositions[i].append(None)
                    continue

                tickPositions[i].append(x)

                if self.pOrientation is "radial":
                    p1 = [x, x]
                    p2 = [x, x]
                    p1[axis] = tickStart
                    p2[axis] = tickStop
                    if self.grid is False:
                        p2[axis] += tickLength * tickDir
                else:
                    p1, p2 = self.getAzimuthalLines(self.boundingRect(), x)

                tickPen = self.pen()
                color = tickPen.color()
                color.setAlpha(lineAlpha)
                tickPen.setColor(color)
                tickSpecs.append((tickPen, Point(p1), Point(p2)))

        if self.style['stopAxisAtTick'][0] is True:
            stop = max(span[0].y(), min(map(min, tickPositions)))
            if axis == 0:
                span[0].setY(stop)
            else:
                span[0].setX(stop)
        if self.style['stopAxisAtTick'][1] is True:
            stop = min(span[1].y(), max(map(max, tickPositions)))
            if axis == 0:
                span[1].setY(stop)
            else:
                span[1].setX(stop)
        axisSpec = (self.pen(), span[0], span[1])

        textOffset = self.style['tickTextOffset'][
            axis]  ## spacing between axis and text
        # if self.style['autoExpandTextSpace'] is True:
        # textWidth = self.textWidth
        # textHeight = self.textHeight
        # else:
        # textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text
        # textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text

        textSize2 = 0
        textRects = []
        textSpecs = []  ## list of draw

        # If values are hidden, return early
        if not self.style['showValues']:
            return (axisSpec, tickSpecs, textSpecs)

        # For appropriate sizing of boxes
        if self.tickFont is None: return (axisSpec, tickSpecs, textSpecs)
        p.setFont(self.tickFont)
        for i in range(min(len(tickLevels), self.style['maxTextLevel'] + 1)):
            ## Get the list of strings to display for this level
            if tickStrings is None:
                spacing, values = tickLevels[i]
                strings = self.tickStrings(values,
                                           self.autoSIPrefixScale * self.scale,
                                           spacing)
            else:
                strings = tickStrings[i]

            if len(strings) == 0:
                continue

            ## ignore strings belonging to ticks that were previously ignored
            for j in range(len(strings)):
                if tickPositions[i][j] is None:
                    strings[j] = None

            ## Measure density of text; decide whether to draw this level
            rects = []
            for s in strings:
                if s is None:
                    rects.append(None)
                else:
                    br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100),
                                        QtCore.Qt.AlignCenter, s)
                    ## boundingRect is usually just a bit too large
                    ## (but this probably depends on per-font metrics?)
                    br.setHeight(br.height() * 0.8)

                    rects.append(br)
                    textRects.append(rects[-1])

            if len(textRects) > 0:
                ## measure all text, make sure there's enough room
                if axis == 0:
                    textSize = np.sum([r.height() for r in textRects])
                    textSize2 = np.max([r.width() for r in textRects])
                else:
                    textSize = np.sum([r.width() for r in textRects])
                    textSize2 = np.max([r.height() for r in textRects])
            else:
                textSize = 0
                textSize2 = 0

            if i > 0:  ## always draw top level
                ## If the strings are too crowded, stop drawing text now.
                ## We use three different crowding limits based on the number
                ## of texts drawn so far.
                textFillRatio = float(textSize) / lengthInPixels
                finished = False
                for nTexts, limit in self.style['textFillLimits']:
                    if len(textSpecs) >= nTexts and textFillRatio >= limit:
                        finished = True
                        break
                if finished:
                    break

            # spacing, values = tickLevels[best]
            # strings = self.tickStrings(values, self.scale, spacing)
            # Determine exactly where tick text should be drawn
            for j in range(len(strings)):
                vstr = strings[j]
                if vstr is None:  ## this tick was ignored because it is out of bounds
                    continue
                x = tickPositions[i][j]
                # textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr)
                textRect = rects[j]
                height = textRect.height()
                width = textRect.width()
                # self.textHeight = height
                offset = max(0, self.style['tickLength']) + textOffset
                textFlags = QtCore.Qt.TextDontClip | QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter

                if self.pOrientation is "azimuthal":

                    # Originally wanted to put the text on the outside of
                    # the viewbox, but that was breaking things, especially
                    # because it wasn't handling intialization correctly, when the
                    # viewbox is first being setup.
                    #pp = tickSpecs[j][-1] # Take the outside coordinate
                    pp = self._bounds["radial"][1] if np.isfinite(
                        self._bounds["radial"][1]) else tickSpecs[j][-1]
                    pp = Point(pp * np.cos(x * 3.14159 / 180),
                               -pp * np.sin(x * 3.14159 / 180))

                    rect = QtCore.QRectF(0, 0, width, height)
                    rect.moveCenter(Point(0, 0))
                    xm = x % 360
                    if xm == 0:
                        rect.moveLeft(pp.x())
                        # print("00pp", pp)
                    elif 0 < xm < 90:
                        rect.moveBottomLeft(pp)
                    elif xm == 90:
                        rect.moveBottom(pp.y())
                        # print("90pp", pp)
                    elif 90 < xm < 180:
                        rect.moveBottomRight(pp)
                    elif xm == 180:
                        rect.moveRight(pp.x())
                    elif 180 < xm < 270:
                        rect.moveTopRight(pp)
                    elif xm == 270:
                        rect.moveTop(pp.y())
                    elif 270 < xm < 360:
                        rect.moveTopLeft(pp)
                elif self.pOrientation is "radial":
                    rect = QtCore.QRectF(0, 0, width, height)
                    # rect.moveCenter(Point(0, 0))
                    rect.moveBottomLeft(
                        Point(
                            x *
                            np.cos(self._radialLabelPosition * np.pi / 180),
                            # Don't forget to flip the axis
                            x *
                            np.sin(-self._radialLabelPosition * np.pi / 180)))
                self.fullBoundingRect |= rect

                # p.setPen(self.pen())
                # p.drawText(rect, textFlags, vstr)
                textSpecs.append((rect, textFlags, vstr))

        ## update max text size if needed.
        self._updateMaxTextSize(textSize2)

        return (axisSpec, tickSpecs, textSpecs)
    def scaleBy(self, s=None, center=None, x=None, y=None):

        if s is not None:
            scale = Point(s)
        else:
            scale = [x, y]

        affect = [True, True]
        if scale[0] is None and scale[1] is None:
            return
        elif scale[0] is None:
            affect[0] = False
            scale[0] = 1.0
        elif scale[1] is None:
            affect[1] = False
            scale[1] = 1.0

        scale = Point(scale)

        if self.state['aspectLocked'] is not False:

            scale[0] = scale[1]
        """Edit"""
        vr = self.viewRect()
        """End of Edit"""

        if center is None:
            center = Point(vr.center())
        else:
            center = Point(center)

        tl = center + (vr.topLeft() - center) * scale
        br = center + (vr.bottomRight() - center) * scale
        """Addition"""
        yMax = self.state['limits']['yLimits'][1]
        xMax = self.state['limits']['xLimits'][1]
        xMin = self.state['limits']['xLimits'][0]
        yMin = self.state['limits']['yLimits'][0]

        scale_limit = []
        if yMax is not None and affect[0]:

            if (br.y() > yMax):

                scale_limit.append((yMax - center.y()) /
                                   (1.0 * vr.bottomRight().y() - center.y()))
                print 'yMax scale is :' + str(scale_limit[-1])

        if xMax is not None:

            if (br.x() > xMax) and affect[1]:
                scale_limit.append((xMax - center.x()) /
                                   (1.0 * vr.bottomRight().x() - center.x()))
                print 'xMax scale is :' + str(scale_limit[-1])

        if xMin is not None:

            if (tl.x() < xMin) and affect[1]:
                scale_limit.append((xMin - center.x()) /
                                   (1.0 * vr.topLeft().x() - center.x()))
                print 'xMin scale is :' + str(scale_limit[-1])

        if yMin is not None:

            if (tl.y() < yMin) and affect[0]:
                scale_limit.append((yMin - center.y()) /
                                   (1.0 * vr.topLeft().y() - center.y()))
                print 'yMin scale is :' + str(scale_limit[-1])

        if self.state['aspectLocked'] is not False and len(scale_limit) > 0:

            min_scale_limit = min(scale_limit)

            scale[1] = min_scale_limit
            scale[0] = scale[1]

            tl = center + (vr.topLeft() - center) * scale
            br = center + (vr.bottomRight() - center) * scale
        """End of Addition"""

        if not affect[0]:
            self.setYRange(tl.y(), br.y(), padding=0)
        elif not affect[1]:
            self.setXRange(tl.x(), br.x(), padding=0)
        else:
            self.setRange(QtCore.QRectF(tl, br), padding=0)