1 | define(["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "../plot2d/Indicator", |
---|
2 | "dojo/has", "../plot2d/common", "../axis2d/common", "dojox/gfx"], |
---|
3 | function(lang, array, declare, Indicator, has){ |
---|
4 | |
---|
5 | var getXYCoordinates = function(v, values, data){ |
---|
6 | var c2, c1 = v?{ x: values[0], y : data[0][0] } : |
---|
7 | { x : data[0][0], y : values[0] }; |
---|
8 | if(values.length > 1){ |
---|
9 | c2 = v?{ x: values[1], y : data[1][0] } : |
---|
10 | { x : data[1][0], y : values[1] }; |
---|
11 | } |
---|
12 | return [c1, c2]; |
---|
13 | }; |
---|
14 | |
---|
15 | var _IndicatorElement = declare("dojox.charting.action2d._IndicatorElement", Indicator, { |
---|
16 | // summary: |
---|
17 | // Internal element used by indicator actions. |
---|
18 | // tags: |
---|
19 | // private |
---|
20 | constructor: function(chart, kwArgs){ |
---|
21 | if(!kwArgs){ kwArgs = {}; } |
---|
22 | this.inter = kwArgs.inter; |
---|
23 | }, |
---|
24 | _updateVisibility: function(cp, limit, attr){ |
---|
25 | var axis = attr=="x"?this._hAxis:this._vAxis; |
---|
26 | var scale = axis.getWindowScale(); |
---|
27 | this.chart.setAxisWindow(axis.name, scale, axis.getWindowOffset() + (cp[attr] - limit[attr]) / scale); |
---|
28 | this._noDirty = true; |
---|
29 | this.chart.render(); |
---|
30 | this._noDirty = false; |
---|
31 | this._initTrack(); |
---|
32 | }, |
---|
33 | _trackMove: function(){ |
---|
34 | // let's update the selector |
---|
35 | this._updateIndicator(this.pageCoord); |
---|
36 | // if we reached that point once, then we don't stop until mouse up |
---|
37 | // use a recursive setTimeout to avoid intervals that might get backed up |
---|
38 | this._tracker = setTimeout(lang.hitch(this, this._trackMove), 100); |
---|
39 | }, |
---|
40 | _initTrack: function(){ |
---|
41 | if(!this._tracker){ |
---|
42 | this._tracker = setTimeout(lang.hitch(this, this._trackMove), 500); |
---|
43 | } |
---|
44 | }, |
---|
45 | stopTrack: function(){ |
---|
46 | if(this._tracker){ |
---|
47 | clearTimeout(this._tracker); |
---|
48 | this._tracker = null; |
---|
49 | } |
---|
50 | }, |
---|
51 | render: function(){ |
---|
52 | if(!this.isDirty()){ |
---|
53 | return; |
---|
54 | } |
---|
55 | |
---|
56 | var inter = this.inter, plot = inter.plot, v = inter.opt.vertical; |
---|
57 | |
---|
58 | this.opt.offset = inter.opt.offset || (v?{ x:0 , y: 5}: { x: 5, y: 0}); |
---|
59 | |
---|
60 | if(inter.opt.labelFunc){ |
---|
61 | // adapt to indicator labelFunc format |
---|
62 | this.opt.labelFunc = function(index, values, data, fixed, precision){ |
---|
63 | var coords = getXYCoordinates(v, values, data); |
---|
64 | return inter.opt.labelFunc(coords[0], coords[1], fixed, precision); |
---|
65 | }; |
---|
66 | } |
---|
67 | if(inter.opt.fillFunc){ |
---|
68 | // adapt to indicator fillFunc format |
---|
69 | this.opt.fillFunc = function(index, values, data){ |
---|
70 | var coords = getXYCoordinates(v, values, data); |
---|
71 | return inter.opt.fillFunc(coords[0], coords[1]); |
---|
72 | }; |
---|
73 | } |
---|
74 | |
---|
75 | this.opt = lang.delegate(inter.opt, this.opt); |
---|
76 | |
---|
77 | if(!this.pageCoord){ |
---|
78 | this.opt.values = null; |
---|
79 | this.inter.onChange({}); |
---|
80 | }else{ |
---|
81 | // let's create a fake coordinate to not block parent render method |
---|
82 | // actual coordinate will be computed in _updateCoordinates |
---|
83 | this.opt.values = []; |
---|
84 | this.opt.labels = this.secondCoord?"trend":"markers"; |
---|
85 | } |
---|
86 | |
---|
87 | // take axis on the interactor plot and forward them onto the indicator plot |
---|
88 | this.hAxis = plot.hAxis; |
---|
89 | this.vAxis = plot.vAxis; |
---|
90 | |
---|
91 | this.inherited(arguments); |
---|
92 | }, |
---|
93 | _updateIndicator: function(){ |
---|
94 | var coordinates = this._updateCoordinates(this.pageCoord, this.secondCoord); |
---|
95 | if(coordinates.length > 1){ |
---|
96 | var v = this.opt.vertical; |
---|
97 | this._data= []; |
---|
98 | this.opt.values = []; |
---|
99 | array.forEach(coordinates, function(value){ |
---|
100 | if(value){ |
---|
101 | this.opt.values.push(v?value.x:value.y); |
---|
102 | this._data.push([v?value.y:value.x]); |
---|
103 | } |
---|
104 | }, this); |
---|
105 | }else{ |
---|
106 | this.inter.onChange({}); |
---|
107 | return; |
---|
108 | } |
---|
109 | this.inherited(arguments); |
---|
110 | }, |
---|
111 | _renderText: function(g, text, t, x, y, index, values, data){ |
---|
112 | // render only if labels is true |
---|
113 | if(this.inter.opt.labels){ |
---|
114 | this.inherited(arguments); |
---|
115 | } |
---|
116 | // send the event in all cases |
---|
117 | var coords = getXYCoordinates(this.opt.vertical, values, data); |
---|
118 | this.inter.onChange({ |
---|
119 | start: coords[0], |
---|
120 | end: coords[1], |
---|
121 | label: text |
---|
122 | }); |
---|
123 | }, |
---|
124 | _updateCoordinates: function(cp1, cp2){ |
---|
125 | // chart mirroring starts |
---|
126 | if(has("dojo-bidi")){ |
---|
127 | this._checkXCoords(cp1, cp2); |
---|
128 | } |
---|
129 | // chart mirroring ends |
---|
130 | var inter = this.inter, plot = inter.plot, v = inter.opt.vertical; |
---|
131 | var hAxis = this.chart.getAxis(plot.hAxis), vAxis = this.chart.getAxis(plot.vAxis); |
---|
132 | var hn = hAxis.name, vn = vAxis.name, hb = hAxis.getScaler().bounds, vb = vAxis.getScaler().bounds; |
---|
133 | var attr = v?"x":"y", n = v?hn:vn, bounds = v?hb:vb; |
---|
134 | |
---|
135 | // sort data point |
---|
136 | if(cp2){ |
---|
137 | var tmp; |
---|
138 | if(v){ |
---|
139 | if(cp1.x > cp2.x){ |
---|
140 | tmp = cp2; |
---|
141 | cp2 = cp1; |
---|
142 | cp1 = tmp; |
---|
143 | } |
---|
144 | }else{ |
---|
145 | if(cp1.y > cp2.y){ |
---|
146 | tmp = cp2; |
---|
147 | cp2 = cp1; |
---|
148 | cp1 = tmp; |
---|
149 | } |
---|
150 | } |
---|
151 | } |
---|
152 | |
---|
153 | var cd1 = plot.toData(cp1), cd2; |
---|
154 | if(cp2){ |
---|
155 | cd2 = plot.toData(cp2); |
---|
156 | } |
---|
157 | |
---|
158 | var o = {}; |
---|
159 | o[hn] = hb.from; |
---|
160 | o[vn] = vb.from; |
---|
161 | var min = plot.toPage(o); |
---|
162 | o[hn] = hb.to; |
---|
163 | o[vn] = vb.to; |
---|
164 | var max = plot.toPage(o); |
---|
165 | |
---|
166 | if(cd1[n] < bounds.from){ |
---|
167 | // do not autoscroll if dual indicator |
---|
168 | if(!cd2 && inter.opt.autoScroll && !inter.opt.mouseOver){ |
---|
169 | this._updateVisibility(cp1, min, attr); |
---|
170 | return []; |
---|
171 | }else{ |
---|
172 | if(inter.opt.mouseOver){ |
---|
173 | return[]; |
---|
174 | } |
---|
175 | cp1[attr] = min[attr]; |
---|
176 | } |
---|
177 | // cp1 might have changed, let's update cd1 |
---|
178 | cd1 = plot.toData(cp1); |
---|
179 | }else if(cd1[n] > bounds.to){ |
---|
180 | if(!cd2 && inter.opt.autoScroll && !inter.opt.mouseOver){ |
---|
181 | this._updateVisibility(cp1, max, attr); |
---|
182 | return []; |
---|
183 | }else{ |
---|
184 | if(inter.opt.mouseOver){ |
---|
185 | return[]; |
---|
186 | } |
---|
187 | cp1[attr] = max[attr]; |
---|
188 | } |
---|
189 | // cp1 might have changed, let's update cd1 |
---|
190 | cd1 = plot.toData(cp1); |
---|
191 | } |
---|
192 | |
---|
193 | var c1 = this._snapData(cd1, attr, v), c2; |
---|
194 | |
---|
195 | if(c1.y == null){ |
---|
196 | // we have no data for that point let's just return |
---|
197 | return []; |
---|
198 | } |
---|
199 | |
---|
200 | if(cp2){ |
---|
201 | if(cd2[n] < bounds.from){ |
---|
202 | cp2[attr] = min[attr]; |
---|
203 | cd2 = plot.toData(cp2); |
---|
204 | }else if(cd2[n] > bounds.to){ |
---|
205 | cp2[attr] = max[attr]; |
---|
206 | cd2 = plot.toData(cp2); |
---|
207 | } |
---|
208 | c2 = this._snapData(cd2, attr, v); |
---|
209 | if(c2.y == null){ |
---|
210 | // we have no data for that point let's pretend we have a single touch point |
---|
211 | c2 = null; |
---|
212 | } |
---|
213 | } |
---|
214 | |
---|
215 | return [c1, c2]; |
---|
216 | }, |
---|
217 | _snapData: function(cd, attr, v){ |
---|
218 | // we need to find which actual data point is "close" to the data value |
---|
219 | var data = this.chart.getSeries(this.inter.opt.series).data; |
---|
220 | // let's consider data are sorted because anyway rendering will be "weird" with unsorted data |
---|
221 | // i is an index in the array, which is different from a x-axis value even for index based data |
---|
222 | var i, r, l = data.length; |
---|
223 | // first let's find which data index we are in |
---|
224 | for (i = 0; i < l; ++i){ |
---|
225 | r = data[i]; |
---|
226 | if(r == null){ |
---|
227 | // move to next item |
---|
228 | }else if(typeof r == "number"){ |
---|
229 | if(i + 1 > cd[attr]){ |
---|
230 | break; |
---|
231 | } |
---|
232 | }else if(r[attr] > cd[attr]){ |
---|
233 | break; |
---|
234 | } |
---|
235 | } |
---|
236 | var x, y, px, py; |
---|
237 | if(typeof r == "number"){ |
---|
238 | x = i+1; |
---|
239 | y = r; |
---|
240 | if(i > 0){ |
---|
241 | px = i; |
---|
242 | py = data[i-1]; |
---|
243 | } |
---|
244 | }else{ |
---|
245 | x = r.x; |
---|
246 | y = r.y; |
---|
247 | if(i > 0){ |
---|
248 | px = data[i-1].x; |
---|
249 | py = data[i-1].y; |
---|
250 | } |
---|
251 | } |
---|
252 | if(i > 0){ |
---|
253 | var m = v?(x+px)/2:(y+py)/2; |
---|
254 | if(cd[attr]<=m){ |
---|
255 | x = px; |
---|
256 | y = py; |
---|
257 | } |
---|
258 | } |
---|
259 | return {x: x, y: y}; |
---|
260 | }, |
---|
261 | cleanGroup: function(creator){ |
---|
262 | // summary: |
---|
263 | // Clean any elements (HTML or GFX-based) out of our group, and create a new one. |
---|
264 | // creator: dojox/gfx/Surface? |
---|
265 | // An optional surface to work with. |
---|
266 | // returns: dojox/charting/Element |
---|
267 | // A reference to this object for functional chaining. |
---|
268 | this.inherited(arguments); |
---|
269 | // we always want to be above regular plots and not clipped |
---|
270 | this.group.moveToFront(); |
---|
271 | return this; // dojox/charting/Element |
---|
272 | }, |
---|
273 | isDirty: function(){ |
---|
274 | // summary: |
---|
275 | // Return whether or not this plot needs to be redrawn. |
---|
276 | // returns: Boolean |
---|
277 | // If this plot needs to be rendered, this will return true. |
---|
278 | return !this._noDirty && (this.dirty || this.inter.plot.isDirty()); |
---|
279 | } |
---|
280 | }); |
---|
281 | if(has("dojo-bidi")){ |
---|
282 | _IndicatorElement.extend({ |
---|
283 | _checkXCoords: function(cp1, cp2){ |
---|
284 | if(this.chart.isRightToLeft()){ |
---|
285 | if(cp1){ |
---|
286 | cp1.x = this.chart.dim.width + (this.chart.offsets.l - this.chart.offsets.r) - cp1.x; |
---|
287 | } |
---|
288 | if(cp2){ |
---|
289 | cp2.x = this.chart.dim.width + (this.chart.offsets.l - this.chart.offsets.r) - cp2.x; |
---|
290 | } |
---|
291 | } |
---|
292 | } |
---|
293 | }); |
---|
294 | } |
---|
295 | return _IndicatorElement; |
---|
296 | }); |
---|