source: Dev/trunk/src/client/dojox/charting/plot2d/Spider.js @ 533

Last change on this file since 533 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 16.7 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/connect", "dojo/_base/array",
2        "dojo/dom-geometry", "dojo/_base/fx", "dojo/fx", "dojo/sniff",
3        "./Base", "./_PlotEvents", "./common", "../axis2d/common",
4        "dojox/gfx", "dojox/gfx/matrix", "dojox/gfx/fx", "dojox/lang/functional",
5        "dojox/lang/utils", "dojo/fx/easing"],
6        function(lang, declare, hub, arr, domGeom, baseFx, coreFx, has,
7                        Base, PlotEvents, dc, da, g, m, gfxfx, df, du, easing){
8
9        var FUDGE_FACTOR = 0.2; // use to overlap fans
10
11        var Spider = declare("dojox.charting.plot2d.Spider", [Base, PlotEvents], {
12                // summary:
13                //              The plot that represents a typical Spider chart.
14                defaultParams: {
15                        labels:                 true,
16                        ticks:                  false,
17                        fixed:                  true,
18                        precision:              1,
19                        labelOffset:    -10,
20                        labelStyle:             "default",      // default/rows/auto
21                        htmlLabels:             true,           // use HTML to draw labels
22                        startAngle:             -90,            // start angle for slices in degrees
23                        divisions:               3,                     // radius tick count
24                        axisColor:               "",            // spider axis color
25                        axisWidth:               0,                     // spider axis stroke width
26                        spiderColor:     "",            // spider web color
27                        spiderWidth:     0,                     // spider web stroke width
28                        seriesWidth:     0,                     // plot border with
29                        seriesFillAlpha: 0.2,           // plot fill alpha
30                        spiderOrigin:    0.16,
31                        markerSize:              3,                     // radius of plot vertex (px)
32                        spiderType:              "polygon", //"circle"
33                        animationType:   easing.backOut,
34                        axisTickFont:           "",
35                        axisTickFontColor:      "",
36                        axisFont:                       "",
37                        axisFontColor:          ""
38                },
39                optionalParams: {
40                        radius:         0,
41                        font:           "",
42                        fontColor:      ""
43                },
44
45                constructor: function(chart, kwArgs){
46                        // summary:
47                        //              Create a Spider plot.
48                        // chart: dojox/charting/Chart
49                        //              The chart this plot belongs to.
50                        // kwArgs: dojox.charting.plot2d.__DefaultCtorArgs?
51                        //              An optional keyword arguments object to help define this plot's parameters.
52                        this.opt = lang.clone(this.defaultParams);
53                        du.updateWithObject(this.opt, kwArgs);
54                        du.updateWithPattern(this.opt, kwArgs, this.optionalParams);
55                        this.dyn = [];
56                        this.datas = {};
57                        this.labelKey = [];
58                        this.oldSeriePoints = {};
59                        this.animations = {};
60                },
61                clear: function(){
62                        // summary:
63                        //              Clear out all of the information tied to this plot.
64                        // returns: dojox/charting/plot2d/Spider
65                        //              A reference to this plot for functional chaining.
66                        this.inherited(arguments);
67                        this.dyn = [];
68                        this.axes = [];
69                        this.datas = {};
70                        this.labelKey = [];
71                        this.oldSeriePoints = {};
72                        this.animations = {};
73                        return this;    //      dojox/charting/plot2d/Spider
74                },
75                setAxis: function(axis){
76                        // summary:
77                        //              Optionally set axis min and max property.
78                        // returns: dojox/charting/plot2d/Spider
79                        //              The reference to this plot for functional chaining.
80
81                        // override the computed min/max with provided values if any
82                        if(axis){
83                                if(axis.opt.min != undefined){
84                                        this.datas[axis.name].min = axis.opt.min;
85                                }
86                                if(axis.opt.max != undefined){
87                                        this.datas[axis.name].max = axis.opt.max;
88                                }
89                        }
90                        return this;    //      dojox/charting/plot2d/Spider
91                },
92                addSeries: function(run){
93                        // summary:
94                        //              Add a data series to this plot.
95                        // run: dojox.charting.Series
96                        //              The series to be added.
97                        // returns: dojox/charting/plot2d/Base
98                        //              A reference to this plot for functional chaining.
99                        this.series.push(run);
100                        var key;
101                        for(key in run.data){
102                                var val = run.data[key],
103                                        data = this.datas[key];
104                                if(data){
105                                        data.vlist.push(val);
106                                        data.min = Math.min(data.min, val);
107                                        data.max = Math.max(data.max, val);
108                                }else{
109                                        var axisKey = "__"+key;
110                                        this.axes.push(axisKey);
111                                        this[axisKey] = key;
112                                        this.datas[key] = {min: val, max: val, vlist: [val]};
113                                }
114                        }
115                        if(this.labelKey.length <= 0){
116                                for(key in run.data){
117                                        this.labelKey.push(key);
118                                }
119                        }
120                        return this;    //      dojox.charting.plot2d.Base
121                },
122                getSeriesStats: function(){
123                        // summary:
124                        //              Calculate the min/max on all attached series in both directions.
125                        // returns: Object
126                        //              {hmin, hmax, vmin, vmax} min/max in both directions.
127                        return dc.collectSimpleStats(this.series); // Object
128                },
129                render: function(dim, offsets){
130                        // summary:
131                        //              Render the plot on the chart.
132                        // dim: Object
133                        //              An object of the form { width, height }.
134                        // offsets: Object
135                        //              An object of the form { l, r, t, b }.
136                        // returns: dojox/charting/plot2d/Spider
137                        //              A reference to this plot for functional chaining.
138                        if(!this.dirty){ return this; }
139                        this.dirty = false;
140                        this.cleanGroup();
141                        var s = this.group, t = this.chart.theme;
142                        this.resetEvents();
143
144                        if(!this.series || !this.series.length){
145                                return this;
146                        }
147
148                        // calculate the geometry
149                        var o = this.opt, ta = t.axis,
150                                rx = (dim.width  - offsets.l - offsets.r) / 2,
151                                ry = (dim.height - offsets.t - offsets.b) / 2,
152                                r  = Math.min(rx, ry),
153                                axisTickFont = o.font || (ta.majorTick && ta.majorTick.font) || (ta.tick && ta.tick.font) || "normal normal normal 7pt Tahoma",
154                                axisFont = o.axisFont || (ta.tick && ta.tick.titleFont) || "normal normal normal 11pt Tahoma",
155                                axisTickFontColor = o.axisTickFontColor || (ta.majorTick && ta.majorTick.fontColor) || (ta.tick && ta.tick.fontColor) || "silver",
156                                axisFontColor = o.axisFontColor || (ta.tick && ta.tick.titleFontColor) || "black",
157                                axisColor = o.axisColor || (ta.tick && ta.tick.axisColor) || "silver",
158                                spiderColor = o.spiderColor || (ta.tick && ta.tick.spiderColor) || "silver",
159                                axisWidth = o.axisWidth || (ta.stroke && ta.stroke.width) || 2,
160                                spiderWidth = o.spiderWidth || (ta.stroke && ta.stroke.width) || 2,
161                                seriesWidth = o.seriesWidth || (ta.stroke && ta.stroke.width) || 2,
162                                asize = g.normalizedLength(g.splitFontString(axisFont).size),
163                                startAngle = m._degToRad(o.startAngle),
164                                start = startAngle, labels, shift, labelR,
165                                outerPoints, innerPoints, divisionPoints, divisionRadius, labelPoints,
166                                ro = o.spiderOrigin, dv = o.divisions >= 3 ? o.divisions : 3, ms = o.markerSize,
167                                spt = o.spiderType, at = o.animationType, lboffset = o.labelOffset < -10 ? o.labelOffset : -10,
168                                axisExtra = 0.2,
169                                i, j, point, len, fontWidth, render, serieEntry, run, data, min, max, distance;
170                       
171                        if(o.labels){
172                                labels = arr.map(this.series, function(s){
173                                        return s.name;
174                                }, this);
175                                shift = df.foldl1(df.map(labels, function(label){
176                                        var font = t.series.font;
177                                        return g._base._getTextBox(label, {
178                                                font: font
179                                        }).w;
180                                }, this), "Math.max(a, b)") / 2;
181                                r = Math.min(rx - 2 * shift, ry - asize) + lboffset;
182                                labelR = r - lboffset;
183                        }
184                        if("radius" in o){
185                                r = o.radius;
186                                labelR = r - lboffset;
187                        }
188                        r /= (1+axisExtra);
189                        var circle = {
190                                cx: offsets.l + rx,
191                                cy: offsets.t + ry,
192                                r: r
193                        };
194
195                        for (i = this.series.length - 1; i >= 0; i--){
196                                serieEntry = this.series[i];
197                                if(!this.dirty && !serieEntry.dirty){
198                                        t.skip();
199                                        continue;
200                                }
201                                serieEntry.cleanGroup();
202                                run = serieEntry.data;
203                                if(run !== null){
204                                        len = this._getObjectLength(run);
205                                        //construct connect points
206                                        if(!outerPoints || outerPoints.length <= 0){
207                                                outerPoints = [], innerPoints = [], labelPoints = [];
208                                                this._buildPoints(outerPoints, len, circle, r, start, true, dim);
209                                                this._buildPoints(innerPoints, len, circle, r*ro, start, true, dim);
210                                                this._buildPoints(labelPoints, len, circle, labelR, start, false, dim);
211                                                if(dv > 2){
212                                                        divisionPoints = [], divisionRadius = [];
213                                                        for (j = 0; j < dv - 2; j++){
214                                                                divisionPoints[j] = [];
215                                                                this._buildPoints(divisionPoints[j], len, circle, r*(ro + (1-ro)*(j+1)/(dv-1)), start, true, dim);
216                                                                divisionRadius[j] = r*(ro + (1-ro)*(j+1)/(dv-1));
217                                                        }
218                                                }
219                                        }
220                                }
221                        }
222                       
223                        //draw Spider
224                        //axis
225                        var axisGroup = s.createGroup(), axisStroke = {color: axisColor, width: axisWidth},
226                                spiderStroke = {color: spiderColor, width: spiderWidth};
227                        for (j = outerPoints.length - 1; j >= 0; --j){
228                                point = outerPoints[j];
229                                var st = {
230                                                x: point.x + (point.x - circle.cx) * axisExtra,
231                                                y: point.y + (point.y - circle.cy) * axisExtra
232                                        },
233                                        nd = {
234                                                x: point.x + (point.x - circle.cx) * axisExtra / 2,
235                                                y: point.y + (point.y - circle.cy) * axisExtra / 2
236                                        };
237                                axisGroup.createLine({
238                                        x1: circle.cx,
239                                        y1: circle.cy,
240                                        x2: st.x,
241                                        y2: st.y
242                                }).setStroke(axisStroke);
243                                //arrow
244                                this._drawArrow(axisGroup, st, nd, axisStroke);
245                        }
246                       
247                        // draw the label
248                        var labelGroup = s.createGroup();
249                        for (j = labelPoints.length - 1; j >= 0; --j){
250                                point = labelPoints[j];
251                                fontWidth = g._base._getTextBox(this.labelKey[j], {font: axisFont}).w || 0;
252                                render = this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx";
253                                var elem = da.createText[render](this.chart, labelGroup, (!domGeom.isBodyLtr() && render == "html") ? (point.x + fontWidth - dim.width) : point.x, point.y,
254                                                        "middle", this.labelKey[j], axisFont, axisFontColor);
255                                if(this.opt.htmlLabels){
256                                        this.htmlElements.push(elem);
257                                }
258                        }
259                       
260                        //spider web: polygon or circle
261                        var spiderGroup = s.createGroup();
262                        if(spt == "polygon"){
263                                spiderGroup.createPolyline(outerPoints).setStroke(spiderStroke);
264                                spiderGroup.createPolyline(innerPoints).setStroke(spiderStroke);
265                                if(divisionPoints.length > 0){
266                                        for (j = divisionPoints.length - 1; j >= 0; --j){
267                                                spiderGroup.createPolyline(divisionPoints[j]).setStroke(spiderStroke);
268                                        }
269                                }
270                        }else{//circle
271                                spiderGroup.createCircle({cx: circle.cx, cy: circle.cy, r: r}).setStroke(spiderStroke);
272                                spiderGroup.createCircle({cx: circle.cx, cy: circle.cy, r: r*ro}).setStroke(spiderStroke);
273                                if(divisionRadius.length > 0){
274                                        for (j = divisionRadius.length - 1; j >= 0; --j){
275                                                spiderGroup.createCircle({cx: circle.cx, cy: circle.cy, r: divisionRadius[j]}).setStroke(spiderStroke);
276                                        }
277                                }
278                        }
279                        //text
280                        len = this._getObjectLength(this.datas);
281                        var textGroup = s.createGroup(), k = 0;
282                        for(var key in this.datas){
283                                data = this.datas[key];
284                                min = data.min;
285                                max = data.max;
286                                distance = max - min;
287                                        end = start + 2 * Math.PI * k / len;
288                                for (i = 0; i < dv; i++){
289                                        var text = min + distance*i/(dv-1);
290                                        point = this._getCoordinate(circle, r*(ro + (1-ro)*i/(dv-1)), end, dim);
291                                        text = this._getLabel(text);
292                                        fontWidth = g._base._getTextBox(text, {font: axisTickFont}).w || 0;
293                                                render = this.opt.htmlLabels && g.renderer != "vml" ? "html" : "gfx";
294                                        if(this.opt.htmlLabels){
295                                                this.htmlElements.push(da.createText[render]
296                                                        (this.chart, textGroup, (!domGeom.isBodyLtr() && render == "html") ? (point.x + fontWidth - dim.width) : point.x, point.y,
297                                                                "start", text, axisTickFont, axisTickFontColor));
298                                        }
299                                }
300                                k++;
301                        }
302                       
303                        //draw series (animation)
304                        this.chart.seriesShapes = {};
305                        for (i = this.series.length - 1; i >= 0; i--){
306                                serieEntry = this.series[i];
307                                run = serieEntry.data;
308                                if(run !== null){
309                                        //series polygon
310                                        var seriePoints = [], tipData = [];
311                                        k = 0;
312                                        for(key in run){
313                                                data = this.datas[key];
314                                                min = data.min;
315                                                max = data.max;
316                                                distance = max - min;
317                                                var entry = run[key], end = start + 2 * Math.PI * k / len;
318                                                        point = this._getCoordinate(circle, r*(ro + (1-ro)*(entry-min)/distance), end, dim);
319                                                seriePoints.push(point);
320                                                tipData.push({sname: serieEntry.name, key: key, data: entry});
321                                                k++;
322                                        }
323                                        seriePoints[seriePoints.length] = seriePoints[0];
324                                        tipData[tipData.length] = tipData[0];
325                                        var polygonBoundRect = this._getBoundary(seriePoints),
326                                                theme = t.next("spider", [o, serieEntry]), ts = serieEntry.group,
327                                                f = g.normalizeColor(theme.series.fill), sk = {color: theme.series.fill, width: seriesWidth};
328                                        f.a = o.seriesFillAlpha;
329                                        serieEntry.dyn = {fill: f, stroke: sk};
330                                       
331                                        var osps = this.oldSeriePoints[serieEntry.name];
332                                        var cs = this._createSeriesEntry(ts, (osps || innerPoints), seriePoints, f, sk, r, ro, ms, at);
333                                        this.chart.seriesShapes[serieEntry.name] = cs;
334                                        this.oldSeriePoints[serieEntry.name] = seriePoints;
335                                       
336                                        var po = {
337                                                element: "spider_poly",
338                                                index:   i,
339                                                id:              "spider_poly_"+serieEntry.name,
340                                                run:     serieEntry,
341                                                plot:    this,
342                                                shape:   cs.poly,
343                                                parent:  ts,
344                                                brect:   polygonBoundRect,
345                                                cx:              circle.cx,
346                                                cy:              circle.cy,
347                                                cr:              r,
348                                                f:               f,
349                                                s:               s
350                                        };
351                                        this._connectEvents(po);
352                                       
353                                        var so = {
354                                                element: "spider_plot",
355                                                index:   i,
356                                                id:              "spider_plot_"+serieEntry.name,
357                                                run:     serieEntry,
358                                                plot:    this,
359                                                shape:   serieEntry.group
360                                        };
361                                        this._connectEvents(so);
362                                       
363                                        arr.forEach(cs.circles, function(c, i){
364                                                var co = {
365                                                                element: "spider_circle",
366                                                                index:   i,
367                                                                id:              "spider_circle_"+serieEntry.name+i,
368                                                                run:     serieEntry,
369                                                                plot:    this,
370                                                                shape:   c,
371                                                                parent:  ts,
372                                                                tdata:   tipData[i],
373                                                                cx:              seriePoints[i].x,
374                                                                cy:              seriePoints[i].y,
375                                                                f:               f,
376                                                                s:               s
377                                                        };
378                                                this._connectEvents(co);
379                                        }, this);
380                                }
381                        }
382                        return this;    //      dojox/charting/plot2d/Spider
383                },
384                _createSeriesEntry: function(ts, osps, sps, f, sk, r, ro, ms, at){
385                        //polygon
386                        var spoly = ts.createPolyline(osps).setFill(f).setStroke(sk), scircle = [];
387                        for (var j = 0; j < osps.length; j++){
388                                var point = osps[j], cr = ms;
389                                var circle = ts.createCircle({cx: point.x, cy: point.y, r: cr}).setFill(f).setStroke(sk);
390                                scircle.push(circle);
391                        }
392                       
393                        var anims = arr.map(sps, function(np, j){
394                                // create animation
395                                var sp = osps[j],
396                                        anim = new baseFx.Animation({
397                                        duration: 1000,
398                                        easing:   at,
399                                        curve:    [sp.y, np.y]
400                                });
401                                var spl = spoly, sc = scircle[j];
402                                hub.connect(anim, "onAnimate", function(y){
403                                        //apply poly
404                                        var pshape = spl.getShape();
405                                        pshape.points[j].y = y;
406                                        spl.setShape(pshape);
407                                        //apply circle
408                                        var cshape = sc.getShape();
409                                        cshape.cy = y;
410                                        sc.setShape(cshape);
411                                });
412                                return anim;
413                        });
414                       
415                        var anims1 = arr.map(sps, function(np, j){
416                                // create animation
417                                var sp = osps[j],
418                                        anim = new baseFx.Animation({
419                                        duration: 1000,
420                                        easing:   at,
421                                        curve:    [sp.x, np.x]
422                                });
423                                var spl = spoly, sc = scircle[j];
424                                hub.connect(anim, "onAnimate", function(x){
425                                        //apply poly
426                                        var pshape = spl.getShape();
427                                        pshape.points[j].x = x;
428                                        spl.setShape(pshape);
429                                        //apply circle
430                                        var cshape = sc.getShape();
431                                        cshape.cx = x;
432                                        sc.setShape(cshape);
433                                });
434                                return anim;
435                        });
436                        var masterAnimation = coreFx.combine(anims.concat(anims1)); //dojo.fx.chain(anims);
437                        masterAnimation.play();
438                        return {group :ts, poly: spoly, circles: scircle};
439                },
440                plotEvent: function(o){
441                        // summary:
442                        //              Stub function for use by specific plots.
443                        // o: Object
444                        //              An object intended to represent event parameters.
445                        if(o.element == "spider_plot"){
446                                //dojo gfx function "moveToFront" not work in IE
447                                if(o.type == "onmouseover" && !has("ie")){
448                                        o.shape.moveToFront();
449                                }
450                        }
451                },
452
453                tooltipFunc: function(o){
454                        if(o.element == "spider_circle"){
455                                return o.tdata.sname + "<br/>" + o.tdata.key + "<br/>" + o.tdata.data;
456                        }else{
457                                return null;
458                        }
459                },
460
461                _getBoundary: function(points){
462                        var xmax = points[0].x,
463                                xmin = points[0].x,
464                                ymax = points[0].y,
465                                ymin = points[0].y;
466                        for(var i = 0; i < points.length; i++){
467                                var point = points[i];
468                                xmax = Math.max(point.x, xmax);
469                                ymax = Math.max(point.y, ymax);
470                                xmin = Math.min(point.x, xmin);
471                                ymin = Math.min(point.y, ymin);
472                        }
473                        return {
474                                x: xmin,
475                                y: ymin,
476                                width: xmax - xmin,
477                                height: ymax - ymin
478                        };
479                },
480               
481                _drawArrow: function(s, start, end, stroke){
482                        var len = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)),
483                                sin = (end.y - start.y)/len, cos = (end.x - start.x)/len,
484                                point2 = {x: end.x + (len/3)*(-sin), y: end.y + (len/3)*cos},
485                                point3 = {x: end.x + (len/3)*sin, y: end.y + (len/3)*(-cos)};
486                        s.createPolyline([start, point2, point3]).setFill(stroke.color).setStroke(stroke);
487                },
488               
489                _buildPoints: function(points, count, circle, radius, angle, recursive, dim){
490                        for(var i = 0; i < count; i++){
491                                var end = angle + 2 * Math.PI * i / count;
492                                points.push(this._getCoordinate(circle, radius, end, dim));
493                        }
494                        if(recursive){
495                                points.push(this._getCoordinate(circle, radius, angle + 2 * Math.PI, dim));
496                        }
497                },
498               
499                _getCoordinate: function(circle, radius, angle, dim){
500                        var x = circle.cx + radius * Math.cos(angle);
501                        if(has("dojo-bidi") && this.chart.isRightToLeft() && dim){
502                                x = dim.width - x;
503                        }
504                        return {
505                                x: x,
506                                y: circle.cy + radius * Math.sin(angle)
507                        }
508                },
509               
510                _getObjectLength: function(obj){
511                        var count = 0;
512                        if(lang.isObject(obj)){
513                                for(var key in obj){
514                                        count++;
515                                }
516                        }
517                        return count;
518                },
519
520                // utilities
521                _getLabel: function(number){
522                        return dc.getLabel(number, this.opt.fixed, this.opt.precision);
523                }
524        });
525
526        return Spider; // dojox/plot2d/Spider
527});
Note: See TracBrowser for help on using the repository browser.