378 lines
12 KiB
JavaScript
378 lines
12 KiB
JavaScript
//>>built
|
|
define("dojox/charting/plot2d/Default", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array",
|
|
"./Base", "./common", "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils", "dojox/gfx/fx"],
|
|
function(lang, declare, arr, Base, dc, df, dfr, du, fx){
|
|
|
|
/*=====
|
|
dojo.declare("dojox.charting.plot2d.__DefaultCtorArgs", dojox.charting.plot2d.__PlotCtorArgs, {
|
|
// summary:
|
|
// The arguments used for any/most plots.
|
|
|
|
// hAxis: String?
|
|
// The horizontal axis name.
|
|
hAxis: "x",
|
|
|
|
// vAxis: String?
|
|
// The vertical axis name
|
|
vAxis: "y",
|
|
|
|
// lines: Boolean?
|
|
// Whether or not to draw lines on this plot. Defaults to true.
|
|
lines: true,
|
|
|
|
// areas: Boolean?
|
|
// Whether or not to draw areas on this plot. Defaults to false.
|
|
areas: false,
|
|
|
|
// markers: Boolean?
|
|
// Whether or not to draw markers at data points on this plot. Default is false.
|
|
markers: false,
|
|
|
|
// tension: Number|String?
|
|
// Whether or not to apply 'tensioning' to the lines on this chart.
|
|
// Options include a number, "X", "x", or "S"; if a number is used, the
|
|
// simpler bezier curve calculations are used to draw the lines. If X, x or S
|
|
// is used, the more accurate smoothing algorithm is used.
|
|
tension: "",
|
|
|
|
// animate: Boolean?
|
|
// Whether or not to animate the chart to place.
|
|
animate: false,
|
|
|
|
// stroke: dojox.gfx.Stroke?
|
|
// An optional stroke to use for any series on the plot.
|
|
stroke: {},
|
|
|
|
// outline: dojox.gfx.Stroke?
|
|
// An optional stroke used to outline any series on the plot.
|
|
outline: {},
|
|
|
|
// shadow: dojox.gfx.Stroke?
|
|
// An optional stroke to use to draw any shadows for a series on a plot.
|
|
shadow: {},
|
|
|
|
// fill: dojox.gfx.Fill?
|
|
// Any fill to be used for elements on the plot (such as areas).
|
|
fill: {},
|
|
|
|
// font: String?
|
|
// A font definition to be used for labels and other text-based elements on the plot.
|
|
font: "",
|
|
|
|
// fontColor: String|dojo.Color?
|
|
// The color to be used for any text-based elements on the plot.
|
|
fontColor: "",
|
|
|
|
// markerStroke: dojo.gfx.Stroke?
|
|
// An optional stroke to use for any markers on the plot.
|
|
markerStroke: {},
|
|
|
|
// markerOutline: dojo.gfx.Stroke?
|
|
// An optional outline to use for any markers on the plot.
|
|
markerOutline: {},
|
|
|
|
// markerShadow: dojo.gfx.Stroke?
|
|
// An optional shadow to use for any markers on the plot.
|
|
markerShadow: {},
|
|
|
|
// markerFill: dojo.gfx.Fill?
|
|
// An optional fill to use for any markers on the plot.
|
|
markerFill: {},
|
|
|
|
// markerFont: String?
|
|
// An optional font definition to use for any markers on the plot.
|
|
markerFont: "",
|
|
|
|
// markerFontColor: String|dojo.Color?
|
|
// An optional color to use for any marker text on the plot.
|
|
markerFontColor: "",
|
|
|
|
// enableCache: Boolean?
|
|
// Whether the markers are cached from one rendering to another. This improves the rendering performance of
|
|
// successive rendering but penalize the first rendering. Default false.
|
|
enableCache: false
|
|
});
|
|
|
|
var Base = dojox.charting.plot2d.Base;
|
|
=====*/
|
|
|
|
var purgeGroup = dfr.lambda("item.purgeGroup()");
|
|
|
|
var DEFAULT_ANIMATION_LENGTH = 1200; // in ms
|
|
|
|
return declare("dojox.charting.plot2d.Default", Base, {
|
|
defaultParams: {
|
|
hAxis: "x", // use a horizontal axis named "x"
|
|
vAxis: "y", // use a vertical axis named "y"
|
|
lines: true, // draw lines
|
|
areas: false, // draw areas
|
|
markers: false, // draw markers
|
|
tension: "", // draw curved lines (tension is "X", "x", or "S")
|
|
animate: false, // animate chart to place
|
|
enableCache: false
|
|
},
|
|
optionalParams: {
|
|
// theme component
|
|
stroke: {},
|
|
outline: {},
|
|
shadow: {},
|
|
fill: {},
|
|
font: "",
|
|
fontColor: "",
|
|
markerStroke: {},
|
|
markerOutline: {},
|
|
markerShadow: {},
|
|
markerFill: {},
|
|
markerFont: "",
|
|
markerFontColor: ""
|
|
},
|
|
|
|
constructor: function(chart, kwArgs){
|
|
// summary:
|
|
// Return a new plot.
|
|
// chart: dojox.charting.Chart
|
|
// The chart this plot belongs to.
|
|
// kwArgs: dojox.charting.plot2d.__DefaultCtorArgs?
|
|
// An optional arguments object to help define this plot.
|
|
this.opt = lang.clone(this.defaultParams);
|
|
du.updateWithObject(this.opt, kwArgs);
|
|
du.updateWithPattern(this.opt, kwArgs, this.optionalParams);
|
|
this.series = [];
|
|
this.hAxis = this.opt.hAxis;
|
|
this.vAxis = this.opt.vAxis;
|
|
|
|
// animation properties
|
|
this.animate = this.opt.animate;
|
|
},
|
|
|
|
createPath: function(run, creator, params){
|
|
var path;
|
|
if(this.opt.enableCache && run._pathFreePool.length > 0){
|
|
path = run._pathFreePool.pop();
|
|
path.setShape(params);
|
|
// was cleared, add it back
|
|
creator.add(path);
|
|
}else{
|
|
path = creator.createPath(params);
|
|
}
|
|
if(this.opt.enableCache){
|
|
run._pathUsePool.push(path);
|
|
}
|
|
return path;
|
|
},
|
|
|
|
render: function(dim, offsets){
|
|
// summary:
|
|
// Render/draw everything on this plot.
|
|
// dim: Object
|
|
// An object of the form { width, height }
|
|
// offsets: Object
|
|
// An object of the form { l, r, t, b }
|
|
// returns: dojox.charting.plot2d.Default
|
|
// A reference to this plot for functional chaining.
|
|
|
|
// make sure all the series is not modified
|
|
if(this.zoom && !this.isDataDirty()){
|
|
return this.performZoom(dim, offsets);
|
|
}
|
|
|
|
this.resetEvents();
|
|
this.dirty = this.isDirty();
|
|
if(this.dirty){
|
|
arr.forEach(this.series, purgeGroup);
|
|
this._eventSeries = {};
|
|
this.cleanGroup();
|
|
this.group.setTransform(null);
|
|
var s = this.group;
|
|
df.forEachRev(this.series, function(item){ item.cleanGroup(s); });
|
|
}
|
|
var t = this.chart.theme, stroke, outline, marker, events = this.events();
|
|
|
|
for(var i = this.series.length - 1; i >= 0; --i){
|
|
var run = this.series[i];
|
|
if(!this.dirty && !run.dirty){
|
|
t.skip();
|
|
this._reconnectEvents(run.name);
|
|
continue;
|
|
}
|
|
run.cleanGroup();
|
|
if(this.opt.enableCache){
|
|
run._pathFreePool = (run._pathFreePool?run._pathFreePool:[]).concat(run._pathUsePool?run._pathUsePool:[]);
|
|
run._pathUsePool = [];
|
|
}
|
|
if(!run.data.length){
|
|
run.dirty = false;
|
|
t.skip();
|
|
continue;
|
|
}
|
|
|
|
var theme = t.next(this.opt.areas ? "area" : "line", [this.opt, run], true),
|
|
s = run.group, rsegments = [], startindexes = [], rseg = null, lpoly,
|
|
ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler),
|
|
vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler),
|
|
eventSeries = this._eventSeries[run.name] = new Array(run.data.length);
|
|
|
|
// optim works only for index based case
|
|
var indexed = typeof run.data[0] == "number";
|
|
var min = indexed?Math.max(0, Math.floor(this._hScaler.bounds.from - 1)):0,
|
|
max = indexed?Math.min(run.data.length, Math.ceil(this._hScaler.bounds.to)):run.data.length;
|
|
|
|
// split the run data into dense segments (each containing no nulls)
|
|
for(var j = min; j < max; j++){
|
|
if(run.data[j] != null){
|
|
if(!rseg){
|
|
rseg = [];
|
|
startindexes.push(j);
|
|
rsegments.push(rseg);
|
|
}
|
|
rseg.push(run.data[j]);
|
|
}else{
|
|
rseg = null;
|
|
}
|
|
}
|
|
|
|
for(var seg = 0; seg < rsegments.length; seg++){
|
|
if(typeof rsegments[seg][0] == "number"){
|
|
lpoly = arr.map(rsegments[seg], function(v, i){
|
|
return {
|
|
x: ht(i + startindexes[seg] + 1) + offsets.l,
|
|
y: dim.height - offsets.b - vt(v)
|
|
};
|
|
}, this);
|
|
}else{
|
|
lpoly = arr.map(rsegments[seg], function(v, i){
|
|
return {
|
|
x: ht(v.x) + offsets.l,
|
|
y: dim.height - offsets.b - vt(v.y)
|
|
};
|
|
}, this);
|
|
}
|
|
|
|
var lpath = this.opt.tension ? dc.curve(lpoly, this.opt.tension) : "";
|
|
|
|
if(this.opt.areas && lpoly.length > 1){
|
|
var fill = theme.series.fill;
|
|
var apoly = lang.clone(lpoly);
|
|
if(this.opt.tension){
|
|
var apath = "L" + apoly[apoly.length-1].x + "," + (dim.height - offsets.b) +
|
|
" L" + apoly[0].x + "," + (dim.height - offsets.b) +
|
|
" L" + apoly[0].x + "," + apoly[0].y;
|
|
run.dyn.fill = s.createPath(lpath + " " + apath).setFill(fill).getFill();
|
|
} else {
|
|
apoly.push({x: lpoly[lpoly.length - 1].x, y: dim.height - offsets.b});
|
|
apoly.push({x: lpoly[0].x, y: dim.height - offsets.b});
|
|
apoly.push(lpoly[0]);
|
|
run.dyn.fill = s.createPolyline(apoly).setFill(fill).getFill();
|
|
}
|
|
}
|
|
if(this.opt.lines || this.opt.markers){
|
|
// need a stroke
|
|
stroke = theme.series.stroke;
|
|
if(theme.series.outline){
|
|
outline = run.dyn.outline = dc.makeStroke(theme.series.outline);
|
|
outline.width = 2 * outline.width + stroke.width;
|
|
}
|
|
}
|
|
if(this.opt.markers){
|
|
run.dyn.marker = theme.symbol;
|
|
}
|
|
var frontMarkers = null, outlineMarkers = null, shadowMarkers = null;
|
|
if(stroke && theme.series.shadow && lpoly.length > 1){
|
|
var shadow = theme.series.shadow,
|
|
spoly = arr.map(lpoly, function(c){
|
|
return {x: c.x + shadow.dx, y: c.y + shadow.dy};
|
|
});
|
|
if(this.opt.lines){
|
|
if(this.opt.tension){
|
|
run.dyn.shadow = s.createPath(dc.curve(spoly, this.opt.tension)).setStroke(shadow).getStroke();
|
|
} else {
|
|
run.dyn.shadow = s.createPolyline(spoly).setStroke(shadow).getStroke();
|
|
}
|
|
}
|
|
if(this.opt.markers && theme.marker.shadow){
|
|
shadow = theme.marker.shadow;
|
|
shadowMarkers = arr.map(spoly, function(c){
|
|
return this.createPath(run, s, "M" + c.x + " " + c.y + " " + theme.symbol).
|
|
setStroke(shadow).setFill(shadow.color);
|
|
}, this);
|
|
}
|
|
}
|
|
if(this.opt.lines && lpoly.length > 1){
|
|
if(outline){
|
|
if(this.opt.tension){
|
|
run.dyn.outline = s.createPath(lpath).setStroke(outline).getStroke();
|
|
} else {
|
|
run.dyn.outline = s.createPolyline(lpoly).setStroke(outline).getStroke();
|
|
}
|
|
}
|
|
if(this.opt.tension){
|
|
run.dyn.stroke = s.createPath(lpath).setStroke(stroke).getStroke();
|
|
} else {
|
|
run.dyn.stroke = s.createPolyline(lpoly).setStroke(stroke).getStroke();
|
|
}
|
|
}
|
|
if(this.opt.markers){
|
|
frontMarkers = new Array(lpoly.length);
|
|
outlineMarkers = new Array(lpoly.length);
|
|
outline = null;
|
|
if(theme.marker.outline){
|
|
outline = dc.makeStroke(theme.marker.outline);
|
|
outline.width = 2 * outline.width + (theme.marker.stroke ? theme.marker.stroke.width : 0);
|
|
}
|
|
arr.forEach(lpoly, function(c, i){
|
|
var path = "M" + c.x + " " + c.y + " " + theme.symbol;
|
|
if(outline){
|
|
outlineMarkers[i] = this.createPath(run, s, path).setStroke(outline);
|
|
}
|
|
frontMarkers[i] = this.createPath(run, s, path).setStroke(theme.marker.stroke).setFill(theme.marker.fill);
|
|
}, this);
|
|
run.dyn.markerFill = theme.marker.fill;
|
|
run.dyn.markerStroke = theme.marker.stroke;
|
|
if(events){
|
|
arr.forEach(frontMarkers, function(s, i){
|
|
var o = {
|
|
element: "marker",
|
|
index: i + startindexes[seg],
|
|
run: run,
|
|
shape: s,
|
|
outline: outlineMarkers[i] || null,
|
|
shadow: shadowMarkers && shadowMarkers[i] || null,
|
|
cx: lpoly[i].x,
|
|
cy: lpoly[i].y
|
|
};
|
|
if(typeof rsegments[seg][0] == "number"){
|
|
o.x = i + startindexes[seg] + 1;
|
|
o.y = rsegments[seg][i];
|
|
}else{
|
|
o.x = rsegments[seg][i].x;
|
|
o.y = rsegments[seg][i].y;
|
|
}
|
|
this._connectEvents(o);
|
|
eventSeries[i + startindexes[seg]] = o;
|
|
}, this);
|
|
}else{
|
|
delete this._eventSeries[run.name];
|
|
}
|
|
}
|
|
}
|
|
run.dirty = false;
|
|
}
|
|
if(this.animate){
|
|
// grow from the bottom
|
|
var plotGroup = this.group;
|
|
fx.animateTransform(lang.delegate({
|
|
shape: plotGroup,
|
|
duration: DEFAULT_ANIMATION_LENGTH,
|
|
transform:[
|
|
{name:"translate", start: [0, dim.height - offsets.b], end: [0, 0]},
|
|
{name:"scale", start: [1, 0], end:[1, 1]},
|
|
{name:"original"}
|
|
]
|
|
}, this.animate)).play();
|
|
}
|
|
this.dirty = false;
|
|
return this; // dojox.charting.plot2d.Default
|
|
}
|
|
});
|
|
});
|