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.
500 lines
16 KiB
500 lines
16 KiB
define(["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./CartesianBase", "./_PlotEvents", "./common", |
|
"../axis2d/common", "dojox/gfx", "dojox/lang/utils", "dojox/gfx/fx", "dojo/has"], |
|
function(lang, array, declare, CartesianBase, _PlotEvents, dcpc, dcac, gfx, du, fx, has){ |
|
|
|
// all the code below should be removed when http://trac.dojotoolkit.org/ticket/11299 will be available |
|
var getBoundingBox = function(shape){ |
|
return getTextBBox(shape, shape.getShape().text); |
|
}; |
|
var getTextBBox = function(s, t){ |
|
var c = s.declaredClass; |
|
var w, h; |
|
if(c.indexOf("svg")!=-1){ |
|
// try/catch the FF native getBBox error. cheaper than walking up in the DOM |
|
// hierarchy to check the conditions (bench show /10 ) |
|
try { |
|
return lang.mixin({}, s.rawNode.getBBox()); |
|
}catch (e){ |
|
return null; |
|
} |
|
}else if(c.indexOf("vml")!=-1){ |
|
var rawNode = s.rawNode, _display = rawNode.style.display; |
|
rawNode.style.display = "inline"; |
|
w = gfx.pt2px(parseFloat(rawNode.currentStyle.width)); |
|
h = gfx.pt2px(parseFloat(rawNode.currentStyle.height)); |
|
var sz = {x: 0, y: 0, width: w, height: h}; |
|
// in VML, the width/height we get are in view coordinates |
|
// in our case we don't zoom the view so that is ok |
|
// It's impossible to get the x/y from the currentStyle.left/top, |
|
// because all negative coordinates are 'clipped' to 0. |
|
// (x:0 + translate(-100) -> x=0 |
|
computeLocation(s, sz); |
|
rawNode.style.display = _display; |
|
return sz; |
|
}else if(c.indexOf("silverlight")!=-1){ |
|
var bb = {width: s.rawNode.actualWidth, height: s.rawNode.actualHeight}; |
|
return computeLocation(s, bb, 0.75); |
|
}else if(s.getTextWidth){ |
|
// canvas |
|
w = s.getTextWidth(); |
|
var font = s.getFont(); |
|
var fz = font ? font.size : gfx.defaultFont.size; |
|
h = gfx.normalizedLength(fz); |
|
sz = {width: w, height: h}; |
|
computeLocation(s, sz, 0.75); |
|
return sz; |
|
} |
|
return null; |
|
}; |
|
var computeLocation = function(s, sz, coef){ |
|
var width = sz.width, height = sz.height, sh = s.getShape(), align = sh.align; |
|
switch (align) { |
|
case "end": |
|
sz.x = sh.x - width; |
|
break; |
|
case "middle": |
|
sz.x = sh.x - width / 2; |
|
break; |
|
case "start": |
|
default: |
|
sz.x = sh.x; |
|
break; |
|
} |
|
coef = coef || 1; |
|
sz.y = sh.y - height*coef; // rough approximation of the ascent!... |
|
return sz; |
|
}; |
|
|
|
/*===== |
|
declare("dojox.charting.plot2d.__IndicatorCtorArgs", dojox.charting.plot2d.__CartesianCtorArgs, { |
|
// summary: |
|
// A special keyword arguments object that is specific to a indicator "plot". |
|
|
|
// lines: Boolean? |
|
// Whether the indicator lines are visible or not. The lines are displayed for each of the |
|
// 'values' of the indicator. Default is true. |
|
lines: true, |
|
|
|
// labels: String? |
|
// Describes how the indicator labels are displayed. |
|
// Possible values are: |
|
// `"line"` for displaying the value of each indicator line, |
|
// `"marker"` for displaying the values of the markers of each indicator line, |
|
// `"trend"` for displaying the percentage variation from the first to the last indicator line, |
|
// `"none"` to prevent any label to display. |
|
// Default is "line". |
|
labels: "line", |
|
|
|
// markers: Boolean? |
|
// Whether the markers on the indicator lines are visible or not. The markers are displayed for each |
|
// of the indicator lines using the series attached to the indicator plot. Default is true. |
|
markers: true, |
|
|
|
// values: Boolean |
|
// The data values at which each of the indicator line is drawn. For a single value a Number can be provided |
|
// otherwise an Array of Number is required. fault is []. |
|
values: [], |
|
|
|
// offset: {x, y}? |
|
// A pair of (x, y) pixel coordinate to specifiy the offset between the end of the indicator line and the |
|
// position at which the labels are rendered. Default is no offset. |
|
offset: {}, |
|
|
|
// start: Boolean? |
|
// Whether the label is rendered at the start or end of the indicator line. Default is false meaning end of |
|
// the line. |
|
start: false, |
|
|
|
// 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, |
|
|
|
// vertical: Boolean? |
|
// Whether the indicator is vertical or not. Default is true. |
|
vertical: true, |
|
|
|
// fixed: Boolean? |
|
// Whether a fixed precision must be applied to data values for display. Default is true. |
|
fixed: true, |
|
|
|
// precision: Number? |
|
// The precision at which to round data values for display. Default is 0. |
|
precision: 0, |
|
|
|
// lineStroke: dojo/gfx/Stroke? |
|
// An optional stroke to use for indicator line. |
|
lineStroke: {}, |
|
|
|
// lineOutline: dojo/gfx/Stroke? |
|
// An optional outline to use for indicator line. |
|
lineOutline: {}, |
|
|
|
// lineShadow: dojo/gfx/Stroke? |
|
// An optional shadow to use for indicator line. |
|
lineShadow: {}, |
|
|
|
// stroke: dojo.gfx.Stroke? |
|
// An optional stroke to use for indicator label background. |
|
stroke: {}, |
|
|
|
// outline: dojo.gfx.Stroke? |
|
// An optional outline to use for indicator label background. |
|
outline: {}, |
|
|
|
// shadow: dojo.gfx.Stroke? |
|
// An optional shadow to use for indicator label background. |
|
shadow: {}, |
|
|
|
// fill: dojo.gfx.Fill? |
|
// An optional fill to use for indicator label background. |
|
fill: {}, |
|
|
|
// fillFunc: Function? |
|
// An optional function to use to compute label background fill. It takes precedence over |
|
// fill property when available. |
|
// | function fillFunc(index, values, data) {} |
|
// `index` is the index in the values array of the label being drawn. |
|
// `values` is the entire array of values. |
|
// `data` is the entire array of marker values. |
|
fillFunc: null, |
|
|
|
// labelFunc: Function? |
|
// An optional function to use to compute label text. It takes precedence over |
|
// the default text when available. |
|
// | function labelFunc(index, values, data, fixed, precision, labels) {} |
|
// `index` is the index in the values array of the label being drawn. |
|
// `values` is the entire array of values. |
|
// `data` is the entire array of marker values. |
|
// `fixed` is true if fixed precision must be applied. |
|
// `precision` is the requested precision to be applied. |
|
// `labels` is the labels mode of the indicator. |
|
labelFunc: null, |
|
|
|
// font: String? |
|
// A font definition to use for indicator label background. |
|
font: "", |
|
|
|
// fontColor: String|dojo.Color? |
|
// The color to use for indicator label background. |
|
fontColor: "", |
|
|
|
// markerStroke: dojo.gfx.Stroke? |
|
// An optional stroke to use for indicator marker. |
|
markerStroke: {}, |
|
|
|
// markerOutline: dojo.gfx.Stroke? |
|
// An optional outline to use for indicator marker. |
|
markerOutline: {}, |
|
|
|
// markerShadow: dojo.gfx.Stroke? |
|
// An optional shadow to use for indicator marker. |
|
markerShadow: {}, |
|
|
|
// markerFill: dojo.gfx.Fill? |
|
// An optional fill to use for indicator marker. |
|
markerFill: {}, |
|
|
|
// markerSymbol: String? |
|
// An optional symbol string to use for indicator marker. |
|
markerSymbol: "", |
|
}); |
|
=====*/ |
|
|
|
var Indicator = declare("dojox.charting.plot2d.Indicator", [CartesianBase, _PlotEvents], { |
|
// summary: |
|
// A "faux" plot that can be placed behind or above other plots to represent a line or multi-line |
|
// threshold on the chart. |
|
defaultParams: { |
|
vertical: true, |
|
fixed: true, |
|
precision: 0, |
|
lines: true, |
|
labels: "line", // "line" | "trend" | "markers" | "none" |
|
markers: true |
|
}, |
|
optionalParams: { |
|
lineStroke: {}, |
|
outlineStroke: {}, |
|
shadowStroke: {}, |
|
lineFill: {}, |
|
stroke: {}, |
|
outline: {}, |
|
shadow: {}, |
|
fill: {}, |
|
fillFunc: null, |
|
labelFunc: null, |
|
font: "", |
|
fontColor: "", |
|
markerStroke: {}, |
|
markerOutline: {}, |
|
markerShadow: {}, |
|
markerFill: {}, |
|
markerSymbol: "", |
|
values: [], |
|
offset: {}, |
|
start: false, |
|
animate: false |
|
}, |
|
|
|
constructor: function(chart, kwArgs){ |
|
// summary: |
|
// Create the faux Grid plot. |
|
// chart: dojox/charting/Chart |
|
// The chart this plot belongs to. |
|
// kwArgs: dojox.charting.plot2d.__GridCtorArgs? |
|
// An optional keyword arguments object to help define the parameters of the underlying grid. |
|
this.opt = lang.clone(this.defaultParams); |
|
du.updateWithObject(this.opt, kwArgs); |
|
if(typeof kwArgs.values == "number"){ |
|
kwArgs.values = [ kwArgs.values ]; |
|
} |
|
du.updateWithPattern(this.opt, kwArgs, this.optionalParams); |
|
this.animate = this.opt.animate; |
|
}, |
|
render: function(dim, offsets){ |
|
if(this.zoom){ |
|
return this.performZoom(dim, offsets); |
|
} |
|
|
|
if(!this.isDirty()){ |
|
return this; |
|
} |
|
|
|
this.cleanGroup(null, true); |
|
|
|
if(!this.opt.values){ |
|
return this; |
|
} |
|
|
|
this._updateIndicator(); |
|
return this; |
|
}, |
|
_updateIndicator: function(){ |
|
var t = this.chart.theme; |
|
var hn = this._hAxis.name, vn = this._vAxis.name, |
|
hb = this._hAxis.getScaler().bounds, vb = this._vAxis.getScaler().bounds; |
|
var o = {}; |
|
o[hn] = hb.from; |
|
o[vn] = vb.from; |
|
var min = this.toPage(o); |
|
o[hn] = hb.to; |
|
o[vn] = vb.to; |
|
var max = this.toPage(o); |
|
var events = this.events(); |
|
var results = array.map(this.opt.values, function(value, index){ |
|
return this._renderIndicator(value, index, hn, vn, min, max, events, this.animate); |
|
}, this); |
|
var length = results.length; |
|
if(this.opt.labels == "trend"){ |
|
var v = this.opt.vertical; |
|
var first = this._data[0][0]; |
|
var last = this._data[length - 1][0]; |
|
var delta = last-first; |
|
var text = this.opt.labelFunc?this.opt.labelFunc(-1, this.values, this._data, this.opt.fixed, this.opt.precision): |
|
(dcpc.getLabel(delta, this.opt.fixed, this.opt.precision)+" ("+dcpc.getLabel(100*delta/first, true, 2)+"%)"); |
|
this._renderText(this.getGroup(), text, this.chart.theme, v?(results[0].x+results[length - 1].x)/2:results[1].x, |
|
v?results[0].y:(results[1].y+results[length - 1].y)/2, -1, this.opt.values, this._data); |
|
} |
|
var lineFill = this.opt.lineFill!=undefined?this.opt.lineFill:t.indicator.lineFill; |
|
if(lineFill && length > 1){ |
|
var x0 = Math.min(results[0].x1, results[length - 1].x1); |
|
var y0 = Math.min(results[0].y1, results[length - 1].y1); |
|
var r = this.getGroup().createRect({x: x0, y: y0, width: Math.max(results[0].x2, results[length - 1].x2) - x0, |
|
height: Math.max(results[0].y2, results[length - 1].y2) - y0}). |
|
setFill(lineFill); |
|
r.moveToBack(); |
|
} |
|
}, |
|
_renderIndicator: function(coord, index, hn, vn, min, max, events, animate){ |
|
var t = this.chart.theme, c = this.chart.getCoords(), v = this.opt.vertical; |
|
|
|
var g = this.getGroup().createGroup(); |
|
var mark = {}; |
|
mark[hn] = v?coord:0; |
|
mark[vn] = v?0:coord; |
|
if(has("dojo-bidi")){ |
|
mark.x = this._getMarkX(mark.x); |
|
} |
|
mark = this.toPage(mark); |
|
var visible = v?mark.x >= min.x && mark.x <= max.x:mark.y >= max.y && mark.y <= min.y; |
|
|
|
var cx = mark.x - c.x, cy = mark.y - c.y; |
|
var x1 = v?cx:min.x - c.x, y1 = v?min.y - c.y:cy, x2 = v?x1:max.x - c.x, y2 = v?max.y - c.y:y1; |
|
|
|
if(this.opt.lines && visible){ |
|
var sh = this.opt.hasOwnProperty("lineShadow")?this.opt.lineShadow:t.indicator.lineShadow, |
|
ls = this.opt.hasOwnProperty("lineStroke")?this.opt.lineStroke:t.indicator.lineStroke, |
|
ol = this.opt.hasOwnProperty("lineOutline")?this.opt.lineOutline:t.indicator.lineOutline; |
|
if(sh){ |
|
g.createLine({x1: x1 + sh.dx, y1: y1 + sh.dy, x2: x2 + sh.dx, y2: y2 + sh.dy}).setStroke(sh); |
|
} |
|
if(ol){ |
|
ol = dcpc.makeStroke(ol); |
|
ol.width = 2 * ol.width + (ls?ls.width:0); |
|
g.createLine({x1: x1, y1: y1, x2: x2, y2: y2}).setStroke(ol); |
|
} |
|
g.createLine({x1: x1, y1: y1, x2: x2, y2: y2}).setStroke(ls); |
|
} |
|
|
|
// series items represent markers on the indicator |
|
var data; |
|
if(this.opt.markers && visible){ |
|
var d = this._data[index]; |
|
var self = this; |
|
if(d){ |
|
data = array.map(d, function(value, index){ |
|
mark[hn] = v?coord:value; |
|
mark[vn] = v?value:coord; |
|
if(has("dojo-bidi")){ |
|
mark.x = self._getMarkX(mark.x); |
|
} |
|
mark = this.toPage(mark); |
|
if(v?mark.y <= min.y && mark.y >= max.y:mark.x >= min.x && mark.x <= max.x){ |
|
cx = mark.x - c.x |
|
cy = mark.y - c.y; |
|
var ms = this.opt.markerSymbol?this.opt.markerSymbol:t.indicator.markerSymbol, |
|
path = "M" + cx + " " + cy + " " + ms; |
|
sh = this.opt.markerShadow!=undefined?this.opt.markerShadow:t.indicator.markerShadow; |
|
ls = this.opt.markerStroke!=undefined?this.opt.markerStroke:t.indicator.markerStroke; |
|
ol = this.opt.markerOutline!=undefined?this.opt.markerOutline:t.indicator.markerOutline; |
|
if(sh){ |
|
var sp = "M" + (cx + sh.dx) + " " + (cy + sh.dy) + " " + ms; |
|
g.createPath(sp).setFill(sh.color).setStroke(sh); |
|
} |
|
if(ol){ |
|
ol = dcpc.makeStroke(ol); |
|
ol.width = 2 * ol.width + (ls?ls.width:0); |
|
g.createPath(path).setStroke(ol); |
|
} |
|
|
|
var shape = g.createPath(path); |
|
var sf = this._shapeFill(this.opt.markerFill != undefined?this.opt.markerFill:t.indicator.markerFill, shape.getBoundingBox()); |
|
shape.setFill(sf).setStroke(ls); |
|
} |
|
return value; |
|
}, this); |
|
} |
|
} |
|
var ctext; |
|
if(this.opt.start){ |
|
ctext = { |
|
x: v?x1:x1, |
|
y: v?y1:y2 |
|
}; |
|
}else{ |
|
ctext = { |
|
x: v?x1:x2, |
|
y: v?y2:y1 |
|
}; |
|
} |
|
|
|
if(this.opt.labels && this.opt.labels != "trend" && visible){ |
|
var text; |
|
if(this.opt.labelFunc){ |
|
text = this.opt.labelFunc(index, this.opt.values, this._data, |
|
this.opt.fixed, this.opt.precision, this.opt.labels); |
|
}else{ |
|
if(this.opt.labels == "markers"){ |
|
text = array.map(data, function(value){ |
|
return dcpc.getLabel(value, this.opt.fixed, this.opt.precision); |
|
}, this); |
|
text = text.length != 1 ? "[ "+text.join(", ")+" ]" : text[0]; |
|
}else{ |
|
text = dcpc.getLabel(coord, this.opt.fixed, this.opt.precision); |
|
} |
|
} |
|
this._renderText(g, text, t, ctext.x, ctext.y, index, this.opt.values, this._data); |
|
} |
|
|
|
if(events){ |
|
this._connectEvents({ |
|
element: "indicator", |
|
run: this.run?this.run[index]:undefined, |
|
shape: g, |
|
value: coord |
|
}); |
|
} |
|
|
|
if(animate){ |
|
this._animateIndicator(g, v, v?y1:x1, v?(y1 + y2):(x1 + x2), animate); |
|
} |
|
|
|
return lang.mixin(ctext, {x1: x1, y1: y1, x2: x2, y2: y2}); |
|
}, |
|
_animateIndicator: function(shape, vertical, offset, size, animate){ |
|
var transStart = vertical ? [0, offset] : [offset, 0]; |
|
var scaleStart = vertical ? [1, 1 / size] : [1 / size, 1]; |
|
fx.animateTransform(lang.delegate({ |
|
shape: shape, |
|
duration: 1200, |
|
transform: [ |
|
{name: "translate", start: transStart, end: [0, 0]}, |
|
{name: "scale", start: scaleStart, end: [1, 1]}, |
|
{name: "original"} |
|
] |
|
}, animate)).play(); |
|
}, |
|
clear: function(){ |
|
this.inherited(arguments); |
|
this._data = []; |
|
}, |
|
addSeries: function(run){ |
|
this.inherited(arguments); |
|
this._data.push(run.data); |
|
}, |
|
_renderText: function(g, text, t, x, y, index, values, data){ |
|
if(this.opt.offset){ |
|
x += this.opt.offset.x; |
|
y += this.opt.offset.y; |
|
} |
|
var label = dcac.createText.gfx( |
|
this.chart, |
|
g, |
|
x, y, |
|
this.opt.vertical? "middle" : (this.opt.start ? "start":"end"), |
|
text, |
|
this.opt.font?this.opt.font:t.indicator.font, |
|
this.opt.fontColor?this.opt.fontColor:t.indicator.fontColor); |
|
var b = getBoundingBox(label); |
|
if(this.opt.vertical && !this.opt.start) { |
|
b.y += b.height/2; |
|
label.setShape({y: y+b.height/2}); |
|
} |
|
b.x-=2; b.y-=1; b.width+=4; b.height+=2; |
|
b.r = this.opt.radius?this.opt.radius:t.indicator.radius; |
|
var sh = this.opt.shadow!=undefined?this.opt.shadow:t.indicator.shadow, |
|
ls = this.opt.stroke!=undefined?this.opt.stroke:t.indicator.stroke, |
|
ol = this.opt.outline!=undefined?this.opt.outline:t.indicator.outline; |
|
if(sh){ |
|
g.createRect(b).setFill(sh.color).setStroke(sh); |
|
} |
|
if(ol){ |
|
ol = dcpc.makeStroke(ol); |
|
ol.width = 2 * ol.width + (ls?ls.width:0); |
|
g.createRect(b).setStroke(ol); |
|
} |
|
var f = this.opt.fillFunc?this.opt.fillFunc(index, values, data):(this.opt.fill!=undefined?this.opt.fill:t.indicator.fill); |
|
g.createRect(b).setFill(this._shapeFill(f, b)).setStroke(ls); |
|
label.moveToFront(); |
|
}, |
|
getSeriesStats: function(){ |
|
// summary: |
|
// Returns default stats (irrelevant for this type of plot). |
|
// returns: Object |
|
// {hmin, hmax, vmin, vmax} min/max in both directions. |
|
return lang.delegate(dcpc.defaultStats); |
|
} |
|
}); |
|
if(has("dojo-bidi")){ |
|
Indicator.extend({ |
|
_getMarkX: function(x){ |
|
if(this.chart.isRightToLeft()){ |
|
return this.chart.axes.x.scaler.bounds.to + this.chart.axes.x.scaler.bounds.from - x; |
|
} |
|
return x; |
|
} |
|
}); |
|
} |
|
return Indicator; |
|
});
|
|
|