source: Dev/branches/rest-dojo-ui/client/dojox/charting/plot2d/Pie.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 16.5 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/array" ,"dojo/_base/declare",
2                "../Element", "./_PlotEvents", "./common", "../axis2d/common",
3                "dojox/gfx", "dojox/gfx/matrix", "dojox/lang/functional", "dojox/lang/utils"],
4        function(lang, arr, declare, Element, PlotEvents, dc, da, g, m, df, du){
5
6        /*=====
7        var Element = dojox.charting.Element;
8        var PlotEvents = dojox.charting.plot2d._PlotEvents;
9        dojo.declare("dojox.charting.plot2d.__PieCtorArgs", dojox.charting.plot2d.__DefaultCtorArgs, {
10                //      summary:
11                //              Specialized keyword arguments object for use in defining parameters on a Pie chart.
12       
13                //      labels: Boolean?
14                //              Whether or not to draw labels for each pie slice.  Default is true.
15                labels:                 true,
16       
17                //      ticks: Boolean?
18                //              Whether or not to draw ticks to labels within each slice. Default is false.
19                ticks:                  false,
20       
21                //      fixed: Boolean?
22                //              TODO
23                fixed:                  true,
24       
25                //      precision: Number?
26                //              The precision at which to sum/add data values. Default is 1.
27                precision:              1,
28       
29                //      labelOffset: Number?
30                //              The amount in pixels by which to offset labels.  Default is 20.
31                labelOffset:    20,
32       
33                //      labelStyle: String?
34                //              Options as to where to draw labels.  Values include "default", and "columns".   Default is "default".
35                labelStyle:             "default",      // default/columns
36       
37                //      htmlLabels: Boolean?
38                //              Whether or not to use HTML to render slice labels. Default is true.
39                htmlLabels:             true,
40       
41                //      radGrad: String?
42                //              The type of radial gradient to use in rendering.  Default is "native".
43                radGrad:        "native",
44       
45                //      fanSize: Number?
46                //              The amount for a radial gradient.  Default is 5.
47                fanSize:                5,
48       
49                //      startAngle: Number?
50                //              Where to being rendering gradients in slices, in degrees.  Default is 0.
51                startAngle:     0,
52       
53                //      radius: Number?
54                //              The size of the radial gradient.  Default is 0.
55                radius:         0
56        });
57        =====*/
58
59        var FUDGE_FACTOR = 0.2; // use to overlap fans
60
61        return declare("dojox.charting.plot2d.Pie", [Element, PlotEvents], {
62                //      summary:
63                //              The plot that represents a typical pie chart.
64                defaultParams: {
65                        labels:                 true,
66                        ticks:                  false,
67                        fixed:                  true,
68                        precision:              1,
69                        labelOffset:    20,
70                        labelStyle:             "default",      // default/columns
71                        htmlLabels:             true,           // use HTML to draw labels
72                        radGrad:        "native",       // or "linear", or "fan"
73                        fanSize:                5,                      // maximum fan size in degrees
74                        startAngle:     0                       // start angle for slices in degrees
75                },
76                optionalParams: {
77                        radius:         0,
78                        // theme components
79                        stroke:         {},
80                        outline:        {},
81                        shadow:         {},
82                        fill:           {},
83                        font:           "",
84                        fontColor:      "",
85                        labelWiring: {}
86                },
87
88                constructor: function(chart, kwArgs){
89                        //      summary:
90                        //              Create a pie plot.
91                        this.opt = lang.clone(this.defaultParams);
92                        du.updateWithObject(this.opt, kwArgs);
93                        du.updateWithPattern(this.opt, kwArgs, this.optionalParams);
94                        this.run = null;
95                        this.dyn = [];
96                },
97                clear: function(){
98                        //      summary:
99                        //              Clear out all of the information tied to this plot.
100                        //      returns: dojox.charting.plot2d.Pie
101                        //              A reference to this plot for functional chaining.
102                        this.dirty = true;
103                        this.dyn = [];
104                        this.run = null;
105                        return this;    //      dojox.charting.plot2d.Pie
106                },
107                setAxis: function(axis){
108                        //      summary:
109                        //              Dummy method, since axes are irrelevant with a Pie chart.
110                        //      returns: dojox.charting.plot2d.Pie
111                        //              The reference to this plot for functional chaining.
112                        return this;    //      dojox.charting.plot2d.Pie
113                },
114                addSeries: function(run){
115                        //      summary:
116                        //              Add a series of data to this plot.
117                        //      returns: dojox.charting.plot2d.Pie
118                        //              The reference to this plot for functional chaining.
119                        this.run = run;
120                        return this;    //      dojox.charting.plot2d.Pie
121                },
122                getSeriesStats: function(){
123                        //      summary:
124                        //              Returns default stats (irrelevant for this type of plot).
125                        //      returns: Object
126                        //              {hmin, hmax, vmin, vmax} min/max in both directions.
127                        return lang.delegate(dc.defaultStats);
128                },
129                initializeScalers: function(){
130                        //      summary:
131                        //              Does nothing (irrelevant for this type of plot).
132                        return this;
133                },
134                getRequiredColors: function(){
135                        //      summary:
136                        //              Return the number of colors needed to draw this plot.
137                        return this.run ? this.run.data.length : 0;
138                },
139
140                render: function(dim, offsets){
141                        //      summary:
142                        //              Render the plot on the chart.
143                        //      dim: Object
144                        //              An object of the form { width, height }.
145                        //      offsets: Object
146                        //              An object of the form { l, r, t, b }.
147                        //      returns: dojox.charting.plot2d.Pie
148                        //              A reference to this plot for functional chaining.
149                        if(!this.dirty){ return this; }
150                        this.resetEvents();
151                        this.dirty = false;
152                        this._eventSeries = {};
153                        this.cleanGroup();
154                        var s = this.group, t = this.chart.theme;
155
156                        if(!this.run || !this.run.data.length){
157                                return this;
158                        }
159
160                        // calculate the geometry
161                        var rx = (dim.width  - offsets.l - offsets.r) / 2,
162                                ry = (dim.height - offsets.t - offsets.b) / 2,
163                                r  = Math.min(rx, ry),
164                                taFont = "font" in this.opt ? this.opt.font : t.axis.font,
165                                size = taFont ? g.normalizedLength(g.splitFontString(taFont).size) : 0,
166                                taFontColor = "fontColor" in this.opt ? this.opt.fontColor : t.axis.fontColor,
167                                startAngle = m._degToRad(this.opt.startAngle),
168                                start = startAngle, step, filteredRun, slices, labels, shift, labelR,
169                                run = this.run.data,
170                                events = this.events();
171                        if(typeof run[0] == "number"){
172                                filteredRun = df.map(run, "x ? Math.max(x, 0) : 0");
173                                if(df.every(filteredRun, "<= 0")){
174                                        return this;
175                                }
176                                slices = df.map(filteredRun, "/this", df.foldl(filteredRun, "+", 0));
177                                if(this.opt.labels){
178                                        labels = arr.map(slices, function(x){
179                                                return x > 0 ? this._getLabel(x * 100) + "%" : "";
180                                        }, this);
181                                }
182                        }else{
183                                filteredRun = df.map(run, "x ? Math.max(x.y, 0) : 0");
184                                if(df.every(filteredRun, "<= 0")){
185                                        return this;
186                                }
187                                slices = df.map(filteredRun, "/this", df.foldl(filteredRun, "+", 0));
188                                if(this.opt.labels){
189                                        labels = arr.map(slices, function(x, i){
190                                                if(x <= 0){ return ""; }
191                                                var v = run[i];
192                                                return "text" in v ? v.text : this._getLabel(x * 100) + "%";
193                                        }, this);
194                                }
195                        }
196                        var themes = df.map(run, function(v, i){
197                                if(v === null || typeof v == "number"){
198                                        return t.next("slice", [this.opt, this.run], true);
199                                }
200                                return t.next("slice", [this.opt, this.run, v], true);
201                        }, this);
202                        if(this.opt.labels){
203                                shift = df.foldl1(df.map(labels, function(label, i){
204                                        var font = themes[i].series.font;
205                                        return g._base._getTextBox(label, {font: font}).w;
206                                }, this), "Math.max(a, b)") / 2;
207                                if(this.opt.labelOffset < 0){
208                                        r = Math.min(rx - 2 * shift, ry - size) + this.opt.labelOffset;
209                                }
210                                labelR = r - this.opt.labelOffset;
211                        }
212                        if("radius" in this.opt){
213                                r = this.opt.radius;
214                                labelR = r - this.opt.labelOffset;
215                        }
216                        var     circle = {
217                                        cx: offsets.l + rx,
218                                        cy: offsets.t + ry,
219                                        r:  r
220                                };
221
222                        this.dyn = [];
223                        // draw slices
224                        var eventSeries = new Array(slices.length);
225                        arr.some(slices, function(slice, i){
226                                if(slice < 0){
227                                        // degenerated slice
228                                        return false;   // continue
229                                }
230                                if(slice == 0){
231                                  this.dyn.push({fill: null, stroke: null});
232                                  return false;
233                                }
234                                var v = run[i], theme = themes[i], specialFill;
235                                if(slice >= 1){
236                                        // whole pie
237                                        specialFill = this._plotFill(theme.series.fill, dim, offsets);
238                                        specialFill = this._shapeFill(specialFill,
239                                                {
240                                                        x: circle.cx - circle.r, y: circle.cy - circle.r,
241                                                        width: 2 * circle.r, height: 2 * circle.r
242                                                });
243                                        specialFill = this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, circle.r);
244                                        var shape = s.createCircle(circle).setFill(specialFill).setStroke(theme.series.stroke);
245                                        this.dyn.push({fill: specialFill, stroke: theme.series.stroke});
246
247                                        if(events){
248                                                var o = {
249                                                        element: "slice",
250                                                        index:   i,
251                                                        run:     this.run,
252                                                        shape:   shape,
253                                                        x:       i,
254                                                        y:       typeof v == "number" ? v : v.y,
255                                                        cx:      circle.cx,
256                                                        cy:      circle.cy,
257                                                        cr:      r
258                                                };
259                                                this._connectEvents(o);
260                                                eventSeries[i] = o;
261                                        }
262
263                                        return true;    // stop iteration
264                                }
265                                // calculate the geometry of the slice
266                                var end = start + slice * 2 * Math.PI;
267                                if(i + 1 == slices.length){
268                                        end = startAngle + 2 * Math.PI;
269                                }
270                                var     step = end - start,
271                                        x1 = circle.cx + r * Math.cos(start),
272                                        y1 = circle.cy + r * Math.sin(start),
273                                        x2 = circle.cx + r * Math.cos(end),
274                                        y2 = circle.cy + r * Math.sin(end);
275                                // draw the slice
276                                var fanSize = m._degToRad(this.opt.fanSize);
277                                if(theme.series.fill && theme.series.fill.type === "radial" && this.opt.radGrad === "fan" && step > fanSize){
278                                        var group = s.createGroup(), nfans = Math.ceil(step / fanSize), delta = step / nfans;
279                                        specialFill = this._shapeFill(theme.series.fill,
280                                                {x: circle.cx - circle.r, y: circle.cy - circle.r, width: 2 * circle.r, height: 2 * circle.r});
281                                        for(var j = 0; j < nfans; ++j){
282                                                var fansx = j == 0 ? x1 : circle.cx + r * Math.cos(start + (j - FUDGE_FACTOR) * delta),
283                                                        fansy = j == 0 ? y1 : circle.cy + r * Math.sin(start + (j - FUDGE_FACTOR) * delta),
284                                                        fanex = j == nfans - 1 ? x2 : circle.cx + r * Math.cos(start + (j + 1 + FUDGE_FACTOR) * delta),
285                                                        faney = j == nfans - 1 ? y2 : circle.cy + r * Math.sin(start + (j + 1 + FUDGE_FACTOR) * delta),
286                                                        fan = group.createPath().
287                                                                moveTo(circle.cx, circle.cy).
288                                                                lineTo(fansx, fansy).
289                                                                arcTo(r, r, 0, delta > Math.PI, true, fanex, faney).
290                                                                lineTo(circle.cx, circle.cy).
291                                                                closePath().
292                                                                setFill(this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, r, start + (j + 0.5) * delta, start + (j + 0.5) * delta));
293                                        }
294                                        group.createPath().
295                                                moveTo(circle.cx, circle.cy).
296                                                lineTo(x1, y1).
297                                                arcTo(r, r, 0, step > Math.PI, true, x2, y2).
298                                                lineTo(circle.cx, circle.cy).
299                                                closePath().
300                                                setStroke(theme.series.stroke);
301                                        shape = group;
302                                }else{
303                                        shape = s.createPath().
304                                                moveTo(circle.cx, circle.cy).
305                                                lineTo(x1, y1).
306                                                arcTo(r, r, 0, step > Math.PI, true, x2, y2).
307                                                lineTo(circle.cx, circle.cy).
308                                                closePath().
309                                                setStroke(theme.series.stroke);
310                                        var specialFill = theme.series.fill;
311                                        if(specialFill && specialFill.type === "radial"){
312                                                specialFill = this._shapeFill(specialFill, {x: circle.cx - circle.r, y: circle.cy - circle.r, width: 2 * circle.r, height: 2 * circle.r});
313                                                if(this.opt.radGrad === "linear"){
314                                                        specialFill = this._pseudoRadialFill(specialFill, {x: circle.cx, y: circle.cy}, r, start, end);
315                                                }
316                                        }else if(specialFill && specialFill.type === "linear"){
317                                                specialFill = this._plotFill(specialFill, dim, offsets);
318                                                specialFill = this._shapeFill(specialFill, shape.getBoundingBox());
319                                        }
320                                        shape.setFill(specialFill);
321                                }
322                                this.dyn.push({fill: specialFill, stroke: theme.series.stroke});
323
324                                if(events){
325                                        var o = {
326                                                element: "slice",
327                                                index:   i,
328                                                run:     this.run,
329                                                shape:   shape,
330                                                x:       i,
331                                                y:       typeof v == "number" ? v : v.y,
332                                                cx:      circle.cx,
333                                                cy:      circle.cy,
334                                                cr:      r
335                                        };
336                                        this._connectEvents(o);
337                                        eventSeries[i] = o;
338                                }
339
340                                start = end;
341
342                                return false;   // continue
343                        }, this);
344                        // draw labels
345                        if(this.opt.labels){
346                                if(this.opt.labelStyle == "default"){
347                                        start = startAngle;
348                                        arr.some(slices, function(slice, i){
349                                                if(slice <= 0){
350                                                        // degenerated slice
351                                                        return false;   // continue
352                                                }
353                                                var theme = themes[i];
354                                                if(slice >= 1){
355                                                        // whole pie
356                                                        var v = run[i], elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"](
357                                                                        this.chart, s, circle.cx, circle.cy + size / 2, "middle", labels[i],
358                                                                        theme.series.font, theme.series.fontColor);
359                                                        if(this.opt.htmlLabels){
360                                                                this.htmlElements.push(elem);
361                                                        }
362                                                        return true;    // stop iteration
363                                                }
364                                                // calculate the geometry of the slice
365                                                var end = start + slice * 2 * Math.PI, v = run[i];
366                                                if(i + 1 == slices.length){
367                                                        end = startAngle + 2 * Math.PI;
368                                                }
369                                                var     labelAngle = (start + end) / 2,
370                                                        x = circle.cx + labelR * Math.cos(labelAngle),
371                                                        y = circle.cy + labelR * Math.sin(labelAngle) + size / 2;
372                                                // draw the label
373                                                var elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"]
374                                                                (this.chart, s, x, y, "middle", labels[i], theme.series.font, theme.series.fontColor);
375                                                if(this.opt.htmlLabels){
376                                                        this.htmlElements.push(elem);
377                                                }
378                                                start = end;
379                                                return false;   // continue
380                                        }, this);
381                                }else if(this.opt.labelStyle == "columns"){
382                                        start = startAngle;
383                                        //calculate label angles
384                                        var labeledSlices = [];
385                                        arr.forEach(slices, function(slice, i){
386                                                var end = start + slice * 2 * Math.PI;
387                                                if(i + 1 == slices.length){
388                                                        end = startAngle + 2 * Math.PI;
389                                                }
390                                                var labelAngle = (start + end) / 2;
391                                                labeledSlices.push({
392                                                        angle: labelAngle,
393                                                        left: Math.cos(labelAngle) < 0,
394                                                        theme: themes[i],
395                                                        index: i,
396                                                        omit: end - start < 0.001
397                                                });
398                                                start = end;
399                                        });
400                                        //calculate label radius to each slice
401                                        var labelHeight = g._base._getTextBox("a",{font:taFont}).h;
402                                        this._getProperLabelRadius(labeledSlices, labelHeight, circle.r * 1.1);
403                                        //draw label and wiring
404                                        arr.forEach(labeledSlices, function(slice, i){
405                                                if (!slice.omit) {
406                                                        var leftColumn = circle.cx - circle.r * 2,
407                                                                rightColumn = circle.cx + circle.r * 2,
408                                                                labelWidth = g._base._getTextBox(labels[i], {font: taFont}).w,
409                                                                x = circle.cx + slice.labelR * Math.cos(slice.angle),
410                                                                y = circle.cy + slice.labelR * Math.sin(slice.angle),
411                                                                jointX = (slice.left) ? (leftColumn + labelWidth) : (rightColumn - labelWidth),
412                                                                labelX = (slice.left) ? leftColumn : jointX;
413                                                        var wiring = s.createPath().moveTo(circle.cx + circle.r * Math.cos(slice.angle), circle.cy + circle.r * Math.sin(slice.angle))
414                                                        if (Math.abs(slice.labelR * Math.cos(slice.angle)) < circle.r * 2 - labelWidth) {
415                                                                wiring.lineTo(x, y);
416                                                        }
417                                                        wiring.lineTo(jointX, y).setStroke(slice.theme.series.labelWiring);
418                                                        var elem = da.createText[this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx"](
419                                                                this.chart, s, labelX, y, "left", labels[i], slice.theme.series.font, slice.theme.series.fontColor);
420                                                        if (this.opt.htmlLabels) {
421                                                                this.htmlElements.push(elem);
422                                                        }
423                                                }
424                                        },this);
425                                }
426                        }
427                        // post-process events to restore the original indexing
428                        var esi = 0;
429                        this._eventSeries[this.run.name] = df.map(run, function(v){
430                                return v <= 0 ? null : eventSeries[esi++];
431                        });
432                        return this;    //      dojox.charting.plot2d.Pie
433                },
434               
435                _getProperLabelRadius: function(slices, labelHeight, minRidius){
436                        var leftCenterSlice = {},rightCenterSlice = {},leftMinSIN = 1, rightMinSIN = 1;
437                        if (slices.length == 1) {
438                                slices[0].labelR = minRidius;
439                                return;
440                        }
441                        for(var i = 0;i<slices.length;i++){
442                                var tempSIN = Math.abs(Math.sin(slices[i].angle));
443                                if(slices[i].left){
444                                        if(leftMinSIN > tempSIN){
445                                                leftMinSIN = tempSIN;
446                                                leftCenterSlice = slices[i];
447                                        }
448                                }else{
449                                        if(rightMinSIN > tempSIN){
450                                                rightMinSIN = tempSIN;
451                                                rightCenterSlice = slices[i];
452                                        }
453                                }
454                        }
455                        leftCenterSlice.labelR = rightCenterSlice.labelR = minRidius;
456                        this._calculateLabelR(leftCenterSlice,slices,labelHeight);
457                        this._calculateLabelR(rightCenterSlice,slices,labelHeight);
458                },
459                _calculateLabelR: function(firstSlice,slices,labelHeight){
460                        var i = firstSlice.index,length = slices.length,
461                                currentLabelR = firstSlice.labelR;
462                        while(!(slices[i%length].left ^ slices[(i+1)%length].left)){
463                                if (!slices[(i + 1) % length].omit) {
464                                        var nextLabelR = (Math.sin(slices[i % length].angle) * currentLabelR + ((slices[i % length].left) ? (-labelHeight) : labelHeight)) /
465                                        Math.sin(slices[(i + 1) % length].angle);
466                                        currentLabelR = (nextLabelR < firstSlice.labelR) ? firstSlice.labelR : nextLabelR;
467                                        slices[(i + 1) % length].labelR = currentLabelR;
468                                }
469                                i++;
470                        }
471                        i = firstSlice.index;
472                        var j = (i == 0)?length-1 : i - 1;
473                        while(!(slices[i].left ^ slices[j].left)){
474                                if (!slices[j].omit) {
475                                        var nextLabelR = (Math.sin(slices[i].angle) * currentLabelR + ((slices[i].left) ? labelHeight : (-labelHeight))) /
476                                        Math.sin(slices[j].angle);
477                                        currentLabelR = (nextLabelR < firstSlice.labelR) ? firstSlice.labelR : nextLabelR;
478                                        slices[j].labelR = currentLabelR;
479                                }
480                                i--;j--;
481                                i = (i < 0)?i+slices.length:i;
482                                j = (j < 0)?j+slices.length:j;
483                        }
484                },
485                // utilities
486                _getLabel: function(number){
487                        return dc.getLabel(number, this.opt.fixed, this.opt.precision);
488                }
489        });
490});
Note: See TracBrowser for help on using the repository browser.