예제 #1
0
파일: legends.py 프로젝트: tina-713/vaccapp
class SubColProperty(PropHolder):
    dividerLines = 0
    _attrMap = AttrMap(
        minWidth=AttrMapValue(isNumber, desc="minimum width for this subcol"),
        rpad=AttrMapValue(isNumber, desc="right padding for this subcol"),
        align=AttrMapValue(OneOf('left', 'right', 'center', 'centre',
                                 'numeric'),
                           desc='alignment in subCol'),
        fontName=AttrMapValue(isString, desc="Font name of the strings"),
        fontSize=AttrMapValue(isNumber, desc="Font size of the strings"),
        leading=AttrMapValue(isNumberOrNone, desc="leading for the strings"),
        fillColor=AttrMapValue(isColorOrNone, desc="fontColor"),
        underlines=AttrMapValue(EitherOr((NoneOr(isInstanceOf(Line)),
                                          SequenceOf(isInstanceOf(Line),
                                                     emptyOK=0,
                                                     lo=0,
                                                     hi=0x7fffffff))),
                                desc="underline definitions"),
        overlines=AttrMapValue(EitherOr((NoneOr(isInstanceOf(Line)),
                                         SequenceOf(isInstanceOf(Line),
                                                    emptyOK=0,
                                                    lo=0,
                                                    hi=0x7fffffff))),
                               desc="overline definitions"),
        dx=AttrMapValue(isNumber, desc="x offset from default position"),
        dy=AttrMapValue(isNumber, desc="y offset from default position"),
        vAlign=AttrMapValue(OneOf('top', 'bottom', 'middle'),
                            desc='vertical alignment in the row'),
    )
예제 #2
0
class BarChartLabel(PMVLabel):
    """
    An extended Label allowing for nudging, lines visibility etc
    """
    _attrMap = AttrMap(
        BASE=PMVLabel,
        lineStrokeWidth=AttrMapValue(isNumberOrNone,
                                     desc="Non-zero for a drawn line"),
        lineStrokeColor=AttrMapValue(isColorOrNone,
                                     desc="Color for a drawn line"),
        fixedEnd=AttrMapValue(NoneOrInstanceOfLabelOffset,
                              desc="None or fixed draw ends +/-"),
        fixedStart=AttrMapValue(NoneOrInstanceOfLabelOffset,
                                desc="None or fixed draw starts +/-"),
        nudge=AttrMapValue(isNumber, desc="Non-zero sign dependent nudge"),
        boxTarget=AttrMapValue(
            OneOf('normal', 'anti', 'lo', 'hi', 'mid'),
            desc="one of ('normal','anti','lo','hi','mid')"),
    )

    def __init__(self):
        PMVLabel.__init__(self)
        self.lineStrokeWidth = 0
        self.lineStrokeColor = None
        self.fixedStart = self.fixedEnd = None
        self.nudge = 0
예제 #3
0
class LineChartProperties(PropHolder):
    _attrMap = AttrMap(
        strokeWidth=AttrMapValue(isNumber, desc='Width of a line.'),
        strokeColor=AttrMapValue(isColorOrNone,
                                 desc='Color of a line or border.'),
        fillColor=AttrMapValue(isColorOrNone, desc='fill color of a bar.'),
        strokeDashArray=AttrMapValue(isListOfNumbersOrNone,
                                     desc='Dash array of a line.'),
        symbol=AttrMapValue(NoneOr(isSymbol),
                            desc='Widget placed at data points.',
                            advancedUsage=1),
        shader=AttrMapValue(None, desc='Shader Class.', advancedUsage=1),
        filler=AttrMapValue(None, desc='Filler Class.', advancedUsage=1),
        name=AttrMapValue(isStringOrNone, desc='Name of the line.'),
        lineStyle=AttrMapValue(NoneOr(OneOf('line', 'joinedLine', 'bar')),
                               desc="What kind of plot this line is",
                               advancedUsage=1),
        barWidth=AttrMapValue(
            isNumberOrNone,
            desc="Percentage of available width to be used for a bar",
            advancedUsage=1),
        inFill=AttrMapValue(isBoolean,
                            desc='If true flood fill to x axis',
                            advancedUsage=1),
    )
예제 #4
0
    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):
예제 #5
0
파일: markers.py 프로젝트: biniyi/reportlab
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
예제 #6
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 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
        checkLabelOverlap = self.checkLabelOverlap
        L = []
        L_add = L.append

        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)

                    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
            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)
                    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
예제 #7
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(
            EitherOr((isListOfNoneOrNumber, isListOfListOfNoneOrNumber)),
            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),
        innerRadiusFraction=AttrMapValue(
            isNumberOrNone,
            desc=
            'None or the fraction of the radius to be used as the inner hole.\nIf not a suitable default will be used.'
        ),
    )

    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.innerRadiusFraction = None

        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
        data = self.data
        multi = isListOfListOfNoneOrNumber(data)
        if multi:
            #it's a nested list, more than one sequence
            normData = []
            n = []
            for l in data:
                t = self.normalizeData(l)
                normData.append(t)
                n.append(len(t))
            self._seriesCount = max(n)
        else:
            normData = self.normalizeData(data)
            n = len(normData)
            self._seriesCount = n

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

        labels = self.labels
        if labels is None:
            labels = []
            if not multi:
                labels = [''] * n
            else:
                for m in n:
                    labels = list(labels) + [''] * m
        else:
            #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 multi:
                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
        self.labels = labels

        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)
        irf = self.innerRadiusFraction

        if multi:
            #multi-series doughnut
            ndata = len(data)
            if irf is None:
                yir = (yradius / 2.5) / ndata
                xir = (xradius / 2.5) / ndata
            else:
                yir = yradius * irf
                xir = xradius * irf
            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
                    aa = abs(startAngle - endAngle)
                    if aa < 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[sn, 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 len(series) > 1:
                        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

                    shader = sectorStyle.shadingKind
                    if shader:
                        nshades = aa / float(sectorStyle.shadingAngle)
                        if nshades > 1:
                            shader = colors.Whiter if shader == 'lighten' else colors.Blacker
                            nshades = 1 + int(nshades)
                            shadingAmount = 1 - sectorStyle.shadingAmount
                            if sectorStyle.shadingDirection == 'normal':
                                dsh = (1 - shadingAmount) / float(nshades - 1)
                                shf1 = shadingAmount
                            else:
                                dsh = (shadingAmount - 1) / float(nshades - 1)
                                shf1 = 1
                            shda = (a2 - a1) / float(nshades)
                            shsc = sectorStyle.fillColor
                            theSector.fillColor = None
                            for ish in range(nshades):
                                sha1 = a1 + ish * shda
                                sha2 = a1 + (ish + 1) * shda
                                shc = shader(shsc, shf1 + dsh * ish)
                                if len(series) > 1:
                                    shSector = Wedge(cx,
                                                     cy,
                                                     xr,
                                                     sha1,
                                                     sha2,
                                                     yradius=yr,
                                                     radius1=xr1,
                                                     yradius1=yr1)
                                else:
                                    shSector = Wedge(cx,
                                                     cy,
                                                     xr,
                                                     sha1,
                                                     sha2,
                                                     yradius=yr,
                                                     radius1=xr1,
                                                     yradius1=yr1,
                                                     annular=True)
                                shSector.fillColor = shc
                                shSector.strokeColor = None
                                shSector.strokeWidth = 0
                                g.add(shSector)

                    g.add(theSector)

                    if sn == 0 and sectorStyle.visible and sectorStyle.label_visible:
                        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
            if irf is None:
                yir = yradius / 2.5
                xir = xradius / 2.5
            else:
                yir = yradius * irf
                xir = xradius * irf
            for i, angle in enumerate(normData):
                endAngle = (startAngle + (angle * whichWay))  #% 360
                aa = abs(startAngle - endAngle)
                if aa < 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

                shader = sectorStyle.shadingKind
                if shader:
                    nshades = aa / float(sectorStyle.shadingAngle)
                    if nshades > 1:
                        shader = colors.Whiter if shader == 'lighten' else colors.Blacker
                        nshades = 1 + int(nshades)
                        shadingAmount = 1 - sectorStyle.shadingAmount
                        if sectorStyle.shadingDirection == 'normal':
                            dsh = (1 - shadingAmount) / float(nshades - 1)
                            shf1 = shadingAmount
                        else:
                            dsh = (shadingAmount - 1) / float(nshades - 1)
                            shf1 = 1
                        shda = (a2 - a1) / float(nshades)
                        shsc = sectorStyle.fillColor
                        theSector.fillColor = None
                        for ish in range(nshades):
                            sha1 = a1 + ish * shda
                            sha2 = a1 + (ish + 1) * shda
                            shc = shader(shsc, shf1 + dsh * ish)
                            if n > 1:
                                shSector = Wedge(cx,
                                                 cy,
                                                 xradius,
                                                 sha1,
                                                 sha2,
                                                 yradius=yradius,
                                                 radius1=xir,
                                                 yradius1=yir)
                            elif n == 1:
                                shSector = Wedge(cx,
                                                 cy,
                                                 xradius,
                                                 sha1,
                                                 sha2,
                                                 yradius=yradius,
                                                 radius1=xir,
                                                 yradius1=yir,
                                                 annular=True)
                            shSector.fillColor = shc
                            shSector.strokeColor = None
                            shSector.strokeWidth = 0
                            g.add(shSector)

                g.add(theSector)

                # now draw a label
                if labels[
                        i] and sectorStyle.visible and sectorStyle.label_visible:
                    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
예제 #8
0
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),
    )

    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

        # 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.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 xrange(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 xrange(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 xrange(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 isSeqType(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
                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,
                               y,
                               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...
            if isAuto(col):
                chart = getattr(col, 'chart', getattr(col, 'obj', None))
                c = chart.makeSwatchSample(getattr(col, 'index', i), x, thisy,
                                           dx, dy)
            elif isinstance(col, colors.Color):
                if isSymbol(swatchMarker):
                    c = uSymbol2Symbol(swatchMarker, x + dx / 2.,
                                       thisy + dy / 2., col)
                else:
                    c = self._defaultSwatch(x,
                                            thisy,
                                            dx,
                                            dy,
                                            fillColor=col,
                                            strokeWidth=strokeWidth,
                                            strokeColor=strokeColor)
            elif col is not None:
                try:
                    c = copy.deepcopy(col)
                    c.x = x
                    c.y = thisy
                    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 = map(lambda i: (getattr(colors, i), i), items)
        legend.colorNamePairs = items

        d.add(legend, 'legend')

        return d
예제 #9
0
파일: grids.py 프로젝트: biniyi/reportlab
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
예제 #10
0
class LegendedHorizontalLineChart(HorizontalLineChart):
    """A subclass of Legend for drawing legends with lines as the
    swatches rather than rectangles. Useful for lineCharts and
    linePlots. Should be similar in all other ways the the standard
    Legend class.
    """

    _attrMap = AttrMap(
        BASE=HorizontalLineChart,
        drawLegend=AttrMapValue(isBoolean, desc='If true draw legend.', advancedUsage=1),
        legendPositionType=AttrMapValue(
            OneOf(
                "null",
                "top-left", "top-mid", "top-right",
                "bottom-left", "bottom-mid", "bottom-right"
            ),
            desc="The position of LinLegend."),
        legendAdjustX=AttrMapValue(isNumber, desc='xxx.'),
        legendAdjustY=AttrMapValue(isNumber, desc='xxx.'),
        legendCategoryNames=AttrMapValue(isListOfStringsOrNone, desc='List of legend category names.'),
        titleMain=AttrMapValue(isString, desc='main title text.'),
        titleMainFontName=AttrMapValue(isString, desc='main title font name.'),
        titleMainFontSize=AttrMapValue(isNumberInRange(0, 100), desc='main title font size.'),
        titleMainFontColor=AttrMapValue(isColor, desc='main title font color.'),
        legendFontSize=AttrMapValue(isNumberInRange(0, 100), desc='legend text font size.'),
        labels_height=AttrMapValue(isNumberInRange(0, 100), desc='the max height of x-labels.')
    )

    def __init__(self):
        HorizontalLineChart.__init__(self)

        self.drawLegend = False
        self.legendPositionType = "null"
        self.legendCategoryNames = None
        self.legendAdjustX = 0
        self.legendAdjustY = 0
        self.titleMain = ""
        self.titleMainFontColor = colors.gray
        self.titleMainFontName = DefaultFontName
        self.titleMainFontSize = STATE_DEFAULTS['fontSize']

        self.legendFontSize = 7

        self.labels_height = 0

    def set_line_color(self):
        if self.legendCategoryNames is None:
            self.legendCategoryNames = []
        legend_num = len(self.legendCategoryNames)
        data_num = len(self.data)
        for i in range(data_num):
            line = self.lines[i]
            line.strokeColor = ALL_COLORS[i]
            if i >= legend_num:
                self.legendCategoryNames.append("unknown")

        legend_num = len(self.legendCategoryNames)
        temp_category_names = self.legendCategoryNames[:]
        if legend_num >= 1:
            self.legendCategoryNames = []
            color_name_pairs = [(0, name) for name in temp_category_names]

            legend_width = ChartsLegend.calc_legend_width(
                color_name_pairs, 10, 10, DefaultFontName, self.legendFontSize)
            per_legend_width = int(legend_width / legend_num)
            legend_num_per_row = int(self.width / per_legend_width)
            index = 0
            row_names = []
            for name in temp_category_names:
                row_names.append(name)
                index += 1
                if index == legend_num_per_row:
                    index = 0
                    self.legendCategoryNames.append(row_names)
                    row_names = []
            if len(row_names) > 0:
                self.legendCategoryNames.append(row_names)
        else:
            self.legendCategoryNames = temp_category_names

    def _calc_labels_size(self):
        max_width = 0
        index = 0
        for label_text in self.categoryAxis.categoryNames:
            tmp_width = get_string_width(label_text, self.categoryAxis.labels.fontName,
                                         self.categoryAxis.labels.fontSize)
            if tmp_width > max_width:
                max_width = tmp_width

            if self.categoryAxis.labels[index].angle % 90 == 0:
                self.categoryAxis.labels[index].dx = \
                    int(tmp_width * math.cos(self.categoryAxis.labels[index].angle / 180 * math.pi) / 2) - \
                    int(self.categoryAxis.labels.fontSize *
                        math.sin(self.categoryAxis.labels[index].angle / 180 * math.pi)
                        / 2)
            index += 1

        self.labels_height = \
            int(max_width * math.sin(self.categoryAxis.labels.angle / 180 * math.pi)) + \
            int(self.categoryAxis.labels.fontSize * math.cos(self.categoryAxis.labels.angle / 180 * math.pi))

        return self.labels_height

    def _adjust_positon(self):
        self.x = 30
        if self.labels_height > 20:
            self.y = self.labels_height + 10
        else:
            self.y = 30
        self.width -= self.x + 30
        self.height -= self.y + self.titleMainFontSize + 20

    def draw(self):
        self._calc_labels_size()
        self._adjust_positon()

        self.set_line_color()
        if self.drawLegend is True:
            if self.legendPositionType in ["bottom-left", "bottom-mid", "bottom-right"]:
                row_count = len(self.legendCategoryNames) + 1
                self.height -= row_count * self.legendFontSize
                self.y += row_count * self.legendFontSize

        g = HorizontalLineChart.draw(self)

        if self.drawLegend:
            legend_count = 0
            for i in range(len(self.legendCategoryNames)):
                legend = ChartsLegend()

                legend.positionType = self.legendPositionType
                if self.legendPositionType != "null":
                    if self.legendPositionType in ["bottom-left", "bottom-mid", "bottom-right"]:
                        legend.backgroundRect = \
                            Rect(self.x,
                                 self.y + legend.bottom_gap - self.labels_height - 15 - ((i+1) * legend.fontSize),
                                 self.width, self.height)
                    else:
                        legend.backgroundRect = Rect(self.x, self.y - (i * legend.fontSize * 1.2),
                                                     self.width, self.height)

                legend.adjustX = self.legendAdjustX
                legend.adjustY = self.legendAdjustY

                legend.fontSize = self.legendFontSize

                legend.colorNamePairs = []
                for j in range(len(self.legendCategoryNames[i])):
                    legend.colorNamePairs.append((ALL_COLORS[legend_count + j],
                                                  self.legendCategoryNames[i][j]))
                legend_count += len(self.legendCategoryNames[i])

                g.add(legend)

        if self.titleMain != "":
            title = String(0, 0, self.titleMain)
            title.fontSize = self.titleMainFontSize
            title.fontName = self.titleMainFontName
            title.fillColor = self.titleMainFontColor
            title.textAnchor = 'start'
            # title.x = self.x - 20
            title.x = 0
            title.y = self.y + self.height + 20

            g.add(title)

        return g
예제 #11
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 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(map(min,data)) >=0, "Cannot do spider plots of negative numbers!"
        norm = max(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 callable(fmt):
            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 xrange(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 xrange(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

        map(g.add,STRANDAREAS+STRANDS+syms+S+labs)
        return g
예제 #12
0
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"),
        autoYPadding = AttrMapValue(isNumber, desc="y Padding between rows if deltay=None"),
        yGap = AttrMapValue(isNumber, desc="Additional gap between rows"),
        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=""),
        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') ..."),
        callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))"),
        boxAnchor = AttrMapValue(isBoxAnchor,'Anchor point for the legend area'),
        variColumn = AttrMapValue(isBoolean,'If true column widths may vary (default is false)'),
        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'),
        dividerWidth = AttrMapValue(isNumber, desc="dividerLines width"),
        dividerColor = AttrMapValue(isColorOrNone, desc="dividerLines color"),
        dividerDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array for dividerLines.'),
        dividerOffsX = AttrMapValue(SequenceOf(isNumber,emptyOK=0,lo=2,hi=2), desc='divider lines X offsets'),
        dividerOffsY = AttrMapValue(isNumber, desc="dividerLines Y offset"),
        sepSpace = AttrMapValue(isNumber, desc="separator spacing"),
        colEndCallout = AttrMapValue(None, desc="a user callout(self,g, x, xt, y,width, lWidth)"),
       )

    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

        # 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.sepSpace = 0
        self.colEndCallout = None

    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 = colorNamePairs.chart
            texts = [str(chart.getSeriesName(i,'series %d' % i)) for i in xrange(chart._seriesCount)]
        return texts

    def _calculateMaxWidth(self, colorNamePairs):
        "Calculate the maximum width of some given strings."
        M = []
        a = M.append
        for t in self._getTexts(colorNamePairs):
            M.append(_getWidth(t, self.fontName, self.fontSize,self.sepSpace))
        if not M: return 0
        if self.variColumn:
            columnMaximum = self.columnMaximum
            return [max(M[r:r+columnMaximum]) for r in range(0,len(M),self.columnMaximum)]
        else:
            return max(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
        leading = fontSize*1.2
        yGap = self.yGap
        if not deltay:
            deltay = max(dy,leading)+self.autoYPadding
        ba = self.boxAnchor
        maxWidth = self._calculateMaxWidth(colorNamePairs)
        nCols = int((n+columnMaximum-1)/columnMaximum)
        xW = dx+dxTextSpace+self.autoXPadding
        variColumn = self.variColumn
        if variColumn:
            width = reduce(operator.add,maxWidth,0)+xW*(nCols-1)
        else:
            deltax = max(maxWidth+xW,deltax)
            width = maxWidth+(nCols-1)*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()
        def gAdd(t,g=g,fontName=fontName,fontSize=fontSize,fillColor=fillColor):
            t.fontName = fontName
            t.fontSize = fontSize
            t.fillColor = fillColor
            return g.add(t)

        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)
        dividerLines = self.dividerLines
        if dividerLines:
            dividerWidth = self.dividerWidth
            dividerColor = self.dividerColor
            dividerDashArray = self.dividerDashArray
            dividerOffsX = self.dividerOffsX
            dividerOffsY = self.dividerOffsY

        for i in xrange(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 = []
            j = int(i/columnMaximum)

            # 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":
                if isSeqType(name):
                    for t in T[0]:
                        S.append(String(thisx,y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
                                textAnchor = "start"))
                        y -= leading
                    yd = y
                    y = y0
                    for t in T[1]:
                        S.append(String(thisx+maxWidth[j],y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
                                textAnchor = "end"))
                        y -= leading
                    y = min(yd,y)
                else:
                    for t in T:
                        # align text to left
                        S.append(String(thisx+maxWidth[j],y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
                                textAnchor = "end"))
                        y -= leading
                x = thisx+maxWidth[j]+dxTextSpace
            elif alignment == "right":
                if isSeqType(name):
                    y0 = y
                    for t in T[0]:
                        S.append(String(thisx+dx+dxTextSpace,y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
                                textAnchor = "start"))
                        y -= leading
                    yd = y
                    y = y0
                    for t in T[1]:
                        S.append(String(thisx+dx+dxTextSpace+maxWidth[j],y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
                                textAnchor = "end"))
                        y -= leading
                    y = min(yd,y)
                else:
                    for t in T:
                        # align text to right
                        S.append(String(thisx+dx+dxTextSpace,y,t,fontName=fontName,fontSize=fontSize,fillColor=fillColor,
                                textAnchor = "start"))
                        y -= leading
                x = thisx
            else:
                raise ValueError, "bad alignment"
            leadingMove = 2*y0-y-thisy

            if dividerLines:
                xd = thisx+dx+dxTextSpace+maxWidth[j]+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...
            if isAuto(col):
                chart = getattr(col,'chart',getattr(col,'obj',None))
                g.add(chart.makeSwatchSample(getattr(col,'index',i),x,thisy,dx,dy))
            elif isinstance(col, colors.Color):
                if isSymbol(swatchMarker):
                    g.add(uSymbol2Symbol(swatchMarker,x+dx/2.,thisy+dy/2.,col))
                else:
                    g.add(self._defaultSwatch(x,thisy,dx,dy,fillColor=col,strokeWidth=strokeWidth,strokeColor=strokeColor))
            else:
                try:
                    c = copy.deepcopy(col)
                    c.x = x
                    c.y = thisy
                    c.width = dx
                    c.height = dy
                    g.add(c)
                except:
                    pass

            map(gAdd,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, maxWidth[j], maxWidth[j]+dx+dxTextSpace)

            if i%columnMaximum==lim:
                if variColumn:
                    thisx += maxWidth[j]+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 = map(lambda i:(getattr(colors, i), i), items)
        legend.colorNamePairs = items

        d.add(legend, 'legend')

        return d
예제 #13
0
class ChartsLegend(LineLegend):
    """A subclass of Legend for drawing legends with lines as the
    swatches rather than rectangles. Useful for lineCharts and
    linePlots. Should be similar in all other ways the the standard
    Legend class.
    """

    _attrMap = AttrMap(
        BASE=LineLegend,
        positionType=AttrMapValue(OneOf("null", "top-left", "top-mid",
                                        "top-right", "bottom-left",
                                        "bottom-mid", "bottom-right", "right"),
                                  desc="The position of LinLegend."),
        backgroundRect=AttrMapValue(None, desc="The position of LinLegend."),
        adjustX=AttrMapValue(isNumber, desc='xxx.'),
        adjustY=AttrMapValue(isNumber, desc='xxx.'),
        bottom_gap=AttrMapValue(isNumber, desc='xxx.'))

    def __init__(self):
        LineLegend.__init__(self)

        self.positionType = "null"
        self.backgroundRect = None
        self.adjustX = 0
        self.adjustY = 0
        self.fontName = DefaultFontName
        self.deltax = 10
        self.deltay = 0
        self.boxAnchor = 'w'
        self.columnMaximum = 1
        self.yGap = 0
        self.fontSize = 7
        self.alignment = 'right'
        self.dxTextSpace = 5

        self.bottom_gap = 40

    @staticmethod
    def calc_legend_width(color_name_pairs,
                          dx,
                          deltax,
                          font_name,
                          font_size,
                          sub_cols=None):
        pairs_num = len(color_name_pairs)

        max_text_width = 0
        x_width = 0
        for x in color_name_pairs:
            if type(x[1]) is tuple:
                for str_i in x[1]:
                    tmp_width = stringWidth(str(str_i), font_name, font_size)
                    if sub_cols is not None and tmp_width < sub_cols[
                            0].minWidth:
                        tmp_width = sub_cols[0].minWidth
                    x_width += tmp_width
            else:
                str_x = x[1]
                x_width = stringWidth(str_x, font_name, font_size)
            if x_width > max_text_width:
                max_text_width = x_width
        total_text_width = (pairs_num - 1) * max_text_width + x_width

        legend_width = total_text_width + (dx * pairs_num) + (deltax *
                                                              pairs_num)

        return legend_width

    def draw(self):
        legend_width = self.calc_legend_width(self.colorNamePairs, self.dx,
                                              self.deltax, self.fontName,
                                              self.fontSize, self.subCols)

        if self.positionType != "null" and self.backgroundRect is not None:
            if self.positionType == "top-left":
                self.x = self.backgroundRect.x
                self.y = self.backgroundRect.y + self.backgroundRect.height
            elif self.positionType == "top-mid":
                self.x = self.backgroundRect.x + int(
                    self.backgroundRect.width / 2) - int(legend_width / 2)
                self.y = self.backgroundRect.y + self.backgroundRect.height
            elif self.positionType == "top-right":
                self.x = self.backgroundRect.x + self.backgroundRect.width - legend_width
                self.y = self.backgroundRect.y + self.backgroundRect.height
            elif self.positionType == "bottom-left":
                self.x = self.backgroundRect.x
                self.y = self.backgroundRect.y - self.bottom_gap
            elif self.positionType == "bottom-mid":
                self.x = self.backgroundRect.x + int(
                    self.backgroundRect.width / 2) - int(legend_width / 2)
                self.y = self.backgroundRect.y - self.bottom_gap
            elif self.positionType == "bottom-right":
                self.x = self.backgroundRect.x + self.backgroundRect.width - legend_width
                self.y = self.backgroundRect.y - self.bottom_gap
            elif self.positionType == "right":
                self.x = self.backgroundRect.x + self.backgroundRect.width + 10
                self.y = self.backgroundRect.y + self.backgroundRect.height

            self.x += self.adjustX
            self.y += self.adjustY

        return LineLegend.draw(self)
예제 #14
0
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()
예제 #15
0
파일: grids.py 프로젝트: biniyi/reportlab
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 = (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
예제 #16
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
예제 #17
0
class ReportLabBarChart(BarChart):

    _flipXY = 0

    _attrMap = AttrMap(
        BASE=BarChart,
        drawLegend=AttrMapValue(isBoolean,
                                desc='If true draw legend.',
                                advancedUsage=1),
        legendPositionType=AttrMapValue(OneOf("null", "top-left", "top-mid",
                                              "top-right", "bottom-left",
                                              "bottom-mid", "bottom-right"),
                                        desc="The position of LinLegend."),
        legendAdjustX=AttrMapValue(isNumber, desc='xxx.'),
        legendAdjustY=AttrMapValue(isNumber, desc='xxx.'),
        legendCategoryNames=AttrMapValue(
            isListOfStringsOrNone, desc='List of legend category names.'),
        titleMain=AttrMapValue(isString, desc='main title text.'),
        titleMainFontName=AttrMapValue(isString, desc='main title font name.'),
        titleMainFontSize=AttrMapValue(isNumberInRange(0, 100),
                                       desc='main title font size.'),
        titleMainFontColor=AttrMapValue(isColor,
                                        desc='main title font color.'),
        legendFontSize=AttrMapValue(isNumberInRange(0, 100),
                                    desc='legend text font size.'),
        x_labels_height=AttrMapValue(isNumberInRange(0, 100),
                                     desc='the max height in x-labels.'),
        y_labels_height=AttrMapValue(isNumberInRange(0, 50),
                                     desc='the max height in y-labels.'))

    def __init__(self,
                 x,
                 y,
                 width,
                 height,
                 cat_names,
                 data,
                 step_count=4,
                 style="parallel",
                 label_format=None,
                 label_sum=False,
                 legend_names=None,
                 legend_position="top-right",
                 legend_adjust_x=0,
                 legend_adjust_y=0,
                 main_title="",
                 main_title_font_name=None,
                 main_title_font_size=None,
                 main_title_font_color=None,
                 x_desc=None,
                 y_desc=None,
                 cat_label_angle=30,
                 cat_label_all=False):
        BarChart.__init__(self)

        if self._flipXY:
            self.categoryAxis = YCategoryAxisWithDesc(desc=y_desc)
            self.valueAxis = XValueAxisWithDesc(desc=x_desc)
        else:
            self.categoryAxis = XCategoryAxisWithDesc(desc=x_desc)
            self.valueAxis = YValueAxisWithDesc(desc=y_desc)

        if style not in ["stacked", "parallel"]:
            style = "parallel"
        self.categoryAxis.style = style

        self.valueAxis.visibleGrid = 1
        self.valueAxis.gridStrokeColor = colors.Color(0.5, 0.5, 0.5, 0.5)
        self.valueAxis.gridStrokeWidth = 1

        self.x = x
        self.y = y
        self.height = height
        self.width = width
        self.data = data
        self.strokeColor = colors.black
        self.categoryAxis.labels.boxAnchor = 'ne'
        # self.categoryAxis.labels.dx = 0
        # self.categoryAxis.labels.dy = 0
        self.categoryAxis.labels.angle = cat_label_angle
        # self.categoryAxis.labels.boxFillColor = colors.Color(1, 0, 0, 1)

        if cat_label_all is False:
            cat_names_num = len(cat_names)
            show_cat_num = 4
            if cat_names_num > show_cat_num:
                gap_num = int(cat_names_num / show_cat_num)
                for i in range(cat_names_num):
                    if i % gap_num != 0:
                        cat_names[i] = ""
        self.categoryAxis.categoryNames = cat_names

        self._lable_sum = []
        if label_format is not None:
            self.barLabelFormat = label_format
            if len(data) > 1 and style == "stacked":
                if label_sum:
                    self._cal_col_sum()

                    self.barLabels.boxTarget = "hi"
                    self.barLabels.nudge = 15
                else:
                    self.barLabels.boxTarget = "mid"
            else:
                self.barLabels.boxTarget = "hi"
                self.barLabels.nudge = 15

        min_value, max_value, step = self.get_limit_value(step_count)

        self.valueAxis.valueMin = min_value
        self.valueAxis.valueMax = max_value
        self.valueAxis.valueStep = step

        self.drawLegend = False
        self.legendCategoryNames = None
        if legend_names is not None and isListOfStrings(legend_names) is True:
            self.drawLegend = True
            self.legendCategoryNames = legend_names
        self.legendPositionType = legend_position
        self.legendAdjustX = legend_adjust_x
        self.legendAdjustY = legend_adjust_y
        self.legendFontSize = 7

        self.titleMain = main_title
        self.titleMainFontName = DefaultFontName
        self.titleMainFontSize = STATE_DEFAULTS['fontSize']
        self.titleMainFontColor = colors.black
        if main_title_font_name is not None:
            self.titleMainFontName = main_title_font_name
        if main_title_font_size is not None:
            self.titleMainFontSize = main_title_font_size
        if main_title_font_color is not None:
            self.titleMainFontColor = main_title_font_color

        self.x_labels_height = 0
        self.y_labels_height = 0

    def _cal_col_sum(self):
        for i in range(len(self.data[0])):
            self._lable_sum.append(0)
        for d in self.data:
            idx = 0
            for i in d:
                self._lable_sum[idx] += i
                idx += 1

    def get_limit_value(self, step_count):
        min_value = 0xffffffff
        max_value = 0 - min_value

        _data = []
        if self.categoryAxis.style == "stacked":
            flag = True
            for d in self.data:
                idx = 0
                for i in d:
                    if flag:
                        _data.append(i)
                    else:
                        _data[idx] += i
                    idx += 1
                flag = False

            for d in _data:
                if d > max_value:
                    max_value = d
            for d in self.data:
                for i in d:
                    if i < min_value:
                        min_value = i
        else:
            _data = self.data[:]

            for d in _data:
                for i in d:
                    if i > max_value:
                        max_value = i
                    if i < min_value:
                        min_value = i

        max_value += int(max_value / 10)
        max_value = int(max_value / 5) * 5
        min_value -= int(min_value / 10)
        min_value = int(min_value / 5) * 5

        step = int((max_value - min_value) / step_count)
        step = int(step / 5 + 1) * 5

        max_value = min_value + (step * step_count)

        return min_value, max_value, step

    def set_bar_color(self):
        if self.legendCategoryNames is None:
            self.legendCategoryNames = []
        legend_num = len(self.legendCategoryNames)
        data_num = len(self.data)
        for i in range(data_num):
            bar = self.bars[i]
            bar.strokeColor = ALL_COLORS[i]
            bar.fillColor = ALL_COLORS[i]
            if i >= legend_num:
                self.legendCategoryNames.append("unknown")

        legend_num = len(self.legendCategoryNames)
        temp_category_names = self.legendCategoryNames[:]
        if legend_num >= 1:
            self.legendCategoryNames = []
            color_name_pairs = [(0, name) for name in temp_category_names]

            legend_width = ChartsLegend.calc_legend_width(
                color_name_pairs, 10, 10, DefaultFontName, self.legendFontSize)
            per_legend_width = int(legend_width / legend_num)
            legend_num_per_row = int(self.width / per_legend_width)
            index = 0
            row_names = []
            for name in temp_category_names:
                row_names.append(name)
                index += 1
                if index == legend_num_per_row:
                    index = 0
                    self.legendCategoryNames.append(row_names)
                    row_names = []
            if len(row_names) > 0:
                self.legendCategoryNames.append(row_names)
        else:
            self.legendCategoryNames = temp_category_names

    def _draw_legend(self, g):
        legend_count = 0
        for i in range(len(self.legendCategoryNames)):
            legend = ChartsLegend()

            legend.positionType = self.legendPositionType
            if self.legendPositionType != "null":
                if self.legendPositionType in [
                        "bottom-left", "bottom-mid", "bottom-right"
                ]:
                    legend.backgroundRect = \
                        Rect(self.x,
                             self.y + legend.bottom_gap - self.x_labels_height - 15 - ((i + 1) * legend.fontSize),
                             self.width, self.height)
                else:
                    legend.backgroundRect = Rect(
                        self.x, self.y + (i * legend.fontSize * 1.2),
                        self.width, self.height)

            legend.adjustX = self.legendAdjustX
            legend.adjustY = self.legendAdjustY

            legend.fontSize = self.legendFontSize

            legend.colorNamePairs = []
            for j in range(len(self.legendCategoryNames[i])):
                legend.colorNamePairs.append((ALL_COLORS[legend_count + j],
                                              self.legendCategoryNames[i][j]))
            legend_count += len(self.legendCategoryNames[i])

            g.add(legend)

    def _get_label_sum_text(self, row_no, col_no):
        """
        return formatted label text
        :param row_no:
        :param col_no:
        :return:
        """
        len_row = len(self.data)
        if row_no != len_row - 1:
            return None

        text = self._lable_sum[col_no]

        label_fmt = self.barLabelFormat
        if isinstance(label_fmt, (list, tuple)):
            label_fmt = label_fmt[row_no]
            if isinstance(label_fmt, (list, tuple)):
                label_fmt = label_fmt[col_no]

        if label_fmt is None:
            label_text = None
        elif label_fmt == 'values':
            label_text = text
        elif isStr(label_fmt):
            label_text = label_fmt % text
        elif hasattr(label_fmt, '__call__'):
            label_text = label_fmt(text)
        else:
            msg = "Unknown formatter type %s, expected string or function" % label_fmt
            raise Exception(msg)
        return label_text

    def _addBarLabel(self, g, row_no, col_no, x, y, width, height):
        if self._lable_sum:
            text = self._get_label_sum_text(row_no, col_no)
        else:
            text = self._getLabelText(row_no, col_no)
        if text:
            self._addLabel(text, self.barLabels[(row_no, col_no)], g, row_no,
                           col_no, x, y, width, height)

    def draw(self):
        self.set_bar_color()
        if self.drawLegend is True:
            if self.legendPositionType in [
                    "bottom-left", "bottom-mid", "bottom-right"
            ]:
                row_count = len(self.legendCategoryNames) + 1
                self.height -= row_count * self.legendFontSize
                self.y += row_count * self.legendFontSize

        g = BarChart.draw(self)

        if self.drawLegend is True:
            self._draw_legend(g)

        if self.titleMain != "":
            title = String(0, 0, self.titleMain)
            title.fontSize = self.titleMainFontSize
            title.fontName = self.titleMainFontName
            title.fillColor = self.titleMainFontColor
            title.textAnchor = 'start'
            # title.x = self.x - 20
            # title.y = self.y + self.height + 20
            title.x = 0
            title.y = self.y + self.height + 20

            g.add(title)

        return g