//>>built define("dojox/charting/plot2d/Pie", ["dojo/_base/lang", "dojo/_base/array" ,"dojo/_base/declare", "../Element", "./_PlotEvents", "./common", "../axis2d/common", "dojox/gfx", "dojox/gfx/matrix", "dojox/lang/functional", "dojox/lang/utils"], function(lang, arr, declare, Element, PlotEvents, dc, da, g, m, df, du){ /*===== var Element = dojox.charting.Element; var PlotEvents = dojox.charting.plot2d._PlotEvents; dojo.declare("dojox.charting.plot2d.__PieCtorArgs", dojox.charting.plot2d.__DefaultCtorArgs, { // summary: // Specialized keyword arguments object for use in defining parameters on a Pie chart. // labels: Boolean? // Whether or not to draw labels for each pie slice. Default is true. labels: true, // ticks: Boolean? // Whether or not to draw ticks to labels within each slice. Default is false. ticks: false, // fixed: Boolean? // TODO fixed: true, // precision: Number? // The precision at which to sum/add data values. Default is 1. precision: 1, // labelOffset: Number? // The amount in pixels by which to offset labels. Default is 20. labelOffset: 20, // labelStyle: String? // Options as to where to draw labels. Values include "default", and "columns". Default is "default". labelStyle: "default", // default/columns // htmlLabels: Boolean? // Whether or not to use HTML to render slice labels. Default is true. htmlLabels: true, // radGrad: String? // The type of radial gradient to use in rendering. Default is "native". radGrad: "native", // fanSize: Number? // The amount for a radial gradient. Default is 5. fanSize: 5, // startAngle: Number? // Where to being rendering gradients in slices, in degrees. Default is 0. startAngle: 0, // radius: Number? // The size of the radial gradient. Default is 0. radius: 0 }); =====*/ var FUDGE_FACTOR = 0.2; // use to overlap fans return declare("dojox.charting.plot2d.Pie", [Element, PlotEvents], { // summary: // The plot that represents a typical pie chart. defaultParams: { labels: true, ticks: false, fixed: true, precision: 1, labelOffset: 20, labelStyle: "default", // default/columns htmlLabels: true, // use HTML to draw labels radGrad: "native", // or "linear", or "fan" fanSize: 5, // maximum fan size in degrees startAngle: 0 // start angle for slices in degrees }, optionalParams: { radius: 0, // theme components stroke: {}, outline: {}, shadow: {}, fill: {}, font: "", fontColor: "", labelWiring: {} }, constructor: function(chart, kwArgs){ // summary: // Create a pie plot. this.opt = lang.clone(this.defaultParams); du.updateWithObject(this.opt, kwArgs); du.updateWithPattern(this.opt, kwArgs, this.optionalParams); this.run = null; this.dyn = []; }, clear: function(){ // summary: // Clear out all of the information tied to this plot. // returns: dojox.charting.plot2d.Pie // A reference to this plot for functional chaining. this.dirty = true; this.dyn = []; this.run = null; return this; // dojox.charting.plot2d.Pie }, setAxis: function(axis){ // summary: // Dummy method, since axes are irrelevant with a Pie chart. // returns: dojox.charting.plot2d.Pie // The reference to this plot for functional chaining. return this; // dojox.charting.plot2d.Pie }, addSeries: function(run){ // summary: // Add a series of data to this plot. // returns: dojox.charting.plot2d.Pie // The reference to this plot for functional chaining. this.run = run; return this; // dojox.charting.plot2d.Pie }, getSeriesStats: function(){ // summary: // Returns default stats (irrelevant for this type of plot). // returns: Object // {hmin, hmax, vmin, vmax} min/max in both directions. return lang.delegate(dc.defaultStats); }, initializeScalers: function(){ // summary: // Does nothing (irrelevant for this type of plot). return this; }, getRequiredColors: function(){ // summary: // Return the number of colors needed to draw this plot. return this.run ? this.run.data.length : 0; }, render: function(dim, offsets){ // summary: // Render the plot on the chart. // dim: Object // An object of the form { width, height }. // offsets: Object // An object of the form { l, r, t, b }. // returns: dojox.charting.plot2d.Pie // A reference to this plot for functional chaining. if(!this.dirty){ return this; } this.resetEvents(); this.dirty = false; this._eventSeries = {}; this.cleanGroup(); var s = this.group, t = this.chart.theme; if(!this.run || !this.run.data.length){ return this; } // calculate the geometry var rx = (dim.width - offsets.l - offsets.r) / 2, ry = (dim.height - offsets.t - offsets.b) / 2, r = Math.min(rx, ry), taFont = "font" in this.opt ? this.opt.font : t.axis.font, size = taFont ? g.normalizedLength(g.splitFontString(taFont).size) : 0, taFontColor = "fontColor" in this.opt ? this.opt.fontColor : t.axis.fontColor, startAngle = m._degToRad(this.opt.startAngle), start = startAngle, step, filteredRun, slices, labels, shift, labelR, run = this.run.data, events = this.events(); if(typeof run[0] == "number"){ filteredRun = df.map(run, "x ? Math.max(x, 0) : 0"); if(df.every(filteredRun, "<= 0")){ return this; } slices = df.map(filteredRun, "/this", df.foldl(filteredRun, "+", 0)); if(this.opt.labels){ labels = arr.map(slices, function(x){ return x > 0 ? this._getLabel(x * 100) + "%" : ""; }, this); } }else{ filteredRun = df.map(run, "x ? Math.max(x.y, 0) : 0"); if(df.every(filteredRun, "<= 0")){ return this; } slices = df.map(filteredRun, "/this", df.foldl(filteredRun, "+", 0)); if(this.opt.labels){ labels = arr.map(slices, function(x, i){ if(x <= 0){ return ""; } var v = run[i]; return "text" in v ? v.text : this._getLabel(x * 100) + "%"; }, this); } } var themes = df.map(run, function(v, i){ if(v === null || typeof v == "number"){ return t.next("slice", [this.opt, this.run], true); } return t.next("slice", [this.opt, this.run, v], true); }, this); if(this.opt.labels){ shift = df.foldl1(df.map(labels, function(label, i){ var font = themes[i].series.font; return g._base._getTextBox(label, {font: font}).w; }, this), "Math.max(a, b)") / 2; if(this.opt.labelOffset < 0){ r = Math.min(rx - 2 * shift, ry - size) + this.opt.labelOffset; } labelR = r - this.opt.labelOffset; } if("radius" in this.opt){ r = this.opt.radius; labelR = r - this.opt.labelOffset; } var circle = { cx: offsets.l + rx, cy: offsets.t + ry, r: r }; this.dyn = []; // draw slices var eventSeries = new Array(slices.length); arr.some(slices, function(slice, i){ if(slice < 0){ // degenerated slice return false; // continue } if(slice == 0){ this.dyn.push({fill: null, stroke: null}); return false; } var v = run[i], theme = themes[i], specialFill; if(slice >= 1){ // whole pie specialFill = this._plotFill(theme.series.fill, dim, offsets); specialFill = this._shapeFill(specialFill, { x: circle.cx - circle.r, y: circle.cy - circle.r, width: 2 * circle.r, height: 2 * circle.r }); specialFill = this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, circle.r); var shape = s.createCircle(circle).setFill(specialFill).setStroke(theme.series.stroke); this.dyn.push({fill: specialFill, stroke: theme.series.stroke}); if(events){ var o = { element: "slice", index: i, run: this.run, shape: shape, x: i, y: typeof v == "number" ? v : v.y, cx: circle.cx, cy: circle.cy, cr: r }; this._connectEvents(o); eventSeries[i] = o; } return true; // stop iteration } // calculate the geometry of the slice var end = start + slice * 2 * Math.PI; if(i + 1 == slices.length){ end = startAngle + 2 * Math.PI; } var step = end - start, x1 = circle.cx + r * Math.cos(start), y1 = circle.cy + r * Math.sin(start), x2 = circle.cx + r * Math.cos(end), y2 = circle.cy + r * Math.sin(end); // draw the slice var fanSize = m._degToRad(this.opt.fanSize); if(theme.series.fill && theme.series.fill.type === "radial" && this.opt.radGrad === "fan" && step > fanSize){ var group = s.createGroup(), nfans = Math.ceil(step / fanSize), delta = step / nfans; specialFill = this._shapeFill(theme.series.fill, {x: circle.cx - circle.r, y: circle.cy - circle.r, width: 2 * circle.r, height: 2 * circle.r}); for(var j = 0; j < nfans; ++j){ var fansx = j == 0 ? x1 : circle.cx + r * Math.cos(start + (j - FUDGE_FACTOR) * delta), fansy = j == 0 ? y1 : circle.cy + r * Math.sin(start + (j - FUDGE_FACTOR) * delta), fanex = j == nfans - 1 ? x2 : circle.cx + r * Math.cos(start + (j + 1 + FUDGE_FACTOR) * delta), faney = j == nfans - 1 ? y2 : circle.cy + r * Math.sin(start + (j + 1 + FUDGE_FACTOR) * delta), fan = group.createPath(). moveTo(circle.cx, circle.cy). lineTo(fansx, fansy). arcTo(r, r, 0, delta > Math.PI, true, fanex, faney). lineTo(circle.cx, circle.cy). closePath(). setFill(this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, r, start + (j + 0.5) * delta, start + (j + 0.5) * delta)); } group.createPath(). moveTo(circle.cx, circle.cy). lineTo(x1, y1). arcTo(r, r, 0, step > Math.PI, true, x2, y2). lineTo(circle.cx, circle.cy). closePath(). setStroke(theme.series.stroke); shape = group; }else{ shape = s.createPath(). moveTo(circle.cx, circle.cy). lineTo(x1, y1). arcTo(r, r, 0, step > Math.PI, true, x2, y2). lineTo(circle.cx, circle.cy). closePath(). setStroke(theme.series.stroke); var specialFill = theme.series.fill; if(specialFill && specialFill.type === "radial"){ specialFill = this._shapeFill(specialFill, {x: circle.cx - circle.r, y: circle.cy - circle.r, width: 2 * circle.r, height: 2 * circle.r}); if(this.opt.radGrad === "linear"){ specialFill = this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, r, start, end); } }else if(specialFill && specialFill.type === "linear"){ specialFill = this._plotFill(specialFill, dim, offsets); specialFill = this._shapeFill(specialFill, shape.getBoundingBox()); } shape.setFill(specialFill); } this.dyn.push({fill: specialFill, stroke: theme.series.stroke}); if(events){ var o = { element: "slice", index: i, run: this.run, shape: shape, x: i, y: typeof v == "number" ? v : v.y, cx: circle.cx, cy: circle.cy, cr: r }; this._connectEvents(o); eventSeries[i] = o; } start = end; return false; // continue }, this); // draw labels if(this.opt.labels){ if(this.opt.labelStyle == "default"){ start = startAngle; arr.some(slices, function(slice, i){ if(slice <= 0){ // degenerated slice return false; // continue } var theme = themes[i]; if(slice >= 1){ // whole pie var v = run[i], elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"]( this.chart, s, circle.cx, circle.cy + size / 2, "middle", labels[i], theme.series.font, theme.series.fontColor); if(this.opt.htmlLabels){ this.htmlElements.push(elem); } return true; // stop iteration } // calculate the geometry of the slice var end = start + slice * 2 * Math.PI, v = run[i]; if(i + 1 == slices.length){ end = startAngle + 2 * Math.PI; } var labelAngle = (start + end) / 2, x = circle.cx + labelR * Math.cos(labelAngle), y = circle.cy + labelR * Math.sin(labelAngle) + size / 2; // draw the label var elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"] (this.chart, s, x, y, "middle", labels[i], theme.series.font, theme.series.fontColor); if(this.opt.htmlLabels){ this.htmlElements.push(elem); } start = end; return false; // continue }, this); }else if(this.opt.labelStyle == "columns"){ start = startAngle; //calculate label angles var labeledSlices = []; arr.forEach(slices, function(slice, i){ var end = start + slice * 2 * Math.PI; if(i + 1 == slices.length){ end = startAngle + 2 * Math.PI; } var labelAngle = (start + end) / 2; labeledSlices.push({ angle: labelAngle, left: Math.cos(labelAngle) < 0, theme: themes[i], index: i, omit: end - start < 0.001 }); start = end; }); //calculate label radius to each slice var labelHeight = g._base._getTextBox("a",{font:taFont}).h; this._getProperLabelRadius(labeledSlices, labelHeight, circle.r * 1.1); //draw label and wiring arr.forEach(labeledSlices, function(slice, i){ if (!slice.omit) { var leftColumn = circle.cx - circle.r * 2, rightColumn = circle.cx + circle.r * 2, labelWidth = g._base._getTextBox(labels[i], {font: taFont}).w, x = circle.cx + slice.labelR * Math.cos(slice.angle), y = circle.cy + slice.labelR * Math.sin(slice.angle), jointX = (slice.left) ? (leftColumn + labelWidth) : (rightColumn - labelWidth), labelX = (slice.left) ? leftColumn : jointX; var wiring = s.createPath().moveTo(circle.cx + circle.r * Math.cos(slice.angle), circle.cy + circle.r * Math.sin(slice.angle)) if (Math.abs(slice.labelR * Math.cos(slice.angle)) < circle.r * 2 - labelWidth) { wiring.lineTo(x, y); } wiring.lineTo(jointX, y).setStroke(slice.theme.series.labelWiring); var elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"]( this.chart, s, labelX, y, "left", labels[i], slice.theme.series.font, slice.theme.series.fontColor); if (this.opt.htmlLabels) { this.htmlElements.push(elem); } } },this); } } // post-process events to restore the original indexing var esi = 0; this._eventSeries[this.run.name] = df.map(run, function(v){ return v <= 0 ? null : eventSeries[esi++]; }); return this; // dojox.charting.plot2d.Pie }, _getProperLabelRadius: function(slices, labelHeight, minRidius){ var leftCenterSlice = {},rightCenterSlice = {},leftMinSIN = 1, rightMinSIN = 1; if (slices.length == 1) { slices[0].labelR = minRidius; return; } for(var i = 0;i tempSIN){ leftMinSIN = tempSIN; leftCenterSlice = slices[i]; } }else{ if(rightMinSIN > tempSIN){ rightMinSIN = tempSIN; rightCenterSlice = slices[i]; } } } leftCenterSlice.labelR = rightCenterSlice.labelR = minRidius; this._calculateLabelR(leftCenterSlice,slices,labelHeight); this._calculateLabelR(rightCenterSlice,slices,labelHeight); }, _calculateLabelR: function(firstSlice,slices,labelHeight){ var i = firstSlice.index,length = slices.length, currentLabelR = firstSlice.labelR; while(!(slices[i%length].left ^ slices[(i+1)%length].left)){ if (!slices[(i + 1) % length].omit) { var nextLabelR = (Math.sin(slices[i % length].angle) * currentLabelR + ((slices[i % length].left) ? (-labelHeight) : labelHeight)) / Math.sin(slices[(i + 1) % length].angle); currentLabelR = (nextLabelR < firstSlice.labelR) ? firstSlice.labelR : nextLabelR; slices[(i + 1) % length].labelR = currentLabelR; } i++; } i = firstSlice.index; var j = (i == 0)?length-1 : i - 1; while(!(slices[i].left ^ slices[j].left)){ if (!slices[j].omit) { var nextLabelR = (Math.sin(slices[i].angle) * currentLabelR + ((slices[i].left) ? labelHeight : (-labelHeight))) / Math.sin(slices[j].angle); currentLabelR = (nextLabelR < firstSlice.labelR) ? firstSlice.labelR : nextLabelR; slices[j].labelR = currentLabelR; } i--;j--; i = (i < 0)?i+slices.length:i; j = (j < 0)?j+slices.length:j; } }, // utilities _getLabel: function(number){ return dc.getLabel(number, this.opt.fixed, this.opt.precision); } }); });