dojox
Version:
Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.
306 lines (286 loc) • 8.43 kB
JavaScript
define(["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "../plot2d/Indicator",
"dojo/has", "../plot2d/common", "../axis2d/common", "dojox/gfx"],
function(lang, array, declare, Indicator, has){
var getXYCoordinates = function(v, values, data){
var c2, c1 = v?{ x: values[0], y : data[0][0] } :
{ x : data[0][0], y : values[0] };
if(values.length > 1){
c2 = v?{ x: values[1], y : data[1][0] } :
{ x : data[1][0], y : values[1] };
}
return [c1, c2];
};
var _IndicatorElement = declare("dojox.charting.action2d._IndicatorElement", Indicator, {
// summary:
// Internal element used by indicator actions.
// tags:
// private
constructor: function(chart, kwArgs){
if(!kwArgs){ kwArgs = {}; }
this.inter = kwArgs.inter;
},
_updateVisibility: function(cp, limit, attr){
var axis = attr=="x"?this._hAxis:this._vAxis;
var scale = axis.getWindowScale();
this.chart.setAxisWindow(axis.name, scale, axis.getWindowOffset() + (cp[attr] - limit[attr]) / scale);
this._noDirty = true;
this.chart.render();
this._noDirty = false;
this._initTrack();
},
_trackMove: function(){
// let's update the selector
this._updateIndicator(this.pageCoord);
// if we reached that point once, then we don't stop until mouse up
// use a recursive setTimeout to avoid intervals that might get backed up
this._tracker = setTimeout(lang.hitch(this, this._trackMove), 100);
},
_initTrack: function(){
if(!this._tracker){
this._tracker = setTimeout(lang.hitch(this, this._trackMove), 500);
}
},
stopTrack: function(){
if(this._tracker){
clearTimeout(this._tracker);
this._tracker = null;
}
},
render: function(){
if(!this.isDirty()){
return;
}
var inter = this.inter, plot = inter.plot, v = inter.opt.vertical;
this.opt.offset = inter.opt.offset || (v?{ x:0 , y: 5}: { x: 5, y: 0});
if(inter.opt.labelFunc){
// adapt to indicator labelFunc format
this.opt.labelFunc = function(index, values, data, fixed, precision){
var coords = getXYCoordinates(v, values, data);
return inter.opt.labelFunc(coords[0], coords[1], fixed, precision);
};
}
if(inter.opt.fillFunc){
// adapt to indicator fillFunc format
this.opt.fillFunc = function(index, values, data){
var coords = getXYCoordinates(v, values, data);
return inter.opt.fillFunc(coords[0], coords[1]);
};
}
this.opt = lang.delegate(inter.opt, this.opt);
if(!this.pageCoord){
this.opt.values = null;
this.inter.onChange({});
}else{
// let's create a fake coordinate to not block parent render method
// actual coordinate will be computed in _updateCoordinates
this.opt.values = [];
this.opt.labels = this.secondCoord?"trend":"markers";
}
// take axis on the interactor plot and forward them onto the indicator plot
this.hAxis = plot.hAxis;
this.vAxis = plot.vAxis;
this.inherited(arguments);
},
_updateIndicator: function(){
var coordinates = this._updateCoordinates(this.pageCoord, this.secondCoord);
if(coordinates.length > 1){
var v = this.opt.vertical;
this._data= [];
this.opt.values = [];
array.forEach(coordinates, function(value){
if(value){
this.opt.values.push(v?value.x:value.y);
this._data.push([v?value.y:value.x]);
}
}, this);
}else{
this.inter.onChange({});
return;
}
this.inherited(arguments);
},
_renderText: function(g, text, t, x, y, index, values, data){
// render only if labels is true
if(this.inter.opt.labels){
this.inherited(arguments);
}
// send the event in all cases
var coords = getXYCoordinates(this.opt.vertical, values, data);
this.inter.onChange({
start: coords[0],
end: coords[1],
label: text
});
},
_updateCoordinates: function(cp1, cp2){
// chart mirroring starts
if(has("dojo-bidi")){
this._checkXCoords(cp1, cp2);
}
// chart mirroring ends
var inter = this.inter, plot = inter.plot, v = inter.opt.vertical;
var hAxis = this.chart.getAxis(plot.hAxis), vAxis = this.chart.getAxis(plot.vAxis);
var hn = hAxis.name, vn = vAxis.name, hb = hAxis.getScaler().bounds, vb = vAxis.getScaler().bounds;
var attr = v?"x":"y", n = v?hn:vn, bounds = v?hb:vb;
// sort data point
if(cp2){
var tmp;
if(v){
if(cp1.x > cp2.x){
tmp = cp2;
cp2 = cp1;
cp1 = tmp;
}
}else{
if(cp1.y > cp2.y){
tmp = cp2;
cp2 = cp1;
cp1 = tmp;
}
}
}
var cd1 = plot.toData(cp1), cd2;
if(cp2){
cd2 = plot.toData(cp2);
}
var o = {};
o[hn] = hb.from;
o[vn] = vb.from;
var min = plot.toPage(o);
o[hn] = hb.to;
o[vn] = vb.to;
var max = plot.toPage(o);
if(cd1[n] < bounds.from){
// do not autoscroll if dual indicator
if(!cd2 && inter.opt.autoScroll && !inter.opt.mouseOver){
this._updateVisibility(cp1, min, attr);
return [];
}else{
if(inter.opt.mouseOver){
return[];
}
cp1[attr] = min[attr];
}
// cp1 might have changed, let's update cd1
cd1 = plot.toData(cp1);
}else if(cd1[n] > bounds.to){
if(!cd2 && inter.opt.autoScroll && !inter.opt.mouseOver){
this._updateVisibility(cp1, max, attr);
return [];
}else{
if(inter.opt.mouseOver){
return[];
}
cp1[attr] = max[attr];
}
// cp1 might have changed, let's update cd1
cd1 = plot.toData(cp1);
}
var c1 = this._snapData(cd1, attr, v), c2;
if(c1.y == null){
// we have no data for that point let's just return
return [];
}
if(cp2){
if(cd2[n] < bounds.from){
cp2[attr] = min[attr];
cd2 = plot.toData(cp2);
}else if(cd2[n] > bounds.to){
cp2[attr] = max[attr];
cd2 = plot.toData(cp2);
}
c2 = this._snapData(cd2, attr, v);
if(c2.y == null){
// we have no data for that point let's pretend we have a single touch point
c2 = null;
}
}
return [c1, c2];
},
_snapData: function(cd, attr, v){
// we need to find which actual data point is "close" to the data value
var data = this.chart.getSeries(this.inter.opt.series).data;
// let's consider data are sorted because anyway rendering will be "weird" with unsorted data
// i is an index in the array, which is different from a x-axis value even for index based data
var i, r, l = data.length;
// first let's find which data index we are in
for (i = 0; i < l; ++i){
r = data[i];
if(r == null){
// move to next item
}else if(typeof r == "number"){
if(i + 1 > cd[attr]){
break;
}
}else if(r[attr] > cd[attr]){
break;
}
}
var x, y, px, py;
if(typeof r == "number"){
x = i+1;
y = r;
if(i > 0){
px = i;
py = data[i-1];
}
}else{
x = r.x;
y = r.y;
if(i > 0){
px = data[i-1].x;
py = data[i-1].y;
}
}
if(i > 0){
var m = v?(x+px)/2:(y+py)/2;
if(cd[attr]<=m){
x = px;
y = py;
}
}
return {x: x, y: y};
},
cleanGroup: function(creator){
// summary:
// Clean any elements (HTML or GFX-based) out of our group, and create a new one.
// creator: dojox/gfx/Surface?
// An optional surface to work with.
// returns: dojox/charting/Element
// A reference to this object for functional chaining.
this.inherited(arguments);
// we always want to be above regular plots and not clipped
this.group.moveToFront();
return this; // dojox/charting/Element
},
isDirty: function(){
// summary:
// Return whether or not this plot needs to be redrawn.
// returns: Boolean
// If this plot needs to be rendered, this will return true.
return !this._noDirty && (this.dirty || this.inter.plot.isDirty());
}
});
if(has("dojo-bidi")){
_IndicatorElement.extend({
_checkXCoords: function(cp1, cp2){
if(this.chart.isRightToLeft() && this.isDirty()){
var offset = this.chart.node.offsetLeft;
var transform = function transform(plot, cp) {
var x = cp.x - offset;
var shift = (plot.chart.offsets.l - plot.chart.offsets.r);
var transformed_x = plot.chart.dim.width + shift - x;
return transformed_x + offset;
};
if(cp1){
cp1.x = transform(this, cp1);
}
if(cp2){
cp2.x = transform(this, cp2);
}
}
}
});
}
return _IndicatorElement;
});