A Few Tips for gRaphaël Line Charts

October 1st, 2011 Permalink

gRaphaël is a compact chart display package for the Raphaël canvas library - terse, well-written, to-the-point code. It lacks some frills, definitely lacks documentation, and can become overly slow on mobile devices given even a modest number of points on a chart, but it gets the job done and makes it easy to produce attractive charts with a minimum of design talent on the part of the developer. That last point is ever a bonus from my point of view.

Assembling simple charts with gRaphaël is a breeze. Most of the time you spend on it in any given project will be the hacking and grumbling about the one or two things you absolutely must have but that gRaphaël won't let you do out of the box. I'll focus here on line charts for the sake of keeping this post down to a reasonable length, and I'll be using Raphaël 1.5.2 and gRaphaël 0.4.1 - but I've pulled the latest non-minified g.line.js file for building line charts from GitHub so that you can more clearly see the minor alterations I'll be proposing, should you be so inclined.

A Simple Example

Here's an example showing just how simple it is to build a chart if you don't have any design and display goals that gRaphaël will struggle with:

<script src="https://www.exratione.com/assets/raphael.1.5.2.min.js"
    type="text/javascript" charset="utf-8"></script>
<script src="https://www.exratione.com/assets/g.raphael.0.4.1.min.js"
    type="text/javascript" charset="utf-8"></script>
<script src="https://www.exratione.com/assets/g.line.0.4.2.altered.js"
    type="text/javascript" charset="utf-8"></script>

<style type="text/css">
.chartWrapper {
  margin: 15px auto;
  width: 500px;
  height: 200px;
  text-align: center;
}
</style>

<div id="simpleExample" class="chartWrapper"></div>

<script type="text/javascript">
  var r = Raphael("simpleExample");
  var chart = r.g.linechart(
    10, 10,      // top left anchor
    490, 180,    // bottom right anchor
    [
      [1, 2, 3, 4, 5, 6, 7],        // red line x-values
      [3.5, 4.5, 5.5, 6.5, 7, 8]    // blue line x-values
    ],
    [
      [12, 32, 23, 15, 17, 27, 22], // red line y-values
      [10, 20, 30, 25, 15, 28]      // blue line y-values
    ],
    {
       nostroke: false,   // lines between points are drawn
       axis: "0 0 1 1",   // draw axes on the left and bottom
       symbol: "disc",    // use a filled circle as the point symbol
       smooth: true,      // curve the lines to smooth turns on the chart
       dash: "-",         // draw the lines dashed
       colors: [
         "#995555",       // the first line is red
         "#555599"        // the second line is blue
       ]
     });
</script>

About the only thing worth remarking on in the chart above is the need to leave space between the declared edge of the canvas frame and the declared edge of the chart. The axis labels fall outside the anchor points you declare, and so does the outer half of the outermost point. If you cut it off exactly, then the far right blue disc would be cut off, and so would the axis labels. Which leads us into the first tip:

Sizing the Chart Axes

gRaphaël charts automatically size their axes to fit the data provided, but sometimes you want the chart to be larger than that. The quickest recourse here is to provide an additional two-point data set with the minimum and maximum values you are after and make it transparent, like this:

  var r = Raphael("sizingExample");
  var chart = r.g.linechart(
    10, 10,      // top left anchor
    490, 180,    // bottom right anchor
    [
      [1, 2, 3, 4, 5, 6, 7],        // red line x-values
      [3.5, 4.5, 5.5, 6.5, 7, 8],   // blue line x-values
      [0, 10]                       // transparent line x-values
    ],
    [
      [12, 32, 23, 15, 17, 27, 22], // red line y-values
      [10, 20, 30, 25, 15, 28],     // blue line y-values
      [0, 40]                       // transparent line y-values
    ],
    {
       nostroke: false,   // lines between points are drawn
       axis: "0 0 1 1",   // draw axes on the left and bottom
       symbol: "disc",    // use a filled circle as the point symbol
       smooth: true,      // curve the lines to smooth turns on the chart
       dash: "-",         // draw the lines dashed
       colors: [
         "#995555",       // the first line is red
         "#555599",       // the second line is blue
         "transparent"    // the third line is invisible
       ]
    });

The Largest Data Set Has to Be First

The data set with the most points has to be first in the array of x-values and array of y-values; if it isn't, you'll get a nicely obscure Javascript error and the chart will not render. In the examples above, you'll see that the seven-point set is ahead of the six-point and two-point sets for exactly this reason.

Different Symbols and Line Types on the Same Chart

The gRaphaël code as it presently stands allows you to pass in an array of symbols in the options object. For example:

    {
       symbol: [
         "disc",          // the first data set is filled circles
         "square"         // the second data set is filled squares
       ],
       colors: [
         "#995555",       // the first line is red
         "#555599",       // the second line is blue
       ]
    }

Unfortunately, the code as it stands doesn't allow you to do this with the dash parameter - but that can be fixed. Find the following in g.line.js:

for (i = 0, ii = valuesy.length; i < ii; i++) {
  if (!opts.nostroke) {
    lines.push(line = this.path().attr({
      stroke: colors[i],
      "stroke-width": opts.width || 2,
      "stroke-linejoin": "round",
      "stroke-linecap": "round",
      "stroke-dasharray": opts.dash || ""
    }));
  }

And replace "opts.dash" as shown below:

for (i = 0, ii = valuesy.length; i < ii; i++) {
  if (!opts.nostroke) {
    var dash = this.raphael.is(opts.dash, "array") ? opts.dash[i] : opts.dash || "";
    lines.push(line = this.path().attr({
      stroke: colors[i],
      "stroke-width": opts.width || 2,
      "stroke-linejoin": "round",
      "stroke-linecap": "round",
      "stroke-dasharray": dash
    }));
  }

That opening for-loop is iterating over the array of data sets or lines, so you could apply this same strategy to allow you to change any of the parameters there to accept values per data set displayed on the chart. Here is a sample chart with lines of varying color, symbol, and dash format:

  var r = Raphael("variedExample");
  var chart = r.g.linechart(
    10, 10,      // top left anchor
    490, 180,    // bottom right anchor
    [
      [1, 2, 3, 4, 5, 6, 7],        // red line x-values
      [3.5, 4.5, 5.5, 6.5, 7, 8],   // blue line x-values
      [2, 3, 4, 4.5, 6, 7, 8.5]     // green line x-values
    ],
    [
      [12, 32, 23, 15, 17, 27, 22], // red line y-values
      [10, 20, 30, 25, 15, 28],     // blue line y-values
      [25, 10, 25, 30, 15, 10, 20]  // green line y-values
    ],
    {
       nostroke: false,   // lines between points are drawn
       axis: "0 0 1 1",   // draw axes on the left and bottom
       symbol: [
         "disc",           // the red data set is filled circles
         "square",         // the blue data set is filled squares
         "triangle"        // the green data set is filled triangles
       ],
       smooth: true,      // curve the lines to smooth turns on the chart
       dash: [
         "-",        // the red line is dashed
         "",         // the blue line is solid
         "-."        // the green line is mixed dash-dot
       ],
       colors: [
         "#995555",       // the first line is red
         "#555599",       // the second line is blue
         "#559955"        // the third line is green
       ]
    });

Changing the Axis Colors

In gRaphaël 0.4.1, you can have any color of axis lines and text you like ... so long as it's black. This is of course something of a problem when you want to show a light chart on a dark background, or have the sudden and irresistible urge to present in purple. Fortunately all of the graphical objects in Raphaël exist in the DOM and in other data structures, and can be manipulated rapidly after the chart is generated. It is well worth your time to explore the structure of the chart and DOM in your toolkit of choice (e.g. Firebug or the developer tools in WebKit based browsers), as almost everything is conveniently open to editing, rearrangement, or even animation after the fact.

So for the chart above, we could do something simple and subtle such as change the axes and labels to a more sedate grey:

  for( var i = 0, l = chart.axis.length; i < l; i++ ) {
    // change the axis and tick-marks
    chart.axis[i].attr("stroke", "#999999");

    // change the axis labels
    var axisItems = chart.axis[i].text.items
    for( var ii = 0, ll = axisItems.length; ii < ll; ii++ ) {
       axisItems[ii].attr("fill", "#999999");
    }
  }

Given that this will add some processing time to the chart rendering - not an issue on the desktop, but definitely noticeable on today's mobile devices - a better way forward here would be to edit the gRaphaël code to allow axis and label colors to be passed in via the options object. That would entail changes to g.raphael.js as well as g.line.js, so I leave that as an exercise for the reader. I have to draw the line somewhere, and in any case an example in which I alter a chart post-facto makes for a better segue into the next tip.

Using Date Axes

gRaphaël line graphs are for numeric values, so if you want to display dates, then the actual values have to be numeric representations of those dates. i.e. Unix-style timestamps or time in milliseconds, that sort of thing. However, your axis labels will not look pretty if you do that - they will just display whatever large number you are using to represent a date rather than the date itself.

The quick solution to this problem is, as for the previous example, to alter the chart data after its creation. For formatting dates the popular dateFormat library is used. An example follows:

// using the excellent dateFormat code from Steve Levithan.
// See: http://blog.stevenlevithan.com/archives/date-time-format
<script src="https://www.exratione.com/assets/date.format.1.2.3.min.js"
    type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
  var r = Raphael("dateExample");

  // assemble some x-axis dates
  var xAxisValues = [];
  for( var i = 0; i < 8; i++ ) {
    var date = new Date();
    date.setDate(date.getDate() + i);
    xAxisValues.push(date.getTime());
  }
  var yAxisValues = [1, 5, 10, 5, 15, 6, 22];

  var chart = r.g.linechart(
    10, 10,      // top left anchor
    490, 180,    // bottom right anchor
    xAxisValues,
    yAxisValues,
    {
       nostroke: false,   // lines between points are drawn
       axis: "0 0 1 1",   // draw axes on the left and bottom
       symbol: "disc",            // the data set is filled circles
       smooth: true,      // curve the lines to smooth turns on the chart
       colors: ["#995555" ]     // the line is red
     });

    // change the x-axis labels
    var axisItems = chart.axis[0].text.items
    for( var i = 0, l = axisItems.length; i < l; i++ ) {
       var date = new Date(parseInt(axisItems[i].attr("text")));
       // using the excellent dateFormat code from Steve Levithan
       // See: http://blog.stevenlevithan.com/archives/date-time-format
       axisItems[i].attr("text", dateFormat(date, "mm/dd, htt"));
    }
</script>

I'm sure that further refinements on the date axis placement and content of labels will immediately spring to mind - the initial dry run illustrated above is far from perfect, after all. But it is a start, and I encourage you to dig into the gRaphaël axis sizing code to see how you might tinker with it to create effects better suited to your project specifications.