_IndicatorElement.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. define("dojox/charting/action2d/_IndicatorElement", ["dojo/_base/lang", "dojo/_base/declare", "../Element", "../plot2d/common",
  2. "../axis2d/common", "dojox/gfx"],
  3. function(lang, declare, Element, dcpc, dcac, gfx){
  4. // all the code below should be removed when http://trac.dojotoolkit.org/ticket/11299 will be available
  5. var getBoundingBox = function(shape){
  6. return getTextBBox(shape, shape.getShape().text);
  7. };
  8. var getTextBBox = function(s, t){
  9. var c = s.declaredClass;
  10. if (c.indexOf("svg")!=-1){
  11. // try/catch the FF native getBBox error. cheaper than walking up in the DOM
  12. // hierarchy to check the conditions (bench show /10 )
  13. try {
  14. return lang.mixin({}, s.rawNode.getBBox());
  15. }catch (e){
  16. return null;
  17. }
  18. }else if(c.indexOf("vml")!=-1){
  19. var rawNode = s.rawNode, _display = rawNode.style.display;
  20. rawNode.style.display = "inline";
  21. var w = gfx.pt2px(parseFloat(rawNode.currentStyle.width));
  22. var h = gfx.pt2px(parseFloat(rawNode.currentStyle.height));
  23. var sz = {x: 0, y: 0, width: w, height: h};
  24. // in VML, the width/height we get are in view coordinates
  25. // in our case we don't zoom the view so that is ok
  26. // It's impossible to get the x/y from the currentStyle.left/top,
  27. // because all negative coordinates are 'clipped' to 0.
  28. // (x:0 + translate(-100) -> x=0
  29. computeLocation(s, sz);
  30. rawNode.style.display = _display;
  31. return sz;
  32. }else if(c.indexOf("silverlight")!=-1){
  33. var bb = {width: s.rawNode.actualWidth, height: s.rawNode.actualHeight};
  34. return computeLocation(s, bb, 0.75);
  35. }else if(s.getTextWidth){
  36. // canvas
  37. var w = s.getTextWidth();
  38. var font = s.getFont();
  39. var fz = font ? font.size : gfx.defaultFont.size;
  40. var h = gfx.normalizedLength(fz);
  41. sz = {width: w, height: h};
  42. computeLocation(s, sz, 0.75);
  43. return sz;
  44. }
  45. };
  46. var computeLocation = function(s, sz, coef){
  47. var width = sz.width, height = sz.height, sh = s.getShape(), align = sh.align;
  48. switch (align) {
  49. case "end":
  50. sz.x = sh.x - width;
  51. break;
  52. case "middle":
  53. sz.x = sh.x - width / 2;
  54. break;
  55. case "start":
  56. default:
  57. sz.x = sh.x;
  58. break;
  59. }
  60. coef = coef || 1;
  61. sz.y = sh.y - height*coef; // rough approximation of the ascent!...
  62. return sz;
  63. };
  64. return declare("dojox.charting.action2d._IndicatorElement",[Element], {
  65. // summary:
  66. // Internal element used by indicator actions.
  67. // tags:
  68. // private
  69. constructor: function(chart, kwArgs){
  70. if(!kwArgs){ kwArgs = {}; }
  71. this.inter = kwArgs.inter;
  72. },
  73. _updateVisibility: function(cp, limit, attr){
  74. var axis = attr=="x"?this.inter.plot._hAxis:this.inter.plot._vAxis;
  75. var scale = axis.getWindowScale();
  76. this.chart.setAxisWindow(axis.name, scale, axis.getWindowOffset() + (cp[attr] - limit[attr]) / scale);
  77. this._noDirty = true;
  78. this.chart.render();
  79. this._noDirty = false;
  80. if(!this._tracker){
  81. this.initTrack();
  82. }
  83. },
  84. _trackMove: function(){
  85. // let's update the selector
  86. this._updateIndicator(this.pageCoord);
  87. // if we reached that point once, then we don't stop until mouse up
  88. if(this._initTrackPhase){
  89. this._initTrackPhase = false;
  90. this._tracker = setInterval(lang.hitch(this, this._trackMove), 100);
  91. }
  92. },
  93. initTrack: function(){
  94. this._initTrackPhase = true;
  95. this._tracker = setTimeout(lang.hitch(this, this._trackMove), 500);
  96. },
  97. stopTrack: function(){
  98. if(this._tracker){
  99. if(this._initTrackPhase){
  100. clearTimeout(this._tracker);
  101. }else{
  102. clearInterval(this._tracker);
  103. }
  104. this._tracker = null;
  105. }
  106. },
  107. render: function(){
  108. if(!this.isDirty()){
  109. return;
  110. }
  111. this.cleanGroup();
  112. if (!this.pageCoord){
  113. return;
  114. }
  115. this._updateIndicator(this.pageCoord, this.secondCoord);
  116. },
  117. _updateIndicator: function(cp1, cp2){
  118. var inter = this.inter, plot = inter.plot, v = inter.opt.vertical;
  119. var hAxis = this.chart.getAxis(plot.hAxis), vAxis = this.chart.getAxis(plot.vAxis);
  120. var hn = hAxis.name, vn = vAxis.name, hb = hAxis.getScaler().bounds, vb = vAxis.getScaler().bounds;
  121. var attr = v?"x":"y", n = v?hn:vn, bounds = v?hb:vb;
  122. // sort data point
  123. if(cp2){
  124. var tmp;
  125. if(v){
  126. if(cp1.x>cp2.x){
  127. tmp = cp2;
  128. cp2 = cp1;
  129. cp1 = tmp;
  130. }
  131. }else{
  132. if(cp1.y>cp2.y){
  133. tmp = cp2;
  134. cp2 = cp1;
  135. cp1 = tmp;
  136. }
  137. }
  138. }
  139. var cd1 = plot.toData(cp1), cd2;
  140. if(cp2){
  141. cd2 = plot.toData(cp2);
  142. }
  143. var o = {};
  144. o[hn] = hb.from;
  145. o[vn] = vb.from;
  146. var min = plot.toPage(o);
  147. o[hn] = hb.to;
  148. o[vn] = vb.to;
  149. var max = plot.toPage(o);
  150. if(cd1[n] < bounds.from){
  151. // do not autoscroll if dual indicator
  152. if(!cd2 && inter.opt.autoScroll){
  153. this._updateVisibility(cp1, min, attr);
  154. return;
  155. }else{
  156. cp1[attr] = min[attr];
  157. }
  158. // cp1 might have changed, let's update cd1
  159. cd1 = plot.toData(cp1);
  160. }else if(cd1[n] > bounds.to){
  161. if(!cd2 && inter.opt.autoScroll){
  162. this._updateVisibility(cp1, max, attr);
  163. return;
  164. }else{
  165. cp1[attr] = max[attr];
  166. }
  167. // cp1 might have changed, let's update cd1
  168. cd1 = plot.toData(cp1);
  169. }
  170. var c1 = this._getData(cd1, attr, v), c2;
  171. if(c1.y == null){
  172. // we have no data for that point let's just return
  173. return;
  174. }
  175. if(cp2){
  176. if(cd2[n] < bounds.from){
  177. cp2[attr] = min[attr];
  178. cd2 = plot.toData(cp2);
  179. }else if(cd2[n] > bounds.to){
  180. cp2[attr] = max[attr];
  181. cd2 = plot.toData(cp2);
  182. }
  183. c2 = this._getData(cd2, attr, v);
  184. if(c2.y == null){
  185. // we have no data for that point let's pretend we have a single touch point
  186. cp2 = null;
  187. }
  188. }
  189. var t1 = this._renderIndicator(c1, cp2?1:0, hn, vn, min, max);
  190. if(cp2){
  191. var t2 = this._renderIndicator(c2, 2, hn, vn, min, max);
  192. var delta = v?c2.y-c1.y:c2.x-c1.y;
  193. var text = inter.opt.labelFunc?inter.opt.labelFunc(c1, c2, inter.opt.fixed, inter.opt.precision):
  194. (dcpc.getLabel(delta, inter.opt.fixed, inter.opt.precision)+" ("+dcpc.getLabel(100*delta/(v?c1.y:c1.x), true, 2)+"%)");
  195. this._renderText(text, inter, this.chart.theme, v?(t1.x+t2.x)/2:t1.x, v?t1.y:(t1.y+t2.y)/2, c1, c2);
  196. };
  197. },
  198. _renderIndicator: function(coord, index, hn, vn, min, max){
  199. var t = this.chart.theme, c = this.chart.getCoords(), inter = this.inter, plot = inter.plot, v = inter.opt.vertical;
  200. var mark = {};
  201. mark[hn] = coord.x;
  202. mark[vn] = coord.y;
  203. mark = plot.toPage(mark);
  204. var cx = mark.x - c.x, cy = mark.y - c.y;
  205. 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;
  206. var sh = inter.opt.lineShadow?inter.opt.lineShadow:t.indicator.lineShadow,
  207. ls = inter.opt.lineStroke?inter.opt.lineStroke:t.indicator.lineStroke,
  208. ol = inter.opt.lineOutline?inter.opt.lineOutline:t.indicator.lineOutline;
  209. if(sh){
  210. this.group.createLine({x1: x1 + sh.dx, y1: y1 + sh.dy, x2: x2 + sh.dx, y2: y2 + sh.dy}).setStroke(sh);
  211. }
  212. if(ol){
  213. ol = dcpc.makeStroke(ol);
  214. ol.width = 2 * ol.width + ls.width;
  215. this.group.createLine({x1: x1, y1: y1, x2: x2, y2: y2}).setStroke(ol);
  216. }
  217. this.group.createLine({x1: x1, y1: y1, x2: x2, y2: y2}).setStroke(ls);
  218. var ms = inter.opt.markerSymbol?inter.opt.markerSymbol:t.indicator.markerSymbol,
  219. path = "M" + cx + " " + cy + " " + ms;
  220. sh = inter.opt.markerShadow?inter.opt.markerShadow:t.indicator.markerShadow;
  221. ls = inter.opt.markerStroke?inter.opt.markerStroke:t.indicator.markerStroke;
  222. ol = inter.opt.markerOutline?inter.opt.markerOutline:t.indicator.markerOutline;
  223. if(sh){
  224. var sp = "M" + (cx + sh.dx) + " " + (cy + sh.dy) + " " + ms;
  225. this.group.createPath(sp).setFill(sh.color).setStroke(sh);
  226. }
  227. if(ol){
  228. ol = dcpc.makeStroke(ol);
  229. ol.width = 2 * ol.width + ls.width;
  230. this.group.createPath(path).setStroke(ol);
  231. }
  232. var shape = this.group.createPath(path);
  233. var sf = this._shapeFill(inter.opt.markerFill?inter.opt.markerFill:t.indicator.markerFill, shape.getBoundingBox());
  234. shape.setFill(sf).setStroke(ls);
  235. if(index==0){
  236. var text = inter.opt.labelFunc?inter.opt.labelFunc(coord, null, inter.opt.fixed, inter.opt.precision):
  237. dcpc.getLabel(v?coord.y:coord.x, inter.opt.fixed, inter.opt.precision);
  238. this._renderText(text, inter, t, v?x1:x2+5, v?y2+5:y1, coord);
  239. }else{
  240. return v?{x: x1, y: y2+5}:{x: x2+5, y: y1};
  241. }
  242. },
  243. _renderText: function(text, inter, t, x, y, c1, c2){
  244. var label = dcac.createText.gfx(
  245. this.chart,
  246. this.group,
  247. x, y,
  248. "middle",
  249. text, inter.opt.font?inter.opt.font:t.indicator.font, inter.opt.fontColor?inter.opt.fontColor:t.indicator.fontColor);
  250. var b = getBoundingBox(label);
  251. b.x-=2; b.y-=1; b.width+=4; b.height+=2; b.r = inter.opt.radius?inter.opt.radius:t.indicator.radius;
  252. sh = inter.opt.shadow?inter.opt.shadow:t.indicator.shadow;
  253. ls = inter.opt.stroke?inter.opt.stroke:t.indicator.stroke;
  254. ol = inter.opt.outline?inter.opt.outline:t.indicator.outline;
  255. if(sh){
  256. this.group.createRect(b).setFill(sh.color).setStroke(sh);
  257. }
  258. if(ol){
  259. ol = dcpc.makeStroke(ol);
  260. ol.width = 2 * ol.width + ls.width;
  261. this.group.createRect(b).setStroke(ol);
  262. }
  263. var f = inter.opt.fillFunc?inter.opt.fillFunc(c1, c2):(inter.opt.fill?inter.opt.fill:t.indicator.fill);
  264. this.group.createRect(b).setFill(this._shapeFill(f, b)).setStroke(ls);
  265. label.moveToFront();
  266. },
  267. _getData: function(cd, attr, v){
  268. // we need to find which actual data point is "close" to the data value
  269. var data = this.chart.getSeries(this.inter.opt.series).data;
  270. // let's consider data are sorted because anyway rendering will be "weird" with unsorted data
  271. // i is an index in the array, which is different from a x-axis value even for index based data
  272. var i, r, l = data.length;
  273. for (i = 0; i < l; ++i){
  274. r = data[i];
  275. if(r == null){
  276. // move to next item
  277. }else if(typeof r == "number"){
  278. if(i + 1 > cd[attr]){
  279. break;
  280. }
  281. }else if(r[attr] > cd[attr]){
  282. break;
  283. }
  284. }
  285. var x,y,px,py;
  286. if(typeof r == "number"){
  287. x = i+1;
  288. y = r;
  289. if(i>0){
  290. px = i;
  291. py = data[i-1];
  292. }
  293. }else{
  294. x = r.x;
  295. y = r.y;
  296. if(i>0){
  297. px = data[i-1].x;
  298. py = data[i-1].y;
  299. }
  300. }
  301. if(i>0){
  302. var m = v?(x+px)/2:(y+py)/2;
  303. if(cd[attr]<=m){
  304. x = px;
  305. y = py;
  306. }
  307. }
  308. return {x: x, y: y};
  309. },
  310. cleanGroup: function(creator){
  311. // summary:
  312. // Clean any elements (HTML or GFX-based) out of our group, and create a new one.
  313. // creator: dojox.gfx.Surface?
  314. // An optional surface to work with.
  315. // returns: dojox.charting.Element
  316. // A reference to this object for functional chaining.
  317. this.inherited(arguments);
  318. // we always want to be above regular plots and not clipped
  319. this.group.moveToFront();
  320. return this; // dojox.charting.Element
  321. },
  322. clear: function(){
  323. // summary:
  324. // Clear out any parameters set on this plot.
  325. // returns: dojox.charting.action2d._IndicatorElement
  326. // The reference to this plot for functional chaining.
  327. this.dirty = true;
  328. return this; // dojox.charting.plot2d._IndicatorElement
  329. },
  330. getSeriesStats: function(){
  331. // summary:
  332. // Returns default stats (irrelevant for this type of plot).
  333. // returns: Object
  334. // {hmin, hmax, vmin, vmax} min/max in both directions.
  335. return lang.delegate(dcpc.defaultStats);
  336. },
  337. initializeScalers: function(){
  338. // summary:
  339. // Does nothing (irrelevant for this type of plot).
  340. return this;
  341. },
  342. isDirty: function(){
  343. // summary:
  344. // Return whether or not this plot needs to be redrawn.
  345. // returns: Boolean
  346. // If this plot needs to be rendered, this will return true.
  347. return !this._noDirty && (this.dirty || this.inter.plot.isDirty());
  348. }
  349. });
  350. });