UNPKG

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
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; });