1131 lines
37 KiB
JavaScript
1131 lines
37 KiB
JavaScript
//>>built
|
|
define("dojox/charting/Chart", ["dojo/_base/lang", "dojo/_base/array","dojo/_base/declare", "dojo/_base/html",
|
|
"dojo/dom", "dojo/dom-geometry", "dojo/dom-construct","dojo/_base/Color", "dojo/_base/sniff",
|
|
"./Element", "./Theme", "./Series", "./axis2d/common",
|
|
"dojox/gfx", "dojox/lang/functional", "dojox/lang/functional/fold", "dojox/lang/functional/reversed"],
|
|
function(lang, arr, declare, html,
|
|
dom, domGeom, domConstruct, Color, has,
|
|
Element, Theme, Series, common,
|
|
g, func, funcFold, funcReversed){
|
|
/*=====
|
|
dojox.charting.__ChartCtorArgs = function(margins, stroke, fill, delayInMs){
|
|
// summary:
|
|
// The keyword arguments that can be passed in a Chart constructor.
|
|
//
|
|
// margins: Object?
|
|
// Optional margins for the chart, in the form of { l, t, r, b}.
|
|
// stroke: dojox.gfx.Stroke?
|
|
// An optional outline/stroke for the chart.
|
|
// fill: dojox.gfx.Fill?
|
|
// An optional fill for the chart.
|
|
// delayInMs: Number
|
|
// Delay in ms for delayedRender(). Default: 200.
|
|
this.margins = margins;
|
|
this.stroke = stroke;
|
|
this.fill = fill;
|
|
this.delayInMs = delayInMs;
|
|
}
|
|
=====*/
|
|
var dc = dojox.charting,
|
|
clear = func.lambda("item.clear()"),
|
|
purge = func.lambda("item.purgeGroup()"),
|
|
destroy = func.lambda("item.destroy()"),
|
|
makeClean = func.lambda("item.dirty = false"),
|
|
makeDirty = func.lambda("item.dirty = true"),
|
|
getName = func.lambda("item.name");
|
|
|
|
declare("dojox.charting.Chart", null, {
|
|
// summary:
|
|
// The main chart object in dojox.charting. This will create a two dimensional
|
|
// chart based on dojox.gfx.
|
|
//
|
|
// description:
|
|
// dojox.charting.Chart is the primary object used for any kind of charts. It
|
|
// is simple to create--just pass it a node reference, which is used as the
|
|
// container for the chart--and a set of optional keyword arguments and go.
|
|
//
|
|
// Note that like most of dojox.gfx, most of dojox.charting.Chart's methods are
|
|
// designed to return a reference to the chart itself, to allow for functional
|
|
// chaining. This makes defining everything on a Chart very easy to do.
|
|
//
|
|
// example:
|
|
// Create an area chart, with smoothing.
|
|
// | new dojox.charting.Chart(node))
|
|
// | .addPlot("default", { type: "Areas", tension: "X" })
|
|
// | .setTheme(dojox.charting.themes.Shrooms)
|
|
// | .addSeries("Series A", [1, 2, 0.5, 1.5, 1, 2.8, 0.4])
|
|
// | .addSeries("Series B", [2.6, 1.8, 2, 1, 1.4, 0.7, 2])
|
|
// | .addSeries("Series C", [6.3, 1.8, 3, 0.5, 4.4, 2.7, 2])
|
|
// | .render();
|
|
//
|
|
// example:
|
|
// The form of data in a data series can take a number of forms: a simple array,
|
|
// an array of objects {x,y}, or something custom (as determined by the plot).
|
|
// Here's an example of a Candlestick chart, which expects an object of
|
|
// { open, high, low, close }.
|
|
// | new dojox.charting.Chart(node))
|
|
// | .addPlot("default", {type: "Candlesticks", gap: 1})
|
|
// | .addAxis("x", {fixLower: "major", fixUpper: "major", includeZero: true})
|
|
// | .addAxis("y", {vertical: true, fixLower: "major", fixUpper: "major", natural: true})
|
|
// | .addSeries("Series A", [
|
|
// | { open: 20, close: 16, high: 22, low: 8 },
|
|
// | { open: 16, close: 22, high: 26, low: 6, mid: 18 },
|
|
// | { open: 22, close: 18, high: 22, low: 11, mid: 21 },
|
|
// | { open: 18, close: 29, high: 32, low: 14, mid: 27 },
|
|
// | { open: 29, close: 24, high: 29, low: 13, mid: 27 },
|
|
// | { open: 24, close: 8, high: 24, low: 5 },
|
|
// | { open: 8, close: 16, high: 22, low: 2 },
|
|
// | { open: 16, close: 12, high: 19, low: 7 },
|
|
// | { open: 12, close: 20, high: 22, low: 8 },
|
|
// | { open: 20, close: 16, high: 22, low: 8 },
|
|
// | { open: 16, close: 22, high: 26, low: 6, mid: 18 },
|
|
// | { open: 22, close: 18, high: 22, low: 11, mid: 21 },
|
|
// | { open: 18, close: 29, high: 32, low: 14, mid: 27 },
|
|
// | { open: 29, close: 24, high: 29, low: 13, mid: 27 },
|
|
// | { open: 24, close: 8, high: 24, low: 5 },
|
|
// | { open: 8, close: 16, high: 22, low: 2 },
|
|
// | { open: 16, close: 12, high: 19, low: 7 },
|
|
// | { open: 12, close: 20, high: 22, low: 8 },
|
|
// | { open: 20, close: 16, high: 22, low: 8 },
|
|
// | { open: 16, close: 22, high: 26, low: 6 },
|
|
// | { open: 22, close: 18, high: 22, low: 11 },
|
|
// | { open: 18, close: 29, high: 32, low: 14 },
|
|
// | { open: 29, close: 24, high: 29, low: 13 },
|
|
// | { open: 24, close: 8, high: 24, low: 5 },
|
|
// | { open: 8, close: 16, high: 22, low: 2 },
|
|
// | { open: 16, close: 12, high: 19, low: 7 },
|
|
// | { open: 12, close: 20, high: 22, low: 8 },
|
|
// | { open: 20, close: 16, high: 22, low: 8 }
|
|
// | ],
|
|
// | { stroke: { color: "green" }, fill: "lightgreen" }
|
|
// | )
|
|
// | .render();
|
|
|
|
// theme: dojox.charting.Theme?
|
|
// An optional theme to use for styling the chart.
|
|
// axes: dojox.charting.Axis{}?
|
|
// A map of axes for use in plotting a chart.
|
|
// stack: dojox.charting.plot2d.Base[]
|
|
// A stack of plotters.
|
|
// plots: dojox.charting.plot2d.Base{}
|
|
// A map of plotter indices
|
|
// series: dojox.charting.Series[]
|
|
// The stack of data runs used to create plots.
|
|
// runs: dojox.charting.Series{}
|
|
// A map of series indices
|
|
// margins: Object?
|
|
// The margins around the chart. Default is { l:10, t:10, r:10, b:10 }.
|
|
// stroke: dojox.gfx.Stroke?
|
|
// The outline of the chart (stroke in vector graphics terms).
|
|
// fill: dojox.gfx.Fill?
|
|
// The color for the chart.
|
|
// node: DOMNode
|
|
// The container node passed to the constructor.
|
|
// surface: dojox.gfx.Surface
|
|
// The main graphics surface upon which a chart is drawn.
|
|
// dirty: Boolean
|
|
// A boolean flag indicating whether or not the chart needs to be updated/re-rendered.
|
|
// coords: Object
|
|
// The coordinates on a page of the containing node, as returned from dojo.coords.
|
|
|
|
constructor: function(/* DOMNode */node, /* dojox.charting.__ChartCtorArgs? */kwArgs){
|
|
// summary:
|
|
// The constructor for a new Chart. Initializes all parameters used for a chart.
|
|
// returns: dojox.charting.Chart
|
|
// The newly created chart.
|
|
|
|
// initialize parameters
|
|
if(!kwArgs){ kwArgs = {}; }
|
|
this.margins = kwArgs.margins ? kwArgs.margins : {l: 10, t: 10, r: 10, b: 10};
|
|
this.stroke = kwArgs.stroke;
|
|
this.fill = kwArgs.fill;
|
|
this.delayInMs = kwArgs.delayInMs || 200;
|
|
this.title = kwArgs.title;
|
|
this.titleGap = kwArgs.titleGap;
|
|
this.titlePos = kwArgs.titlePos;
|
|
this.titleFont = kwArgs.titleFont;
|
|
this.titleFontColor = kwArgs.titleFontColor;
|
|
this.chartTitle = null;
|
|
|
|
// default initialization
|
|
this.theme = null;
|
|
this.axes = {}; // map of axes
|
|
this.stack = []; // stack of plotters
|
|
this.plots = {}; // map of plotter indices
|
|
this.series = []; // stack of data runs
|
|
this.runs = {}; // map of data run indices
|
|
this.dirty = true;
|
|
this.coords = null;
|
|
|
|
// create a surface
|
|
this.node = dom.byId(node);
|
|
var box = domGeom.getMarginBox(node);
|
|
this.surface = g.createSurface(this.node, box.w || 400, box.h || 300);
|
|
},
|
|
destroy: function(){
|
|
// summary:
|
|
// Cleanup when a chart is to be destroyed.
|
|
// returns: void
|
|
arr.forEach(this.series, destroy);
|
|
arr.forEach(this.stack, destroy);
|
|
func.forIn(this.axes, destroy);
|
|
if(this.chartTitle && this.chartTitle.tagName){
|
|
// destroy title if it is a DOM node
|
|
domConstruct.destroy(this.chartTitle);
|
|
}
|
|
this.surface.destroy();
|
|
},
|
|
getCoords: function(){
|
|
// summary:
|
|
// Get the coordinates and dimensions of the containing DOMNode, as
|
|
// returned by dojo.coords.
|
|
// returns: Object
|
|
// The resulting coordinates of the chart. See dojo.coords for details.
|
|
return html.coords(this.node, true); // Object
|
|
},
|
|
setTheme: function(theme){
|
|
// summary:
|
|
// Set a theme of the chart.
|
|
// theme: dojox.charting.Theme
|
|
// The theme to be used for visual rendering.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
this.theme = theme.clone();
|
|
this.dirty = true;
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
addAxis: function(name, kwArgs){
|
|
// summary:
|
|
// Add an axis to the chart, for rendering.
|
|
// name: String
|
|
// The name of the axis.
|
|
// kwArgs: dojox.charting.axis2d.__AxisCtorArgs?
|
|
// An optional keyword arguments object for use in defining details of an axis.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
var axis, axisType = kwArgs && kwArgs.type || "Default";
|
|
if(typeof axisType == "string"){
|
|
if(!dc.axis2d || !dc.axis2d[axisType]){
|
|
throw Error("Can't find axis: " + axisType + " - Check " + "require() dependencies.");
|
|
}
|
|
axis = new dc.axis2d[axisType](this, kwArgs);
|
|
}else{
|
|
axis = new axisType(this, kwArgs);
|
|
}
|
|
axis.name = name;
|
|
axis.dirty = true;
|
|
if(name in this.axes){
|
|
this.axes[name].destroy();
|
|
}
|
|
this.axes[name] = axis;
|
|
this.dirty = true;
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
getAxis: function(name){
|
|
// summary:
|
|
// Get the given axis, by name.
|
|
// name: String
|
|
// The name the axis was defined by.
|
|
// returns: dojox.charting.axis2d.Default
|
|
// The axis as stored in the chart's axis map.
|
|
return this.axes[name]; // dojox.charting.axis2d.Default
|
|
},
|
|
removeAxis: function(name){
|
|
// summary:
|
|
// Remove the axis that was defined using name.
|
|
// name: String
|
|
// The axis name, as defined in addAxis.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(name in this.axes){
|
|
// destroy the axis
|
|
this.axes[name].destroy();
|
|
delete this.axes[name];
|
|
// mark the chart as dirty
|
|
this.dirty = true;
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
addPlot: function(name, kwArgs){
|
|
// summary:
|
|
// Add a new plot to the chart, defined by name and using the optional keyword arguments object.
|
|
// Note that dojox.charting assumes the main plot to be called "default"; if you do not have
|
|
// a plot called "default" and attempt to add data series to the chart without specifying the
|
|
// plot to be rendered on, you WILL get errors.
|
|
// name: String
|
|
// The name of the plot to be added to the chart. If you only plan on using one plot, call it "default".
|
|
// kwArgs: dojox.charting.plot2d.__PlotCtorArgs
|
|
// An object with optional parameters for the plot in question.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
var plot, plotType = kwArgs && kwArgs.type || "Default";
|
|
if(typeof plotType == "string"){
|
|
if(!dc.plot2d || !dc.plot2d[plotType]){
|
|
throw Error("Can't find plot: " + plotType + " - didn't you forget to dojo" + ".require() it?");
|
|
}
|
|
plot = new dc.plot2d[plotType](this, kwArgs);
|
|
}else{
|
|
plot = new plotType(this, kwArgs);
|
|
}
|
|
plot.name = name;
|
|
plot.dirty = true;
|
|
if(name in this.plots){
|
|
this.stack[this.plots[name]].destroy();
|
|
this.stack[this.plots[name]] = plot;
|
|
}else{
|
|
this.plots[name] = this.stack.length;
|
|
this.stack.push(plot);
|
|
}
|
|
this.dirty = true;
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
getPlot: function(name){
|
|
// summary:
|
|
// Get the given plot, by name.
|
|
// name: String
|
|
// The name the plot was defined by.
|
|
// returns: dojox.charting.plot2d.Base
|
|
// The plot.
|
|
return this.stack[this.plots[name]];
|
|
},
|
|
removePlot: function(name){
|
|
// summary:
|
|
// Remove the plot defined using name from the chart's plot stack.
|
|
// name: String
|
|
// The name of the plot as defined using addPlot.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(name in this.plots){
|
|
// get the index and remove the name
|
|
var index = this.plots[name];
|
|
delete this.plots[name];
|
|
// destroy the plot
|
|
this.stack[index].destroy();
|
|
// remove the plot from the stack
|
|
this.stack.splice(index, 1);
|
|
// update indices to reflect the shift
|
|
func.forIn(this.plots, function(idx, name, plots){
|
|
if(idx > index){
|
|
plots[name] = idx - 1;
|
|
}
|
|
});
|
|
// remove all related series
|
|
var ns = arr.filter(this.series, function(run){ return run.plot != name; });
|
|
if(ns.length < this.series.length){
|
|
// kill all removed series
|
|
arr.forEach(this.series, function(run){
|
|
if(run.plot == name){
|
|
run.destroy();
|
|
}
|
|
});
|
|
// rebuild all necessary data structures
|
|
this.runs = {};
|
|
arr.forEach(ns, function(run, index){
|
|
this.runs[run.plot] = index;
|
|
}, this);
|
|
this.series = ns;
|
|
}
|
|
// mark the chart as dirty
|
|
this.dirty = true;
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
getPlotOrder: function(){
|
|
// summary:
|
|
// Returns an array of plot names in the current order
|
|
// (the top-most plot is the first).
|
|
// returns: Array
|
|
return func.map(this.stack, getName); // Array
|
|
},
|
|
setPlotOrder: function(newOrder){
|
|
// summary:
|
|
// Sets new order of plots. newOrder cannot add or remove
|
|
// plots. Wrong names, or dups are ignored.
|
|
// newOrder: Array:
|
|
// Array of plot names compatible with getPlotOrder().
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
var names = {},
|
|
order = func.filter(newOrder, function(name){
|
|
if(!(name in this.plots) || (name in names)){
|
|
return false;
|
|
}
|
|
names[name] = 1;
|
|
return true;
|
|
}, this);
|
|
if(order.length < this.stack.length){
|
|
func.forEach(this.stack, function(plot){
|
|
var name = plot.name;
|
|
if(!(name in names)){
|
|
order.push(name);
|
|
}
|
|
});
|
|
}
|
|
var newStack = func.map(order, function(name){
|
|
return this.stack[this.plots[name]];
|
|
}, this);
|
|
func.forEach(newStack, function(plot, i){
|
|
this.plots[plot.name] = i;
|
|
}, this);
|
|
this.stack = newStack;
|
|
this.dirty = true;
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
movePlotToFront: function(name){
|
|
// summary:
|
|
// Moves a given plot to front.
|
|
// name: String:
|
|
// Plot's name to move.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(name in this.plots){
|
|
var index = this.plots[name];
|
|
if(index){
|
|
var newOrder = this.getPlotOrder();
|
|
newOrder.splice(index, 1);
|
|
newOrder.unshift(name);
|
|
return this.setPlotOrder(newOrder); // dojox.charting.Chart
|
|
}
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
movePlotToBack: function(name){
|
|
// summary:
|
|
// Moves a given plot to back.
|
|
// name: String:
|
|
// Plot's name to move.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(name in this.plots){
|
|
var index = this.plots[name];
|
|
if(index < this.stack.length - 1){
|
|
var newOrder = this.getPlotOrder();
|
|
newOrder.splice(index, 1);
|
|
newOrder.push(name);
|
|
return this.setPlotOrder(newOrder); // dojox.charting.Chart
|
|
}
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
addSeries: function(name, data, kwArgs){
|
|
// summary:
|
|
// Add a data series to the chart for rendering.
|
|
// name: String:
|
|
// The name of the data series to be plotted.
|
|
// data: Array|Object:
|
|
// The array of data points (either numbers or objects) that
|
|
// represents the data to be drawn. Or it can be an object. In
|
|
// the latter case, it should have a property "data" (an array),
|
|
// destroy(), and setSeriesObject().
|
|
// kwArgs: dojox.charting.__SeriesCtorArgs?:
|
|
// An optional keyword arguments object that will be mixed into
|
|
// the resultant series object.
|
|
// returns: dojox.charting.Chart:
|
|
// A reference to the current chart for functional chaining.
|
|
var run = new Series(this, data, kwArgs);
|
|
run.name = name;
|
|
if(name in this.runs){
|
|
this.series[this.runs[name]].destroy();
|
|
this.series[this.runs[name]] = run;
|
|
}else{
|
|
this.runs[name] = this.series.length;
|
|
this.series.push(run);
|
|
}
|
|
this.dirty = true;
|
|
// fix min/max
|
|
if(!("ymin" in run) && "min" in run){ run.ymin = run.min; }
|
|
if(!("ymax" in run) && "max" in run){ run.ymax = run.max; }
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
getSeries: function(name){
|
|
// summary:
|
|
// Get the given series, by name.
|
|
// name: String
|
|
// The name the series was defined by.
|
|
// returns: dojox.charting.Series
|
|
// The series.
|
|
return this.series[this.runs[name]];
|
|
},
|
|
removeSeries: function(name){
|
|
// summary:
|
|
// Remove the series defined by name from the chart.
|
|
// name: String
|
|
// The name of the series as defined by addSeries.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(name in this.runs){
|
|
// get the index and remove the name
|
|
var index = this.runs[name];
|
|
delete this.runs[name];
|
|
// destroy the run
|
|
this.series[index].destroy();
|
|
// remove the run from the stack of series
|
|
this.series.splice(index, 1);
|
|
// update indices to reflect the shift
|
|
func.forIn(this.runs, function(idx, name, runs){
|
|
if(idx > index){
|
|
runs[name] = idx - 1;
|
|
}
|
|
});
|
|
this.dirty = true;
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
updateSeries: function(name, data){
|
|
// summary:
|
|
// Update the given series with a new set of data points.
|
|
// name: String
|
|
// The name of the series as defined in addSeries.
|
|
// data: Array|Object:
|
|
// The array of data points (either numbers or objects) that
|
|
// represents the data to be drawn. Or it can be an object. In
|
|
// the latter case, it should have a property "data" (an array),
|
|
// destroy(), and setSeriesObject().
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(name in this.runs){
|
|
var run = this.series[this.runs[name]];
|
|
run.update(data);
|
|
this._invalidateDependentPlots(run.plot, false);
|
|
this._invalidateDependentPlots(run.plot, true);
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
getSeriesOrder: function(plotName){
|
|
// summary:
|
|
// Returns an array of series names in the current order
|
|
// (the top-most series is the first) within a plot.
|
|
// plotName: String:
|
|
// Plot's name.
|
|
// returns: Array
|
|
return func.map(func.filter(this.series, function(run){
|
|
return run.plot == plotName;
|
|
}), getName);
|
|
},
|
|
setSeriesOrder: function(newOrder){
|
|
// summary:
|
|
// Sets new order of series within a plot. newOrder cannot add
|
|
// or remove series. Wrong names, or dups are ignored.
|
|
// newOrder: Array:
|
|
// Array of series names compatible with getPlotOrder(). All
|
|
// series should belong to the same plot.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
var plotName, names = {},
|
|
order = func.filter(newOrder, function(name){
|
|
if(!(name in this.runs) || (name in names)){
|
|
return false;
|
|
}
|
|
var run = this.series[this.runs[name]];
|
|
if(plotName){
|
|
if(run.plot != plotName){
|
|
return false;
|
|
}
|
|
}else{
|
|
plotName = run.plot;
|
|
}
|
|
names[name] = 1;
|
|
return true;
|
|
}, this);
|
|
func.forEach(this.series, function(run){
|
|
var name = run.name;
|
|
if(!(name in names) && run.plot == plotName){
|
|
order.push(name);
|
|
}
|
|
});
|
|
var newSeries = func.map(order, function(name){
|
|
return this.series[this.runs[name]];
|
|
}, this);
|
|
this.series = newSeries.concat(func.filter(this.series, function(run){
|
|
return run.plot != plotName;
|
|
}));
|
|
func.forEach(this.series, function(run, i){
|
|
this.runs[run.name] = i;
|
|
}, this);
|
|
this.dirty = true;
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
moveSeriesToFront: function(name){
|
|
// summary:
|
|
// Moves a given series to front of a plot.
|
|
// name: String:
|
|
// Series' name to move.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(name in this.runs){
|
|
var index = this.runs[name],
|
|
newOrder = this.getSeriesOrder(this.series[index].plot);
|
|
if(name != newOrder[0]){
|
|
newOrder.splice(index, 1);
|
|
newOrder.unshift(name);
|
|
return this.setSeriesOrder(newOrder); // dojox.charting.Chart
|
|
}
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
moveSeriesToBack: function(name){
|
|
// summary:
|
|
// Moves a given series to back of a plot.
|
|
// name: String:
|
|
// Series' name to move.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(name in this.runs){
|
|
var index = this.runs[name],
|
|
newOrder = this.getSeriesOrder(this.series[index].plot);
|
|
if(name != newOrder[newOrder.length - 1]){
|
|
newOrder.splice(index, 1);
|
|
newOrder.push(name);
|
|
return this.setSeriesOrder(newOrder); // dojox.charting.Chart
|
|
}
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
resize: function(width, height){
|
|
// summary:
|
|
// Resize the chart to the dimensions of width and height.
|
|
// description:
|
|
// Resize the chart and its surface to the width and height dimensions.
|
|
// If no width/height or box is provided, resize the surface to the marginBox of the chart.
|
|
// width: Number
|
|
// The new width of the chart.
|
|
// height: Number
|
|
// The new height of the chart.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
var box;
|
|
switch(arguments.length){
|
|
// case 0, do not resize the div, just the surface
|
|
case 1:
|
|
// argument, override node box
|
|
box = lang.mixin({}, width);
|
|
domGeom.setMarginBox(this.node, box);
|
|
break;
|
|
case 2:
|
|
box = {w: width, h: height};
|
|
// argument, override node box
|
|
domGeom.setMarginBox(this.node, box);
|
|
break;
|
|
}
|
|
// in all cases take back the computed box
|
|
box = domGeom.getMarginBox(this.node);
|
|
var d = this.surface.getDimensions();
|
|
if(d.width != box.w || d.height != box.h){
|
|
// and set it on the surface
|
|
this.surface.setDimensions(box.w, box.h);
|
|
this.dirty = true;
|
|
return this.render(); // dojox.charting.Chart
|
|
}else{
|
|
return this;
|
|
}
|
|
},
|
|
getGeometry: function(){
|
|
// summary:
|
|
// Returns a map of information about all axes in a chart and what they represent
|
|
// in terms of scaling (see dojox.charting.axis2d.Default.getScaler).
|
|
// returns: Object
|
|
// An map of geometry objects, a one-to-one mapping of axes.
|
|
var ret = {};
|
|
func.forIn(this.axes, function(axis){
|
|
if(axis.initialized()){
|
|
ret[axis.name] = {
|
|
name: axis.name,
|
|
vertical: axis.vertical,
|
|
scaler: axis.scaler,
|
|
ticks: axis.ticks
|
|
};
|
|
}
|
|
});
|
|
return ret; // Object
|
|
},
|
|
setAxisWindow: function(name, scale, offset, zoom){
|
|
// summary:
|
|
// Zooms an axis and all dependent plots. Can be used to zoom in 1D.
|
|
// name: String
|
|
// The name of the axis as defined by addAxis.
|
|
// scale: Number
|
|
// The scale on the target axis.
|
|
// offset: Number
|
|
// Any offest, as measured by axis tick
|
|
// zoom: Boolean|Object?
|
|
// The chart zooming animation trigger. This is null by default,
|
|
// e.g. {duration: 1200}, or just set true.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
var axis = this.axes[name];
|
|
if(axis){
|
|
axis.setWindow(scale, offset);
|
|
arr.forEach(this.stack,function(plot){
|
|
if(plot.hAxis == name || plot.vAxis == name){
|
|
plot.zoom = zoom;
|
|
}
|
|
});
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
setWindow: function(sx, sy, dx, dy, zoom){
|
|
// summary:
|
|
// Zooms in or out any plots in two dimensions.
|
|
// sx: Number
|
|
// The scale for the x axis.
|
|
// sy: Number
|
|
// The scale for the y axis.
|
|
// dx: Number
|
|
// The pixel offset on the x axis.
|
|
// dy: Number
|
|
// The pixel offset on the y axis.
|
|
// zoom: Boolean|Object?
|
|
// The chart zooming animation trigger. This is null by default,
|
|
// e.g. {duration: 1200}, or just set true.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(!("plotArea" in this)){
|
|
this.calculateGeometry();
|
|
}
|
|
func.forIn(this.axes, function(axis){
|
|
var scale, offset, bounds = axis.getScaler().bounds,
|
|
s = bounds.span / (bounds.upper - bounds.lower);
|
|
if(axis.vertical){
|
|
scale = sy;
|
|
offset = dy / s / scale;
|
|
}else{
|
|
scale = sx;
|
|
offset = dx / s / scale;
|
|
}
|
|
axis.setWindow(scale, offset);
|
|
});
|
|
arr.forEach(this.stack, function(plot){ plot.zoom = zoom; });
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
zoomIn: function(name, range){
|
|
// summary:
|
|
// Zoom the chart to a specific range on one axis. This calls render()
|
|
// directly as a convenience method.
|
|
// name: String
|
|
// The name of the axis as defined by addAxis.
|
|
// range: Array
|
|
// The end points of the zoom range, measured in axis ticks.
|
|
var axis = this.axes[name];
|
|
if(axis){
|
|
var scale, offset, bounds = axis.getScaler().bounds;
|
|
var lower = Math.min(range[0],range[1]);
|
|
var upper = Math.max(range[0],range[1]);
|
|
lower = range[0] < bounds.lower ? bounds.lower : lower;
|
|
upper = range[1] > bounds.upper ? bounds.upper : upper;
|
|
scale = (bounds.upper - bounds.lower) / (upper - lower);
|
|
offset = lower - bounds.lower;
|
|
this.setAxisWindow(name, scale, offset);
|
|
this.render();
|
|
}
|
|
},
|
|
calculateGeometry: function(){
|
|
// summary:
|
|
// Calculate the geometry of the chart based on the defined axes of
|
|
// a chart.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(this.dirty){
|
|
return this.fullGeometry();
|
|
}
|
|
|
|
// calculate geometry
|
|
var dirty = arr.filter(this.stack, function(plot){
|
|
return plot.dirty ||
|
|
(plot.hAxis && this.axes[plot.hAxis].dirty) ||
|
|
(plot.vAxis && this.axes[plot.vAxis].dirty);
|
|
}, this);
|
|
calculateAxes(dirty, this.plotArea);
|
|
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
fullGeometry: function(){
|
|
// summary:
|
|
// Calculate the full geometry of the chart. This includes passing
|
|
// over all major elements of a chart (plots, axes, series, container)
|
|
// in order to ensure proper rendering.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
this._makeDirty();
|
|
|
|
// clear old values
|
|
arr.forEach(this.stack, clear);
|
|
|
|
// rebuild new connections, and add defaults
|
|
|
|
// set up a theme
|
|
if(!this.theme){
|
|
this.setTheme(new Theme(dojox.charting._def));
|
|
}
|
|
|
|
// assign series
|
|
arr.forEach(this.series, function(run){
|
|
if(!(run.plot in this.plots)){
|
|
if(!dc.plot2d || !dc.plot2d.Default){
|
|
throw Error("Can't find plot: Default - didn't you forget to dojo" + ".require() it?");
|
|
}
|
|
var plot = new dc.plot2d.Default(this, {});
|
|
plot.name = run.plot;
|
|
this.plots[run.plot] = this.stack.length;
|
|
this.stack.push(plot);
|
|
}
|
|
this.stack[this.plots[run.plot]].addSeries(run);
|
|
}, this);
|
|
// assign axes
|
|
arr.forEach(this.stack, function(plot){
|
|
if(plot.hAxis){
|
|
plot.setAxis(this.axes[plot.hAxis]);
|
|
}
|
|
if(plot.vAxis){
|
|
plot.setAxis(this.axes[plot.vAxis]);
|
|
}
|
|
}, this);
|
|
|
|
// calculate geometry
|
|
|
|
// 1st pass
|
|
var dim = this.dim = this.surface.getDimensions();
|
|
dim.width = g.normalizedLength(dim.width);
|
|
dim.height = g.normalizedLength(dim.height);
|
|
func.forIn(this.axes, clear);
|
|
calculateAxes(this.stack, dim);
|
|
|
|
// assumption: we don't have stacked axes yet
|
|
var offsets = this.offsets = { l: 0, r: 0, t: 0, b: 0 };
|
|
func.forIn(this.axes, function(axis){
|
|
func.forIn(axis.getOffsets(), function(o, i){ offsets[i] += o; });
|
|
});
|
|
// add title area
|
|
if(this.title){
|
|
this.titleGap = (this.titleGap==0) ? 0 : this.titleGap || this.theme.chart.titleGap || 20;
|
|
this.titlePos = this.titlePos || this.theme.chart.titlePos || "top";
|
|
this.titleFont = this.titleFont || this.theme.chart.titleFont;
|
|
this.titleFontColor = this.titleFontColor || this.theme.chart.titleFontColor || "black";
|
|
var tsize = g.normalizedLength(g.splitFontString(this.titleFont).size);
|
|
offsets[this.titlePos=="top" ? "t":"b"] += (tsize + this.titleGap);
|
|
}
|
|
// add margins
|
|
func.forIn(this.margins, function(o, i){ offsets[i] += o; });
|
|
|
|
// 2nd pass with realistic dimensions
|
|
this.plotArea = {
|
|
width: dim.width - offsets.l - offsets.r,
|
|
height: dim.height - offsets.t - offsets.b
|
|
};
|
|
func.forIn(this.axes, clear);
|
|
calculateAxes(this.stack, this.plotArea);
|
|
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
render: function(){
|
|
// summary:
|
|
// Render the chart according to the current information defined. This should
|
|
// be the last call made when defining/creating a chart, or if data within the
|
|
// chart has been changed.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(this.theme){
|
|
this.theme.clear();
|
|
}
|
|
|
|
if(this.dirty){
|
|
return this.fullRender();
|
|
}
|
|
|
|
this.calculateGeometry();
|
|
|
|
// go over the stack backwards
|
|
func.forEachRev(this.stack, function(plot){ plot.render(this.dim, this.offsets); }, this);
|
|
|
|
// go over axes
|
|
func.forIn(this.axes, function(axis){ axis.render(this.dim, this.offsets); }, this);
|
|
|
|
this._makeClean();
|
|
|
|
// BEGIN FOR HTML CANVAS
|
|
if(this.surface.render){ this.surface.render(); };
|
|
// END FOR HTML CANVAS
|
|
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
fullRender: function(){
|
|
// summary:
|
|
// Force a full rendering of the chart, including full resets on the chart itself.
|
|
// You should not call this method directly unless absolutely necessary.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
|
|
// calculate geometry
|
|
this.fullGeometry();
|
|
var offsets = this.offsets, dim = this.dim, rect;
|
|
|
|
// get required colors
|
|
//var requiredColors = func.foldl(this.stack, "z + plot.getRequiredColors()", 0);
|
|
//this.theme.defineColors({num: requiredColors, cache: false});
|
|
|
|
// clear old shapes
|
|
arr.forEach(this.series, purge);
|
|
func.forIn(this.axes, purge);
|
|
arr.forEach(this.stack, purge);
|
|
if(this.chartTitle && this.chartTitle.tagName){
|
|
// destroy title if it is a DOM node
|
|
domConstruct.destroy(this.chartTitle);
|
|
}
|
|
this.surface.clear();
|
|
this.chartTitle = null;
|
|
|
|
// generate shapes
|
|
|
|
// draw a plot background
|
|
var t = this.theme,
|
|
fill = t.plotarea && t.plotarea.fill,
|
|
stroke = t.plotarea && t.plotarea.stroke,
|
|
// size might be neg if offsets are bigger that chart size this happens quite often at
|
|
// initialization time if the chart widget is used in a BorderContainer
|
|
// this will fail on IE/VML
|
|
w = Math.max(0, dim.width - offsets.l - offsets.r),
|
|
h = Math.max(0, dim.height - offsets.t - offsets.b),
|
|
rect = {
|
|
x: offsets.l - 1, y: offsets.t - 1,
|
|
width: w + 2,
|
|
height: h + 2
|
|
};
|
|
if(fill){
|
|
fill = Element.prototype._shapeFill(Element.prototype._plotFill(fill, dim, offsets), rect);
|
|
this.surface.createRect(rect).setFill(fill);
|
|
}
|
|
if(stroke){
|
|
this.surface.createRect({
|
|
x: offsets.l, y: offsets.t,
|
|
width: w + 1,
|
|
height: h + 1
|
|
}).setStroke(stroke);
|
|
}
|
|
|
|
// go over the stack backwards
|
|
func.foldr(this.stack, function(z, plot){ return plot.render(dim, offsets), 0; }, 0);
|
|
|
|
// pseudo-clipping: matting
|
|
fill = this.fill !== undefined ? this.fill : (t.chart && t.chart.fill);
|
|
stroke = this.stroke !== undefined ? this.stroke : (t.chart && t.chart.stroke);
|
|
|
|
// TRT: support for "inherit" as a named value in a theme.
|
|
if(fill == "inherit"){
|
|
// find the background color of the nearest ancestor node, and use that explicitly.
|
|
var node = this.node, fill = new Color(html.style(node, "backgroundColor"));
|
|
while(fill.a==0 && node!=document.documentElement){
|
|
fill = new Color(html.style(node, "backgroundColor"));
|
|
node = node.parentNode;
|
|
}
|
|
}
|
|
|
|
if(fill){
|
|
fill = Element.prototype._plotFill(fill, dim, offsets);
|
|
if(offsets.l){ // left
|
|
rect = {
|
|
width: offsets.l,
|
|
height: dim.height + 1
|
|
};
|
|
this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect));
|
|
}
|
|
if(offsets.r){ // right
|
|
rect = {
|
|
x: dim.width - offsets.r,
|
|
width: offsets.r + 1,
|
|
height: dim.height + 2
|
|
};
|
|
this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect));
|
|
}
|
|
if(offsets.t){ // top
|
|
rect = {
|
|
width: dim.width + 1,
|
|
height: offsets.t
|
|
};
|
|
this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect));
|
|
}
|
|
if(offsets.b){ // bottom
|
|
rect = {
|
|
y: dim.height - offsets.b,
|
|
width: dim.width + 1,
|
|
height: offsets.b + 2
|
|
};
|
|
this.surface.createRect(rect).setFill(Element.prototype._shapeFill(fill, rect));
|
|
}
|
|
}
|
|
if(stroke){
|
|
this.surface.createRect({
|
|
width: dim.width - 1,
|
|
height: dim.height - 1
|
|
}).setStroke(stroke);
|
|
}
|
|
|
|
//create title: Whether to make chart title as a widget which extends dojox.charting.Element?
|
|
if(this.title){
|
|
var forceHtmlLabels = (g.renderer == "canvas"),
|
|
labelType = forceHtmlLabels || !has("ie") && !has("opera") ? "html" : "gfx",
|
|
tsize = g.normalizedLength(g.splitFontString(this.titleFont).size);
|
|
this.chartTitle = common.createText[labelType](
|
|
this,
|
|
this.surface,
|
|
dim.width/2,
|
|
this.titlePos=="top" ? tsize + this.margins.t : dim.height - this.margins.b,
|
|
"middle",
|
|
this.title,
|
|
this.titleFont,
|
|
this.titleFontColor
|
|
);
|
|
}
|
|
|
|
// go over axes
|
|
func.forIn(this.axes, function(axis){ axis.render(dim, offsets); });
|
|
|
|
this._makeClean();
|
|
|
|
// BEGIN FOR HTML CANVAS
|
|
if(this.surface.render){ this.surface.render(); };
|
|
// END FOR HTML CANVAS
|
|
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
delayedRender: function(){
|
|
// summary:
|
|
// Delayed render, which is used to collect multiple updates
|
|
// within a delayInMs time window.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
|
|
if(!this._delayedRenderHandle){
|
|
this._delayedRenderHandle = setTimeout(
|
|
lang.hitch(this, function(){
|
|
clearTimeout(this._delayedRenderHandle);
|
|
this._delayedRenderHandle = null;
|
|
this.render();
|
|
}),
|
|
this.delayInMs
|
|
);
|
|
}
|
|
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
connectToPlot: function(name, object, method){
|
|
// summary:
|
|
// A convenience method to connect a function to a plot.
|
|
// name: String
|
|
// The name of the plot as defined by addPlot.
|
|
// object: Object
|
|
// The object to be connected.
|
|
// method: Function
|
|
// The function to be executed.
|
|
// returns: Array
|
|
// A handle to the connection, as defined by dojo.connect (see dojo.connect).
|
|
return name in this.plots ? this.stack[this.plots[name]].connect(object, method) : null; // Array
|
|
},
|
|
fireEvent: function(seriesName, eventName, index){
|
|
// summary:
|
|
// Fires a synthetic event for a series item.
|
|
// seriesName: String:
|
|
// Series name.
|
|
// eventName: String:
|
|
// Event name to simulate: onmouseover, onmouseout, onclick.
|
|
// index: Number:
|
|
// Valid data value index for the event.
|
|
// returns: dojox.charting.Chart
|
|
// A reference to the current chart for functional chaining.
|
|
if(seriesName in this.runs){
|
|
var plotName = this.series[this.runs[seriesName]].plot;
|
|
if(plotName in this.plots){
|
|
var plot = this.stack[this.plots[plotName]];
|
|
if(plot){
|
|
plot.fireEvent(seriesName, eventName, index);
|
|
}
|
|
}
|
|
}
|
|
return this; // dojox.charting.Chart
|
|
},
|
|
_makeClean: function(){
|
|
// reset dirty flags
|
|
arr.forEach(this.axes, makeClean);
|
|
arr.forEach(this.stack, makeClean);
|
|
arr.forEach(this.series, makeClean);
|
|
this.dirty = false;
|
|
},
|
|
_makeDirty: function(){
|
|
// reset dirty flags
|
|
arr.forEach(this.axes, makeDirty);
|
|
arr.forEach(this.stack, makeDirty);
|
|
arr.forEach(this.series, makeDirty);
|
|
this.dirty = true;
|
|
},
|
|
_invalidateDependentPlots: function(plotName, /* Boolean */ verticalAxis){
|
|
if(plotName in this.plots){
|
|
var plot = this.stack[this.plots[plotName]], axis,
|
|
axisName = verticalAxis ? "vAxis" : "hAxis";
|
|
if(plot[axisName]){
|
|
axis = this.axes[plot[axisName]];
|
|
if(axis && axis.dependOnData()){
|
|
axis.dirty = true;
|
|
// find all plots and mark them dirty
|
|
arr.forEach(this.stack, function(p){
|
|
if(p[axisName] && p[axisName] == plot[axisName]){
|
|
p.dirty = true;
|
|
}
|
|
});
|
|
}
|
|
}else{
|
|
plot.dirty = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
function hSection(stats){
|
|
return {min: stats.hmin, max: stats.hmax};
|
|
}
|
|
|
|
function vSection(stats){
|
|
return {min: stats.vmin, max: stats.vmax};
|
|
}
|
|
|
|
function hReplace(stats, h){
|
|
stats.hmin = h.min;
|
|
stats.hmax = h.max;
|
|
}
|
|
|
|
function vReplace(stats, v){
|
|
stats.vmin = v.min;
|
|
stats.vmax = v.max;
|
|
}
|
|
|
|
function combineStats(target, source){
|
|
if(target && source){
|
|
target.min = Math.min(target.min, source.min);
|
|
target.max = Math.max(target.max, source.max);
|
|
}
|
|
return target || source;
|
|
}
|
|
|
|
function calculateAxes(stack, plotArea){
|
|
var plots = {}, axes = {};
|
|
arr.forEach(stack, function(plot){
|
|
var stats = plots[plot.name] = plot.getSeriesStats();
|
|
if(plot.hAxis){
|
|
axes[plot.hAxis] = combineStats(axes[plot.hAxis], hSection(stats));
|
|
}
|
|
if(plot.vAxis){
|
|
axes[plot.vAxis] = combineStats(axes[plot.vAxis], vSection(stats));
|
|
}
|
|
});
|
|
arr.forEach(stack, function(plot){
|
|
var stats = plots[plot.name];
|
|
if(plot.hAxis){
|
|
hReplace(stats, axes[plot.hAxis]);
|
|
}
|
|
if(plot.vAxis){
|
|
vReplace(stats, axes[plot.vAxis]);
|
|
}
|
|
plot.initializeScalers(plotArea, stats);
|
|
});
|
|
}
|
|
|
|
return dojox.charting.Chart;
|
|
});
|