diff options
Diffstat (limited to 'bin/reportlab/docs/graphguide/ch4_widgets.py')
| -rw-r--r-- | bin/reportlab/docs/graphguide/ch4_widgets.py | 422 |
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 |
