summaryrefslogtreecommitdiff
path: root/bin/reportlab/graphics/charts/legends.py
diff options
context:
space:
mode:
Diffstat (limited to 'bin/reportlab/graphics/charts/legends.py')
-rw-r--r--bin/reportlab/graphics/charts/legends.py486
1 files changed, 486 insertions, 0 deletions
diff --git a/bin/reportlab/graphics/charts/legends.py b/bin/reportlab/graphics/charts/legends.py
new file mode 100644
index 00000000000..f991bd7ed7c
--- /dev/null
+++ b/bin/reportlab/graphics/charts/legends.py
@@ -0,0 +1,486 @@
+#Copyright ReportLab Europe Ltd. 2000-2004
+#see license.txt for license details
+#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/legends.py
+"""This will be a collection of legends to be used with charts.
+"""
+__version__=''' $Id$ '''
+
+import string, copy
+
+from reportlab.lib import colors
+from reportlab.lib.validators import isNumber, OneOf, isString, isColorOrNone, isNumberOrNone, isListOfNumbersOrNone
+from reportlab.lib.attrmap import *
+from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
+from reportlab.graphics.widgetbase import Widget
+from reportlab.graphics.shapes import Drawing, Group, String, Rect, Line, STATE_DEFAULTS
+
+
+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"),
+ 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"),
+ callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))"),
+ )
+
+ 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']
+
+ def _calculateMaxWidth(self, colorNamePairs):
+ "Calculate the maximum width of some given strings."
+ m = 0
+ for t in map(lambda p:str(p[1]),colorNamePairs):
+ if t:
+ for s in string.split(t,'\n'):
+ m = max(m,stringWidth(s, self.fontName, self.fontSize))
+ return m
+
+
+ def _calcHeight(self):
+ deltay = self.deltay
+ dy = self.dy
+ thisy = upperlefty = self.y - dy
+ ascent=getFont(self.fontName).face.ascent/1000.
+ if ascent==0: ascent=0.718 # default (from helvetica)
+ leading = self.fontSize*1.2
+ columnCount = 0
+ count = 0
+ lowy = upperlefty
+ for unused, name in colorNamePairs:
+ T = string.split(name and str(name) or '','\n')
+ S = []
+ # thisy+dy/2 = y+leading/2
+ y = thisy+(dy-ascent)*0.5-leading
+ newy = thisy-max(deltay,len(S)*leading)
+ lowy = min(y,newy)
+ if count == columnMaximum-1:
+ count = 0
+ thisy = upperlefty
+ columnCount = columnCount + 1
+ else:
+ thisy = newy
+ count = count+1
+ return upperlefty - lowy
+
+ def draw(self):
+ g = Group()
+ colorNamePairs = self.colorNamePairs
+ thisx = upperleftx = self.x
+ thisy = upperlefty = self.y - self.dy
+ dx, dy, alignment, columnMaximum = self.dx, self.dy, self.alignment, self.columnMaximum
+ deltax, deltay, dxTextSpace = self.deltax, self.deltay, self.dxTextSpace
+ fontName, fontSize, fillColor = self.fontName, self.fontSize, self.fillColor
+ strokeWidth, strokeColor = self.strokeWidth, self.strokeColor
+ leading = fontSize*1.2
+ if not deltay:
+ deltay = max(dy,leading)+self.autoYPadding
+ if not deltax:
+ maxWidth = self._calculateMaxWidth(colorNamePairs)
+ deltax = maxWidth+dx+dxTextSpace+self.autoXPadding
+ else:
+ if alignment=='left': maxWidth = self._calculateMaxWidth(colorNamePairs)
+
+ 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=ascent*fontSize # normalize
+
+ columnCount = 0
+ count = 0
+ callout = getattr(self,'callout',None)
+ for col, name in colorNamePairs:
+ T = string.split(name and str(name) or '','\n')
+ S = []
+ # thisy+dy/2 = y+leading/2
+ y = thisy+(dy-ascent)*0.5
+ if callout: callout(self,g,thisx,y,colorNamePairs[count])
+ if alignment == "left":
+ for t in T:
+ # align text to left
+ s = String(thisx+maxWidth,y,t)
+ s.textAnchor = "end"
+ S.append(s)
+ y = y-leading
+ x = thisx+maxWidth+dxTextSpace
+ elif alignment == "right":
+ for t in T:
+ # align text to right
+ s = String(thisx+dx+dxTextSpace, y, t)
+ s.textAnchor = "start"
+ S.append(s)
+ y = y-leading
+ x = thisx
+ else:
+ raise ValueError, "bad alignment"
+
+ # Make a 'normal' color swatch...
+ if isinstance(col, colors.Color):
+ r = Rect(x, thisy, dx, dy)
+ r.fillColor = col
+ r.strokeColor = strokeColor
+ r.strokeWidth = strokeWidth
+ g.add(r)
+ else:
+ #try and see if we should do better.
+ 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 count%columnMaximum == columnMaximum-1:
+ thisx = thisx+deltax
+ thisy = upperlefty
+ columnCount = columnCount + 1
+ else:
+ thisy = thisy-max(deltay,len(S)*leading)
+ count = count+1
+
+ return g
+
+
+class LineSwatch(Widget):
+ """basically a Line with properties added so it can be used in a LineLegend"""
+ _attrMap = AttrMap(
+ x = AttrMapValue(isNumber, desc="x-coordinate for swatch line start point"),
+ y = AttrMapValue(isNumber, desc="y-coordinate for swatch line start point"),
+ width = AttrMapValue(isNumber, desc="length of swatch line"),
+ height = AttrMapValue(isNumber, desc="used for line strokeWidth"),
+ strokeColor = AttrMapValue(isColorOrNone, desc="color of swatch line"),
+ strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc="dash array for swatch line"),
+ )
+
+ def __init__(self):
+ from reportlab.lib.colors import red
+ from reportlab.graphics.shapes import Line
+ self.x = 0
+ self.y = 0
+ self.width = 20
+ self.height = 1
+ self.strokeColor = red
+ self.strokeDashArray = None
+
+ def draw(self):
+ l = Line(self.x,self.y,self.x+self.width,self.y)
+ l.strokeColor = self.strokeColor
+ l.strokeDashArray = self.strokeDashArray
+ l.strokeWidth = self.height
+ return l
+
+class LineLegend(Legend):
+ """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(
+ 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 line-swatches"),
+ deltay = AttrMapValue(isNumberOrNone, desc="y-distance between neighbouring line-swatches"),
+ dxTextSpace = AttrMapValue(isNumber, desc="Distance between line-swatches and text"),
+ autoXPadding = AttrMapValue(isNumber, desc="x Padding between columns if deltax=None"),
+ autoYPadding = AttrMapValue(isNumber, desc="y Padding between rows if deltay=None"),
+ dx = AttrMapValue(isNumber, desc="Width of line-swatch - ie length of the line"),
+ dy = AttrMapValue(isNumber, desc="Height of line-swatch - ie strokeWidth to be used for the line"),
+ columnMaximum = AttrMapValue(isNumber, desc="Max. number of items per column"),
+ alignment = AttrMapValue(OneOf("left", "right"), desc="Alignment of text with respect to line-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="Stroke color of the line-swatches"),
+ strokeWidth = AttrMapValue(isNumber, desc="Width of the line-swatches"),
+ callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))"),
+ )
+
+ def __init__(self):
+ Legend.__init__(self)
+
+ # Size of swatch rectangle.
+ self.dx = 10 #width of line
+ self.dy = 2 #strokeWidth for line
+
+ # Color/name pairs.
+ self.colorNamePairs = []
+ for col, colName in [ (colors.red, "red"),
+ (colors.blue, "blue"),
+ (colors.green, "green"),
+ (colors.pink, "pink"),
+ (colors.yellow, "yellow") ]:
+ l = LineSwatch()
+ l.strokeColor = col
+ self.colorNamePairs.append((l, colName))
+
+ # Font name and size of the labels.
+ self.fillColor = STATE_DEFAULTS['fillColor']
+ self.strokeColor = STATE_DEFAULTS['strokeColor']
+ self.strokeWidth = STATE_DEFAULTS['strokeWidth']
+
+ def draw(self):
+ g = Group()
+ colorNamePairs = self.colorNamePairs
+ thisx = upperleftx = self.x
+ thisy = upperlefty = self.y - self.dy
+ dx, dy, alignment, columnMaximum = self.dx, self.dy, self.alignment, self.columnMaximum
+ deltax, deltay, dxTextSpace = self.deltax, self.deltay, self.dxTextSpace
+ fontName, fontSize, fillColor = self.fontName, self.fontSize, self.fillColor
+ strokeWidth, strokeColor = self.strokeWidth, self.strokeColor
+ leading = fontSize*1.2
+ if not deltay:
+ deltay = max(dy,leading)+self.autoYPadding
+ if not deltax:
+ maxWidth = self._calculateMaxWidth(colorNamePairs)
+ deltax = maxWidth+dx+dxTextSpace+self.autoXPadding
+ else:
+ if alignment=='left': maxWidth = self._calculateMaxWidth(colorNamePairs)
+
+ 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=ascent*fontSize # normalize
+
+ columnCount = 0
+ count = 0
+ callout = getattr(self,'callout',None)
+ for col, name in colorNamePairs:
+ T = string.split(name and str(name) or '','\n')
+ S = []
+ # thisy+dy/2 = y+leading/2
+ y = thisy+(dy-ascent)*0.5
+ if callout: callout(self,g,thisx,y,colorNamePairs[count])
+ if alignment == "left":
+ for t in T:
+ # align text to left
+ s = String(thisx+maxWidth,y,t)
+ s.textAnchor = "end"
+ S.append(s)
+ y = y-leading
+ x = thisx+maxWidth+dxTextSpace
+ elif alignment == "right":
+ for t in T:
+ # align text to right
+ s = String(thisx+dx+dxTextSpace, y, t)
+ s.textAnchor = "start"
+ S.append(s)
+ y = y-leading
+ x = thisx
+ else:
+ raise ValueError, "bad alignment"
+
+ # Make a 'normal' color line-swatch...
+ if isinstance(col, colors.Color):
+ l = LineSwatch()
+ l.x = x
+ l.y = thisy
+ l.width = dx
+ l.height = dy
+ l.strokeColor = col
+ g.add(l)
+ else:
+ #try and see if we should do better.
+ 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 count%columnMaximum == columnMaximum-1:
+ thisx = thisx+deltax
+ thisy = upperlefty
+ columnCount = columnCount + 1
+ else:
+ thisy = thisy-max(deltay,len(S)*leading)
+ count = count+1
+
+ 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 = string.split('red green blue yellow pink black white', ' ')
+ items = map(lambda i:(getattr(colors, i), i), items)
+ legend.colorNamePairs = items
+
+ d.add(legend, 'legend')
+
+ return d
+
+
+def sample1c():
+ "Make sample legend."
+
+ d = Drawing(200, 100)
+
+ legend = Legend()
+ legend.alignment = 'right'
+ legend.x = 0
+ legend.y = 100
+ legend.dxTextSpace = 5
+ items = string.split('red green blue yellow pink black white', ' ')
+ items = map(lambda i:(getattr(colors, i), i), items)
+ legend.colorNamePairs = items
+
+ d.add(legend, 'legend')
+
+ return d
+
+
+def sample2c():
+ "Make sample legend."
+
+ d = Drawing(200, 100)
+
+ legend = Legend()
+ legend.alignment = 'right'
+ legend.x = 20
+ legend.y = 90
+ legend.deltax = 60
+ legend.dxTextSpace = 10
+ legend.columnMaximum = 4
+ items = string.split('red green blue yellow pink black white', ' ')
+ items = map(lambda i:(getattr(colors, i), i), items)
+ legend.colorNamePairs = items
+
+ d.add(legend, 'legend')
+
+ return d
+
+def sample3():
+ "Make sample legend with line swatches."
+
+ d = Drawing(200, 100)
+
+ legend = LineLegend()
+ legend.alignment = 'right'
+ legend.x = 20
+ legend.y = 90
+ legend.deltax = 60
+ legend.dxTextSpace = 10
+ legend.columnMaximum = 4
+ items = string.split('red green blue yellow pink black white', ' ')
+ items = map(lambda i:(getattr(colors, i), i), items)
+ legend.colorNamePairs = items
+ d.add(legend, 'legend')
+
+ return d
+
+
+def sample3a():
+ "Make sample legend with line swatches and dasharrays on the lines."
+
+ d = Drawing(200, 100)
+
+ legend = LineLegend()
+ legend.alignment = 'right'
+ legend.x = 20
+ legend.y = 90
+ legend.deltax = 60
+ legend.dxTextSpace = 10
+ legend.columnMaximum = 4
+ items = string.split('red green blue yellow pink black white', ' ')
+ darrays = ([2,1], [2,5], [2,2,5,5], [1,2,3,4], [4,2,3,4], [1,2,3,4,5,6], [1])
+ cnp = []
+ for i in range(0, len(items)):
+ l = LineSwatch()
+ l.strokeColor = getattr(colors, items[i])
+ l.strokeDashArray = darrays[i]
+ cnp.append((l, items[i]))
+ legend.colorNamePairs = cnp
+ d.add(legend, 'legend')
+
+ return d