Advanced Charting
While most developers only need basic charts, dojox/charting
is capable of highly advanced charts: charts with animations, charts that respond to changes in data, and charts that respond to events. In this tutorial, you will learn about using some these advanced capabilities within dojox/charting
.
Getting Started
Creating advanced, dynamic charts with dojox/charting
is probably easier than you would believe. Creating charts that handle changes in data, zooming, panning, and scrolling is simple, thanks to the Dojo Toolkit!
Dojo Stores and Charting
Dojo provides an outstanding, flexible Store API which allows developers to manage (add, edit, delete, query, etc.) data in an efficient manner. Due to the prevalence of Store usage within Dojo applications, dojox/charting/StoreSeries
has incorporated solutions for creating data series from Store data.
StoreSeries
dojox/charting/StoreSeries
was specifically created to incorporate data stores within charts. The first step in using store data within a chart is creating the store:
require(["dojo/store/Observable", "dojo/store/Memory"], function(ObservableStore, MemoryStore) {
// Initial data
var data = [
// This information, presumably, would come from a database or web service
// Note that the values for site are either 1 or 2
{ id: 1, value: 20, site: 1 },
{ id: 2, value: 16, site: 1 },
{ id: 3, value: 11, site: 1 },
{ id: 4, value: 18, site: 1 },
{ id: 5, value: 26, site: 1 },
{ id: 6, value: 19, site: 2 },
{ id: 7, value: 20, site: 2 },
{ id: 8, value: 28, site: 2 },
{ id: 9, value: 12, site: 2 },
{ id: 10, value: 4, site: 2 }
];
// Create the data store
// Store information in a data store on the client side
var store = new ObservableStore(new MemoryStore({
data: {
identifier: "id",
label: "Users Online",
items: data
}
}));
});
Wrapping the store in Observable is important, as it allows notifications to be sent to the store, which in turn notifies the StoreSeries instance we will create.
With the store in place, the chart, plot, and axes should be added just as they were in the basic tutorial. With the chart, plot, and axes created, it's time to implement StoreSeries:
// Adds a StoreSeries to the y axis, queries for all site 1 items
chart.addSeries("y", new StoreSeries(store, { query: { site: 1 } }, "value"));
With the StoreSeries in place, each time the data store is notified of a change, the series is re-rendered on the chart.
Charting Animation: Zooming, Scrolling, and Panning
Dojo's charting solution is flexible enough to allow a change in data at any time, so it's only natural that charts would also need to be flexible enough to accommodate those changes in data. Zooming, scrolling, and panning in charts allows just that.
The role that each animation plays is straight-forward:
- Zooming - Allows developers to enlarge elements within the chart without enlarging the chart itself
- Scrolling - Allows the user to click and drag their way around the chart
- Panning - Allows the user to elegantly pan to a different view of the chart
These effects can be accomplished with two chart methods: setAxisWindow
and setWindow
.
setAxisWindow(name,scale,offset)
setAxisWindow
defines a window on the named axis with a scale factor, which starts at the set offset in data coordinates. The setAxisWindow
method accepts three arguments:
- name - The name of the axis
- scale - Scale which the chart should change to
- offset - The chart's destination offset
Usage of setAxisWindow
would look like:
// Changes the x axis to twice the scale, offset by 100
chart.setAxisWindow("x",2,100).render();
setWindow(sx,sy,dx,dy)
setWindow
sets scale and offsets on all plots of the chart. The setWindow
method accepts four arguments:
- sx - The magnification factor on horizontal axes
- sy - The magnification factor on vertical axes
- dx - The offset of horizontal axes in pixels
- dy - The offset of vertical axes in pixels
Usage of setWindow
would look like:
// Returns the chart to it original position
chart.setWindow(1, 1, 0, 0).render();
Each method requires the chart's render
method to be called for changes to be reflected on the chart.
Example: Zooming, Scrolling, and Panning
The following example allows the user to zoom, pan, and scroll the chart using sliders.
<script>
// Require the dependencies
require(["dijit/form/HorizontalSlider", "dijit/form/HorizontalRule", "dijit/form/HorizontalRuleLabels", "dojox/charting/Chart", "dojox/charting/themes/Claro", "dojox/lang/functional/object", "dijit/registry", "dojo/on", "dojo/dom", "dojo/_base/event", "dojo/parser", "dojox/charting/axis2d/Default", "dojox/charting/plot2d/Areas", "dojox/charting/plot2d/Grid", "dojo/domReady!"], function(HorizontalSlider, HorizontalRule, HorizontalRuleLabels, Chart, Claro, functionalObject, registry, on, dom, baseEvent, parser) {
// Initialize chart, scales, and offsets
var chart, moveable, scaleX = 1, scaleY = 1, offsetX = 0, offsetY = 0;
// Updates the slider values, animates the change in scale and offsets
var reflect = function(){
functionalObject.forIn(chart.axes, function(axis){
var scale = axis.getWindowScale(),
offset = Math.round(axis.getWindowOffset() * axis.getScaler().bounds.scale);
if(axis.vertical){
scaleY = scale;
offsetY = offset;
}else{
scaleX = scale;
offsetX = offset;
}
});
setTimeout(function(){
registry.byId("scaleXSlider").set("value", scaleX);
registry.byId("offsetXSlider").set("value", offsetX);
registry.byId("scaleYSlider").set("value", scaleY);
registry.byId("offsetYSlider").set("value", offsetY);
}, 25);
};
// Update the scale and offsets of *all* plots on the chart
var update = function(){
chart.setWindow(scaleX, scaleY, offsetX, offsetY, { duration: 1500 }).render();
reflect();
};
// The following four methods are fired when the corresponding sliders are changed
var scaleXEvent = function(value){
scaleX = value;
dom.byId("scaleXValue").innerHTML = value;
update();
};
var scaleYEvent = function(value){
scaleY = value;
dom.byId("scaleYValue").innerHTML = value;
update();
};
var offsetXEvent = function(value){
offsetX = value;
dom.byId("offsetXValue").innerHTML = value;
update();
};
var offsetYEvent = function(value){
offsetY = value;
dom.byId("offsetYValue").innerHTML = value;
update();
};
// Function called when the mouse goes down
var _init = null;
var onMouseDown = function(e){
console.warn("mousedown");
_init = {x: e.clientX, y: e.clientY, ox: offsetX, oy: offsetY};
baseEvent.stop(e);
};
// Function called when the mouse is released
var onMouseUp = function(e){
if(_init){
// Clears the click/drag, updates the chart
console.warn("mouseup");
_init = null;
reflect();
baseEvent.stop(e);
}
};
// Create the base chart
chart = new Chart("chart");
chart.setTheme(Claro);
chart.addAxis("x", {fixLower: "minor", natural: true, stroke: "grey",
majorTick: {stroke: "black", length: 4}, minorTick: {stroke: "gray", length: 2}});
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}});
chart.addPlot("default", {type: "Areas", animate: {duration: 1800}});
chart.addSeries("Series A", [0, 25, 5, 20, 10, 15, 5, 20, 0, 25]);
chart.addAxis("x2", {fixLower: "minor", natural: true, leftBottom: false, stroke: "grey",
majorTick: {stroke: "black", length: 4}, minorTick: {stroke: "gray", length: 2}});
chart.addAxis("y2", {vertical: true, min: 0, max: 20, leftBottom: false, stroke: "grey",
majorTick: {stroke: "black", length: 4}, minorTick: {stroke: "gray", length: 2}});
chart.addPlot("plot2", {type: "Areas", hAxis: "x2", vAxis: "y2", animate: {duration: 1800}});
chart.addSeries("Series B", [15, 0, 15, 0, 15, 0, 15, 0, 15, 0, 15, 0, 15, 0, 15, 0, 15], {plot: "plot2"});
chart.addPlot("grid", { type: "Grid", hMinorLines: true });
chart.render();
parser.parse();
// Add change events to the sliders to know when chart changes should be triggered
registry.byId("scaleXSlider").on("Change", scaleXEvent, true);
registry.byId("scaleYSlider").on("Change", scaleYEvent, true);
registry.byId("offsetXSlider").on("Change", offsetXEvent, true);
registry.byId("offsetYSlider").on("Change", offsetYEvent, true);
// Add mouse events to the chart to allow click and drag
var chartNode = dom.byId("chart");
on(chartNode, "mousedown", onMouseDown);
on(chartNode, "mouseup", onMouseUp);
});
</script>
<!-- create the sliders to control chart scale and offsets -->
<table>
<tr><td align="center" class="pad">Scale X (<span id="scaleXValue">1</span>)</td></tr>
<tr><td>
<div id="scaleXSlider" data-dojo-type="dijit/form/HorizontalSlider" data-dojo-props="
value: 1, minimum: 1, maximum: 5, discreteValues: 5, showButtons: false"
style="width: 600px;">
<div data-dojo-type="dijit/form/HorizontalRule" data-dojo-props="
container: 'bottomDecoration', count: 5" style="height:5px;"></div>
<div data-dojo-type="dijit/form/HorizontalRuleLabels" data-dojo-props="
container: 'bottomDecoration', count: 5, minimum: 1, maximum: 5, constraints: {pattern: '##'}" style="height:1.2em;font-size:75%;color:gray;"></div>
</div>
</td></tr>
<tr><td align="center" class="pad">Scale Y (<span id="scaleYValue">1</span>)</td></tr>
<tr><td>
<div id="scaleYSlider" data-dojo-type="dijit/form/HorizontalSlider" data-dojo-props="
value: 1, minimum: 1, maximum: 5, discreteValues: 5, showButtons: false"
style="width: 600px;">
<div data-dojo-type="dijit/form/HorizontalRule" data-dojo-props="
container: 'bottomDecoration', count: 5" style="height:5px;"></div>
<div data-dojo-type="dijit/form/HorizontalRuleLabels" data-dojo-props="
container: 'bottomDecoration', count: 5, minimum: 1, maximum: 5, constraints: {pattern: '##'}" style="height:1.2em;font-size:75%;color:gray;"></div>
</div>
</td></tr>
<tr><td align="center" class="pad">Offset X (<span id="offsetXValue">0</span>)</td></tr>
<tr><td>
<div id="offsetXSlider" data-dojo-type="dijit/form/HorizontalSlider" data-dojo-props="
value: 1, minimum: 0, maximum: 500, discreteValues: 501, showButtons: false"
style="width: 600px;">
<div data-dojo-type="dijit/form/HorizontalRule" data-dojo-props="
container: 'bottomDecoration', count: 6" style="height:5px;"></div>
<div data-dojo-type="dijit/form/HorizontalRuleLabels" data-dojo-props="
container: 'bottomDecoration', count: 6, minimum: 0, maximum: 500, constraints: {pattern: '####'}" style="height:1.2em;font-size:75%;color:gray;"></div>
</div>
</td></tr>
<tr><td align="center" class="pad">Offset Y (<span id="offsetYValue">0</span>)</td></tr>
<tr><td>
<div id="offsetYSlider" data-dojo-type="dijit/form/HorizontalSlider" data-dojo-props="
value: 1, minimum: 0, maximum: 500, discreteValues: 501, showButtons: false"
style="width: 600px;">
<div data-dojo-type="dijit/form/HorizontalRule" data-dojo-props="
container: 'bottomDecoration', count: 6" style="height:5px;"></div>
<div data-dojo-type="dijit/form/HorizontalRuleLabels" data-dojo-props="
container: 'bottomDecoration', count: 6, minimum: 0, maximum: 500, constraints: {pattern: '####'}" style="height:1.2em;font-size:75%;color:gray;"></div>
</div>
</td></tr>
</table><br /><br />
<!-- the chart node -->
<div id="chart" style="width: 800px; height: 400px;"></div>
dojox/charting Events
Event connections within all interactive interfaces are important. It's important that they are effectively and efficiently relayed, as well as plainly available. With those goals in mind, an API by which developers can connect and respond to user-triggered events has been created.
Event listeners are assigned to specific plots on a given chart with the connectToPlot
method:
// Connect an event to the "default" plot
chart.connectToPlot("default", function(evt) {
// Use console to output information about the event
console.info("Chart event on default plot!",evt);
console.info("Event type is: ",evt.type);
console.info("The element clicked was: ",evt.element);
});
The connectToPlot's event
object is very different from a traditional DOM event. This event object contains the following key properties:
- type - The type of event (
onclick
,onmouseover
, oronmouseleave
) - element - The type of element hovered (
marker
,bar
,column
,circle
,slice
) - x - The
x
value of the point - y - The
y
value of the point - shape - The gfx shape object that represents a data point
A full listing of event properties can be found at the dojox/charting reference guide.
The plugins covered within the basic charting tutorial use charting event solutions to trigger movement in shapes.
Example: Using Chart Events
This example illustrates using charting events to change the color pie piece when hovered over, and rotate the piece 360 degrees when clicked:
// Require the basic 2d chart resource: Chart
// Retrieve the Tooltip class
// Require the theme of our choosing
require(["dojox/charting/Chart", "dojox/charting/action2d/Tooltip", "dojox/charting/themes/Claro", "dojox/charting/plot2d/Pie", "dojox/charting/axis2d/Default", "dojo/domReady!"], function(Chart, Tooltip, Claro) {
// Define the data
var chartData = [10000,9200,11811,12000,7662,13887,14200,12222,12000,10009,11288,12099];
// Create the chart within it's "holding" node
var chart = new Chart("chartNode");
// Set the theme
chart.setTheme(Claro);
// Add the only/default plot
chart.addPlot("default", {
type: "Pie",
markers: true
});
// Add axes
chart.addAxis("x");
chart.addAxis("y", { min: 5000, max: 30000, vertical: true, fixLower: "major", fixUpper: "major" });
// Add the series of data
chart.addSeries("Monthly Sales - 2010", chartData);
// Create the tooltip
var tip = new Tooltip(chart, "default");
// Render the chart!
chart.render();
// Add a mouseover event to the plot
chart.connectToPlot("default",function(evt) {
// Output some debug information to the console
console.warn(evt.type," on element ",evt.element," with shape ",evt.shape);
// Get access to the shape and type
var shape = evt.shape, type = evt.type;
// React to click event
if(type == "onclick") {
// Update its fill
var rotateFx = new gfxFx.animateTransform({
duration: 1200,
shape: shape,
transform: [
{ name: "rotategAt", start: [0,240,240], end: [360,240,240] }
]
}).play();
}
// If it's a mouseover event
else if(type == "onmouseover") {
// Store the original color
if(!shape.originalFill) {
shape.originalFill = shape.fillStyle;
}
// Set the fill color to pink
shape.setFill("pink");
}
// If it's a mouseout event
else if(type == "onmouseout") {
// Set the fill the original fill
shape.setFill(shape.originalFill);
}
});
});
It's important to realize that every element within a chart is just a GFX graphic, so elements of the chart may be treated and animated as such, allowing for us to create some unique effects.
Conclusion
Creating basic Dojo charts isn't always enough. Dynamic data calls for dynamic charts, and the Dojo Toolkit's dojox/charting
library provides all the tools to make your charts as flexible as your data.
dojox/charting Resources
Looking for more detail about Dojo's charting library? Check out these great resources: