]>
Git — Sourcephile - gargantext.git/blob - ekg-assets/jquery.flot.js
1 /*! Javascript plotting library for jQuery, v. 0.7.
3 * Released under the MIT license by IOLA, December 2007.
7 // first an inline dependency, jquery.colorhelpers.js, we inline it here
10 /* Plugin for jQuery for working with colors.
14 * Inspiration from jQuery color animation plugin by John Resig.
16 * Released under the MIT license by Ole Laursen, October 2009.
20 * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
21 * var c = $.color.extract($("#mydiv"), 'background-color');
22 * console.log(c.r, c.g, c.b, c.a);
23 * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
25 * Note that .scale() and .add() return the same modified object
26 * instead of making a new one.
28 * V. 1.1: Fix error handling so e.g. parsing an empty string does
29 * produce a color rather than just crashing.
31 (function(B
){B
.color
={};B
.color
.make=function(F
,E
,C
,D
){var G
={};G
.r
=F
||0;G
.g
=E
||0;G
.b
=C
||0;G
.a
=D
!=null?D:1;G
.add=function(J
,I
){for(var H
=0;H
<J
.length
;++H
){G
[J
.charAt(H
)]+=I
}return G
.normalize()};G
.scale=function(J
,I
){for(var H
=0;H
<J
.length
;++H
){G
[J
.charAt(H
)]*=I
}return G
.normalize()};G
.toString=function(){if(G
.a
>=1){return"rgb("+[G
.r
,G
.g
,G
.b
].join(",")+")"}else{return"rgba("+[G
.r
,G
.g
,G
.b
,G
.a
].join(",")+")"}};G
.normalize=function(){function H(J
,K
,I
){return K
<J
?J:(K
>I
?I:K
)}G
.r
=H(0,parseInt(G
.r
),255);G
.g
=H(0,parseInt(G
.g
),255);G
.b
=H(0,parseInt(G
.b
),255);G
.a
=H(0,G
.a
,1);return G
};G
.clone=function(){return B
.color
.make(G
.r
,G
.b
,G
.g
,G
.a
)};return G
.normalize()};B
.color
.extract=function(D
,C
){var E
;do{E
=D
.css(C
).toLowerCase();if(E
!=""&&E
!="transparent"){break}D
=D
.parent()}while(!B
.nodeName(D
.get(0),"body"));if(E
=="rgba(0, 0, 0, 0)"){E
="transparent"}return B
.color
.parse(E
)};B
.color
.parse=function(F
){var E
,C
=B
.color
.make
;if(E
=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F
)){return C(parseInt(E
[1],10),parseInt(E
[2],10),parseInt(E
[3],10))}if(E
=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F
)){return C(parseInt(E
[1],10),parseInt(E
[2],10),parseInt(E
[3],10),parseFloat(E
[4]))}if(E
=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F
)){return C(parseFloat(E
[1])*2.55,parseFloat(E
[2])*2.55,parseFloat(E
[3])*2.55)}if(E
=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F
)){return C(parseFloat(E
[1])*2.55,parseFloat(E
[2])*2.55,parseFloat(E
[3])*2.55,parseFloat(E
[4]))}if(E
=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F
)){return C(parseInt(E
[1],16),parseInt(E
[2],16),parseInt(E
[3],16))}if(E
=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F
)){return C(parseInt(E
[1]+E
[1],16),parseInt(E
[2]+E
[2],16),parseInt(E
[3]+E
[3],16))}var D
=B
.trim(F
).toLowerCase();if(D
=="transparent"){return C(255,255,255,0)}else{E
=A
[D
]||[0,0,0];return C(E
[0],E
[1],E
[2])}};var A
={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery
);
33 // the actual Flot code
35 function Plot(placeholder
, data_
, options_
, plugins
) {
36 // data is on the form:
37 // [ series1, series2 ... ]
38 // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
39 // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
43 // the color theme used for graphs
44 colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
47 noColumns: 1, // number of colums in legend table
48 labelFormatter: null, // fn: string -> string
49 labelBoxBorderColor: "#ccc", // border color for the little label boxes
50 container: null, // container (as jQuery object) to put legend in, null means default on top of graph
51 position: "ne", // position of default legend container within plot
52 margin: 5, // distance from grid edge to default legend container within plot
53 backgroundColor: null, // null means auto-detect
54 backgroundOpacity: 0.85 // set to 0 to avoid background
57 show: null, // null = auto-detect, true = always, false = never
58 position: "bottom", // or "top"
59 mode: null, // null or "time"
60 color: null, // base color, labels, ticks
61 tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
62 transform: null, // null or f: number -> number to transform axis
63 inverseTransform: null, // if transform is set, this should be the inverse function
64 min: null, // min. value to show, null means set automatically
65 max: null, // max. value to show, null means set automatically
66 autoscaleMargin: null, // margin in % to add if auto-setting min/max
67 ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
68 tickFormatter: null, // fn: number -> string
69 labelWidth: null, // size of tick labels in pixels
71 reserveSpace: null, // whether to reserve space even if axis isn't shown
72 tickLength: null, // size in pixels of ticks, or "full" for whole line
73 alignTicksWithAxis: null, // axis number or null for no sync
75 // mode specific options
76 tickDecimals: null, // no. of decimals, null means auto
77 tickSize: null, // number or [number, "unit"]
78 minTickSize: null, // number or [number, "unit"]
79 monthNames: null, // list of names of months
80 timeformat: null, // format string to use
81 twelveHourClock: false // 12 or 24 time in time mode
84 autoscaleMargin: 0.02,
85 position: "left" // or "right"
93 lineWidth: 2, // in pixels
96 symbol: "circle" // or callback
99 // we don't put in show: false so we can see
100 // whether lines were actively disabled
101 lineWidth: 2, // in pixels
108 lineWidth: 2, // in pixels
109 barWidth: 1, // in units of the x axis
112 align: "left", // or "center"
120 color: "#545454", // primary color used for outline and labels
121 backgroundColor: null, // null for transparent, else color
122 borderColor: null, // set if different from the grid color
123 tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
124 labelMargin: 5, // in pixels
125 axisMargin: 8, // in pixels
126 borderWidth: 2, // in pixels
127 minBorderMargin: null, // in pixels, null means taken from points radius
128 markings: null, // array of ranges or fn: axes -> array of ranges
129 markingsColor: "#f4f4f4",
130 markingsLineWidth: 2,
134 autoHighlight: true, // highlight in case mouse is near
135 mouseActiveRadius: 10 // how far the mouse can be away to activate an item
139 canvas
= null, // the canvas for the plot itself
140 overlay
= null, // canvas for interactive stuff on top of plot
141 eventHolder
= null, // jQuery object that events should be bound to
142 ctx
= null, octx
= null,
143 xaxes
= [], yaxes
= [],
144 plotOffset
= { left: 0, right: 0, top: 0, bottom: 0},
145 canvasWidth
= 0, canvasHeight
= 0,
146 plotWidth
= 0, plotHeight
= 0,
150 processDatapoints: [],
160 plot
.setData
= setData
;
161 plot
.setupGrid
= setupGrid
;
163 plot
.getPlaceholder = function() { return placeholder
; };
164 plot
.getCanvas = function() { return canvas
; };
165 plot
.getPlotOffset = function() { return plotOffset
; };
166 plot
.width = function () { return plotWidth
; };
167 plot
.height = function () { return plotHeight
; };
168 plot
.offset = function () {
169 var o
= eventHolder
.offset();
170 o
.left
+= plotOffset
.left
;
171 o
.top
+= plotOffset
.top
;
174 plot
.getData = function () { return series
; };
175 plot
.getAxes = function () {
177 $.each(xaxes
.concat(yaxes
), function (_
, axis
) {
179 res
[axis
.direction
+ (axis
.n
!= 1 ? axis
.n : "") + "axis"] = axis
;
183 plot
.getXAxes = function () { return xaxes
; };
184 plot
.getYAxes = function () { return yaxes
; };
185 plot
.c2p
= canvasToAxisCoords
;
186 plot
.p2c
= axisToCanvasCoords
;
187 plot
.getOptions = function () { return options
; };
188 plot
.highlight
= highlight
;
189 plot
.unhighlight
= unhighlight
;
190 plot
.triggerRedrawOverlay
= triggerRedrawOverlay
;
191 plot
.pointOffset = function(point
) {
193 left: parseInt(xaxes
[axisNumber(point
, "x") - 1].p2c(+point
.x
) + plotOffset
.left
),
194 top: parseInt(yaxes
[axisNumber(point
, "y") - 1].p2c(+point
.y
) + plotOffset
.top
)
197 plot
.shutdown
= shutdown
;
198 plot
.resize = function () {
199 getCanvasDimensions();
200 resizeCanvas(canvas
);
201 resizeCanvas(overlay
);
209 parseOptions(options_
);
217 function executeHooks(hook
, args
) {
218 args
= [plot
].concat(args
);
219 for (var i
= 0; i
< hook
.length
; ++i
)
220 hook
[i
].apply(this, args
);
223 function initPlugins() {
224 for (var i
= 0; i
< plugins
.length
; ++i
) {
228 $.extend(true, options
, p
.options
);
232 function parseOptions(opts
) {
235 $.extend(true, options
, opts
);
237 if (options
.xaxis
.color
== null)
238 options
.xaxis
.color
= options
.grid
.color
;
239 if (options
.yaxis
.color
== null)
240 options
.yaxis
.color
= options
.grid
.color
;
242 if (options
.xaxis
.tickColor
== null) // backwards-compatibility
243 options
.xaxis
.tickColor
= options
.grid
.tickColor
;
244 if (options
.yaxis
.tickColor
== null) // backwards-compatibility
245 options
.yaxis
.tickColor
= options
.grid
.tickColor
;
247 if (options
.grid
.borderColor
== null)
248 options
.grid
.borderColor
= options
.grid
.color
;
249 if (options
.grid
.tickColor
== null)
250 options
.grid
.tickColor
= $.color
.parse(options
.grid
.color
).scale('a', 0.22).toString();
252 // fill in defaults in axes, copy at least always the
253 // first as the rest of the code assumes it'll be there
254 for (i
= 0; i
< Math
.max(1, options
.xaxes
.length
); ++i
)
255 options
.xaxes
[i
] = $.extend(true, {}, options
.xaxis
, options
.xaxes
[i
]);
256 for (i
= 0; i
< Math
.max(1, options
.yaxes
.length
); ++i
)
257 options
.yaxes
[i
] = $.extend(true, {}, options
.yaxis
, options
.yaxes
[i
]);
259 // backwards compatibility, to be removed in future
260 if (options
.xaxis
.noTicks
&& options
.xaxis
.ticks
== null)
261 options
.xaxis
.ticks
= options
.xaxis
.noTicks
;
262 if (options
.yaxis
.noTicks
&& options
.yaxis
.ticks
== null)
263 options
.yaxis
.ticks
= options
.yaxis
.noTicks
;
264 if (options
.x2axis
) {
265 options
.xaxes
[1] = $.extend(true, {}, options
.xaxis
, options
.x2axis
);
266 options
.xaxes
[1].position
= "top";
268 if (options
.y2axis
) {
269 options
.yaxes
[1] = $.extend(true, {}, options
.yaxis
, options
.y2axis
);
270 options
.yaxes
[1].position
= "right";
272 if (options
.grid
.coloredAreas
)
273 options
.grid
.markings
= options
.grid
.coloredAreas
;
274 if (options
.grid
.coloredAreasColor
)
275 options
.grid
.markingsColor
= options
.grid
.coloredAreasColor
;
277 $.extend(true, options
.series
.lines
, options
.lines
);
279 $.extend(true, options
.series
.points
, options
.points
);
281 $.extend(true, options
.series
.bars
, options
.bars
);
282 if (options
.shadowSize
!= null)
283 options
.series
.shadowSize
= options
.shadowSize
;
285 // save options on axes for future reference
286 for (i
= 0; i
< options
.xaxes
.length
; ++i
)
287 getOrCreateAxis(xaxes
, i
+ 1).options
= options
.xaxes
[i
];
288 for (i
= 0; i
< options
.yaxes
.length
; ++i
)
289 getOrCreateAxis(yaxes
, i
+ 1).options
= options
.yaxes
[i
];
291 // add hooks from options
293 if (options
.hooks
[n
] && options
.hooks
[n
].length
)
294 hooks
[n
] = hooks
[n
].concat(options
.hooks
[n
]);
296 executeHooks(hooks
.processOptions
, [options
]);
299 function setData(d
) {
300 series
= parseData(d
);
301 fillInSeriesOptions();
305 function parseData(d
) {
307 for (var i
= 0; i
< d
.length
; ++i
) {
308 var s
= $.extend(true, {}, options
.series
);
310 if (d
[i
].data
!= null) {
311 s
.data
= d
[i
].data
; // move the data instead of deep-copy
314 $.extend(true, s
, d
[i
]);
326 function axisNumber(obj
, coord
) {
327 var a
= obj
[coord
+ "axis"];
328 if (typeof a
== "object") // if we got a real axis, extract number
330 if (typeof a
!= "number")
331 a
= 1; // default to first axis
336 // return flat array without annoying null entries
337 return $.grep(xaxes
.concat(yaxes
), function (a
) { return a
; });
340 function canvasToAxisCoords(pos
) {
341 // return an object with x/y corresponding to all used axes
342 var res
= {}, i
, axis
;
343 for (i
= 0; i
< xaxes
.length
; ++i
) {
345 if (axis
&& axis
.used
)
346 res
["x" + axis
.n
] = axis
.c2p(pos
.left
);
349 for (i
= 0; i
< yaxes
.length
; ++i
) {
351 if (axis
&& axis
.used
)
352 res
["y" + axis
.n
] = axis
.c2p(pos
.top
);
355 if (res
.x1
!== undefined)
357 if (res
.y1
!== undefined)
363 function axisToCanvasCoords(pos
) {
364 // get canvas coords from the first pair of x/y found in pos
365 var res
= {}, i
, axis
, key
;
367 for (i
= 0; i
< xaxes
.length
; ++i
) {
369 if (axis
&& axis
.used
) {
371 if (pos
[key
] == null && axis
.n
== 1)
374 if (pos
[key
] != null) {
375 res
.left
= axis
.p2c(pos
[key
]);
381 for (i
= 0; i
< yaxes
.length
; ++i
) {
383 if (axis
&& axis
.used
) {
385 if (pos
[key
] == null && axis
.n
== 1)
388 if (pos
[key
] != null) {
389 res
.top
= axis
.p2c(pos
[key
]);
398 function getOrCreateAxis(axes
, number
) {
399 if (!axes
[number
- 1])
401 n: number
, // save the number for future reference
402 direction: axes
== xaxes
? "x" : "y",
403 options: $.extend(true, {}, axes
== xaxes
? options
.xaxis : options
.yaxis
)
406 return axes
[number
- 1];
409 function fillInSeriesOptions() {
412 // collect what we already got of colors
413 var neededColors
= series
.length
,
416 for (i
= 0; i
< series
.length
; ++i
) {
417 var sc
= series
[i
].color
;
420 if (typeof sc
== "number")
421 assignedColors
.push(sc
);
423 usedColors
.push($.color
.parse(series
[i
].color
));
427 // we might need to generate more colors if higher indices
429 for (i
= 0; i
< assignedColors
.length
; ++i
) {
430 neededColors
= Math
.max(neededColors
, assignedColors
[i
] + 1);
433 // produce colors as needed
434 var colors
= [], variation
= 0;
436 while (colors
.length
< neededColors
) {
438 if (options
.colors
.length
== i
) // check degenerate case
439 c
= $.color
.make(100, 100, 100);
441 c
= $.color
.parse(options
.colors
[i
]);
443 // vary color if needed
444 var sign
= variation
% 2 == 1 ? -1 : 1;
445 c
.scale('rgb', 1 + sign
* Math
.ceil(variation
/ 2) * 0.2)
447 // FIXME: if we're getting to close to something else,
448 // we should probably skip this one
452 if (i
>= options
.colors
.length
) {
458 // fill in the options
460 for (i
= 0; i
< series
.length
; ++i
) {
464 if (s
.color
== null) {
465 s
.color
= colors
[colori
].toString();
468 else if (typeof s
.color
== "number")
469 s
.color
= colors
[s
.color
].toString();
471 // turn on lines automatically in case nothing is set
472 if (s
.lines
.show
== null) {
475 if (s
[v
] && s
[v
].show
) {
484 s
.xaxis
= getOrCreateAxis(xaxes
, axisNumber(s
, "x"));
485 s
.yaxis
= getOrCreateAxis(yaxes
, axisNumber(s
, "y"));
489 function processData() {
490 var topSentry
= Number
.POSITIVE_INFINITY
,
491 bottomSentry
= Number
.NEGATIVE_INFINITY
,
492 fakeInfinity
= Number
.MAX_VALUE
,
494 s
, points
, ps
, x
, y
, axis
, val
, f
, p
;
496 function updateAxis(axis
, min
, max
) {
497 if (min
< axis
.datamin
&& min
!= -fakeInfinity
)
499 if (max
> axis
.datamax
&& max
!= fakeInfinity
)
503 $.each(allAxes(), function (_
, axis
) {
505 axis
.datamin
= topSentry
;
506 axis
.datamax
= bottomSentry
;
510 for (i
= 0; i
< series
.length
; ++i
) {
512 s
.datapoints
= { points: [] };
514 executeHooks(hooks
.processRawData
, [ s
, s
.data
, s
.datapoints
]);
517 // first pass: clean and copy data
518 for (i
= 0; i
< series
.length
; ++i
) {
521 var data
= s
.data
, format
= s
.datapoints
.format
;
525 // find out how to copy
526 format
.push({ x: true, number: true, required: true });
527 format
.push({ y: true, number: true, required: true });
529 if (s
.bars
.show
|| (s
.lines
.show
&& s
.lines
.fill
)) {
530 format
.push({ y: true, number: true, required: false, defaultValue: 0 });
531 if (s
.bars
.horizontal
) {
532 delete format
[format
.length
- 1].y
;
533 format
[format
.length
- 1].x
= true;
537 s
.datapoints
.format
= format
;
540 if (s
.datapoints
.pointsize
!= null)
541 continue; // already filled in
543 s
.datapoints
.pointsize
= format
.length
;
545 ps
= s
.datapoints
.pointsize
;
546 points
= s
.datapoints
.points
;
548 insertSteps
= s
.lines
.show
&& s
.lines
.steps
;
549 s
.xaxis
.used
= s
.yaxis
.used
= true;
551 for (j
= k
= 0; j
< data
.length
; ++j
, k
+= ps
) {
554 var nullify
= p
== null;
556 for (m
= 0; m
< ps
; ++m
) {
561 if (f
.number
&& val
!= null) {
562 val
= +val
; // convert to number
565 else if (val
== Infinity
)
567 else if (val
== -Infinity
)
575 if (f
.defaultValue
!= null)
576 val
= f
.defaultValue
;
585 for (m
= 0; m
< ps
; ++m
) {
589 // extract min/max info
591 updateAxis(s
.xaxis
, val
, val
);
593 updateAxis(s
.yaxis
, val
, val
);
595 points
[k
+ m
] = null;
599 // a little bit of line specific stuff that
600 // perhaps shouldn't be here, but lacking
602 if (insertSteps
&& k
> 0
603 && points
[k
- ps
] != null
604 && points
[k
- ps
] != points
[k
]
605 && points
[k
- ps
+ 1] != points
[k
+ 1]) {
606 // copy the point to make room for a middle point
607 for (m
= 0; m
< ps
; ++m
)
608 points
[k
+ ps
+ m
] = points
[k
+ m
];
610 // middle point has same y
611 points
[k
+ 1] = points
[k
- ps
+ 1];
613 // we've added a point, better reflect that
620 // give the hooks a chance to run
621 for (i
= 0; i
< series
.length
; ++i
) {
624 executeHooks(hooks
.processDatapoints
, [ s
, s
.datapoints
]);
627 // second pass: find datamax/datamin for auto-scaling
628 for (i
= 0; i
< series
.length
; ++i
) {
630 points
= s
.datapoints
.points
,
631 ps
= s
.datapoints
.pointsize
;
633 var xmin
= topSentry
, ymin
= topSentry
,
634 xmax
= bottomSentry
, ymax
= bottomSentry
;
636 for (j
= 0; j
< points
.length
; j
+= ps
) {
637 if (points
[j
] == null)
640 for (m
= 0; m
< ps
; ++m
) {
643 if (!f
|| val
== fakeInfinity
|| val
== -fakeInfinity
)
662 // make sure we got room for the bar on the dancing floor
663 var delta
= s
.bars
.align
== "left" ? 0 : -s
.bars
.barWidth
/2;
664 if (s
.bars
.horizontal
) {
666 ymax
+= delta
+ s
.bars
.barWidth
;
670 xmax
+= delta
+ s
.bars
.barWidth
;
674 updateAxis(s
.xaxis
, xmin
, xmax
);
675 updateAxis(s
.yaxis
, ymin
, ymax
);
678 $.each(allAxes(), function (_
, axis
) {
679 if (axis
.datamin
== topSentry
)
681 if (axis
.datamax
== bottomSentry
)
686 function makeCanvas(skipPositioning
, cls
) {
687 var c
= document
.createElement('canvas');
689 c
.width
= canvasWidth
;
690 c
.height
= canvasHeight
;
692 if (!skipPositioning
)
693 $(c
).css({ position: 'absolute', left: 0, top: 0 });
695 $(c
).appendTo(placeholder
);
697 if (!c
.getContext
) // excanvas hack
698 c
= window
.G_vmlCanvasManager
.initElement(c
);
700 // used for resetting in case we get replotted
701 c
.getContext("2d").save();
706 function getCanvasDimensions() {
707 canvasWidth
= placeholder
.width();
708 canvasHeight
= placeholder
.height();
710 if (canvasWidth
<= 0 || canvasHeight
<= 0)
711 throw "Invalid dimensions for plot, width = " + canvasWidth
+ ", height = " + canvasHeight
;
714 function resizeCanvas(c
) {
715 // resizing should reset the state (excanvas seems to be
717 if (c
.width
!= canvasWidth
)
718 c
.width
= canvasWidth
;
720 if (c
.height
!= canvasHeight
)
721 c
.height
= canvasHeight
;
723 // so try to get back to the initial state (even if it's
724 // gone now, this should be safe according to the spec)
725 var cctx
= c
.getContext("2d");
732 function setupCanvases() {
734 existingCanvas
= placeholder
.children("canvas.base"),
735 existingOverlay
= placeholder
.children("canvas.overlay");
737 if (existingCanvas
.length
== 0 || existingOverlay
== 0) {
740 placeholder
.html(""); // make sure placeholder is clear
742 placeholder
.css({ padding: 0 }); // padding messes up the positioning
744 if (placeholder
.css("position") == 'static')
745 placeholder
.css("position", "relative"); // for positioning labels and overlay
747 getCanvasDimensions();
749 canvas
= makeCanvas(true, "base");
750 overlay
= makeCanvas(false, "overlay"); // overlay canvas for interactive features
755 // reuse existing elements
757 canvas
= existingCanvas
.get(0);
758 overlay
= existingOverlay
.get(0);
763 ctx
= canvas
.getContext("2d");
764 octx
= overlay
.getContext("2d");
766 // we include the canvas in the event holder too, because IE 7
767 // sometimes has trouble with the stacking order
768 eventHolder
= $([overlay
, canvas
]);
771 // run shutdown in the old plot object
772 placeholder
.data("plot").shutdown();
774 // reset reused canvases
777 // make sure overlay pixels are cleared (canvas is cleared when we redraw)
778 octx
.clearRect(0, 0, canvasWidth
, canvasHeight
);
780 // then whack any remaining obvious garbage left
781 eventHolder
.unbind();
782 placeholder
.children().not([canvas
, overlay
]).remove();
785 // save in case we get replotted
786 placeholder
.data("plot", plot
);
789 function bindEvents() {
791 if (options
.grid
.hoverable
) {
792 eventHolder
.mousemove(onMouseMove
);
793 eventHolder
.mouseleave(onMouseLeave
);
796 if (options
.grid
.clickable
)
797 eventHolder
.click(onClick
);
799 executeHooks(hooks
.bindEvents
, [eventHolder
]);
802 function shutdown() {
804 clearTimeout(redrawTimeout
);
806 eventHolder
.unbind("mousemove", onMouseMove
);
807 eventHolder
.unbind("mouseleave", onMouseLeave
);
808 eventHolder
.unbind("click", onClick
);
810 executeHooks(hooks
.shutdown
, [eventHolder
]);
813 function setTransformationHelpers(axis
) {
814 // set helper functions on the axis, assumes plot area
815 // has been computed already
817 function identity(x
) { return x
; }
819 var s
, m
, t
= axis
.options
.transform
|| identity
,
820 it
= axis
.options
.inverseTransform
;
822 // precompute how much the axis is scaling a point
824 if (axis
.direction
== "x") {
825 s
= axis
.scale
= plotWidth
/ Math
.abs(t(axis
.max
) - t(axis
.min
));
826 m
= Math
.min(t(axis
.max
), t(axis
.min
));
829 s
= axis
.scale
= plotHeight
/ Math
.abs(t(axis
.max
) - t(axis
.min
));
831 m
= Math
.max(t(axis
.max
), t(axis
.min
));
834 // data point to canvas coordinate
835 if (t
== identity
) // slight optimization
836 axis
.p2c = function (p
) { return (p
- m
) * s
; };
838 axis
.p2c = function (p
) { return (t(p
) - m
) * s
; };
839 // canvas coordinate to data point
841 axis
.c2p = function (c
) { return m
+ c
/ s
; };
843 axis
.c2p = function (c
) { return it(m
+ c
/ s
); };
846 function measureTickLabels(axis
) {
847 var opts
= axis
.options
, i
, ticks
= axis
.ticks
|| [], labels
= [],
848 l
, w
= opts
.labelWidth
, h
= opts
.labelHeight
, dummyDiv
;
850 function makeDummyDiv(labels
, width
) {
851 return $('<div style="position:absolute;top:-10000px;' + width
+ 'font-size:smaller">' +
852 '<div class="' + axis
.direction
+ 'Axis ' + axis
.direction
+ axis
.n
+ 'Axis">'
853 + labels
.join("") + '</div></div>')
854 .appendTo(placeholder
);
857 if (axis
.direction
== "x") {
858 // to avoid measuring the widths of the labels (it's slow), we
859 // construct fixed-size boxes and put the labels inside
860 // them, we don't need the exact figures and the
861 // fixed-size box content is easy to center
863 w
= Math
.floor(canvasWidth
/ (ticks
.length
> 0 ? ticks
.length : 1));
865 // measure x label heights
868 for (i
= 0; i
< ticks
.length
; ++i
) {
871 labels
.push('<div class="tickLabel" style="float:left;width:' + w
+ 'px">' + l
+ '</div>');
874 if (labels
.length
> 0) {
875 // stick them all in the same div and measure
877 labels
.push('<div style="clear:left"></div>');
878 dummyDiv
= makeDummyDiv(labels
, "width:10000px;");
879 h
= dummyDiv
.height();
884 else if (w
== null || h
== null) {
885 // calculate y label dimensions
886 for (i
= 0; i
< ticks
.length
; ++i
) {
889 labels
.push('<div class="tickLabel">' + l
+ '</div>');
892 if (labels
.length
> 0) {
893 dummyDiv
= makeDummyDiv(labels
, "");
895 w
= dummyDiv
.children().width();
897 h
= dummyDiv
.find("div.tickLabel").height();
908 axis
.labelHeight
= h
;
911 function allocateAxisBoxFirstPhase(axis
) {
912 // find the bounding box of the axis by looking at label
913 // widths/heights and ticks, make room by diminishing the
916 var lw
= axis
.labelWidth
,
917 lh
= axis
.labelHeight
,
918 pos
= axis
.options
.position
,
919 tickLength
= axis
.options
.tickLength
,
920 axismargin
= options
.grid
.axisMargin
,
921 padding
= options
.grid
.labelMargin
,
922 all
= axis
.direction
== "x" ? xaxes : yaxes
,
925 // determine axis margin
926 var samePosition
= $.grep(all
, function (a
) {
927 return a
&& a
.options
.position
== pos
&& a
.reserveSpace
;
929 if ($.inArray(axis
, samePosition
) == samePosition
.length
- 1)
930 axismargin
= 0; // outermost
932 // determine tick length - if we're innermost, we can use "full"
933 if (tickLength
== null)
936 var sameDirection
= $.grep(all
, function (a
) {
937 return a
&& a
.reserveSpace
;
940 var innermost
= $.inArray(axis
, sameDirection
) == 0;
941 if (!innermost
&& tickLength
== "full")
944 if (!isNaN(+tickLength
))
945 padding
+= +tickLength
;
948 if (axis
.direction
== "x") {
951 if (pos
== "bottom") {
952 plotOffset
.bottom
+= lh
+ axismargin
;
953 axis
.box
= { top: canvasHeight
- plotOffset
.bottom
, height: lh
};
956 axis
.box
= { top: plotOffset
.top
+ axismargin
, height: lh
};
957 plotOffset
.top
+= lh
+ axismargin
;
964 axis
.box
= { left: plotOffset
.left
+ axismargin
, width: lw
};
965 plotOffset
.left
+= lw
+ axismargin
;
968 plotOffset
.right
+= lw
+ axismargin
;
969 axis
.box
= { left: canvasWidth
- plotOffset
.right
, width: lw
};
973 // save for future reference
975 axis
.tickLength
= tickLength
;
976 axis
.box
.padding
= padding
;
977 axis
.innermost
= innermost
;
980 function allocateAxisBoxSecondPhase(axis
) {
981 // set remaining bounding box coordinates
982 if (axis
.direction
== "x") {
983 axis
.box
.left
= plotOffset
.left
;
984 axis
.box
.width
= plotWidth
;
987 axis
.box
.top
= plotOffset
.top
;
988 axis
.box
.height
= plotHeight
;
992 function setupGrid() {
993 var i
, axes
= allAxes();
995 // first calculate the plot and axis box dimensions
997 $.each(axes
, function (_
, axis
) {
998 axis
.show
= axis
.options
.show
;
999 if (axis
.show
== null)
1000 axis
.show
= axis
.used
; // by default an axis is visible if it's got data
1002 axis
.reserveSpace
= axis
.show
|| axis
.options
.reserveSpace
;
1007 allocatedAxes
= $.grep(axes
, function (axis
) { return axis
.reserveSpace
; });
1009 plotOffset
.left
= plotOffset
.right
= plotOffset
.top
= plotOffset
.bottom
= 0;
1010 if (options
.grid
.show
) {
1011 $.each(allocatedAxes
, function (_
, axis
) {
1013 setupTickGeneration(axis
);
1015 snapRangeToTicks(axis
, axis
.ticks
);
1017 // find labelWidth/Height for axis
1018 measureTickLabels(axis
);
1021 // with all dimensions in house, we can compute the
1022 // axis boxes, start from the outside (reverse order)
1023 for (i
= allocatedAxes
.length
- 1; i
>= 0; --i
)
1024 allocateAxisBoxFirstPhase(allocatedAxes
[i
]);
1026 // make sure we've got enough space for things that
1028 var minMargin
= options
.grid
.minBorderMargin
;
1029 if (minMargin
== null) {
1031 for (i
= 0; i
< series
.length
; ++i
)
1032 minMargin
= Math
.max(minMargin
, series
[i
].points
.radius
+ series
[i
].points
.lineWidth
/2);
1035 for (var a
in plotOffset
) {
1036 plotOffset
[a
] += options
.grid
.borderWidth
;
1037 plotOffset
[a
] = Math
.max(minMargin
, plotOffset
[a
]);
1041 plotWidth
= canvasWidth
- plotOffset
.left
- plotOffset
.right
;
1042 plotHeight
= canvasHeight
- plotOffset
.bottom
- plotOffset
.top
;
1044 // now we got the proper plotWidth/Height, we can compute the scaling
1045 $.each(axes
, function (_
, axis
) {
1046 setTransformationHelpers(axis
);
1049 if (options
.grid
.show
) {
1050 $.each(allocatedAxes
, function (_
, axis
) {
1051 allocateAxisBoxSecondPhase(axis
);
1060 function setRange(axis
) {
1061 var opts
= axis
.options
,
1062 min
= +(opts
.min
!= null ? opts
.min : axis
.datamin
),
1063 max
= +(opts
.max
!= null ? opts
.max : axis
.datamax
),
1068 var widen
= max
== 0 ? 1 : 0.01;
1070 if (opts
.min
== null)
1072 // always widen max if we couldn't widen min to ensure we
1073 // don't fall into min == max which doesn't work
1074 if (opts
.max
== null || opts
.min
!= null)
1078 // consider autoscaling
1079 var margin
= opts
.autoscaleMargin
;
1080 if (margin
!= null) {
1081 if (opts
.min
== null) {
1082 min
-= delta
* margin
;
1083 // make sure we don't go below zero if all values
1085 if (min
< 0 && axis
.datamin
!= null && axis
.datamin
>= 0)
1088 if (opts
.max
== null) {
1089 max
+= delta
* margin
;
1090 if (max
> 0 && axis
.datamax
!= null && axis
.datamax
<= 0)
1099 function setupTickGeneration(axis
) {
1100 var opts
= axis
.options
;
1102 // estimate number of ticks
1104 if (typeof opts
.ticks
== "number" && opts
.ticks
> 0)
1105 noTicks
= opts
.ticks
;
1107 // heuristic based on the model a*sqrt(x) fitted to
1108 // some data points that seemed reasonable
1109 noTicks
= 0.3 * Math
.sqrt(axis
.direction
== "x" ? canvasWidth : canvasHeight
);
1111 var delta
= (axis
.max
- axis
.min
) / noTicks
,
1112 size
, generator
, unit
, formatter
, i
, magn
, norm
;
1114 if (opts
.mode
== "time") {
1115 // pretty handling of time
1117 // map of app. size of time units in milliseconds
1118 var timeUnitSize
= {
1120 "minute": 60 * 1000,
1121 "hour": 60 * 60 * 1000,
1122 "day": 24 * 60 * 60 * 1000,
1123 "month": 30 * 24 * 60 * 60 * 1000,
1124 "year": 365.2425 * 24 * 60 * 60 * 1000
1128 // the allowed tick sizes, after 1 year we use
1129 // an integer algorithm
1131 [1, "second"], [2, "second"], [5, "second"], [10, "second"],
1133 [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
1135 [1, "hour"], [2, "hour"], [4, "hour"],
1136 [8, "hour"], [12, "hour"],
1137 [1, "day"], [2, "day"], [3, "day"],
1138 [0.25, "month"], [0.5, "month"], [1, "month"],
1139 [2, "month"], [3, "month"], [6, "month"],
1144 if (opts
.minTickSize
!= null) {
1145 if (typeof opts
.tickSize
== "number")
1146 minSize
= opts
.tickSize
;
1148 minSize
= opts
.minTickSize
[0] * timeUnitSize
[opts
.minTickSize
[1]];
1151 for (var i
= 0; i
< spec
.length
- 1; ++i
)
1152 if (delta
< (spec
[i
][0] * timeUnitSize
[spec
[i
][1]]
1153 + spec
[i
+ 1][0] * timeUnitSize
[spec
[i
+ 1][1]]) / 2
1154 && spec
[i
][0] * timeUnitSize
[spec
[i
][1]] >= minSize
)
1159 // special-case the possibility of several years
1160 if (unit
== "year") {
1161 magn
= Math
.pow(10, Math
.floor(Math
.log(delta
/ timeUnitSize
.year
) / Math
.LN10
));
1162 norm
= (delta
/ timeUnitSize
.year
) / magn
;
1167 else if (norm
< 7.5)
1175 axis
.tickSize
= opts
.tickSize
|| [size
, unit
];
1177 generator = function(axis
) {
1179 tickSize
= axis
.tickSize
[0], unit
= axis
.tickSize
[1],
1180 d
= new Date(axis
.min
);
1182 var step
= tickSize
* timeUnitSize
[unit
];
1184 if (unit
== "second")
1185 d
.setUTCSeconds(floorInBase(d
.getUTCSeconds(), tickSize
));
1186 if (unit
== "minute")
1187 d
.setUTCMinutes(floorInBase(d
.getUTCMinutes(), tickSize
));
1189 d
.setUTCHours(floorInBase(d
.getUTCHours(), tickSize
));
1190 if (unit
== "month")
1191 d
.setUTCMonth(floorInBase(d
.getUTCMonth(), tickSize
));
1193 d
.setUTCFullYear(floorInBase(d
.getUTCFullYear(), tickSize
));
1195 // reset smaller components
1196 d
.setUTCMilliseconds(0);
1197 if (step
>= timeUnitSize
.minute
)
1199 if (step
>= timeUnitSize
.hour
)
1201 if (step
>= timeUnitSize
.day
)
1203 if (step
>= timeUnitSize
.day
* 4)
1205 if (step
>= timeUnitSize
.year
)
1209 var carry
= 0, v
= Number
.NaN
, prev
;
1214 if (unit
== "month") {
1216 // a bit complicated - we'll divide the month
1217 // up but we need to take care of fractions
1218 // so we don't end up in the middle of a day
1220 var start
= d
.getTime();
1221 d
.setUTCMonth(d
.getUTCMonth() + 1);
1222 var end
= d
.getTime();
1223 d
.setTime(v
+ carry
* timeUnitSize
.hour
+ (end
- start
) * tickSize
);
1224 carry
= d
.getUTCHours();
1228 d
.setUTCMonth(d
.getUTCMonth() + tickSize
);
1230 else if (unit
== "year") {
1231 d
.setUTCFullYear(d
.getUTCFullYear() + tickSize
);
1234 d
.setTime(v
+ step
);
1235 } while (v
< axis
.max
&& v
!= prev
);
1240 formatter = function (v
, axis
) {
1241 var d
= new Date(v
);
1243 // first check global format
1244 if (opts
.timeformat
!= null)
1245 return $.plot
.formatDate(d
, opts
.timeformat
, opts
.monthNames
);
1247 var t
= axis
.tickSize
[0] * timeUnitSize
[axis
.tickSize
[1]];
1248 var span
= axis
.max
- axis
.min
;
1249 var suffix
= (opts
.twelveHourClock
) ? " %p" : "";
1251 if (t
< timeUnitSize
.minute
)
1252 fmt
= "%h:%M:%S" + suffix
;
1253 else if (t
< timeUnitSize
.day
) {
1254 if (span
< 2 * timeUnitSize
.day
)
1255 fmt
= "%h:%M" + suffix
;
1257 fmt
= "%b %d %h:%M" + suffix
;
1259 else if (t
< timeUnitSize
.month
)
1261 else if (t
< timeUnitSize
.year
) {
1262 if (span
< timeUnitSize
.year
)
1270 return $.plot
.formatDate(d
, fmt
, opts
.monthNames
);
1274 // pretty rounding of base-10 numbers
1275 var maxDec
= opts
.tickDecimals
;
1276 var dec
= -Math
.floor(Math
.log(delta
) / Math
.LN10
);
1277 if (maxDec
!= null && dec
> maxDec
)
1280 magn
= Math
.pow(10, -dec
);
1281 norm
= delta
/ magn
; // norm is between 1.0 and 10.0
1285 else if (norm
< 3) {
1287 // special case for 2.5, requires an extra decimal
1288 if (norm
> 2.25 && (maxDec
== null || dec
+ 1 <= maxDec
)) {
1293 else if (norm
< 7.5)
1300 if (opts
.minTickSize
!= null && size
< opts
.minTickSize
)
1301 size
= opts
.minTickSize
;
1303 axis
.tickDecimals
= Math
.max(0, maxDec
!= null ? maxDec : dec
);
1304 axis
.tickSize
= opts
.tickSize
|| size
;
1306 generator = function (axis
) {
1309 // spew out all possible ticks
1310 var start
= floorInBase(axis
.min
, axis
.tickSize
),
1311 i
= 0, v
= Number
.NaN
, prev
;
1314 v
= start
+ i
* axis
.tickSize
;
1317 } while (v
< axis
.max
&& v
!= prev
);
1321 formatter = function (v
, axis
) {
1322 return v
.toFixed(axis
.tickDecimals
);
1326 if (opts
.alignTicksWithAxis
!= null) {
1327 var otherAxis
= (axis
.direction
== "x" ? xaxes : yaxes
)[opts
.alignTicksWithAxis
- 1];
1328 if (otherAxis
&& otherAxis
.used
&& otherAxis
!= axis
) {
1329 // consider snapping min/max to outermost nice ticks
1330 var niceTicks
= generator(axis
);
1331 if (niceTicks
.length
> 0) {
1332 if (opts
.min
== null)
1333 axis
.min
= Math
.min(axis
.min
, niceTicks
[0]);
1334 if (opts
.max
== null && niceTicks
.length
> 1)
1335 axis
.max
= Math
.max(axis
.max
, niceTicks
[niceTicks
.length
- 1]);
1338 generator = function (axis
) {
1339 // copy ticks, scaled to this axis
1340 var ticks
= [], v
, i
;
1341 for (i
= 0; i
< otherAxis
.ticks
.length
; ++i
) {
1342 v
= (otherAxis
.ticks
[i
].v
- otherAxis
.min
) / (otherAxis
.max
- otherAxis
.min
);
1343 v
= axis
.min
+ v
* (axis
.max
- axis
.min
);
1349 // we might need an extra decimal since forced
1350 // ticks don't necessarily fit naturally
1351 if (axis
.mode
!= "time" && opts
.tickDecimals
== null) {
1352 var extraDec
= Math
.max(0, -Math
.floor(Math
.log(delta
) / Math
.LN10
) + 1),
1353 ts
= generator(axis
);
1355 // only proceed if the tick interval rounded
1356 // with an extra decimal doesn't give us a
1358 if (!(ts
.length
> 1 && /\..*0$/.test((ts
[1] - ts
[0]).toFixed(extraDec
))))
1359 axis
.tickDecimals
= extraDec
;
1364 axis
.tickGenerator
= generator
;
1365 if ($.isFunction(opts
.tickFormatter
))
1366 axis
.tickFormatter = function (v
, axis
) { return "" + opts
.tickFormatter(v
, axis
); };
1368 axis
.tickFormatter
= formatter
;
1371 function setTicks(axis
) {
1372 var oticks
= axis
.options
.ticks
, ticks
= [];
1373 if (oticks
== null || (typeof oticks
== "number" && oticks
> 0))
1374 ticks
= axis
.tickGenerator(axis
);
1376 if ($.isFunction(oticks
))
1377 // generate the ticks
1378 ticks
= oticks({ min: axis
.min
, max: axis
.max
});
1383 // clean up/labelify the supplied ticks, copy them over
1386 for (i
= 0; i
< ticks
.length
; ++i
) {
1389 if (typeof t
== "object") {
1397 label
= axis
.tickFormatter(v
, axis
);
1399 axis
.ticks
.push({ v: v
, label: label
});
1403 function snapRangeToTicks(axis
, ticks
) {
1404 if (axis
.options
.autoscaleMargin
&& ticks
.length
> 0) {
1406 if (axis
.options
.min
== null)
1407 axis
.min
= Math
.min(axis
.min
, ticks
[0].v
);
1408 if (axis
.options
.max
== null && ticks
.length
> 1)
1409 axis
.max
= Math
.max(axis
.max
, ticks
[ticks
.length
- 1].v
);
1414 ctx
.clearRect(0, 0, canvasWidth
, canvasHeight
);
1416 var grid
= options
.grid
;
1418 // draw background, if any
1419 if (grid
.show
&& grid
.backgroundColor
)
1422 if (grid
.show
&& !grid
.aboveData
)
1425 for (var i
= 0; i
< series
.length
; ++i
) {
1426 executeHooks(hooks
.drawSeries
, [ctx
, series
[i
]]);
1427 drawSeries(series
[i
]);
1430 executeHooks(hooks
.draw
, [ctx
]);
1432 if (grid
.show
&& grid
.aboveData
)
1436 function extractRange(ranges
, coord
) {
1437 var axis
, from, to
, key
, axes
= allAxes();
1439 for (i
= 0; i
< axes
.length
; ++i
) {
1441 if (axis
.direction
== coord
) {
1442 key
= coord
+ axis
.n
+ "axis";
1443 if (!ranges
[key
] && axis
.n
== 1)
1444 key
= coord
+ "axis"; // support x1axis as xaxis
1446 from = ranges
[key
].from;
1447 to
= ranges
[key
].to
;
1453 // backwards-compat stuff - to be removed in future
1455 axis
= coord
== "x" ? xaxes
[0] : yaxes
[0];
1456 from = ranges
[coord
+ "1"];
1457 to
= ranges
[coord
+ "2"];
1460 // auto-reverse as an added bonus
1461 if (from != null && to
!= null && from > to
) {
1467 return { from: from, to: to
, axis: axis
};
1470 function drawBackground() {
1472 ctx
.translate(plotOffset
.left
, plotOffset
.top
);
1474 ctx
.fillStyle
= getColorOrGradient(options
.grid
.backgroundColor
, plotHeight
, 0, "rgba(255, 255, 255, 0)");
1475 ctx
.fillRect(0, 0, plotWidth
, plotHeight
);
1479 function drawGrid() {
1483 ctx
.translate(plotOffset
.left
, plotOffset
.top
);
1486 var markings
= options
.grid
.markings
;
1488 if ($.isFunction(markings
)) {
1489 var axes
= plot
.getAxes();
1490 // xmin etc. is backwards compatibility, to be
1491 // removed in the future
1492 axes
.xmin
= axes
.xaxis
.min
;
1493 axes
.xmax
= axes
.xaxis
.max
;
1494 axes
.ymin
= axes
.yaxis
.min
;
1495 axes
.ymax
= axes
.yaxis
.max
;
1497 markings
= markings(axes
);
1500 for (i
= 0; i
< markings
.length
; ++i
) {
1501 var m
= markings
[i
],
1502 xrange
= extractRange(m
, "x"),
1503 yrange
= extractRange(m
, "y");
1506 if (xrange
.from == null)
1507 xrange
.from = xrange
.axis
.min
;
1508 if (xrange
.to
== null)
1509 xrange
.to
= xrange
.axis
.max
;
1510 if (yrange
.from == null)
1511 yrange
.from = yrange
.axis
.min
;
1512 if (yrange
.to
== null)
1513 yrange
.to
= yrange
.axis
.max
;
1516 if (xrange
.to
< xrange
.axis
.min
|| xrange
.from > xrange
.axis
.max
||
1517 yrange
.to
< yrange
.axis
.min
|| yrange
.from > yrange
.axis
.max
)
1520 xrange
.from = Math
.max(xrange
.from, xrange
.axis
.min
);
1521 xrange
.to
= Math
.min(xrange
.to
, xrange
.axis
.max
);
1522 yrange
.from = Math
.max(yrange
.from, yrange
.axis
.min
);
1523 yrange
.to
= Math
.min(yrange
.to
, yrange
.axis
.max
);
1525 if (xrange
.from == xrange
.to
&& yrange
.from == yrange
.to
)
1529 xrange
.from = xrange
.axis
.p2c(xrange
.from);
1530 xrange
.to
= xrange
.axis
.p2c(xrange
.to
);
1531 yrange
.from = yrange
.axis
.p2c(yrange
.from);
1532 yrange
.to
= yrange
.axis
.p2c(yrange
.to
);
1534 if (xrange
.from == xrange
.to
|| yrange
.from == yrange
.to
) {
1537 ctx
.strokeStyle
= m
.color
|| options
.grid
.markingsColor
;
1538 ctx
.lineWidth
= m
.lineWidth
|| options
.grid
.markingsLineWidth
;
1539 ctx
.moveTo(xrange
.from, yrange
.from);
1540 ctx
.lineTo(xrange
.to
, yrange
.to
);
1545 ctx
.fillStyle
= m
.color
|| options
.grid
.markingsColor
;
1546 ctx
.fillRect(xrange
.from, yrange
.to
,
1547 xrange
.to
- xrange
.from,
1548 yrange
.from - yrange
.to
);
1554 var axes
= allAxes(), bw
= options
.grid
.borderWidth
;
1556 for (var j
= 0; j
< axes
.length
; ++j
) {
1557 var axis
= axes
[j
], box
= axis
.box
,
1558 t
= axis
.tickLength
, x
, y
, xoff
, yoff
;
1559 if (!axis
.show
|| axis
.ticks
.length
== 0)
1562 ctx
.strokeStyle
= axis
.options
.tickColor
|| $.color
.parse(axis
.options
.color
).scale('a', 0.22).toString();
1566 if (axis
.direction
== "x") {
1569 y
= (axis
.position
== "top" ? 0 : plotHeight
);
1571 y
= box
.top
- plotOffset
.top
+ (axis
.position
== "top" ? box
.height : 0);
1576 x
= (axis
.position
== "left" ? 0 : plotWidth
);
1578 x
= box
.left
- plotOffset
.left
+ (axis
.position
== "left" ? box
.width : 0);
1582 if (!axis
.innermost
) {
1585 if (axis
.direction
== "x")
1590 if (ctx
.lineWidth
== 1) {
1591 x
= Math
.floor(x
) + 0.5;
1592 y
= Math
.floor(y
) + 0.5;
1596 ctx
.lineTo(x
+ xoff
, y
+ yoff
);
1602 for (i
= 0; i
< axis
.ticks
.length
; ++i
) {
1603 var v
= axis
.ticks
[i
].v
;
1607 if (v
< axis
.min
|| v
> axis
.max
1608 // skip those lying on the axes if we got a border
1609 || (t
== "full" && bw
> 0
1610 && (v
== axis
.min
|| v
== axis
.max
)))
1613 if (axis
.direction
== "x") {
1615 yoff
= t
== "full" ? -plotHeight : t
;
1617 if (axis
.position
== "top")
1622 xoff
= t
== "full" ? -plotWidth : t
;
1624 if (axis
.position
== "left")
1628 if (ctx
.lineWidth
== 1) {
1629 if (axis
.direction
== "x")
1630 x
= Math
.floor(x
) + 0.5;
1632 y
= Math
.floor(y
) + 0.5;
1636 ctx
.lineTo(x
+ xoff
, y
+ yoff
);
1646 ctx
.strokeStyle
= options
.grid
.borderColor
;
1647 ctx
.strokeRect(-bw
/2, -bw
/2, plotWidth
+ bw
, plotHeight
+ bw
);
1653 function insertAxisLabels() {
1654 placeholder
.find(".tickLabels").remove();
1656 var html
= ['<div class="tickLabels" style="font-size:smaller">'];
1658 var axes
= allAxes();
1659 for (var j
= 0; j
< axes
.length
; ++j
) {
1660 var axis
= axes
[j
], box
= axis
.box
;
1663 //debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>')
1664 html
.push('<div class="' + axis
.direction
+ 'Axis ' + axis
.direction
+ axis
.n
+ 'Axis" style="color:' + axis
.options
.color
+ '">');
1665 for (var i
= 0; i
< axis
.ticks
.length
; ++i
) {
1666 var tick
= axis
.ticks
[i
];
1667 if (!tick
.label
|| tick
.v
< axis
.min
|| tick
.v
> axis
.max
)
1670 var pos
= {}, align
;
1672 if (axis
.direction
== "x") {
1674 pos
.left
= Math
.round(plotOffset
.left
+ axis
.p2c(tick
.v
) - axis
.labelWidth
/2);
1675 if (axis
.position
== "bottom")
1676 pos
.top
= box
.top
+ box
.padding
;
1678 pos
.bottom
= canvasHeight
- (box
.top
+ box
.height
- box
.padding
);
1681 pos
.top
= Math
.round(plotOffset
.top
+ axis
.p2c(tick
.v
) - axis
.labelHeight
/2);
1682 if (axis
.position
== "left") {
1683 pos
.right
= canvasWidth
- (box
.left
+ box
.width
- box
.padding
)
1687 pos
.left
= box
.left
+ box
.padding
;
1692 pos
.width
= axis
.labelWidth
;
1694 var style
= ["position:absolute", "text-align:" + align
];
1696 style
.push(a
+ ":" + pos
[a
] + "px")
1698 html
.push('<div class="tickLabel" style="' + style
.join(';') + '">' + tick
.label
+ '</div>');
1700 html
.push('</div>');
1703 html
.push('</div>');
1705 placeholder
.append(html
.join(""));
1708 function drawSeries(series
) {
1709 if (series
.lines
.show
)
1710 drawSeriesLines(series
);
1711 if (series
.bars
.show
)
1712 drawSeriesBars(series
);
1713 if (series
.points
.show
)
1714 drawSeriesPoints(series
);
1717 function drawSeriesLines(series
) {
1718 function plotLine(datapoints
, xoffset
, yoffset
, axisx
, axisy
) {
1719 var points
= datapoints
.points
,
1720 ps
= datapoints
.pointsize
,
1721 prevx
= null, prevy
= null;
1724 for (var i
= ps
; i
< points
.length
; i
+= ps
) {
1725 var x1
= points
[i
- ps
], y1
= points
[i
- ps
+ 1],
1726 x2
= points
[i
], y2
= points
[i
+ 1];
1728 if (x1
== null || x2
== null)
1732 if (y1
<= y2
&& y1
< axisy
.min
) {
1734 continue; // line segment is outside
1735 // compute new intersection point
1736 x1
= (axisy
.min
- y1
) / (y2
- y1
) * (x2
- x1
) + x1
;
1739 else if (y2
<= y1
&& y2
< axisy
.min
) {
1742 x2
= (axisy
.min
- y1
) / (y2
- y1
) * (x2
- x1
) + x1
;
1747 if (y1
>= y2
&& y1
> axisy
.max
) {
1750 x1
= (axisy
.max
- y1
) / (y2
- y1
) * (x2
- x1
) + x1
;
1753 else if (y2
>= y1
&& y2
> axisy
.max
) {
1756 x2
= (axisy
.max
- y1
) / (y2
- y1
) * (x2
- x1
) + x1
;
1761 if (x1
<= x2
&& x1
< axisx
.min
) {
1764 y1
= (axisx
.min
- x1
) / (x2
- x1
) * (y2
- y1
) + y1
;
1767 else if (x2
<= x1
&& x2
< axisx
.min
) {
1770 y2
= (axisx
.min
- x1
) / (x2
- x1
) * (y2
- y1
) + y1
;
1775 if (x1
>= x2
&& x1
> axisx
.max
) {
1778 y1
= (axisx
.max
- x1
) / (x2
- x1
) * (y2
- y1
) + y1
;
1781 else if (x2
>= x1
&& x2
> axisx
.max
) {
1784 y2
= (axisx
.max
- x1
) / (x2
- x1
) * (y2
- y1
) + y1
;
1788 if (x1
!= prevx
|| y1
!= prevy
)
1789 ctx
.moveTo(axisx
.p2c(x1
) + xoffset
, axisy
.p2c(y1
) + yoffset
);
1793 ctx
.lineTo(axisx
.p2c(x2
) + xoffset
, axisy
.p2c(y2
) + yoffset
);
1798 function plotLineArea(datapoints
, axisx
, axisy
) {
1799 var points
= datapoints
.points
,
1800 ps
= datapoints
.pointsize
,
1801 bottom
= Math
.min(Math
.max(0, axisy
.min
), axisy
.max
),
1802 i
= 0, top
, areaOpen
= false,
1803 ypos
= 1, segmentStart
= 0, segmentEnd
= 0;
1805 // we process each segment in two turns, first forward
1806 // direction to sketch out top, then once we hit the
1807 // end we go backwards to sketch the bottom
1809 if (ps
> 0 && i
> points
.length
+ ps
)
1812 i
+= ps
; // ps is negative if going backwards
1814 var x1
= points
[i
- ps
],
1815 y1
= points
[i
- ps
+ ypos
],
1816 x2
= points
[i
], y2
= points
[i
+ ypos
];
1819 if (ps
> 0 && x1
!= null && x2
== null) {
1827 if (ps
< 0 && i
== segmentStart
+ ps
) {
1828 // done with the reverse sweep
1833 i
= segmentStart
= segmentEnd
+ ps
;
1838 if (x1
== null || x2
== null)
1844 if (x1
<= x2
&& x1
< axisx
.min
) {
1847 y1
= (axisx
.min
- x1
) / (x2
- x1
) * (y2
- y1
) + y1
;
1850 else if (x2
<= x1
&& x2
< axisx
.min
) {
1853 y2
= (axisx
.min
- x1
) / (x2
- x1
) * (y2
- y1
) + y1
;
1858 if (x1
>= x2
&& x1
> axisx
.max
) {
1861 y1
= (axisx
.max
- x1
) / (x2
- x1
) * (y2
- y1
) + y1
;
1864 else if (x2
>= x1
&& x2
> axisx
.max
) {
1867 y2
= (axisx
.max
- x1
) / (x2
- x1
) * (y2
- y1
) + y1
;
1874 ctx
.moveTo(axisx
.p2c(x1
), axisy
.p2c(bottom
));
1878 // now first check the case where both is outside
1879 if (y1
>= axisy
.max
&& y2
>= axisy
.max
) {
1880 ctx
.lineTo(axisx
.p2c(x1
), axisy
.p2c(axisy
.max
));
1881 ctx
.lineTo(axisx
.p2c(x2
), axisy
.p2c(axisy
.max
));
1884 else if (y1
<= axisy
.min
&& y2
<= axisy
.min
) {
1885 ctx
.lineTo(axisx
.p2c(x1
), axisy
.p2c(axisy
.min
));
1886 ctx
.lineTo(axisx
.p2c(x2
), axisy
.p2c(axisy
.min
));
1890 // else it's a bit more complicated, there might
1891 // be a flat maxed out rectangle first, then a
1892 // triangular cutout or reverse; to find these
1893 // keep track of the current x values
1894 var x1old
= x1
, x2old
= x2
;
1896 // clip the y values, without shortcutting, we
1897 // go through all cases in turn
1900 if (y1
<= y2
&& y1
< axisy
.min
&& y2
>= axisy
.min
) {
1901 x1
= (axisy
.min
- y1
) / (y2
- y1
) * (x2
- x1
) + x1
;
1904 else if (y2
<= y1
&& y2
< axisy
.min
&& y1
>= axisy
.min
) {
1905 x2
= (axisy
.min
- y1
) / (y2
- y1
) * (x2
- x1
) + x1
;
1910 if (y1
>= y2
&& y1
> axisy
.max
&& y2
<= axisy
.max
) {
1911 x1
= (axisy
.max
- y1
) / (y2
- y1
) * (x2
- x1
) + x1
;
1914 else if (y2
>= y1
&& y2
> axisy
.max
&& y1
<= axisy
.max
) {
1915 x2
= (axisy
.max
- y1
) / (y2
- y1
) * (x2
- x1
) + x1
;
1919 // if the x value was changed we got a rectangle
1922 ctx
.lineTo(axisx
.p2c(x1old
), axisy
.p2c(y1
));
1923 // it goes to (x1, y1), but we fill that below
1926 // fill triangular section, this sometimes result
1927 // in redundant points if (x1, y1) hasn't changed
1928 // from previous line to, but we just ignore that
1929 ctx
.lineTo(axisx
.p2c(x1
), axisy
.p2c(y1
));
1930 ctx
.lineTo(axisx
.p2c(x2
), axisy
.p2c(y2
));
1932 // fill the other rectangle if it's there
1934 ctx
.lineTo(axisx
.p2c(x2
), axisy
.p2c(y2
));
1935 ctx
.lineTo(axisx
.p2c(x2old
), axisy
.p2c(y2
));
1941 ctx
.translate(plotOffset
.left
, plotOffset
.top
);
1942 ctx
.lineJoin
= "round";
1944 var lw
= series
.lines
.lineWidth
,
1945 sw
= series
.shadowSize
;
1946 // FIXME: consider another form of shadow when filling is turned on
1947 if (lw
> 0 && sw
> 0) {
1948 // draw shadow as a thick and thin line with transparency
1950 ctx
.strokeStyle
= "rgba(0,0,0,0.1)";
1951 // position shadow at angle from the mid of line
1952 var angle
= Math
.PI
/18;
1953 plotLine(series
.datapoints
, Math
.sin(angle
) * (lw
/2 + sw
/2), Math
.cos(angle
) * (lw
/2 + sw
/2), series
.xaxis
, series
.yaxis
);
1954 ctx
.lineWidth
= sw
/2;
1955 plotLine(series
.datapoints
, Math
.sin(angle
) * (lw
/2 + sw
/4), Math
.cos(angle
) * (lw
/2 + sw
/4), series
.xaxis
, series
.yaxis
);
1959 ctx
.strokeStyle
= series
.color
;
1960 var fillStyle
= getFillStyle(series
.lines
, series
.color
, 0, plotHeight
);
1962 ctx
.fillStyle
= fillStyle
;
1963 plotLineArea(series
.datapoints
, series
.xaxis
, series
.yaxis
);
1967 plotLine(series
.datapoints
, 0, 0, series
.xaxis
, series
.yaxis
);
1971 function drawSeriesPoints(series
) {
1972 function plotPoints(datapoints
, radius
, fillStyle
, offset
, shadow
, axisx
, axisy
, symbol
) {
1973 var points
= datapoints
.points
, ps
= datapoints
.pointsize
;
1975 for (var i
= 0; i
< points
.length
; i
+= ps
) {
1976 var x
= points
[i
], y
= points
[i
+ 1];
1977 if (x
== null || x
< axisx
.min
|| x
> axisx
.max
|| y
< axisy
.min
|| y
> axisy
.max
)
1982 y
= axisy
.p2c(y
) + offset
;
1983 if (symbol
== "circle")
1984 ctx
.arc(x
, y
, radius
, 0, shadow
? Math
.PI : Math
.PI
* 2, false);
1986 symbol(ctx
, x
, y
, radius
, shadow
);
1990 ctx
.fillStyle
= fillStyle
;
1998 ctx
.translate(plotOffset
.left
, plotOffset
.top
);
2000 var lw
= series
.points
.lineWidth
,
2001 sw
= series
.shadowSize
,
2002 radius
= series
.points
.radius
,
2003 symbol
= series
.points
.symbol
;
2004 if (lw
> 0 && sw
> 0) {
2005 // draw shadow in two steps
2008 ctx
.strokeStyle
= "rgba(0,0,0,0.1)";
2009 plotPoints(series
.datapoints
, radius
, null, w
+ w
/2, true,
2010 series
.xaxis
, series
.yaxis
, symbol
);
2012 ctx
.strokeStyle
= "rgba(0,0,0,0.2)";
2013 plotPoints(series
.datapoints
, radius
, null, w
/2, true,
2014 series
.xaxis
, series
.yaxis
, symbol
);
2018 ctx
.strokeStyle
= series
.color
;
2019 plotPoints(series
.datapoints
, radius
,
2020 getFillStyle(series
.points
, series
.color
), 0, false,
2021 series
.xaxis
, series
.yaxis
, symbol
);
2025 function drawBar(x
, y
, b
, barLeft
, barRight
, offset
, fillStyleCallback
, axisx
, axisy
, c
, horizontal
, lineWidth
) {
2026 var left
, right
, bottom
, top
,
2027 drawLeft
, drawRight
, drawTop
, drawBottom
,
2030 // in horizontal mode, we start the bar from the left
2031 // instead of from the bottom so it appears to be
2032 // horizontal rather than vertical
2034 drawBottom
= drawRight
= drawTop
= true;
2039 bottom
= y
+ barRight
;
2041 // account for negative bars
2051 drawLeft
= drawRight
= drawTop
= true;
2054 right
= x
+ barRight
;
2058 // account for negative bars
2069 if (right
< axisx
.min
|| left
> axisx
.max
||
2070 top
< axisy
.min
|| bottom
> axisy
.max
)
2073 if (left
< axisx
.min
) {
2078 if (right
> axisx
.max
) {
2083 if (bottom
< axisy
.min
) {
2088 if (top
> axisy
.max
) {
2093 left
= axisx
.p2c(left
);
2094 bottom
= axisy
.p2c(bottom
);
2095 right
= axisx
.p2c(right
);
2096 top
= axisy
.p2c(top
);
2099 if (fillStyleCallback
) {
2101 c
.moveTo(left
, bottom
);
2102 c
.lineTo(left
, top
);
2103 c
.lineTo(right
, top
);
2104 c
.lineTo(right
, bottom
);
2105 c
.fillStyle
= fillStyleCallback(bottom
, top
);
2110 if (lineWidth
> 0 && (drawLeft
|| drawRight
|| drawTop
|| drawBottom
)) {
2113 // FIXME: inline moveTo is buggy with excanvas
2114 c
.moveTo(left
, bottom
+ offset
);
2116 c
.lineTo(left
, top
+ offset
);
2118 c
.moveTo(left
, top
+ offset
);
2120 c
.lineTo(right
, top
+ offset
);
2122 c
.moveTo(right
, top
+ offset
);
2124 c
.lineTo(right
, bottom
+ offset
);
2126 c
.moveTo(right
, bottom
+ offset
);
2128 c
.lineTo(left
, bottom
+ offset
);
2130 c
.moveTo(left
, bottom
+ offset
);
2135 function drawSeriesBars(series
) {
2136 function plotBars(datapoints
, barLeft
, barRight
, offset
, fillStyleCallback
, axisx
, axisy
) {
2137 var points
= datapoints
.points
, ps
= datapoints
.pointsize
;
2139 for (var i
= 0; i
< points
.length
; i
+= ps
) {
2140 if (points
[i
] == null)
2142 drawBar(points
[i
], points
[i
+ 1], points
[i
+ 2], barLeft
, barRight
, offset
, fillStyleCallback
, axisx
, axisy
, ctx
, series
.bars
.horizontal
, series
.bars
.lineWidth
);
2147 ctx
.translate(plotOffset
.left
, plotOffset
.top
);
2149 // FIXME: figure out a way to add shadows (for instance along the right edge)
2150 ctx
.lineWidth
= series
.bars
.lineWidth
;
2151 ctx
.strokeStyle
= series
.color
;
2152 var barLeft
= series
.bars
.align
== "left" ? 0 : -series
.bars
.barWidth
/2;
2153 var fillStyleCallback
= series
.bars
.fill
? function (bottom
, top
) { return getFillStyle(series
.bars
, series
.color
, bottom
, top
); } : null;
2154 plotBars(series
.datapoints
, barLeft
, barLeft
+ series
.bars
.barWidth
, 0, fillStyleCallback
, series
.xaxis
, series
.yaxis
);
2158 function getFillStyle(filloptions
, seriesColor
, bottom
, top
) {
2159 var fill
= filloptions
.fill
;
2163 if (filloptions
.fillColor
)
2164 return getColorOrGradient(filloptions
.fillColor
, bottom
, top
, seriesColor
);
2166 var c
= $.color
.parse(seriesColor
);
2167 c
.a
= typeof fill
== "number" ? fill : 0.4;
2169 return c
.toString();
2172 function insertLegend() {
2173 placeholder
.find(".legend").remove();
2175 if (!options
.legend
.show
)
2178 var fragments
= [], rowStarted
= false,
2179 lf
= options
.legend
.labelFormatter
, s
, label
;
2180 for (var i
= 0; i
< series
.length
; ++i
) {
2186 if (i
% options
.legend
.noColumns
== 0) {
2188 fragments
.push('</tr>');
2189 fragments
.push('<tr>');
2194 label
= lf(label
, s
);
2197 '<td class="legendColorBox"><div style="border:1px solid ' + options
.legend
.labelBoxBorderColor
+ ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s
.color
+ ';overflow:hidden"></div></div></td>' +
2198 '<td class="legendLabel">' + label
+ '</td>');
2201 fragments
.push('</tr>');
2203 if (fragments
.length
== 0)
2206 var table
= '<table style="font-size:smaller;color:' + options
.grid
.color
+ '">' + fragments
.join("") + '</table>';
2207 if (options
.legend
.container
!= null)
2208 $(options
.legend
.container
).html(table
);
2211 p
= options
.legend
.position
,
2212 m
= options
.legend
.margin
;
2215 if (p
.charAt(0) == "n")
2216 pos
+= 'top:' + (m
[1] + plotOffset
.top
) + 'px;';
2217 else if (p
.charAt(0) == "s")
2218 pos
+= 'bottom:' + (m
[1] + plotOffset
.bottom
) + 'px;';
2219 if (p
.charAt(1) == "e")
2220 pos
+= 'right:' + (m
[0] + plotOffset
.right
) + 'px;';
2221 else if (p
.charAt(1) == "w")
2222 pos
+= 'left:' + (m
[0] + plotOffset
.left
) + 'px;';
2223 var legend
= $('<div class="legend">' + table
.replace('style="', 'style="position:absolute;' + pos
+';') + '</div>').appendTo(placeholder
);
2224 if (options
.legend
.backgroundOpacity
!= 0.0) {
2225 // put in the transparent background
2226 // separately to avoid blended labels and
2228 var c
= options
.legend
.backgroundColor
;
2230 c
= options
.grid
.backgroundColor
;
2231 if (c
&& typeof c
== "string")
2232 c
= $.color
.parse(c
);
2234 c
= $.color
.extract(legend
, 'background-color');
2238 var div
= legend
.children();
2239 $('<div style="position:absolute;width:' + div
.width() + 'px;height:' + div
.height() + 'px;' + pos
+'background-color:' + c
+ ';"> </div>').prependTo(legend
).css('opacity', options
.legend
.backgroundOpacity
);
2245 // interactive features
2247 var highlights
= [],
2248 redrawTimeout
= null;
2250 // returns the data item the mouse is over, or null if none is found
2251 function findNearbyItem(mouseX
, mouseY
, seriesFilter
) {
2252 var maxDistance
= options
.grid
.mouseActiveRadius
,
2253 smallestDistance
= maxDistance
* maxDistance
+ 1,
2254 item
= null, foundPoint
= false, i
, j
;
2256 for (i
= series
.length
- 1; i
>= 0; --i
) {
2257 if (!seriesFilter(series
[i
]))
2263 points
= s
.datapoints
.points
,
2264 ps
= s
.datapoints
.pointsize
,
2265 mx
= axisx
.c2p(mouseX
), // precompute some stuff to make the loop faster
2266 my
= axisy
.c2p(mouseY
),
2267 maxx
= maxDistance
/ axisx
.scale
,
2268 maxy
= maxDistance
/ axisy
.scale
;
2270 // with inverse transforms, we can't use the maxx/maxy
2271 // optimization, sadly
2272 if (axisx
.options
.inverseTransform
)
2273 maxx
= Number
.MAX_VALUE
;
2274 if (axisy
.options
.inverseTransform
)
2275 maxy
= Number
.MAX_VALUE
;
2277 if (s
.lines
.show
|| s
.points
.show
) {
2278 for (j
= 0; j
< points
.length
; j
+= ps
) {
2279 var x
= points
[j
], y
= points
[j
+ 1];
2283 // For points and lines, the cursor must be within a
2284 // certain distance to the data point
2285 if (x
- mx
> maxx
|| x
- mx
< -maxx
||
2286 y
- my
> maxy
|| y
- my
< -maxy
)
2289 // We have to calculate distances in pixels, not in
2290 // data units, because the scales of the axes may be different
2291 var dx
= Math
.abs(axisx
.p2c(x
) - mouseX
),
2292 dy
= Math
.abs(axisy
.p2c(y
) - mouseY
),
2293 dist
= dx
* dx
+ dy
* dy
; // we save the sqrt
2295 // use <= to ensure last point takes precedence
2296 // (last generally means on top of)
2297 if (dist
< smallestDistance
) {
2298 smallestDistance
= dist
;
2304 if (s
.bars
.show
&& !item
) { // no other point can be nearby
2305 var barLeft
= s
.bars
.align
== "left" ? 0 : -s
.bars
.barWidth
/2,
2306 barRight
= barLeft
+ s
.bars
.barWidth
;
2308 for (j
= 0; j
< points
.length
; j
+= ps
) {
2309 var x
= points
[j
], y
= points
[j
+ 1], b
= points
[j
+ 2];
2313 // for a bar graph, the cursor must be inside the bar
2314 if (series
[i
].bars
.horizontal
?
2315 (mx
<= Math
.max(b
, x
) && mx
>= Math
.min(b
, x
) &&
2316 my
>= y
+ barLeft
&& my
<= y
+ barRight
) :
2317 (mx
>= x
+ barLeft
&& mx
<= x
+ barRight
&&
2318 my
>= Math
.min(b
, y
) && my
<= Math
.max(b
, y
)))
2327 ps
= series
[i
].datapoints
.pointsize
;
2329 return { datapoint: series
[i
].datapoints
.points
.slice(j
* ps
, (j
+ 1) * ps
),
2338 function onMouseMove(e
) {
2339 if (options
.grid
.hoverable
)
2340 triggerClickHoverEvent("plothover", e
,
2341 function (s
) { return s
["hoverable"] != false; });
2344 function onMouseLeave(e
) {
2345 if (options
.grid
.hoverable
)
2346 triggerClickHoverEvent("plothover", e
,
2347 function (s
) { return false; });
2350 function onClick(e
) {
2351 triggerClickHoverEvent("plotclick", e
,
2352 function (s
) { return s
["clickable"] != false; });
2355 // trigger click or hover event (they send the same parameters
2356 // so we share their code)
2357 function triggerClickHoverEvent(eventname
, event
, seriesFilter
) {
2358 var offset
= eventHolder
.offset(),
2359 canvasX
= event
.pageX
- offset
.left
- plotOffset
.left
,
2360 canvasY
= event
.pageY
- offset
.top
- plotOffset
.top
,
2361 pos
= canvasToAxisCoords({ left: canvasX
, top: canvasY
});
2363 pos
.pageX
= event
.pageX
;
2364 pos
.pageY
= event
.pageY
;
2366 var item
= findNearbyItem(canvasX
, canvasY
, seriesFilter
);
2369 // fill in mouse pos for any listeners out there
2370 item
.pageX
= parseInt(item
.series
.xaxis
.p2c(item
.datapoint
[0]) + offset
.left
+ plotOffset
.left
);
2371 item
.pageY
= parseInt(item
.series
.yaxis
.p2c(item
.datapoint
[1]) + offset
.top
+ plotOffset
.top
);
2374 if (options
.grid
.autoHighlight
) {
2375 // clear auto-highlights
2376 for (var i
= 0; i
< highlights
.length
; ++i
) {
2377 var h
= highlights
[i
];
2378 if (h
.auto
== eventname
&&
2379 !(item
&& h
.series
== item
.series
&&
2380 h
.point
[0] == item
.datapoint
[0] &&
2381 h
.point
[1] == item
.datapoint
[1]))
2382 unhighlight(h
.series
, h
.point
);
2386 highlight(item
.series
, item
.datapoint
, eventname
);
2389 placeholder
.trigger(eventname
, [ pos
, item
]);
2392 function triggerRedrawOverlay() {
2394 redrawTimeout
= setTimeout(drawOverlay
, 30);
2397 function drawOverlay() {
2398 redrawTimeout
= null;
2402 octx
.clearRect(0, 0, canvasWidth
, canvasHeight
);
2403 octx
.translate(plotOffset
.left
, plotOffset
.top
);
2406 for (i
= 0; i
< highlights
.length
; ++i
) {
2409 if (hi
.series
.bars
.show
)
2410 drawBarHighlight(hi
.series
, hi
.point
);
2412 drawPointHighlight(hi
.series
, hi
.point
);
2416 executeHooks(hooks
.drawOverlay
, [octx
]);
2419 function highlight(s
, point
, auto
) {
2420 if (typeof s
== "number")
2423 if (typeof point
== "number") {
2424 var ps
= s
.datapoints
.pointsize
;
2425 point
= s
.datapoints
.points
.slice(ps
* point
, ps
* (point
+ 1));
2428 var i
= indexOfHighlight(s
, point
);
2430 highlights
.push({ series: s
, point: point
, auto: auto
});
2432 triggerRedrawOverlay();
2435 highlights
[i
].auto
= false;
2438 function unhighlight(s
, point
) {
2439 if (s
== null && point
== null) {
2441 triggerRedrawOverlay();
2444 if (typeof s
== "number")
2447 if (typeof point
== "number")
2448 point
= s
.data
[point
];
2450 var i
= indexOfHighlight(s
, point
);
2452 highlights
.splice(i
, 1);
2454 triggerRedrawOverlay();
2458 function indexOfHighlight(s
, p
) {
2459 for (var i
= 0; i
< highlights
.length
; ++i
) {
2460 var h
= highlights
[i
];
2461 if (h
.series
== s
&& h
.point
[0] == p
[0]
2462 && h
.point
[1] == p
[1])
2468 function drawPointHighlight(series
, point
) {
2469 var x
= point
[0], y
= point
[1],
2470 axisx
= series
.xaxis
, axisy
= series
.yaxis
;
2472 if (x
< axisx
.min
|| x
> axisx
.max
|| y
< axisy
.min
|| y
> axisy
.max
)
2475 var pointRadius
= series
.points
.radius
+ series
.points
.lineWidth
/ 2;
2476 octx
.lineWidth
= pointRadius
;
2477 octx
.strokeStyle
= $.color
.parse(series
.color
).scale('a', 0.5).toString();
2478 var radius
= 1.5 * pointRadius
,
2483 if (series
.points
.symbol
== "circle")
2484 octx
.arc(x
, y
, radius
, 0, 2 * Math
.PI
, false);
2486 series
.points
.symbol(octx
, x
, y
, radius
, false);
2491 function drawBarHighlight(series
, point
) {
2492 octx
.lineWidth
= series
.bars
.lineWidth
;
2493 octx
.strokeStyle
= $.color
.parse(series
.color
).scale('a', 0.5).toString();
2494 var fillStyle
= $.color
.parse(series
.color
).scale('a', 0.5).toString();
2495 var barLeft
= series
.bars
.align
== "left" ? 0 : -series
.bars
.barWidth
/2;
2496 drawBar(point
[0], point
[1], point
[2] || 0, barLeft
, barLeft
+ series
.bars
.barWidth
,
2497 0, function () { return fillStyle
; }, series
.xaxis
, series
.yaxis
, octx
, series
.bars
.horizontal
, series
.bars
.lineWidth
);
2500 function getColorOrGradient(spec
, bottom
, top
, defaultColor
) {
2501 if (typeof spec
== "string")
2504 // assume this is a gradient spec; IE currently only
2505 // supports a simple vertical gradient properly, so that's
2506 // what we support too
2507 var gradient
= ctx
.createLinearGradient(0, top
, 0, bottom
);
2509 for (var i
= 0, l
= spec
.colors
.length
; i
< l
; ++i
) {
2510 var c
= spec
.colors
[i
];
2511 if (typeof c
!= "string") {
2512 var co
= $.color
.parse(defaultColor
);
2513 if (c
.brightness
!= null)
2514 co
= co
.scale('rgb', c
.brightness
)
2515 if (c
.opacity
!= null)
2519 gradient
.addColorStop(i
/ (l
- 1), c
);
2527 $.plot = function(placeholder
, data
, options
) {
2528 //var t0 = new Date();
2529 var plot
= new Plot($(placeholder
), data
, options
, $.plot
.plugins
);
2530 //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
2534 $.plot
.version
= "0.7";
2536 $.plot
.plugins
= [];
2538 // returns a string with the date d formatted according to fmt
2539 $.plot
.formatDate = function(d
, fmt
, monthNames
) {
2540 var leftPad = function(n
) {
2542 return n
.length
== 1 ? "0" + n : n
;
2546 var escape
= false, padNext
= false;
2547 var hours
= d
.getUTCHours();
2548 var isAM
= hours
< 12;
2549 if (monthNames
== null)
2550 monthNames
= ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2552 if (fmt
.search(/%p|%P/) != -1) {
2555 } else if (hours
== 0) {
2559 for (var i
= 0; i
< fmt
.length
; ++i
) {
2560 var c
= fmt
.charAt(i
);
2564 case 'h': c
= "" + hours
; break;
2565 case 'H': c
= leftPad(hours
); break;
2566 case 'M': c
= leftPad(d
.getUTCMinutes()); break;
2567 case 'S': c
= leftPad(d
.getUTCSeconds()); break;
2568 case 'd': c
= "" + d
.getUTCDate(); break;
2569 case 'm': c
= "" + (d
.getUTCMonth() + 1); break;
2570 case 'y': c
= "" + d
.getUTCFullYear(); break;
2571 case 'b': c
= "" + monthNames
[d
.getUTCMonth()]; break;
2572 case 'p': c
= (isAM
) ? ("" + "am") : ("" + "pm"); break;
2573 case 'P': c
= (isAM
) ? ("" + "AM") : ("" + "PM"); break;
2574 case '0': c
= ""; padNext
= true; break;
2594 // round to nearby lower multiple of base
2595 function floorInBase(n
, base
) {
2596 return base
* Math
.floor(n
/ base
);