"Fossies" - the Fresh Open Source Software Archive

Member "reportlab-3.5.32/docs/userguide/graph_charts.py" (1 Oct 2019, 52769 Bytes) of package /linux/privat/reportlab-3.5.32.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "graph_charts.py" see the Fossies "Dox" file reference documentation.

    1 #Copyright ReportLab Europe Ltd. 2000-2017
    2 #see license.txt for license details
    3 __version__='3.3.0'
    4 from tools.docco.rl_doc_utils import *
    5 from reportlab.graphics.shapes import *
    6 
    7 heading2("Charts")
    8 
    9 disc("""
   10 The motivation for much of this is to create a flexible chart
   11 package.
   12 This section presents a treatment of the ideas behind our charting
   13 model, what the design goals are and what components of the chart
   14 package already exist.
   15 """)
   16 
   17 
   18 heading3("Design Goals")
   19 
   20 disc("Here are some of the design goals: ")
   21 
   22 disc("<i>Make simple top-level use really simple </i>")
   23 disc("""<para lindent="+36">It should be possible to create a simple chart with minimum lines of
   24        code, yet have it 'do the right things' with sensible automatic
   25        settings. The pie chart snippets above do this. If a real chart has
   26        many subcomponents, you still should not need to interact with them
   27        unless you want to customize what they do.</para>""")
   28 
   29 disc("<i>Allow precise positioning </i>")
   30 disc("""<para lindent="+36">An absolute requirement in publishing and graphic design is to control
   31        the placing and style of every element. We will try to have properties
   32        that specify things in fixed sizes and proportions of the drawing,
   33        rather than having automatic resizing. Thus, the 'inner plot
   34        rectangle' will not magically change when you make the font size of
   35        the y labels bigger, even if this means your labels can spill out of
   36        the left edge of the chart rectangle. It is your job to preview the
   37        chart and choose sizes and spaces which will work.</para>""")
   38 
   39 disc("""<para lindent="+36">Some things do need to be automatic. For example, if you want to fit N
   40        bars into a 200 point space and don't know N in advance, we specify
   41        bar separation as a percentage of the width of a bar rather than a
   42        point size, and let the chart work it out. This is still deterministic
   43        and controllable.</para>""")
   44 
   45 disc("<i>Control child elements individually or as a group</i>")
   46 disc("""<para lindent="+36">We use smart collection classes that let you customize a group of
   47        things, or just one of them. For example you can do this in our
   48        experimental pie chart:</para>""")
   49 
   50 eg("""
   51 d = Drawing(400,200)
   52 pc = Pie()
   53 pc.x = 150
   54 pc.y = 50
   55 pc.data = [10,20,30,40,50,60]
   56 pc.labels = ['a','b','c','d','e','f']
   57 pc.slices.strokeWidth=0.5
   58 pc.slices[3].popout = 20
   59 pc.slices[3].strokeWidth = 2
   60 pc.slices[3].strokeDashArray = [2,2]
   61 pc.slices[3].labelRadius = 1.75
   62 pc.slices[3].fontColor = colors.red
   63 d.add(pc, '')
   64 """)
   65 
   66 disc("""<para lindent="+36">pc.slices[3] actually lazily creates a little object which holds
   67        information about the slice in question; this will be used to format a
   68        fourth slice at draw-time if there is one.</para>""")
   69 
   70 disc("<i>Only expose things you should change </i>")
   71 disc("""<para lindent="+36">It would be wrong from a statistical viewpoint to let you directly
   72        adjust the angle of one of the pie slices in the above example, since
   73        that is determined by the data. So not everything will be exposed
   74        through the public properties. There may be 'back doors' to let you
   75        violate this when you really need to, or methods to provide advanced
   76        functionality, but in general properties will be orthogonal.</para>""")
   77 
   78 disc("<i>Composition and component based </i>")
   79 disc("""<para lindent="+36">Charts are built out of reusable child widgets. A Legend is an
   80        easy-to-grasp example. If you need a specialized type of legend (e.g.
   81        circular colour swatches), you should subclass the standard Legend
   82        widget. Then you could either do something like...</para>""")
   83 
   84 eg("""
   85 c = MyChartWithLegend()
   86 c.legend = MyNewLegendClass()    # just change it
   87 c.legend.swatchRadius = 5    # set a property only relevant to the new one
   88 c.data = [10,20,30]   #   and then configure as usual...
   89 """)
   90 
   91 disc("""<para lindent="+36">...or create/modify your own chart or drawing class which creates one
   92        of these by default. This is also very relevant for time series
   93        charts, where there can be many styles of x axis.</para>""")
   94 
   95 disc("""<para lindent="+36">Top level chart classes will create a number of such components, and
   96        then either call methods or set private properties to tell them their
   97        height and position - all the stuff which should be done for you and
   98        which you cannot customise. We are working on modelling what the
   99        components should be and will publish their APIs here as a consensus
  100        emerges.</para>""")
  101 
  102 disc("<i>Multiples </i>")
  103 disc("""<para lindent="+36">A corollary of the component approach is that you can create diagrams
  104        with multiple charts, or custom data graphics. Our favourite example
  105        of what we are aiming for is the weather report in our gallery
  106        contributed by a user; we'd like to make it easy to create such
  107        drawings, hook the building blocks up to their legends, and feed that
  108        data in a consistent way.</para>""")
  109 disc("""<para lindent="+36">(If you want to see the image, it is available on our website
  110 <font color="blue"><a href="https://www.reportlab.com/media/imadj/data/RLIMG_e5e5cb85cc0a555f5433528ac38c5884.PDF">here</a></font>)</para>""")
  111 
  112 
  113 ##heading3("Key Concepts and Components")
  114 heading3("Overview")
  115 
  116 disc("""A chart or plot is an object which is placed on a drawing; it is not
  117        itself a drawing. You can thus control where it goes, put several on
  118        the same drawing, or add annotations.""")
  119 
  120 disc("""Charts have two axes; axes may be Value or Category axes. Axes in turn
  121        have a Labels property which lets you configure all text labels or
  122        each one individually. Most of the configuration details which vary
  123        from chart to chart relate to axis properties, or axis labels.""")
  124 
  125 disc("""Objects expose properties through the interfaces discussed in the
  126        previous section; these are all optional and are there to let the end
  127        user configure the appearance. Things which must be set for a chart to
  128        work, and essential communication between a chart and its components,
  129        are handled through methods.""")
  130 
  131 disc("""You can subclass any chart component and use your replacement instead
  132        of the original provided you implement the essential methods and
  133        properties.""")
  134 
  135 
  136 heading2("Labels")
  137 
  138 disc("""
  139 A label is a string of text attached to some chart element.
  140 They are used on axes, for titles or alongside axes, or attached
  141 to individual data points.
  142 Labels may contain newline characters, but only one font.
  143 """)
  144 
  145 disc("""The text and 'origin' of a label are typically set by its parent
  146        object. They are accessed by methods rather than properties. Thus, the
  147        X axis decides the 'reference point' for each tickmark label and the
  148        numeric or date text for each label. However, the end user can set
  149        properties of the label (or collection of labels) directly to affect
  150        its position relative to this origin and all of its formatting.""")
  151 
  152 eg("""
  153 from reportlab.graphics import shapes
  154 from reportlab.graphics.charts.textlabels import Label
  155 
  156 d = Drawing(200, 100)
  157 
  158 # mark the origin of the label
  159 d.add(Circle(100,90, 5, fillColor=colors.green))
  160 
  161 lab = Label()
  162 lab.setOrigin(100,90)
  163 lab.boxAnchor = 'ne'
  164 lab.angle = 45
  165 lab.dx = 0
  166 lab.dy = -20
  167 lab.boxStrokeColor = colors.green
  168 lab.setText('Some\nMulti-Line\nLabel')
  169 
  170 d.add(lab)
  171 """)
  172 
  173 
  174 from reportlab.graphics import shapes
  175 from reportlab.graphics.charts.textlabels import Label
  176 
  177 d = Drawing(200, 100)
  178 
  179 # mark the origin of the label
  180 d.add(Circle(100,90, 5, fillColor=colors.green))
  181 
  182 lab = Label()
  183 lab.setOrigin(100,90)
  184 lab.boxAnchor = 'ne'
  185 lab.angle = 45
  186 lab.dx = 0
  187 lab.dy = -20
  188 lab.boxStrokeColor = colors.green
  189 lab.setText('Some\nMulti-Line\nLabel')
  190 
  191 d.add(lab)
  192 
  193 draw(d, 'Label example')
  194 
  195 
  196 
  197 disc("""
  198 In the drawing above, the label is defined relative to the green blob.
  199 The text box should have its north-east corner ten points down from
  200 the origin, and be rotated by 45 degrees about that corner.
  201 """)
  202 
  203 disc("""
  204 At present labels have the following properties, which we believe are
  205 sufficient for all charts we have seen to date:
  206 """)
  207 
  208 disc("")
  209 
  210 data=[["Property", "Meaning"],
  211       ["dx", """The label's x displacement."""],
  212       ["dy", """The label's y displacement."""],
  213       ["angle", """The angle of rotation (counterclockwise) applied to the label."""],
  214       ["boxAnchor", "The label's box anchor, one of 'n', 'e', 'w', 's', 'ne', 'nw', 'se', 'sw'."],
  215       ["textAnchor", """The place where to anchor the label's text, one of 'start', 'middle', 'end'."""],
  216       ["boxFillColor", """The fill color used in the label's box."""],
  217       ["boxStrokeColor", "The stroke color used in the label's box."],
  218       ["boxStrokeWidth", """The line width of the label's box."""],
  219       ["fontName", """The label's font name."""],
  220       ["fontSize", """The label's font size."""],
  221       ["leading", """The leading value of the label's text lines."""],
  222       ["x", """The X-coordinate of the reference point."""],
  223       ["y", """The Y-coordinate of the reference point."""],
  224       ["width", """The label's width."""],
  225       ["height", """The label's height."""]
  226       ]
  227 t=Table(data, colWidths=(100,330))
  228 t.setStyle(TableStyle([
  229             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
  230             ('FONT',(0,1),(0,-1),'Courier',8,8),
  231             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
  232             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
  233             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
  234             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
  235             ]))
  236 getStory().append(t)
  237 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - Label properties""")
  238 
  239 disc("""
  240 To see many more examples of $Label$ objects with different
  241 combinations of properties, please have a look into the
  242 ReportLab test suite in the folder $tests$, run the
  243 script $test_charts_textlabels.py$ and look at the PDF document
  244 it generates!
  245 """)
  246 
  247 
  248 
  249 heading2("Axes")
  250 
  251 disc("""
  252 We identify two basic kinds of axes - <i>Value</i> and <i>Category</i>
  253 ones.
  254 Both come in horizontal and vertical flavors.
  255 Both can be subclassed to make very specific kinds of axis.
  256 For example, if you have complex rules for which dates to display
  257 in a time series application, or want irregular scaling, you override
  258 the axis and make a new one.
  259 """)
  260 
  261 disc("""
  262 Axes are responsible for determining the mapping from data to image
  263 coordinates; transforming points on request from the chart; drawing
  264 themselves and their tickmarks, gridlines and axis labels.
  265 """)
  266 
  267 disc("""
  268 This drawing shows two axes, one of each kind, which have been created
  269 directly without reference to any chart:
  270 """)
  271 
  272 
  273 from reportlab.graphics import shapes
  274 from reportlab.graphics.charts.axes import XCategoryAxis,YValueAxis
  275 
  276 drawing = Drawing(400, 200)
  277 
  278 data = [(10, 20, 30, 40), (15, 22, 37, 42)]
  279 
  280 xAxis = XCategoryAxis()
  281 xAxis.setPosition(75, 75, 300)
  282 xAxis.configure(data)
  283 xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
  284 xAxis.labels.boxAnchor = 'n'
  285 xAxis.labels[3].dy = -15
  286 xAxis.labels[3].angle = 30
  287 xAxis.labels[3].fontName = 'Times-Bold'
  288 
  289 yAxis = YValueAxis()
  290 yAxis.setPosition(50, 50, 125)
  291 yAxis.configure(data)
  292 
  293 drawing.add(xAxis)
  294 drawing.add(yAxis)
  295 
  296 draw(drawing, 'Two isolated axes')
  297 
  298 
  299 disc("Here is the code that created them: ")
  300 
  301 eg("""
  302 from reportlab.graphics import shapes
  303 from reportlab.graphics.charts.axes import XCategoryAxis,YValueAxis
  304 
  305 drawing = Drawing(400, 200)
  306 
  307 data = [(10, 20, 30, 40), (15, 22, 37, 42)]
  308 
  309 xAxis = XCategoryAxis()
  310 xAxis.setPosition(75, 75, 300)
  311 xAxis.configure(data)
  312 xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
  313 xAxis.labels.boxAnchor = 'n'
  314 xAxis.labels[3].dy = -15
  315 xAxis.labels[3].angle = 30
  316 xAxis.labels[3].fontName = 'Times-Bold'
  317 
  318 yAxis = YValueAxis()
  319 yAxis.setPosition(50, 50, 125)
  320 yAxis.configure(data)
  321 
  322 drawing.add(xAxis)
  323 drawing.add(yAxis)
  324 """)
  325 
  326 disc("""
  327 Remember that, usually, you won't have to create axes directly;
  328 when using a standard chart, it comes with ready-made axes.
  329 The methods are what the chart uses to configure it and take care
  330 of the geometry.
  331 However, we will talk through them in detail below.
  332 The orthogonally dual axes to those we describe have essentially
  333 the same properties, except for those refering to ticks.
  334 """)
  335 
  336 
  337 heading3("XCategoryAxis class")
  338 
  339 disc("""
  340 A Category Axis doesn't really have a scale; it just divides itself
  341 into equal-sized buckets.
  342 It is simpler than a value axis.
  343 The chart (or programmer) sets its location with the method
  344 $setPosition(x, y, length)$.
  345 The next stage is to show it the data so that it can configure
  346 itself.
  347 This is easy for a category axis - it just counts the number of
  348 data points in one of the data series. The $reversed$ attribute (if 1)
  349 indicates that the categories should be reversed.
  350 When the drawing is drawn, the axis can provide some help to the
  351 chart with its $scale()$ method, which tells the chart where
  352 a given category begins and ends on the page.
  353 We have not yet seen any need to let people override the widths
  354 or positions of categories.
  355 """)
  356 
  357 disc("An XCategoryAxis has the following editable properties:")
  358 
  359 disc("")
  360 
  361 data=[["Property", "Meaning"],
  362       ["visible", """Should the axis be drawn at all? Sometimes you don't want
  363 to display one or both axes, but they still need to be there as
  364 they manage the scaling of points."""],
  365       ["strokeColor", "Color of the axis"],
  366       ["strokeDashArray", """Whether to draw axis with a dash and, if so, what kind.
  367 Defaults to None"""],
  368       ["strokeWidth", "Width of axis in points"],
  369       ["tickUp", """How far above the axis should the tick marks protrude?
  370 (Note that making this equal to chart height gives you a gridline)"""],
  371       ["tickDown", """How far below the axis should the tick mark protrude?"""],
  372       ["categoryNames", """Either None, or a list of strings. This should have the
  373 same length as each data series."""],
  374       ["labels", """A collection of labels for the tick marks. By default the 'north'
  375 of each text label (i.e top centre) is positioned 5 points down
  376 from the centre of each category on the axis. You may redefine
  377 any property of the whole label group or of any one label. If
  378 categoryNames=None, no labels are drawn."""],
  379       ["title", """Not Implemented Yet. This needs to be like a label, but also
  380 lets you set the text directly. It would have a default
  381 location below the axis."""]]
  382 t=Table(data, colWidths=(100,330))
  383 t.setStyle(TableStyle([
  384             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
  385             ('FONT',(0,1),(0,-1),'Courier',8,8),
  386             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
  387             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
  388             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
  389             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
  390             ]))
  391 getStory().append(t)
  392 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - XCategoryAxis properties""")
  393 
  394 
  395 heading3("YValueAxis")
  396 
  397 disc("""
  398 The left axis in the diagram is a YValueAxis.
  399 A Value Axis differs from a Category Axis in that each point along
  400 its length corresponds to a y value in chart space.
  401 It is the job of the axis to configure itself, and to convert Y values
  402 from chart space to points on demand to assist the parent chart in
  403 plotting.
  404 """)
  405 
  406 disc("""
  407 $setPosition(x, y, length)$ and $configure(data)$ work exactly as
  408 for a category axis.
  409 If you have not fully specified the maximum, minimum and tick
  410 interval, then $configure()$ results in the axis choosing suitable
  411 values.
  412 Once configured, the value axis can convert y data values to drawing
  413 space with the $scale()$ method.
  414 Thus:
  415 """)
  416 
  417 eg("""
  418 >>> yAxis = YValueAxis()
  419 >>> yAxis.setPosition(50, 50, 125)
  420 >>> data = [(10, 20, 30, 40),(15, 22, 37, 42)]
  421 >>> yAxis.configure(data)
  422 >>> yAxis.scale(10)  # should be bottom of chart
  423 50.0
  424 >>> yAxis.scale(40)  # should be near the top
  425 167.1875
  426 >>>
  427 """)
  428 
  429 disc("""By default, the highest data point is aligned with the top of the
  430        axis, the lowest with the bottom of the axis, and the axis choose
  431        'nice round numbers' for its tickmark points. You may override these
  432        settings with the properties below. """)
  433 
  434 disc("")
  435 
  436 data=[["Property", "Meaning"],
  437       ["visible", """Should the axis be drawn at all? Sometimes you don't want
  438 to display one or both axes, but they still need to be there as
  439 they manage the scaling of points."""],
  440       ["strokeColor", "Color of the axis"],
  441       ["strokeDashArray", """Whether to draw axis with a dash and, if so, what kind.
  442 Defaults to None"""],
  443       ["strokeWidth", "Width of axis in points"],
  444       ["tickLeft", """How far to the left of the axis should the tick marks protrude?
  445 (Note that making this equal to chart height gives you a gridline)"""],
  446       ["tickRight", """How far to the right of the axis should the tick mark protrude?"""],
  447 
  448       ["valueMin", """The y value to which the bottom of the axis should correspond.
  449 Default value is None in which case the axis sets it to the lowest
  450 actual data point (e.g. 10 in the example above). It is common to set
  451 this to zero to avoid misleading the eye."""],
  452       ["valueMax", """The y value to which the top of the axis should correspond.
  453 Default value is None in which case the axis sets it to the highest
  454 actual data point (e.g. 42 in the example above). It is common to set
  455 this to a 'round number' so data bars do not quite reach the top."""],
  456       ["valueStep", """The y change between tick intervals. By default this is
  457 None, and the chart tries to pick 'nice round numbers' which are
  458 just wider than the minimumTickSpacing below."""],
  459 
  460       ["valueSteps", """A list of numbers at which to place ticks."""],
  461 
  462       ["minimumTickSpacing", """This is used when valueStep is set to None, and ignored
  463 otherwise. The designer specified that tick marks should be no
  464 closer than X points apart (based, presumably, on considerations
  465 of the label font size and angle). The chart tries values of the
  466 type 1,2,5,10,20,50,100... (going down below 1 if necessary) until
  467 it finds an interval which is greater than the desired spacing, and
  468 uses this for the step."""],
  469       ["labelTextFormat", """This determines what goes in the labels. Unlike a category
  470 axis which accepts fixed strings, the labels on a ValueAxis are
  471 supposed to be numbers. You may provide either a 'format string'
  472 like '%0.2f' (show two decimal places), or an arbitrary function
  473 which accepts a number and returns a string. One use for the
  474 latter is to convert a timestamp to a readable year-month-day
  475 format."""],
  476       ["title", """Not Implemented Yet. This needs to be like a label, but also
  477 lets you set the text directly. It would have a default
  478 location below the axis."""]]
  479 t=Table(data, colWidths=(100,330))
  480 t.setStyle(TableStyle([
  481             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
  482             ('FONT',(0,1),(0,-1),'Courier',8,8),
  483             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
  484             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
  485             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
  486             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
  487             ]))
  488 getStory().append(t)
  489 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - YValueAxis properties""")
  490 
  491 disc("""
  492 The $valueSteps$ property lets you explicitly specify the
  493 tick mark locations, so you don't have to follow regular intervals.
  494 Hence, you can plot month ends and month end dates with a couple of
  495 helper functions, and without needing special time series chart
  496 classes.
  497 The following code show how to create a simple $XValueAxis$ with special
  498 tick intervals. Make sure to set the $valueSteps$ attribute before calling
  499 the configure method!
  500 """)
  501 
  502 eg("""
  503 from reportlab.graphics.shapes import Drawing
  504 from reportlab.graphics.charts.axes import XValueAxis
  505 
  506 drawing = Drawing(400, 100)
  507 
  508 data = [(10, 20, 30, 40)]
  509 
  510 xAxis = XValueAxis()
  511 xAxis.setPosition(75, 50, 300)
  512 xAxis.valueSteps = [10, 15, 20, 30, 35, 40]
  513 xAxis.configure(data)
  514 xAxis.labels.boxAnchor = 'n'
  515 
  516 drawing.add(xAxis)
  517 """)
  518 
  519 
  520 from reportlab.graphics import shapes
  521 from reportlab.graphics.charts.axes import XValueAxis
  522 
  523 drawing = Drawing(400, 100)
  524 
  525 data = [(10, 20, 30, 40)]
  526 
  527 xAxis = XValueAxis()
  528 xAxis.setPosition(75, 50, 300)
  529 xAxis.valueSteps = [10, 15, 20, 30, 35, 40]
  530 xAxis.configure(data)
  531 xAxis.labels.boxAnchor = 'n'
  532 
  533 drawing.add(xAxis)
  534 
  535 draw(drawing, 'An axis with non-equidistant tick marks')
  536 
  537 
  538 disc("""
  539 In addition to these properties, all axes classes have three
  540 properties describing how to join two of them to each other.
  541 Again, this is interesting only if you define your own charts
  542 or want to modify the appearance of an existing chart using
  543 such axes.
  544 These properties are listed here only very briefly for now,
  545 but you can find a host of sample functions in the module
  546 $reportlab/graphics/axes.py$ which you can examine...
  547 """)
  548 
  549 disc("""
  550 One axis is joined to another, by calling the method
  551 $joinToAxis(otherAxis, mode, pos)$ on the first axis,
  552 with $mode$ and $pos$ being the properties described by
  553 $joinAxisMode$ and $joinAxisPos$, respectively.
  554 $'points'$ means to use an absolute value, and $'value'$
  555 to use a relative value (both indicated by the the
  556 $joinAxisPos$ property) along the axis.
  557 """)
  558 
  559 disc("")
  560 
  561 data=[["Property", "Meaning"],
  562       ["joinAxis", """Join both axes if true."""],
  563       ["joinAxisMode", """Mode used for connecting axis ('bottom', 'top', 'left', 'right', 'value', 'points', None)."""],
  564       ["joinAxisPos", """Position at which to join with other axis."""],
  565       ]
  566 t=Table(data, colWidths=(100,330))
  567 t.setStyle(TableStyle([
  568             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
  569             ('FONT',(0,1),(0,-1),'Courier',8,8),
  570             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
  571             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
  572             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
  573             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
  574             ]))
  575 getStory().append(t)
  576 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - Axes joining properties""")
  577 
  578 
  579 heading2("Bar Charts")
  580 
  581 disc("""
  582 This describes our current $VerticalBarChart$ class, which uses the
  583 axes and labels above.
  584 We think it is step in the right direction but is is
  585 far from final.
  586 Note that people we speak to are divided about 50/50 on whether to
  587 call this a 'Vertical' or 'Horizontal' bar chart.
  588 We chose this name because 'Vertical' appears next to 'Bar', so
  589 we take it to mean that the bars rather than the category axis
  590 are vertical.
  591 """)
  592 
  593 disc("""
  594 As usual, we will start with an example:
  595 """)
  596 
  597 from reportlab.graphics.shapes import Drawing
  598 from reportlab.graphics.charts.barcharts import VerticalBarChart
  599 
  600 drawing = Drawing(400, 200)
  601 
  602 data = [
  603         (13, 5, 20, 22, 37, 45, 19, 4),
  604         (14, 6, 21, 23, 38, 46, 20, 5)
  605         ]
  606 
  607 bc = VerticalBarChart()
  608 bc.x = 50
  609 bc.y = 50
  610 bc.height = 125
  611 bc.width = 300
  612 bc.data = data
  613 bc.strokeColor = colors.black
  614 
  615 bc.valueAxis.valueMin = 0
  616 bc.valueAxis.valueMax = 50
  617 bc.valueAxis.valueStep = 10
  618 
  619 bc.categoryAxis.labels.boxAnchor = 'ne'
  620 bc.categoryAxis.labels.dx = 8
  621 bc.categoryAxis.labels.dy = -2
  622 bc.categoryAxis.labels.angle = 30
  623 bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99',
  624        'Apr-99','May-99','Jun-99','Jul-99','Aug-99']
  625 
  626 drawing.add(bc)
  627 
  628 draw(drawing, 'Simple bar chart with two data series')
  629 
  630 
  631 eg("""
  632     # code to produce the above chart
  633 
  634     from reportlab.graphics.shapes import Drawing
  635     from reportlab.graphics.charts.barcharts import VerticalBarChart
  636 
  637     drawing = Drawing(400, 200)
  638 
  639     data = [
  640             (13, 5, 20, 22, 37, 45, 19, 4),
  641             (14, 6, 21, 23, 38, 46, 20, 5)
  642             ]
  643 
  644     bc = VerticalBarChart()
  645     bc.x = 50
  646     bc.y = 50
  647     bc.height = 125
  648     bc.width = 300
  649     bc.data = data
  650     bc.strokeColor = colors.black
  651 
  652     bc.valueAxis.valueMin = 0
  653     bc.valueAxis.valueMax = 50
  654     bc.valueAxis.valueStep = 10
  655 
  656     bc.categoryAxis.labels.boxAnchor = 'ne'
  657     bc.categoryAxis.labels.dx = 8
  658     bc.categoryAxis.labels.dy = -2
  659     bc.categoryAxis.labels.angle = 30
  660     bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99',
  661            'Apr-99','May-99','Jun-99','Jul-99','Aug-99']
  662 
  663     drawing.add(bc)
  664 """)
  665 
  666 disc("""
  667 Most of this code is concerned with setting up the axes and
  668 labels, which we have already covered.
  669 Here are the top-level properties of the $VerticalBarChart$ class:
  670 """)
  671 
  672 disc("")
  673 
  674 data=[["Property", "Meaning"],
  675       ["data", """This should be a "list of lists of numbers" or "list of
  676 tuples of numbers". If you have just one series, write it as
  677 data = [(10,20,30,42),]"""],
  678       ["x, y, width, height", """These define the inner 'plot rectangle'. We
  679 highlighted this with a yellow border above. Note that it is
  680 your job to place the chart on the drawing in a way which leaves
  681 room for all the axis labels and tickmarks. We specify this 'inner
  682 rectangle' because it makes it very easy to lay out multiple charts
  683 in a consistent manner."""],
  684       ["strokeColor", """Defaults to None. This will draw a border around the
  685 plot rectangle, which may be useful in debugging. Axes will
  686 overwrite this."""],
  687       ["fillColor", """Defaults to None. This will fill the plot rectangle with
  688 a solid color. (Note that we could implement dashArray etc.
  689 as for any other solid shape)"""],
  690       ["useAbsolute", """Defaults to 0. If 1, the three properties below are
  691 absolute values in points (which means you can make a chart
  692 where the bars stick out from the plot rectangle); if 0,
  693 they are relative quantities and indicate the proportional
  694 widths of the elements involved."""],
  695       ["barWidth", """As it says. Defaults to 10."""],
  696       ["groupSpacing", """Defaults to 5. This is the space between each group of
  697 bars. If you have only one series, use groupSpacing and not
  698 barSpacing to split them up. Half of the groupSpacing is used
  699 before the first bar in the chart, and another half at the end."""],
  700       ["barSpacing", """Defaults to 0. This is the spacing between bars in each
  701 group. If you wanted a little gap between green and red bars in
  702 the example above, you would make this non-zero."""],
  703       ["barLabelFormat", """Defaults to None. As with the YValueAxis, if you supply
  704 a function or format string then labels will be drawn next to each bar
  705 showing the numeric value. They are positioned automatically
  706 above the bar for positive values and below for negative ones."""],
  707       ["barLabels", """A collection of labels used to format all bar labels. Since
  708 this is a two-dimensional array, you may explicitly format the
  709 third label of the second series using this syntax:
  710   chart.barLabels[(1,2)].fontSize = 12"""],
  711       ["valueAxis", """The value axis, which may be formatted as described
  712 previously."""],
  713       ["categoryAxis", """The category axis, which may be formatted as described
  714 previously."""],
  715 
  716       ["title", """Not Implemented Yet. This needs to be like a label, but also
  717 lets you set the text directly. It would have a default
  718 location below the axis."""]]
  719 t=Table(data, colWidths=(100,330))
  720 t.setStyle(TableStyle([
  721             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
  722             ('FONT',(0,1),(0,-1),'Courier',8,8),
  723             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
  724             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
  725             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
  726             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
  727             ]))
  728 getStory().append(t)
  729 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - VerticalBarChart properties""")
  730 
  731 
  732 disc("""
  733 From this table we deduce that adding the following lines to our code
  734 above should double the spacing between bar groups (the $groupSpacing$
  735 attribute has a default value of five points) and we should also see
  736 some tiny space between bars of the same group ($barSpacing$).
  737 """)
  738 
  739 eg("""
  740     bc.groupSpacing = 10
  741     bc.barSpacing = 2.5
  742 """)
  743 
  744 disc("""
  745 And, in fact, this is exactly what we can see after adding these
  746 lines to the code above.
  747 Notice how the width of the individual bars has changed as well.
  748 This is because the space added between the bars has to be 'taken'
  749 from somewhere as the total chart width stays unchanged.
  750 """)
  751 
  752 from reportlab.graphics.shapes import Drawing
  753 from reportlab.graphics.charts.barcharts import VerticalBarChart
  754 
  755 drawing = Drawing(400, 200)
  756 
  757 data = [
  758         (13, 5, 20, 22, 37, 45, 19, 4),
  759         (14, 6, 21, 23, 38, 46, 20, 5)
  760         ]
  761 
  762 bc = VerticalBarChart()
  763 bc.x = 50
  764 bc.y = 50
  765 bc.height = 125
  766 bc.width = 300
  767 bc.data = data
  768 bc.strokeColor = colors.black
  769 
  770 bc.groupSpacing = 10
  771 bc.barSpacing = 2.5
  772 
  773 bc.valueAxis.valueMin = 0
  774 bc.valueAxis.valueMax = 50
  775 bc.valueAxis.valueStep = 10
  776 
  777 bc.categoryAxis.labels.boxAnchor = 'ne'
  778 bc.categoryAxis.labels.dx = 8
  779 bc.categoryAxis.labels.dy = -2
  780 bc.categoryAxis.labels.angle = 30
  781 bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99',
  782        'Apr-99','May-99','Jun-99','Jul-99','Aug-99']
  783 
  784 drawing.add(bc)
  785 
  786 draw(drawing, 'Like before, but with modified spacing')
  787 
  788 disc("""
  789 Bars labels are automatically displayed for negative values
  790 <i>below</i> the lower end of the bar for positive values
  791 <i>above</i> the upper end of the other ones.
  792 """)
  793 
  794 
  795 disc("""
  796 Stacked bars are also supported for vertical bar graphs.
  797 You enable this layout for your chart by setting the $style$
  798 attribute to $'stacked'$ on the $categoryAxis$.
  799 """)
  800 
  801 eg("""
  802     bc.categoryAxis.style = 'stacked'
  803 """)
  804 
  805 disc("""
  806 Here is an example of the previous chart values arranged
  807 in the stacked style.
  808 """)
  809 
  810 
  811 drawing = Drawing(400, 200)
  812 
  813 data = [
  814         (13, 5, 20, 22, 37, 45, 19, 4),
  815         (14, 6, 21, 23, 38, 46, 20, 5)
  816         ]
  817 
  818 bc = VerticalBarChart()
  819 bc.x = 50
  820 bc.y = 50
  821 bc.height = 125
  822 bc.width = 300
  823 bc.data = data
  824 bc.strokeColor = colors.black
  825 
  826 bc.groupSpacing = 10
  827 bc.barSpacing = 2.5
  828 
  829 bc.valueAxis.valueMin = 0
  830 bc.valueAxis.valueMax = 100
  831 bc.valueAxis.valueStep = 20
  832 
  833 bc.categoryAxis.labels.boxAnchor = 'ne'
  834 bc.categoryAxis.labels.dx = 8
  835 bc.categoryAxis.labels.dy = -2
  836 bc.categoryAxis.labels.angle = 30
  837 bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99',
  838        'Apr-99','May-99','Jun-99','Jul-99','Aug-99']
  839 bc.categoryAxis.style = 'stacked'
  840 
  841 drawing.add(bc)
  842 draw(drawing, 'Stacking bars on top of each other.')
  843 
  844 
  845 ##Property Value
  846 ##data This should be a "list of lists of numbers" or "list of tuples of numbers". If you have just one series, write it as
  847 ##data = [(10,20,30,42),]
  848 ##
  849 ##x, y, width, height These define the inner 'plot rectangle'. We highlighted this with a yellow border above. Note that it is your job to place the chart on the drawing in a way which leaves room for all the axis labels and tickmarks. We specify this 'inner rectangle' because it makes it very easy to lay out multiple charts in a consistent manner.
  850 ##strokeColor Defaults to None. This will draw a border around the plot rectangle, which may be useful in debugging. Axes will overwrite this.
  851 ##fillColor Defaults to None. This will fill the plot rectangle with a solid color. (Note that we could implement dashArray etc. as for any other solid shape)
  852 ##barLabelFormat This is a format string or function used for displaying labels above each bar. We're working on ways to position these labels so that they work for positive and negative bars.
  853 ##useAbsolute Defaults to 0. If 1, the three properties below are absolute values in points (which means you can make a chart where the bars stick out from the plot rectangle); if 0, they are relative quantities and indicate the proportional widths of the elements involved.
  854 ##barWidth As it says. Defaults to 10.
  855 ##groupSpacing Defaults to 5. This is the space between each group of bars. If you have only one series, use groupSpacing and not barSpacing to split them up. Half of the groupSpacing is used before the first bar in the chart, and another half at the end.
  856 ##barSpacing Defaults to 0. This is the spacing between bars in each group. If you wanted a little gap between green and red bars in the example above, you would make this non-zero.
  857 ##barLabelFormat Defaults to None. As with the YValueAxis, if you supply a function or format string then labels will be drawn next to each bar showing the numeric value.
  858 ##barLabels A collection of labels used to format all bar labels. Since this is a two-dimensional array, you may explicitly format the third label of the second series using this syntax:
  859 ##    chart.barLabels[(1,2)].fontSize = 12
  860 ##
  861 ##valueAxis The value axis, which may be formatted as described previously
  862 ##categoryAxis The categoryAxis, which may be formatted as described previously
  863 ##title, subTitle Not implemented yet. These would be label-like objects whose text could be set directly and which would appear in sensible locations. For now, you can just place extra strings on the drawing.
  864 
  865 
  866 heading2("Line Charts")
  867 
  868 disc("""
  869 We consider "Line Charts" to be essentially the same as
  870 "Bar Charts", but with lines instead of bars.
  871 Both share the same pair of Category/Value axes pairs.
  872 This is in contrast to "Line Plots", where both axes are
  873 <i>Value</i> axes.
  874 """)
  875 
  876 disc("""
  877 The following code and its output shall serve as a simple
  878 example.
  879 More explanation will follow.
  880 For the time being you can also study the output of running
  881 the tool $reportlab/lib/graphdocpy.py$ withough any arguments
  882 and search the generated PDF document for examples of
  883 Line Charts.
  884 """)
  885 
  886 eg("""
  887 from reportlab.graphics.charts.linecharts import HorizontalLineChart
  888 
  889 drawing = Drawing(400, 200)
  890 
  891 data = [
  892     (13, 5, 20, 22, 37, 45, 19, 4),
  893     (5, 20, 46, 38, 23, 21, 6, 14)
  894 ]
  895 
  896 lc = HorizontalLineChart()
  897 lc.x = 50
  898 lc.y = 50
  899 lc.height = 125
  900 lc.width = 300
  901 lc.data = data
  902 lc.joinedLines = 1
  903 catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
  904 lc.categoryAxis.categoryNames = catNames
  905 lc.categoryAxis.labels.boxAnchor = 'n'
  906 lc.valueAxis.valueMin = 0
  907 lc.valueAxis.valueMax = 60
  908 lc.valueAxis.valueStep = 15
  909 lc.lines[0].strokeWidth = 2
  910 lc.lines[1].strokeWidth = 1.5
  911 drawing.add(lc)
  912 """)
  913 
  914 from reportlab.graphics.charts.linecharts import HorizontalLineChart
  915 
  916 drawing = Drawing(400, 200)
  917 
  918 data = [
  919     (13, 5, 20, 22, 37, 45, 19, 4),
  920     (5, 20, 46, 38, 23, 21, 6, 14)
  921 ]
  922 
  923 lc = HorizontalLineChart()
  924 lc.x = 50
  925 lc.y = 50
  926 lc.height = 125
  927 lc.width = 300
  928 lc.data = data
  929 lc.joinedLines = 1
  930 catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
  931 lc.categoryAxis.categoryNames = catNames
  932 lc.categoryAxis.labels.boxAnchor = 'n'
  933 lc.valueAxis.valueMin = 0
  934 lc.valueAxis.valueMax = 60
  935 lc.valueAxis.valueStep = 15
  936 lc.lines[0].strokeWidth = 2
  937 lc.lines[1].strokeWidth = 1.5
  938 drawing.add(lc)
  939 
  940 draw(drawing, 'HorizontalLineChart sample')
  941 
  942 
  943 disc("")
  944 
  945 data=[["Property","Meaning"],
  946       ["data", "Data to be plotted, list of (lists of) numbers."],
  947       ["x, y, width, height", """Bounding box of the line chart.
  948 Note that x and y do NOT specify the centre but the bottom left corner"""],
  949       ["valueAxis", """The value axis, which may be formatted as described previously."""],
  950       ["categoryAxis", """The category axis, which may be formatted as described previously."""],
  951  ["strokeColor", """Defaults to None. This will draw a border around the plot rectangle,
  952 which may be useful in debugging. Axes will overwrite this."""],
  953       ["fillColor", """Defaults to None. This will fill the plot rectangle with a solid color."""],
  954       ["lines.strokeColor", """Color of the line."""],
  955       ["lines.strokeWidth", """Width of the line."""],
  956       ["lineLabels", """A collection of labels used to format all line labels. Since
  957 this is a two-dimensional array, you may explicitly format the
  958 third label of the second line using this syntax:
  959   chart.lineLabels[(1,2)].fontSize = 12"""],
  960       ["lineLabelFormat", """Defaults to None. As with the YValueAxis, if you supply
  961 a function or format string then labels will be drawn next
  962 to each line showing the numeric value. You can also set it
  963 to 'values' to display the values explicity defined in lineLabelArray."""],
  964       ["lineLabelArray", """Explicit array of line label values, must match size of data if present.
  965 These labels values will be displayed only if the property
  966 lineLabelFormat above is set to 'values'."""]]
  967 t=Table(data, colWidths=(100,330))
  968 t.setStyle(TableStyle([
  969             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
  970             ('FONT',(0,1),(0,-1),'Courier',8,8),
  971             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
  972             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
  973             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
  974             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
  975             ]))
  976 getStory().append(t)
  977 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - HorizontalLineChart properties""")
  978 
  979 heading2("Line Plots")
  980 
  981 disc("""
  982 Below we show a more complex example of a Line Plot that
  983 also uses some experimental features like line markers
  984 placed at each data point.
  985 """)
  986 
  987 eg("""
  988 from reportlab.graphics.charts.lineplots import LinePlot
  989 from reportlab.graphics.widgets.markers import makeMarker
  990 
  991 drawing = Drawing(400, 200)
  992 
  993 data = [
  994     ((1,1), (2,2), (2.5,1), (3,3), (4,5)),
  995     ((1,2), (2,3), (2.5,2), (3.5,5), (4,6))
  996 ]
  997 
  998 lp = LinePlot()
  999 lp.x = 50
 1000 lp.y = 50
 1001 lp.height = 125
 1002 lp.width = 300
 1003 lp.data = data
 1004 lp.joinedLines = 1
 1005 lp.lines[0].symbol = makeMarker('FilledCircle')
 1006 lp.lines[1].symbol = makeMarker('Circle')
 1007 lp.lineLabelFormat = '%2.0f'
 1008 lp.strokeColor = colors.black
 1009 lp.xValueAxis.valueMin = 0
 1010 lp.xValueAxis.valueMax = 5
 1011 lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5]
 1012 lp.xValueAxis.labelTextFormat = '%2.1f'
 1013 lp.yValueAxis.valueMin = 0
 1014 lp.yValueAxis.valueMax = 7
 1015 lp.yValueAxis.valueSteps = [1, 2, 3, 5, 6]
 1016 
 1017 drawing.add(lp)
 1018 """)
 1019 
 1020 
 1021 from reportlab.graphics.charts.lineplots import LinePlot
 1022 from reportlab.graphics.widgets.markers import makeMarker
 1023 
 1024 drawing = Drawing(400, 200)
 1025 
 1026 data = [
 1027     ((1,1), (2,2), (2.5,1), (3,3), (4,5)),
 1028     ((1,2), (2,3), (2.5,2), (3.5,5), (4,6))
 1029 ]
 1030 
 1031 lp = LinePlot()
 1032 lp.x = 50
 1033 lp.y = 50
 1034 lp.height = 125
 1035 lp.width = 300
 1036 lp.data = data
 1037 lp.joinedLines = 1
 1038 lp.lines[0].symbol = makeMarker('FilledCircle')
 1039 lp.lines[1].symbol = makeMarker('Circle')
 1040 lp.lineLabelFormat = '%2.0f'
 1041 lp.strokeColor = colors.black
 1042 lp.xValueAxis.valueMin = 0
 1043 lp.xValueAxis.valueMax = 5
 1044 lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5]
 1045 lp.xValueAxis.labelTextFormat = '%2.1f'
 1046 lp.yValueAxis.valueMin = 0
 1047 lp.yValueAxis.valueMax = 7
 1048 lp.yValueAxis.valueSteps = [1, 2, 3, 5, 6]
 1049 
 1050 drawing.add(lp)
 1051 
 1052 draw(drawing, 'LinePlot sample')
 1053 
 1054 
 1055 
 1056 disc("")
 1057 
 1058 data=[["Property","Meaning"],
 1059       ["data", "Data to be plotted, list of (lists of) numbers."],
 1060       ["x, y, width, height", """Bounding box of the line chart.
 1061 Note that x and y do NOT specify the centre but the bottom left corner"""],
 1062       ["xValueAxis", """The vertical value axis, which may be formatted as described previously."""],
 1063       ["yValueAxis", """The horizontal value axis, which may be formatted as described previously."""],
 1064       ["strokeColor", """Defaults to None. This will draw a border around the plot rectangle,
 1065 which may be useful in debugging. Axes will overwrite this."""],
 1066       ["strokeWidth", """Defaults to None. Width of the border around the plot rectangle."""],
 1067       ["fillColor", """Defaults to None. This will fill the plot rectangle with a solid color."""],
 1068       ["lines.strokeColor", """Color of the line."""],
 1069       ["lines.strokeWidth", """Width of the line."""],
 1070       ["lines.symbol", """Marker used for each point.
 1071 You can create a new marker using the function makeMarker().
 1072 For example to use a circle, the function call would be makeMarker('Circle')"""],
 1073       ["lineLabels", """A collection of labels used to format all line labels. Since
 1074 this is a two-dimensional array, you may explicitly format the
 1075 third label of the second line using this syntax:
 1076   chart.lineLabels[(1,2)].fontSize = 12"""],
 1077       ["lineLabelFormat", """Defaults to None. As with the YValueAxis, if you supply
 1078 a function or format string then labels will be drawn next
 1079 to each line showing the numeric value. You can also set it
 1080 to 'values' to display the values explicity defined in lineLabelArray."""],
 1081       ["lineLabelArray", """Explicit array of line label values, must match size of data if present.
 1082 These labels values will be displayed only if the property
 1083 lineLabelFormat above is set to 'values'."""]]
 1084 t=Table(data, colWidths=(100,330))
 1085 t.setStyle(TableStyle([
 1086             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
 1087             ('FONT',(0,1),(0,-1),'Courier',8,8),
 1088             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
 1089             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
 1090             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
 1091             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
 1092             ]))
 1093 getStory().append(t)
 1094 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - LinePlot properties""")
 1095 
 1096 
 1097 
 1098 
 1099 heading2("Pie Charts")
 1100 
 1101 disc("""
 1102 As usual, we will start with an example:
 1103 """)
 1104 
 1105 eg("""
 1106 from reportlab.graphics.charts.piecharts import Pie
 1107 d = Drawing(200, 100)
 1108 
 1109 pc = Pie()
 1110 pc.x = 65
 1111 pc.y = 15
 1112 pc.width = 70
 1113 pc.height = 70
 1114 pc.data = [10,20,30,40,50,60]
 1115 pc.labels = ['a','b','c','d','e','f']
 1116 
 1117 pc.slices.strokeWidth=0.5
 1118 pc.slices[3].popout = 10
 1119 pc.slices[3].strokeWidth = 2
 1120 pc.slices[3].strokeDashArray = [2,2]
 1121 pc.slices[3].labelRadius = 1.75
 1122 pc.slices[3].fontColor = colors.red
 1123 d.add(pc)
 1124 """)
 1125 
 1126 from reportlab.graphics.charts.piecharts import Pie
 1127 
 1128 d = Drawing(400, 200)
 1129 
 1130 pc = Pie()
 1131 pc.x = 125
 1132 pc.y = 25
 1133 pc.width = 150
 1134 pc.height = 150
 1135 pc.data = [10,20,30,40,50,60]
 1136 pc.labels = ['a','b','c','d','e','f']
 1137 
 1138 pc.slices.strokeWidth=0.5
 1139 pc.slices[3].popout = 10
 1140 pc.slices[3].strokeWidth = 2
 1141 pc.slices[3].strokeDashArray = [2,2]
 1142 pc.slices[3].labelRadius = 1.25
 1143 pc.slices[3].fontColor = colors.red
 1144 
 1145 d.add(pc)
 1146 
 1147 draw(d, 'A bare bones pie chart')
 1148 
 1149 disc("""
 1150 Properties are covered below.
 1151 The pie has a 'slices' collection and we document wedge properties
 1152 in the same table.
 1153 """)
 1154 
 1155 disc("")
 1156 
 1157 data=[["Property", "Meaning"],
 1158       ["data", "A list or tuple of numbers"],
 1159       ["x, y, width, height", """Bounding box of the pie.
 1160 Note that x and y do NOT specify the centre but the bottom left
 1161 corner, and that width and height do not have to be equal;
 1162 pies may be elliptical and slices will be drawn correctly."""],
 1163       ["labels", """None, or a list of strings.
 1164 Make it None if you don't want labels around the edge of the pie.
 1165 Since it is impossible to know the size of slices, we generally
 1166 discourage placing labels in or around pies; it is much better 
 1167 to put them in a legend alongside."""],
 1168       ["startAngle", """Where is the start angle of the first pie slice?
 1169 The default is '90' which is twelve o'clock."""],
 1170       ["direction", """Which direction do slices progress in?
 1171 The default is 'clockwise'."""],
 1172       ["sideLabels", """This creates a chart with the labels in two columns,
 1173 one on either side."""],
 1174       ["sideLabelsOffset", """This is a fraction of the width of the pie that defines the horizontal
 1175 distance between the pie and the columns of labels."""],
 1176       ["simpleLabels", """Default is 1. Set to 0 to enable the use of customizable labels 
 1177 and of properties prefixed by label_ in the collection slices."""],
 1178       ["slices", """Collection of slices.
 1179 This lets you customise each wedge, or individual ones. See below"""],
 1180       ["slices.strokeWidth", "Border width for wedge"],
 1181       ["slices.strokeColor", "Border color"],
 1182       ["slices.strokeDashArray", "Solid or dashed line configuration"],
 1183       ["slices.popout", """How far out should the slice(s) stick from the centre of the pie?
 1184 Default is zero."""],
 1185       ["slices.fontName", "Name of the label font"],
 1186       ["slices.fontSize", "Size of the label font"],
 1187       ["slices.fontColor", "Color of the label text"],
 1188       ["slices.labelRadius", """This controls the anchor point for a text label.
 1189 It is a fraction of the radius; 0.7 will place the text inside the
 1190 pie, 1.2 will place it slightly outside. (note that if we add labels,
 1191 we will keep this to specify their anchor point)"""]]
 1192 t=Table(data, colWidths=(130,300))
 1193 t.setStyle(TableStyle([
 1194             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
 1195             ('FONT',(0,1),(0,-1),'Courier',8,8),
 1196             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
 1197             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
 1198             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
 1199             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
 1200             ]))
 1201 getStory().append(t)
 1202 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - Pie properties""")
 1203 
 1204 heading3("Customizing Labels")
 1205 
 1206 disc("""
 1207 Each slide label can be customized individually by changing
 1208 the properties prefixed by $label_$ in the collection $slices$.
 1209 For example $pc.slices[2].label_angle = 10$ changes the angle 
 1210 of the third label.
 1211 """)
 1212 
 1213 disc("""
 1214 Before being able to use these customization properties, you need
 1215 to disable simple labels with: $pc.simplesLabels = 0$
 1216 """)
 1217 
 1218 disc("")
 1219 
 1220 data=[["Property", "Meaning"],
 1221       ["label_dx", """X Offset of the label"""],
 1222       ["label_dy", """Y Offset of the label"""],
 1223       ["label_angle", """Angle of the label, default (0) is horizontal, 90 is vertical,
 1224 180 is upside down"""],
 1225       ["label_boxAnchor", """Anchoring point of the label"""],
 1226       ["label_boxStrokeColor", """Border color for the label box"""],
 1227       ["label_boxStrokeWidth", """Border width for the label box"""],
 1228       ["label_boxFillColor", """Filling color of the label box"""],
 1229       ["label_strokeColor", """Border color for the label text"""],
 1230       ["label_strokeWidth", """Border width for the label text"""],
 1231       ["label_text", """Text of the label"""],
 1232       ["label_width", """Width of the label"""],
 1233       ["label_maxWidth", """Maximum width the label can grow to"""],
 1234       ["label_height", """Height of the label"""],
 1235       ["label_textAnchor", """Maximum height the label can grow to"""],
 1236       ["label_visible", """True if the label is to be drawn"""],
 1237       ["label_topPadding", """Padding at top of box"""],
 1238       ["label_leftPadding", """Padding at left of box"""],
 1239       ["label_rightPadding", """Padding at right of box"""],
 1240       ["label_bottomPadding", """Padding at bottom of box"""],
 1241       ["label_simple_pointer", """Set to 1 for simple pointers"""],
 1242       ["label_pointer_strokeColor", """Color of indicator line"""],
 1243       ["label_pointer_strokeWidth", """Width of indicator line"""]]
 1244 t=Table(data, colWidths=(130,300))
 1245 t.setStyle(TableStyle([
 1246             ('FONT',(0,0),(-1,0),'Times-Bold',10,12),
 1247             ('FONT',(0,1),(0,-1),'Courier',8,8),
 1248             ('FONT',(1,1),(1,-1),'Times-Roman',10,12),
 1249             ('VALIGN',(0,0),(-1,-1),'MIDDLE'),
 1250             ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
 1251             ('BOX', (0,0), (-1,-1), 0.25, colors.black),
 1252             ]))
 1253 getStory().append(t)
 1254 caption("""Table <seq template="%(Chapter)s-%(Table+)s"/> - Pie.slices label customization properties""")
 1255 
 1256 heading3("Side Labels")
 1257 
 1258 disc("""
 1259 If the sideLabels attribute is set to true, then the labels of 
 1260 the slices are placed in two columns, one on either side of the 
 1261 pie and the start angle of the pie will be set automatically.
 1262 The anchor of the right hand column is set to 'start' and the 
 1263 anchor of the left hand column is set to 'end'.
 1264 The distance from the edge of the pie from the edge of either 
 1265 column is decided by the sideLabelsOffset attribute, which is 
 1266 a fraction of the width of the pie.
 1267 If xradius is changed, the pie can overlap the labels, and so 
 1268 we advise leaving xradius as None.
 1269 There is an example below.
 1270 """)
 1271 
 1272 from reportlab.graphics.charts.piecharts import sample5, sample7, sample8
 1273 drawing5 = sample5()
 1274 draw(drawing5, 'An example of a piechart with sideLabels =1')
 1275 
 1276 disc("""
 1277 If you have sideLabels set to True, then some of the attributes 
 1278 become redundant, such as pointerLabelMode.
 1279 Also sideLabelsOffset only changes the piechart if sideLabels is 
 1280 set to true.
 1281 """)
 1282 
 1283 heading4("Some issues")
 1284 
 1285 disc("""
 1286 The pointers can cross if there are too many slices.
 1287 """)
 1288 
 1289 drawing7 = sample7()
 1290 draw(drawing7, 'An example of pointers crossing')
 1291 
 1292 disc("""
 1293 Also the labels can overlap despite checkLabelOverlap if they 
 1294 correspond to slices that are not adjacent.
 1295 """)
 1296 
 1297 drawing8 = sample8()
 1298 draw(drawing8, 'An example of labels overlapping')
 1299 
 1300 heading2("Legends")
 1301 
 1302 disc("""
 1303 Various preliminary legend classes can be found but need a
 1304 cleanup to be consistent with the rest of the charting
 1305 model.
 1306 Legends are the natural place to specify the colors and line
 1307 styles of charts; we propose that each chart is created with
 1308 a $legend$ attribute which is invisible.
 1309 One would then do the following to specify colors:
 1310 """)
 1311 
 1312 eg("""
 1313 myChart.legend.defaultColors = [red, green, blue]
 1314 """)
 1315 
 1316 disc("""
 1317 One could also define a group of charts sharing the same legend:
 1318 """)
 1319 
 1320 eg("""
 1321 myLegend = Legend()
 1322 myLegend.defaultColor = [red, green.....] #yuck!
 1323 myLegend.columns = 2
 1324 # etc.
 1325 chart1.legend = myLegend
 1326 chart2.legend = myLegend
 1327 chart3.legend = myLegend
 1328 """)
 1329 
 1330 # Hack to force a new paragraph before the todo() :-(
 1331 disc("")
 1332 
 1333 todo("""Does this work? Is it an acceptable complication over specifying chart
 1334 colors directly?""")
 1335 
 1336 
 1337 
 1338 heading3("Remaining Issues")
 1339 
 1340 disc("""
 1341 There are several issues that are <i>almost</i> solved, but for which
 1342 is is a bit too early to start making them really public.
 1343 Nevertheless, here is a list of things that are under way:
 1344 """)
 1345 
 1346 bullet("""
 1347 Color specification - right now the chart has an undocumented property
 1348 $defaultColors$, which provides a list of colors to cycle through,
 1349 such that each data series gets its own color.
 1350 Right now, if you introduce a legend, you need to make sure it shares
 1351 the same list of colors.
 1352 Most likely, this will be replaced with a scheme to specify a kind
 1353 of legend containing attributes with different values for each data
 1354 series.
 1355 This legend can then also be shared by several charts, but need not
 1356 be visible itself.
 1357 """)
 1358 
 1359 bullet("""
 1360 Additional chart types - when the current design will have become
 1361 more stable, we expect to add variants of bar charts to deal with
 1362 percentile bars as well as the side-by-side variant seen here.
 1363 """)
 1364 
 1365 
 1366 heading3("Outlook")
 1367 
 1368 disc("""
 1369 It will take some time to deal with the full range of chart types.
 1370 We expect to finalize bars and pies first and to produce trial
 1371 implementations of more general plots, thereafter.
 1372 """)
 1373 
 1374 
 1375 heading3("X-Y Plots")
 1376 
 1377 disc("""
 1378 Most other plots involve two value axes and directly plotting
 1379 x-y data in some form.
 1380 The series can be plotted as lines, marker symbols, both, or
 1381 custom graphics such as open-high-low-close graphics.
 1382 All share the concepts of scaling and axis/title formatting.
 1383 At a certain point, a routine will loop over the data series and
 1384 'do something' with the data points at given x-y locations.
 1385 Given a basic line plot, it should be very easy to derive a
 1386 custom chart type just by overriding a single method - say,
 1387 $drawSeries()$.
 1388 """)
 1389 
 1390 
 1391 heading3("Marker customisation and custom shapes")
 1392 
 1393 disc("""
 1394 Well known plotting packages such as excel, Mathematica and Excel
 1395 offer ranges of marker types to add to charts.
 1396 We can do better - you can write any kind of chart widget you
 1397 want and just tell the chart to use it as an example.
 1398 """)
 1399 
 1400 
 1401 heading4("Combination plots")
 1402 
 1403 disc("""
 1404 Combining multiple plot types is really easy.
 1405 You can just draw several charts (bar, line or whatever) in
 1406 the same rectangle, suppressing axes as needed.
 1407 So a chart could correlate a line with Scottish typhoid cases
 1408 over a 15 year period on the left axis with a set of bars showing
 1409 inflation rates on the right axis.
 1410 If anyone can remind us where this example came from we'll
 1411 attribute it, and happily show the well-known graph as an
 1412 example.
 1413 """)
 1414 
 1415 
 1416 heading3("Interactive editors")
 1417 
 1418 disc("""
 1419 One principle of the Graphics package is to make all 'interesting'
 1420 properties of its graphic components accessible and changeable by
 1421 setting apropriate values of corresponding public attributes.
 1422 This makes it very tempting to build a tool like a GUI editor that
 1423 that helps you with doing that interactively.
 1424 """)
 1425 
 1426 disc("""
 1427 ReportLab has built such a tool using the Tkinter toolkit that
 1428 loads pure Python code describing a drawing and records your
 1429 property editing operations.
 1430 This "change history" is then used to create code for a subclass
 1431 of that chart, say, that can be saved and used instantly just
 1432 like any other chart or as a new starting point for another
 1433 interactive editing session.
 1434 """)
 1435 
 1436 disc("""
 1437 This is still work in progress, though, and the conditions for
 1438 releasing this need to be further elaborated.
 1439 """)
 1440 
 1441 
 1442 heading3("Misc.")
 1443 
 1444 disc("""
 1445 This has not been an exhaustive look at all the chart classes.
 1446 Those classes are constantly being worked on.
 1447 To see exactly what is in the current distribution, use the
 1448 $graphdocpy.py$ utility.
 1449 By default, it will run on reportlab/graphics, and produce a full
 1450 report.
 1451 (If you want to run it on other modules or packages,
 1452 $graphdocpy.py -h$ prints a help message that will tell you
 1453 how.)
 1454 """)
 1455 
 1456 disc("""
 1457 This is the tool that was mentioned in the section on 'Documenting
 1458 Widgets'.
 1459 """)