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), )
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(isNumber, 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"), )
class LineChartProperties(PropHolder): _attrMap = AttrMap( strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'), strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line.'), strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'), symbol = AttrMapValue(NoneOr(isSymbol), desc='Widget placed at data points.'), shader = AttrMapValue(None, desc='Shader Class.'), filler = AttrMapValue(None, desc='Filler Class.'), name = AttrMapValue(isStringOrNone, desc='Name of the line.'), )
class WedgeProperties(PropHolder): """This holds descriptive information about the wedges in a pie chart. It is not to be confused with the 'wedge itself'; this just holds a recipe for how to format one, and does not allow you to hack the angles. It can format a genuine Wedge object for you with its format method. """ _attrMap = AttrMap( strokeWidth = AttrMapValue(isNumber), fillColor = AttrMapValue(isColorOrNone), strokeColor = AttrMapValue(isColorOrNone), strokeDashArray = AttrMapValue(isListOfNumbersOrNone), popout = AttrMapValue(isNumber), fontName = AttrMapValue(isString), fontSize = AttrMapValue(isNumber), fontColor = AttrMapValue(isColorOrNone), labelRadius = AttrMapValue(isNumber), label_dx = AttrMapValue(isNumber), label_dy = AttrMapValue(isNumber), label_angle = AttrMapValue(isNumber), label_boxAnchor = AttrMapValue(isBoxAnchor), label_boxStrokeColor = AttrMapValue(isColorOrNone), label_boxStrokeWidth = AttrMapValue(isNumber), label_boxFillColor = AttrMapValue(isColorOrNone), label_strokeColor = AttrMapValue(isColorOrNone), label_strokeWidth = AttrMapValue(isNumber), label_text = AttrMapValue(isStringOrNone), label_leading = AttrMapValue(isNumberOrNone), label_width = AttrMapValue(isNumberOrNone), label_maxWidth = AttrMapValue(isNumberOrNone), label_height = AttrMapValue(isNumberOrNone), label_textAnchor = AttrMapValue(isTextAnchor), label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"), label_topPadding = AttrMapValue(isNumber,'padding at top of box'), label_leftPadding = AttrMapValue(isNumber,'padding at left of box'), label_rightPadding = AttrMapValue(isNumber,'padding at right of box'), label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'), label_pointer_strokeColor = AttrMapValue(isColorOrNone,desc='Color of indicator line'), label_pointer_strokeWidth = AttrMapValue(isNumber,desc='StrokeWidth of indicator line'), label_pointer_elbowLength = AttrMapValue(isNumber,desc='length of final indicator line segment'), label_pointer_edgePad = AttrMapValue(isNumber,desc='pad between pointer label and box'), label_pointer_piePad = AttrMapValue(isNumber,desc='pad between pointer label and pie'), swatchMarker = AttrMapValue(NoneOr(isSymbol), desc="None or makeMarker('Diamond') ..."), visible = AttrMapValue(isBoolean,'set to false to skip displaying'), ) def __init__(self): self.strokeWidth = 0 self.fillColor = None self.strokeColor = STATE_DEFAULTS["strokeColor"] self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"] self.popout = 0 self.fontName = STATE_DEFAULTS["fontName"] self.fontSize = STATE_DEFAULTS["fontSize"] self.fontColor = STATE_DEFAULTS["fillColor"] self.labelRadius = 1.2 self.label_dx = self.label_dy = self.label_angle = 0 self.label_text = None self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0 self.label_boxAnchor = 'c' self.label_boxStrokeColor = None #boxStroke self.label_boxStrokeWidth = 0.5 #boxStrokeWidth self.label_boxFillColor = None self.label_strokeColor = None self.label_strokeWidth = 0.1 self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None self.label_textAnchor = 'start' self.label_visible = 1 self.label_pointer_strokeColor = colors.black self.label_pointer_strokeWidth = 0.5 self.label_pointer_elbowLength = 3 self.label_pointer_edgePad = 2 self.label_pointer_piePad = 3 self.visible = 1
v = A._y if jA: if flipXY: _v = jA._x else: _v = jA._y if mode == 'high': v = _v + jA._length elif mode == 'low': v = _v elif mode == 'bar': v = _v + val return v + delta NoneOrInstanceOfLabelOffset = NoneOr(isInstanceOf(LabelOffset)) class PMVLabel(Label): _attrMap = AttrMap(BASE=Label, ) def __init__(self): Label.__init__(self) self._pmv = 0 def _getBoxAnchor(self): a = Label._getBoxAnchor(self) if self._pmv < 0: a = { 'nw': 'se', 'n': 's',
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
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
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), ddf = AttrMapValue(NoneOr(isSubclassOf(DirectDraw),'NoneOrDirectDraw'),desc="A DirectDrawFlowable instance", _advancedUsage=1), ddfKlass = AttrMapValue(NoneOr(isSubclassOf(Flowable),'NoneOrDirectDraw'),desc="A Flowable class for direct drawing (default is XPreformatted", _advancedUsage=1), ddfStyle = AttrMapValue(NoneOr((isSubclassOf(PropertySet),isInstanceOf(PropertySet))),desc="A style or style class for a ddfKlass or None", _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, ddf = DirectDrawFlowable, ddfKlass = None, ddfStyle = None, ) 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 _getBaseLineRatio(self): 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 def _computeSizeEnd(self,objH): self._height = self.height or (objH + self.topPadding + self.bottomPadding) self._ewidth = (self._width-self.leftPadding-self.rightPadding) self._eheight = (self._height-self.topPadding-self.bottomPadding) boxAnchor = self._getBoxAnchor() if boxAnchor in ['n','ne','nw']: self._top = -self.topPadding elif boxAnchor in ['s','sw','se']: self._top = self._height-self.topPadding else: self._top = 0.5*self._eheight self._bottom = self._top - self._eheight if boxAnchor in ['ne','e','se']: self._left = self.leftPadding - self._width elif boxAnchor in ['nw','w','sw']: self._left = self.leftPadding else: self._left = -self._ewidth*0.5 self._right = self._left+self._ewidth def computeSize(self): # the thing will draw in its own coordinate system ddfKlass = getattr(self,'ddfKlass',None) if not ddfKlass: self._lineWidths = [] self._lines = simpleSplit(self._text,self.fontName,self.fontSize,self.maxWidth) if not self.width: self._width = self.leftPadding+self.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 self._getBaseLineRatio() if self.leading: self._leading = self.leading elif self.useAscentDescent: self._leading = self._ascent - self._descent else: self._leading = self.fontSize*1.2 objH = self._leading*len(self._lines) else: if self.ddf is None: raise RuntimeError('DirectDrawFlowable class is not available you need the rlextra package as well as reportlab') sty = dict( name='xlabel-generated', fontName=self.fontName, fontSize=self.fontSize, fillColor=self.fillColor, strokeColor=self.strokeColor, ) if not self.ddfStyle: sty = ParagraphStyle(**sty) elif issubclass(self.ddfStyle,PropertySet): sty = self.ddfStyle(**sty) else: sty = self.ddfStyle.clone() self._style = sty self._getBaseLineRatio() if self.useAscentDescent: sty.autoLeading = True sty.leading = self._ascent - self._descent else: sty.leading = self.leading if self.leading else self.fontSize*1.2 self._leading = sty.leading ta = self._getTextAnchor() aW = self.maxWidth or 0x7fffffff if ta!='start': sty.alignment = TA_LEFT obj = ddfKlass(self._text,style=sty) _, objH = obj.wrap(aW,0x7fffffff) aW = self.maxWidth or obj._width_max sty.alignment = _ta2al[ta] self._ddfObj = obj = ddfKlass(self._text,style=sty) _, objH = obj.wrap(aW,0x7fffffff) if not self.width: self._width = self.leftPadding+self.rightPadding self._width += obj._width_max else: self._width = self.width self._computeSizeEnd(objH) 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) ddfKlass = getattr(self,'ddfKlass',None) if ddfKlass: x = self._left else: 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) ) if ddfKlass: g1 = Group() g1.translate(x,self._top-self._eheight) g1.add(self.ddf(self._ddfObj)) g.add(g1) else: 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()
class ISBNBarcodeWidget(Ean13BarcodeWidget): """ ISBN Barcodes optionally print the EAN-5 supplemental price barcode (with the price in dollars or pounds). Set price to a string that follows the EAN-5 for ISBN spec: leading digit 0, 1 = GBP 3 = AUD 4 = NZD 5 = USD 6 = CAD next 4 digits = price between 00.00 and 99.98, i.e.: price='52499' # $24.99 USD """ codeName = 'ISBN' _attrMap = AttrMap( BASE=Ean13BarcodeWidget, price=AttrMapValue(NoneOr(nDigits(5)), desc='None or the price to display'), ) def draw(self): g = Ean13BarcodeWidget.draw(self) price = getattr(self, 'price', None) if not price: return g bounds = g.getBounds() x = bounds[2] pricecode = Ean5BarcodeWidget(x=x, value=price, price=True, humanReadable=True, barHeight=self.barHeight, quiet=self.quiet) g.add(pricecode) return g def _add_human_readable(self, s, gAdd): Ean13BarcodeWidget._add_human_readable(self, s, gAdd) barWidth = self.barWidth barHeight = self.barHeight fontSize = self.fontSize textColor = self.textColor fontName = self.fontName fth = fontSize * 1.2 y = self.y + 0.2 * fth + barHeight x = self._lquiet * barWidth isbn = 'ISBN ' segments = [s[0:3], s[3:4], s[4:9], s[9:12], s[12]] isbn += '-'.join(segments) gAdd( String(x, y, isbn, fontName=fontName, fontSize=fontSize, fillColor=textColor))