4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-chart-axis-Axis'>/**
19 </span> * @class Ext.chart.axis.Axis
20 * @extends Ext.chart.axis.Abstract
22 * Defines axis for charts. The axis position, type, style can be configured.
23 * The axes are defined in an axes array of configuration objects where the type,
24 * field, grid and other configuration options can be set. To know more about how
25 * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
26 * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
32 * fields: ['data1', 'data2', 'data3'],
33 * title: 'Number of Hits',
47 * title: 'Month of the Year',
56 * In this case we use a `Numeric` axis for displaying the values of the Area series and a `Category` axis for displaying the names of
57 * the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart.
58 * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
59 * category axis the labels will be rotated so they can fit the space better.
61 Ext.define('Ext.chart.axis.Axis', {
63 /* Begin Definitions */
65 extend: 'Ext.chart.axis.Abstract',
67 alternateClassName: 'Ext.chart.Axis',
69 requires: ['Ext.draw.Draw'],
73 <span id='Ext-chart-axis-Axis-cfg-grid'> /**
74 </span> * @cfg {Boolean/Object} grid
75 * The grid configuration enables you to set a background grid for an axis.
76 * If set to *true* on a vertical axis, vertical lines will be drawn.
77 * If set to *true* on a horizontal axis, horizontal lines will be drawn.
78 * If both are set, a proper grid with horizontal and vertical lines will be drawn.
80 * You can set specific options for the grid configuration for odd and/or even lines/rows.
81 * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
82 * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
83 * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
85 * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
86 * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
94 * fields: ['data1', 'data2', 'data3'],
95 * title: 'Number of Hits',
106 * position: 'bottom',
108 * title: 'Month of the Year',
114 <span id='Ext-chart-axis-Axis-cfg-majorTickSteps'> /**
115 </span> * @cfg {Number} majorTickSteps
116 * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
119 <span id='Ext-chart-axis-Axis-cfg-minorTickSteps'> /**
120 </span> * @cfg {Number} minorTickSteps
121 * The number of small ticks between two major ticks. Default is zero.
124 <span id='Ext-chart-axis-Axis-cfg-title'> /**
125 </span> * @cfg {String} title
126 * The title for the Axis
129 //@private force min/max values from store
132 <span id='Ext-chart-axis-Axis-cfg-dashSize'> /**
133 </span> * @cfg {Number} dashSize
134 * The size of the dash marker. Default's 3.
138 <span id='Ext-chart-axis-Axis-cfg-position'> /**
139 </span> * @cfg {String} position
140 * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
147 <span id='Ext-chart-axis-Axis-cfg-length'> /**
148 </span> * @cfg {Number} length
149 * Offset axis position. Default's 0.
153 <span id='Ext-chart-axis-Axis-cfg-width'> /**
154 </span> * @cfg {Number} width
155 * Offset axis width. Default's 0.
159 majorTickSteps: false,
162 applyData: Ext.emptyFn,
164 getRange: function () {
166 store = me.chart.getChartStore(),
173 min = isNaN(me.minimum) ? Infinity : me.minimum,
174 max = isNaN(me.maximum) ? -Infinity : me.maximum,
175 total = 0, i, l, value, values, rec,
177 series = me.chart.series.items;
179 //if one series is stacked I have to aggregate the values
181 // TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
182 // listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
183 // Assuming only stack on y-axis.
184 for (i = 0, l = series.length; !aggregate && i < l; i++) {
185 aggregate = aggregate || (me.position == 'left' || me.position == 'right') && series[i].stacked;
186 excludes = series[i].__excludes || excludes;
188 store.each(function(record) {
190 if (!isFinite(min)) {
193 for (values = [0, 0], i = 0; i < ln; i++) {
197 rec = record.get(fields[i]);
198 values[+(rec > 0)] += math.abs(rec);
200 max = mmax(max, -values[0], +values[1]);
201 min = mmin(min, -values[0], +values[1]);
204 for (i = 0; i < ln; i++) {
208 value = record.get(fields[i]);
209 max = mmax(max, +value);
210 min = mmin(min, +value);
214 if (!isFinite(max)) {
215 max = me.prevMax || 0;
217 if (!isFinite(min)) {
218 min = me.prevMin || 0;
220 //normalize min max for snapEnds.
221 if (min != max && (max != Math.floor(max))) {
222 max = Math.floor(max) + 1;
225 if (!isNaN(me.minimum)) {
229 if (!isNaN(me.maximum)) {
233 return {min: min, max: max};
236 // @private creates a structure with start, end and step points.
237 calcEnds: function() {
240 range = me.getRange(),
245 out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ? (me.majorTickSteps +1) : me.steps);
248 if (me.forceMinMax) {
256 if (!isNaN(me.maximum)) {
257 //TODO(nico) users are responsible for their own minimum/maximum values set.
258 //Clipping should be added to remove lines in the chart which are below the axis.
261 if (!isNaN(me.minimum)) {
262 //TODO(nico) users are responsible for their own minimum/maximum values set.
263 //Clipping should be added to remove lines in the chart which are below the axis.
264 out.from = me.minimum;
267 //Adjust after adjusting minimum and maximum
268 out.step = (out.to - out.from) / (outto - outfrom) * out.step;
270 if (me.adjustMaximumByMajorUnit) {
273 if (me.adjustMinimumByMajorUnit) {
274 out.from -= out.step;
276 me.prevMin = min == max? 0 : min;
281 <span id='Ext-chart-axis-Axis-method-drawAxis'> /**
282 </span> * Renders the axis into the screen and updates its position.
284 drawAxis: function (init) {
289 gutterX = me.chart.maxGutter[0],
290 gutterY = me.chart.maxGutter[1],
291 dashSize = me.dashSize,
292 subDashesX = me.minorTickSteps || 0,
293 subDashesY = me.minorTickSteps || 0,
295 position = me.position,
298 stepCalcs = me.applyData(),
299 step = stepCalcs.step,
300 steps = stepCalcs.steps,
301 from = stepCalcs.from,
312 //If no steps are specified
313 //then don't draw the axis. This generally happens
314 //when an empty store.
315 if (me.hidden || isNaN(step) || (from == to)) {
319 me.from = stepCalcs.from;
320 me.to = stepCalcs.to;
321 if (position == 'left' || position == 'right') {
322 currentX = Math.floor(x) + 0.5;
323 path = ["M", currentX, y, "l", 0, -length];
324 trueLength = length - (gutterY * 2);
327 currentY = Math.floor(y) + 0.5;
328 path = ["M", x, currentY, "l", length, 0];
329 trueLength = length - (gutterX * 2);
332 delta = trueLength / (steps || 1);
333 dashesX = Math.max(subDashesX +1, 0);
334 dashesY = Math.max(subDashesY +1, 0);
335 if (me.type == 'Numeric' || me.type == 'Time') {
337 me.labels = [stepCalcs.from];
339 if (position == 'right' || position == 'left') {
340 currentY = y - gutterY;
341 currentX = x - ((position == 'left') * dashSize * 2);
342 while (currentY >= y - gutterY - trueLength) {
343 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
344 if (currentY != y - gutterY) {
345 for (i = 1; i < dashesY; i++) {
346 path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
349 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
352 me.labels.push(me.labels[me.labels.length -1] + step);
358 if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
359 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
360 for (i = 1; i < dashesY; i++) {
361 path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
363 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
365 me.labels.push(me.labels[me.labels.length -1] + step);
369 currentX = x + gutterX;
370 currentY = y - ((position == 'top') * dashSize * 2);
371 while (currentX <= x + gutterX + trueLength) {
372 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
373 if (currentX != x + gutterX) {
374 for (i = 1; i < dashesX; i++) {
375 path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
378 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
381 me.labels.push(me.labels[me.labels.length -1] + step);
387 if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
388 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
389 for (i = 1; i < dashesX; i++) {
390 path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
392 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
394 me.labels.push(me.labels[me.labels.length -1] + step);
399 me.axis = me.chart.surface.add(Ext.apply({
404 me.axis.setAttributes({
407 me.inflections = inflections;
408 if (!init && me.grid) {
411 me.axisBBox = me.axis.getBBox();
415 <span id='Ext-chart-axis-Axis-method-drawGrid'> /**
416 </span> * Renders an horizontal and/or vertical grid into the Surface.
418 drawGrid: function() {
420 surface = me.chart.surface,
424 inflections = me.inflections,
425 ln = inflections.length - ((odd || even)? 0 : 1),
426 position = me.position,
427 gutter = me.chart.maxGutter,
428 width = me.width - 2,
432 path = [], styles, lineWidth, dlineWidth,
433 oddPath = [], evenPath = [];
435 if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
436 (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
440 for (; i < ln; i++) {
441 point = inflections[i];
442 prevPoint = inflections[i - 1];
444 path = (i % 2)? oddPath : evenPath;
445 styles = ((i % 2)? odd : even) || {};
446 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
447 dlineWidth = 2 * lineWidth;
448 if (position == 'left') {
449 path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
450 "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
451 "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
452 "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
454 else if (position == 'right') {
455 path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
456 "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
457 "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
458 "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
460 else if (position == 'top') {
461 path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
462 "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
463 "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
464 "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
467 path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
468 "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
469 "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
470 "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
473 if (position == 'left') {
474 path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
476 else if (position == 'right') {
477 path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
479 else if (position == 'top') {
480 path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
483 path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
488 if (oddPath.length) {
489 if (!me.gridOdd && oddPath.length) {
490 me.gridOdd = surface.add({
495 me.gridOdd.setAttributes(Ext.apply({
498 }, odd || {}), true);
500 if (evenPath.length) {
502 me.gridEven = surface.add({
507 me.gridEven.setAttributes(Ext.apply({
510 }, even || {}), true);
516 me.gridLines = me.chart.surface.add({
519 "stroke-width": me.lineWidth || 1,
520 stroke: me.gridColor || '#ccc'
523 me.gridLines.setAttributes({
528 else if (me.gridLines) {
529 me.gridLines.hide(true);
535 getOrCreateLabel: function(i, text) {
537 labelGroup = me.labelGroup,
538 textLabel = labelGroup.getAt(i),
539 surface = me.chart.surface;
541 if (text != textLabel.attr.text) {
542 textLabel.setAttributes(Ext.apply({
545 textLabel._bbox = textLabel.getBBox();
549 textLabel = surface.add(Ext.apply({
556 surface.renderItem(textLabel);
557 textLabel._bbox = textLabel.getBBox();
559 //get untransformed bounding box
560 if (me.label.rotation) {
561 textLabel.setAttributes({
566 textLabel._ubbox = textLabel.getBBox();
567 textLabel.setAttributes(me.label, true);
569 textLabel._ubbox = textLabel._bbox;
574 rect2pointArray: function(sprite) {
575 var surface = this.chart.surface,
576 rect = surface.getBBox(sprite, true),
577 p1 = [rect.x, rect.y],
579 p2 = [rect.x + rect.width, rect.y],
581 p3 = [rect.x + rect.width, rect.y + rect.height],
583 p4 = [rect.x, rect.y + rect.height],
585 matrix = sprite.matrix;
586 //transform the points
587 p1[0] = matrix.x.apply(matrix, p1p);
588 p1[1] = matrix.y.apply(matrix, p1p);
590 p2[0] = matrix.x.apply(matrix, p2p);
591 p2[1] = matrix.y.apply(matrix, p2p);
593 p3[0] = matrix.x.apply(matrix, p3p);
594 p3[1] = matrix.y.apply(matrix, p3p);
596 p4[0] = matrix.x.apply(matrix, p4p);
597 p4[1] = matrix.y.apply(matrix, p4p);
598 return [p1, p2, p3, p4];
601 intersect: function(l1, l2) {
602 var r1 = this.rect2pointArray(l1),
603 r2 = this.rect2pointArray(l2);
604 return !!Ext.draw.Draw.intersect(r1, r2).length;
607 drawHorizontalLabels: function() {
609 labelConf = me.label,
612 axes = me.chart.axes,
613 position = me.position,
614 inflections = me.inflections,
615 ln = inflections.length,
617 labelGroup = me.labelGroup,
620 gutterY = me.chart.maxGutter[1],
621 ubbox, bbox, point, prevX, prevLabel,
623 textLabel, attr, textRight, text,
624 label, last, x, y, i, firstLabel;
627 //get a reference to the first text label dimensions
628 point = inflections[0];
629 firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
630 ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
632 for (i = 0; i < ln; i++) {
633 point = inflections[i];
634 text = me.label.renderer(labels[i]);
635 textLabel = me.getOrCreateLabel(i, text);
636 bbox = textLabel._bbox;
637 maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
638 x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
639 if (me.chart.maxGutter[0] == 0) {
640 if (i == 0 && axes.findIndex('position', 'left') == -1) {
643 else if (i == last && axes.findIndex('position', 'right') == -1) {
644 x = point[0] - bbox.width;
647 if (position == 'top') {
648 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
651 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
654 textLabel.setAttributes({
660 // Skip label if there isn't available minimum space
661 if (i != 0 && (me.intersect(textLabel, prevLabel)
662 || me.intersect(textLabel, firstLabel))) {
663 textLabel.hide(true);
667 prevLabel = textLabel;
673 drawVerticalLabels: function() {
675 inflections = me.inflections,
676 position = me.position,
677 ln = inflections.length,
683 axes = me.chart.axes,
684 gutterY = me.chart.maxGutter[1],
685 ubbox, bbox, point, prevLabel,
687 textLabel, attr, textRight, text,
688 label, last, x, y, i;
691 for (i = 0; i < last; i++) {
692 point = inflections[i];
693 text = me.label.renderer(labels[i]);
694 textLabel = me.getOrCreateLabel(i, text);
695 bbox = textLabel._bbox;
697 maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
699 if (gutterY < bbox.height / 2) {
700 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
701 y = me.y - me.length + ceil(bbox.height / 2);
703 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
704 y = me.y - floor(bbox.height / 2);
707 if (position == 'left') {
708 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
711 x = point[0] + me.dashSize + me.label.padding + 2;
713 textLabel.setAttributes(Ext.apply({
718 // Skip label if there isn't available minimum space
719 if (i != 0 && me.intersect(textLabel, prevLabel)) {
720 textLabel.hide(true);
723 prevLabel = textLabel;
729 <span id='Ext-chart-axis-Axis-method-drawLabel'> /**
730 </span> * Renders the labels in the axes.
732 drawLabel: function() {
734 position = me.position,
735 labelGroup = me.labelGroup,
736 inflections = me.inflections,
741 if (position == 'left' || position == 'right') {
742 maxWidth = me.drawVerticalLabels();
744 maxHeight = me.drawHorizontalLabels();
748 ln = labelGroup.getCount();
749 i = inflections.length;
750 for (; i < ln; i++) {
751 labelGroup.getAt(i).hide(true);
755 Ext.apply(me.bbox, me.axisBBox);
756 me.bbox.height = maxHeight;
757 me.bbox.width = maxWidth;
758 if (Ext.isString(me.title)) {
759 me.drawTitle(maxWidth, maxHeight);
763 // @private creates the elipsis for the text.
764 elipsis: function(sprite, text, desiredWidth, minWidth, center) {
768 if (desiredWidth < minWidth) {
772 while (text.length > 4) {
773 text = text.substr(0, text.length - 4) + "...";
774 sprite.setAttributes({
777 bbox = sprite.getBBox();
778 if (bbox.width < desiredWidth) {
779 if (typeof center == 'number') {
780 sprite.setAttributes({
781 x: Math.floor(center - (bbox.width / 2))
790 <span id='Ext-chart-axis-Axis-method-setTitle'> /**
791 </span> * Updates the {@link #title} of this axis.
792 * @param {String} title
794 setTitle: function(title) {
799 // @private draws the title for the axis.
800 drawTitle: function(maxWidth, maxHeight) {
802 position = me.position,
803 surface = me.chart.surface,
804 displaySprite = me.displaySprite,
806 rotate = (position == 'left' || position == 'right'),
812 displaySprite.setAttributes({text: title}, true);
820 displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
821 surface.renderItem(displaySprite);
823 bbox = displaySprite.getBBox();
824 pad = me.dashSize + me.label.padding;
827 y -= ((me.length / 2) - (bbox.height / 2));
828 if (position == 'left') {
829 x -= (maxWidth + pad + (bbox.width / 2));
832 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
834 me.bbox.width += bbox.width + 10;
837 x += (me.length / 2) - (bbox.width * 0.5);
838 if (position == 'top') {
839 y -= (maxHeight + pad + (bbox.height * 0.3));
842 y += (maxHeight + pad + (bbox.height * 0.8));
844 me.bbox.height += bbox.height + 10;
846 displaySprite.setAttributes({