You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
481 lines
16 KiB
481 lines
16 KiB
define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", "dojo/has", |
|
"./CartesianBase", "./_PlotEvents", "./common", "dojox/lang/functional", "dojox/lang/utils", "dojox/gfx/fx"], |
|
function(lang, declare, arr, has, CartesianBase, _PlotEvents, dc, df, du, fx){ |
|
|
|
/*===== |
|
declare("dojox.charting.plot2d.__DefaultCtorArgs", dojox.charting.plot2d.__CartesianCtorArgs, { |
|
// summary: |
|
// The arguments used for any/most plots. |
|
|
|
// 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?|Number? |
|
// Whether or not to animate the chart to place. When a Number it specifies the duration of the animation. |
|
// Default is false. |
|
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: {}, |
|
|
|
// filter: dojox.gfx.Filter? |
|
// An SVG filter to be used for elements on the plot. gfx SVG renderer must be used and dojox/gfx/svgext must |
|
// be required for this to work. |
|
filter: {}, |
|
|
|
// styleFunc: Function? |
|
// A function that returns a styling object for the a given data item. |
|
styleFunc: null, |
|
|
|
// 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, |
|
|
|
// interpolate: Boolean? |
|
// Whether when there is a null data point in the data the plot interpolates it or if the lines is split at that |
|
// point. Default false. |
|
interpolate: false, |
|
|
|
// zeroLine: Number? |
|
// Zero line for an area fill. Should be a numeric value in user coordinates. |
|
// Default: the lowest value on a vertical axis. |
|
zeroLine: 0 |
|
}); |
|
=====*/ |
|
|
|
var DEFAULT_ANIMATION_LENGTH = 1200; // in ms |
|
|
|
return declare("dojox.charting.plot2d.Default", [CartesianBase, _PlotEvents], { |
|
|
|
// defaultParams: |
|
// The default parameters of this plot. |
|
defaultParams: { |
|
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, |
|
interpolate: false |
|
}, |
|
|
|
// optionalParams: |
|
// The optional parameters of this plot. |
|
optionalParams: { |
|
// theme component |
|
stroke: {}, |
|
outline: {}, |
|
shadow: {}, |
|
fill: {}, |
|
filter: {}, |
|
styleFunc: null, |
|
font: "", |
|
fontColor: "", |
|
marker: "", |
|
markerStroke: {}, |
|
markerOutline: {}, |
|
markerShadow: {}, |
|
markerFill: {}, |
|
markerFont: "", |
|
markerFontColor: "", |
|
zeroLine: 0 |
|
}, |
|
|
|
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(lang.mixin(this.opt, this.defaultParams)); |
|
du.updateWithObject(this.opt, kwArgs); |
|
du.updateWithPattern(this.opt, kwArgs, this.optionalParams); |
|
// 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; |
|
}, |
|
|
|
buildSegments: function(i, indexed){ |
|
var run = this.series[i], |
|
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, |
|
rseg = null, segments = []; |
|
|
|
// split the run data into dense segments (each containing no nulls) |
|
// except if interpolates is false in which case ignore null between valid data |
|
for(var j = min; j < max; j++){ |
|
if(!this.isNullValue(run.data[j])){ |
|
if(!rseg){ |
|
rseg = []; |
|
segments.push({index: j, rseg: rseg}); |
|
} |
|
rseg.push((indexed && run.data[j].hasOwnProperty("y"))?run.data[j].y:run.data[j]); |
|
}else{ |
|
if(!this.opt.interpolate || indexed){ |
|
// we break the line only if not interpolating or if we have indexed data |
|
rseg = null; |
|
} |
|
} |
|
} |
|
return segments; |
|
}, |
|
|
|
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(); |
|
var s; |
|
if(this.dirty){ |
|
arr.forEach(this.series, dc.purgeGroup); |
|
this._eventSeries = {}; |
|
this.cleanGroup(); |
|
this.getGroup().setTransform(null); |
|
s = this.getGroup(); |
|
df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); |
|
} |
|
var t = this.chart.theme, stroke, outline, events = this.events(); |
|
|
|
for(var i = 0; i < this.series.length; 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), |
|
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); |
|
|
|
s = run.group; |
|
if(run.hidden){ |
|
if(this.opt.lines){ |
|
run.dyn.stroke = theme.series.stroke; |
|
} |
|
if (run.markers || (run.markers === undefined && this.opt.markers)) { |
|
run.dyn.markerFill = theme.marker.fill; |
|
run.dyn.markerStroke = theme.marker.stroke; |
|
run.dyn.marker = theme.symbol; |
|
} |
|
if(this.opt.areas){ |
|
run.dyn.fill = theme.series.fill; |
|
} |
|
continue; |
|
} |
|
|
|
// optim works only for index based case |
|
var indexed = arr.some(run.data, function(item){ |
|
return typeof item == "number" || (item && !item.hasOwnProperty("x")); |
|
}); |
|
|
|
var rsegments = this.buildSegments(i, indexed); |
|
for(var seg = 0; seg < rsegments.length; seg++){ |
|
var rsegment = rsegments[seg]; |
|
if(indexed){ |
|
lpoly = arr.map(rsegment.rseg, function(v, i){ |
|
return { |
|
x: ht(i + rsegment.index + 1) + offsets.l, |
|
y: dim.height - offsets.b - vt(v), |
|
data: v |
|
}; |
|
}, this); |
|
}else{ |
|
lpoly = arr.map(rsegment.rseg, function(v){ |
|
return { |
|
x: ht(v.x) + offsets.l, |
|
y: dim.height - offsets.b - vt(v.y), |
|
data: v |
|
}; |
|
}, this); |
|
} |
|
|
|
// if we are indexed & we interpolate we need to put all the segments as a single one now |
|
if(indexed && this.opt.interpolate){ |
|
while(seg < rsegments.length) { |
|
seg++; |
|
rsegment = rsegments[seg]; |
|
if(rsegment){ |
|
lpoly = lpoly.concat(arr.map(rsegment.rseg, function(v, i){ |
|
return { |
|
x: ht(i + rsegment.index + 1) + offsets.l, |
|
y: dim.height - offsets.b - vt(v), |
|
data: v |
|
}; |
|
}, this)); |
|
} |
|
} |
|
} |
|
|
|
var lpath = this.opt.tension ? dc.curve(lpoly, this.opt.tension) : ""; |
|
|
|
if(this.opt.areas && lpoly.length > 1){ |
|
var fill = this._plotFill(theme.series.fill, dim, offsets), apoly = lang.clone(lpoly), |
|
zeroLine = dim.height - offsets.b; |
|
if(!isNaN(this.opt.zeroLine)){ |
|
zeroLine = Math.max(offsets.t, Math.min(dim.height - offsets.b - vt(this.opt.zeroLine), zeroLine)); |
|
} |
|
if(this.opt.tension){ |
|
var apath = "L" + apoly[apoly.length-1].x + "," + zeroLine + |
|
" L" + apoly[0].x + "," + zeroLine + |
|
" 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: zeroLine}); |
|
apoly.push({x: lpoly[0].x, y: zeroLine}); |
|
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 && stroke.width || 0); |
|
} |
|
} |
|
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){ |
|
var shape; |
|
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 = (shape = s.createPath(lpath)).setStroke(stroke).getStroke(); |
|
} else { |
|
run.dyn.stroke = (shape = s.createPolyline(lpoly)).setStroke(stroke).getStroke(); |
|
} |
|
if(shape.setFilter && theme.series.filter){ |
|
shape.setFilter(theme.series.filter); |
|
} |
|
} |
|
var markerBox = null; |
|
if(this.opt.markers){ |
|
var markerTheme = theme; |
|
frontMarkers = new Array(lpoly.length); |
|
outlineMarkers = new Array(lpoly.length); |
|
outline = null; |
|
if(markerTheme.marker.outline){ |
|
outline = dc.makeStroke(markerTheme.marker.outline); |
|
outline.width = 2 * outline.width + (markerTheme.marker.stroke ? markerTheme.marker.stroke.width : 0); |
|
} |
|
arr.forEach(lpoly, function(c, i){ |
|
if(this.opt.styleFunc || typeof c.data != "number"){ |
|
var tMixin = typeof c.data != "number" ? [c.data] : []; |
|
if(this.opt.styleFunc){ |
|
tMixin.push(this.opt.styleFunc(c.data)); |
|
} |
|
markerTheme = t.addMixin(theme, "marker", tMixin, true); |
|
}else{ |
|
markerTheme = t.post(theme, "marker"); |
|
} |
|
var path = "M" + c.x + " " + c.y + " " + markerTheme.symbol; |
|
if(outline){ |
|
outlineMarkers[i] = this.createPath(run, s, path).setStroke(outline); |
|
} |
|
frontMarkers[i] = this.createPath(run, s, path).setStroke(markerTheme.marker.stroke).setFill(markerTheme.marker.fill); |
|
}, this); |
|
run.dyn.markerFill = markerTheme.marker.fill; |
|
run.dyn.markerStroke = markerTheme.marker.stroke; |
|
if(!markerBox && this.opt.labels){ |
|
markerBox = frontMarkers[0].getBoundingBox(); |
|
} |
|
if(events){ |
|
arr.forEach(frontMarkers, function(s, i){ |
|
var o = { |
|
element: "marker", |
|
index: i + rsegment.index, |
|
run: run, |
|
shape: s, |
|
outline: outlineMarkers[i] || null, |
|
shadow: shadowMarkers && shadowMarkers[i] || null, |
|
cx: lpoly[i].x, |
|
cy: lpoly[i].y |
|
}; |
|
if(indexed){ |
|
o.x = i + rsegment.index + 1; |
|
o.y = run.data[i + rsegment.index]; |
|
}else{ |
|
o.x = rsegment.rseg[i].x; |
|
o.y = run.data[i + rsegment.index].y; |
|
} |
|
this._connectEvents(o); |
|
eventSeries[i + rsegment.index] = o; |
|
}, this); |
|
}else{ |
|
delete this._eventSeries[run.name]; |
|
} |
|
} |
|
if(this.opt.labels){ |
|
var labelBoxW = markerBox?markerBox.width:2; |
|
var labelBoxH = markerBox?markerBox.height:2; |
|
arr.forEach(lpoly, function(c, i){ |
|
if(this.opt.styleFunc || typeof c.data != "number"){ |
|
var tMixin = typeof c.data != "number" ? [c.data] : []; |
|
if(this.opt.styleFunc){ |
|
tMixin.push(this.opt.styleFunc(c.data)); |
|
} |
|
markerTheme = t.addMixin(theme, "marker", tMixin, true); |
|
}else{ |
|
markerTheme = t.post(theme, "marker"); |
|
} |
|
this.createLabel(s, rsegment.rseg[i], { x: c.x - labelBoxW / 2, y: c.y - labelBoxH / 2, |
|
width: labelBoxW , height: labelBoxH }, markerTheme); |
|
}, this); |
|
} |
|
} |
|
run.dirty = false; |
|
} |
|
// chart mirroring starts |
|
if(has("dojo-bidi")){ |
|
this._checkOrientation(this.group, dim, offsets); |
|
} |
|
// chart mirroring ends |
|
if(this.animate){ |
|
// grow from the bottom |
|
var plotGroup = this.getGroup(); |
|
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 |
|
} |
|
}); |
|
});
|
|
|