/** * @class Ext.chart.axis.Axis * @extends Ext.chart.axis.Abstract * * Defines axis for charts. The axis position, type, style can be configured. * The axes are defined in an axes array of configuration objects where the type, * field, grid and other configuration options can be set. To know more about how * to create a Chart please check the Chart class documentation. Here's an example for the axes part: * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be: *

    axes: [{
        type: 'Numeric',
        grid: true,
        position: 'left',
        fields: ['data1', 'data2', 'data3'],
        title: 'Number of Hits',
        grid: {
            odd: {
                opacity: 1,
                fill: '#ddd',
                stroke: '#bbb',
                'stroke-width': 1
            }
        },
        minimum: 0,
        adjustMinimumByMajorUnit: 0
    }, {
        type: 'Category',
        position: 'bottom',
        fields: ['name'],
        title: 'Month of the Year',
        grid: true,
        label: {
            rotate: {
                degrees: 315
            }
        }
    }]
   
* * 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 * 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. * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the * category axis the labels will be rotated so they can fit the space better. * */ Ext.define('Ext.chart.axis.Axis', { /* Begin Definitions */ extend: 'Ext.chart.axis.Abstract', requires: ['Ext.draw.Draw'], /* End Definitions */
/** * @cfg {Number} dashSize * The size of the dash marker. Default's 3. */ dashSize: 3,
/** * @cfg {String} position * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`. */ position: 'bottom', // @private skipFirst: false,
/** * @cfg {Number} length * Offset axis position. Default's 0. */ length: 0,
/** * @cfg {Number} width * Offset axis width. Default's 0. */ width: 0, // @private applyData: Ext.emptyFn, // @private creates a structure with start, end and step points. calcEnds: function() { var me = this, store = me.chart.store, series = me.chart.series.items, fields = me.fields, ln = fields.length, i, l, value, out, min = isNaN(me.minimum) ? Infinity : me.minimum, max = isNaN(me.maximum) ? -Infinity : me.maximum, prevMin = me.prevMin, prevMax = me.prevMax, aggregate = false, total = 0, excludes = []; //if one series is stacked I have to aggregate the values //for the scale. for (i = 0, l = series.length; !aggregate && i < l; i++) { aggregate = aggregate || series[i].stacked; excludes = series[i].__excludes || excludes; } store.each(function(record) { if (aggregate) { if (!isFinite(min)) { min = 0; } for (value = 0, i = 0; i < ln; i++) { if (excludes[i]) { continue; } value += record.get(fields[i]); } max = Math.max(max, value); min = Math.min(min, value); } else { for (i = 0; i < ln; i++) { if (excludes[i]) { continue; } value = record.get(fields[i]); max = Math.max(max, value); min = Math.min(min, value); } } }); if (!isFinite(max)) { max = me.prevMax || 0; } if (!isFinite(min)) { min = me.prevMin || 0; } out = Ext.draw.Draw.snapEnds(min, max >> 0, me.steps); if (!isNaN(me.maximum)) { out.to = Math.max(out.to, me.maximum); } if (!isNaN(me.minimum)) { out.from = Math.min(out.from, me.minimum); } if (me.adjustMaximumByMajorUnit) { out.to += out.step; } if (me.adjustMinimumByMajorUnit) { out.from -= out.step; } me.prevMin = min; me.prevMax = max; return out; },
/** * Renders the axis into the screen and updates it's position. */ drawAxis: function (init) { var me = this, x = me.x, y = me.y, gutterX = me.chart.maxGutter[0], gutterY = me.chart.maxGutter[1], dashSize = me.dashSize, length = me.length, position = me.position, inflections = [], calcLabels = false, stepCalcs = me.applyData(), step = stepCalcs.step, trueLength, currentX, currentY, path, prev, delta; me.from = stepCalcs.from; me.to = stepCalcs.to; if (position == 'left' || position == 'right') { currentX = Math.floor(x) + 0.5; path = ["M", currentX, y, "l", 0, -length]; trueLength = length - (gutterY * 2); } else { currentY = Math.floor(y) + 0.5; path = ["M", x, currentY, "l", length, 0]; trueLength = length - (gutterX * 2); } delta = trueLength / stepCalcs.steps; if (me.type == 'Numeric') { calcLabels = true; me.labels = [stepCalcs.from]; } if (position == 'right' || position == 'left') { currentY = y - gutterY; currentX = x - ((position == 'left') * dashSize * 2); while (currentY >= y - gutterY - trueLength) { path = path.concat(["M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0]); inflections.push([ Math.floor(x), Math.floor(currentY) ]); currentY -= delta; if (calcLabels) { me.labels.push(me.labels[me.labels.length -1] + step); } } if (Math.round(currentY + delta - (y - gutterY - trueLength))) { path = path.concat(["M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0]); inflections.push([ Math.floor(x), Math.floor(currentY) ]); if (calcLabels) { me.labels.push(me.labels[me.labels.length -1] + step); } } } else { currentX = x + gutterX; currentY = y - (!!(position == 'top') * dashSize * 2); while (currentX <= x + gutterX + trueLength) { path = path.concat(["M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1]); inflections.push([ Math.floor(currentX), Math.floor(y) ]); currentX += delta; if (calcLabels) { me.labels.push(me.labels[me.labels.length -1] + step); } } if (Math.round(currentX - delta - (x + gutterX + trueLength))) { path = path.concat(["M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1]); inflections.push([ Math.floor(currentX), Math.floor(y) ]); if (calcLabels) { me.labels.push(me.labels[me.labels.length -1] + step); } } } if (!me.axis) { me.axis = me.chart.surface.add(Ext.apply({ type: 'path', path: path }, me.axisStyle)); } me.axis.setAttributes({ path: path }, true); me.inflections = inflections; if (!init && me.grid) { me.drawGrid(); } me.axisBBox = me.axis.getBBox(); me.drawLabels(); },
/** * Renders an horizontal and/or vertical grid into the Surface. */ drawGrid: function() { var me = this, surface = me.chart.surface, grid = me.grid, odd = grid.odd, even = grid.even, inflections = me.inflections, ln = inflections.length - ((odd || even)? 0 : 1), position = me.position, gutter = me.chart.maxGutter, width = me.width - 2, vert = false, point, prevPoint, i = 1, path = [], styles, lineWidth, dlineWidth, oddPath = [], evenPath = []; if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) || (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) { i = 0; ln++; } for (; i < ln; i++) { point = inflections[i]; prevPoint = inflections[i - 1]; if (odd || even) { path = (i % 2)? oddPath : evenPath; styles = ((i % 2)? odd : even) || {}; lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2; dlineWidth = 2 * lineWidth; if (position == 'left') { path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth, "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth, "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth, "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z"); } else if (position == 'right') { path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth, "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth, "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth, "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z"); } else if (position == 'top') { path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth, "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth, "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth, "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z"); } else { path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth, "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth, "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth, "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z"); } } else { if (position == 'left') { path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]); } else if (position == 'right') { path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]); } else if (position == 'top') { path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]); } else { path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]); } } } if (odd || even) { if (oddPath.length) { if (!me.gridOdd && oddPath.length) { me.gridOdd = surface.add({ type: 'path', path: oddPath }); } me.gridOdd.setAttributes(Ext.apply({ path: oddPath, hidden: false }, odd || {}), true); } if (evenPath.length) { if (!me.gridEven) { me.gridEven = surface.add({ type: 'path', path: evenPath }); } me.gridEven.setAttributes(Ext.apply({ path: evenPath, hidden: false }, even || {}), true); } } else { if (path.length) { if (!me.gridLines) { me.gridLines = me.chart.surface.add({ type: 'path', path: path, "stroke-width": me.lineWidth || 1, stroke: me.gridColor || '#ccc' }); } me.gridLines.setAttributes({ hidden: false, path: path }, true); } else if (me.gridLines) { me.gridLines.hide(true); } } },
/** * Renders the labels in the axes. */ drawLabels: function() { var me = this, inflections = me.inflections, ln = inflections.length, chart = me.chart, position = me.position, labels = me.labels, surface = chart.surface, labelGroup = me.labelGroup, maxWidth = 0, maxHeight = 0, gutterY = me.chart.maxGutter[1], bbox, point, prevX, prevY, prevLabel, textLabel, labelAttr, textRight, text, label, last, x, y, i; if (position == 'left' || position == 'right') { last = ln; for (i = 0; i < last; i++) { point = inflections[i]; text = me.label.renderer(labels[i]); // Re-use existing textLabel or create a new one textLabel = labelGroup.getAt(i); if (textLabel) { if (text != textLabel.attr.text) { textLabel.setAttributes(Ext.apply({ text: text }, me.label), true); textLabel._bbox = textLabel.getBBox(); } } else { textLabel = surface.add(Ext.apply({ group: labelGroup, type: 'text', x: 0, y: 0, text: text }, me.label)); surface.renderItem(textLabel); textLabel._bbox = textLabel.getBBox(); } labelAttr = textLabel.attr; bbox = textLabel._bbox; maxWidth = Math.max(maxWidth, bbox.width + me.dashSize + me.label.padding); y = point[1]; if (gutterY < bbox.height / 2) { if (i == last - 1 && chart.axes.findIndex('position', 'top') == -1) { y = me.y - me.length + Math.ceil(bbox.height / 2); } else if (i == 0 && chart.axes.findIndex('position', 'bottom') == -1) { y = me.y - Math.floor(bbox.height / 2); } } if (position == 'left') { x = point[0] - bbox.width - me.dashSize - me.label.padding - 2; } else { x = point[0] + me.dashSize + me.label.padding + 2; } if (x != labelAttr.x || y != labelAttr.y || labelAttr.hidden) { textLabel.setAttributes(Ext.apply({ hidden: false, x: x, y: y }, me.label), true); } } } else { last = ln - 1; for (i = last; i >= 0; i--) { point = inflections[i]; text = me.label.renderer(labels[i]); // Re-use existing textLabel or create a new one textLabel = labelGroup.getAt(last - i); if (textLabel) { if (text != textLabel.attr.text) { textLabel.setAttributes({ text: text }, true); textLabel._bbox = textLabel.getBBox(); } } else { textLabel = surface.add(Ext.apply({ group: labelGroup, type: 'text', x: 0, y: 0, text: text }, me.label)); surface.renderItem(textLabel); textLabel._bbox = textLabel.getBBox(); } labelAttr = textLabel.attr; bbox = textLabel._bbox; maxHeight = Math.max(maxHeight, bbox.height + me.dashSize + me.label.padding); x = Math.floor(point[0] - (bbox.width / 2) - bbox.x * Math.abs(Math.sin(((labelAttr.rotation && labelAttr.rotation.degrees || 0) * Math.PI / 180) || 0))); if (me.chart.maxGutter[0] == 0) { if (i == 0 && chart.axes.findIndex('position', 'left') == -1) { x = point[0]; } else if (i == last && chart.axes.findIndex('position', 'right') == -1) { x = point[0] - bbox.width; } } textRight = x + bbox.width + me.label.padding; // Skip label if there isn't available minimum space if (i != 0 && (i != last) && textRight > prevX && !(labelAttr.rotation && labelAttr.rotation.degrees)) { if (!me.elipsis(textLabel, text, prevX - x, 35, point[0])) { textLabel.hide(true); continue; } } if (i == 0 && prevX < textRight) { if (labelGroup.getCount() > 2) { prevLabel = labelGroup.getAt((last - i) - 1); me.elipsis(prevLabel, prevLabel.attr.text, labelGroup.getAt((last - i) - 2).getBBox().x - textRight, 35, inflections[i + 1][0]); } } prevX = x; if (position == 'top') { y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2); } else { y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2); } textLabel.setAttributes({ hidden: false, x: x, y: y }, true); } } // Hide unused bars ln = labelGroup.getCount(); i = inflections.length; for (; i < ln; i++) { labelGroup.getAt(i).hide(true); } me.bbox = {}; Ext.apply(me.bbox, me.axisBBox); me.bbox.height = maxHeight; me.bbox.width = maxWidth; if (Ext.isString(me.title)) { me.drawTitle(maxWidth, maxHeight); } }, // @private creates the elipsis for the text. elipsis: function(sprite, text, desiredWidth, minWidth, center) { var bbox, x; if (desiredWidth < minWidth) { sprite.hide(true); return false; } while (text.length > 4) { text = text.substr(0, text.length - 4) + "..."; sprite.setAttributes({ text: text }, true); bbox = sprite.getBBox(); if (bbox.width < desiredWidth) { if (typeof center == 'number') { sprite.setAttributes({ x: Math.floor(center - (bbox.width / 2)) }, true); } break; } } return true; }, // @private draws the title for the axis. drawTitle: function(maxWidth, maxHeight) { var me = this, position = me.position, surface = me.chart.surface, rotate = (position == 'left' || position == 'right'), x = me.x, y = me.y, base, bbox, pad; if (!me.displaySprite) { base = { type: 'text', x: 0, y: 0, text: me.title }; me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle)); surface.renderItem(me.displaySprite); } bbox = me.displaySprite.getBBox(); pad = me.dashSize + me.label.padding; if (rotate) { y -= ((me.length / 2) - (bbox.height / 2)); if (position == 'left') { x -= (maxWidth + pad + (bbox.width / 2)); } else { x += (maxWidth + pad + bbox.width - (bbox.width / 2)); } me.bbox.width += bbox.width + 10; } else { x += (me.length / 2) - (bbox.width * 0.5); if (position == 'top') { y -= (maxHeight + pad + (bbox.height * 0.3)); } else { y += (maxHeight + pad + (bbox.height * 0.8)); } me.bbox.height += bbox.height + 10; } me.displaySprite.setAttributes({ translate: { x: x, y: y } }, true); } });