source: Dev/trunk/src/client/dojox/charting/plot2d/Indicator.js

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

Added Dojo 1.9.3 release.

File size: 16.2 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./CartesianBase", "./_PlotEvents", "./common",
2    "../axis2d/common", "dojox/gfx", "dojox/lang/utils", "dojox/gfx/fx", "dojo/has"],
3        function(lang, array, declare, CartesianBase, _PlotEvents, dcpc, dcac, gfx, du, fx, has){
4
5        // all the code below should be removed when http://trac.dojotoolkit.org/ticket/11299 will be available
6        var getBoundingBox = function(shape){
7                return getTextBBox(shape, shape.getShape().text);
8        };
9        var getTextBBox = function(s, t){
10                var c = s.declaredClass;
11                var w, h;
12                if(c.indexOf("svg")!=-1){
13                        // try/catch the FF native getBBox error. cheaper than walking up in the DOM
14                        // hierarchy to check the conditions (bench show /10 )
15                        try {
16                                return lang.mixin({}, s.rawNode.getBBox());
17                        }catch (e){
18                                return null;
19                        }
20                }else if(c.indexOf("vml")!=-1){
21                        var rawNode = s.rawNode, _display = rawNode.style.display;
22                        rawNode.style.display = "inline";
23                        w = gfx.pt2px(parseFloat(rawNode.currentStyle.width));
24                        h = gfx.pt2px(parseFloat(rawNode.currentStyle.height));
25                        var sz = {x: 0, y: 0, width: w, height: h};
26                        // in VML, the width/height we get are in view coordinates
27                        // in our case we don't zoom the view so that is ok
28                        // It's impossible to get the x/y from the currentStyle.left/top,
29                        // because all negative coordinates are 'clipped' to 0.
30                        // (x:0 + translate(-100) -> x=0
31                        computeLocation(s, sz);
32                        rawNode.style.display = _display;
33                        return sz;
34                }else if(c.indexOf("silverlight")!=-1){
35                        var bb = {width: s.rawNode.actualWidth, height: s.rawNode.actualHeight};
36                        return computeLocation(s, bb, 0.75);
37                }else if(s.getTextWidth){
38                        // canvas
39                        w = s.getTextWidth();
40                        var font = s.getFont();
41                        var fz = font ? font.size : gfx.defaultFont.size;
42                        h = gfx.normalizedLength(fz);
43                        sz = {width: w, height: h};
44                        computeLocation(s, sz, 0.75);
45                        return sz;
46                }
47                return null;
48        };
49        var computeLocation =  function(s, sz, coef){
50                var width = sz.width, height = sz.height, sh = s.getShape(), align = sh.align;
51                switch (align) {
52                case "end":
53                        sz.x = sh.x - width;
54                        break;
55                case "middle":
56                        sz.x = sh.x - width / 2;
57                        break;
58                case "start":
59                default:
60                        sz.x = sh.x;
61                break;
62                }
63                coef = coef || 1;
64                sz.y = sh.y - height*coef; // rough approximation of the ascent!...
65                return sz;
66        };
67
68        /*=====
69        declare("dojox.charting.plot2d.__IndicatorCtorArgs", dojox.charting.plot2d.__CartesianCtorArgs, {
70                // summary:
71                //              A special keyword arguments object that is specific to a indicator "plot".
72
73                // lines: Boolean?
74                //              Whether the indicator lines are visible or not. The lines are displayed for each of the
75                //              'values' of the indicator. Default is true.
76                lines: true,
77
78                // labels: String?
79                //              Describes how the indicator labels are displayed.
80                //              Possible values are:
81                //              `"line"` for displaying the value of each indicator line,
82                //              `"marker"` for displaying the values of the markers of each indicator line,
83                //              `"trend"` for displaying the percentage variation from the first to the last indicator line,
84                //              `"none"` to prevent any label to display.
85                //              Default is "line".
86                labels: "line",
87
88                // markers: Boolean?
89                //              Whether the markers on the indicator lines are visible or not. The markers are displayed for each
90                //              of the indicator lines using the series attached to the indicator plot. Default is true.
91                markers: true,
92
93                // values: Boolean
94                //              The data values at which each of the indicator line is drawn. For a single value a Number can be provided
95                //              otherwise an Array of Number is required. fault is [].
96                values: [],
97
98                // offset: {x, y}?
99                //              A pair of (x, y) pixel coordinate to specifiy the offset between the end of the indicator line and the
100                //              position at which the labels are rendered. Default is no offset.
101                offset: {},
102
103                // start: Boolean?
104                //              Whether the label is rendered at the start or end of the indicator line. Default is false meaning end of
105                //              the line.
106                start: false,
107
108                // animate: Boolean?|Number?
109                //              Whether or not to animate the chart to place. When a Number it specifies the duration of the animation.
110                //              Default is false.
111                animate: false,
112
113                // vertical: Boolean?
114                //              Whether the indicator is vertical or not. Default is true.
115                vertical: true,
116
117                // fixed: Boolean?
118                //              Whether a fixed precision must be applied to data values for display. Default is true.
119                fixed: true,
120
121                // precision: Number?
122                //              The precision at which to round data values for display. Default is 0.
123                precision: 0,
124
125                // lineStroke: dojo/gfx/Stroke?
126                //              An optional stroke to use for indicator line.
127                lineStroke: {},
128
129                // lineOutline: dojo/gfx/Stroke?
130                //              An optional outline to use for indicator line.
131                lineOutline: {},
132
133                // lineShadow: dojo/gfx/Stroke?
134                //              An optional shadow to use for indicator line.
135                lineShadow: {},
136
137                // stroke: dojo.gfx.Stroke?
138                //              An optional stroke to use for indicator label background.
139                stroke: {},
140
141                // outline: dojo.gfx.Stroke?
142                //              An optional outline to use for indicator label background.
143                outline: {},
144
145                // shadow: dojo.gfx.Stroke?
146                //              An optional shadow to use for indicator label background.
147                shadow: {},
148
149                // fill: dojo.gfx.Fill?
150                //              An optional fill to use for indicator label background.
151                fill: {},
152
153                // fillFunc: Function?
154                //              An optional function to use to compute label background fill. It takes precedence over
155                //              fill property when available.
156                //      |               function fillFunc(index, values, data) {}
157                //              `index` is the index in the values array of the label being drawn.
158                //              `values` is the entire array of values.
159                //              `data` is the entire array of marker values.
160                fillFunc: null,
161
162                // labelFunc: Function?
163                //              An optional function to use to compute label text. It takes precedence over
164                //              the default text when available.
165                //      |               function labelFunc(index, values, data, fixed, precision, labels) {}
166                //              `index` is the index in the values array of the label being drawn.
167                //              `values` is the entire array of values.
168                //              `data` is the entire array of marker values.
169                //              `fixed` is true if fixed precision must be applied.
170                //              `precision` is the requested precision to be applied.
171                //              `labels` is the labels mode of the indicator.
172                labelFunc: null,
173
174                // font: String?
175                //              A font definition to use for indicator label background.
176                font: "",
177
178                // fontColor: String|dojo.Color?
179                //              The color to use for indicator label background.
180                fontColor: "",
181
182                // markerStroke: dojo.gfx.Stroke?
183                //              An optional stroke to use for indicator marker.
184                markerStroke: {},
185
186                // markerOutline: dojo.gfx.Stroke?
187                //              An optional outline to use for indicator marker.
188                markerOutline: {},
189
190                // markerShadow: dojo.gfx.Stroke?
191                //              An optional shadow to use for indicator marker.
192                markerShadow: {},
193
194                // markerFill: dojo.gfx.Fill?
195                //              An optional fill to use for indicator marker.
196                markerFill: {},
197
198                // markerSymbol: String?
199                //              An optional symbol string to use for indicator marker.
200                markerSymbol: "",
201        });
202        =====*/
203
204        var Indicator = declare("dojox.charting.plot2d.Indicator", [CartesianBase, _PlotEvents], {
205                // summary:
206                //              A "faux" plot that can be placed behind or above other plots to represent a line or multi-line
207                //              threshold on the chart.
208                defaultParams: {
209                        vertical: true,
210                        fixed: true,
211                        precision: 0,
212                        lines: true,
213                        labels: "line", // "line" | "trend" | "markers" | "none"
214                        markers: true
215                },
216                optionalParams: {
217                        lineStroke: {},
218                        outlineStroke: {},
219                        shadowStroke: {},
220                        lineFill: {},
221                        stroke:         {},
222                        outline:        {},
223                        shadow:         {},
224                        fill:           {},
225                        fillFunc:  null,
226                        labelFunc: null,
227                        font:           "",
228                        fontColor:      "",
229                        markerStroke:           {},
230                        markerOutline:          {},
231                        markerShadow:           {},
232                        markerFill:                     {},
233                        markerSymbol:           "",
234                        values: [],
235                        offset: {},
236                        start: false,
237                        animate: false
238                },
239
240                constructor: function(chart, kwArgs){
241                        // summary:
242                        //              Create the faux Grid plot.
243                        // chart: dojox/charting/Chart
244                        //              The chart this plot belongs to.
245                        // kwArgs: dojox.charting.plot2d.__GridCtorArgs?
246                        //              An optional keyword arguments object to help define the parameters of the underlying grid.
247                        this.opt = lang.clone(this.defaultParams);
248                        du.updateWithObject(this.opt, kwArgs);
249                        if(typeof kwArgs.values == "number"){
250                                kwArgs.values = [ kwArgs.values ];
251                        }
252                        du.updateWithPattern(this.opt, kwArgs, this.optionalParams);
253                        this.animate = this.opt.animate;
254                },
255                render: function(dim, offsets){
256                        if(this.zoom){
257                                return this.performZoom(dim, offsets);
258                        }
259
260                        if(!this.isDirty()){
261                                return this;
262                        }
263
264                        this.cleanGroup(null, true);
265
266                        if(!this.opt.values){
267                                return this;
268                        }
269
270                        this._updateIndicator();
271                        return this;
272                },
273                _updateIndicator: function(){
274                        var t = this.chart.theme;
275                        var hn = this._hAxis.name, vn = this._vAxis.name,
276                                hb = this._hAxis.getScaler().bounds, vb = this._vAxis.getScaler().bounds;
277                        var o = {};
278                        o[hn] = hb.from;
279                        o[vn] = vb.from;
280                        var min = this.toPage(o);
281                        o[hn] = hb.to;
282                        o[vn] = vb.to;
283                        var max = this.toPage(o);
284                        var events = this.events();
285                        var results = array.map(this.opt.values, function(value, index){
286                                return this._renderIndicator(value, index, hn, vn, min, max, events, this.animate);
287                        }, this);
288                        var length = results.length;
289                        if(this.opt.labels == "trend"){
290                                var v = this.opt.vertical;
291                                var first = this._data[0][0];
292                                var last = this._data[length - 1][0];
293                                var delta = last-first;
294                                var text = this.opt.labelFunc?this.opt.labelFunc(-1, this.values, this._data, this.opt.fixed, this.opt.precision):
295                                                (dcpc.getLabel(delta, this.opt.fixed, this.opt.precision)+" ("+dcpc.getLabel(100*delta/first, true, 2)+"%)");
296                                this._renderText(this.getGroup(), text, this.chart.theme, v?(results[0].x+results[length - 1].x)/2:results[1].x,
297                                        v?results[0].y:(results[1].y+results[length - 1].y)/2, -1, this.opt.values, this._data);
298                        }
299                        var lineFill = this.opt.lineFill!=undefined?this.opt.lineFill:t.indicator.lineFill;
300                        if(lineFill && length > 1){
301                                var x0 = Math.min(results[0].x1, results[length - 1].x1);
302                                var y0 =  Math.min(results[0].y1, results[length - 1].y1);
303                                var r = this.getGroup().createRect({x: x0, y: y0, width: Math.max(results[0].x2, results[length - 1].x2) - x0,
304                                                                                                                         height: Math.max(results[0].y2, results[length - 1].y2) - y0}).
305                                        setFill(lineFill);
306                                r.moveToBack();
307                        }
308                },
309                _renderIndicator: function(coord, index, hn, vn, min, max, events, animate){
310                        var t = this.chart.theme, c = this.chart.getCoords(), v = this.opt.vertical;
311
312                        var g = this.getGroup().createGroup();
313                        var mark = {};
314                        mark[hn] = v?coord:0;
315                        mark[vn] = v?0:coord;
316                        if(has("dojo-bidi")){
317                                mark.x = this._getMarkX(mark.x);
318                        }
319                        mark = this.toPage(mark);
320                        var visible = v?mark.x >= min.x && mark.x <= max.x:mark.y >= max.y && mark.y <= min.y;
321
322                        var cx = mark.x - c.x, cy = mark.y - c.y;
323                        var x1 = v?cx:min.x - c.x, y1 = v?min.y - c.y:cy, x2 = v?x1:max.x - c.x, y2 = v?max.y - c.y:y1;
324
325                        if(this.opt.lines && visible){
326                                var sh = this.opt.hasOwnProperty("lineShadow")?this.opt.lineShadow:t.indicator.lineShadow,
327                                        ls = this.opt.hasOwnProperty("lineStroke")?this.opt.lineStroke:t.indicator.lineStroke,
328                                        ol = this.opt.hasOwnProperty("lineOutline")?this.opt.lineOutline:t.indicator.lineOutline;
329                                if(sh){
330                                        g.createLine({x1: x1 + sh.dx, y1: y1 + sh.dy, x2: x2 + sh.dx, y2: y2 + sh.dy}).setStroke(sh);
331                                }
332                                if(ol){
333                                        ol = dcpc.makeStroke(ol);
334                                        ol.width = 2 * ol.width + (ls?ls.width:0);
335                                        g.createLine({x1: x1, y1: y1, x2: x2, y2: y2}).setStroke(ol);
336                                }
337                                g.createLine({x1: x1, y1: y1, x2: x2, y2: y2}).setStroke(ls);
338                        }
339
340                        // series items represent markers on the indicator
341                        var data;
342                        if(this.opt.markers && visible){
343                                var d = this._data[index];
344                                var self = this;
345                                if(d){
346                                        data = array.map(d, function(value, index){
347                                                mark[hn] = v?coord:value;
348                                                mark[vn] = v?value:coord;
349                                                if(has("dojo-bidi")){
350                                                        mark.x = self._getMarkX(mark.x);
351                                                }
352                                                mark = this.toPage(mark);
353                                                if(v?mark.y <= min.y && mark.y >= max.y:mark.x >= min.x && mark.x <= max.x){
354                                                        cx = mark.x - c.x
355                                                        cy = mark.y - c.y;
356                                                        var ms = this.opt.markerSymbol?this.opt.markerSymbol:t.indicator.markerSymbol,
357                                                                path = "M" + cx + " " + cy + " " + ms;
358                                                        sh = this.opt.markerShadow!=undefined?this.opt.markerShadow:t.indicator.markerShadow;
359                                                        ls = this.opt.markerStroke!=undefined?this.opt.markerStroke:t.indicator.markerStroke;
360                                                        ol = this.opt.markerOutline!=undefined?this.opt.markerOutline:t.indicator.markerOutline;
361                                                        if(sh){
362                                                                var sp = "M" + (cx + sh.dx) + " " + (cy + sh.dy) + " " + ms;
363                                                                g.createPath(sp).setFill(sh.color).setStroke(sh);
364                                                        }
365                                                        if(ol){
366                                                                ol = dcpc.makeStroke(ol);
367                                                                ol.width = 2 * ol.width + (ls?ls.width:0);
368                                                                g.createPath(path).setStroke(ol);
369                                                        }
370
371                                                        var shape = g.createPath(path);
372                                                        var sf = this._shapeFill(this.opt.markerFill != undefined?this.opt.markerFill:t.indicator.markerFill, shape.getBoundingBox());
373                                                        shape.setFill(sf).setStroke(ls);
374                                                }
375                                                return value;
376                                        }, this);
377                                }
378                        }
379                        var ctext;
380                        if(this.opt.start){
381                                ctext = {
382                                        x: v?x1:x1,
383                                        y: v?y1:y2
384                                };
385                        }else{
386                                ctext = {
387                                        x: v?x1:x2,
388                                        y: v?y2:y1
389                                };
390                        }
391
392                        if(this.opt.labels && this.opt.labels != "trend" && visible){
393                                var text;
394                                if(this.opt.labelFunc){
395                                        text = this.opt.labelFunc(index, this.opt.values, this._data,
396                                                this.opt.fixed, this.opt.precision, this.opt.labels);
397                                }else{
398                                        if(this.opt.labels == "markers"){
399                                                text = array.map(data, function(value){
400                                                        return dcpc.getLabel(value, this.opt.fixed, this.opt.precision);
401                                                }, this);
402                                                text = text.length != 1 ? "[ "+text.join(", ")+" ]" : text[0];
403                                        }else{
404                                                text = dcpc.getLabel(coord, this.opt.fixed, this.opt.precision);
405                                        }
406                                }
407                                this._renderText(g, text, t, ctext.x, ctext.y, index, this.opt.values, this._data);
408                        }
409
410                        if(events){
411                                this._connectEvents({
412                                        element: "indicator",
413                                        run:     this.run?this.run[index]:undefined,
414                                        shape:   g,
415                                        value:   coord
416                                });
417                        }
418
419                        if(animate){
420                                this._animateIndicator(g, v, v?y1:x1, v?(y1 + y2):(x1 + x2), animate);
421                        }
422
423                        return lang.mixin(ctext, {x1: x1, y1: y1, x2: x2, y2: y2});
424                },
425                _animateIndicator: function(shape, vertical, offset, size, animate){
426                        var transStart = vertical ? [0, offset] : [offset, 0];
427                        var scaleStart = vertical ? [1, 1 / size] : [1 / size, 1];
428                        fx.animateTransform(lang.delegate({
429                                shape: shape,
430                                duration: 1200,
431                                transform: [
432                                        {name: "translate", start: transStart, end: [0, 0]},
433                                        {name: "scale", start: scaleStart, end: [1, 1]},
434                                        {name: "original"}
435                                ]
436                        }, animate)).play();
437                },
438                clear: function(){
439                        this.inherited(arguments);
440                        this._data = [];
441                },
442                addSeries: function(run){
443                        this.inherited(arguments);
444                        this._data.push(run.data);
445                },
446                _renderText: function(g, text, t, x, y, index, values, data){
447                        if(this.opt.offset){
448                                x += this.opt.offset.x;
449                                y += this.opt.offset.y;
450                        }
451                        var label = dcac.createText.gfx(
452                                        this.chart,
453                                        g,
454                                        x, y,
455                                        "middle",
456                                        text, this.opt.font?this.opt.font:t.indicator.font, this.opt.fontColor?this.opt.fontColor:t.indicator.fontColor);
457                        var b = getBoundingBox(label);
458                        b.x-=2; b.y-=1; b.width+=4; b.height+=2; b.r = this.opt.radius?this.opt.radius:t.indicator.radius;
459                        var sh = this.opt.shadow!=undefined?this.opt.shadow:t.indicator.shadow,
460                                ls = this.opt.stroke!=undefined?this.opt.stroke:t.indicator.stroke,
461                                ol = this.opt.outline!=undefined?this.opt.outline:t.indicator.outline;
462                        if(sh){
463                                g.createRect(b).setFill(sh.color).setStroke(sh);
464                        }
465                        if(ol){
466                                ol = dcpc.makeStroke(ol);
467                                ol.width = 2 * ol.width + (ls?ls.width:0);
468                                g.createRect(b).setStroke(ol);
469                        }
470                        var f = this.opt.fillFunc?this.opt.fillFunc(index, values, data):(this.opt.fill!=undefined?this.opt.fill:t.indicator.fill);
471                        g.createRect(b).setFill(this._shapeFill(f, b)).setStroke(ls);
472                        label.moveToFront();
473                },
474                getSeriesStats: function(){
475                        // summary:
476                        //              Returns default stats (irrelevant for this type of plot).
477                        // returns: Object
478                        //              {hmin, hmax, vmin, vmax} min/max in both directions.
479                        return lang.delegate(dcpc.defaultStats);
480                }
481        });
482        if(has("dojo-bidi")){
483                Indicator.extend({
484                        _getMarkX: function(x){
485                                if(this.chart.isRightToLeft()){
486                                        return this.chart.axes.x.scaler.bounds.to + this.chart.axes.x.scaler.bounds.from - x;
487                                }
488                                return x;
489                        }                       
490                });
491        }
492        return Indicator;
493});
Note: See TracBrowser for help on using the repository browser.