summaryrefslogtreecommitdiff
path: root/bin/reportlab/docs/graphguide/ch4_widgets.py
diff options
context:
space:
mode:
authorpinky <>2007-01-07 21:41:39 +0000
committerpinky <>2007-01-07 21:41:39 +0000
commit05bb5d58295cd959c34f1fbcd5b605cd6dafaacc (patch)
tree3b3651037037c1617a54ef3d216e8c11ea8cdddf /bin/reportlab/docs/graphguide/ch4_widgets.py
parent62524af2f41454a2302efb21f15a95318d257a76 (diff)
Repotrlab 2.0
bzr revid: pinky-b8333ccaab5dad8ff965b6c380fadadf30d5212a
Diffstat (limited to 'bin/reportlab/docs/graphguide/ch4_widgets.py')
-rw-r--r--bin/reportlab/docs/graphguide/ch4_widgets.py422
1 files changed, 422 insertions, 0 deletions
diff --git a/bin/reportlab/docs/graphguide/ch4_widgets.py b/bin/reportlab/docs/graphguide/ch4_widgets.py
new file mode 100644
index 00000000000..eed115d6a86
--- /dev/null
+++ b/bin/reportlab/docs/graphguide/ch4_widgets.py
@@ -0,0 +1,422 @@
+#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/docs/graphguide/ch4_widgets.py
+
+from reportlab.tools.docco.rl_doc_utils import *
+from reportlab.graphics.shapes import *
+from reportlab.graphics.widgets import signsandsymbols
+
+heading1("Widgets")
+
+disc("""
+We now describe widgets and how they relate to shapes.
+Using many examples it is shown how widgets make reusable
+graphics components.
+""")
+
+
+heading2("Shapes vs. Widgets")
+
+disc("""Up until now, Drawings have been 'pure data'. There is no code in them
+ to actually do anything, except assist the programmer in checking and
+ inspecting the drawing. In fact, that's the cornerstone of the whole
+ concept and is what lets us achieve portability - a renderer only
+ needs to implement the primitive shapes.""")
+
+disc("""We want to build reusable graphic objects, including a powerful chart
+ library. To do this we need to reuse more tangible things than
+ rectangles and circles. We should be able to write objects for other
+ to reuse - arrows, gears, text boxes, UML diagram nodes, even fully
+ fledged charts.""")
+
+disc("""
+The Widget standard is a standard built on top of the shapes module.
+Anyone can write new widgets, and we can build up libraries of them.
+Widgets support the $getProperties()$ and $setProperties()$ methods,
+so you can inspect and modify as well as document them in a uniform
+way.
+""")
+
+bullet("A widget is a reusable shape ")
+bullet("""it can be initialized with no arguments
+ when its $draw()$ method is called it creates a primitive Shape or a
+ Group to represent itself""")
+bullet("""It can have any parameters you want, and they can drive the way it is
+ drawn""")
+bullet("""it has a $demo()$ method which should return an attractively drawn
+ example of itself in a 200x100 rectangle. This is the cornerstone of
+ the automatic documentation tools. The $demo()$ method should also have
+ a well written docstring, since that is printed too!""")
+
+disc("""Widgets run contrary to the idea that a drawing is just a bundle of
+ shapes; surely they have their own code? The way they work is that a
+ widget can convert itself to a group of primitive shapes. If some of
+ its components are themselves widgets, they will get converted too.
+ This happens automatically during rendering; the renderer will not see
+ your chart widget, but just a collection of rectangles, lines and
+ strings. You can also explicitly 'flatten out' a drawing, causing all
+ widgets to be converted to primitives.""")
+
+
+heading2("Using a Widget")
+
+disc("""
+Let's imagine a simple new widget.
+We will use a widget to draw a face, then show how it was implemented.""")
+
+eg("""
+>>> from reportlab.lib import colors
+>>> from reportlab.graphics import shapes
+>>> from reportlab.graphics import widgetbase
+>>> from reportlab.graphics import renderPDF
+>>> d = shapes.Drawing(200, 100)
+>>> f = widgetbase.Face()
+>>> f.skinColor = colors.yellow
+>>> f.mood = "sad"
+>>> d.add(f)
+>>> renderPDF.drawToFile(d, 'face.pdf', 'A Face')
+""")
+
+from reportlab.graphics import widgetbase
+d = Drawing(200, 120)
+f = widgetbase.Face()
+f.x = 50
+f.y = 10
+f.skinColor = colors.yellow
+f.mood = "sad"
+d.add(f)
+draw(d, 'A sample widget')
+
+disc("""
+Let's see what properties it has available, using the $setProperties()$
+method we have seen earlier:
+""")
+
+eg("""
+>>> f.dumpProperties()
+eyeColor = Color(0.00,0.00,1.00)
+mood = sad
+size = 80
+skinColor = Color(1.00,1.00,0.00)
+x = 10
+y = 10
+>>>
+""")
+
+disc("""
+One thing which seems strange about the above code is that we did not
+set the size or position when we made the face.
+This is a necessary trade-off to allow a uniform interface for
+constructing widgets and documenting them - they cannot require
+arguments in their $__init__()$ method.
+Instead, they are generally designed to fit in a 200 x 100
+window, and you move or resize them by setting properties such as
+x, y, width and so on after creation.
+""")
+
+disc("""
+In addition, a widget always provides a $demo()$ method.
+Simple ones like this always do something sensible before setting
+properties, but more complex ones like a chart would not have any
+data to plot.
+The documentation tool calls $demo()$ so that your fancy new chart
+class can create a drawing showing what it can do.
+""")
+
+disc("""
+Here are a handful of simple widgets available in the module
+<i>signsandsymbols.py</i>:
+""")
+
+from reportlab.graphics.shapes import Drawing
+from reportlab.graphics.widgets import signsandsymbols
+
+d = Drawing(230, 230)
+
+ne = signsandsymbols.NoEntry()
+ds = signsandsymbols.DangerSign()
+fd = signsandsymbols.FloppyDisk()
+ns = signsandsymbols.NoSmoking()
+
+ne.x, ne.y = 10, 10
+ds.x, ds.y = 120, 10
+fd.x, fd.y = 10, 120
+ns.x, ns.y = 120, 120
+
+d.add(ne)
+d.add(ds)
+d.add(fd)
+d.add(ns)
+
+draw(d, 'A few samples from signsandsymbols.py')
+
+disc("""
+And this is the code needed to generate them as seen in the drawing above:
+""")
+
+eg("""
+from reportlab.graphics.shapes import Drawing
+from reportlab.graphics.widgets import signsandsymbols
+
+d = Drawing(230, 230)
+
+ne = signsandsymbols.NoEntry()
+ds = signsandsymbols.DangerSign()
+fd = signsandsymbols.FloppyDisk()
+ns = signsandsymbols.NoSmoking()
+
+ne.x, ne.y = 10, 10
+ds.x, ds.y = 120, 10
+fd.x, fd.y = 10, 120
+ns.x, ns.y = 120, 120
+
+d.add(ne)
+d.add(ds)
+d.add(fd)
+d.add(ns)
+""")
+
+
+heading2("Compound Widgets")
+
+disc("""Let's imagine a compound widget which draws two faces side by side.
+ This is easy to build when you have the Face widget.""")
+
+eg("""
+>>> tf = widgetbase.TwoFaces()
+>>> tf.faceOne.mood
+'happy'
+>>> tf.faceTwo.mood
+'sad'
+>>> tf.dumpProperties()
+faceOne.eyeColor = Color(0.00,0.00,1.00)
+faceOne.mood = happy
+faceOne.size = 80
+faceOne.skinColor = None
+faceOne.x = 10
+faceOne.y = 10
+faceTwo.eyeColor = Color(0.00,0.00,1.00)
+faceTwo.mood = sad
+faceTwo.size = 80
+faceTwo.skinColor = None
+faceTwo.x = 100
+faceTwo.y = 10
+>>>
+""")
+
+disc("""The attributes 'faceOne' and 'faceTwo' are deliberately exposed so you
+ can get at them directly. There could also be top-level attributes,
+ but there aren't in this case.""")
+
+
+heading2("Verifying Widgets")
+
+disc("""The widget designer decides the policy on verification, but by default
+ they work like shapes - checking every assignment - if the designer
+ has provided the checking information.""")
+
+
+heading2("Implementing Widgets")
+
+disc("""We tried to make it as easy to implement widgets as possible. Here's
+ the code for a Face widget which does not do any type checking:""")
+
+eg("""
+class Face(Widget):
+ \"\"\"This draws a face with two eyes, mouth and nose.\"\"\"
+
+ def __init__(self):
+ self.x = 10
+ self.y = 10
+ self.size = 80
+ self.skinColor = None
+ self.eyeColor = colors.blue
+ self.mood = 'happy'
+
+ def draw(self):
+ s = self.size # abbreviate as we will use this a lot
+ g = shapes.Group()
+ g.transform = [1,0,0,1,self.x, self.y]
+ # background
+ g.add(shapes.Circle(s * 0.5, s * 0.5, s * 0.5,
+ fillColor=self.skinColor))
+ # CODE OMITTED TO MAKE MORE SHAPES
+ return g
+""")
+
+disc("""We left out all the code to draw the shapes in this document, but you
+ can find it in the distribution in $widgetbase.py$.""")
+
+disc("""By default, any attribute without a leading underscore is returned by
+ setProperties. This is a deliberate policy to encourage consistent
+ coding conventions.""")
+
+disc("""Once your widget works, you probably want to add support for
+ verification. This involves adding a dictionary to the class called
+ $_verifyMap$, which map from attribute names to 'checking functions'.
+ The $widgetbase.py$ module defines a bunch of checking functions with names
+ like $isNumber$, $isListOfShapes$ and so on. You can also simply use $None$,
+ which means that the attribute must be present but can have any type.
+ And you can and should write your own checking functions. We want to
+ restrict the "mood" custom attribute to the values "happy", "sad" or
+ "ok". So we do this:""")
+
+eg("""
+class Face(Widget):
+ \"\"\"This draws a face with two eyes. It exposes a
+ couple of properties to configure itself and hides
+ all other details\"\"\"
+ def checkMood(moodName):
+ return (moodName in ('happy','sad','ok'))
+ _verifyMap = {
+ 'x': shapes.isNumber,
+ 'y': shapes.isNumber,
+ 'size': shapes.isNumber,
+ 'skinColor':shapes.isColorOrNone,
+ 'eyeColor': shapes.isColorOrNone,
+ 'mood': checkMood
+ }
+""")
+
+disc("""This checking will be performed on every attribute assignment; or, if
+ $config.shapeChecking$ is off, whenever you call $myFace.verify()$.""")
+
+
+heading2("Documenting Widgets")
+
+disc("""
+We are working on a generic tool to document any Python package or
+module; this is already checked into ReportLab and will be used to
+generate a reference for the ReportLab package.
+When it encounters widgets, it adds extra sections to the
+manual including:""")
+
+bullet("the doc string for your widget class ")
+bullet("the code snippet from your <i>demo()</i> method, so people can see how to use it")
+bullet("the drawing produced by the <i>demo()</i> method ")
+bullet("the property dump for the widget in the drawing. ")
+
+disc("""
+This tool will mean that we can have guaranteed up-to-date
+documentation on our widgets and charts, both on the web site
+and in print; and that you can do the same for your own widgets,
+too!
+""")
+
+
+heading2("Widget Design Strategies")
+
+disc("""We could not come up with a consistent architecture for designing
+ widgets, so we are leaving that problem to the authors! If you do not
+ like the default verification strategy, or the way
+ $setProperties/getProperties$ works, you can override them yourself.""")
+
+disc("""For simple widgets it is recommended that you do what we did above:
+ select non-overlapping properties, initialize every property on
+ $__init__$ and construct everything when $draw()$ is called. You can
+ instead have $__setattr__$ hooks and have things updated when certain
+ attributes are set. Consider a pie chart. If you want to expose the
+ individual wedges, you might write code like this:""")
+
+eg("""
+from reportlab.graphics.charts import piecharts
+pc = piecharts.Pie()
+pc.defaultColors = [navy, blue, skyblue] #used in rotation
+pc.data = [10,30,50,25]
+pc.slices[7].strokeWidth = 5
+""")
+#removed 'pc.backColor = yellow' from above code example
+
+disc("""The last line is problematic as we have only created four wedges - in
+ fact we might not have created them yet. Does $pc.wedges[7]$ raise an
+ error? Is it a prescription for what should happen if a seventh wedge
+ is defined, used to override the default settings? We dump this
+ problem squarely on the widget author for now, and recommend that you
+ get a simple one working before exposing 'child objects' whose
+ existence depends on other properties' values :-)""")
+
+disc("""We also discussed rules by which parent widgets could pass properties
+ to their children. There seems to be a general desire for a global way
+ to say that 'all wedges get their lineWidth from the lineWidth of
+ their parent' without a lot of repetitive coding. We do not have a
+ universal solution, so again leave that to widget authors. We hope
+ people will experiment with push-down, pull-down and pattern-matching
+ approaches and come up with something nice. In the meantime, we
+ certainly can write monolithic chart widgets which work like the ones
+ in, say, Visual Basic and Delphi.""")
+
+disc("""For now have a look at the following sample code using an early
+ version of a pie chart widget and the output it generates:""")
+
+eg("""
+from reportlab.lib.colors import *
+from reportlab.graphics import shapes,renderPDF
+from reportlab.graphics.charts.piecharts import Pie
+
+d = Drawing(400,200)
+d.add(String(100,175,"Without labels", textAnchor="middle"))
+d.add(String(300,175,"With labels", textAnchor="middle"))
+
+pc = Pie()
+pc.x = 25
+pc.y = 50
+pc.data = [10,20,30,40,50,60]
+pc.slices[0].popout = 5
+d.add(pc, 'pie1')
+
+pc2 = Pie()
+pc2.x = 150
+pc2.y = 50
+pc2.data = [10,20,30,40,50,60]
+pc2.labels = ['a','b','c','d','e','f']
+d.add(pc2, 'pie2')
+
+pc3 = Pie()
+pc3.x = 275
+pc3.y = 50
+pc3.data = [10,20,30,40,50,60]
+pc3.labels = ['a','b','c','d','e','f']
+pc3.wedges.labelRadius = 0.65
+pc3.wedges.fontName = "Helvetica-Bold"
+pc3.wedges.fontSize = 16
+pc3.wedges.fontColor = colors.yellow
+d.add(pc3, 'pie3')
+""")
+
+# Hack to force a new paragraph before the todo() :-(
+disc("")
+
+from reportlab.lib.colors import *
+from reportlab.graphics import shapes,renderPDF
+from reportlab.graphics.charts.piecharts import Pie
+
+d = Drawing(400,200)
+d.add(String(100,175,"Without labels", textAnchor="middle"))
+d.add(String(300,175,"With labels", textAnchor="middle"))
+
+pc = Pie()
+pc.x = 25
+pc.y = 50
+pc.data = [10,20,30,40,50,60]
+pc.slices[0].popout = 5
+d.add(pc, 'pie1')
+
+pc2 = Pie()
+pc2.x = 150
+pc2.y = 50
+pc2.data = [10,20,30,40,50,60]
+pc2.labels = ['a','b','c','d','e','f']
+d.add(pc2, 'pie2')
+
+pc3 = Pie()
+pc3.x = 275
+pc3.y = 50
+pc3.data = [10,20,30,40,50,60]
+pc3.labels = ['a','b','c','d','e','f']
+pc3.slices.labelRadius = 0.65
+pc3.slices.fontName = "Helvetica-Bold"
+pc3.slices.fontSize = 16
+pc3.slices.fontColor = colors.yellow
+d.add(pc3, 'pie3')
+
+draw(d, 'Some sample Pies') \ No newline at end of file