class Legend(Widget):
    """A simple legend containing rectangular swatches and strings.

    The swatches are filled rectangles whenever the respective
    color object in 'colorNamePairs' is a subclass of Color in
    reportlab.lib.colors. Otherwise the object passed instead is
    assumed to have 'x', 'y', 'width' and 'height' attributes.
    A legend then tries to set them or catches any error. This
    lets you plug-in any widget you like as a replacement for
    the default rectangular swatches.

    Strings can be nicely aligned left or right to the swatches.
    """

    _attrMap = AttrMap(
        x=AttrMapValue(isNumber,
                       desc="x-coordinate of upper-left reference point"),
        y=AttrMapValue(isNumber,
                       desc="y-coordinate of upper-left reference point"),
        deltax=AttrMapValue(isNumberOrNone,
                            desc="x-distance between neighbouring swatches"),
        deltay=AttrMapValue(isNumberOrNone,
                            desc="y-distance between neighbouring swatches"),
        dxTextSpace=AttrMapValue(
            isNumber, desc="Distance between swatch rectangle and text"),
        autoXPadding=AttrMapValue(
            isNumber,
            desc="x Padding between columns if deltax=None",
            advancedUsage=1),
        autoYPadding=AttrMapValue(isNumber,
                                  desc="y Padding between rows if deltay=None",
                                  advancedUsage=1),
        yGap=AttrMapValue(isNumber,
                          desc="Additional gap between rows",
                          advancedUsage=1),
        dx=AttrMapValue(isNumber, desc="Width of swatch rectangle"),
        dy=AttrMapValue(isNumber, desc="Height of swatch rectangle"),
        columnMaximum=AttrMapValue(isNumber,
                                   desc="Max. number of items per column"),
        alignment=AttrMapValue(
            OneOf("left", "right"),
            desc="Alignment of text with respect to swatches"),
        colorNamePairs=AttrMapValue(
            None, desc="List of color/name tuples (color can also be widget)"),
        fontName=AttrMapValue(isString, desc="Font name of the strings"),
        fontSize=AttrMapValue(isNumber, desc="Font size of the strings"),
        fillColor=AttrMapValue(isColorOrNone, desc="swatches filling color"),
        strokeColor=AttrMapValue(isColorOrNone,
                                 desc="Border color of the swatches"),
        strokeWidth=AttrMapValue(
            isNumber, desc="Width of the border color of the swatches"),
        swatchMarker=AttrMapValue(
            NoneOr(AutoOr(isSymbol)),
            desc="None, Auto() or makeMarker('Diamond') ...",
            advancedUsage=1),
        callout=AttrMapValue(None,
                             desc="a user callout(self,g,x,y,(color,text))",
                             advancedUsage=1),
        boxAnchor=AttrMapValue(isBoxAnchor,
                               'Anchor point for the legend area'),
        variColumn=AttrMapValue(
            isBoolean,
            'If true column widths may vary (default is false)',
            advancedUsage=1),
        dividerLines=AttrMapValue(
            OneOf(0, 1, 2, 3, 4, 5, 6, 7),
            'If 1 we have dividers between the rows | 2 for extra top | 4 for bottom',
            advancedUsage=1),
        dividerWidth=AttrMapValue(isNumber,
                                  desc="dividerLines width",
                                  advancedUsage=1),
        dividerColor=AttrMapValue(isColorOrNone,
                                  desc="dividerLines color",
                                  advancedUsage=1),
        dividerDashArray=AttrMapValue(isListOfNumbersOrNone,
                                      desc='Dash array for dividerLines.',
                                      advancedUsage=1),
        dividerOffsX=AttrMapValue(SequenceOf(isNumber, emptyOK=0, lo=2, hi=2),
                                  desc='divider lines X offsets',
                                  advancedUsage=1),
        dividerOffsY=AttrMapValue(isNumber,
                                  desc="dividerLines Y offset",
                                  advancedUsage=1),
        colEndCallout=AttrMapValue(
            None,
            desc="a user callout(self,g, x, xt, y,width, lWidth)",
            advancedUsage=1),
        subCols=AttrMapValue(None, desc="subColumn properties"),
        swatchCallout=AttrMapValue(
            None,
            desc="a user swatch callout(self,g,x,y,i,(col,name),swatch)",
            advancedUsage=1),
        swdx=AttrMapValue(isNumber,
                          desc="x position adjustment for the swatch"),
        swdy=AttrMapValue(isNumber,
                          desc="y position adjustment for the swatch"),
    )

    def __init__(self):
        # Upper-left reference point.
        self.x = 0
        self.y = 0

        # Alginment of text with respect to swatches.
        self.alignment = "left"

        # x- and y-distances between neighbouring swatches.
        self.deltax = 75
        self.deltay = 20
        self.autoXPadding = 5
        self.autoYPadding = 2

        # Size of swatch rectangle.
        self.dx = 10
        self.dy = 10

        self.swdx = 0
        self.swdy = 0

        # Distance between swatch rectangle and text.
        self.dxTextSpace = 10

        # Max. number of items per column.
        self.columnMaximum = 3

        # Color/name pairs.
        self.colorNamePairs = [(colors.red, "red"), (colors.blue, "blue"),
                               (colors.green, "green"), (colors.pink, "pink"),
                               (colors.yellow, "yellow")]

        # Font name and size of the labels.
        self.fontName = STATE_DEFAULTS['fontName']
        self.fontSize = STATE_DEFAULTS['fontSize']
        self.fillColor = STATE_DEFAULTS['fillColor']
        self.strokeColor = STATE_DEFAULTS['strokeColor']
        self.strokeWidth = STATE_DEFAULTS['strokeWidth']
        self.swatchMarker = None
        self.boxAnchor = 'nw'
        self.yGap = 0
        self.variColumn = 0
        self.dividerLines = 0
        self.dividerWidth = 0.5
        self.dividerDashArray = None
        self.dividerColor = colors.black
        self.dividerOffsX = (0, 0)
        self.dividerOffsY = 0
        self.colEndCallout = None
        self._init_subCols()

    def _init_subCols(self):
        sc = self.subCols = TypedPropertyCollection(SubColProperty)
        sc.rpad = 1
        sc.dx = sc.dy = sc.minWidth = 0
        sc.align = 'right'
        sc[0].align = 'left'

    def _getChartStyleName(self, chart):
        for a in 'lines', 'bars', 'slices', 'strands':
            if hasattr(chart, a): return a
        return None

    def _getChartStyle(self, chart):
        return getattr(chart, self._getChartStyleName(chart), None)

    def _getTexts(self, colorNamePairs):
        if not isAuto(colorNamePairs):
            texts = [_getStr(p[1]) for p in colorNamePairs]
        else:
            chart = getattr(colorNamePairs, 'chart',
                            getattr(colorNamePairs, 'obj', None))
            texts = [
                chart.getSeriesName(i, 'series %d' % i)
                for i in range(chart._seriesCount)
            ]
        return texts

    def _calculateMaxBoundaries(self, colorNamePairs):
        "Calculate the maximum width of some given strings."
        fontName = self.fontName
        fontSize = self.fontSize
        subCols = self.subCols

        M = [
            _getWidths(i, m, fontName, fontSize, subCols)
            for i, m in enumerate(self._getTexts(colorNamePairs))
        ]
        if not M:
            return [0, 0]
        n = max([len(m) for m in M])
        if self.variColumn:
            columnMaximum = self.columnMaximum
            return [
                _transMax(n, M[r:r + columnMaximum])
                for r in range(0, len(M), self.columnMaximum)
            ]
        else:
            return _transMax(n, M)

    def _calcHeight(self):
        dy = self.dy
        yGap = self.yGap
        thisy = upperlefty = self.y - dy
        fontSize = self.fontSize
        ascent = getFont(self.fontName).face.ascent / 1000.
        if ascent == 0: ascent = 0.718  # default (from helvetica)
        ascent *= fontSize
        leading = fontSize * 1.2
        deltay = self.deltay
        if not deltay: deltay = max(dy, leading) + self.autoYPadding
        columnCount = 0
        count = 0
        lowy = upperlefty
        lim = self.columnMaximum - 1
        for name in self._getTexts(self.colorNamePairs):
            y0 = thisy + (dy - ascent) * 0.5
            y = y0 - _getLineCount(name) * leading
            leadingMove = 2 * y0 - y - thisy
            newy = thisy - max(deltay, leadingMove) - yGap
            lowy = min(y, newy, lowy)
            if count == lim:
                count = 0
                thisy = upperlefty
                columnCount = columnCount + 1
            else:
                thisy = newy
                count = count + 1
        return upperlefty - lowy

    def _defaultSwatch(self, x, thisy, dx, dy, fillColor, strokeWidth,
                       strokeColor):
        return Rect(
            x,
            thisy,
            dx,
            dy,
            fillColor=fillColor,
            strokeColor=strokeColor,
            strokeWidth=strokeWidth,
        )

    def draw(self):
        colorNamePairs = self.colorNamePairs
        autoCP = isAuto(colorNamePairs)
        if autoCP:
            chart = getattr(colorNamePairs, 'chart',
                            getattr(colorNamePairs, 'obj', None))
            swatchMarker = None
            autoCP = Auto(obj=chart)
            n = chart._seriesCount
            chartTexts = self._getTexts(colorNamePairs)
        else:
            swatchMarker = getattr(self, 'swatchMarker', None)
            if isAuto(swatchMarker):
                chart = getattr(swatchMarker, 'chart',
                                getattr(swatchMarker, 'obj', None))
                swatchMarker = Auto(obj=chart)
            n = len(colorNamePairs)
        dx = self.dx
        dy = self.dy
        alignment = self.alignment
        columnMaximum = self.columnMaximum
        deltax = self.deltax
        deltay = self.deltay
        dxTextSpace = self.dxTextSpace
        fontName = self.fontName
        fontSize = self.fontSize
        fillColor = self.fillColor
        strokeWidth = self.strokeWidth
        strokeColor = self.strokeColor
        subCols = self.subCols
        leading = fontSize * 1.2
        yGap = self.yGap
        if not deltay:
            deltay = max(dy, leading) + self.autoYPadding
        ba = self.boxAnchor
        maxWidth = self._calculateMaxBoundaries(colorNamePairs)
        nCols = int((n + columnMaximum - 1) / (columnMaximum * 1.0))
        xW = dx + dxTextSpace + self.autoXPadding
        variColumn = self.variColumn
        if variColumn:
            width = reduce(operator.add, [m[-1]
                                          for m in maxWidth], 0) + xW * nCols
        else:
            deltax = max(maxWidth[-1] + xW, deltax)
            width = maxWidth[-1] + nCols * deltax
            maxWidth = nCols * [maxWidth]

        thisx = self.x
        thisy = self.y - self.dy
        if ba not in ('ne', 'n', 'nw', 'autoy'):
            height = self._calcHeight()
            if ba in ('e', 'c', 'w'):
                thisy += height / 2.
            else:
                thisy += height
        if ba not in ('nw', 'w', 'sw', 'autox'):
            if ba in ('n', 'c', 's'):
                thisx -= width / 2
            else:
                thisx -= width
        upperlefty = thisy

        g = Group()

        ascent = getFont(fontName).face.ascent / 1000.
        if ascent == 0: ascent = 0.718  # default (from helvetica)
        ascent *= fontSize  # normalize

        lim = columnMaximum - 1
        callout = getattr(self, 'callout', None)
        scallout = getattr(self, 'swatchCallout', None)
        dividerLines = self.dividerLines
        if dividerLines:
            dividerWidth = self.dividerWidth
            dividerColor = self.dividerColor
            dividerDashArray = self.dividerDashArray
            dividerOffsX = self.dividerOffsX
            dividerOffsY = self.dividerOffsY

        for i in range(n):
            if autoCP:
                col = autoCP
                col.index = i
                name = chartTexts[i]
            else:
                col, name = colorNamePairs[i]
                if isAuto(swatchMarker):
                    col = swatchMarker
                    col.index = i
                if isAuto(name):
                    name = getattr(swatchMarker, 'chart',
                                   getattr(swatchMarker, 'obj',
                                           None)).getSeriesName(
                                               i, 'series %d' % i)
            T = _getLines(name)
            S = []
            aS = S.append
            j = int(i / (columnMaximum * 1.0))
            jOffs = maxWidth[j]

            # thisy+dy/2 = y+leading/2
            y = y0 = thisy + (dy - ascent) * 0.5

            if callout: callout(self, g, thisx, y, (col, name))
            if alignment == "left":
                x = thisx
                xn = thisx + jOffs[-1] + dxTextSpace
            elif alignment == "right":
                x = thisx + dx + dxTextSpace
                xn = thisx
            else:
                raise ValueError("bad alignment")
            if not isSeq(name):
                T = [T]
            yd = y
            for k, lines in enumerate(T):
                y = y0
                kk = k * 2
                x1 = x + jOffs[kk]
                x2 = x + jOffs[kk + 1]
                sc = subCols[k, i]
                anchor = sc.align
                scdx = sc.dx
                scdy = sc.dy
                fN = getattr(sc, 'fontName', fontName)
                fS = getattr(sc, 'fontSize', fontSize)
                fC = getattr(sc, 'fillColor', fillColor)
                fL = getattr(sc, 'leading', 1.2 * fontSize)
                if fN == fontName:
                    fA = (ascent * fS) / fontSize
                else:
                    fA = getFont(fontName).face.ascent / 1000.
                    if fA == 0: fA = 0.718
                    fA *= fS
                if anchor == 'left':
                    anchor = 'start'
                    xoffs = x1
                elif anchor == 'right':
                    anchor = 'end'
                    xoffs = x2
                elif anchor == 'numeric':
                    xoffs = x2
                else:
                    anchor = 'middle'
                    xoffs = 0.5 * (x1 + x2)
                for t in lines:
                    aS(
                        String(xoffs + scdx,
                               y + scdy,
                               t,
                               fontName=fN,
                               fontSize=fS,
                               fillColor=fC,
                               textAnchor=anchor))
                    y -= fL
                yd = min(yd, y)
                y += fL
                for iy, a in ((y - max(fL - fA, 0), 'underlines'),
                              (y + fA, 'overlines')):
                    il = getattr(sc, a, None)
                    if il:
                        if not isinstance(il, (tuple, list)): il = (il, )
                        for l in il:
                            l = copy.copy(l)
                            l.y1 += iy
                            l.y2 += iy
                            l.x1 += x1
                            l.x2 += x2
                            aS(l)
            x = xn
            y = yd
            leadingMove = 2 * y0 - y - thisy

            if dividerLines:
                xd = thisx + dx + dxTextSpace + jOffs[-1] + dividerOffsX[1]
                yd = thisy + dy * 0.5 + dividerOffsY
                if ((dividerLines & 1) and i % columnMaximum) or (
                    (dividerLines & 2) and not i % columnMaximum):
                    g.add(
                        Line(thisx + dividerOffsX[0],
                             yd,
                             xd,
                             yd,
                             strokeColor=dividerColor,
                             strokeWidth=dividerWidth,
                             strokeDashArray=dividerDashArray))

                if (dividerLines & 4) and (i % columnMaximum == lim
                                           or i == (n - 1)):
                    yd -= max(deltay, leadingMove) + yGap
                    g.add(
                        Line(thisx + dividerOffsX[0],
                             yd,
                             xd,
                             yd,
                             strokeColor=dividerColor,
                             strokeWidth=dividerWidth,
                             strokeDashArray=dividerDashArray))

            # Make a 'normal' color swatch...
            swatchX = x + getattr(self, 'swdx', 0)
            swatchY = thisy + getattr(self, 'swdy', 0)

            if isAuto(col):
                chart = getattr(col, 'chart', getattr(col, 'obj', None))
                c = chart.makeSwatchSample(getattr(col, 'index', i), swatchX,
                                           swatchY, dx, dy)
            elif isinstance(col, colors.Color):
                if isSymbol(swatchMarker):
                    c = uSymbol2Symbol(swatchMarker, swatchX + dx / 2.,
                                       swatchY + dy / 2., col)
                else:
                    c = self._defaultSwatch(swatchX,
                                            swatchY,
                                            dx,
                                            dy,
                                            fillColor=col,
                                            strokeWidth=strokeWidth,
                                            strokeColor=strokeColor)
            elif col is not None:
                try:
                    c = copy.deepcopy(col)
                    c.x = swatchX
                    c.y = swatchY
                    c.width = dx
                    c.height = dy
                except:
                    c = None
            else:
                c = None

            if c:
                g.add(c)
                if scallout: scallout(self, g, thisx, y0, i, (col, name), c)

            for s in S:
                g.add(s)
            if self.colEndCallout and (i % columnMaximum == lim
                                       or i == (n - 1)):
                if alignment == "left":
                    xt = thisx
                else:
                    xt = thisx + dx + dxTextSpace
                yd = thisy + dy * 0.5 + dividerOffsY - (
                    max(deltay, leadingMove) + yGap)
                self.colEndCallout(self, g, thisx, xt, yd, jOffs[-1],
                                   jOffs[-1] + dx + dxTextSpace)

            if i % columnMaximum == lim:
                if variColumn:
                    thisx += jOffs[-1] + xW
                else:
                    thisx = thisx + deltax
                thisy = upperlefty
            else:
                thisy = thisy - max(deltay, leadingMove) - yGap

        return g

    def demo(self):
        "Make sample legend."

        d = Drawing(200, 100)

        legend = Legend()
        legend.alignment = 'left'
        legend.x = 0
        legend.y = 100
        legend.dxTextSpace = 5
        items = 'red green blue yellow pink black white'.split()
        items = [(getattr(colors, i), i) for i in items]
        legend.colorNamePairs = items

        d.add(legend, 'legend')

        return d
Exemple #2
0
class ShadedRect(Widget):
    """This makes a rectangle with shaded colors between two colors.

    Colors are interpolated linearly between 'fillColorStart'
    and 'fillColorEnd', both of which appear at the margins.
    If 'numShades' is set to one, though, only 'fillColorStart'
    is used.
    """

    _attrMap = AttrMap(
        x = AttrMapValue(isNumber, desc="The grid's lower-left x position."),
        y = AttrMapValue(isNumber, desc="The grid's lower-left y position."),
        width = AttrMapValue(isNumber, desc="The grid's width."),
        height = AttrMapValue(isNumber, desc="The grid's height."),
        orientation = AttrMapValue(OneOf(('vertical', 'horizontal')), desc='Determines if stripes are vertical or horizontal.'),
        numShades = AttrMapValue(isNumber, desc='The number of interpolating colors.'),
        fillColorStart = AttrMapValue(isColorOrNone, desc='Start value of the color shade.'),
        fillColorEnd = AttrMapValue(isColorOrNone, desc='End value of the color shade.'),
        strokeColor = AttrMapValue(isColorOrNone, desc='Color used for border line.'),
        strokeWidth = AttrMapValue(isNumber, desc='Width used for lines.'),
        cylinderMode = AttrMapValue(isBoolean, desc='True if shading reverses in middle.'),
        )

    def __init__(self,**kw):
        self.x = 0
        self.y = 0
        self.width = 100
        self.height = 100
        self.orientation = 'vertical'
        self.numShades = 20
        self.fillColorStart = colors.pink
        self.fillColorEnd = colors.black
        self.strokeColor = colors.black
        self.strokeWidth = 2
        self.cylinderMode = 0
        self.setProperties(kw)

    def demo(self):
        D = Drawing(100, 100)
        g = ShadedRect()
        D.add(g)

        return D

    def _flipRectCorners(self):
        "Flip rectangle's corners if width or height is negative."
        x, y, width, height, fillColorStart, fillColorEnd = self.x, self.y, self.width, self.height, self.fillColorStart, self.fillColorEnd
        if width < 0 and height > 0:
            x = x + width
            width = -width
            if self.orientation=='vertical': fillColorStart, fillColorEnd = fillColorEnd, fillColorStart
        elif height<0 and width>0:
            y = y + height
            height = -height
            if self.orientation=='horizontal': fillColorStart, fillColorEnd = fillColorEnd, fillColorStart
        elif height < 0 and height < 0:
            x = x + width
            width = -width
            y = y + height
            height = -height
        return x, y, width, height, fillColorStart, fillColorEnd

    def draw(self):
        # general widget bits
        group = Group()
        x, y, w, h, c0, c1 = self._flipRectCorners()
        numShades = self.numShades
        if self.cylinderMode:
            if not numShades%2: numShades = numShades+1
            halfNumShades = int((numShades-1)/2) + 1
        num = float(numShades) # must make it float!
        vertical = self.orientation == 'vertical'
        if vertical:
            if numShades == 1:
                V = [x]
            else:
                V = frange(x, x + w, w/num)
        else:
            if numShades == 1:
                V = [y]
            else:
                V = frange(y, y + h, h/num)

        for v in V:
            stripe = vertical and Rect(v, y, w/num, h) or Rect(x, v, w, h/num)
            if self.cylinderMode:
                if V.index(v)>=halfNumShades:
                    col = colors.linearlyInterpolatedColor(c1,c0,V[halfNumShades],V[-1], v)
                else:
                    col = colors.linearlyInterpolatedColor(c0,c1,V[0],V[halfNumShades], v)
            else:
                col = colors.linearlyInterpolatedColor(c0,c1,V[0],V[-1], v)
            stripe.fillColor = col
            stripe.strokeColor = col
            stripe.strokeWidth = 1
            group.add(stripe)
        if self.strokeColor and self.strokeWidth>=0:
            rect = Rect(x, y, w, h)
            rect.strokeColor = self.strokeColor
            rect.strokeWidth = self.strokeWidth
            rect.fillColor = None
            group.add(rect)
        return group
Exemple #3
0
class Grid(Widget):
    """This makes a rectangular grid of equidistant stripes.

    The grid contains an outer border rectangle, and stripes
    inside which can be drawn with lines and/or as solid tiles.
    The drawing order is: outer rectangle, then lines and tiles.

    The stripes' width is indicated as 'delta'. The sequence of
    stripes can have an offset named 'delta0'. Both values need
    to be positive!
    """

    _attrMap = AttrMap(
        x = AttrMapValue(isNumber, desc="The grid's lower-left x position."),
        y = AttrMapValue(isNumber, desc="The grid's lower-left y position."),
        width = AttrMapValue(isNumber, desc="The grid's width."),
        height = AttrMapValue(isNumber, desc="The grid's height."),
        orientation = AttrMapValue(OneOf(('vertical', 'horizontal')),
            desc='Determines if stripes are vertical or horizontal.'),
        useLines = AttrMapValue(OneOf((0, 1)),
            desc='Determines if stripes are drawn with lines.'),
        useRects = AttrMapValue(OneOf((0, 1)),
            desc='Determines if stripes are drawn with solid rectangles.'),
        delta = AttrMapValue(isNumber,
            desc='Determines the width/height of the stripes.'),
        delta0 = AttrMapValue(isNumber,
            desc='Determines the stripes initial width/height offset.'),
        deltaSteps = AttrMapValue(isListOfNumbers,
            desc='List of deltas to be used cyclically.'),
        stripeColors = AttrMapValue(isListOfColors,
            desc='Colors applied cyclically in the right or upper direction.'),
        fillColor = AttrMapValue(isColorOrNone,
            desc='Background color for entire rectangle.'),
        strokeColor = AttrMapValue(isColorOrNone,
            desc='Color used for lines.'),
        strokeWidth = AttrMapValue(isNumber,
            desc='Width used for lines.'),
        rectStrokeColor = AttrMapValue(isColorOrNone, desc='Color for outer rect stroke.'),
        rectStrokeWidth = AttrMapValue(isNumberOrNone, desc='Width for outer rect stroke.'),
        )

    def __init__(self):
        self.x = 0
        self.y = 0
        self.width = 100
        self.height = 100
        self.orientation = 'vertical'
        self.useLines = 0
        self.useRects = 1
        self.delta = 20
        self.delta0 = 0
        self.deltaSteps = []
        self.fillColor = colors.white
        self.stripeColors = [colors.red, colors.green, colors.blue]
        self.strokeColor = colors.black
        self.strokeWidth = 2


    def demo(self):
        D = Drawing(100, 100)

        g = Grid()
        D.add(g)

        return D

    def makeOuterRect(self):
        strokeColor = getattr(self,'rectStrokeColor',self.strokeColor)
        strokeWidth = getattr(self,'rectStrokeWidth',self.strokeWidth)
        if self.fillColor or (strokeColor and strokeWidth):
            rect = Rect(self.x, self.y, self.width, self.height)
            rect.fillColor = self.fillColor
            rect.strokeColor = strokeColor
            rect.strokeWidth = strokeWidth
            return rect
        else:
            return None

    def makeLinePosList(self, start, isX=0):
        "Returns a list of positions where to place lines."

        w, h = self.width, self.height
        if isX:
            length = w
        else:
            length = h
        if self.deltaSteps:
            r = [start + self.delta0]
            i = 0
            while 1:
                if r[-1] > start + length:
                    del r[-1]
                    break
                r.append(r[-1] + self.deltaSteps[i % len(self.deltaSteps)])
                i = i + 1
        else:
            r = frange(start + self.delta0, start + length, self.delta)

        r.append(start + length)
        if self.delta0 != 0:
            r.insert(0, start)
        #print 'Grid.makeLinePosList() -> %s' % r
        return r


    def makeInnerLines(self):
        # inner grid lines
        group = Group()

        w, h = self.width, self.height

        if self.useLines == 1:
            if self.orientation == 'vertical':
                r = self.makeLinePosList(self.x, isX=1)
                for x in r:
                    line = Line(x, self.y, x, self.y + h)
                    line.strokeColor = self.strokeColor
                    line.strokeWidth = self.strokeWidth
                    group.add(line)
            elif self.orientation == 'horizontal':
                r = self.makeLinePosList(self.y, isX=0)
                for y in r:
                    line = Line(self.x, y, self.x + w, y)
                    line.strokeColor = self.strokeColor
                    line.strokeWidth = self.strokeWidth
                    group.add(line)

        return group


    def makeInnerTiles(self):
        # inner grid lines
        group = Group()

        w, h = self.width, self.height

        # inner grid stripes (solid rectangles)
        if self.useRects == 1:
            cols = self.stripeColors

            if self.orientation == 'vertical':
                r = self.makeLinePosList(self.x, isX=1)
            elif self.orientation == 'horizontal':
                r = self.makeLinePosList(self.y, isX=0)

            dist = makeDistancesList(r)

            i = 0
            for j in range(len(dist)):
                if self.orientation == 'vertical':
                    x = r[j]
                    stripe = Rect(x, self.y, dist[j], h)
                elif self.orientation == 'horizontal':
                    y = r[j]
                    stripe = Rect(self.x, y, w, dist[j])
                stripe.fillColor = cols[i % len(cols)]
                stripe.strokeColor = None
                group.add(stripe)
                i = i + 1

        return group


    def draw(self):
        # general widget bits
        group = Group()

        group.add(self.makeOuterRect())
        group.add(self.makeInnerTiles())
        group.add(self.makeInnerLines(),name='_gridLines')

        return group
class Label(Widget):
    """A text label to attach to something else, such as a chart axis.

    This allows you to specify an offset, angle and many anchor
    properties relative to the label's origin.  It allows, for example,
    angled multiline axis labels.
    """
    # fairly straight port of Robin Becker's textbox.py to new widgets
    # framework.

    _attrMap = AttrMap(
        x=AttrMapValue(isNumber, desc=''),
        y=AttrMapValue(isNumber, desc=''),
        dx=AttrMapValue(isNumber, desc='delta x - offset'),
        dy=AttrMapValue(isNumber, desc='delta y - offset'),
        angle=AttrMapValue(
            isNumber,
            desc=
            'angle of label: default (0), 90 is vertical, 180 is upside down, etc'
        ),
        boxAnchor=AttrMapValue(isBoxAnchor,
                               desc='anchoring point of the label'),
        boxStrokeColor=AttrMapValue(isColorOrNone,
                                    desc='border color of the box'),
        boxStrokeWidth=AttrMapValue(isNumber, desc='border width'),
        boxFillColor=AttrMapValue(isColorOrNone,
                                  desc='the filling color of the box'),
        boxTarget=AttrMapValue(OneOf('normal', 'anti', 'lo', 'hi'),
                               desc="one of ('normal','anti','lo','hi')"),
        fillColor=AttrMapValue(isColorOrNone, desc='label text color'),
        strokeColor=AttrMapValue(isColorOrNone,
                                 desc='label text border color'),
        strokeWidth=AttrMapValue(isNumber, desc='label text border width'),
        text=AttrMapValue(isString, desc='the actual text to display'),
        fontName=AttrMapValue(isString, desc='the name of the font used'),
        fontSize=AttrMapValue(isNumber, desc='the size of the font'),
        leading=AttrMapValue(isNumberOrNone, desc=''),
        width=AttrMapValue(isNumberOrNone, desc='the width of the label'),
        maxWidth=AttrMapValue(isNumberOrNone,
                              desc='maximum width the label can grow to'),
        height=AttrMapValue(isNumberOrNone, desc='the height of the text'),
        textAnchor=AttrMapValue(
            isTextAnchor,
            desc='the anchoring point of the text inside the label'),
        visible=AttrMapValue(isBoolean,
                             desc="True if the label is to be drawn"),
        topPadding=AttrMapValue(isNumber, desc='padding at top of box'),
        leftPadding=AttrMapValue(isNumber, desc='padding at left of box'),
        rightPadding=AttrMapValue(isNumber, desc='padding at right of box'),
        bottomPadding=AttrMapValue(isNumber, desc='padding at bottom of box'),
        useAscentDescent=AttrMapValue(
            isBoolean,
            desc=
            "If True then the font's Ascent & Descent will be used to compute default heights and baseline."
        ),
        customDrawChanger=AttrMapValue(
            isNoneOrCallable,
            desc=
            "An instance of CustomDrawChanger to modify the behavior at draw time",
            _advancedUsage=1),
    )

    def __init__(self, **kw):
        self._setKeywords(**kw)
        self._setKeywords(
            _text='Multi-Line\nString',
            boxAnchor='c',
            angle=0,
            x=0,
            y=0,
            dx=0,
            dy=0,
            topPadding=0,
            leftPadding=0,
            rightPadding=0,
            bottomPadding=0,
            boxStrokeWidth=0.5,
            boxStrokeColor=None,
            boxTarget='normal',
            strokeColor=None,
            boxFillColor=None,
            leading=None,
            width=None,
            maxWidth=None,
            height=None,
            fillColor=STATE_DEFAULTS['fillColor'],
            fontName=STATE_DEFAULTS['fontName'],
            fontSize=STATE_DEFAULTS['fontSize'],
            strokeWidth=0.1,
            textAnchor='start',
            visible=1,
            useAscentDescent=False,
        )

    def setText(self, text):
        """Set the text property.  May contain embedded newline characters.
        Called by the containing chart or axis."""
        self._text = text

    def setOrigin(self, x, y):
        """Set the origin.  This would be the tick mark or bar top relative to
        which it is defined.  Called by the containing chart or axis."""
        self.x = x
        self.y = y

    def demo(self):
        """This shows a label positioned with its top right corner
        at the top centre of the drawing, and rotated 45 degrees."""

        d = Drawing(200, 100)

        # mark the origin of the label
        d.add(Circle(100, 90, 5, fillColor=colors.green))

        lab = Label()
        lab.setOrigin(100, 90)
        lab.boxAnchor = 'ne'
        lab.angle = 45
        lab.dx = 0
        lab.dy = -20
        lab.boxStrokeColor = colors.green
        lab.setText('Another\nMulti-Line\nString')
        d.add(lab)

        return d

    def _getBoxAnchor(self):
        '''hook for allowing special box anchor effects'''
        ba = self.boxAnchor
        if ba in ('autox', 'autoy'):
            angle = self.angle
            na = (int((angle % 360) / 45.) * 45) % 360
            if not (na % 90):  # we have a right angle case
                da = (angle - na) % 360
                if abs(da) > 5:
                    na = na + (da > 0 and 45 or -45)
            ba = _A2BA[ba[-1]][na]
        return ba

    def computeSize(self):
        # the thing will draw in its own coordinate system
        self._lineWidths = []
        topPadding = self.topPadding
        leftPadding = self.leftPadding
        rightPadding = self.rightPadding
        bottomPadding = self.bottomPadding
        self._lines = simpleSplit(self._text, self.fontName, self.fontSize,
                                  self.maxWidth)
        if not self.width:
            self._width = leftPadding + rightPadding
            if self._lines:
                self._lineWidths = [
                    stringWidth(line, self.fontName, self.fontSize)
                    for line in self._lines
                ]
                self._width += max(self._lineWidths)
        else:
            self._width = self.width
        if self.useAscentDescent:
            self._ascent, self._descent = getAscentDescent(
                self.fontName, self.fontSize)
            self._baselineRatio = self._ascent / (self._ascent - self._descent)
        else:
            self._baselineRatio = 1 / 1.2
        if self.leading:
            self._leading = self.leading
        elif self.useAscentDescent:
            self._leading = self._ascent - self._descent
        else:
            self._leading = self.fontSize * 1.2
        self._height = self.height or (self._leading * len(self._lines) +
                                       topPadding + bottomPadding)
        self._ewidth = (self._width - leftPadding - rightPadding)
        self._eheight = (self._height - topPadding - bottomPadding)
        boxAnchor = self._getBoxAnchor()
        if boxAnchor in ['n', 'ne', 'nw']:
            self._top = -topPadding
        elif boxAnchor in ['s', 'sw', 'se']:
            self._top = self._height - topPadding
        else:
            self._top = 0.5 * self._eheight
        self._bottom = self._top - self._eheight

        if boxAnchor in ['ne', 'e', 'se']:
            self._left = leftPadding - self._width
        elif boxAnchor in ['nw', 'w', 'sw']:
            self._left = leftPadding
        else:
            self._left = -self._ewidth * 0.5
        self._right = self._left + self._ewidth

    def _getTextAnchor(self):
        '''This can be overridden to allow special effects'''
        ta = self.textAnchor
        if ta == 'boxauto': ta = _BA2TA[self._getBoxAnchor()]
        return ta

    def _rawDraw(self):
        _text = self._text
        self._text = _text or ''
        self.computeSize()
        self._text = _text
        g = Group()
        g.translate(self.x + self.dx, self.y + self.dy)
        g.rotate(self.angle)

        y = self._top - self._leading * self._baselineRatio
        textAnchor = self._getTextAnchor()
        if textAnchor == 'start':
            x = self._left
        elif textAnchor == 'middle':
            x = self._left + self._ewidth * 0.5
        else:
            x = self._right

        # paint box behind text just in case they
        # fill it
        if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth):
            g.add(
                Rect(self._left - self.leftPadding,
                     self._bottom - self.bottomPadding,
                     self._width,
                     self._height,
                     strokeColor=self.boxStrokeColor,
                     strokeWidth=self.boxStrokeWidth,
                     fillColor=self.boxFillColor))

        fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize
        strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, self._leading
        svgAttrs = getattr(self, '_svgAttrs', {})
        if strokeColor:
            for line in self._lines:
                s = _text2Path(line, x, y, fontName, fontSize, textAnchor)
                s.fillColor = fillColor
                s.strokeColor = strokeColor
                s.strokeWidth = strokeWidth
                g.add(s)
                y -= leading
        else:
            for line in self._lines:
                s = String(x, y, line, _svgAttrs=svgAttrs)
                s.textAnchor = textAnchor
                s.fontName = fontName
                s.fontSize = fontSize
                s.fillColor = fillColor
                g.add(s)
                y -= leading

        return g

    def draw(self):
        customDrawChanger = getattr(self, 'customDrawChanger', None)
        if customDrawChanger:
            customDrawChanger(True, self)
            try:
                return self._rawDraw()
            finally:
                customDrawChanger(False, self)
        else:
            return self._rawDraw()
Exemple #5
0
class Marker(Widget):
    '''A polymorphic class of markers'''
    _attrMap = AttrMap(
        BASE=Widget,
        kind=AttrMapValue(OneOf(
            None, 'Square', 'Diamond', 'Circle', 'Cross', 'Triangle',
            'StarSix', 'Pentagon', 'Hexagon', 'Heptagon', 'Octagon',
            'StarFive', 'FilledSquare', 'FilledCircle', 'FilledDiamond',
            'FilledCross', 'FilledTriangle', 'FilledStarSix', 'FilledPentagon',
            'FilledHexagon', 'FilledHeptagon', 'FilledOctagon',
            'FilledStarFive', 'Smiley', 'ArrowHead', 'FilledArrowHead'),
                          desc='marker type name'),
        size=AttrMapValue(isNumber, desc='marker size'),
        x=AttrMapValue(isNumber, desc='marker x coordinate'),
        y=AttrMapValue(isNumber, desc='marker y coordinate'),
        dx=AttrMapValue(isNumber, desc='marker x coordinate adjustment'),
        dy=AttrMapValue(isNumber, desc='marker y coordinate adjustment'),
        angle=AttrMapValue(isNumber, desc='marker rotation'),
        fillColor=AttrMapValue(isColorOrNone, desc='marker fill colour'),
        strokeColor=AttrMapValue(isColorOrNone, desc='marker stroke colour'),
        strokeWidth=AttrMapValue(isNumber, desc='marker stroke width'),
        arrowBarbDx=AttrMapValue(isNumber,
                                 desc='arrow only the delta x for the barbs'),
        arrowHeight=AttrMapValue(isNumber, desc='arrow only height'),
    )

    def __init__(self, *args, **kw):
        self.setProperties(kw)
        self._setKeywords(
            kind=None,
            strokeColor=black,
            strokeWidth=0.1,
            fillColor=None,
            size=5,
            x=0,
            y=0,
            dx=0,
            dy=0,
            angle=0,
            arrowBarbDx=-1.25,
            arrowHeight=1.875,
        )

    def clone(self):
        return new.instance(self.__class__, self.__dict__.copy())

    def _Smiley(self):
        x, y = self.x + self.dx, self.y + self.dy
        d = self.size / 2.0
        s = SmileyFace()
        s.fillColor = self.fillColor
        s.strokeWidth = self.strokeWidth
        s.strokeColor = self.strokeColor
        s.x = x - d
        s.y = y - d
        s.size = d * 2
        return s

    def _Square(self):
        x, y = self.x + self.dx, self.y + self.dy
        d = self.size / 2.0
        s = Rect(x - d,
                 y - d,
                 2 * d,
                 2 * d,
                 fillColor=self.fillColor,
                 strokeColor=self.strokeColor,
                 strokeWidth=self.strokeWidth)
        return s

    def _Diamond(self):
        d = self.size / 2.0
        return self._doPolygon((-d, 0, 0, d, d, 0, 0, -d))

    def _Circle(self):
        x, y = self.x + self.dx, self.y + self.dy
        s = Circle(x,
                   y,
                   self.size / 2.0,
                   fillColor=self.fillColor,
                   strokeColor=self.strokeColor,
                   strokeWidth=self.strokeWidth)
        return s

    def _Cross(self):
        x, y = self.x + self.dx, self.y + self.dy
        s = float(self.size)
        h, s = s / 2, s / 6
        return self._doPolygon((-s, -h, -s, -s, -h, -s, -h, s, -s, s, -s, h, s,
                                h, s, s, h, s, h, -s, s, -s, s, -h))

    def _Triangle(self):
        x, y = self.x + self.dx, self.y + self.dy
        r = float(self.size) / 2
        c = 30 * _toradians
        s = sin(30 * _toradians) * r
        c = cos(c) * r
        return self._doPolygon((0, r, -c, -s, c, -s))

    def _StarSix(self):
        r = float(self.size) / 2
        c = 30 * _toradians
        s = sin(c) * r
        c = cos(c) * r
        z = s / 2
        g = c / 2
        return self._doPolygon((0, r, -z, s, -c, s, -s, 0, -c, -s, -z, -s, 0,
                                -r, z, -s, c, -s, s, 0, c, s, z, s))

    def _StarFive(self):
        R = float(self.size) / 2
        r = R * sin(18 * _toradians) / cos(36 * _toradians)
        P = []
        angle = 90
        for i in xrange(5):
            for radius in R, r:
                theta = angle * _toradians
                P.append(radius * cos(theta))
                P.append(radius * sin(theta))
                angle = angle + 36
        return self._doPolygon(P)

    def _Pentagon(self):
        return self._doNgon(5)

    def _Hexagon(self):
        return self._doNgon(6)

    def _Heptagon(self):
        return self._doNgon(7)

    def _Octagon(self):
        return self._doNgon(8)

    def _ArrowHead(self):
        s = self.size
        h = self.arrowHeight
        b = self.arrowBarbDx
        return self._doPolygon((0, 0, b, -h, s, 0, b, h))

    def _doPolygon(self, P):
        x, y = self.x + self.dx, self.y + self.dy
        if x or y:
            P = map(lambda i, P=P, A=[x, y]: P[i] + A[i & 1], range(len(P)))
        return Polygon(P,
                       strokeWidth=self.strokeWidth,
                       strokeColor=self.strokeColor,
                       fillColor=self.fillColor)

    def _doFill(self):
        old = self.fillColor
        if old is None:
            self.fillColor = self.strokeColor
        r = (self.kind and getattr(self, '_' + self.kind[6:]) or Group)()
        self.fillColor = old
        return r

    def _doNgon(self, n):
        P = []
        size = float(self.size) / 2
        for i in xrange(n):
            r = (2. * i / n + 0.5) * pi
            P.append(size * cos(r))
            P.append(size * sin(r))
        return self._doPolygon(P)

    _FilledCircle = _doFill
    _FilledSquare = _doFill
    _FilledDiamond = _doFill
    _FilledCross = _doFill
    _FilledTriangle = _doFill
    _FilledStarSix = _doFill
    _FilledPentagon = _doFill
    _FilledHexagon = _doFill
    _FilledHeptagon = _doFill
    _FilledOctagon = _doFill
    _FilledStarFive = _doFill
    _FilledArrowHead = _doFill

    def draw(self):
        if self.kind:
            m = getattr(self, '_' + self.kind)
            if self.angle:
                _x, _dx, _y, _dy = self.x, self.dx, self.y, self.dy
                self.x, self.dx, self.y, self.dy = 0, 0, 0, 0
                try:
                    m = m()
                finally:
                    self.x, self.dx, self.y, self.dy = _x, _dx, _y, _dy
                if not isinstance(m, Group):
                    _m, m = m, Group()
                    m.add(_m)
                if self.angle: m.rotate(self.angle)
                x, y = _x + _dx, _y + _dy
                if x or y: m.shift(x, y)
            else:
                m = m()
        else:
            m = Group()
        return m
    def decorate(self, l, L):
        chart, g, rowNo, colNo, x, y, width, height, x00, y00, x0, y0 = l._callOutInfo
        L.setText(chart.categoryAxis.categoryNames[colNo])
        g.add(L)

    def __call__(self, l):
        from copy import deepcopy
        L = Label()
        for a, v in self.__dict__.items():
            if v is None: v = getattr(l, a, None)
            setattr(L, a, v)
        self.decorate(l, L)


isOffsetMode = OneOf('high', 'low', 'bar', 'axis')


class LabelOffset(PropHolder):
    _attrMap = AttrMap(
        posMode=AttrMapValue(isOffsetMode, desc="Where to base +ve offset"),
        pos=AttrMapValue(isNumber, desc='Value for positive elements'),
        negMode=AttrMapValue(isOffsetMode, desc="Where to base -ve offset"),
        neg=AttrMapValue(isNumber, desc='Value for negative elements'),
    )

    def __init__(self):
        self.posMode = self.negMode = 'axis'
        self.pos = self.neg = 0

    def _getValue(self, chart, val):
Exemple #7
0
class SpiderChart(PlotArea):
    _attrMap = AttrMap(
        BASE=PlotArea,
        data=AttrMapValue(
            None, desc='Data to be plotted, list of (lists of) numbers.'),
        labels=AttrMapValue(
            isListOfStringsOrNone,
            desc="optional list of labels to use for each data point"),
        startAngle=AttrMapValue(
            isNumber,
            desc="angle of first slice; like the compass, 0 is due North"),
        direction=AttrMapValue(OneOf('clockwise', 'anticlockwise'),
                               desc="'clockwise' or 'anticlockwise'"),
        strands=AttrMapValue(None,
                             desc="collection of strand descriptor objects"),
        spokes=AttrMapValue(None,
                            desc="collection of spoke descriptor objects"),
        strandLabels=AttrMapValue(
            None, desc="collection of strand label descriptor objects"),
        spokeLabels=AttrMapValue(
            None, desc="collection of spoke label descriptor objects"),
    )

    def makeSwatchSample(self, rowNo, x, y, width, height):
        baseStyle = self.strands
        styleIdx = rowNo % len(baseStyle)
        style = baseStyle[styleIdx]
        strokeColor = getattr(style, 'strokeColor',
                              getattr(baseStyle, 'strokeColor', None))
        fillColor = getattr(style, 'fillColor',
                            getattr(baseStyle, 'fillColor', None))
        strokeDashArray = getattr(style, 'strokeDashArray',
                                  getattr(baseStyle, 'strokeDashArray', None))
        strokeWidth = getattr(style, 'strokeWidth',
                              getattr(baseStyle, 'strokeWidth', 0))
        symbol = getattr(style, 'symbol', getattr(baseStyle, 'symbol', None))
        ym = y + height / 2.0
        if fillColor is None and strokeColor is not None and strokeWidth > 0:
            bg = Line(x,
                      ym,
                      x + width,
                      ym,
                      strokeWidth=strokeWidth,
                      strokeColor=strokeColor,
                      strokeDashArray=strokeDashArray)
        elif fillColor is not None:
            bg = Rect(x,
                      y,
                      width,
                      height,
                      strokeWidth=strokeWidth,
                      strokeColor=strokeColor,
                      strokeDashArray=strokeDashArray,
                      fillColor=fillColor)
        else:
            bg = None
        if symbol:
            symbol = uSymbol2Symbol(symbol, x + width / 2., ym, color)
            if bg:
                g = Group()
                g.add(bg)
                g.add(symbol)
                return g
        return symbol or bg

    def getSeriesName(self, i, default=None):
        '''return series name i or default'''
        return _objStr(getattr(self.strands[i], 'name', default))

    def __init__(self):
        PlotArea.__init__(self)

        self.data = [[10, 12, 14, 16, 14, 12], [6, 8, 10, 12, 9, 11]]
        self.labels = None  # or list of strings
        self.labels = ['a', 'b', 'c', 'd', 'e', 'f']
        self.startAngle = 90
        self.direction = "clockwise"

        self.strands = TypedPropertyCollection(StrandProperty)
        self.spokes = TypedPropertyCollection(SpokeProperty)
        self.spokeLabels = TypedPropertyCollection(SpokeLabel)
        self.spokeLabels._text = None
        self.strandLabels = TypedPropertyCollection(StrandLabel)
        self.x = 10
        self.y = 10
        self.width = 180
        self.height = 180

    def demo(self):
        d = Drawing(200, 200)
        d.add(SpiderChart())
        return d

    def normalizeData(self, outer=0.0):
        """Turns data into normalized ones where each datum is < 1.0,
        and 1.0 = maximum radius.  Adds 10% at outside edge by default"""
        data = self.data
        assert min(list(map(
            min, data))) >= 0, "Cannot do spider plots of negative numbers!"
        norm = max(list(map(max, data)))
        norm *= (1.0 + outer)
        if norm < 1e-9: norm = 1.0
        self._norm = norm
        return [[e / norm for e in row] for row in data]

    def _innerDrawLabel(self,
                        sty,
                        radius,
                        cx,
                        cy,
                        angle,
                        car,
                        sar,
                        labelClass=StrandLabel):
        "Draw a label for a given item in the list."
        fmt = sty.format
        value = radius * self._norm
        if not fmt:
            text = None
        elif isinstance(fmt, str):
            if fmt == 'values':
                text = sty._text
            else:
                text = fmt % value
        elif hasattr(fmt, '__call__'):
            text = fmt(value)
        else:
            raise ValueError(
                "Unknown formatter type %s, expected string or function" % fmt)

        if text:
            dR = sty.dR
            if dR:
                radius += dR / self._radius
            L = _setupLabel(labelClass, text, radius, cx, cy, angle, car, sar,
                            sty)
            if dR < 0: L._anti = 1
        else:
            L = None
        return L

    def draw(self):
        # normalize slice data
        g = self.makeBackground() or Group()

        xradius = self.width / 2.0
        yradius = self.height / 2.0
        self._radius = radius = min(xradius, yradius)
        cx = self.x + xradius
        cy = self.y + yradius

        data = self.normalizeData()

        self._seriesCount = len(data)
        n = len(data[0])

        #labels
        if self.labels is None:
            labels = [''] * n
        else:
            labels = self.labels
            #there's no point in raising errors for less than enough errors if
            #we silently create all for the extreme case of no labels.
            i = n - len(labels)
            if i > 0:
                labels = labels + [''] * i

        S = []
        STRANDS = []
        STRANDAREAS = []
        syms = []
        labs = []
        csa = []
        angle = self.startAngle * pi / 180
        direction = self.direction == "clockwise" and -1 or 1
        angleBetween = direction * (2 * pi) / float(n)
        spokes = self.spokes
        spokeLabels = self.spokeLabels
        for i in range(n):
            car = cos(angle) * radius
            sar = sin(angle) * radius
            csa.append((car, sar, angle))
            si = self.spokes[i]
            if si.visible:
                spoke = Line(cx,
                             cy,
                             cx + car,
                             cy + sar,
                             strokeWidth=si.strokeWidth,
                             strokeColor=si.strokeColor,
                             strokeDashArray=si.strokeDashArray)
            S.append(spoke)
            sli = spokeLabels[i]
            text = sli._text
            if not text: text = labels[i]
            if text:
                S.append(
                    _setupLabel(WedgeLabel, text, si.labelRadius, cx, cy,
                                angle, car, sar, sli))
            angle += angleBetween

        # now plot the polygons
        rowIdx = 0
        strands = self.strands
        strandLabels = self.strandLabels
        for row in data:
            # series plot
            rsty = strands[rowIdx]
            points = []
            car, sar = csa[-1][:2]
            r = row[-1]
            points.append(cx + car * r)
            points.append(cy + sar * r)
            for i in range(n):
                car, sar, angle = csa[i]
                r = row[i]
                points.append(cx + car * r)
                points.append(cy + sar * r)
                L = self._innerDrawLabel(strandLabels[(rowIdx, i)],
                                         r,
                                         cx,
                                         cy,
                                         angle,
                                         car,
                                         sar,
                                         labelClass=StrandLabel)
                if L: labs.append(L)
                sty = strands[(rowIdx, i)]
                uSymbol = sty.symbol

                # put in a marker, if it needs one
                if uSymbol:
                    s_x = cx + car * r
                    s_y = cy + sar * r
                    s_fillColor = sty.fillColor
                    s_strokeColor = sty.strokeColor
                    s_strokeWidth = sty.strokeWidth
                    s_angle = 0
                    s_size = sty.symbolSize
                    if type(uSymbol) is type(''):
                        symbol = makeMarker(
                            uSymbol,
                            size=s_size,
                            x=s_x,
                            y=s_y,
                            fillColor=s_fillColor,
                            strokeColor=s_strokeColor,
                            strokeWidth=s_strokeWidth,
                            angle=s_angle,
                        )
                    else:
                        symbol = uSymbol2Symbol(uSymbol, s_x, s_y, s_fillColor)
                        for k, v in (
                            ('size', s_size),
                            ('fillColor', s_fillColor),
                            ('x', s_x),
                            ('y', s_y),
                            ('strokeColor', s_strokeColor),
                            ('strokeWidth', s_strokeWidth),
                            ('angle', s_angle),
                        ):
                            if getattr(symbol, k, None) is None:
                                try:
                                    setattr(symbol, k, v)
                                except:
                                    pass
                    syms.append(symbol)

            # make up the 'strand'
            if rsty.fillColor:
                strand = Polygon(points)
                strand.fillColor = rsty.fillColor
                strand.strokeColor = None
                strand.strokeWidth = 0
                STRANDAREAS.append(strand)
            if rsty.strokeColor and rsty.strokeWidth:
                strand = PolyLine(points)
                strand.strokeColor = rsty.strokeColor
                strand.strokeWidth = rsty.strokeWidth
                strand.strokeDashArray = rsty.strokeDashArray
                STRANDS.append(strand)
            rowIdx += 1

        for s in (STRANDAREAS + STRANDS + syms + S + labs):
            g.add(s)
        return g
Exemple #8
0
class Doughnut(AbstractPieChart):
    _attrMap = AttrMap(
        x=AttrMapValue(isNumber,
                       desc='X position of the chart within its container.'),
        y=AttrMapValue(isNumber,
                       desc='Y position of the chart within its container.'),
        width=AttrMapValue(
            isNumber,
            desc='width of doughnut bounding box. Need not be same as width.'),
        height=AttrMapValue(
            isNumber,
            desc='height of doughnut bounding box.  Need not be same as height.'
        ),
        data=AttrMapValue(
            None,
            desc='list of numbers defining sector sizes; need not sum to 1'),
        labels=AttrMapValue(
            isListOfStringsOrNone,
            desc="optional list of labels to use for each data point"),
        startAngle=AttrMapValue(
            isNumber,
            desc="angle of first slice; like the compass, 0 is due North"),
        direction=AttrMapValue(OneOf('clockwise', 'anticlockwise'),
                               desc="'clockwise' or 'anticlockwise'"),
        slices=AttrMapValue(None,
                            desc="collection of sector descriptor objects"),
        simpleLabels=AttrMapValue(
            isBoolean,
            desc="If true(default) use String not super duper WedgeLabel"),
        # advanced usage
        checkLabelOverlap=AttrMapValue(
            isBoolean,
            desc=
            "If true check and attempt to fix\n standard label overlaps(default off)",
            advancedUsage=1),
        sideLabels=AttrMapValue(
            isBoolean,
            desc=
            "If true attempt to make chart with labels along side and pointers",
            advancedUsage=1))

    def __init__(self):
        self.x = 0
        self.y = 0
        self.width = 100
        self.height = 100
        self.data = [1, 1]
        self.labels = None  # or list of strings
        self.startAngle = 90
        self.direction = "clockwise"
        self.simpleLabels = 1
        self.checkLabelOverlap = 0
        self.sideLabels = 0

        self.slices = TypedPropertyCollection(SectorProperties)
        self.slices[0].fillColor = colors.darkcyan
        self.slices[1].fillColor = colors.blueviolet
        self.slices[2].fillColor = colors.blue
        self.slices[3].fillColor = colors.cyan
        self.slices[4].fillColor = colors.pink
        self.slices[5].fillColor = colors.magenta
        self.slices[6].fillColor = colors.yellow

    def demo(self):
        d = Drawing(200, 100)

        dn = Doughnut()
        dn.x = 50
        dn.y = 10
        dn.width = 100
        dn.height = 80
        dn.data = [10, 20, 30, 40, 50, 60]
        dn.labels = ['a', 'b', 'c', 'd', 'e', 'f']

        dn.slices.strokeWidth = 0.5
        dn.slices[3].popout = 10
        dn.slices[3].strokeWidth = 2
        dn.slices[3].strokeDashArray = [2, 2]
        dn.slices[3].labelRadius = 1.75
        dn.slices[3].fontColor = colors.red
        dn.slices[0].fillColor = colors.darkcyan
        dn.slices[1].fillColor = colors.blueviolet
        dn.slices[2].fillColor = colors.blue
        dn.slices[3].fillColor = colors.cyan
        dn.slices[4].fillColor = colors.aquamarine
        dn.slices[5].fillColor = colors.cadetblue
        dn.slices[6].fillColor = colors.lightcoral

        d.add(dn)
        return d

    def normalizeData(self, data=None):
        from operator import add
        sum = float(reduce(add, data, 0))
        return abs(sum) >= 1e-8 and list(
            map(lambda x, f=360. / sum: f * x, data)) or len(data) * [0]

    def makeSectors(self):
        # normalize slice data
        if isinstance(self.data,
                      (list, tuple)) and isinstance(self.data[0],
                                                    (list, tuple)):
            #it's a nested list, more than one sequence
            normData = []
            n = []
            for l in self.data:
                t = self.normalizeData(l)
                normData.append(t)
                n.append(len(t))
            self._seriesCount = max(n)
        else:
            normData = self.normalizeData(self.data)
            n = len(normData)
            self._seriesCount = n

        #labels
        checkLabelOverlap = self.checkLabelOverlap
        L = []
        L_add = L.append

        if self.labels is None:
            labels = []
            if not isinstance(n, (list, tuple)):
                labels = [''] * n
            else:
                for m in n:
                    labels = list(labels) + [''] * m
        else:
            labels = self.labels
            #there's no point in raising errors for less than enough labels if
            #we silently create all for the extreme case of no labels.
            if not isinstance(n, (list, tuple)):
                i = n - len(labels)
                if i > 0:
                    labels = list(labels) + [''] * i
            else:
                tlab = 0
                for m in n:
                    tlab += m
                i = tlab - len(labels)
                if i > 0:
                    labels = list(labels) + [''] * i

        xradius = self.width / 2.0
        yradius = self.height / 2.0
        centerx = self.x + xradius
        centery = self.y + yradius

        if self.direction == "anticlockwise":
            whichWay = 1
        else:
            whichWay = -1

        g = Group()

        startAngle = self.startAngle  #% 360
        styleCount = len(self.slices)
        if isinstance(self.data[0], (list, tuple)):
            #multi-series doughnut
            ndata = len(self.data)
            yir = (yradius / 2.5) / ndata
            xir = (xradius / 2.5) / ndata
            ydr = (yradius - yir) / ndata
            xdr = (xradius - xir) / ndata
            for sn, series in enumerate(normData):
                for i, angle in enumerate(series):
                    endAngle = (startAngle + (angle * whichWay))  #% 360
                    if abs(startAngle - endAngle) < 1e-5:
                        startAngle = endAngle
                        continue
                    if startAngle < endAngle:
                        a1 = startAngle
                        a2 = endAngle
                    else:
                        a1 = endAngle
                        a2 = startAngle
                    startAngle = endAngle

                    #if we didn't use %stylecount here we'd end up with the later sectors
                    #all having the default style
                    sectorStyle = self.slices[i % styleCount]

                    # is it a popout?
                    cx, cy = centerx, centery
                    if sectorStyle.popout != 0:
                        # pop out the sector
                        averageAngle = (a1 + a2) / 2.0
                        aveAngleRadians = averageAngle * pi / 180.0
                        popdistance = sectorStyle.popout
                        cx = centerx + popdistance * cos(aveAngleRadians)
                        cy = centery + popdistance * sin(aveAngleRadians)

                    yr1 = yir + sn * ydr
                    yr = yr1 + ydr
                    xr1 = xir + sn * xdr
                    xr = xr1 + xdr
                    if isinstance(n, (list, tuple)):
                        theSector = Wedge(cx,
                                          cy,
                                          xr,
                                          a1,
                                          a2,
                                          yradius=yr,
                                          radius1=xr1,
                                          yradius1=yr1)
                    else:
                        theSector = Wedge(cx,
                                          cy,
                                          xr,
                                          a1,
                                          a2,
                                          yradius=yr,
                                          radius1=xr1,
                                          yradius1=yr1,
                                          annular=True)

                    theSector.fillColor = sectorStyle.fillColor
                    theSector.strokeColor = sectorStyle.strokeColor
                    theSector.strokeWidth = sectorStyle.strokeWidth
                    theSector.strokeDashArray = sectorStyle.strokeDashArray

                    g.add(theSector)

                    if sn == 0:
                        text = self.getSeriesName(i, '')
                        if text:
                            averageAngle = (a1 + a2) / 2.0
                            aveAngleRadians = averageAngle * pi / 180.0
                            labelRadius = sectorStyle.labelRadius
                            rx = xradius * labelRadius
                            ry = yradius * labelRadius
                            labelX = centerx + (0.5 * self.width *
                                                cos(aveAngleRadians) *
                                                labelRadius)
                            labelY = centery + (0.5 * self.height *
                                                sin(aveAngleRadians) *
                                                labelRadius)
                            l = _addWedgeLabel(self, text, averageAngle,
                                               labelX, labelY, sectorStyle)
                            if checkLabelOverlap:
                                l._origdata = {
                                    'x': labelX,
                                    'y': labelY,
                                    'angle': averageAngle,
                                    'rx': rx,
                                    'ry': ry,
                                    'cx': cx,
                                    'cy': cy,
                                    'bounds': l.getBounds(),
                                }
                            L_add(l)

        else:
            #single series doughnut
            yir = yradius / 2.5
            xir = xradius / 2.5
            for i, angle in enumerate(normData):
                endAngle = (startAngle + (angle * whichWay))  #% 360
                if abs(startAngle - endAngle) < 1e-5:
                    startAngle = endAngle
                    continue
                if startAngle < endAngle:
                    a1 = startAngle
                    a2 = endAngle
                else:
                    a1 = endAngle
                    a2 = startAngle
                startAngle = endAngle

                #if we didn't use %stylecount here we'd end up with the later sectors
                #all having the default style
                sectorStyle = self.slices[i % styleCount]

                # is it a popout?
                cx, cy = centerx, centery
                if sectorStyle.popout != 0:
                    # pop out the sector
                    averageAngle = (a1 + a2) / 2.0
                    aveAngleRadians = averageAngle * pi / 180.0
                    popdistance = sectorStyle.popout
                    cx = centerx + popdistance * cos(aveAngleRadians)
                    cy = centery + popdistance * sin(aveAngleRadians)

                if n > 1:
                    theSector = Wedge(cx,
                                      cy,
                                      xradius,
                                      a1,
                                      a2,
                                      yradius=yradius,
                                      radius1=xir,
                                      yradius1=yir)
                elif n == 1:
                    theSector = Wedge(cx,
                                      cy,
                                      xradius,
                                      a1,
                                      a2,
                                      yradius=yradius,
                                      radius1=xir,
                                      yradius1=yir,
                                      annular=True)

                theSector.fillColor = sectorStyle.fillColor
                theSector.strokeColor = sectorStyle.strokeColor
                theSector.strokeWidth = sectorStyle.strokeWidth
                theSector.strokeDashArray = sectorStyle.strokeDashArray

                g.add(theSector)

                # now draw a label
                if labels[i] != "":
                    averageAngle = (a1 + a2) / 2.0
                    aveAngleRadians = averageAngle * pi / 180.0
                    labelRadius = sectorStyle.labelRadius
                    labelX = centerx + (0.5 * self.width *
                                        cos(aveAngleRadians) * labelRadius)
                    labelY = centery + (0.5 * self.height *
                                        sin(aveAngleRadians) * labelRadius)
                    rx = xradius * labelRadius
                    ry = yradius * labelRadius
                    l = _addWedgeLabel(self, labels[i], averageAngle, labelX,
                                       labelY, sectorStyle)
                    if checkLabelOverlap:
                        l._origdata = {
                            'x': labelX,
                            'y': labelY,
                            'angle': averageAngle,
                            'rx': rx,
                            'ry': ry,
                            'cx': cx,
                            'cy': cy,
                            'bounds': l.getBounds(),
                        }
                    L_add(l)

        if checkLabelOverlap and L:
            fixLabelOverlaps(L)

        for l in L:
            g.add(l)

        return g

    def draw(self):
        g = Group()
        g.add(self.makeSectors())
        return g
Exemple #9
0
class Doughnut(AbstractPieChart):
    _attrMap = AttrMap(
        x=AttrMapValue(isNumber,
                       desc='X position of the chart within its container.'),
        y=AttrMapValue(isNumber,
                       desc='Y position of the chart within its container.'),
        width=AttrMapValue(
            isNumber,
            desc='width of doughnut bounding box. Need not be same as width.'),
        height=AttrMapValue(
            isNumber,
            desc='height of doughnut bounding box.  Need not be same as height.'
        ),
        data=AttrMapValue(
            None,
            desc='list of numbers defining sector sizes; need not sum to 1'),
        labels=AttrMapValue(
            isListOfStringsOrNone,
            desc="optional list of labels to use for each data point"),
        startAngle=AttrMapValue(
            isNumber,
            desc="angle of first slice; like the compass, 0 is due North"),
        direction=AttrMapValue(OneOf('clockwise', 'anticlockwise'),
                               desc="'clockwise' or 'anticlockwise'"),
        slices=AttrMapValue(None,
                            desc="collection of sector descriptor objects"),
        simpleLabels=AttrMapValue(
            isBoolean,
            desc="If true(default) use String not super duper WedgeLabel"),
    )

    def __init__(self):
        self.x = 0
        self.y = 0
        self.width = 100
        self.height = 100
        self.data = [1, 1]
        self.labels = None  # or list of strings
        self.startAngle = 90
        self.direction = "clockwise"
        self.simpleLabels = 1

        self.slices = TypedPropertyCollection(SectorProperties)
        self.slices[0].fillColor = colors.darkcyan
        self.slices[1].fillColor = colors.blueviolet
        self.slices[2].fillColor = colors.blue
        self.slices[3].fillColor = colors.cyan

    def demo(self):
        d = Drawing(200, 100)

        dn = Doughnut()
        dn.x = 50
        dn.y = 10
        dn.width = 100
        dn.height = 80
        dn.data = [10, 20, 30, 40, 50, 60]
        dn.labels = ['a', 'b', 'c', 'd', 'e', 'f']

        dn.slices.strokeWidth = 0.5
        dn.slices[3].popout = 10
        dn.slices[3].strokeWidth = 2
        dn.slices[3].strokeDashArray = [2, 2]
        dn.slices[3].labelRadius = 1.75
        dn.slices[3].fontColor = colors.red
        dn.slices[0].fillColor = colors.darkcyan
        dn.slices[1].fillColor = colors.blueviolet
        dn.slices[2].fillColor = colors.blue
        dn.slices[3].fillColor = colors.cyan
        dn.slices[4].fillColor = colors.aquamarine
        dn.slices[5].fillColor = colors.cadetblue
        dn.slices[6].fillColor = colors.lightcoral

        d.add(dn)
        return d

    def normalizeData(self, data=None):
        from operator import add
        sum = float(reduce(add, data, 0))
        return abs(sum) >= 1e-8 and map(lambda x, f=360. / sum: f * x,
                                        data) or len(data) * [0]

    def makeSectors(self):
        # normalize slice data
        if type(self.data) in (ListType, TupleType) and type(
                self.data[0]) in (ListType, TupleType):
            #it's a nested list, more than one sequence
            normData = []
            n = []
            for l in self.data:
                t = self.normalizeData(l)
                normData.append(t)
                n.append(len(t))
            self._seriesCount = max(n)
        else:
            normData = self.normalizeData(self.data)
            n = len(normData)
            self._seriesCount = n

        #labels
        if self.labels is None:
            labels = []
            if type(n) not in (ListType, TupleType):
                labels = [''] * n
            else:
                for m in n:
                    labels = list(labels) + [''] * m
        else:
            labels = self.labels
            #there's no point in raising errors for less than enough labels if
            #we silently create all for the extreme case of no labels.
            if type(n) not in (ListType, TupleType):
                i = n - len(labels)
                if i > 0:
                    labels = list(labels) + [''] * i
            else:
                tlab = 0
                for m in n:
                    tlab += m
                i = tlab - len(labels)
                if i > 0:
                    labels = list(labels) + [''] * i

        xradius = self.width / 2.0
        yradius = self.height / 2.0
        centerx = self.x + xradius
        centery = self.y + yradius

        if self.direction == "anticlockwise":
            whichWay = 1
        else:
            whichWay = -1

        g = Group()

        startAngle = self.startAngle  #% 360
        styleCount = len(self.slices)
        if type(self.data[0]) in (ListType, TupleType):
            #multi-series doughnut
            iradius = (self.height / 5.0) / len(self.data)
            for sn, series in enumerate(normData):
                for i, angle in enumerate(series):
                    endAngle = (startAngle + (angle * whichWay))  #% 360
                    if abs(startAngle - endAngle) < 1e-5:
                        startAngle = endAngle
                        continue
                    if startAngle < endAngle:
                        a1 = startAngle
                        a2 = endAngle
                    else:
                        a1 = endAngle
                        a2 = startAngle
                    startAngle = endAngle

                    #if we didn't use %stylecount here we'd end up with the later sectors
                    #all having the default style
                    sectorStyle = self.slices[i % styleCount]

                    # is it a popout?
                    cx, cy = centerx, centery
                    if sectorStyle.popout != 0:
                        # pop out the sector
                        averageAngle = (a1 + a2) / 2.0
                        aveAngleRadians = averageAngle * pi / 180.0
                        popdistance = sectorStyle.popout
                        cx = centerx + popdistance * cos(aveAngleRadians)
                        cy = centery + popdistance * sin(aveAngleRadians)

                    if type(n) in (ListType, TupleType):
                        theSector = Wedge(
                            cx,
                            cy,
                            xradius + (sn * iradius) - iradius,
                            a1,
                            a2,
                            yradius=yradius + (sn * iradius) - iradius,
                            radius1=yradius + (sn * iradius) - (2 * iradius))
                    else:
                        theSector = Wedge(cx,
                                          cy,
                                          xradius,
                                          a1,
                                          a2,
                                          yradius=yradius,
                                          radius1=iradius)

                    theSector.fillColor = sectorStyle.fillColor
                    theSector.strokeColor = sectorStyle.strokeColor
                    theSector.strokeWidth = sectorStyle.strokeWidth
                    theSector.strokeDashArray = sectorStyle.strokeDashArray

                    g.add(theSector)

                    text = self.getSeriesName(i, '')
                    if text:
                        averageAngle = (a1 + a2) / 2.0
                        aveAngleRadians = averageAngle * pi / 180.0
                        labelRadius = sectorStyle.labelRadius
                        labelX = centerx + (0.5 * self.width *
                                            cos(aveAngleRadians) * labelRadius)
                        labelY = centery + (0.5 * self.height *
                                            sin(aveAngleRadians) * labelRadius)
                        g.add(
                            _addWedgeLabel(self, text, averageAngle, labelX,
                                           labelY, sectorStyle))

        else:
            #single series doughnut
            iradius = self.height / 5.0
            for i, angle in enumerate(normData):
                endAngle = (startAngle + (angle * whichWay))  #% 360
                if abs(startAngle - endAngle) < 1e-5:
                    startAngle = endAngle
                    continue
                if startAngle < endAngle:
                    a1 = startAngle
                    a2 = endAngle
                else:
                    a1 = endAngle
                    a2 = startAngle
                startAngle = endAngle

                #if we didn't use %stylecount here we'd end up with the later sectors
                #all having the default style
                sectorStyle = self.slices[i % styleCount]

                # is it a popout?
                cx, cy = centerx, centery
                if sectorStyle.popout != 0:
                    # pop out the sector
                    averageAngle = (a1 + a2) / 2.0
                    aveAngleRadians = averageAngle * pi / 180.0
                    popdistance = sectorStyle.popout
                    cx = centerx + popdistance * cos(aveAngleRadians)
                    cy = centery + popdistance * sin(aveAngleRadians)

                if n > 1:
                    theSector = Wedge(cx,
                                      cy,
                                      xradius,
                                      a1,
                                      a2,
                                      yradius=yradius,
                                      radius1=iradius)
                elif n == 1:
                    theSector = Wedge(cx,
                                      cy,
                                      xradius,
                                      a1,
                                      a2,
                                      yradius=yradius,
                                      iradius=iradius)

                theSector.fillColor = sectorStyle.fillColor
                theSector.strokeColor = sectorStyle.strokeColor
                theSector.strokeWidth = sectorStyle.strokeWidth
                theSector.strokeDashArray = sectorStyle.strokeDashArray

                g.add(theSector)

                # now draw a label
                if labels[i] != "":
                    averageAngle = (a1 + a2) / 2.0
                    aveAngleRadians = averageAngle * pi / 180.0
                    labelRadius = sectorStyle.labelRadius
                    labelX = centerx + (0.5 * self.width *
                                        cos(aveAngleRadians) * labelRadius)
                    labelY = centery + (0.5 * self.height *
                                        sin(aveAngleRadians) * labelRadius)

                    theLabel = String(labelX, labelY, labels[i])
                    theLabel.textAnchor = "middle"
                    theLabel.fontSize = sectorStyle.fontSize
                    theLabel.fontName = sectorStyle.fontName
                    theLabel.fillColor = sectorStyle.fontColor

                    g.add(theLabel)

        return g

    def draw(self):
        g = Group()
        g.add(self.makeSectors())
        return g
Exemple #10
0
class Pie(AbstractPieChart):
    _attrMap = AttrMap(BASE=AbstractPieChart,
        data = AttrMapValue(isListOfNumbers, desc='list of numbers defining wedge sizes; need not sum to 1'),
        labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"),
        startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"),
        direction = AttrMapValue(OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"),
        slices = AttrMapValue(None, desc="collection of wedge descriptor objects"),
        simpleLabels = AttrMapValue(isBoolean, desc="If true(default) use String not super duper WedgeLabel"),
        other_threshold = AttrMapValue(isNumber, desc='A value for doing threshholding, not used yet.'),
        checkLabelOverlap = AttrMapValue(isBoolean, desc="If true check and attempt to fix standard label overlaps(default off)"),
        pointerLabelMode = AttrMapValue(OneOf(None,'LeftRight','LeftAndRight'), desc=""),
        sameRadii = AttrMapValue(isBoolean, desc="If true make x/y radii the same(default off)"),
        orderMode = AttrMapValue(OneOf('fixed','alternate')),
        xradius = AttrMapValue(isNumberOrNone, desc="X direction Radius"),
        yradius = AttrMapValue(isNumberOrNone, desc="Y direction Radius"),
        )
    other_threshold=None

    def __init__(self,**kwd):
        PlotArea.__init__(self)
        self.x = 0
        self.y = 0
        self.width = 100
        self.height = 100
        self.data = [1,2.3,1.7,4.2]
        self.labels = None  # or list of strings
        self.startAngle = 90
        self.direction = "clockwise"
        self.simpleLabels = 1
        self.checkLabelOverlap = 0
        self.pointerLabelMode = None
        self.sameRadii = False
        self.orderMode = 'fixed'
        self.xradius = self.yradius = None

        self.slices = TypedPropertyCollection(WedgeProperties)
        self.slices[0].fillColor = colors.darkcyan
        self.slices[1].fillColor = colors.blueviolet
        self.slices[2].fillColor = colors.blue
        self.slices[3].fillColor = colors.cyan
        self.slices[4].fillColor = colors.pink
        self.slices[5].fillColor = colors.magenta
        self.slices[6].fillColor = colors.yellow

    def demo(self):
        d = Drawing(200, 100)

        pc = Pie()
        pc.x = 50
        pc.y = 10
        pc.width = 100
        pc.height = 80
        pc.data = [10,20,30,40,50,60]
        pc.labels = ['a','b','c','d','e','f']

        pc.slices.strokeWidth=0.5
        pc.slices[3].popout = 10
        pc.slices[3].strokeWidth = 2
        pc.slices[3].strokeDashArray = [2,2]
        pc.slices[3].labelRadius = 1.75
        pc.slices[3].fontColor = colors.red
        pc.slices[0].fillColor = colors.darkcyan
        pc.slices[1].fillColor = colors.blueviolet
        pc.slices[2].fillColor = colors.blue
        pc.slices[3].fillColor = colors.cyan
        pc.slices[4].fillColor = colors.aquamarine
        pc.slices[5].fillColor = colors.cadetblue
        pc.slices[6].fillColor = colors.lightcoral

        d.add(pc)
        return d

    def makePointerLabels(self,angles,plMode):
        class PL:
            def __init__(self,centerx,centery,xradius,yradius,data,lu=0,ru=0):
                self.centerx = centerx
                self.centery = centery
                self.xradius = xradius
                self.yradius = yradius
                self.data = data
                self.lu = lu
                self.ru = ru

        labelX = self.width-2
        labelY = self.height
        n = nr = nl = maxW = sumH = 0
        styleCount = len(self.slices)
        L=[]
        L_add = L.append
        refArcs = _makeSideArcDefs(self.startAngle,self.direction)
        for i, A in angles:
            if A[1] is None: continue
            sn = self.getSeriesName(i,'')
            if not sn: continue
            style = self.slices[i%styleCount]
            if not style.label_visible or not style.visible: continue
            n += 1
            _addWedgeLabel(self,sn,L_add,180,labelX,labelY,style,labelClass=WedgeLabel)
            l = L[-1]
            b = l.getBounds()
            w = b[2]-b[0]
            h = b[3]-b[1]
            ri = [(a[0],intervalIntersection(A,(a[1],a[2]))) for a in refArcs]
            li = _findLargestArc(ri,0)
            ri = _findLargestArc(ri,1)
            if li and ri:
                if plMode=='LeftAndRight':
                    if li[1]-li[0]<ri[1]-ri[0]:
                        li = None
                    else:
                        ri = None
                else:
                    if li[1]-li[0]<0.02*(ri[1]-ri[0]):
                        li = None
                    elif (li[1]-li[0])*0.02>ri[1]-ri[0]:
                        ri = None
            if ri: nr += 1
            if li: nl += 1
            l._origdata = dict(bounds=b,width=w,height=h,li=li,ri=ri,index=i,edgePad=style.label_pointer_edgePad,piePad=style.label_pointer_piePad,elbowLength=style.label_pointer_elbowLength)
            maxW = max(w,maxW)
            sumH += h+2

        if not n:   #we have no labels
            xradius = self.width*0.5
            yradius = self.height*0.5
            centerx = self.x+xradius
            centery = self.y+yradius
            if self.xradius: xradius = self.xradius
            if self.yradius: yradius = self.yradius
            if self.sameRadii: xradius=yradius=min(xradius,yradius)
            return PL(centerx,centery,xradius,yradius,[])

        aonR = nr==n
        if sumH<self.height and (aonR or nl==n):
            side=int(aonR)
        else:
            side=None
        G,lu,ru,mel = _fixPointerLabels(len(angles),L,self.x,self.y,self.width,self.height,side=side)
        if plMode=='LeftAndRight':
            lu = ru = max(lu,ru)
        x0 = self.x+lu
        x1 = self.x+self.width-ru
        xradius = (x1-x0)*0.5
        yradius = self.height*0.5-mel
        centerx = x0+xradius
        centery = self.y+yradius+mel
        if self.xradius: xradius = self.xradius
        if self.yradius: yradius = self.yradius
        if self.sameRadii: xradius=yradius=min(xradius,yradius)
        return PL(centerx,centery,xradius,yradius,G,lu,ru)

    def normalizeData(self):
        data = map(abs,self.data)
        s = self._sum = float(sum(data))
        if s>1e-8:
            f = 360./s
            return [f*x for x in data]
        else:
            return [0]*len(data)

    def makeAngles(self):
        startAngle = self.startAngle % 360
        whichWay = self.direction == "clockwise" and -1 or 1
        D = [a for a in enumerate(self.normalizeData())]
        if self.orderMode=='alternate':
            W = [a for a in D if abs(a[1])>=1e-5]
            W.sort(_arcCF)
            T = [[],[]]
            i = 0
            while W:
                if i<2:
                    a = W.pop(0)
                else:
                    a = W.pop(-1)
                T[i%2].append(a)
                i += 1
                i %= 4
            T[1].reverse()
            D = T[0]+T[1] + [a for a in D if abs(a[1])<1e-5]
        A = []
        a = A.append
        for i, angle in D:
            endAngle = (startAngle + (angle * whichWay))
            if abs(angle)>=1e-5:
                if startAngle >= endAngle:
                    aa = endAngle,startAngle
                else:
                    aa = startAngle,endAngle
            else:
                aa = startAngle, None
            startAngle = endAngle
            a((i,aa))
        return A

    def makeWedges(self):
        angles = self.makeAngles()
        n = len(angles)
        labels = _fixLabels(self.labels,n)

        self._seriesCount = n
        styleCount = len(self.slices)

        plMode = self.pointerLabelMode
        if plMode:
            checkLabelOverlap = False
            PL=self.makePointerLabels(angles,plMode)
            xradius = PL.xradius
            yradius = PL.yradius
            centerx = PL.centerx
            centery = PL.centery
            PL_data = PL.data
            gSN = lambda i: ''
        else:
            xradius = self.width*0.5
            yradius = self.height*0.5
            centerx = self.x + xradius
            centery = self.y + yradius
            if self.xradius: xradius = self.xradius
            if self.yradius: yradius = self.yradius
            if self.sameRadii: xradius=yradius=min(xradius,yradius)
            checkLabelOverlap = self.checkLabelOverlap
            gSN = lambda i: self.getSeriesName(i,'')

        g = Group()
        g_add = g.add
        if checkLabelOverlap:
            L = []
            L_add = L.append
        else:
            L_add = g_add

        for i,(a1,a2) in angles:
            if a2 is None: continue
            #if we didn't use %stylecount here we'd end up with the later wedges
            #all having the default style
            wedgeStyle = self.slices[i%styleCount]
            if not wedgeStyle.visible: continue

            # is it a popout?
            cx, cy = centerx, centery
            text = gSN(i)
            popout = wedgeStyle.popout
            if text or popout:
                averageAngle = (a1+a2)/2.0
                aveAngleRadians = averageAngle/_180_pi
                cosAA = cos(aveAngleRadians)
                sinAA = sin(aveAngleRadians)
                if popout:
                    # pop out the wedge
                    cx = centerx + popout*cosAA
                    cy = centery + popout*sinAA

            if n > 1:
                theWedge = Wedge(cx, cy, xradius, a1, a2, yradius=yradius)
            elif n==1:
                theWedge = Ellipse(cx, cy, xradius, yradius)

            theWedge.fillColor = wedgeStyle.fillColor
            theWedge.strokeColor = wedgeStyle.strokeColor
            theWedge.strokeWidth = wedgeStyle.strokeWidth
            theWedge.strokeDashArray = wedgeStyle.strokeDashArray

            g_add(theWedge)
            if wedgeStyle.label_visible:
                if text:
                    labelRadius = wedgeStyle.labelRadius
                    rx = xradius*labelRadius
                    ry = yradius*labelRadius
                    labelX = cx + rx*cosAA
                    labelY = cy + ry*sinAA
                    _addWedgeLabel(self,text,L_add,averageAngle,labelX,labelY,wedgeStyle)
                    if checkLabelOverlap:
                        l = L[-1]
                        l._origdata = { 'x': labelX, 'y':labelY, 'angle': averageAngle,
                                        'rx': rx, 'ry':ry, 'cx':cx, 'cy':cy,
                                        'bounds': l.getBounds(),
                                        }
                elif plMode and PL_data:
                    l = PL_data[i]
                    if l:
                        data = l._origdata
                        sinM = data['smid']
                        cosM = data['cmid']
                        lX = cx + xradius*cosM
                        lY = cy + yradius*sinM
                        lpel = wedgeStyle.label_pointer_elbowLength
                        lXi = lX + lpel*cosM
                        lYi = lY + lpel*sinM
                        L_add(PolyLine((lX,lY,lXi,lYi,l.x,l.y),
                                strokeWidth=wedgeStyle.label_pointer_strokeWidth,
                                strokeColor=wedgeStyle.label_pointer_strokeColor))
                        L_add(l)

        if checkLabelOverlap and L:
            fixLabelOverlaps(L)
            map(g_add,L)

        return g

    def draw(self):
        G = self.makeBackground()
        w = self.makeWedges()
        if G: return Group(G,w)
        return w