]> git.proxmox.com Git - sencha-touch.git/blob - src/src/chart/series/sprite/Line.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / chart / series / sprite / Line.js
1 /**
2 * @class Ext.chart.series.sprite.Line
3 * @extends Ext.chart.series.sprite.Aggregative
4 *
5 * Line series sprite.
6 */
7 Ext.define('Ext.chart.series.sprite.Line', {
8 alias: 'sprite.lineSeries',
9 extend: 'Ext.chart.series.sprite.Aggregative',
10
11 inheritableStatics: {
12 def: {
13 processors: {
14 smooth: 'bool',
15 fillArea: 'bool',
16 step: 'bool',
17 preciseStroke: 'bool'
18 },
19
20 defaults: {
21 /**
22 * @cfg {Boolean} smooth 'true' if the sprite uses line smoothing.
23 */
24 smooth: false,
25
26 /**
27 * @cfg {Boolean} fillArea 'true' if the sprite paints the area underneath the line.
28 */
29 fillArea: false,
30
31 /**
32 * @cfg {Boolean} step 'true' if the line uses steps instead of straight lines to connect the dots.
33 * It is ignored if `smooth` is true.
34 */
35 step: false,
36
37 /**
38 * @cfg {Boolean} preciseStroke 'true' if the line uses precise stroke.
39 */
40 preciseStroke: true
41 },
42
43 dirtyTriggers: {
44 dataX: 'dataX,bbox,smooth',
45 dataY: 'dataY,bbox,smooth',
46 smooth: 'smooth'
47 },
48
49 updaters: {
50 smooth: function (attr) {
51 if (attr.smooth && attr.dataX && attr.dataY && attr.dataX.length > 2 && attr.dataY.length > 2) {
52 this.smoothX = Ext.draw.Draw.spline(attr.dataX);
53 this.smoothY = Ext.draw.Draw.spline(attr.dataY);
54 } else {
55 delete this.smoothX;
56 delete this.smoothY;
57 }
58 }
59 }
60 }
61 },
62
63 list: null,
64
65 updatePlainBBox: function (plain) {
66 var attr = this.attr,
67 ymin = Math.min(0, attr.dataMinY),
68 ymax = Math.max(0, attr.dataMaxY);
69 plain.x = attr.dataMinX;
70 plain.y = ymin;
71 plain.width = attr.dataMaxX - attr.dataMinX;
72 plain.height = ymax - ymin;
73 },
74
75 drawStroke: function (surface, ctx, start, end, list, xAxis) {
76 var attr = this.attr,
77 matrix = attr.matrix,
78 xx = matrix.getXX(),
79 yy = matrix.getYY(),
80 dx = matrix.getDX(),
81 dy = matrix.getDY(),
82 smooth = attr.smooth,
83 step = attr.step,
84 scale = Math.pow(2, power(attr.dataX.length, end)),
85 smoothX = this.smoothX,
86 smoothY = this.smoothY,
87 i, j, lineConfig, changes,
88 cx1, cy1, cx2, cy2, x, y, x0, y0, saveOpacity;
89
90 function power(count, end) {
91 var power = 0,
92 n = count;
93 while (n > 0 && n < end) {
94 power++;
95 n += count >> power;
96 }
97 return power > 0 ? power - 1 : power;
98 }
99
100 ctx.beginPath();
101 if (smooth && smoothX && smoothY) {
102 ctx.moveTo(smoothX[start * 3] * xx + dx, smoothY[start * 3] * yy + dy);
103 for (i = 0, j = start * 3 + 1; i < list.length - 3; i += 3, j += 3 * scale) {
104 cx1 = smoothX[j] * xx + dx;
105 cy1 = smoothY[j] * yy + dy;
106 cx2 = smoothX[j + 1] * xx + dx;
107 cy2 = smoothY[j + 1] * yy + dy;
108 x = list[i + 3];
109 y = list[i + 4];
110 x0 = list[i];
111 y0 = list[i + 1];
112 if (attr.renderer) {
113 lineConfig = {
114 type: 'line',
115 smooth: true,
116 step: step,
117 cx1: cx1,
118 cy1: cy1,
119 cx2: cx2,
120 cy2: cy2,
121 x: x,
122 y: y,
123 x0: x0,
124 y0: y0
125 };
126 changes = attr.renderer.call(this, this, lineConfig, {store:this.getStore()}, (i/3 + 1));
127 ctx.save();
128 Ext.apply(ctx, changes);
129 // Fill the area if we need to, using the fill color and transparent strokes.
130 if (attr.fillArea) {
131 saveOpacity = ctx.strokeOpacity;
132 ctx.save();
133 ctx.strokeOpacity = 0;
134 ctx.moveTo(x0, y0);
135 ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
136 ctx.lineTo(x, xAxis);
137 ctx.lineTo(x0, xAxis);
138 ctx.lineTo(x0, y0);
139 ctx.closePath();
140 ctx.fillStroke(attr, true);
141 ctx.restore();
142 ctx.strokeOpacity = saveOpacity;
143 ctx.beginPath();
144 }
145 // Draw the line on top of the filled area.
146 ctx.moveTo(x0, y0);
147 ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
148 ctx.moveTo(x0, y0);
149 ctx.closePath();
150 ctx.stroke();
151 ctx.restore();
152 ctx.beginPath();
153 ctx.moveTo(x, y);
154 } else {
155 ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
156 }
157 }
158 } else {
159 ctx.moveTo(list[0], list[1]);
160 for (i = 3; i < list.length; i += 3) {
161 x = list[i];
162 y = list[i + 1];
163 x0 = list[i - 3];
164 y0 = list[i - 2];
165 if (attr.renderer) {
166 lineConfig = {
167 type: 'line',
168 smooth: false,
169 step: step,
170 x: x,
171 y: y,
172 x0: x0,
173 y0: y0
174 };
175 changes = attr.renderer.call(this, this, lineConfig, {store:this.getStore()}, i/3);
176 ctx.save();
177 Ext.apply(ctx, changes);
178 // Fill the area if we need to, using the fill color and transparent strokes.
179 if (attr.fillArea) {
180 saveOpacity = ctx.strokeOpacity;
181 ctx.save();
182 ctx.strokeOpacity = 0;
183 if (step) {
184 ctx.lineTo(x, y0);
185 } else {
186 ctx.lineTo(x, y);
187 }
188 ctx.lineTo(x, xAxis);
189 ctx.lineTo(x0, xAxis);
190 ctx.lineTo(x0, y0);
191 ctx.closePath();
192 ctx.fillStroke(attr, true);
193 ctx.restore();
194 ctx.strokeOpacity = saveOpacity;
195 ctx.beginPath();
196 }
197 // Draw the line (or the 2 lines if 'step') on top of the filled area.
198 ctx.moveTo(x0, y0);
199 if (step) {
200 ctx.lineTo(x, y0);
201 ctx.closePath();
202 ctx.stroke();
203 ctx.beginPath();
204 ctx.moveTo(x, y0);
205 }
206 ctx.lineTo(x, y);
207 ctx.closePath();
208 ctx.stroke();
209 ctx.restore();
210 ctx.beginPath();
211 ctx.moveTo(x, y);
212 } else {
213 if (step) {
214 ctx.lineTo(x, y0);
215 }
216 ctx.lineTo(x, y);
217 }
218 }
219 }
220 },
221
222 drawLabel: function (text, dataX, dataY, labelId, region) {
223 var me = this,
224 attr = me.attr,
225 label = me.getBoundMarker('labels')[0],
226 labelTpl = label.getTemplate(),
227 labelCfg = me.labelCfg || (me.labelCfg = {}),
228 surfaceMatrix = me.surfaceMatrix,
229 labelX, labelY,
230 labelOverflowPadding = attr.labelOverflowPadding,
231 halfWidth, halfHeight,
232 labelBox,
233 changes;
234
235 labelCfg.text = text;
236
237 labelBox = this.getMarkerBBox('labels', labelId, true);
238 if (!labelBox) {
239 me.putMarker('labels', labelCfg, labelId);
240 labelBox = this.getMarkerBBox('labels', labelId, true);
241 }
242
243 if (attr.flipXY) {
244 labelCfg.rotationRads = Math.PI * 0.5;
245 } else {
246 labelCfg.rotationRads = 0;
247 }
248
249 halfWidth = labelBox.width / 2;
250 halfHeight = labelBox.height / 2;
251
252 labelX = dataX;
253 if (labelTpl.attr.display === 'over') {
254 labelY = dataY + halfHeight + labelOverflowPadding;
255 } else {
256 labelY = dataY - halfHeight - labelOverflowPadding;
257 }
258
259 if (labelX <= region[0] + halfWidth) {
260 labelX = region[0] + halfWidth;
261 } else if (labelX >= region[2] - halfWidth) {
262 labelX = region[2] - halfWidth;
263 }
264
265 if (labelY <= region[1] + halfHeight) {
266 labelY = region[1] + halfHeight;
267 } else if (labelY >= region[3] - halfHeight) {
268 labelY = region[3] - halfHeight;
269 }
270
271 labelCfg.x = surfaceMatrix.x(labelX, labelY);
272 labelCfg.y = surfaceMatrix.y(labelX, labelY);
273
274 if (labelTpl.attr.renderer) {
275 changes = labelTpl.attr.renderer.call(this, text, label, labelCfg, {store: this.getStore()}, labelId);
276 if (typeof changes === 'string') {
277 labelCfg.text = changes;
278 } else {
279 Ext.apply(labelCfg, changes);
280 }
281 }
282
283 me.putMarker('labels', labelCfg, labelId);
284 },
285
286 renderAggregates: function (aggregates, start, end, surface, ctx, clip, region) {
287 var me = this,
288 attr = me.attr,
289 dataX = attr.dataX,
290 dataY = attr.dataY,
291 labels = attr.labels,
292 drawLabels = labels && !!me.getBoundMarker('labels'),
293 matrix = attr.matrix,
294 surfaceMatrix = surface.matrix,
295 pixel = surface.devicePixelRatio,
296 xx = matrix.getXX(),
297 yy = matrix.getYY(),
298 dx = matrix.getDX(),
299 dy = matrix.getDY(),
300 markerCfg = {},
301 list = this.list || (this.list = []),
302 x, y, i, index,
303 minXs = aggregates.minX,
304 maxXs = aggregates.maxX,
305 minYs = aggregates.minY,
306 maxYs = aggregates.maxY,
307 idx = aggregates.startIdx;
308
309 list.length = 0;
310 for (i = start; i < end; i++) {
311 var minX = minXs[i],
312 maxX = maxXs[i],
313 minY = minYs[i],
314 maxY = maxYs[i];
315
316 if (minX < maxX) {
317 list.push(minX * xx + dx, minY * yy + dy, idx[i]);
318 list.push(maxX * xx + dx, maxY * yy + dy, idx[i]);
319 } else if (minX > maxX) {
320 list.push(maxX * xx + dx, maxY * yy + dy, idx[i]);
321 list.push(minX * xx + dx, minY * yy + dy, idx[i]);
322 } else {
323 list.push(maxX * xx + dx, maxY * yy + dy, idx[i]);
324 }
325 }
326
327 if (list.length) {
328 for (i = 0; i < list.length; i += 3) {
329 x = list[i];
330 y = list[i + 1];
331 index = list[i + 2];
332 if (attr.renderer) {
333 markerCfg = {
334 type: 'marker',
335 x: x,
336 y: y
337 };
338 markerCfg = attr.renderer.call(this, this, markerCfg, {store:this.getStore()}, i/3) || {};
339 }
340 markerCfg.translationX = surfaceMatrix.x(x, y);
341 markerCfg.translationY = surfaceMatrix.y(x, y);
342 me.putMarker('markers', markerCfg, index, !attr.renderer);
343
344 if (drawLabels && labels[index]) {
345 me.drawLabel(labels[index], x, y, index, region);
346 }
347 }
348 me.drawStroke(surface, ctx, start, end, list, region[1] - pixel);
349 if (!attr.renderer) {
350 var lastPointX = dataX[dataX.length - 1] * xx + dx + pixel,
351 lastPointY = dataY[dataY.length - 1] * yy + dy,
352 bottomY = region[1] - pixel,
353 firstPointX = dataX[0] * xx + dx - pixel,
354 firstPointY = dataY[0] * yy + dy;
355 ctx.lineTo(lastPointX, lastPointY);
356 ctx.lineTo(lastPointX, bottomY);
357 ctx.lineTo(firstPointX, bottomY);
358 ctx.lineTo(firstPointX, firstPointY);
359 }
360 ctx.closePath();
361
362 if (attr.transformFillStroke) {
363 attr.matrix.toContext(ctx);
364 }
365 if (attr.preciseStroke) {
366 if (attr.fillArea) {
367 ctx.fill();
368 }
369 if (attr.transformFillStroke) {
370 attr.inverseMatrix.toContext(ctx);
371 }
372 me.drawStroke(surface, ctx, start, end, list, region[1] - pixel);
373 if (attr.transformFillStroke) {
374 attr.matrix.toContext(ctx);
375 }
376 ctx.stroke();
377 } else {
378 // Prevent the reverse transform to fix floating point err.
379 if (attr.fillArea) {
380 ctx.fillStroke(attr, true);
381 } else {
382 ctx.stroke(true);
383 }
384 }
385 }
386 }
387 });