X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6746dc89c47ed01b165cc1152533605f97eb8e8d..HEAD:/src/chart/series/Pie.js diff --git a/src/chart/series/Pie.js b/src/chart/series/Pie.js index ff1c8743..ec7e7cf5 100644 --- a/src/chart/series/Pie.js +++ b/src/chart/series/Pie.js @@ -15,29 +15,28 @@ If you are unsure which license is appropriate for your use, please contact the /** * @class Ext.chart.series.Pie * @extends Ext.chart.series.Series - * - * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different + * + * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different * categories that also have a meaning as a whole. - * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart + * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart * documentation for more information. A typical configuration object for the pie series could be: - * - * {@img Ext.chart.series.Pie/Ext.chart.series.Pie.png Ext.chart.series.Pie chart series} * + * @example * var store = Ext.create('Ext.data.JsonStore', { * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'], * data: [ - * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13}, - * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3}, - * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7}, - * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23}, - * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33} + * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 }, + * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 }, + * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 }, + * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 }, + * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 } * ] * }); - * + * * Ext.create('Ext.chart.Chart', { * renderTo: Ext.getBody(), * width: 500, - * height: 300, + * height: 350, * animate: true, * store: store, * theme: 'Base:gradients', @@ -46,22 +45,22 @@ If you are unsure which license is appropriate for your use, please contact the * field: 'data1', * showInLegend: true, * tips: { - * trackMouse: true, - * width: 140, - * height: 28, - * renderer: function(storeItem, item) { - * //calculate and display percentage on hover - * var total = 0; - * store.each(function(rec) { - * total += rec.get('data1'); - * }); - * this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%'); - * } + * trackMouse: true, + * width: 140, + * height: 28, + * renderer: function(storeItem, item) { + * // calculate and display percentage on hover + * var total = 0; + * store.each(function(rec) { + * total += rec.get('data1'); + * }); + * this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%'); + * } * }, * highlight: { - * segment: { - * margin: 20 - * } + * segment: { + * margin: 20 + * } * }, * label: { * field: 'name', @@ -69,16 +68,18 @@ If you are unsure which license is appropriate for your use, please contact the * contrast: true, * font: '18px Arial' * } - * }] + * }] * }); - * - * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options - * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item. - * We set `data1` as the value of the field to determine the angle span for each pie slice. We also set a label configuration object - * where we set the field name of the store field to be renderer as text for the label. The labels will also be displayed rotated. - * We set `contrast` to `true` to flip the color of the label if it is to similar to the background color. Finally, we set the font family - * and size through the `font` parameter. - * + * + * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options + * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item. + * + * We set `data1` as the value of the field to determine the angle span for each pie slice. We also set a label configuration object + * where we set the field name of the store field to be renderer as text for the label. The labels will also be displayed rotated. + * + * We set `contrast` to `true` to flip the color of the label if it is to similar to the background color. Finally, we set the font family + * and size through the `font` parameter. + * * @xtype pie */ Ext.define('Ext.chart.series.Pie', { @@ -92,7 +93,7 @@ Ext.define('Ext.chart.series.Pie', { /* End Definitions */ type: "pie", - + alias: 'series.pie', rad: Math.PI / 180, @@ -104,10 +105,9 @@ Ext.define('Ext.chart.series.Pie', { highlightDuration: 150, /** - * @cfg {String} angleField + * @cfg {String} angleField (required) * The store record field name to be used for the pie angles. * The values bound to this field name must be positive real numbers. - * This parameter is required. */ angleField: false, @@ -115,12 +115,11 @@ Ext.define('Ext.chart.series.Pie', { * @cfg {String} lengthField * The store record field name to be used for the pie slice lengths. * The values bound to this field name must be positive real numbers. - * This parameter is optional. */ lengthField: false, /** - * @cfg {Boolean|Number} donut + * @cfg {Boolean/Number} donut * Whether to set the pie chart as donut chart. * Default's false. Can be set to a particular percentage to set the radius * of the donut chart. @@ -137,13 +136,13 @@ Ext.define('Ext.chart.series.Pie', { * @cfg {Array} colorSet * An array of color values which will be used, in order, as the pie slice fill colors. */ - + /** * @cfg {Object} style * An object containing styles for overriding series styles from Theming. */ style: {}, - + constructor: function(config) { this.callParent(arguments); var me = this, @@ -158,7 +157,7 @@ Ext.define('Ext.chart.series.Pie', { } } }); - Ext.apply(me, config, { + Ext.apply(me, config, { shadowAttributes: [{ "stroke-width": 6, "stroke-opacity": 1, @@ -196,12 +195,13 @@ Ext.define('Ext.chart.series.Pie', { surface.customAttributes.segment = function(opt) { return me.getSegment(opt); }; + me.__excludes = me.__excludes || []; }, - + //@private updates some onbefore render parameters. initialize: function() { var me = this, - store = me.chart.substore || me.chart.store; + store = me.chart.getChartStore(); //Add yFields to be used in Legend.js me.yField = []; if (me.label.field) { @@ -217,58 +217,81 @@ Ext.define('Ext.chart.series.Pie', { rad = me.rad, cos = Math.cos, sin = Math.sin, - abs = Math.abs, x = me.centerX, y = me.centerY, x1 = 0, x2 = 0, x3 = 0, x4 = 0, y1 = 0, y2 = 0, y3 = 0, y4 = 0, + x5 = 0, y5 = 0, x6 = 0, y6 = 0, delta = 1e-2, - r = opt.endRho - opt.startRho, startAngle = opt.startAngle, endAngle = opt.endAngle, midAngle = (startAngle + endAngle) / 2 * rad, margin = opt.margin || 0, - flag = abs(endAngle - startAngle) > 180, a1 = Math.min(startAngle, endAngle) * rad, a2 = Math.max(startAngle, endAngle) * rad, - singleSlice = false; + c1 = cos(a1), s1 = sin(a1), + c2 = cos(a2), s2 = sin(a2), + cm = cos(midAngle), sm = sin(midAngle), + flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5) - x += margin * cos(midAngle); - y += margin * sin(midAngle); - - x1 = x + opt.startRho * cos(a1); - y1 = y + opt.startRho * sin(a1); + if (a2 - a1 < delta) { + return {path: ""}; + } - x2 = x + opt.endRho * cos(a1); - y2 = y + opt.endRho * sin(a1); + if (margin !== 0) { + x += margin * cm; + y += margin * sm; + } - x3 = x + opt.startRho * cos(a2); - y3 = y + opt.startRho * sin(a2); + x2 = x + opt.endRho * c1; + y2 = y + opt.endRho * s1; - x4 = x + opt.endRho * cos(a2); - y4 = y + opt.endRho * sin(a2); + x4 = x + opt.endRho * c2; + y4 = y + opt.endRho * s2; - if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) { - singleSlice = true; + if (Math.abs(x2 - x4) + Math.abs(y2 - y4) < delta) { + cm = hsqr2; + sm = -hsqr2; + flag = 1; } - //Solves mysterious clipping bug with IE - if (singleSlice) { + + x6 = x + opt.endRho * cm; + y6 = y + opt.endRho * sm; + + // TODO(bei): It seems that the canvas engine cannot render half circle command correctly on IE. + // Better fix the VML engine for half circles. + + if (opt.startRho !== 0) { + x1 = x + opt.startRho * c1; + y1 = y + opt.startRho * s1; + + x3 = x + opt.startRho * c2; + y3 = y + opt.startRho * s2; + + x5 = x + opt.startRho * cm; + y5 = y + opt.startRho * sm; + return { path: [ - ["M", x1, y1], - ["L", x2, y2], - ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4], - ["Z"]] + ["M", x2, y2], + ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6], + ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4], + ["L", x3, y3], + ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5], + ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1], + ["Z"] + ] }; } else { return { path: [ - ["M", x1, y1], - ["L", x2, y2], - ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4], - ["L", x3, y3], - ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1], - ["Z"]] + ["M", x, y], + ["L", x2, y2], + ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6], + ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4], + ["L", x, y], + ["Z"] + ] }; } }, @@ -283,11 +306,10 @@ Ext.define('Ext.chart.series.Pie', { startAngle = slice.startAngle, endAngle = slice.endAngle, donut = +me.donut, - a1 = Math.min(startAngle, endAngle) * rad, - a2 = Math.max(startAngle, endAngle) * rad, - midAngle = -(a1 + (a2 - a1) / 2), - xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle), - ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle); + midAngle = -(startAngle + endAngle) * rad / 2, + r = (item.endRho + item.startRho) / 2, + xm = x + r * Math.cos(midAngle), + ym = y - r * Math.sin(midAngle); item.middle = { x: xm, @@ -300,7 +322,7 @@ Ext.define('Ext.chart.series.Pie', { */ drawSeries: function() { var me = this, - store = me.chart.substore || me.chart.store, + store = me.chart.getChartStore(), group = me.group, animate = me.chart.animate, field = me.angleField || me.field || me.xField, @@ -334,6 +356,7 @@ Ext.define('Ext.chart.series.Pie', { colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0, gutterX = chart.maxGutter[0], gutterY = chart.maxGutter[1], + abs = Math.abs, rendererAttributes, shadowGroup, shadowAttr, @@ -361,7 +384,7 @@ Ext.define('Ext.chart.series.Pie', { path, p, spriteOptions, bbox; - + Ext.apply(seriesStyle, me.style || {}); me.setBBox(); @@ -372,12 +395,12 @@ Ext.define('Ext.chart.series.Pie', { colorArrayStyle = me.colorSet; colorArrayLength = colorArrayStyle.length; } - + //if not store or store is empty then there's nothing to draw if (!store || !store.getCount()) { return; } - + me.unHighlightItem(); me.cleanHighlights(); @@ -402,25 +425,26 @@ Ext.define('Ext.chart.series.Pie', { } }, this); + totalField = totalField || 1; store.each(function(record, i) { if (this.__excludes && this.__excludes[i]) { - //hidden series - return; - } - value = record.get(field); - middleAngle = angle - 360 * value / totalField / 2; - // TODO - Put up an empty circle - if (isNaN(middleAngle)) { - middleAngle = 360; - value = 1; - totalField = 1; + value = 0; + } else { + value = record.get(field); + if (first == 0) { + first = 1; + } } + // First slice - if (!i || first == 0) { - angle = 360 - middleAngle; - me.firstAngle = angle; - middleAngle = angle - 360 * value / totalField / 2; + if (first == 1) { + first = 2; + me.firstAngle = angle = 360 * value / totalField / 2; + for (j = 0; j < i; j++) { + slices[j].startAngle = slices[j].endAngle = me.firstAngle; + } } + endAngle = angle - 360 * value / totalField; slice = { series: me, @@ -436,20 +460,11 @@ Ext.define('Ext.chart.series.Pie', { slice.rho = me.radius; } slices[i] = slice; - if((slice.startAngle % 360) == (slice.endAngle % 360)) { - slice.startAngle -= 0.0001; - } angle = endAngle; - first++; }, me); - //do all shadows first. if (enableShadows) { for (i = 0, ln = slices.length; i < ln; i++) { - if (this.__excludes && this.__excludes[i]) { - //hidden series - continue; - } slice = slices[i]; slice.shadowAttrs = []; for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) { @@ -464,7 +479,8 @@ Ext.define('Ext.chart.series.Pie', { rho: slice.rho, startRho: rhoAcum + (deltaRho * donut / 100), endRho: rhoAcum + deltaRho - } + }, + hidden: !slice.value && (slice.startAngle % 360) == (slice.endAngle % 360) }; //create shadows for (shindex = 0, shadows = []; shindex < lnsh; shindex++) { @@ -483,9 +499,7 @@ Ext.define('Ext.chart.series.Pie', { to: shadowAttr }); } else { - shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply(shadowAttr, { - hidden: false - }), i, store); + shadowAttr = me.renderer(shadow, store.getAt(i), shadowAttr, i, store); shadow.setAttributes(shadowAttr, true); } shadows.push(shadow); @@ -496,10 +510,6 @@ Ext.define('Ext.chart.series.Pie', { } //do pie slices after. for (i = 0, ln = slices.length; i < ln; i++) { - if (this.__excludes && this.__excludes[i]) { - //hidden series - continue; - } slice = slices[i]; for (j = 0, rhoAcum = 0; j < layers; j++) { sprite = group.getAt(i * layers + j); @@ -513,7 +523,8 @@ Ext.define('Ext.chart.series.Pie', { rho: slice.rho, startRho: rhoAcum + (deltaRho * donut / 100), endRho: rhoAcum + deltaRho - } + }, + hidden: (!slice.value && (slice.startAngle % 360) == (slice.endAngle % 360)) }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {})); item = Ext.apply({}, rendererAttributes.segment, { @@ -564,7 +575,7 @@ Ext.define('Ext.chart.series.Pie', { rhoAcum += deltaRho; } } - + // Hide unused bars ln = group.getCount(); for (i = 0; i < ln; i++) { @@ -597,7 +608,7 @@ Ext.define('Ext.chart.series.Pie', { centerY = me.centerY, middle = item.middle, endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {}); - + return me.chart.surface.add(Ext.apply({ 'type': 'text', 'text-anchor': 'middle', @@ -629,9 +640,13 @@ Ext.define('Ext.chart.series.Pie', { theta = Math.atan2(y, x || 1), dg = theta * 180 / Math.PI, prevDg; - + if (this.__excludes && this.__excludes[i]) { + opt.hidden = true; + } function fixAngle(a) { - if (a < 0) a += 360; + if (a < 0) { + a += 360; + } return a % 360; } @@ -675,7 +690,7 @@ Ext.define('Ext.chart.series.Pie', { } //ensure the object has zero translation opt.translate = { - x: 0, y: 0 + x: 0, y: 0 }; if (animate && !resizing && (display != 'rotate' || prevDg != null)) { me.onAnimate(label, { @@ -786,8 +801,8 @@ Ext.define('Ext.chart.series.Pie', { startAngle = item.startAngle, endAngle = item.endAngle, rho = Math.sqrt(dx * dx + dy * dy), - angle = Math.atan2(y - cy, x - cx) / me.rad + 360; - + angle = Math.atan2(y - cy, x - cx) / me.rad; + // normalize to the same range of angles created by drawSeries if (angle > me.firstAngle) { angle -= 360; @@ -795,7 +810,7 @@ Ext.define('Ext.chart.series.Pie', { return (angle <= startAngle && angle > endAngle && rho >= item.startRho && rho <= item.endRho); }, - + // @private hides all elements in the series. hideAll: function() { var i, l, shadow, shadows, sh, lsh, sprite; @@ -821,7 +836,7 @@ Ext.define('Ext.chart.series.Pie', { this.drawSeries(); } }, - + // @private shows all elements in the series. showAll: function() { if (!isNaN(this._index)) { @@ -838,13 +853,13 @@ Ext.define('Ext.chart.series.Pie', { var me = this, rad = me.rad; item = item || this.items[this._index]; - + //TODO(nico): sometimes in IE itemmouseover is triggered //twice without triggering itemmouseout in between. This //fixes the highlighting bug. Eventually, events should be //changed to trigger one itemmouseout between two itemmouseovers. this.unHighlightItem(); - + if (!item || item.sprite && item.sprite._animating) { return; } @@ -877,7 +892,7 @@ Ext.define('Ext.chart.series.Pie', { if (Math.abs(y) < 1e-10) { y = 0; } - + if (animate) { label.stopAnimation(); label.animate({ @@ -932,7 +947,7 @@ Ext.define('Ext.chart.series.Pie', { }, /** - * un-highlights the specified item. If no item is provided it will un-highlight the entire series. + * Un-highlights the specified item. If no item is provided it will un-highlight the entire series. * @param item {Object} Info about the item; same format as returned by #getItemForPoint */ unHighlightItem: function() { @@ -1019,7 +1034,7 @@ Ext.define('Ext.chart.series.Pie', { } me.callParent(arguments); }, - + /** * Returns the color of the series (to be displayed as color for the series legend item). * @param item {Object} Info about the item; same format as returned by #getItemForPoint