]> git.proxmox.com Git - extjs.git/blame - extjs/packages/charts/src/chart/CartesianChart.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / charts / src / chart / CartesianChart.js
CommitLineData
6527f429
DM
1/**\r
2 * @class Ext.chart.CartesianChart\r
3 * @extends Ext.chart.AbstractChart\r
4 * @xtype cartesian\r
5 *\r
6 * Represents a chart that uses cartesian coordinates.\r
7 * A cartesian chart has two directions, X direction and Y direction.\r
8 * The series and axes are coordinated along these directions.\r
9 * By default the x direction is horizontal and y direction is vertical,\r
10 * You can swap the direction by setting the {@link #flipXY} config to `true`.\r
11 *\r
12 * Cartesian series often treats x direction an y direction differently.\r
13 * In most cases, data on x direction are assumed to be monotonically increasing.\r
14 * Based on this property, cartesian series can be trimmed and summarized properly\r
15 * to gain a better performance.\r
16 *\r
17 */\r
18\r
19Ext.define('Ext.chart.CartesianChart', {\r
20 extend: 'Ext.chart.AbstractChart',\r
21 alternateClassName: 'Ext.chart.Chart',\r
22 requires: [\r
23 'Ext.chart.grid.HorizontalGrid',\r
24 'Ext.chart.grid.VerticalGrid'\r
25 ],\r
26 xtype: [ 'cartesian', 'chart' ],\r
27 isCartesian: true,\r
28\r
29 config: {\r
30 /**\r
31 * @cfg {Boolean} flipXY Flip the direction of X and Y axis.\r
32 * If flipXY is `true`, the X axes will be vertical and Y axes will be horizontal.\r
33 * Note that {@link Ext.chart.axis.Axis#position positions} of chart axes have\r
34 * to be updated accordingly: axes positioned to the `top` and `bottom` should\r
35 * be positioned to the `left` or `right` and vice versa.\r
36 */\r
37 flipXY: false,\r
38 /*\r
39\r
40 While it may seem tedious to change the position config of all axes every time\r
41 when the value of the flipXY config is changed, it's hard to predict the\r
42 expectaction of the user here, as illustrated below.\r
43\r
44 The 'num' and 'cat' here stand for the numeric and the category axis, respectively.\r
45 And the right column shows the expected (subjective) result of setting the flipXY\r
46 config of the chart to 'true'.\r
47\r
48 As one can see, there's no single rule (e.g. position swapping, clockwise 90° chart\r
49 rotation) that will produce a universally accepted result.\r
50 So we are letting the user decide, instead of doing it for them.\r
51\r
52 ---------------------------------------------\r
53 | flipXY: false | flipXY: true |\r
54 ---------------------------------------------\r
55 | ^ | ^ |\r
56 | | * | | * * * |\r
57 | num1 | * * | cat | * * |\r
58 | | * * * | | * |\r
59 | --------> | --------> |\r
60 | cat | num1 |\r
61 ---------------------------------------------\r
62 | | num1 |\r
63 | ^ ^ | ^-------> |\r
64 | | * | | | * * * |\r
65 | num1 | * * | num2 | cat | * * |\r
66 | | * * * | | | * |\r
67 | --------> | --------> |\r
68 | cat | num2 |\r
69 ---------------------------------------------\r
70\r
71 */\r
72\r
73 innerRect: [0, 0, 1, 1],\r
74\r
75 /**\r
76 * @cfg {Object} innerPadding The amount of inner padding in pixels.\r
77 * Inner padding is the padding from the innermost axes to the series.\r
78 */\r
79 innerPadding: {\r
80 top: 0,\r
81 left: 0,\r
82 right: 0,\r
83 bottom: 0\r
84 }\r
85 },\r
86\r
87 applyInnerPadding: function (padding, oldPadding) {\r
88 if (!Ext.isObject(padding)) {\r
89 return Ext.util.Format.parseBox(padding);\r
90 } else if (!oldPadding) {\r
91 return padding;\r
92 } else {\r
93 return Ext.apply(oldPadding, padding);\r
94 }\r
95 },\r
96\r
97 getDirectionForAxis: function (position) {\r
98 var flipXY = this.getFlipXY();\r
99 if (position === 'left' || position === 'right') {\r
100 if (flipXY) {\r
101 return 'X';\r
102 } else {\r
103 return 'Y';\r
104 }\r
105 } else {\r
106 if (flipXY) {\r
107 return 'Y';\r
108 } else {\r
109 return 'X';\r
110 }\r
111 }\r
112 },\r
113\r
114 /**\r
115 * Layout the axes and series.\r
116 */\r
117 performLayout: function () {\r
118 var me = this;\r
119\r
120 me.animationSuspendCount++;\r
121 if (me.callParent() === false) {\r
122 --me.animationSuspendCount;\r
123 return;\r
124 }\r
125 me.suspendThicknessChanged();\r
126\r
127 var chartRect = me.getSurface('chart').getRect(),\r
128 width = chartRect[2],\r
129 height = chartRect[3],\r
130 axes = me.getAxes(), axis,\r
131 seriesList = me.getSeries(), series,\r
132 axisSurface, thickness,\r
133 insetPadding = me.getInsetPadding(),\r
134 innerPadding = me.getInnerPadding(),\r
135 surface, gridSurface,\r
136 shrinkBox = Ext.apply({}, insetPadding),\r
137 mainRect, innerWidth, innerHeight,\r
138 elements, floating, floatingValue, matrix, i, ln,\r
139 isRtl = me.getInherited().rtl,\r
140 flipXY = me.getFlipXY();\r
141\r
142 if (width <= 0 || height <= 0) {\r
143 return;\r
144 }\r
145\r
146 for (i = 0; i < axes.length; i++) {\r
147 axis = axes[i];\r
148 axisSurface = axis.getSurface();\r
149 floating = axis.getFloating();\r
150 floatingValue = floating ? floating.value : null;\r
151 thickness = axis.getThickness();\r
152 switch (axis.getPosition()) {\r
153 case 'top':\r
154 axisSurface.setRect([0, shrinkBox.top + 1, width, thickness]);\r
155 break;\r
156 case 'bottom':\r
157 axisSurface.setRect([0, height - (shrinkBox.bottom + thickness), width, thickness]);\r
158 break;\r
159 case 'left':\r
160 axisSurface.setRect([shrinkBox.left, 0, thickness, height]);\r
161 break;\r
162 case 'right':\r
163 axisSurface.setRect([width - (shrinkBox.right + thickness), 0, thickness, height]);\r
164 break;\r
165 }\r
166 if (floatingValue === null) {\r
167 shrinkBox[axis.getPosition()] += thickness;\r
168 }\r
169 }\r
170\r
171 width -= shrinkBox.left + shrinkBox.right;\r
172 height -= shrinkBox.top + shrinkBox.bottom;\r
173\r
174 mainRect = [shrinkBox.left, shrinkBox.top, width, height];\r
175\r
176 shrinkBox.left += innerPadding.left;\r
177 shrinkBox.top += innerPadding.top;\r
178 shrinkBox.right += innerPadding.right;\r
179 shrinkBox.bottom += innerPadding.bottom;\r
180\r
181 innerWidth = width - innerPadding.left - innerPadding.right;\r
182 innerHeight = height - innerPadding.top - innerPadding.bottom;\r
183\r
184 me.setInnerRect([shrinkBox.left, shrinkBox.top, innerWidth, innerHeight]);\r
185\r
186 if (innerWidth <= 0 || innerHeight <= 0) {\r
187 return;\r
188 }\r
189\r
190 me.setMainRect(mainRect);\r
191 me.getSurface().setRect(mainRect);\r
192\r
193 for (i = 0, ln = me.surfaceMap.grid && me.surfaceMap.grid.length; i < ln; i++) {\r
194 gridSurface = me.surfaceMap.grid[i];\r
195 gridSurface.setRect(mainRect);\r
196 gridSurface.matrix.set(1, 0, 0, 1, innerPadding.left, innerPadding.top);\r
197 gridSurface.matrix.inverse(gridSurface.inverseMatrix);\r
198 }\r
199\r
200 for (i = 0; i < axes.length; i++) {\r
201 axis = axes[i];\r
202 axisSurface = axis.getSurface();\r
203 matrix = axisSurface.matrix;\r
204 elements = matrix.elements;\r
205 switch (axis.getPosition()) {\r
206 case 'top':\r
207 case 'bottom':\r
208 elements[4] = shrinkBox.left;\r
209 axis.setLength(innerWidth);\r
210 break;\r
211 case 'left':\r
212 case 'right':\r
213 elements[5] = shrinkBox.top;\r
214 axis.setLength(innerHeight);\r
215 break;\r
216 }\r
217 axis.updateTitleSprite();\r
218 matrix.inverse(axisSurface.inverseMatrix);\r
219 }\r
220\r
221 for (i = 0, ln = seriesList.length; i < ln; i++) {\r
222 series = seriesList[i];\r
223 surface = series.getSurface();\r
224 surface.setRect(mainRect);\r
225 if (flipXY) {\r
226 if (isRtl) {\r
227 surface.matrix.set(0, -1, -1, 0,\r
228 innerPadding.left + innerWidth,\r
229 innerPadding.top + innerHeight);\r
230 } else {\r
231 surface.matrix.set(0, -1, 1, 0,\r
232 innerPadding.left,\r
233 innerPadding.top + innerHeight);\r
234 }\r
235 } else {\r
236 surface.matrix.set(1, 0, 0, -1,\r
237 innerPadding.left,\r
238 innerPadding.top + innerHeight);\r
239 }\r
240 surface.matrix.inverse(surface.inverseMatrix);\r
241 series.getOverlaySurface().setRect(mainRect);\r
242 }\r
243 me.redraw();\r
244\r
245 me.animationSuspendCount--;\r
246 me.resumeThicknessChanged();\r
247 },\r
248\r
249 refloatAxes: function () {\r
250 var me = this,\r
251 axes = me.getAxes(),\r
252 axesCount = (axes && axes.length) || 0,\r
253 axis, axisSurface, axisRect,\r
254 floating, value, alongAxis, matrix,\r
255 size = me.getChartSize(),\r
256 inset = me.getInsetPadding(),\r
257 inner = me.getInnerPadding(),\r
258 width = size.width - inset.left - inset.right,\r
259 height = size.height - inset.top - inset.bottom,\r
260 isHorizontal, i;\r
261\r
262 for (i = 0; i < axesCount; i++) {\r
263 axis = axes[i];\r
264 floating = axis.getFloating();\r
265 value = floating ? floating.value : null;\r
266 if (value === null) {\r
267 delete axis.floatingAtCoord;\r
268 continue;\r
269 }\r
270 axisSurface = axis.getSurface();\r
271 axisRect = axisSurface.getRect();\r
272 if (!axisRect) {\r
273 continue;\r
274 }\r
275 axisRect = axisRect.slice();\r
276 alongAxis = me.getAxis(floating.alongAxis);\r
277 if (alongAxis) {\r
278 isHorizontal = alongAxis.getAlignment() === 'horizontal';\r
279 if (Ext.isString(value)) {\r
280 value = alongAxis.getCoordFor(value);\r
281 }\r
282 alongAxis.floatingAxes[axis.getId()] = value;\r
283 matrix = alongAxis.getSprites()[0].attr.matrix;\r
284 if (isHorizontal) {\r
285 value = value * matrix.getXX() + matrix.getDX();\r
286 axis.floatingAtCoord = value + inner.left + inner.right;\r
287 } else {\r
288 value = value * matrix.getYY() + matrix.getDY();\r
289 axis.floatingAtCoord = value + inner.top + inner.bottom;\r
290 }\r
291 } else {\r
292 isHorizontal = axis.getAlignment() === 'horizontal';\r
293 if (isHorizontal) {\r
294 axis.floatingAtCoord = value + inner.top + inner.bottom;\r
295 } else {\r
296 axis.floatingAtCoord = value + inner.left + inner.right;\r
297 }\r
298 value = axisSurface.roundPixel(0.01 * value * (isHorizontal ? height : width));\r
299 }\r
300 switch (axis.getPosition()) {\r
301 case 'top':\r
302 axisRect[1] = inset.top + inner.top + value - axisRect[3] + 1;\r
303 break;\r
304 case 'bottom':\r
305 axisRect[1] = inset.top + inner.top + (alongAxis ? value : height - value);\r
306 break;\r
307 case 'left':\r
308 axisRect[0] = inset.left + inner.left + value - axisRect[2];\r
309 break;\r
310 case 'right':\r
311 axisRect[0] = inset.left + inner.left + (alongAxis ? value : width - value) - 1;\r
312 break;\r
313 }\r
314 axisSurface.setRect(axisRect);\r
315 }\r
316 },\r
317\r
318 redraw: function () {\r
319 var me = this,\r
320 seriesList = me.getSeries(),\r
321 axes = me.getAxes(),\r
322 rect = me.getMainRect(),\r
323 innerWidth, innerHeight,\r
324 innerPadding = me.getInnerPadding(),\r
325 sprites, xRange, yRange, isSide, attr, i, j, ln,\r
326 axis, axisX, axisY, range, visibleRange,\r
327 flipXY = me.getFlipXY(),\r
328 zBase = 1000,\r
329 zIndex, markersZIndex,\r
330 series, sprite, markers;\r
331\r
332 if (!rect) {\r
333 return;\r
334 }\r
335\r
336 innerWidth = rect[2] - innerPadding.left - innerPadding.right;\r
337 innerHeight = rect[3] - innerPadding.top - innerPadding.bottom;\r
338\r
339 for (i = 0; i < seriesList.length; i++) {\r
340 series = seriesList[i];\r
341 if ((axisX = series.getXAxis())) {\r
342 visibleRange = axisX.getVisibleRange();\r
343 xRange = axisX.getRange();\r
344 xRange = [\r
345 xRange[0] + (xRange[1] - xRange[0]) * visibleRange[0],\r
346 xRange[0] + (xRange[1] - xRange[0]) * visibleRange[1]\r
347 ];\r
348 } else {\r
349 xRange = series.getXRange();\r
350 }\r
351\r
352 if ((axisY = series.getYAxis())) {\r
353 visibleRange = axisY.getVisibleRange();\r
354 yRange = axisY.getRange();\r
355 yRange = [\r
356 yRange[0] + (yRange[1] - yRange[0]) * visibleRange[0],\r
357 yRange[0] + (yRange[1] - yRange[0]) * visibleRange[1]\r
358 ];\r
359 } else {\r
360 yRange = series.getYRange();\r
361 }\r
362\r
363 attr = {\r
364 visibleMinX: xRange[0],\r
365 visibleMaxX: xRange[1],\r
366 visibleMinY: yRange[0],\r
367 visibleMaxY: yRange[1],\r
368 innerWidth: innerWidth,\r
369 innerHeight: innerHeight,\r
370 flipXY: flipXY\r
371 };\r
372\r
373 sprites = series.getSprites();\r
374 for (j = 0, ln = sprites.length; j < ln; j++) {\r
375\r
376 // All the series now share the same surface, so we must assign\r
377 // the sprites a zIndex that depends on the index of their series.\r
378 sprite = sprites[j];\r
379 zIndex = sprite.attr.zIndex;\r
380 if (zIndex < zBase) {\r
381 // Set the sprite's zIndex\r
382 zIndex += (i + 1) * 100 + zBase;\r
383 sprite.attr.zIndex = zIndex;\r
384 // If the sprite is a MarkerHolder, set zIndex of the bound markers as well.\r
385 // Do this for the 'items' markers only, as those are the only ones\r
386 // that go into the 'series' surface. 'labels' and 'markers' markers\r
387 // go into the 'overlay' surface instead.\r
388 markers = sprite.getMarker('items');\r
389 if (markers) {\r
390 markersZIndex = markers.attr.zIndex;\r
391 if (markersZIndex === Number.MAX_VALUE) {\r
392 markers.attr.zIndex = zIndex;\r
393 } else if (markersZIndex < zBase) {\r
394 markers.attr.zIndex = zIndex + markersZIndex;\r
395 }\r
396 }\r
397 }\r
398\r
399 sprite.setAttributes(attr, true);\r
400 }\r
401 }\r
402\r
403 for (i = 0; i < axes.length; i++) {\r
404 axis = axes[i];\r
405 isSide = axis.isSide();\r
406 sprites = axis.getSprites();\r
407 range = axis.getRange();\r
408 visibleRange = axis.getVisibleRange();\r
409 attr = {\r
410 dataMin: range[0],\r
411 dataMax: range[1],\r
412 visibleMin: visibleRange[0],\r
413 visibleMax: visibleRange[1]\r
414 };\r
415 if (isSide) {\r
416 attr.length = innerHeight;\r
417 attr.startGap = innerPadding.bottom;\r
418 attr.endGap = innerPadding.top;\r
419 } else {\r
420 attr.length = innerWidth;\r
421 attr.startGap = innerPadding.left;\r
422 attr.endGap = innerPadding.right;\r
423 }\r
424 for (j = 0, ln = sprites.length; j < ln; j++) {\r
425 sprites[j].setAttributes(attr, true);\r
426 }\r
427 }\r
428 me.renderFrame();\r
429 me.callParent(arguments);\r
430 },\r
431\r
432 renderFrame: function () {\r
433 this.refloatAxes();\r
434 this.callParent();\r
435 }\r
436});\r