DIY Column Chart

If you think about it, the XFA grammar has all the graphics primitives you’d need to generate a basic column or bar chart.  It has lines, rectangles and text.  It has repeating elements (subforms).  Ok, so it doesn’t have a shadow effect.  And 3D bars are off the table.  But theoretically, all that stands between us and a simple chart is a bit of JavaScript.

By searching the web you can find samples of packages that render charts in browsers using various JavaScript frameworks.  So why not do the same in an XFA form?

Have a look at this sample PDF (here’s the data).  In particular, try modifying a value in the table and watch the bars update.  Try modifying a value to $200,000 so that the Y-axis needs to be re-scaled.  Cool.

To use one of these charts in your PDF form, open this sample in Designer and turn the chart subform into a fragment that you’ll be able to bring into other forms.  When you open in Designer you’ll see some instructions that get hidden at runtime.  The components within the chart can be styled and customized as required.  The aspects that need not be set at design time are: the positions of the dynamic elements (labels, tics, bar), the contents of the labels and the size of the bars.

Options

Once the chart is sized, positioned and styled as you like, then look at the calculate script on the chart  subform to set the rest of the parameters.  At the top of the script you will see variables for controlling the rest of the chart behaviour:

  • Bar colours
  • grid line control (short tics vs. lines that extend across the chart)
  • x label staggering (prevent overlapping labels)
  • define the gap size between bars
  • Set the maximum number of labels on the Y axis
  • Column stacking (on or off)
  • SOM expressions defining the data series

In all it was around 450 lines of generously commented JavaScript. 

Oh, and lest you think this was all original, I did borrow some Java code (converted to JavaScript) to help with the Y axis label generation.

Performance Considerations

The form makes use of dynamic subforms to draw the variable elements of the chart — X and Y labels and bars. Once the subforms were created, the script resizes and positions them correctly.  A more efficient way to draw the chart would be to pre-create a fixed number of labels and bars at design time — not wrapped in subforms.  Drawing the chart would simply use the pre-created objects and hide the ones not need.  The problem with this approach is that it is harder to author — the author is responsible for making sure there are enough labels and bars.  My version of the form opts for easier design and a slightly less efficient runtime.  But either way, this approach of rendering graphics won’t scale well to larger more complex implementations. 

13 Responses to DIY Column Chart

  1. I updated the sample — removed the picture clause from the options. Specify on the label field instead.

  2. Hi John,Hope you had a good holiday.Your DIY column chart is great. I have used it to generate a single page PDF with three column charts. The data is hardwired into the XFA form, with a drop down list calling different sets of data. All three charts vary depending on the set called. Works fine in Acrobat!I am having difficulty if I Reader Enable the form through Acrobat (v9). While the drop down list still populates the tables, the charts are not generated.The java console states that “vSubform has no properties”.What I have been trying to figure out is what aspect of the “vSubform” script is not permitted in Reader Extended forms.The anticipated use of the form is to have it available on the web to communicate survey results. It is not intended to gather data or process further data. In this case it does not really need to be Reader Enabled; however I am anxious to avoid users with Reader having the “You cannot save this form” prompt coming up everytime it is opened.I would be very grateful if you could advise what aspects of the script will be thrown out by Reader (Reader Enabled form).I can post the form to you, if that helps.Thanks,Niall

  3. Niall:The holiday was great, thanks.I don’t know of any reason for reader-extending to change the behaviour of a form. I tried reader-extending my sample, and it continued to work fine in both Acrobat and Reader. There must be some other logic error.The javascript error “vSubform has no properties” will normally happen if the variable “vSubform” is null.For example, this code will generate the same error:var vSubform = null;vSubform.w = “2in”;I suggest you wrap the call to createChart() in a try/catch block and examine the resulting exception to see the line number and call stack for the error.(for more on exception handling see: http://blogs.adobe.com/formfeed/2009/03/handling_javascript_exceptions.html)good luck!John

  4. Thanks very much John,I found out the problem:It appears that because my form was starting out with no data, the min calculations were falling over. In Acrobat this was throwing errors in the console, but the form worked.When extended in Reader, it just didn’t work!I have now set a default set of values for the charts and the form now works in Acrobat (without errors) and when extended in Reader.Thanks again for the help and a great example!!Regards,Niall

  5. Sorry John to trouble you again!!When testing, I have noticed that when the preference:gridLinesOn : true,then the charts goes astray in Acrobat and Reader version 8. Not only do the lines get pushed to the left, but also the y-axis labels (all outside of their subform!!).Turning the gridlines to false fixes the problem, as the labels stay with the y-axis.I tested your sample in Acrobat/Reader v8 as well and the same issue occurred (column chart was askew, but stacked column chart OK).I am at a loss how to fix it, but happy enough to work without the gridlines (unless the client wants them back on).Regards,Niall

  6. Niall:Two things you could try:1) in Designer make the Y_label subform the same dimensions (width and height) as the parent chart subform.(y_label gets dynamically re-sized to these dimensions — so if it starts out at the right size it may behave better)2) if re-sizing doesn’t work, then at the end of createChart() try calling xfa.layout.relayout().good luck.John

  7. Thanks very much John,Your first solution of resizing the y-labels subform worked!!!!I also implemented the second solution (with the re-size) and it still worked, but was significantly slower.Therefore we are going with the resized subform.Thanks!!!!Niall

  8. Niall:Thanks for reporting back.Glad it worked for you.John

  9. Iakov says:

    “If you think about it, the XFA grammar has all the graphics primitives you’d need to generate a basic column or bar chart. It has lines, rectangles and text.”There is also the circle primitive, and so could pie charts be possible in Designer?This is an extremely interesting example. Thanks!

  10. Lakov:Yes, XFA has an arc, but to do a pie slice you need to connect the arc with two line segments and fill the result. We can’t do that. But maybe you could still get there. Maybe the best strategy is to draw a filled circle and mask off everything except the pie segment.John

  11. Rod says:

    Hi John,Many thanks for this excellent article. I have implemented your chart “control” and it looks great, although I have discovered one minor issue which I would really appreciate your input on.In my document, the chart extracts its data from a table which the user normally updates manually. All is working well; however, there is also a button on the form which programmatically updates some of the table values and this is where things go wrong. When I click the button, the table values change and the X and Y axis labels update as expected, but the actual chart bars do not update. To get the bars to update with the new values, I have to manually edit at least one value in the table. Evidently the table cell exit event is triggering the chart calculate event, so I tried to force this manually by calling “xfa.form.execCalculate” at the end of the button click event, but this doesn’t resolve the bar “repaint” issue. Any thoughts?Thanks,Rod.

  12. John Brinkman says:

    Rod:Glad to hear the chart works for you.My guess is that when your table gets programmatically updated then Reader isn’t correctly issuing a “layout changed” notification to the bar object(s). After you’ve finished programmatically changing the value(s) try explicitly forcing it to re-draw usng xfa.layout.relayout(). It’s a bit brute force, but should work.John

  13. Rod says:

    Hi John,That fixed it :-) … thank you. Incidentally, at first I put the relayout() command in the button click event, but the redraw was only partial, so I moved it to the validate event on the last programmatically changed table cell and it worked as hoped.Thank you,Rod.