Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 espaco 1
/* Flot plugin for plotting error bars.
2
 
3
Copyright (c) 2007-2014 IOLA and Ole Laursen.
4
Licensed under the MIT license.
5
 
6
Error bars are used to show standard deviation and other statistical
7
properties in a plot.
8
 
9
* Created by Rui Pereira  -  rui (dot) pereira (at) gmail (dot) com
10
 
11
This plugin allows you to plot error-bars over points. Set "errorbars" inside
12
the points series to the axis name over which there will be error values in
13
your data array (*even* if you do not intend to plot them later, by setting
14
"show: null" on xerr/yerr).
15
 
16
The plugin supports these options:
17
 
18
        series: {
19
                points: {
20
                        errorbars: "x" or "y" or "xy",
21
                        xerr: {
22
                                show: null/false or true,
23
                                asymmetric: null/false or true,
24
                                upperCap: null or "-" or function,
25
                                lowerCap: null or "-" or function,
26
                                color: null or color,
27
                                radius: null or number
28
                        },
29
                        yerr: { same options as xerr }
30
                }
31
        }
32
 
33
Each data point array is expected to be of the type:
34
 
35
        "x"  [ x, y, xerr ]
36
        "y"  [ x, y, yerr ]
37
        "xy" [ x, y, xerr, yerr ]
38
 
39
Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and
40
equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric
41
error-bars on X and asymmetric on Y would be:
42
 
43
        [ x, y, xerr, yerr_lower, yerr_upper ]
44
 
45
By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will
46
draw a small cap perpendicular to the error bar. They can also be set to a
47
user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg.
48
 
49
        function drawSemiCircle( ctx, x, y, radius ) {
50
                ctx.beginPath();
51
                ctx.arc( x, y, radius, 0, Math.PI, false );
52
                ctx.moveTo( x - radius, y );
53
                ctx.lineTo( x + radius, y );
54
                ctx.stroke();
55
        }
56
 
57
Color and radius both default to the same ones of the points series if not
58
set. The independent radius parameter on xerr/yerr is useful for the case when
59
we may want to add error-bars to a line, without showing the interconnecting
60
points (with radius: 0), and still showing end caps on the error-bars.
61
shadowSize and lineWidth are derived as well from the points series.
62
 
63
*/
64
 
65
(function ($) {
66
    var options = {
67
        series: {
68
            points: {
69
                errorbars: null, //should be 'x', 'y' or 'xy'
70
                xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},
71
                yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}
72
            }
73
        }
74
    };
75
 
76
    function processRawData(plot, series, data, datapoints){
77
        if (!series.points.errorbars)
78
            return;
79
 
80
        // x,y values
81
        var format = [
82
            { x: true, number: true, required: true },
83
            { y: true, number: true, required: true }
84
        ];
85
 
86
        var errors = series.points.errorbars;
87
        // error bars - first X then Y
88
        if (errors == 'x' || errors == 'xy') {
89
            // lower / upper error
90
            if (series.points.xerr.asymmetric) {
91
                format.push({ x: true, number: true, required: true });
92
                format.push({ x: true, number: true, required: true });
93
            } else
94
                format.push({ x: true, number: true, required: true });
95
        }
96
        if (errors == 'y' || errors == 'xy') {
97
            // lower / upper error
98
            if (series.points.yerr.asymmetric) {
99
                format.push({ y: true, number: true, required: true });
100
                format.push({ y: true, number: true, required: true });
101
            } else
102
                format.push({ y: true, number: true, required: true });
103
        }
104
        datapoints.format = format;
105
    }
106
 
107
    function parseErrors(series, i){
108
 
109
        var points = series.datapoints.points;
110
 
111
        // read errors from points array
112
        var exl = null,
113
                exu = null,
114
                eyl = null,
115
                eyu = null;
116
        var xerr = series.points.xerr,
117
                yerr = series.points.yerr;
118
 
119
        var eb = series.points.errorbars;
120
        // error bars - first X
121
        if (eb == 'x' || eb == 'xy') {
122
            if (xerr.asymmetric) {
123
                exl = points[i + 2];
124
                exu = points[i + 3];
125
                if (eb == 'xy')
126
                    if (yerr.asymmetric){
127
                        eyl = points[i + 4];
128
                        eyu = points[i + 5];
129
                    } else eyl = points[i + 4];
130
            } else {
131
                exl = points[i + 2];
132
                if (eb == 'xy')
133
                    if (yerr.asymmetric) {
134
                        eyl = points[i + 3];
135
                        eyu = points[i + 4];
136
                    } else eyl = points[i + 3];
137
            }
138
        // only Y
139
        } else if (eb == 'y')
140
            if (yerr.asymmetric) {
141
                eyl = points[i + 2];
142
                eyu = points[i + 3];
143
            } else eyl = points[i + 2];
144
 
145
        // symmetric errors?
146
        if (exu == null) exu = exl;
147
        if (eyu == null) eyu = eyl;
148
 
149
        var errRanges = [exl, exu, eyl, eyu];
150
        // nullify if not showing
151
        if (!xerr.show){
152
            errRanges[0] = null;
153
            errRanges[1] = null;
154
        }
155
        if (!yerr.show){
156
            errRanges[2] = null;
157
            errRanges[3] = null;
158
        }
159
        return errRanges;
160
    }
161
 
162
    function drawSeriesErrors(plot, ctx, s){
163
 
164
        var points = s.datapoints.points,
165
                ps = s.datapoints.pointsize,
166
                ax = [s.xaxis, s.yaxis],
167
                radius = s.points.radius,
168
                err = [s.points.xerr, s.points.yerr];
169
 
170
        //sanity check, in case some inverted axis hack is applied to flot
171
        var invertX = false;
172
        if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) {
173
            invertX = true;
174
            var tmp = err[0].lowerCap;
175
            err[0].lowerCap = err[0].upperCap;
176
            err[0].upperCap = tmp;
177
        }
178
 
179
        var invertY = false;
180
        if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) {
181
            invertY = true;
182
            var tmp = err[1].lowerCap;
183
            err[1].lowerCap = err[1].upperCap;
184
            err[1].upperCap = tmp;
185
        }
186
 
187
        for (var i = 0; i < s.datapoints.points.length; i += ps) {
188
 
189
            //parse
190
            var errRanges = parseErrors(s, i);
191
 
192
            //cycle xerr & yerr
193
            for (var e = 0; e < err.length; e++){
194
 
195
                var minmax = [ax[e].min, ax[e].max];
196
 
197
                //draw this error?
198
                if (errRanges[e * err.length]){
199
 
200
                    //data coordinates
201
                    var x = points[i],
202
                        y = points[i + 1];
203
 
204
                    //errorbar ranges
205
                    var upper = [x, y][e] + errRanges[e * err.length + 1],
206
                        lower = [x, y][e] - errRanges[e * err.length];
207
 
208
                    //points outside of the canvas
209
                    if (err[e].err == 'x')
210
                        if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max)
211
                            continue;
212
                    if (err[e].err == 'y')
213
                        if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max)
214
                            continue;
215
 
216
                    // prevent errorbars getting out of the canvas
217
                    var drawUpper = true,
218
                        drawLower = true;
219
 
220
                    if (upper > minmax[1]) {
221
                        drawUpper = false;
222
                        upper = minmax[1];
223
                    }
224
                    if (lower < minmax[0]) {
225
                        drawLower = false;
226
                        lower = minmax[0];
227
                    }
228
 
229
                    //sanity check, in case some inverted axis hack is applied to flot
230
                    if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) {
231
                        //swap coordinates
232
                        var tmp = lower;
233
                        lower = upper;
234
                        upper = tmp;
235
                        tmp = drawLower;
236
                        drawLower = drawUpper;
237
                        drawUpper = tmp;
238
                        tmp = minmax[0];
239
                        minmax[0] = minmax[1];
240
                        minmax[1] = tmp;
241
                    }
242
 
243
                    // convert to pixels
244
                    x = ax[0].p2c(x),
245
                        y = ax[1].p2c(y),
246
                        upper = ax[e].p2c(upper);
247
                    lower = ax[e].p2c(lower);
248
                    minmax[0] = ax[e].p2c(minmax[0]);
249
                    minmax[1] = ax[e].p2c(minmax[1]);
250
 
251
                    //same style as points by default
252
                    var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth,
253
                        sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize;
254
 
255
                    //shadow as for points
256
                    if (lw > 0 && sw > 0) {
257
                        var w = sw / 2;
258
                        ctx.lineWidth = w;
259
                        ctx.strokeStyle = "rgba(0,0,0,0.1)";
260
                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax);
261
 
262
                        ctx.strokeStyle = "rgba(0,0,0,0.2)";
263
                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax);
264
                    }
265
 
266
                    ctx.strokeStyle = err[e].color? err[e].color: s.color;
267
                    ctx.lineWidth = lw;
268
                    //draw it
269
                    drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax);
270
                }
271
            }
272
        }
273
    }
274
 
275
    function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){
276
 
277
        //shadow offset
278
        y += offset;
279
        upper += offset;
280
        lower += offset;
281
 
282
        // error bar - avoid plotting over circles
283
        if (err.err == 'x'){
284
            if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]);
285
            else drawUpper = false;
286
            if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] );
287
            else drawLower = false;
288
        }
289
        else {
290
            if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] );
291
            else drawUpper = false;
292
            if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] );
293
            else drawLower = false;
294
        }
295
 
296
        //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
297
        //this is a way to get errorbars on lines without visible connecting dots
298
        radius = err.radius != null? err.radius: radius;
299
 
300
        // upper cap
301
        if (drawUpper) {
302
            if (err.upperCap == '-'){
303
                if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] );
304
                else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] );
305
            } else if ($.isFunction(err.upperCap)){
306
                if (err.err=='x') err.upperCap(ctx, upper, y, radius);
307
                else err.upperCap(ctx, x, upper, radius);
308
            }
309
        }
310
        // lower cap
311
        if (drawLower) {
312
            if (err.lowerCap == '-'){
313
                if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] );
314
                else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] );
315
            } else if ($.isFunction(err.lowerCap)){
316
                if (err.err=='x') err.lowerCap(ctx, lower, y, radius);
317
                else err.lowerCap(ctx, x, lower, radius);
318
            }
319
        }
320
    }
321
 
322
    function drawPath(ctx, pts){
323
        ctx.beginPath();
324
        ctx.moveTo(pts[0][0], pts[0][1]);
325
        for (var p=1; p < pts.length; p++)
326
            ctx.lineTo(pts[p][0], pts[p][1]);
327
        ctx.stroke();
328
    }
329
 
330
    function draw(plot, ctx){
331
        var plotOffset = plot.getPlotOffset();
332
 
333
        ctx.save();
334
        ctx.translate(plotOffset.left, plotOffset.top);
335
        $.each(plot.getData(), function (i, s) {
336
            if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show))
337
                drawSeriesErrors(plot, ctx, s);
338
        });
339
        ctx.restore();
340
    }
341
 
342
    function init(plot) {
343
        plot.hooks.processRawData.push(processRawData);
344
        plot.hooks.draw.push(draw);
345
    }
346
 
347
    $.plot.plugins.push({
348
                init: init,
349
                options: options,
350
                name: 'errorbars',
351
                version: '1.0'
352
            });
353
})(jQuery);