Zooming, Scrolling, and Panning in Dojo Charting

By on May 15, 2008 12:37 am
Notice: There is a newer version of this post available

As mentioned in my previous post Dojo Charting Reorganization, this week I worked on zooming, scrolling, and panning of charts. It turned out to be a more complex task than I anticipated due to the little-known fact that Dojo Charting can stack multiple plots per chart and can show multiple independent axes on all 4 sides of the chart. These problems were solved and a new API was introduced on the chart object:

  • chart.setAxisWindow(name, scale, offset) — defines a window on the named axis with a scale factor, which starts at the set offset in data coordinates.
    • The scale parameter must be >= 1.
    • The offset parameter should be >= 0.
    • For example if I have an array of 10 numeric values, and I want to show them ##3-8, chart.setWindow(“x”, 3, 2) will do the trick.
    • This call affects only plots attached to the named axis, other plots are unaffected.
  • chart.setWindow(sx, sy, dx, dy) — sets scale and offsets on all plots of the chart.
    • The sx parameter specifies the magnification factor on horizontal axes. It should be >= 1.
    • The sy parameter specifies the magnification factor on vertical axes. It should be >= 1.
    • The dx parameter specifies the offset of horizontal axes in pixels. It should be >= 0.
    • The dy parameter specifies the offset of vertical axes in pixels. It should be >= 0.
    • All chart’s axes (and, by extension, plots) will be affected.

Obviously these new methods do sanity checks, and don’t allow you to scroll outside of axis’ boundaries, or zoom out too far.

As you can see these methods are enough to implement arbitrary zooming to drill down to the smallest details of your chart, scrolling, and panning (moving the chart with you mouse in two dimensions). The latter functionality is taxing on the browser, but the new generation of browsers (Firefox 3, Safari 3, Opera 9.5) are up to the task.

While this functionality is available now in the trunk, I plan to incorporate the panning support directly into the Charting widget, so it can be turned on and off by a parameter.

Now it is time for some show-and-tell. Below you can see my test chart in its normal state:

Normal chart.

As you can see this chart has 4 independent axis on all sides, and two plots, each with independent axes: the light brown plot uses the bottom and the the left axes, while the dark brown plot uses the axes on the top and the right sides.

You can access this test application in Dojo Nightlies. Warning: this link uses the raw Dojo served directly from Dojo development server, so the initial loading will be relatively slow. In real applications you should use Dojo builds.

This is the code required to build this chart:

// create our chart
var chart = new dojox.charting.Chart2D("test");

// set a theme
chart.setTheme(dojox.charting.themes.PlotKit.orange);

// create the ordinal horizontal axis with custom visual attributes,
// by default it will be attached to the bottom of the plot area
chart.addAxis("x", {
    fixLower: "minor", natural: true, stroke: "grey",
    majorTick: {stroke: "black", length: 4},
    minorTick: {stroke: "gray", length: 2}
});

// create the ordinal vertical axis with custom visual attributes,
// by default it will be attached to the left of the plot area
chart.addAxis("y", {
    vertical: true, min: 0, max: 30,
    majorTickStep: 5, minorTickStep: 1, stroke: "grey",
    majorTick: {stroke: "black", length: 4},
    minorTick: {stroke: "gray", length: 2}
});

// add the front plot, which uses default axes (named "x" and "y")
chart.addPlot("default", {type: "Areas"});

// add series to the default plot
chart.addSeries("Series A",
    [0, 25, 5, 20, 10, 15, 5, 20, 0, 25]
);

// create the ordinal horizontal axis with custom visual attributes,
// we specify explicitly that it should be attached to the top
chart.addAxis("x2", {
    fixLower: "minor", natural: true,
    leftBottom: false, stroke: "grey",
    majorTick: {stroke: "black", length: 4},
    minorTick: {stroke: "gray", length: 2}
});

// create the ordinal vertical axis with custom visual attributes,
// we specify explicitly that it should be attached to the right
chart.addAxis("y2", {
    vertical: true, min: 0, max: 20,
    leftBottom: false, stroke: "grey",
    majorTick: {stroke: "black", length: 4},
    minorTick: {stroke: "gray", length: 2}
});

// create the back plot, which uses our custom axes ("x2" and "y2")
chart.addPlot("plot2", {
    type: "Areas", hAxis: "x2", vAxis: "y2"
});

// add series to "plot2" we just created
chart.addSeries("Series B",
    [15, 0, 15, 0, 15, 0, 15, 0, 15,
        0, 15, 0, 15, 0, 15, 0, 15],
    {plot: "plot2"}
);

// add the bottom of our stack we put the grid
// that shows major lines, and horizontal minor lines
chart.addPlot("grid", {type: "Grid", hMinorLines: true});

// show what we've built
chart.render();

Now let’s see it magnified by 2 in both dimensions and moved so we can see the peaks in the middle-left part of the chart:

Zoomed chart.

This picture above was made from the previous picture with one call:

chart.setWindow(2, 2, 181, 143).render();

Obviously axes show new labels to reflect the new state of the chart. Using these labels we can get our bearings and understand what we are looking at.

If we want to return back we can always do:

chart.setWindow(1, 1, 0, 0).render();

Coming Attractions

Now is a good time to start working on Chart event support, so developers will be able to add actions depending on where the user clicked, or hovered over. A highly requested feature, events can be used to do a range of things starting from synchronizing an external table with the current position of the chart, to showing some supplementary information on clicked points.

I plan to add Tooltip widget support to the Chart widget — it will cover the most frequent case of the event processing. Stay tuned!