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