logstash-lite 0.2.20101118134500
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/logstash +56 -0
- data/bin/logstash-web +6 -0
- data/etc/logstash-elasticsearch-rabbitmq-river.yaml +41 -0
- data/etc/logstash-mongodb-storage.yaml +5 -0
- data/etc/logstash-parser.yaml +20 -0
- data/etc/logstash-reader.yaml +8 -0
- data/etc/logstash-shipper.yaml +18 -0
- data/etc/logstash-standalone.yaml +47 -0
- data/etc/prod.yaml +38 -0
- data/etc/redhat/logstash +92 -0
- data/etc/redhat/logstash-agent +83 -0
- data/etc/redhat/logstash-agent.sysconfig +7 -0
- data/etc/redhat/logstash.spec +171 -0
- data/etc/redhat/logstash.sysconfig +18 -0
- data/etc/tograylog.yaml +37 -0
- data/examples/test.rb +38 -0
- data/lib/logstash.rb +3 -0
- data/lib/logstash/agent.rb +116 -0
- data/lib/logstash/event.rb +70 -0
- data/lib/logstash/filters.rb +17 -0
- data/lib/logstash/filters/base.rb +17 -0
- data/lib/logstash/filters/date.rb +59 -0
- data/lib/logstash/filters/field.rb +29 -0
- data/lib/logstash/filters/grok.rb +74 -0
- data/lib/logstash/filters/grokdiscovery.rb +60 -0
- data/lib/logstash/inputs.rb +18 -0
- data/lib/logstash/inputs/amqp.rb +48 -0
- data/lib/logstash/inputs/base.rb +32 -0
- data/lib/logstash/inputs/file.rb +47 -0
- data/lib/logstash/inputs/syslog.rb +123 -0
- data/lib/logstash/inputs/tcp.rb +51 -0
- data/lib/logstash/logging.rb +82 -0
- data/lib/logstash/namespace.rb +6 -0
- data/lib/logstash/outputs.rb +15 -0
- data/lib/logstash/outputs/amqp.rb +48 -0
- data/lib/logstash/outputs/base.rb +29 -0
- data/lib/logstash/outputs/elasticsearch.rb +71 -0
- data/lib/logstash/outputs/gelf.rb +35 -0
- data/lib/logstash/outputs/mongodb.rb +19 -0
- data/lib/logstash/outputs/stdout.rb +15 -0
- data/lib/logstash/outputs/websocket.rb +35 -0
- data/lib/logstash/time.rb +27 -0
- data/lib/logstash/web/lib/elasticsearch.rb +79 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/lib/logstash/web/public/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/lib/logstash/web/public/css/smoothness/jquery-ui-1.8.5.custom.css +572 -0
- data/lib/logstash/web/public/js/flot/API.txt +1024 -0
- data/lib/logstash/web/public/js/flot/FAQ.txt +71 -0
- data/lib/logstash/web/public/js/flot/LICENSE.txt +22 -0
- data/lib/logstash/web/public/js/flot/Makefile +15 -0
- data/lib/logstash/web/public/js/flot/NEWS.txt +340 -0
- data/lib/logstash/web/public/js/flot/PLUGINS.txt +105 -0
- data/lib/logstash/web/public/js/flot/README.txt +81 -0
- data/lib/logstash/web/public/js/flot/examples/ajax.html +143 -0
- data/lib/logstash/web/public/js/flot/examples/annotating.html +75 -0
- data/lib/logstash/web/public/js/flot/examples/arrow-down.gif +0 -0
- data/lib/logstash/web/public/js/flot/examples/arrow-left.gif +0 -0
- data/lib/logstash/web/public/js/flot/examples/arrow-right.gif +0 -0
- data/lib/logstash/web/public/js/flot/examples/arrow-up.gif +0 -0
- data/lib/logstash/web/public/js/flot/examples/basic.html +38 -0
- data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-1.json +4 -0
- data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-2.json +4 -0
- data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-3.json +4 -0
- data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-4.json +4 -0
- data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-5.json +4 -0
- data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth.json +4 -0
- data/lib/logstash/web/public/js/flot/examples/data-japan-gdp-growth.json +4 -0
- data/lib/logstash/web/public/js/flot/examples/data-usa-gdp-growth.json +4 -0
- data/lib/logstash/web/public/js/flot/examples/dual-axis.html +39 -0
- data/lib/logstash/web/public/js/flot/examples/graph-types.html +75 -0
- data/lib/logstash/web/public/js/flot/examples/hs-2004-27-a-large_web.jpg +0 -0
- data/lib/logstash/web/public/js/flot/examples/image.html +45 -0
- data/lib/logstash/web/public/js/flot/examples/index.html +43 -0
- data/lib/logstash/web/public/js/flot/examples/interacting.html +93 -0
- data/lib/logstash/web/public/js/flot/examples/layout.css +6 -0
- data/lib/logstash/web/public/js/flot/examples/navigate.html +118 -0
- data/lib/logstash/web/public/js/flot/examples/selection.html +114 -0
- data/lib/logstash/web/public/js/flot/examples/setting-options.html +65 -0
- data/lib/logstash/web/public/js/flot/examples/stacking.html +77 -0
- data/lib/logstash/web/public/js/flot/examples/thresholding.html +54 -0
- data/lib/logstash/web/public/js/flot/examples/time.html +71 -0
- data/lib/logstash/web/public/js/flot/examples/tracking.html +95 -0
- data/lib/logstash/web/public/js/flot/examples/turning-series.html +98 -0
- data/lib/logstash/web/public/js/flot/examples/visitors.html +90 -0
- data/lib/logstash/web/public/js/flot/examples/zooming.html +98 -0
- data/lib/logstash/web/public/js/flot/excanvas.js +1427 -0
- data/lib/logstash/web/public/js/flot/excanvas.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.colorhelpers.js +174 -0
- data/lib/logstash/web/public/js/flot/jquery.colorhelpers.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.crosshair.js +156 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.crosshair.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.image.js +237 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.image.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.js +2119 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.navigate.js +272 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.navigate.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.selection.js +299 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.selection.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.stack.js +152 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.stack.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.threshold.js +103 -0
- data/lib/logstash/web/public/js/flot/jquery.flot.threshold.min.js +1 -0
- data/lib/logstash/web/public/js/flot/jquery.js +4376 -0
- data/lib/logstash/web/public/js/flot/jquery.min.js +19 -0
- data/lib/logstash/web/public/js/jquery-hashchange-1.0.0.js +121 -0
- data/lib/logstash/web/public/js/jquery.livequery.js +250 -0
- data/lib/logstash/web/public/js/jquery.tmpl.min.js +1 -0
- data/lib/logstash/web/public/js/logstash.js +202 -0
- data/lib/logstash/web/server.rb +90 -0
- data/lib/logstash/web/views/header.haml +8 -0
- data/lib/logstash/web/views/layout.haml +21 -0
- data/lib/logstash/web/views/main/index.haml +5 -0
- data/lib/logstash/web/views/search/ajax.haml +32 -0
- data/lib/logstash/web/views/search/results.haml +17 -0
- data/lib/logstash/web/views/style.sass +50 -0
- data/patterns/firewalls +2 -0
- data/patterns/grok-patterns +90 -0
- data/patterns/haproxy +5 -0
- data/patterns/linux-syslog +7 -0
- data/patterns/nagios +7 -0
- data/patterns/ruby +2 -0
- metadata +228 -0
@@ -0,0 +1 @@
|
|
1
|
+
(function(D){var B={series:{images:{show:false,alpha:1,anchor:"corner"}}};D.plot.image={};D.plot.image.loadDataImages=function(G,F,K){var J=[],H=[];var I=F.series.images.show;D.each(G,function(L,M){if(!(I||M.images.show)){return }if(M.data){M=M.data}D.each(M,function(N,O){if(typeof O[0]=="string"){J.push(O[0]);H.push(O)}})});D.plot.image.load(J,function(L){D.each(H,function(N,O){var M=O[0];if(L[M]){O[0]=L[M]}});K()})};D.plot.image.load=function(H,I){var G=H.length,F={};if(G==0){I({})}D.each(H,function(K,J){var L=function(){--G;F[J]=this;if(G==0){I(F)}};D("<img />").load(L).error(L).attr("src",J)})};function A(H,F){var G=H.getPlotOffset();D.each(H.getData(),function(O,P){var X=P.datapoints.points,I=P.datapoints.pointsize;for(var O=0;O<X.length;O+=I){var Q=X[O],M=X[O+1],V=X[O+2],K=X[O+3],T=X[O+4],W=P.xaxis,S=P.yaxis,N;if(!Q||Q.width<=0||Q.height<=0){continue}if(M>K){N=K;K=M;M=N}if(V>T){N=T;T=V;V=N}if(P.images.anchor=="center"){N=0.5*(K-M)/(Q.width-1);M-=N;K+=N;N=0.5*(T-V)/(Q.height-1);V-=N;T+=N}if(M==K||V==T||M>=W.max||K<=W.min||V>=S.max||T<=S.min){continue}var L=0,U=0,J=Q.width,R=Q.height;if(M<W.min){L+=(J-L)*(W.min-M)/(K-M);M=W.min}if(K>W.max){J+=(J-L)*(W.max-K)/(K-M);K=W.max}if(V<S.min){R+=(U-R)*(S.min-V)/(T-V);V=S.min}if(T>S.max){U+=(U-R)*(S.max-T)/(T-V);T=S.max}M=W.p2c(M);K=W.p2c(K);V=S.p2c(V);T=S.p2c(T);if(M>K){N=K;K=M;M=N}if(V>T){N=T;T=V;V=N}N=F.globalAlpha;F.globalAlpha*=P.images.alpha;F.drawImage(Q,L,U,J-L,R-U,M+G.left,V+G.top,K-M,T-V);F.globalAlpha=N}})}function C(I,F,G,H){if(!F.images.show){return }H.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function E(F){F.hooks.processRawData.push(C);F.hooks.draw.push(A)}D.plot.plugins.push({init:E,options:B,name:"image",version:"1.1"})})(jQuery);
|
@@ -0,0 +1,2119 @@
|
|
1
|
+
/* Javascript plotting library for jQuery, v. 0.6.
|
2
|
+
*
|
3
|
+
* Released under the MIT license by IOLA, December 2007.
|
4
|
+
*
|
5
|
+
*/
|
6
|
+
|
7
|
+
// first an inline dependency, jquery.colorhelpers.js, we inline it here
|
8
|
+
// for convenience
|
9
|
+
|
10
|
+
/* Plugin for jQuery for working with colors.
|
11
|
+
*
|
12
|
+
* Version 1.0.
|
13
|
+
*
|
14
|
+
* Inspiration from jQuery color animation plugin by John Resig.
|
15
|
+
*
|
16
|
+
* Released under the MIT license by Ole Laursen, October 2009.
|
17
|
+
*
|
18
|
+
* Examples:
|
19
|
+
*
|
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)"
|
24
|
+
*
|
25
|
+
* Note that .scale() and .add() work in-place instead of returning
|
26
|
+
* new objects.
|
27
|
+
*/
|
28
|
+
(function(){jQuery.color={};jQuery.color.make=function(E,D,B,C){var F={};F.r=E||0;F.g=D||0;F.b=B||0;F.a=C!=null?C:1;F.add=function(I,H){for(var G=0;G<I.length;++G){F[I.charAt(G)]+=H}return F.normalize()};F.scale=function(I,H){for(var G=0;G<I.length;++G){F[I.charAt(G)]*=H}return F.normalize()};F.toString=function(){if(F.a>=1){return"rgb("+[F.r,F.g,F.b].join(",")+")"}else{return"rgba("+[F.r,F.g,F.b,F.a].join(",")+")"}};F.normalize=function(){function G(I,J,H){return J<I?I:(J>H?H:J)}F.r=G(0,parseInt(F.r),255);F.g=G(0,parseInt(F.g),255);F.b=G(0,parseInt(F.b),255);F.a=G(0,F.a,1);return F};F.clone=function(){return jQuery.color.make(F.r,F.b,F.g,F.a)};return F.normalize()};jQuery.color.extract=function(C,B){var D;do{D=C.css(B).toLowerCase();if(D!=""&&D!="transparent"){break}C=C.parent()}while(!jQuery.nodeName(C.get(0),"body"));if(D=="rgba(0, 0, 0, 0)"){D="transparent"}return jQuery.color.parse(D)};jQuery.color.parse=function(E){var D,B=jQuery.color.make;if(D=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10))}if(D=/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(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10),parseFloat(D[4]))}if(D=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55)}if(D=/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(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55,parseFloat(D[4]))}if(D=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(E)){return B(parseInt(D[1],16),parseInt(D[2],16),parseInt(D[3],16))}if(D=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(E)){return B(parseInt(D[1]+D[1],16),parseInt(D[2]+D[2],16),parseInt(D[3]+D[3],16))}var C=jQuery.trim(E).toLowerCase();if(C=="transparent"){return B(255,255,255,0)}else{D=A[C];return B(D[0],D[1],D[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]}})();
|
29
|
+
|
30
|
+
// the actual Flot code
|
31
|
+
(function($) {
|
32
|
+
function Plot(placeholder, data_, options_, plugins) {
|
33
|
+
// data is on the form:
|
34
|
+
// [ series1, series2 ... ]
|
35
|
+
// where series is either just the data as [ [x1, y1], [x2, y2], ... ]
|
36
|
+
// or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
|
37
|
+
|
38
|
+
var series = [],
|
39
|
+
options = {
|
40
|
+
// the color theme used for graphs
|
41
|
+
colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
|
42
|
+
legend: {
|
43
|
+
show: true,
|
44
|
+
noColumns: 1, // number of colums in legend table
|
45
|
+
labelFormatter: null, // fn: string -> string
|
46
|
+
labelBoxBorderColor: "#ccc", // border color for the little label boxes
|
47
|
+
container: null, // container (as jQuery object) to put legend in, null means default on top of graph
|
48
|
+
position: "ne", // position of default legend container within plot
|
49
|
+
margin: 5, // distance from grid edge to default legend container within plot
|
50
|
+
backgroundColor: null, // null means auto-detect
|
51
|
+
backgroundOpacity: 0.85 // set to 0 to avoid background
|
52
|
+
},
|
53
|
+
xaxis: {
|
54
|
+
mode: null, // null or "time"
|
55
|
+
transform: null, // null or f: number -> number to transform axis
|
56
|
+
inverseTransform: null, // if transform is set, this should be the inverse function
|
57
|
+
min: null, // min. value to show, null means set automatically
|
58
|
+
max: null, // max. value to show, null means set automatically
|
59
|
+
autoscaleMargin: null, // margin in % to add if auto-setting min/max
|
60
|
+
ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
|
61
|
+
tickFormatter: null, // fn: number -> string
|
62
|
+
labelWidth: null, // size of tick labels in pixels
|
63
|
+
labelHeight: null,
|
64
|
+
|
65
|
+
// mode specific options
|
66
|
+
tickDecimals: null, // no. of decimals, null means auto
|
67
|
+
tickSize: null, // number or [number, "unit"]
|
68
|
+
minTickSize: null, // number or [number, "unit"]
|
69
|
+
monthNames: null, // list of names of months
|
70
|
+
timeformat: null, // format string to use
|
71
|
+
twelveHourClock: false // 12 or 24 time in time mode
|
72
|
+
},
|
73
|
+
yaxis: {
|
74
|
+
autoscaleMargin: 0.02
|
75
|
+
},
|
76
|
+
x2axis: {
|
77
|
+
autoscaleMargin: null
|
78
|
+
},
|
79
|
+
y2axis: {
|
80
|
+
autoscaleMargin: 0.02
|
81
|
+
},
|
82
|
+
series: {
|
83
|
+
points: {
|
84
|
+
show: false,
|
85
|
+
radius: 3,
|
86
|
+
lineWidth: 2, // in pixels
|
87
|
+
fill: true,
|
88
|
+
fillColor: "#ffffff"
|
89
|
+
},
|
90
|
+
lines: {
|
91
|
+
// we don't put in show: false so we can see
|
92
|
+
// whether lines were actively disabled
|
93
|
+
lineWidth: 2, // in pixels
|
94
|
+
fill: false,
|
95
|
+
fillColor: null,
|
96
|
+
steps: false
|
97
|
+
},
|
98
|
+
bars: {
|
99
|
+
show: false,
|
100
|
+
lineWidth: 2, // in pixels
|
101
|
+
barWidth: 1, // in units of the x axis
|
102
|
+
fill: true,
|
103
|
+
fillColor: null,
|
104
|
+
align: "left", // or "center"
|
105
|
+
horizontal: false // when horizontal, left is now top
|
106
|
+
},
|
107
|
+
shadowSize: 3
|
108
|
+
},
|
109
|
+
grid: {
|
110
|
+
show: true,
|
111
|
+
aboveData: false,
|
112
|
+
color: "#545454", // primary color used for outline and labels
|
113
|
+
backgroundColor: null, // null for transparent, else color
|
114
|
+
tickColor: "rgba(0,0,0,0.15)", // color used for the ticks
|
115
|
+
labelMargin: 5, // in pixels
|
116
|
+
borderWidth: 2, // in pixels
|
117
|
+
borderColor: null, // set if different from the grid color
|
118
|
+
markings: null, // array of ranges or fn: axes -> array of ranges
|
119
|
+
markingsColor: "#f4f4f4",
|
120
|
+
markingsLineWidth: 2,
|
121
|
+
// interactive stuff
|
122
|
+
clickable: false,
|
123
|
+
hoverable: false,
|
124
|
+
autoHighlight: true, // highlight in case mouse is near
|
125
|
+
mouseActiveRadius: 10 // how far the mouse can be away to activate an item
|
126
|
+
},
|
127
|
+
hooks: {}
|
128
|
+
},
|
129
|
+
canvas = null, // the canvas for the plot itself
|
130
|
+
overlay = null, // canvas for interactive stuff on top of plot
|
131
|
+
eventHolder = null, // jQuery object that events should be bound to
|
132
|
+
ctx = null, octx = null,
|
133
|
+
axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} },
|
134
|
+
plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
|
135
|
+
canvasWidth = 0, canvasHeight = 0,
|
136
|
+
plotWidth = 0, plotHeight = 0,
|
137
|
+
hooks = {
|
138
|
+
processOptions: [],
|
139
|
+
processRawData: [],
|
140
|
+
processDatapoints: [],
|
141
|
+
draw: [],
|
142
|
+
bindEvents: [],
|
143
|
+
drawOverlay: []
|
144
|
+
},
|
145
|
+
plot = this;
|
146
|
+
|
147
|
+
// public functions
|
148
|
+
plot.setData = setData;
|
149
|
+
plot.setupGrid = setupGrid;
|
150
|
+
plot.draw = draw;
|
151
|
+
plot.getPlaceholder = function() { return placeholder; };
|
152
|
+
plot.getCanvas = function() { return canvas; };
|
153
|
+
plot.getPlotOffset = function() { return plotOffset; };
|
154
|
+
plot.width = function () { return plotWidth; };
|
155
|
+
plot.height = function () { return plotHeight; };
|
156
|
+
plot.offset = function () {
|
157
|
+
var o = eventHolder.offset();
|
158
|
+
o.left += plotOffset.left;
|
159
|
+
o.top += plotOffset.top;
|
160
|
+
return o;
|
161
|
+
};
|
162
|
+
plot.getData = function() { return series; };
|
163
|
+
plot.getAxes = function() { return axes; };
|
164
|
+
plot.getOptions = function() { return options; };
|
165
|
+
plot.highlight = highlight;
|
166
|
+
plot.unhighlight = unhighlight;
|
167
|
+
plot.triggerRedrawOverlay = triggerRedrawOverlay;
|
168
|
+
plot.pointOffset = function(point) {
|
169
|
+
return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left),
|
170
|
+
top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) };
|
171
|
+
};
|
172
|
+
|
173
|
+
|
174
|
+
// public attributes
|
175
|
+
plot.hooks = hooks;
|
176
|
+
|
177
|
+
// initialize
|
178
|
+
initPlugins(plot);
|
179
|
+
parseOptions(options_);
|
180
|
+
constructCanvas();
|
181
|
+
setData(data_);
|
182
|
+
setupGrid();
|
183
|
+
draw();
|
184
|
+
bindEvents();
|
185
|
+
|
186
|
+
|
187
|
+
function executeHooks(hook, args) {
|
188
|
+
args = [plot].concat(args);
|
189
|
+
for (var i = 0; i < hook.length; ++i)
|
190
|
+
hook[i].apply(this, args);
|
191
|
+
}
|
192
|
+
|
193
|
+
function initPlugins() {
|
194
|
+
for (var i = 0; i < plugins.length; ++i) {
|
195
|
+
var p = plugins[i];
|
196
|
+
p.init(plot);
|
197
|
+
if (p.options)
|
198
|
+
$.extend(true, options, p.options);
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
function parseOptions(opts) {
|
203
|
+
$.extend(true, options, opts);
|
204
|
+
if (options.grid.borderColor == null)
|
205
|
+
options.grid.borderColor = options.grid.color;
|
206
|
+
// backwards compatibility, to be removed in future
|
207
|
+
if (options.xaxis.noTicks && options.xaxis.ticks == null)
|
208
|
+
options.xaxis.ticks = options.xaxis.noTicks;
|
209
|
+
if (options.yaxis.noTicks && options.yaxis.ticks == null)
|
210
|
+
options.yaxis.ticks = options.yaxis.noTicks;
|
211
|
+
if (options.grid.coloredAreas)
|
212
|
+
options.grid.markings = options.grid.coloredAreas;
|
213
|
+
if (options.grid.coloredAreasColor)
|
214
|
+
options.grid.markingsColor = options.grid.coloredAreasColor;
|
215
|
+
if (options.lines)
|
216
|
+
$.extend(true, options.series.lines, options.lines);
|
217
|
+
if (options.points)
|
218
|
+
$.extend(true, options.series.points, options.points);
|
219
|
+
if (options.bars)
|
220
|
+
$.extend(true, options.series.bars, options.bars);
|
221
|
+
if (options.shadowSize)
|
222
|
+
options.series.shadowSize = options.shadowSize;
|
223
|
+
|
224
|
+
for (var n in hooks)
|
225
|
+
if (options.hooks[n] && options.hooks[n].length)
|
226
|
+
hooks[n] = hooks[n].concat(options.hooks[n]);
|
227
|
+
|
228
|
+
executeHooks(hooks.processOptions, [options]);
|
229
|
+
}
|
230
|
+
|
231
|
+
function setData(d) {
|
232
|
+
series = parseData(d);
|
233
|
+
fillInSeriesOptions();
|
234
|
+
processData();
|
235
|
+
}
|
236
|
+
|
237
|
+
function parseData(d) {
|
238
|
+
var res = [];
|
239
|
+
for (var i = 0; i < d.length; ++i) {
|
240
|
+
var s = $.extend(true, {}, options.series);
|
241
|
+
|
242
|
+
if (d[i].data) {
|
243
|
+
s.data = d[i].data; // move the data instead of deep-copy
|
244
|
+
delete d[i].data;
|
245
|
+
|
246
|
+
$.extend(true, s, d[i]);
|
247
|
+
|
248
|
+
d[i].data = s.data;
|
249
|
+
}
|
250
|
+
else
|
251
|
+
s.data = d[i];
|
252
|
+
res.push(s);
|
253
|
+
}
|
254
|
+
|
255
|
+
return res;
|
256
|
+
}
|
257
|
+
|
258
|
+
function axisSpecToRealAxis(obj, attr) {
|
259
|
+
var a = obj[attr];
|
260
|
+
if (!a || a == 1)
|
261
|
+
return axes[attr];
|
262
|
+
if (typeof a == "number")
|
263
|
+
return axes[attr.charAt(0) + a + attr.slice(1)];
|
264
|
+
return a; // assume it's OK
|
265
|
+
}
|
266
|
+
|
267
|
+
function fillInSeriesOptions() {
|
268
|
+
var i;
|
269
|
+
|
270
|
+
// collect what we already got of colors
|
271
|
+
var neededColors = series.length,
|
272
|
+
usedColors = [],
|
273
|
+
assignedColors = [];
|
274
|
+
for (i = 0; i < series.length; ++i) {
|
275
|
+
var sc = series[i].color;
|
276
|
+
if (sc != null) {
|
277
|
+
--neededColors;
|
278
|
+
if (typeof sc == "number")
|
279
|
+
assignedColors.push(sc);
|
280
|
+
else
|
281
|
+
usedColors.push($.color.parse(series[i].color));
|
282
|
+
}
|
283
|
+
}
|
284
|
+
|
285
|
+
// we might need to generate more colors if higher indices
|
286
|
+
// are assigned
|
287
|
+
for (i = 0; i < assignedColors.length; ++i) {
|
288
|
+
neededColors = Math.max(neededColors, assignedColors[i] + 1);
|
289
|
+
}
|
290
|
+
|
291
|
+
// produce colors as needed
|
292
|
+
var colors = [], variation = 0;
|
293
|
+
i = 0;
|
294
|
+
while (colors.length < neededColors) {
|
295
|
+
var c;
|
296
|
+
if (options.colors.length == i) // check degenerate case
|
297
|
+
c = $.color.make(100, 100, 100);
|
298
|
+
else
|
299
|
+
c = $.color.parse(options.colors[i]);
|
300
|
+
|
301
|
+
// vary color if needed
|
302
|
+
var sign = variation % 2 == 1 ? -1 : 1;
|
303
|
+
c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
|
304
|
+
|
305
|
+
// FIXME: if we're getting to close to something else,
|
306
|
+
// we should probably skip this one
|
307
|
+
colors.push(c);
|
308
|
+
|
309
|
+
++i;
|
310
|
+
if (i >= options.colors.length) {
|
311
|
+
i = 0;
|
312
|
+
++variation;
|
313
|
+
}
|
314
|
+
}
|
315
|
+
|
316
|
+
// fill in the options
|
317
|
+
var colori = 0, s;
|
318
|
+
for (i = 0; i < series.length; ++i) {
|
319
|
+
s = series[i];
|
320
|
+
|
321
|
+
// assign colors
|
322
|
+
if (s.color == null) {
|
323
|
+
s.color = colors[colori].toString();
|
324
|
+
++colori;
|
325
|
+
}
|
326
|
+
else if (typeof s.color == "number")
|
327
|
+
s.color = colors[s.color].toString();
|
328
|
+
|
329
|
+
// turn on lines automatically in case nothing is set
|
330
|
+
if (s.lines.show == null) {
|
331
|
+
var v, show = true;
|
332
|
+
for (v in s)
|
333
|
+
if (s[v].show) {
|
334
|
+
show = false;
|
335
|
+
break;
|
336
|
+
}
|
337
|
+
if (show)
|
338
|
+
s.lines.show = true;
|
339
|
+
}
|
340
|
+
|
341
|
+
// setup axes
|
342
|
+
s.xaxis = axisSpecToRealAxis(s, "xaxis");
|
343
|
+
s.yaxis = axisSpecToRealAxis(s, "yaxis");
|
344
|
+
}
|
345
|
+
}
|
346
|
+
|
347
|
+
function processData() {
|
348
|
+
var topSentry = Number.POSITIVE_INFINITY,
|
349
|
+
bottomSentry = Number.NEGATIVE_INFINITY,
|
350
|
+
i, j, k, m, length,
|
351
|
+
s, points, ps, x, y, axis, val, f, p;
|
352
|
+
|
353
|
+
for (axis in axes) {
|
354
|
+
axes[axis].datamin = topSentry;
|
355
|
+
axes[axis].datamax = bottomSentry;
|
356
|
+
axes[axis].used = false;
|
357
|
+
}
|
358
|
+
|
359
|
+
function updateAxis(axis, min, max) {
|
360
|
+
if (min < axis.datamin)
|
361
|
+
axis.datamin = min;
|
362
|
+
if (max > axis.datamax)
|
363
|
+
axis.datamax = max;
|
364
|
+
}
|
365
|
+
|
366
|
+
for (i = 0; i < series.length; ++i) {
|
367
|
+
s = series[i];
|
368
|
+
s.datapoints = { points: [] };
|
369
|
+
|
370
|
+
executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
|
371
|
+
}
|
372
|
+
|
373
|
+
// first pass: clean and copy data
|
374
|
+
for (i = 0; i < series.length; ++i) {
|
375
|
+
s = series[i];
|
376
|
+
|
377
|
+
var data = s.data, format = s.datapoints.format;
|
378
|
+
|
379
|
+
if (!format) {
|
380
|
+
format = [];
|
381
|
+
// find out how to copy
|
382
|
+
format.push({ x: true, number: true, required: true });
|
383
|
+
format.push({ y: true, number: true, required: true });
|
384
|
+
|
385
|
+
if (s.bars.show)
|
386
|
+
format.push({ y: true, number: true, required: false, defaultValue: 0 });
|
387
|
+
|
388
|
+
s.datapoints.format = format;
|
389
|
+
}
|
390
|
+
|
391
|
+
if (s.datapoints.pointsize != null)
|
392
|
+
continue; // already filled in
|
393
|
+
|
394
|
+
if (s.datapoints.pointsize == null)
|
395
|
+
s.datapoints.pointsize = format.length;
|
396
|
+
|
397
|
+
ps = s.datapoints.pointsize;
|
398
|
+
points = s.datapoints.points;
|
399
|
+
|
400
|
+
insertSteps = s.lines.show && s.lines.steps;
|
401
|
+
s.xaxis.used = s.yaxis.used = true;
|
402
|
+
|
403
|
+
for (j = k = 0; j < data.length; ++j, k += ps) {
|
404
|
+
p = data[j];
|
405
|
+
|
406
|
+
var nullify = p == null;
|
407
|
+
if (!nullify) {
|
408
|
+
for (m = 0; m < ps; ++m) {
|
409
|
+
val = p[m];
|
410
|
+
f = format[m];
|
411
|
+
|
412
|
+
if (f) {
|
413
|
+
if (f.number && val != null) {
|
414
|
+
val = +val; // convert to number
|
415
|
+
if (isNaN(val))
|
416
|
+
val = null;
|
417
|
+
}
|
418
|
+
|
419
|
+
if (val == null) {
|
420
|
+
if (f.required)
|
421
|
+
nullify = true;
|
422
|
+
|
423
|
+
if (f.defaultValue != null)
|
424
|
+
val = f.defaultValue;
|
425
|
+
}
|
426
|
+
}
|
427
|
+
|
428
|
+
points[k + m] = val;
|
429
|
+
}
|
430
|
+
}
|
431
|
+
|
432
|
+
if (nullify) {
|
433
|
+
for (m = 0; m < ps; ++m) {
|
434
|
+
val = points[k + m];
|
435
|
+
if (val != null) {
|
436
|
+
f = format[m];
|
437
|
+
// extract min/max info
|
438
|
+
if (f.x)
|
439
|
+
updateAxis(s.xaxis, val, val);
|
440
|
+
if (f.y)
|
441
|
+
updateAxis(s.yaxis, val, val);
|
442
|
+
}
|
443
|
+
points[k + m] = null;
|
444
|
+
}
|
445
|
+
}
|
446
|
+
else {
|
447
|
+
// a little bit of line specific stuff that
|
448
|
+
// perhaps shouldn't be here, but lacking
|
449
|
+
// better means...
|
450
|
+
if (insertSteps && k > 0
|
451
|
+
&& points[k - ps] != null
|
452
|
+
&& points[k - ps] != points[k]
|
453
|
+
&& points[k - ps + 1] != points[k + 1]) {
|
454
|
+
// copy the point to make room for a middle point
|
455
|
+
for (m = 0; m < ps; ++m)
|
456
|
+
points[k + ps + m] = points[k + m];
|
457
|
+
|
458
|
+
// middle point has same y
|
459
|
+
points[k + 1] = points[k - ps + 1];
|
460
|
+
|
461
|
+
// we've added a point, better reflect that
|
462
|
+
k += ps;
|
463
|
+
}
|
464
|
+
}
|
465
|
+
}
|
466
|
+
}
|
467
|
+
|
468
|
+
// give the hooks a chance to run
|
469
|
+
for (i = 0; i < series.length; ++i) {
|
470
|
+
s = series[i];
|
471
|
+
|
472
|
+
executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
|
473
|
+
}
|
474
|
+
|
475
|
+
// second pass: find datamax/datamin for auto-scaling
|
476
|
+
for (i = 0; i < series.length; ++i) {
|
477
|
+
s = series[i];
|
478
|
+
points = s.datapoints.points,
|
479
|
+
ps = s.datapoints.pointsize;
|
480
|
+
|
481
|
+
var xmin = topSentry, ymin = topSentry,
|
482
|
+
xmax = bottomSentry, ymax = bottomSentry;
|
483
|
+
|
484
|
+
for (j = 0; j < points.length; j += ps) {
|
485
|
+
if (points[j] == null)
|
486
|
+
continue;
|
487
|
+
|
488
|
+
for (m = 0; m < ps; ++m) {
|
489
|
+
val = points[j + m];
|
490
|
+
f = format[m];
|
491
|
+
if (!f)
|
492
|
+
continue;
|
493
|
+
|
494
|
+
if (f.x) {
|
495
|
+
if (val < xmin)
|
496
|
+
xmin = val;
|
497
|
+
if (val > xmax)
|
498
|
+
xmax = val;
|
499
|
+
}
|
500
|
+
if (f.y) {
|
501
|
+
if (val < ymin)
|
502
|
+
ymin = val;
|
503
|
+
if (val > ymax)
|
504
|
+
ymax = val;
|
505
|
+
}
|
506
|
+
}
|
507
|
+
}
|
508
|
+
|
509
|
+
if (s.bars.show) {
|
510
|
+
// make sure we got room for the bar on the dancing floor
|
511
|
+
var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
|
512
|
+
if (s.bars.horizontal) {
|
513
|
+
ymin += delta;
|
514
|
+
ymax += delta + s.bars.barWidth;
|
515
|
+
}
|
516
|
+
else {
|
517
|
+
xmin += delta;
|
518
|
+
xmax += delta + s.bars.barWidth;
|
519
|
+
}
|
520
|
+
}
|
521
|
+
|
522
|
+
updateAxis(s.xaxis, xmin, xmax);
|
523
|
+
updateAxis(s.yaxis, ymin, ymax);
|
524
|
+
}
|
525
|
+
|
526
|
+
for (axis in axes) {
|
527
|
+
if (axes[axis].datamin == topSentry)
|
528
|
+
axes[axis].datamin = null;
|
529
|
+
if (axes[axis].datamax == bottomSentry)
|
530
|
+
axes[axis].datamax = null;
|
531
|
+
}
|
532
|
+
}
|
533
|
+
|
534
|
+
function constructCanvas() {
|
535
|
+
function makeCanvas(width, height) {
|
536
|
+
var c = document.createElement('canvas');
|
537
|
+
c.width = width;
|
538
|
+
c.height = height;
|
539
|
+
if ($.browser.msie) // excanvas hack
|
540
|
+
c = window.G_vmlCanvasManager.initElement(c);
|
541
|
+
return c;
|
542
|
+
}
|
543
|
+
|
544
|
+
canvasWidth = placeholder.width();
|
545
|
+
canvasHeight = placeholder.height();
|
546
|
+
placeholder.html(""); // clear placeholder
|
547
|
+
if (placeholder.css("position") == 'static')
|
548
|
+
placeholder.css("position", "relative"); // for positioning labels and overlay
|
549
|
+
|
550
|
+
if (canvasWidth <= 0 || canvasHeight <= 0)
|
551
|
+
throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
|
552
|
+
|
553
|
+
if ($.browser.msie) // excanvas hack
|
554
|
+
window.G_vmlCanvasManager.init_(document); // make sure everything is setup
|
555
|
+
|
556
|
+
// the canvas
|
557
|
+
canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(placeholder).get(0);
|
558
|
+
ctx = canvas.getContext("2d");
|
559
|
+
|
560
|
+
// overlay canvas for interactive features
|
561
|
+
overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(placeholder).get(0);
|
562
|
+
octx = overlay.getContext("2d");
|
563
|
+
octx.stroke();
|
564
|
+
}
|
565
|
+
|
566
|
+
function bindEvents() {
|
567
|
+
// we include the canvas in the event holder too, because IE 7
|
568
|
+
// sometimes has trouble with the stacking order
|
569
|
+
eventHolder = $([overlay, canvas]);
|
570
|
+
|
571
|
+
// bind events
|
572
|
+
if (options.grid.hoverable)
|
573
|
+
eventHolder.mousemove(onMouseMove);
|
574
|
+
|
575
|
+
if (options.grid.clickable)
|
576
|
+
eventHolder.click(onClick);
|
577
|
+
|
578
|
+
executeHooks(hooks.bindEvents, [eventHolder]);
|
579
|
+
}
|
580
|
+
|
581
|
+
function setupGrid() {
|
582
|
+
function setTransformationHelpers(axis, o) {
|
583
|
+
function identity(x) { return x; }
|
584
|
+
|
585
|
+
var s, m, t = o.transform || identity,
|
586
|
+
it = o.inverseTransform;
|
587
|
+
|
588
|
+
// add transformation helpers
|
589
|
+
if (axis == axes.xaxis || axis == axes.x2axis) {
|
590
|
+
// precompute how much the axis is scaling a point
|
591
|
+
// in canvas space
|
592
|
+
s = axis.scale = plotWidth / (t(axis.max) - t(axis.min));
|
593
|
+
m = t(axis.min);
|
594
|
+
|
595
|
+
// data point to canvas coordinate
|
596
|
+
if (t == identity) // slight optimization
|
597
|
+
axis.p2c = function (p) { return (p - m) * s; };
|
598
|
+
else
|
599
|
+
axis.p2c = function (p) { return (t(p) - m) * s; };
|
600
|
+
// canvas coordinate to data point
|
601
|
+
if (!it)
|
602
|
+
axis.c2p = function (c) { return m + c / s; };
|
603
|
+
else
|
604
|
+
axis.c2p = function (c) { return it(m + c / s); };
|
605
|
+
}
|
606
|
+
else {
|
607
|
+
s = axis.scale = plotHeight / (t(axis.max) - t(axis.min));
|
608
|
+
m = t(axis.max);
|
609
|
+
|
610
|
+
if (t == identity)
|
611
|
+
axis.p2c = function (p) { return (m - p) * s; };
|
612
|
+
else
|
613
|
+
axis.p2c = function (p) { return (m - t(p)) * s; };
|
614
|
+
if (!it)
|
615
|
+
axis.c2p = function (c) { return m - c / s; };
|
616
|
+
else
|
617
|
+
axis.c2p = function (c) { return it(m - c / s); };
|
618
|
+
}
|
619
|
+
}
|
620
|
+
|
621
|
+
function measureLabels(axis, axisOptions) {
|
622
|
+
var i, labels = [], l;
|
623
|
+
|
624
|
+
axis.labelWidth = axisOptions.labelWidth;
|
625
|
+
axis.labelHeight = axisOptions.labelHeight;
|
626
|
+
|
627
|
+
if (axis == axes.xaxis || axis == axes.x2axis) {
|
628
|
+
// to avoid measuring the widths of the labels, we
|
629
|
+
// construct fixed-size boxes and put the labels inside
|
630
|
+
// them, we don't need the exact figures and the
|
631
|
+
// fixed-size box content is easy to center
|
632
|
+
if (axis.labelWidth == null)
|
633
|
+
axis.labelWidth = canvasWidth / (axis.ticks.length > 0 ? axis.ticks.length : 1);
|
634
|
+
|
635
|
+
// measure x label heights
|
636
|
+
if (axis.labelHeight == null) {
|
637
|
+
labels = [];
|
638
|
+
for (i = 0; i < axis.ticks.length; ++i) {
|
639
|
+
l = axis.ticks[i].label;
|
640
|
+
if (l)
|
641
|
+
labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>');
|
642
|
+
}
|
643
|
+
|
644
|
+
if (labels.length > 0) {
|
645
|
+
var dummyDiv = $('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'
|
646
|
+
+ labels.join("") + '<div style="clear:left"></div></div>').appendTo(placeholder);
|
647
|
+
axis.labelHeight = dummyDiv.height();
|
648
|
+
dummyDiv.remove();
|
649
|
+
}
|
650
|
+
}
|
651
|
+
}
|
652
|
+
else if (axis.labelWidth == null || axis.labelHeight == null) {
|
653
|
+
// calculate y label dimensions
|
654
|
+
for (i = 0; i < axis.ticks.length; ++i) {
|
655
|
+
l = axis.ticks[i].label;
|
656
|
+
if (l)
|
657
|
+
labels.push('<div class="tickLabel">' + l + '</div>');
|
658
|
+
}
|
659
|
+
|
660
|
+
if (labels.length > 0) {
|
661
|
+
var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">'
|
662
|
+
+ labels.join("") + '</div>').appendTo(placeholder);
|
663
|
+
if (axis.labelWidth == null)
|
664
|
+
axis.labelWidth = dummyDiv.width();
|
665
|
+
if (axis.labelHeight == null)
|
666
|
+
axis.labelHeight = dummyDiv.find("div").height();
|
667
|
+
dummyDiv.remove();
|
668
|
+
}
|
669
|
+
|
670
|
+
}
|
671
|
+
|
672
|
+
if (axis.labelWidth == null)
|
673
|
+
axis.labelWidth = 0;
|
674
|
+
if (axis.labelHeight == null)
|
675
|
+
axis.labelHeight = 0;
|
676
|
+
}
|
677
|
+
|
678
|
+
function setGridSpacing() {
|
679
|
+
// get the most space needed around the grid for things
|
680
|
+
// that may stick out
|
681
|
+
var maxOutset = options.grid.borderWidth;
|
682
|
+
for (i = 0; i < series.length; ++i)
|
683
|
+
maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
|
684
|
+
|
685
|
+
plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset;
|
686
|
+
|
687
|
+
var margin = options.grid.labelMargin + options.grid.borderWidth;
|
688
|
+
|
689
|
+
if (axes.xaxis.labelHeight > 0)
|
690
|
+
plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin);
|
691
|
+
if (axes.yaxis.labelWidth > 0)
|
692
|
+
plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin);
|
693
|
+
if (axes.x2axis.labelHeight > 0)
|
694
|
+
plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin);
|
695
|
+
if (axes.y2axis.labelWidth > 0)
|
696
|
+
plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin);
|
697
|
+
|
698
|
+
plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
|
699
|
+
plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
|
700
|
+
}
|
701
|
+
|
702
|
+
var axis;
|
703
|
+
for (axis in axes)
|
704
|
+
setRange(axes[axis], options[axis]);
|
705
|
+
|
706
|
+
if (options.grid.show) {
|
707
|
+
for (axis in axes) {
|
708
|
+
prepareTickGeneration(axes[axis], options[axis]);
|
709
|
+
setTicks(axes[axis], options[axis]);
|
710
|
+
measureLabels(axes[axis], options[axis]);
|
711
|
+
}
|
712
|
+
|
713
|
+
setGridSpacing();
|
714
|
+
}
|
715
|
+
else {
|
716
|
+
plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
|
717
|
+
plotWidth = canvasWidth;
|
718
|
+
plotHeight = canvasHeight;
|
719
|
+
}
|
720
|
+
|
721
|
+
for (axis in axes)
|
722
|
+
setTransformationHelpers(axes[axis], options[axis]);
|
723
|
+
|
724
|
+
if (options.grid.show)
|
725
|
+
insertLabels();
|
726
|
+
|
727
|
+
insertLegend();
|
728
|
+
}
|
729
|
+
|
730
|
+
function setRange(axis, axisOptions) {
|
731
|
+
var min = +(axisOptions.min != null ? axisOptions.min : axis.datamin),
|
732
|
+
max = +(axisOptions.max != null ? axisOptions.max : axis.datamax),
|
733
|
+
delta = max - min;
|
734
|
+
|
735
|
+
if (delta == 0.0) {
|
736
|
+
// degenerate case
|
737
|
+
var widen = max == 0 ? 1 : 0.01;
|
738
|
+
|
739
|
+
if (axisOptions.min == null)
|
740
|
+
min -= widen;
|
741
|
+
// alway widen max if we couldn't widen min to ensure we
|
742
|
+
// don't fall into min == max which doesn't work
|
743
|
+
if (axisOptions.max == null || axisOptions.min != null)
|
744
|
+
max += widen;
|
745
|
+
}
|
746
|
+
else {
|
747
|
+
// consider autoscaling
|
748
|
+
var margin = axisOptions.autoscaleMargin;
|
749
|
+
if (margin != null) {
|
750
|
+
if (axisOptions.min == null) {
|
751
|
+
min -= delta * margin;
|
752
|
+
// make sure we don't go below zero if all values
|
753
|
+
// are positive
|
754
|
+
if (min < 0 && axis.datamin != null && axis.datamin >= 0)
|
755
|
+
min = 0;
|
756
|
+
}
|
757
|
+
if (axisOptions.max == null) {
|
758
|
+
max += delta * margin;
|
759
|
+
if (max > 0 && axis.datamax != null && axis.datamax <= 0)
|
760
|
+
max = 0;
|
761
|
+
}
|
762
|
+
}
|
763
|
+
}
|
764
|
+
axis.min = min;
|
765
|
+
axis.max = max;
|
766
|
+
}
|
767
|
+
|
768
|
+
function prepareTickGeneration(axis, axisOptions) {
|
769
|
+
// estimate number of ticks
|
770
|
+
var noTicks;
|
771
|
+
if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0)
|
772
|
+
noTicks = axisOptions.ticks;
|
773
|
+
else if (axis == axes.xaxis || axis == axes.x2axis)
|
774
|
+
// heuristic based on the model a*sqrt(x) fitted to
|
775
|
+
// some reasonable data points
|
776
|
+
noTicks = 0.3 * Math.sqrt(canvasWidth);
|
777
|
+
else
|
778
|
+
noTicks = 0.3 * Math.sqrt(canvasHeight);
|
779
|
+
|
780
|
+
var delta = (axis.max - axis.min) / noTicks,
|
781
|
+
size, generator, unit, formatter, i, magn, norm;
|
782
|
+
|
783
|
+
if (axisOptions.mode == "time") {
|
784
|
+
// pretty handling of time
|
785
|
+
|
786
|
+
// map of app. size of time units in milliseconds
|
787
|
+
var timeUnitSize = {
|
788
|
+
"second": 1000,
|
789
|
+
"minute": 60 * 1000,
|
790
|
+
"hour": 60 * 60 * 1000,
|
791
|
+
"day": 24 * 60 * 60 * 1000,
|
792
|
+
"month": 30 * 24 * 60 * 60 * 1000,
|
793
|
+
"year": 365.2425 * 24 * 60 * 60 * 1000
|
794
|
+
};
|
795
|
+
|
796
|
+
|
797
|
+
// the allowed tick sizes, after 1 year we use
|
798
|
+
// an integer algorithm
|
799
|
+
var spec = [
|
800
|
+
[1, "second"], [2, "second"], [5, "second"], [10, "second"],
|
801
|
+
[30, "second"],
|
802
|
+
[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
|
803
|
+
[30, "minute"],
|
804
|
+
[1, "hour"], [2, "hour"], [4, "hour"],
|
805
|
+
[8, "hour"], [12, "hour"],
|
806
|
+
[1, "day"], [2, "day"], [3, "day"],
|
807
|
+
[0.25, "month"], [0.5, "month"], [1, "month"],
|
808
|
+
[2, "month"], [3, "month"], [6, "month"],
|
809
|
+
[1, "year"]
|
810
|
+
];
|
811
|
+
|
812
|
+
var minSize = 0;
|
813
|
+
if (axisOptions.minTickSize != null) {
|
814
|
+
if (typeof axisOptions.tickSize == "number")
|
815
|
+
minSize = axisOptions.tickSize;
|
816
|
+
else
|
817
|
+
minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]];
|
818
|
+
}
|
819
|
+
|
820
|
+
for (i = 0; i < spec.length - 1; ++i)
|
821
|
+
if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
|
822
|
+
+ spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
|
823
|
+
&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
|
824
|
+
break;
|
825
|
+
size = spec[i][0];
|
826
|
+
unit = spec[i][1];
|
827
|
+
|
828
|
+
// special-case the possibility of several years
|
829
|
+
if (unit == "year") {
|
830
|
+
magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
|
831
|
+
norm = (delta / timeUnitSize.year) / magn;
|
832
|
+
if (norm < 1.5)
|
833
|
+
size = 1;
|
834
|
+
else if (norm < 3)
|
835
|
+
size = 2;
|
836
|
+
else if (norm < 7.5)
|
837
|
+
size = 5;
|
838
|
+
else
|
839
|
+
size = 10;
|
840
|
+
|
841
|
+
size *= magn;
|
842
|
+
}
|
843
|
+
|
844
|
+
if (axisOptions.tickSize) {
|
845
|
+
size = axisOptions.tickSize[0];
|
846
|
+
unit = axisOptions.tickSize[1];
|
847
|
+
}
|
848
|
+
|
849
|
+
generator = function(axis) {
|
850
|
+
var ticks = [],
|
851
|
+
tickSize = axis.tickSize[0], unit = axis.tickSize[1],
|
852
|
+
d = new Date(axis.min);
|
853
|
+
|
854
|
+
var step = tickSize * timeUnitSize[unit];
|
855
|
+
|
856
|
+
if (unit == "second")
|
857
|
+
d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
|
858
|
+
if (unit == "minute")
|
859
|
+
d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
|
860
|
+
if (unit == "hour")
|
861
|
+
d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
|
862
|
+
if (unit == "month")
|
863
|
+
d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
|
864
|
+
if (unit == "year")
|
865
|
+
d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
|
866
|
+
|
867
|
+
// reset smaller components
|
868
|
+
d.setUTCMilliseconds(0);
|
869
|
+
if (step >= timeUnitSize.minute)
|
870
|
+
d.setUTCSeconds(0);
|
871
|
+
if (step >= timeUnitSize.hour)
|
872
|
+
d.setUTCMinutes(0);
|
873
|
+
if (step >= timeUnitSize.day)
|
874
|
+
d.setUTCHours(0);
|
875
|
+
if (step >= timeUnitSize.day * 4)
|
876
|
+
d.setUTCDate(1);
|
877
|
+
if (step >= timeUnitSize.year)
|
878
|
+
d.setUTCMonth(0);
|
879
|
+
|
880
|
+
|
881
|
+
var carry = 0, v = Number.NaN, prev;
|
882
|
+
do {
|
883
|
+
prev = v;
|
884
|
+
v = d.getTime();
|
885
|
+
ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
|
886
|
+
if (unit == "month") {
|
887
|
+
if (tickSize < 1) {
|
888
|
+
// a bit complicated - we'll divide the month
|
889
|
+
// up but we need to take care of fractions
|
890
|
+
// so we don't end up in the middle of a day
|
891
|
+
d.setUTCDate(1);
|
892
|
+
var start = d.getTime();
|
893
|
+
d.setUTCMonth(d.getUTCMonth() + 1);
|
894
|
+
var end = d.getTime();
|
895
|
+
d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
|
896
|
+
carry = d.getUTCHours();
|
897
|
+
d.setUTCHours(0);
|
898
|
+
}
|
899
|
+
else
|
900
|
+
d.setUTCMonth(d.getUTCMonth() + tickSize);
|
901
|
+
}
|
902
|
+
else if (unit == "year") {
|
903
|
+
d.setUTCFullYear(d.getUTCFullYear() + tickSize);
|
904
|
+
}
|
905
|
+
else
|
906
|
+
d.setTime(v + step);
|
907
|
+
} while (v < axis.max && v != prev);
|
908
|
+
|
909
|
+
return ticks;
|
910
|
+
};
|
911
|
+
|
912
|
+
formatter = function (v, axis) {
|
913
|
+
var d = new Date(v);
|
914
|
+
|
915
|
+
// first check global format
|
916
|
+
if (axisOptions.timeformat != null)
|
917
|
+
return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames);
|
918
|
+
|
919
|
+
var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
|
920
|
+
var span = axis.max - axis.min;
|
921
|
+
var suffix = (axisOptions.twelveHourClock) ? " %p" : "";
|
922
|
+
|
923
|
+
if (t < timeUnitSize.minute)
|
924
|
+
fmt = "%h:%M:%S" + suffix;
|
925
|
+
else if (t < timeUnitSize.day) {
|
926
|
+
if (span < 2 * timeUnitSize.day)
|
927
|
+
fmt = "%h:%M" + suffix;
|
928
|
+
else
|
929
|
+
fmt = "%b %d %h:%M" + suffix;
|
930
|
+
}
|
931
|
+
else if (t < timeUnitSize.month)
|
932
|
+
fmt = "%b %d";
|
933
|
+
else if (t < timeUnitSize.year) {
|
934
|
+
if (span < timeUnitSize.year)
|
935
|
+
fmt = "%b";
|
936
|
+
else
|
937
|
+
fmt = "%b %y";
|
938
|
+
}
|
939
|
+
else
|
940
|
+
fmt = "%y";
|
941
|
+
|
942
|
+
return $.plot.formatDate(d, fmt, axisOptions.monthNames);
|
943
|
+
};
|
944
|
+
}
|
945
|
+
else {
|
946
|
+
// pretty rounding of base-10 numbers
|
947
|
+
var maxDec = axisOptions.tickDecimals;
|
948
|
+
var dec = -Math.floor(Math.log(delta) / Math.LN10);
|
949
|
+
if (maxDec != null && dec > maxDec)
|
950
|
+
dec = maxDec;
|
951
|
+
|
952
|
+
magn = Math.pow(10, -dec);
|
953
|
+
norm = delta / magn; // norm is between 1.0 and 10.0
|
954
|
+
|
955
|
+
if (norm < 1.5)
|
956
|
+
size = 1;
|
957
|
+
else if (norm < 3) {
|
958
|
+
size = 2;
|
959
|
+
// special case for 2.5, requires an extra decimal
|
960
|
+
if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
|
961
|
+
size = 2.5;
|
962
|
+
++dec;
|
963
|
+
}
|
964
|
+
}
|
965
|
+
else if (norm < 7.5)
|
966
|
+
size = 5;
|
967
|
+
else
|
968
|
+
size = 10;
|
969
|
+
|
970
|
+
size *= magn;
|
971
|
+
|
972
|
+
if (axisOptions.minTickSize != null && size < axisOptions.minTickSize)
|
973
|
+
size = axisOptions.minTickSize;
|
974
|
+
|
975
|
+
if (axisOptions.tickSize != null)
|
976
|
+
size = axisOptions.tickSize;
|
977
|
+
|
978
|
+
axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec);
|
979
|
+
|
980
|
+
generator = function (axis) {
|
981
|
+
var ticks = [];
|
982
|
+
|
983
|
+
// spew out all possible ticks
|
984
|
+
var start = floorInBase(axis.min, axis.tickSize),
|
985
|
+
i = 0, v = Number.NaN, prev;
|
986
|
+
do {
|
987
|
+
prev = v;
|
988
|
+
v = start + i * axis.tickSize;
|
989
|
+
ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
|
990
|
+
++i;
|
991
|
+
} while (v < axis.max && v != prev);
|
992
|
+
return ticks;
|
993
|
+
};
|
994
|
+
|
995
|
+
formatter = function (v, axis) {
|
996
|
+
return v.toFixed(axis.tickDecimals);
|
997
|
+
};
|
998
|
+
}
|
999
|
+
|
1000
|
+
axis.tickSize = unit ? [size, unit] : size;
|
1001
|
+
axis.tickGenerator = generator;
|
1002
|
+
if ($.isFunction(axisOptions.tickFormatter))
|
1003
|
+
axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); };
|
1004
|
+
else
|
1005
|
+
axis.tickFormatter = formatter;
|
1006
|
+
}
|
1007
|
+
|
1008
|
+
function setTicks(axis, axisOptions) {
|
1009
|
+
axis.ticks = [];
|
1010
|
+
|
1011
|
+
if (!axis.used)
|
1012
|
+
return;
|
1013
|
+
|
1014
|
+
if (axisOptions.ticks == null)
|
1015
|
+
axis.ticks = axis.tickGenerator(axis);
|
1016
|
+
else if (typeof axisOptions.ticks == "number") {
|
1017
|
+
if (axisOptions.ticks > 0)
|
1018
|
+
axis.ticks = axis.tickGenerator(axis);
|
1019
|
+
}
|
1020
|
+
else if (axisOptions.ticks) {
|
1021
|
+
var ticks = axisOptions.ticks;
|
1022
|
+
|
1023
|
+
if ($.isFunction(ticks))
|
1024
|
+
// generate the ticks
|
1025
|
+
ticks = ticks({ min: axis.min, max: axis.max });
|
1026
|
+
|
1027
|
+
// clean up the user-supplied ticks, copy them over
|
1028
|
+
var i, v;
|
1029
|
+
for (i = 0; i < ticks.length; ++i) {
|
1030
|
+
var label = null;
|
1031
|
+
var t = ticks[i];
|
1032
|
+
if (typeof t == "object") {
|
1033
|
+
v = t[0];
|
1034
|
+
if (t.length > 1)
|
1035
|
+
label = t[1];
|
1036
|
+
}
|
1037
|
+
else
|
1038
|
+
v = t;
|
1039
|
+
if (label == null)
|
1040
|
+
label = axis.tickFormatter(v, axis);
|
1041
|
+
axis.ticks[i] = { v: v, label: label };
|
1042
|
+
}
|
1043
|
+
}
|
1044
|
+
|
1045
|
+
if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) {
|
1046
|
+
// snap to ticks
|
1047
|
+
if (axisOptions.min == null)
|
1048
|
+
axis.min = Math.min(axis.min, axis.ticks[0].v);
|
1049
|
+
if (axisOptions.max == null && axis.ticks.length > 1)
|
1050
|
+
axis.max = Math.max(axis.max, axis.ticks[axis.ticks.length - 1].v);
|
1051
|
+
}
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
function draw() {
|
1055
|
+
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
1056
|
+
|
1057
|
+
var grid = options.grid;
|
1058
|
+
|
1059
|
+
if (grid.show && !grid.aboveData)
|
1060
|
+
drawGrid();
|
1061
|
+
|
1062
|
+
for (var i = 0; i < series.length; ++i)
|
1063
|
+
drawSeries(series[i]);
|
1064
|
+
|
1065
|
+
executeHooks(hooks.draw, [ctx]);
|
1066
|
+
|
1067
|
+
if (grid.show && grid.aboveData)
|
1068
|
+
drawGrid();
|
1069
|
+
}
|
1070
|
+
|
1071
|
+
function extractRange(ranges, coord) {
|
1072
|
+
var firstAxis = coord + "axis",
|
1073
|
+
secondaryAxis = coord + "2axis",
|
1074
|
+
axis, from, to, reverse;
|
1075
|
+
|
1076
|
+
if (ranges[firstAxis]) {
|
1077
|
+
axis = axes[firstAxis];
|
1078
|
+
from = ranges[firstAxis].from;
|
1079
|
+
to = ranges[firstAxis].to;
|
1080
|
+
}
|
1081
|
+
else if (ranges[secondaryAxis]) {
|
1082
|
+
axis = axes[secondaryAxis];
|
1083
|
+
from = ranges[secondaryAxis].from;
|
1084
|
+
to = ranges[secondaryAxis].to;
|
1085
|
+
}
|
1086
|
+
else {
|
1087
|
+
// backwards-compat stuff - to be removed in future
|
1088
|
+
axis = axes[firstAxis];
|
1089
|
+
from = ranges[coord + "1"];
|
1090
|
+
to = ranges[coord + "2"];
|
1091
|
+
}
|
1092
|
+
|
1093
|
+
// auto-reverse as an added bonus
|
1094
|
+
if (from != null && to != null && from > to)
|
1095
|
+
return { from: to, to: from, axis: axis };
|
1096
|
+
|
1097
|
+
return { from: from, to: to, axis: axis };
|
1098
|
+
}
|
1099
|
+
|
1100
|
+
function drawGrid() {
|
1101
|
+
var i;
|
1102
|
+
|
1103
|
+
ctx.save();
|
1104
|
+
ctx.translate(plotOffset.left, plotOffset.top);
|
1105
|
+
|
1106
|
+
// draw background, if any
|
1107
|
+
if (options.grid.backgroundColor) {
|
1108
|
+
ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
|
1109
|
+
ctx.fillRect(0, 0, plotWidth, plotHeight);
|
1110
|
+
}
|
1111
|
+
|
1112
|
+
// draw markings
|
1113
|
+
var markings = options.grid.markings;
|
1114
|
+
if (markings) {
|
1115
|
+
if ($.isFunction(markings))
|
1116
|
+
// xmin etc. are backwards-compatible, to be removed in future
|
1117
|
+
markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis });
|
1118
|
+
|
1119
|
+
for (i = 0; i < markings.length; ++i) {
|
1120
|
+
var m = markings[i],
|
1121
|
+
xrange = extractRange(m, "x"),
|
1122
|
+
yrange = extractRange(m, "y");
|
1123
|
+
|
1124
|
+
// fill in missing
|
1125
|
+
if (xrange.from == null)
|
1126
|
+
xrange.from = xrange.axis.min;
|
1127
|
+
if (xrange.to == null)
|
1128
|
+
xrange.to = xrange.axis.max;
|
1129
|
+
if (yrange.from == null)
|
1130
|
+
yrange.from = yrange.axis.min;
|
1131
|
+
if (yrange.to == null)
|
1132
|
+
yrange.to = yrange.axis.max;
|
1133
|
+
|
1134
|
+
// clip
|
1135
|
+
if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
|
1136
|
+
yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
|
1137
|
+
continue;
|
1138
|
+
|
1139
|
+
xrange.from = Math.max(xrange.from, xrange.axis.min);
|
1140
|
+
xrange.to = Math.min(xrange.to, xrange.axis.max);
|
1141
|
+
yrange.from = Math.max(yrange.from, yrange.axis.min);
|
1142
|
+
yrange.to = Math.min(yrange.to, yrange.axis.max);
|
1143
|
+
|
1144
|
+
if (xrange.from == xrange.to && yrange.from == yrange.to)
|
1145
|
+
continue;
|
1146
|
+
|
1147
|
+
// then draw
|
1148
|
+
xrange.from = xrange.axis.p2c(xrange.from);
|
1149
|
+
xrange.to = xrange.axis.p2c(xrange.to);
|
1150
|
+
yrange.from = yrange.axis.p2c(yrange.from);
|
1151
|
+
yrange.to = yrange.axis.p2c(yrange.to);
|
1152
|
+
|
1153
|
+
if (xrange.from == xrange.to || yrange.from == yrange.to) {
|
1154
|
+
// draw line
|
1155
|
+
ctx.beginPath();
|
1156
|
+
ctx.strokeStyle = m.color || options.grid.markingsColor;
|
1157
|
+
ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
|
1158
|
+
//ctx.moveTo(Math.floor(xrange.from), yrange.from);
|
1159
|
+
//ctx.lineTo(Math.floor(xrange.to), yrange.to);
|
1160
|
+
ctx.moveTo(xrange.from, yrange.from);
|
1161
|
+
ctx.lineTo(xrange.to, yrange.to);
|
1162
|
+
ctx.stroke();
|
1163
|
+
}
|
1164
|
+
else {
|
1165
|
+
// fill area
|
1166
|
+
ctx.fillStyle = m.color || options.grid.markingsColor;
|
1167
|
+
ctx.fillRect(xrange.from, yrange.to,
|
1168
|
+
xrange.to - xrange.from,
|
1169
|
+
yrange.from - yrange.to);
|
1170
|
+
}
|
1171
|
+
}
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
// draw the inner grid
|
1175
|
+
ctx.lineWidth = 1;
|
1176
|
+
ctx.strokeStyle = options.grid.tickColor;
|
1177
|
+
ctx.beginPath();
|
1178
|
+
var v, axis = axes.xaxis;
|
1179
|
+
for (i = 0; i < axis.ticks.length; ++i) {
|
1180
|
+
v = axis.ticks[i].v;
|
1181
|
+
if (v <= axis.min || v >= axes.xaxis.max)
|
1182
|
+
continue; // skip those lying on the axes
|
1183
|
+
|
1184
|
+
ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0);
|
1185
|
+
ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight);
|
1186
|
+
}
|
1187
|
+
|
1188
|
+
axis = axes.yaxis;
|
1189
|
+
for (i = 0; i < axis.ticks.length; ++i) {
|
1190
|
+
v = axis.ticks[i].v;
|
1191
|
+
if (v <= axis.min || v >= axis.max)
|
1192
|
+
continue;
|
1193
|
+
|
1194
|
+
ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
|
1195
|
+
ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
|
1196
|
+
}
|
1197
|
+
|
1198
|
+
axis = axes.x2axis;
|
1199
|
+
for (i = 0; i < axis.ticks.length; ++i) {
|
1200
|
+
v = axis.ticks[i].v;
|
1201
|
+
if (v <= axis.min || v >= axis.max)
|
1202
|
+
continue;
|
1203
|
+
|
1204
|
+
ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5);
|
1205
|
+
ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5);
|
1206
|
+
}
|
1207
|
+
|
1208
|
+
axis = axes.y2axis;
|
1209
|
+
for (i = 0; i < axis.ticks.length; ++i) {
|
1210
|
+
v = axis.ticks[i].v;
|
1211
|
+
if (v <= axis.min || v >= axis.max)
|
1212
|
+
continue;
|
1213
|
+
|
1214
|
+
ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
|
1215
|
+
ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
|
1216
|
+
}
|
1217
|
+
|
1218
|
+
ctx.stroke();
|
1219
|
+
|
1220
|
+
if (options.grid.borderWidth) {
|
1221
|
+
// draw border
|
1222
|
+
var bw = options.grid.borderWidth;
|
1223
|
+
ctx.lineWidth = bw;
|
1224
|
+
ctx.strokeStyle = options.grid.borderColor;
|
1225
|
+
ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
|
1226
|
+
}
|
1227
|
+
|
1228
|
+
ctx.restore();
|
1229
|
+
}
|
1230
|
+
|
1231
|
+
function insertLabels() {
|
1232
|
+
placeholder.find(".tickLabels").remove();
|
1233
|
+
|
1234
|
+
var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'];
|
1235
|
+
|
1236
|
+
function addLabels(axis, labelGenerator) {
|
1237
|
+
for (var i = 0; i < axis.ticks.length; ++i) {
|
1238
|
+
var tick = axis.ticks[i];
|
1239
|
+
if (!tick.label || tick.v < axis.min || tick.v > axis.max)
|
1240
|
+
continue;
|
1241
|
+
html.push(labelGenerator(tick, axis));
|
1242
|
+
}
|
1243
|
+
}
|
1244
|
+
|
1245
|
+
var margin = options.grid.labelMargin + options.grid.borderWidth;
|
1246
|
+
|
1247
|
+
addLabels(axes.xaxis, function (tick, axis) {
|
1248
|
+
return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
|
1249
|
+
});
|
1250
|
+
|
1251
|
+
|
1252
|
+
addLabels(axes.yaxis, function (tick, axis) {
|
1253
|
+
return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + margin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>";
|
1254
|
+
});
|
1255
|
+
|
1256
|
+
addLabels(axes.x2axis, function (tick, axis) {
|
1257
|
+
return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
|
1258
|
+
});
|
1259
|
+
|
1260
|
+
addLabels(axes.y2axis, function (tick, axis) {
|
1261
|
+
return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>";
|
1262
|
+
});
|
1263
|
+
|
1264
|
+
html.push('</div>');
|
1265
|
+
|
1266
|
+
placeholder.append(html.join(""));
|
1267
|
+
}
|
1268
|
+
|
1269
|
+
function drawSeries(series) {
|
1270
|
+
if (series.lines.show)
|
1271
|
+
drawSeriesLines(series);
|
1272
|
+
if (series.bars.show)
|
1273
|
+
drawSeriesBars(series);
|
1274
|
+
if (series.points.show)
|
1275
|
+
drawSeriesPoints(series);
|
1276
|
+
}
|
1277
|
+
|
1278
|
+
function drawSeriesLines(series) {
|
1279
|
+
function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
|
1280
|
+
var points = datapoints.points,
|
1281
|
+
ps = datapoints.pointsize,
|
1282
|
+
prevx = null, prevy = null;
|
1283
|
+
|
1284
|
+
ctx.beginPath();
|
1285
|
+
for (var i = ps; i < points.length; i += ps) {
|
1286
|
+
var x1 = points[i - ps], y1 = points[i - ps + 1],
|
1287
|
+
x2 = points[i], y2 = points[i + 1];
|
1288
|
+
|
1289
|
+
if (x1 == null || x2 == null)
|
1290
|
+
continue;
|
1291
|
+
|
1292
|
+
// clip with ymin
|
1293
|
+
if (y1 <= y2 && y1 < axisy.min) {
|
1294
|
+
if (y2 < axisy.min)
|
1295
|
+
continue; // line segment is outside
|
1296
|
+
// compute new intersection point
|
1297
|
+
x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
|
1298
|
+
y1 = axisy.min;
|
1299
|
+
}
|
1300
|
+
else if (y2 <= y1 && y2 < axisy.min) {
|
1301
|
+
if (y1 < axisy.min)
|
1302
|
+
continue;
|
1303
|
+
x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
|
1304
|
+
y2 = axisy.min;
|
1305
|
+
}
|
1306
|
+
|
1307
|
+
// clip with ymax
|
1308
|
+
if (y1 >= y2 && y1 > axisy.max) {
|
1309
|
+
if (y2 > axisy.max)
|
1310
|
+
continue;
|
1311
|
+
x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
|
1312
|
+
y1 = axisy.max;
|
1313
|
+
}
|
1314
|
+
else if (y2 >= y1 && y2 > axisy.max) {
|
1315
|
+
if (y1 > axisy.max)
|
1316
|
+
continue;
|
1317
|
+
x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
|
1318
|
+
y2 = axisy.max;
|
1319
|
+
}
|
1320
|
+
|
1321
|
+
// clip with xmin
|
1322
|
+
if (x1 <= x2 && x1 < axisx.min) {
|
1323
|
+
if (x2 < axisx.min)
|
1324
|
+
continue;
|
1325
|
+
y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
|
1326
|
+
x1 = axisx.min;
|
1327
|
+
}
|
1328
|
+
else if (x2 <= x1 && x2 < axisx.min) {
|
1329
|
+
if (x1 < axisx.min)
|
1330
|
+
continue;
|
1331
|
+
y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
|
1332
|
+
x2 = axisx.min;
|
1333
|
+
}
|
1334
|
+
|
1335
|
+
// clip with xmax
|
1336
|
+
if (x1 >= x2 && x1 > axisx.max) {
|
1337
|
+
if (x2 > axisx.max)
|
1338
|
+
continue;
|
1339
|
+
y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
|
1340
|
+
x1 = axisx.max;
|
1341
|
+
}
|
1342
|
+
else if (x2 >= x1 && x2 > axisx.max) {
|
1343
|
+
if (x1 > axisx.max)
|
1344
|
+
continue;
|
1345
|
+
y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
|
1346
|
+
x2 = axisx.max;
|
1347
|
+
}
|
1348
|
+
|
1349
|
+
if (x1 != prevx || y1 != prevy)
|
1350
|
+
ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
|
1351
|
+
|
1352
|
+
prevx = x2;
|
1353
|
+
prevy = y2;
|
1354
|
+
ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
|
1355
|
+
}
|
1356
|
+
ctx.stroke();
|
1357
|
+
}
|
1358
|
+
|
1359
|
+
function plotLineArea(datapoints, axisx, axisy) {
|
1360
|
+
var points = datapoints.points,
|
1361
|
+
ps = datapoints.pointsize,
|
1362
|
+
bottom = Math.min(Math.max(0, axisy.min), axisy.max),
|
1363
|
+
top, lastX = 0, areaOpen = false;
|
1364
|
+
|
1365
|
+
for (var i = ps; i < points.length; i += ps) {
|
1366
|
+
var x1 = points[i - ps], y1 = points[i - ps + 1],
|
1367
|
+
x2 = points[i], y2 = points[i + 1];
|
1368
|
+
|
1369
|
+
if (areaOpen && x1 != null && x2 == null) {
|
1370
|
+
// close area
|
1371
|
+
ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
|
1372
|
+
ctx.fill();
|
1373
|
+
areaOpen = false;
|
1374
|
+
continue;
|
1375
|
+
}
|
1376
|
+
|
1377
|
+
if (x1 == null || x2 == null)
|
1378
|
+
continue;
|
1379
|
+
|
1380
|
+
// clip x values
|
1381
|
+
|
1382
|
+
// clip with xmin
|
1383
|
+
if (x1 <= x2 && x1 < axisx.min) {
|
1384
|
+
if (x2 < axisx.min)
|
1385
|
+
continue;
|
1386
|
+
y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
|
1387
|
+
x1 = axisx.min;
|
1388
|
+
}
|
1389
|
+
else if (x2 <= x1 && x2 < axisx.min) {
|
1390
|
+
if (x1 < axisx.min)
|
1391
|
+
continue;
|
1392
|
+
y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
|
1393
|
+
x2 = axisx.min;
|
1394
|
+
}
|
1395
|
+
|
1396
|
+
// clip with xmax
|
1397
|
+
if (x1 >= x2 && x1 > axisx.max) {
|
1398
|
+
if (x2 > axisx.max)
|
1399
|
+
continue;
|
1400
|
+
y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
|
1401
|
+
x1 = axisx.max;
|
1402
|
+
}
|
1403
|
+
else if (x2 >= x1 && x2 > axisx.max) {
|
1404
|
+
if (x1 > axisx.max)
|
1405
|
+
continue;
|
1406
|
+
y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
|
1407
|
+
x2 = axisx.max;
|
1408
|
+
}
|
1409
|
+
|
1410
|
+
if (!areaOpen) {
|
1411
|
+
// open area
|
1412
|
+
ctx.beginPath();
|
1413
|
+
ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
|
1414
|
+
areaOpen = true;
|
1415
|
+
}
|
1416
|
+
|
1417
|
+
// now first check the case where both is outside
|
1418
|
+
if (y1 >= axisy.max && y2 >= axisy.max) {
|
1419
|
+
ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
|
1420
|
+
ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
|
1421
|
+
lastX = x2;
|
1422
|
+
continue;
|
1423
|
+
}
|
1424
|
+
else if (y1 <= axisy.min && y2 <= axisy.min) {
|
1425
|
+
ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
|
1426
|
+
ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
|
1427
|
+
lastX = x2;
|
1428
|
+
continue;
|
1429
|
+
}
|
1430
|
+
|
1431
|
+
// else it's a bit more complicated, there might
|
1432
|
+
// be two rectangles and two triangles we need to fill
|
1433
|
+
// in; to find these keep track of the current x values
|
1434
|
+
var x1old = x1, x2old = x2;
|
1435
|
+
|
1436
|
+
// and clip the y values, without shortcutting
|
1437
|
+
|
1438
|
+
// clip with ymin
|
1439
|
+
if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
|
1440
|
+
x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
|
1441
|
+
y1 = axisy.min;
|
1442
|
+
}
|
1443
|
+
else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
|
1444
|
+
x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
|
1445
|
+
y2 = axisy.min;
|
1446
|
+
}
|
1447
|
+
|
1448
|
+
// clip with ymax
|
1449
|
+
if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
|
1450
|
+
x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
|
1451
|
+
y1 = axisy.max;
|
1452
|
+
}
|
1453
|
+
else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
|
1454
|
+
x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
|
1455
|
+
y2 = axisy.max;
|
1456
|
+
}
|
1457
|
+
|
1458
|
+
|
1459
|
+
// if the x value was changed we got a rectangle
|
1460
|
+
// to fill
|
1461
|
+
if (x1 != x1old) {
|
1462
|
+
if (y1 <= axisy.min)
|
1463
|
+
top = axisy.min;
|
1464
|
+
else
|
1465
|
+
top = axisy.max;
|
1466
|
+
|
1467
|
+
ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top));
|
1468
|
+
ctx.lineTo(axisx.p2c(x1), axisy.p2c(top));
|
1469
|
+
}
|
1470
|
+
|
1471
|
+
// fill the triangles
|
1472
|
+
ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
|
1473
|
+
ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
|
1474
|
+
|
1475
|
+
// fill the other rectangle if it's there
|
1476
|
+
if (x2 != x2old) {
|
1477
|
+
if (y2 <= axisy.min)
|
1478
|
+
top = axisy.min;
|
1479
|
+
else
|
1480
|
+
top = axisy.max;
|
1481
|
+
|
1482
|
+
ctx.lineTo(axisx.p2c(x2), axisy.p2c(top));
|
1483
|
+
ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top));
|
1484
|
+
}
|
1485
|
+
|
1486
|
+
lastX = Math.max(x2, x2old);
|
1487
|
+
}
|
1488
|
+
|
1489
|
+
if (areaOpen) {
|
1490
|
+
ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
|
1491
|
+
ctx.fill();
|
1492
|
+
}
|
1493
|
+
}
|
1494
|
+
|
1495
|
+
ctx.save();
|
1496
|
+
ctx.translate(plotOffset.left, plotOffset.top);
|
1497
|
+
ctx.lineJoin = "round";
|
1498
|
+
|
1499
|
+
var lw = series.lines.lineWidth,
|
1500
|
+
sw = series.shadowSize;
|
1501
|
+
// FIXME: consider another form of shadow when filling is turned on
|
1502
|
+
if (lw > 0 && sw > 0) {
|
1503
|
+
// draw shadow as a thick and thin line with transparency
|
1504
|
+
ctx.lineWidth = sw;
|
1505
|
+
ctx.strokeStyle = "rgba(0,0,0,0.1)";
|
1506
|
+
// position shadow at angle from the mid of line
|
1507
|
+
var angle = Math.PI/18;
|
1508
|
+
plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
|
1509
|
+
ctx.lineWidth = sw/2;
|
1510
|
+
plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
|
1511
|
+
}
|
1512
|
+
|
1513
|
+
ctx.lineWidth = lw;
|
1514
|
+
ctx.strokeStyle = series.color;
|
1515
|
+
var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
|
1516
|
+
if (fillStyle) {
|
1517
|
+
ctx.fillStyle = fillStyle;
|
1518
|
+
plotLineArea(series.datapoints, series.xaxis, series.yaxis);
|
1519
|
+
}
|
1520
|
+
|
1521
|
+
if (lw > 0)
|
1522
|
+
plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
|
1523
|
+
ctx.restore();
|
1524
|
+
}
|
1525
|
+
|
1526
|
+
function drawSeriesPoints(series) {
|
1527
|
+
function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
|
1528
|
+
var points = datapoints.points, ps = datapoints.pointsize;
|
1529
|
+
|
1530
|
+
for (var i = 0; i < points.length; i += ps) {
|
1531
|
+
var x = points[i], y = points[i + 1];
|
1532
|
+
if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
|
1533
|
+
continue;
|
1534
|
+
|
1535
|
+
ctx.beginPath();
|
1536
|
+
ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, false);
|
1537
|
+
if (fillStyle) {
|
1538
|
+
ctx.fillStyle = fillStyle;
|
1539
|
+
ctx.fill();
|
1540
|
+
}
|
1541
|
+
ctx.stroke();
|
1542
|
+
}
|
1543
|
+
}
|
1544
|
+
|
1545
|
+
ctx.save();
|
1546
|
+
ctx.translate(plotOffset.left, plotOffset.top);
|
1547
|
+
|
1548
|
+
var lw = series.lines.lineWidth,
|
1549
|
+
sw = series.shadowSize,
|
1550
|
+
radius = series.points.radius;
|
1551
|
+
if (lw > 0 && sw > 0) {
|
1552
|
+
// draw shadow in two steps
|
1553
|
+
var w = sw / 2;
|
1554
|
+
ctx.lineWidth = w;
|
1555
|
+
ctx.strokeStyle = "rgba(0,0,0,0.1)";
|
1556
|
+
plotPoints(series.datapoints, radius, null, w + w/2, Math.PI,
|
1557
|
+
series.xaxis, series.yaxis);
|
1558
|
+
|
1559
|
+
ctx.strokeStyle = "rgba(0,0,0,0.2)";
|
1560
|
+
plotPoints(series.datapoints, radius, null, w/2, Math.PI,
|
1561
|
+
series.xaxis, series.yaxis);
|
1562
|
+
}
|
1563
|
+
|
1564
|
+
ctx.lineWidth = lw;
|
1565
|
+
ctx.strokeStyle = series.color;
|
1566
|
+
plotPoints(series.datapoints, radius,
|
1567
|
+
getFillStyle(series.points, series.color), 0, 2 * Math.PI,
|
1568
|
+
series.xaxis, series.yaxis);
|
1569
|
+
ctx.restore();
|
1570
|
+
}
|
1571
|
+
|
1572
|
+
function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) {
|
1573
|
+
var left, right, bottom, top,
|
1574
|
+
drawLeft, drawRight, drawTop, drawBottom,
|
1575
|
+
tmp;
|
1576
|
+
|
1577
|
+
if (horizontal) {
|
1578
|
+
drawBottom = drawRight = drawTop = true;
|
1579
|
+
drawLeft = false;
|
1580
|
+
left = b;
|
1581
|
+
right = x;
|
1582
|
+
top = y + barLeft;
|
1583
|
+
bottom = y + barRight;
|
1584
|
+
|
1585
|
+
// account for negative bars
|
1586
|
+
if (right < left) {
|
1587
|
+
tmp = right;
|
1588
|
+
right = left;
|
1589
|
+
left = tmp;
|
1590
|
+
drawLeft = true;
|
1591
|
+
drawRight = false;
|
1592
|
+
}
|
1593
|
+
}
|
1594
|
+
else {
|
1595
|
+
drawLeft = drawRight = drawTop = true;
|
1596
|
+
drawBottom = false;
|
1597
|
+
left = x + barLeft;
|
1598
|
+
right = x + barRight;
|
1599
|
+
bottom = b;
|
1600
|
+
top = y;
|
1601
|
+
|
1602
|
+
// account for negative bars
|
1603
|
+
if (top < bottom) {
|
1604
|
+
tmp = top;
|
1605
|
+
top = bottom;
|
1606
|
+
bottom = tmp;
|
1607
|
+
drawBottom = true;
|
1608
|
+
drawTop = false;
|
1609
|
+
}
|
1610
|
+
}
|
1611
|
+
|
1612
|
+
// clip
|
1613
|
+
if (right < axisx.min || left > axisx.max ||
|
1614
|
+
top < axisy.min || bottom > axisy.max)
|
1615
|
+
return;
|
1616
|
+
|
1617
|
+
if (left < axisx.min) {
|
1618
|
+
left = axisx.min;
|
1619
|
+
drawLeft = false;
|
1620
|
+
}
|
1621
|
+
|
1622
|
+
if (right > axisx.max) {
|
1623
|
+
right = axisx.max;
|
1624
|
+
drawRight = false;
|
1625
|
+
}
|
1626
|
+
|
1627
|
+
if (bottom < axisy.min) {
|
1628
|
+
bottom = axisy.min;
|
1629
|
+
drawBottom = false;
|
1630
|
+
}
|
1631
|
+
|
1632
|
+
if (top > axisy.max) {
|
1633
|
+
top = axisy.max;
|
1634
|
+
drawTop = false;
|
1635
|
+
}
|
1636
|
+
|
1637
|
+
left = axisx.p2c(left);
|
1638
|
+
bottom = axisy.p2c(bottom);
|
1639
|
+
right = axisx.p2c(right);
|
1640
|
+
top = axisy.p2c(top);
|
1641
|
+
|
1642
|
+
// fill the bar
|
1643
|
+
if (fillStyleCallback) {
|
1644
|
+
c.beginPath();
|
1645
|
+
c.moveTo(left, bottom);
|
1646
|
+
c.lineTo(left, top);
|
1647
|
+
c.lineTo(right, top);
|
1648
|
+
c.lineTo(right, bottom);
|
1649
|
+
c.fillStyle = fillStyleCallback(bottom, top);
|
1650
|
+
c.fill();
|
1651
|
+
}
|
1652
|
+
|
1653
|
+
// draw outline
|
1654
|
+
if (drawLeft || drawRight || drawTop || drawBottom) {
|
1655
|
+
c.beginPath();
|
1656
|
+
|
1657
|
+
// FIXME: inline moveTo is buggy with excanvas
|
1658
|
+
c.moveTo(left, bottom + offset);
|
1659
|
+
if (drawLeft)
|
1660
|
+
c.lineTo(left, top + offset);
|
1661
|
+
else
|
1662
|
+
c.moveTo(left, top + offset);
|
1663
|
+
if (drawTop)
|
1664
|
+
c.lineTo(right, top + offset);
|
1665
|
+
else
|
1666
|
+
c.moveTo(right, top + offset);
|
1667
|
+
if (drawRight)
|
1668
|
+
c.lineTo(right, bottom + offset);
|
1669
|
+
else
|
1670
|
+
c.moveTo(right, bottom + offset);
|
1671
|
+
if (drawBottom)
|
1672
|
+
c.lineTo(left, bottom + offset);
|
1673
|
+
else
|
1674
|
+
c.moveTo(left, bottom + offset);
|
1675
|
+
c.stroke();
|
1676
|
+
}
|
1677
|
+
}
|
1678
|
+
|
1679
|
+
function drawSeriesBars(series) {
|
1680
|
+
function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
|
1681
|
+
var points = datapoints.points, ps = datapoints.pointsize;
|
1682
|
+
|
1683
|
+
for (var i = 0; i < points.length; i += ps) {
|
1684
|
+
if (points[i] == null)
|
1685
|
+
continue;
|
1686
|
+
drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal);
|
1687
|
+
}
|
1688
|
+
}
|
1689
|
+
|
1690
|
+
ctx.save();
|
1691
|
+
ctx.translate(plotOffset.left, plotOffset.top);
|
1692
|
+
|
1693
|
+
// FIXME: figure out a way to add shadows (for instance along the right edge)
|
1694
|
+
ctx.lineWidth = series.bars.lineWidth;
|
1695
|
+
ctx.strokeStyle = series.color;
|
1696
|
+
var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
|
1697
|
+
var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
|
1698
|
+
plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
|
1699
|
+
ctx.restore();
|
1700
|
+
}
|
1701
|
+
|
1702
|
+
function getFillStyle(filloptions, seriesColor, bottom, top) {
|
1703
|
+
var fill = filloptions.fill;
|
1704
|
+
if (!fill)
|
1705
|
+
return null;
|
1706
|
+
|
1707
|
+
if (filloptions.fillColor)
|
1708
|
+
return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
|
1709
|
+
|
1710
|
+
var c = $.color.parse(seriesColor);
|
1711
|
+
c.a = typeof fill == "number" ? fill : 0.4;
|
1712
|
+
c.normalize();
|
1713
|
+
return c.toString();
|
1714
|
+
}
|
1715
|
+
|
1716
|
+
function insertLegend() {
|
1717
|
+
placeholder.find(".legend").remove();
|
1718
|
+
|
1719
|
+
if (!options.legend.show)
|
1720
|
+
return;
|
1721
|
+
|
1722
|
+
var fragments = [], rowStarted = false,
|
1723
|
+
lf = options.legend.labelFormatter, s, label;
|
1724
|
+
for (i = 0; i < series.length; ++i) {
|
1725
|
+
s = series[i];
|
1726
|
+
label = s.label;
|
1727
|
+
if (!label)
|
1728
|
+
continue;
|
1729
|
+
|
1730
|
+
if (i % options.legend.noColumns == 0) {
|
1731
|
+
if (rowStarted)
|
1732
|
+
fragments.push('</tr>');
|
1733
|
+
fragments.push('<tr>');
|
1734
|
+
rowStarted = true;
|
1735
|
+
}
|
1736
|
+
|
1737
|
+
if (lf)
|
1738
|
+
label = lf(label, s);
|
1739
|
+
|
1740
|
+
fragments.push(
|
1741
|
+
'<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>' +
|
1742
|
+
'<td class="legendLabel">' + label + '</td>');
|
1743
|
+
}
|
1744
|
+
if (rowStarted)
|
1745
|
+
fragments.push('</tr>');
|
1746
|
+
|
1747
|
+
if (fragments.length == 0)
|
1748
|
+
return;
|
1749
|
+
|
1750
|
+
var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
|
1751
|
+
if (options.legend.container != null)
|
1752
|
+
$(options.legend.container).html(table);
|
1753
|
+
else {
|
1754
|
+
var pos = "",
|
1755
|
+
p = options.legend.position,
|
1756
|
+
m = options.legend.margin;
|
1757
|
+
if (m[0] == null)
|
1758
|
+
m = [m, m];
|
1759
|
+
if (p.charAt(0) == "n")
|
1760
|
+
pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
|
1761
|
+
else if (p.charAt(0) == "s")
|
1762
|
+
pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
|
1763
|
+
if (p.charAt(1) == "e")
|
1764
|
+
pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
|
1765
|
+
else if (p.charAt(1) == "w")
|
1766
|
+
pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
|
1767
|
+
var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
|
1768
|
+
if (options.legend.backgroundOpacity != 0.0) {
|
1769
|
+
// put in the transparent background
|
1770
|
+
// separately to avoid blended labels and
|
1771
|
+
// label boxes
|
1772
|
+
var c = options.legend.backgroundColor;
|
1773
|
+
if (c == null) {
|
1774
|
+
c = options.grid.backgroundColor;
|
1775
|
+
if (c && typeof c == "string")
|
1776
|
+
c = $.color.parse(c);
|
1777
|
+
else
|
1778
|
+
c = $.color.extract(legend, 'background-color');
|
1779
|
+
c.a = 1;
|
1780
|
+
c = c.toString();
|
1781
|
+
}
|
1782
|
+
var div = legend.children();
|
1783
|
+
$('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
|
1784
|
+
}
|
1785
|
+
}
|
1786
|
+
}
|
1787
|
+
|
1788
|
+
|
1789
|
+
// interactive features
|
1790
|
+
|
1791
|
+
var highlights = [],
|
1792
|
+
redrawTimeout = null;
|
1793
|
+
|
1794
|
+
// returns the data item the mouse is over, or null if none is found
|
1795
|
+
function findNearbyItem(mouseX, mouseY, seriesFilter) {
|
1796
|
+
var maxDistance = options.grid.mouseActiveRadius,
|
1797
|
+
smallestDistance = maxDistance * maxDistance + 1,
|
1798
|
+
item = null, foundPoint = false, i, j;
|
1799
|
+
|
1800
|
+
for (i = 0; i < series.length; ++i) {
|
1801
|
+
if (!seriesFilter(series[i]))
|
1802
|
+
continue;
|
1803
|
+
|
1804
|
+
var s = series[i],
|
1805
|
+
axisx = s.xaxis,
|
1806
|
+
axisy = s.yaxis,
|
1807
|
+
points = s.datapoints.points,
|
1808
|
+
ps = s.datapoints.pointsize,
|
1809
|
+
mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
|
1810
|
+
my = axisy.c2p(mouseY),
|
1811
|
+
maxx = maxDistance / axisx.scale,
|
1812
|
+
maxy = maxDistance / axisy.scale;
|
1813
|
+
|
1814
|
+
if (s.lines.show || s.points.show) {
|
1815
|
+
for (j = 0; j < points.length; j += ps) {
|
1816
|
+
var x = points[j], y = points[j + 1];
|
1817
|
+
if (x == null)
|
1818
|
+
continue;
|
1819
|
+
|
1820
|
+
// For points and lines, the cursor must be within a
|
1821
|
+
// certain distance to the data point
|
1822
|
+
if (x - mx > maxx || x - mx < -maxx ||
|
1823
|
+
y - my > maxy || y - my < -maxy)
|
1824
|
+
continue;
|
1825
|
+
|
1826
|
+
// We have to calculate distances in pixels, not in
|
1827
|
+
// data units, because the scales of the axes may be different
|
1828
|
+
var dx = Math.abs(axisx.p2c(x) - mouseX),
|
1829
|
+
dy = Math.abs(axisy.p2c(y) - mouseY),
|
1830
|
+
dist = dx * dx + dy * dy; // we save the sqrt
|
1831
|
+
|
1832
|
+
// use <= to ensure last point takes precedence
|
1833
|
+
// (last generally means on top of)
|
1834
|
+
if (dist <= smallestDistance) {
|
1835
|
+
smallestDistance = dist;
|
1836
|
+
item = [i, j / ps];
|
1837
|
+
}
|
1838
|
+
}
|
1839
|
+
}
|
1840
|
+
|
1841
|
+
if (s.bars.show && !item) { // no other point can be nearby
|
1842
|
+
var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
|
1843
|
+
barRight = barLeft + s.bars.barWidth;
|
1844
|
+
|
1845
|
+
for (j = 0; j < points.length; j += ps) {
|
1846
|
+
var x = points[j], y = points[j + 1], b = points[j + 2];
|
1847
|
+
if (x == null)
|
1848
|
+
continue;
|
1849
|
+
|
1850
|
+
// for a bar graph, the cursor must be inside the bar
|
1851
|
+
if (series[i].bars.horizontal ?
|
1852
|
+
(mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
|
1853
|
+
my >= y + barLeft && my <= y + barRight) :
|
1854
|
+
(mx >= x + barLeft && mx <= x + barRight &&
|
1855
|
+
my >= Math.min(b, y) && my <= Math.max(b, y)))
|
1856
|
+
item = [i, j / ps];
|
1857
|
+
}
|
1858
|
+
}
|
1859
|
+
}
|
1860
|
+
|
1861
|
+
if (item) {
|
1862
|
+
i = item[0];
|
1863
|
+
j = item[1];
|
1864
|
+
ps = series[i].datapoints.pointsize;
|
1865
|
+
|
1866
|
+
return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
|
1867
|
+
dataIndex: j,
|
1868
|
+
series: series[i],
|
1869
|
+
seriesIndex: i };
|
1870
|
+
}
|
1871
|
+
|
1872
|
+
return null;
|
1873
|
+
}
|
1874
|
+
|
1875
|
+
function onMouseMove(e) {
|
1876
|
+
if (options.grid.hoverable)
|
1877
|
+
triggerClickHoverEvent("plothover", e,
|
1878
|
+
function (s) { return s["hoverable"] != false; });
|
1879
|
+
}
|
1880
|
+
|
1881
|
+
function onClick(e) {
|
1882
|
+
triggerClickHoverEvent("plotclick", e,
|
1883
|
+
function (s) { return s["clickable"] != false; });
|
1884
|
+
}
|
1885
|
+
|
1886
|
+
// trigger click or hover event (they send the same parameters
|
1887
|
+
// so we share their code)
|
1888
|
+
function triggerClickHoverEvent(eventname, event, seriesFilter) {
|
1889
|
+
var offset = eventHolder.offset(),
|
1890
|
+
pos = { pageX: event.pageX, pageY: event.pageY },
|
1891
|
+
canvasX = event.pageX - offset.left - plotOffset.left,
|
1892
|
+
canvasY = event.pageY - offset.top - plotOffset.top;
|
1893
|
+
|
1894
|
+
if (axes.xaxis.used)
|
1895
|
+
pos.x = axes.xaxis.c2p(canvasX);
|
1896
|
+
if (axes.yaxis.used)
|
1897
|
+
pos.y = axes.yaxis.c2p(canvasY);
|
1898
|
+
if (axes.x2axis.used)
|
1899
|
+
pos.x2 = axes.x2axis.c2p(canvasX);
|
1900
|
+
if (axes.y2axis.used)
|
1901
|
+
pos.y2 = axes.y2axis.c2p(canvasY);
|
1902
|
+
|
1903
|
+
var item = findNearbyItem(canvasX, canvasY, seriesFilter);
|
1904
|
+
|
1905
|
+
if (item) {
|
1906
|
+
// fill in mouse pos for any listeners out there
|
1907
|
+
item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
|
1908
|
+
item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
|
1909
|
+
}
|
1910
|
+
|
1911
|
+
if (options.grid.autoHighlight) {
|
1912
|
+
// clear auto-highlights
|
1913
|
+
for (var i = 0; i < highlights.length; ++i) {
|
1914
|
+
var h = highlights[i];
|
1915
|
+
if (h.auto == eventname &&
|
1916
|
+
!(item && h.series == item.series && h.point == item.datapoint))
|
1917
|
+
unhighlight(h.series, h.point);
|
1918
|
+
}
|
1919
|
+
|
1920
|
+
if (item)
|
1921
|
+
highlight(item.series, item.datapoint, eventname);
|
1922
|
+
}
|
1923
|
+
|
1924
|
+
placeholder.trigger(eventname, [ pos, item ]);
|
1925
|
+
}
|
1926
|
+
|
1927
|
+
function triggerRedrawOverlay() {
|
1928
|
+
if (!redrawTimeout)
|
1929
|
+
redrawTimeout = setTimeout(drawOverlay, 30);
|
1930
|
+
}
|
1931
|
+
|
1932
|
+
function drawOverlay() {
|
1933
|
+
redrawTimeout = null;
|
1934
|
+
|
1935
|
+
// draw highlights
|
1936
|
+
octx.save();
|
1937
|
+
octx.clearRect(0, 0, canvasWidth, canvasHeight);
|
1938
|
+
octx.translate(plotOffset.left, plotOffset.top);
|
1939
|
+
|
1940
|
+
var i, hi;
|
1941
|
+
for (i = 0; i < highlights.length; ++i) {
|
1942
|
+
hi = highlights[i];
|
1943
|
+
|
1944
|
+
if (hi.series.bars.show)
|
1945
|
+
drawBarHighlight(hi.series, hi.point);
|
1946
|
+
else
|
1947
|
+
drawPointHighlight(hi.series, hi.point);
|
1948
|
+
}
|
1949
|
+
octx.restore();
|
1950
|
+
|
1951
|
+
executeHooks(hooks.drawOverlay, [octx]);
|
1952
|
+
}
|
1953
|
+
|
1954
|
+
function highlight(s, point, auto) {
|
1955
|
+
if (typeof s == "number")
|
1956
|
+
s = series[s];
|
1957
|
+
|
1958
|
+
if (typeof point == "number")
|
1959
|
+
point = s.data[point];
|
1960
|
+
|
1961
|
+
var i = indexOfHighlight(s, point);
|
1962
|
+
if (i == -1) {
|
1963
|
+
highlights.push({ series: s, point: point, auto: auto });
|
1964
|
+
|
1965
|
+
triggerRedrawOverlay();
|
1966
|
+
}
|
1967
|
+
else if (!auto)
|
1968
|
+
highlights[i].auto = false;
|
1969
|
+
}
|
1970
|
+
|
1971
|
+
function unhighlight(s, point) {
|
1972
|
+
if (s == null && point == null) {
|
1973
|
+
highlights = [];
|
1974
|
+
triggerRedrawOverlay();
|
1975
|
+
}
|
1976
|
+
|
1977
|
+
if (typeof s == "number")
|
1978
|
+
s = series[s];
|
1979
|
+
|
1980
|
+
if (typeof point == "number")
|
1981
|
+
point = s.data[point];
|
1982
|
+
|
1983
|
+
var i = indexOfHighlight(s, point);
|
1984
|
+
if (i != -1) {
|
1985
|
+
highlights.splice(i, 1);
|
1986
|
+
|
1987
|
+
triggerRedrawOverlay();
|
1988
|
+
}
|
1989
|
+
}
|
1990
|
+
|
1991
|
+
function indexOfHighlight(s, p) {
|
1992
|
+
for (var i = 0; i < highlights.length; ++i) {
|
1993
|
+
var h = highlights[i];
|
1994
|
+
if (h.series == s && h.point[0] == p[0]
|
1995
|
+
&& h.point[1] == p[1])
|
1996
|
+
return i;
|
1997
|
+
}
|
1998
|
+
return -1;
|
1999
|
+
}
|
2000
|
+
|
2001
|
+
function drawPointHighlight(series, point) {
|
2002
|
+
var x = point[0], y = point[1],
|
2003
|
+
axisx = series.xaxis, axisy = series.yaxis;
|
2004
|
+
|
2005
|
+
if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
|
2006
|
+
return;
|
2007
|
+
|
2008
|
+
var pointRadius = series.points.radius + series.points.lineWidth / 2;
|
2009
|
+
octx.lineWidth = pointRadius;
|
2010
|
+
octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
|
2011
|
+
var radius = 1.5 * pointRadius;
|
2012
|
+
octx.beginPath();
|
2013
|
+
octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false);
|
2014
|
+
octx.stroke();
|
2015
|
+
}
|
2016
|
+
|
2017
|
+
function drawBarHighlight(series, point) {
|
2018
|
+
octx.lineWidth = series.bars.lineWidth;
|
2019
|
+
octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
|
2020
|
+
var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
|
2021
|
+
var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
|
2022
|
+
drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
|
2023
|
+
0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal);
|
2024
|
+
}
|
2025
|
+
|
2026
|
+
function getColorOrGradient(spec, bottom, top, defaultColor) {
|
2027
|
+
if (typeof spec == "string")
|
2028
|
+
return spec;
|
2029
|
+
else {
|
2030
|
+
// assume this is a gradient spec; IE currently only
|
2031
|
+
// supports a simple vertical gradient properly, so that's
|
2032
|
+
// what we support too
|
2033
|
+
var gradient = ctx.createLinearGradient(0, top, 0, bottom);
|
2034
|
+
|
2035
|
+
for (var i = 0, l = spec.colors.length; i < l; ++i) {
|
2036
|
+
var c = spec.colors[i];
|
2037
|
+
if (typeof c != "string") {
|
2038
|
+
c = $.color.parse(defaultColor).scale('rgb', c.brightness);
|
2039
|
+
c.a *= c.opacity;
|
2040
|
+
c = c.toString();
|
2041
|
+
}
|
2042
|
+
gradient.addColorStop(i / (l - 1), c);
|
2043
|
+
}
|
2044
|
+
|
2045
|
+
return gradient;
|
2046
|
+
}
|
2047
|
+
}
|
2048
|
+
}
|
2049
|
+
|
2050
|
+
$.plot = function(placeholder, data, options) {
|
2051
|
+
var plot = new Plot($(placeholder), data, options, $.plot.plugins);
|
2052
|
+
/*var t0 = new Date();
|
2053
|
+
var t1 = new Date();
|
2054
|
+
var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
|
2055
|
+
if (window.console)
|
2056
|
+
console.log(tstr);
|
2057
|
+
else
|
2058
|
+
alert(tstr);*/
|
2059
|
+
return plot;
|
2060
|
+
};
|
2061
|
+
|
2062
|
+
$.plot.plugins = [];
|
2063
|
+
|
2064
|
+
// returns a string with the date d formatted according to fmt
|
2065
|
+
$.plot.formatDate = function(d, fmt, monthNames) {
|
2066
|
+
var leftPad = function(n) {
|
2067
|
+
n = "" + n;
|
2068
|
+
return n.length == 1 ? "0" + n : n;
|
2069
|
+
};
|
2070
|
+
|
2071
|
+
var r = [];
|
2072
|
+
var escape = false;
|
2073
|
+
var hours = d.getUTCHours();
|
2074
|
+
var isAM = hours < 12;
|
2075
|
+
if (monthNames == null)
|
2076
|
+
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
2077
|
+
|
2078
|
+
if (fmt.search(/%p|%P/) != -1) {
|
2079
|
+
if (hours > 12) {
|
2080
|
+
hours = hours - 12;
|
2081
|
+
} else if (hours == 0) {
|
2082
|
+
hours = 12;
|
2083
|
+
}
|
2084
|
+
}
|
2085
|
+
for (var i = 0; i < fmt.length; ++i) {
|
2086
|
+
var c = fmt.charAt(i);
|
2087
|
+
|
2088
|
+
if (escape) {
|
2089
|
+
switch (c) {
|
2090
|
+
case 'h': c = "" + hours; break;
|
2091
|
+
case 'H': c = leftPad(hours); break;
|
2092
|
+
case 'M': c = leftPad(d.getUTCMinutes()); break;
|
2093
|
+
case 'S': c = leftPad(d.getUTCSeconds()); break;
|
2094
|
+
case 'd': c = "" + d.getUTCDate(); break;
|
2095
|
+
case 'm': c = "" + (d.getUTCMonth() + 1); break;
|
2096
|
+
case 'y': c = "" + d.getUTCFullYear(); break;
|
2097
|
+
case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
|
2098
|
+
case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
|
2099
|
+
case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
|
2100
|
+
}
|
2101
|
+
r.push(c);
|
2102
|
+
escape = false;
|
2103
|
+
}
|
2104
|
+
else {
|
2105
|
+
if (c == "%")
|
2106
|
+
escape = true;
|
2107
|
+
else
|
2108
|
+
r.push(c);
|
2109
|
+
}
|
2110
|
+
}
|
2111
|
+
return r.join("");
|
2112
|
+
};
|
2113
|
+
|
2114
|
+
// round to nearby lower multiple of base
|
2115
|
+
function floorInBase(n, base) {
|
2116
|
+
return base * Math.floor(n / base);
|
2117
|
+
}
|
2118
|
+
|
2119
|
+
})(jQuery);
|