├── .gitignore ├── README.md ├── lib ├── d3.v3.min.js ├── d3zabbix.js ├── gauge.js ├── jquery-2.1.3.min.js └── jqzabbix.js ├── samples.css ├── samples.html └── screenshots ├── samples.png ├── screenshot.png ├── timeSeries.png ├── timeSeriesZbxNext1193.png └── triggerTable.png /.gitignore: -------------------------------------------------------------------------------- 1 | my-credentials.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zabbix-d3js-widgets 2 | 3 | This repository hasn't been updated for some time. In the meantime I started a similar project to get out of library dependency. 4 | Have a look at [timeseries.js](https://github.com/hgruber/timeseries.js). 5 | 6 | just some simple javascript widgets for customizable zabbix dashboards 7 | ![ScreenShot](https://raw.githubusercontent.com/hgruber/zabbix-d3js-widgets/master/screenshots/screenshot.png) 8 | 9 | The widgets provide a very simple way to easily integrate zabbix information on your own dashboard. It just uses javascript (jquery, d3, jqzabbix) and the zabbix api to create self-updating animated tables and graphs. 10 | Have a look at the demo. 11 | 12 | installation 13 | ============ 14 | * copy the files to your document root 15 | * provide the zabbix api url and credentials in samples.html (https:///zabbix/api_jsonrpc.php) 16 | * for zabbix < 2.4: provide the zabbix patch for cross site requests (https://support.zabbix.com/browse/ZBXNEXT-1377) 17 | * for saving bandwidth don't forget to enable gzip compression for api calls (usually about 95%) 18 | * for saving bandwidth zabbix > 3 allows the use of trend.get(). For older versions there's a patch for the trend.get() api call is available (https://support.zabbix.com/browse/ZBXNEXT-1193) 19 | 20 | instant sample 21 | ============== 22 | * clone repository / download zip 23 | * open `samples.html` in the web browser of your choice. 24 | 25 | the `samples.html` is pre-configured to access a demo Zabbix instance located on zabbix.org and shows a sample for each widget. 26 | 27 | itemGauge 28 | ========= 29 | itemGauge shows the last value of a Zabbix item and is refreshed according to the item's update interval. The widget is currently only a wrapper around Tomer Doron's google style gauges using d3.js. 30 | 31 | timeSeries 32 | ========== 33 | timeSeries is a widget to diplay timeseries data from zabbix in a simple to use graph. Intuitive scrolling and panning is provided in realtime doing api calls in the background. When zooming out for several days the trend.get() api call is used to receive data in lower detail. 34 | 35 | triggerTable 36 | ============ 37 | triggerTable displays all alerting zabbix triggers in an animated table. Animations draw the user's attention to the dashboard when changes occur. 38 | 39 | imageReload 40 | =========== 41 | A simple function for flickerfree image reloads in dashboards. Provide the image's container id (not the id of the image itself) and the 42 | refresh interval in seconds. After each timeout the image url will be modified by appending '&refresh=' and then reloaded in the background. As soon as the image is loaded it will be replaced resulting in flickerfree image updates. This works with one image per element only. 43 | 44 | todos 45 | ===== 46 | timeSeries is a class with a high potential for more features. This has to be done very carefully: performance is a critical issue here, transition animations quickly drive the browser to its limits. 47 | A gant chart for events is planned. This will be very usefull for problem correlations. 48 | 49 | links 50 | ===== 51 | * http://www.zabbix.com 52 | * http://d3js.org 53 | * https://github.com/mbostock/d3 54 | * https://github.com/kodai/jqzabbix 55 | 56 | screenshots 57 | =========== 58 | ![ScreenShot](https://raw.githubusercontent.com/hgruber/zabbix-d3js-widgets/master/screenshots/timeSeriesZbxNext1193.png) 59 | ![ScreenShot](https://raw.githubusercontent.com/hgruber/zabbix-d3js-widgets/master/screenshots/samples.png) 60 | -------------------------------------------------------------------------------- /lib/d3zabbix.js: -------------------------------------------------------------------------------- 1 | // 2 | // server class (requests to server) 3 | // 4 | var start_ = []; // array for all startRequest() methods 5 | var req; // global request method 6 | 7 | function serverHandle(arg) { 8 | var jqzabbix = new $.jqzabbix(arg); 9 | jqzabbix.userLogin(null, startWidgets, onError); 10 | $( window ).unload(function() { // logout on close window 11 | jqzabbix.sendAjaxRequest('user.logout', null, null, null); 12 | }); 13 | function startWidgets() { 14 | start_.forEach(function(callback) { // call startRequest() for every widget 15 | callback(); 16 | }); 17 | } 18 | function request(method, params, ok, nok) { 19 | return jqzabbix.sendAjaxRequest(method, params, ok, nok); 20 | } 21 | function onError(a) { 22 | $.each(jqzabbix.isError(), function(key, value) { 23 | console.log(key + ' : ' + value); // log errors to console 24 | }); 25 | } 26 | req = request; 27 | } 28 | 29 | // 30 | // class triggerTable 31 | // 32 | 33 | function triggerTable(arg) { 34 | var method = 'trigger.get'; 35 | var limit = typeof arg.maxItems !== 'undefined' ? arg.maxItems : 25; 36 | var params = { 37 | 'filter': { 'value': 1 }, 38 | 'groupids': arg.groupids, 39 | 'min_severity': typeof arg.minPriority !== 'undefined' ? arg.minPriority : 2, 40 | 'monitored': typeof arg.monitored !== 'undefined' ? arg.monitored : 1, 41 | 'withLastEventUnacknowledged': typeof arg.withLastEventUnacknowledged !== 'undefined' ? arg.withLastEventUnacknowledged : 1, 42 | 'skipDependent': 1, 43 | 'output': [ 'triggerid', 'state', 'error', 'url', 'description', 'comments', 'priority', 'lastchange' ], 44 | 'selectHosts': ['name'], 45 | 'selectLastEvent': ['eventid'], 46 | 'expandDescription': 1, 47 | 'sortfield': [ 'lastchange' ], 48 | 'sortorder': [ 'DESC' ], 49 | 'limit': limit 50 | }; 51 | var refresh = typeof arg.refresh !== 'undefined' ? arg.refresh : 10; 52 | var delayed = typeof arg.oldDelayed !== 'undefined' ? arg.oldDelayed : 1; 53 | var severity = ['not classified', 'information', 'warning', 'average', 'high', 'disaster' ]; 54 | start_.push(startRequests); 55 | var width = $(arg.bindTo).width(); 56 | 57 | function startRequests() { 58 | req(method, params, successMethod, errorMethod); 59 | setTimeout(startRequests, refresh * 1000); 60 | } 61 | 62 | function row(d) { 63 | function pad(s) { 64 | r = s.toString(); 65 | return r.length == 1 ? '0' + r : r; 66 | } 67 | var dat = new Date(d.lastchange*1000); 68 | if (!d.url) { // so let's take the url from comments' field 69 | var pattern = /http.*/g; 70 | d.url = pattern.exec(d.comments); 71 | d.comments = d.comments.replace(pattern,'').slice(0,-1); 72 | } 73 | // TODO: do this in a cleaner way 74 | return '
' + 76 | d.hosts[0].name + ': ' + 77 | ((d.url)?'':'') + d.description + 78 | ((d.url)?'':'') + 79 | '' + pad (dat.getDate ()) + '.' + 80 | pad (dat.getMonth () + 1) + '.' + ' ' + pad (dat.getHours ()) + 81 | ':' + pad (dat.getMinutes ()) + ':' + pad (dat.getSeconds ()) + 82 | '
'; 83 | } 84 | 85 | function errorMethod() { 86 | console.log('request failed'); 87 | d3.select(arg.bindTo).selectAll("div.alert").style('opacity', '0.5'); 88 | } 89 | 90 | function successMethod(response, status) { 91 | width = $(arg.bindTo).width(); 92 | var elements = response.result.length; 93 | var p = d3.select(arg.bindTo) 94 | .selectAll("div") 95 | .sort(function(a,b) { 96 | return d3.descending(a.lastchange, b.lastchange); 97 | }) 98 | .data(response.result.reverse(), function(d) { return d.triggerid; }) 99 | .html(function(d) { return row(d) }) 100 | .style("width", width+'px') 101 | .style('display', function(d,i) { 102 | if (i < elements-limit) return 'none'; 103 | else return 'block'; 104 | }) 105 | .style('opacity', '1.0'); 106 | p.enter() 107 | .insert("div", ":first-child") 108 | .html(row) 109 | .attr("class", function(d) { 110 | return 'alert c' + d.priority; 111 | }) 112 | .attr("id", function(d) { 113 | return 'id' + d.triggerid; 114 | }) 115 | .style('display', function(d,i) { 116 | if (i < elements-limit) return 'none'; 117 | else return 'block'; 118 | }) 119 | .style("width", width+'px') 120 | .style("margin-left", '-' + (100 + width) + 'px') 121 | // TODO: the acknowledge function has to go to row function 122 | /* .on('click', function(d) { 123 | if (confirm('Acknowledge event: ' + d.description + '?')) { 124 | req('event.acknowledge', { 125 | "eventids": d.lastEvent.eventid, 126 | "message": "Acknowledged on the dashboard" 127 | } , function() { 128 | d3.select("#id"+d.triggerid).attr('class', 'alert ack'); 129 | }, 0); 130 | } 131 | }) */ 132 | .transition() 133 | .delay(function(d) { if (delayed) return ($.now()-d.lastchange*1000)*1e-6; else return 0; }) 134 | .duration(1200) 135 | .ease('bounce') 136 | .style("margin-left", "0px") 137 | .style("margin-bottom", "0px"); 138 | p.exit() 139 | .transition() 140 | .duration(3000) 141 | .ease('back') 142 | .style("margin-left", "1000px") 143 | .duration(1000) 144 | .style("height", "0px") 145 | .remove(); 146 | } 147 | } 148 | 149 | // 150 | // displayItem 151 | // 152 | // simply write out a single value 153 | // (or a sum of some values) as a single self updating digit 154 | // 155 | function displayItem(arg) { 156 | var refresh = typeof arg.refresh !== 'undefined' ? arg.refresh : 10; 157 | var width = $(arg.bindTo).width(); 158 | start_.push(startRequests); 159 | function startRequests() { 160 | req('item.get', { 161 | 'itemids': arg.itemid, 162 | 'output': ['name', 'key_', 'lastvalue', 'lastclock', 'units'], 163 | }, 164 | itemCallback, errorMethod); 165 | setTimeout(startRequests, refresh * 1000); 166 | } 167 | function errorMethod() { 168 | console.log('request failed'); 169 | d3.select(arg.bindTo).selectAll("div.alert").style('opacity', '0.5'); 170 | } 171 | function itemCallback(response, status) { 172 | var value = 0; 173 | response.result.forEach(function(item) { 174 | value += Number(item.lastvalue); 175 | }); 176 | var units = response.result[0].units; 177 | $(arg.bindTo).html(String(value) + units); 178 | } 179 | } 180 | 181 | // 182 | // ordered item list 183 | // 184 | // first working version 185 | // shows an ordered list of items 186 | // that reorder in fixed intervals 187 | // 188 | function orderedItems(arg) { 189 | var refresh = typeof arg.refresh !== 'undefined' ? arg.refresh : 10; 190 | start_.push(startRequests); 191 | var width = $(arg.bindTo).width(); 192 | var items = [] 193 | var statements = {} 194 | 195 | function startRequests() { 196 | req('item.get', { 197 | 'search': arg.search, 198 | 'startSearch': 1, 199 | 'output': ['name', 'key_', 'lastvalue', 'lastclock', 'units'], 200 | }, 201 | itemCallback, errorMethod); 202 | setTimeout(startRequests, refresh * 1000); 203 | } 204 | function errorMethod() { 205 | console.log('request failed'); 206 | d3.select(arg.bindTo).selectAll("div.alert").style('opacity', '0.5'); 207 | } 208 | function itemCallback(response, status) { 209 | function cl(d) { 210 | var i = 1 211 | if (d.value > 1) i = 2; 212 | if (d.value > 5) i = 3; 213 | if (d.value > 15) i = 4; 214 | if (d.value > 30) i = 5; 215 | return 'alert c'+i; 216 | } 217 | function text(d) { 218 | return ''+d.name+''+d.value+''; 219 | } 220 | function top(d) { 221 | return (d.position*20)+'px'; 222 | } 223 | now = new Date(); 224 | width = $(arg.bindTo).width(); 225 | last = (now/1000)-600; 226 | data = []; 227 | response.result.sort(function(a,b) { return b.lastvalue-a.lastvalue; }); 228 | position = 0; 229 | response.result.forEach(function(item) { 230 | if (item.lastclock > last) 231 | data.push({ 232 | 'name': item.name, 233 | 'key': item.key_, 234 | 'value': item.lastvalue + item.units, 235 | 'position': position++ 236 | }); 237 | }); 238 | var p = d3.select(arg.bindTo) 239 | .selectAll("div") 240 | .data(data, function(d) { return d.key; }) 241 | .style("width", width+'px'); 242 | p.html(text); 243 | p.transition() 244 | .duration(2000) 245 | .attr("class", cl) 246 | .style("top", top) 247 | .style("opacity", '1.0'); 248 | p.enter() 249 | .insert("div", ":first-child") 250 | .html(text) 251 | .attr("class", cl) 252 | .attr("id", function(d) { return d.key; }) 253 | .style("position", "absolute") 254 | .style("width", width+'px') 255 | .style("top", "1200px") 256 | .transition() 257 | .duration(1200) 258 | .ease('bounce') 259 | .style("top", top) 260 | .style("margin-bottom", "0px"); 261 | p.exit() 262 | .transition() 263 | .duration(3000) 264 | .style("top", "1200px") 265 | .duration(1000) 266 | .style("height", "0px") 267 | .remove(); 268 | } 269 | } 270 | 271 | 272 | // 273 | // imageReload() 274 | // a simple function for flickerfree image reloads in dashboards 275 | // 276 | // Provide the image's container id (not the id of the image itself) and the 277 | // refresh interval in seconds. After each timeout the image url will be 278 | // modified by appending '&refresh=' and then reloaded in the background. 279 | // As soon as the image is loaded it will be replaced resulting in flickerfree 280 | // image updates. This works with one image per element only. 281 | // 282 | function imageReload(id, refresh) { 283 | function reload() { 284 | var url = $(id+'>img')[0].src; 285 | var ts = new Date(); 286 | if (url.match(/refresh=.*/)) { 287 | url = url.replace(/refresh=.*/, "refresh="+(+ts)); 288 | } else { 289 | url = url + "&refresh="+(+ts); 290 | } 291 | var temp = $(''); 292 | temp.attr("src", url).css("display", "none"); 293 | $(id).prepend(temp); 294 | temp.load(function() { 295 | $(id+'>img').not(this).remove(); 296 | $(this).css("display", "block"); 297 | }); 298 | setTimeout(reload, refresh * 1000); 299 | } 300 | setTimeout(reload, refresh * 1000); 301 | } 302 | 303 | // d3Table() 304 | // 305 | // 306 | // 307 | function d3Table() { 308 | } 309 | 310 | // needed for timeSeries 311 | // a and b are arrays of intervals: [ [min_1,max_1], [min_2,max_2], .., [min_n,max_n] ] 312 | // the difference function returns the difference a-b which again is an array of intervals 313 | // if a is your viewport (the data you want to display) and b your cache map 314 | // then you have to iterate through the result array and fetch data for every interval 315 | // the addition function returns the sum a+b which again is an array of intervals 316 | // the intersection function returns the intersection of a and b which is an array of intervals 317 | // the iLength function returns the sum (scalar) of a's interval length 318 | // 319 | // unit tests for substraction(a, b), intersection(a, b) and addition(a, b) 320 | /* 321 | console.log('Result: ' + addition( [[1,3],[8,10],[17,20]] , [[2,11], [14,15]] ) ); 322 | console.log('Result: ' + substraction( [[1,3],[8,10],[17,20]] , [[2,11], [14,15]] ) ); 323 | console.log('Result: ' + intersection( [[1,3],[8,10],[17,20]] , [[2,11], [14,15]] ) ); 324 | console.log('Result: ' + substraction( [[Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]], [[2,11], [14,15]] )); 325 | console.log('Result: ' + addition( [] , [[2,11], [14,15]] ) ); 326 | console.log('Result: ' + substraction( [] , [[2,11], [14,15]] ) ); 327 | console.log('Result: ' + iLength([[1,3],[8,10],[17,20]] )); 328 | console.log('Result: ' + iLength([[Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]])); 329 | */ 330 | // "Result: 1,11,14,15,17,20" 331 | // "Result: 1,2,17,20" 332 | // "Result: 2,3,8,10" 333 | // "Result: -Infinity,2,11,14,15,Infinity" 334 | // "Result: 2,11,14,15" 335 | // "Result: " 336 | // "Result: 7" 337 | // "Result: Infinity" 338 | 339 | function substraction(a, b) { 340 | function difference(m, s) { 341 | if (s[1] <= m[0] || m[1] <= s[0]) return [m]; 342 | if (s[1] < m[1]) { 343 | if (s[0] <= m[0]) return [ [ s[1], m[1] ] ]; 344 | return [ [ m[0], s[0] ], [ s[1], m[1] ] ]; 345 | } 346 | if (s[0] <= m[0]) return []; 347 | return [ [ m[0], s[0] ] ]; 348 | } 349 | function single(m, s) { 350 | diff = []; 351 | m.forEach(function(md) { 352 | difference(md, s).forEach(function(ret) { 353 | diff.push(ret); 354 | }); 355 | }); 356 | return diff; 357 | } 358 | if (a === undefined || b === undefined) return []; 359 | var diff = a; 360 | b.forEach(function(m) { 361 | diff = single(diff, m); 362 | }); 363 | return diff; 364 | } 365 | 366 | function intersection(a, b) { 367 | if (a === undefined || b === undefined) return []; 368 | var b_inverse = substraction([[Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]], b); 369 | return substraction(a,b_inverse); 370 | } 371 | 372 | function addition(a, b) { 373 | function sum(m, s) { 374 | if (s[1] < m[0]) return [ s, m ]; 375 | if (m[1] < s[0]) return [ m, s ]; 376 | if (s[1] < m[1]) { 377 | if (s[0] <= m[0]) return [ [ s[0], m[1] ] ]; 378 | return [ m ]; 379 | } 380 | if (s[0] <= m[0]) return [ s ]; 381 | return [ [ m[0], s[1] ] ]; 382 | } 383 | if (a === undefined || b === undefined) return []; 384 | var dummy = a.concat(b).sort(function(a,b) { 385 | return a[0] - b[0]; 386 | }); 387 | var result = dummy.slice(); 388 | for (i = 1; i < dummy.length; i++) { 389 | var s = sum(dummy[i-1],dummy[i]); 390 | if (s.length==1) { 391 | result.splice(0,1); 392 | result[0] = s[0]; 393 | dummy[i] = s[0]; 394 | } 395 | } 396 | return result; 397 | } 398 | 399 | function iLength(a) { 400 | var length = 0; 401 | a.forEach(function(o) { 402 | length = length + Number(o[1]) - Number(o[0]); 403 | }); 404 | return length; 405 | } 406 | 407 | // 408 | // getIndex(, value) 409 | // 410 | // a binary search on an array of arrays where the first element is 411 | // being searched for and which must be sorted in ascending order. 412 | // The return value is an array with three indeces: 413 | // [0] the index of the previous element next to value 414 | // [1] the index of the following element next to value 415 | // [2] the index of the element closest to value 416 | // * if the value matches, all three indeces are equal 417 | // * if there's no previous or following neighbour -1 is returned 418 | // * if the the given array is non-empty there are at least two valid 419 | // indeces in the resulting array 420 | // 421 | function getIndex(items, value) { 422 | var startIndex = 0, 423 | stopIndex = items.length - 1, 424 | middle = Math.floor((stopIndex + startIndex)/2), 425 | floor = -1, 426 | ceil = -1; 427 | if (items === undefined || items[0] === undefined) return [-1,-1,-1]; 428 | if (value <= items[startIndex][0]) return [ -1, 0, 0 ]; 429 | if (value >= items[stopIndex][0]) return [ stopIndex, -1, stopIndex ]; 430 | while (items[middle][0] != value && startIndex < stopIndex) { 431 | if (value < items[middle][0]) { 432 | stopIndex = middle - 1; 433 | ceil = middle; 434 | } else if (value > items[middle][0]) { 435 | startIndex = middle + 1; 436 | floor = middle; 437 | } 438 | middle = Math.floor((stopIndex + startIndex)/2); 439 | } 440 | if (value <= items[middle][0]) ceil = middle; 441 | if (value >= items[middle][0]) floor = middle; 442 | if (floor == -1) return [ floor, ceil, ceil ]; 443 | if (ceil == -1) return [ floor, ceil, floor ]; 444 | if (value-items[floor][0] < items[ceil][0] - value) return [ floor, ceil, floor ]; 445 | else return [ floor, ceil, ceil ]; 446 | } 447 | /* 448 | var test = [ [1], [2], [8], [10], [10], [12], [19] ]; 449 | console.log(getIndex(test, 0)); 450 | console.log(getIndex(test, 1)); 451 | console.log(getIndex(test, 2)); 452 | console.log(getIndex(test, 3)); 453 | console.log(getIndex(test,10)); 454 | console.log(getIndex(test,15)); 455 | console.log(getIndex(test,20)); 456 | */ 457 | 458 | // reminds me that I want to provide standard bank holidays 459 | function Easter(Y) { 460 | var C = Math.floor(Y/100); 461 | var N = Y - 19*Math.floor(Y/19); 462 | var K = Math.floor((C - 17)/25); 463 | var I = C - Math.floor(C/4) - Math.floor((C - K)/3) + 19*N + 15; 464 | I = I - 30*Math.floor((I/30)); 465 | I = I - Math.floor(I/28)*(1 - Math.floor(I/28)*Math.floor(29/(I + 1))*Math.floor((21 - N)/11)); 466 | var J = Y + Math.floor(Y/4) + I + 2 - C + Math.floor(C/4); 467 | J = J - 7*Math.floor(J/7); 468 | var L = I - J; 469 | var M = 3 + Math.floor((L + 40)/44); 470 | var D = L + 28 - 31*Math.floor(M/4); 471 | return D + '.' + M; 472 | } 473 | 474 | var holidays = { 475 | '1.1': 'Neujahr', 476 | '1.5': 'Maifeiertag', 477 | '-2' : 'Karfreitag', 478 | '+0' : 'Ostersonntag', 479 | '+1' : 'Ostermontag', 480 | '+39': 'Himmelfahrt', 481 | '+49': 'Pfingstsonntag', 482 | '+50': 'Pfingstmontag', 483 | '+60': 'Fronleichnahm', 484 | '3.10': 'Tag der Einheit', 485 | '24.12': 'Heilig Abend', 486 | '25.12': '1. Weihnachtstag', 487 | '26.12': '2. Weihnachtstag', 488 | '31.12': 'Sylvester' 489 | }; 490 | var easterYears = {}; // store dates for every year 491 | var hL = {}; // store all holidays here 492 | 493 | // newdate.getDate() + 87 494 | function isHoliday(date) { 495 | var Y = date.getFullYear(); 496 | var d = date.getDate()+'.'+(date.getMonth()+1); 497 | var di = d+'.'+Y; 498 | if (hL.hasOwnProperty(di)) return hL[di]; 499 | var EasterDate; 500 | if (!easterYears.hasOwnProperty(Y.toString())) { 501 | easterYears[Y] = Easter(Y); 502 | } 503 | var a = easterYears[Y].split('.'); 504 | for(var day in holidays) { 505 | if (d == day) { 506 | hL[di] = holidays[day]; 507 | return holidays[day]; 508 | } else if (day[0] == '-' || day[0] == '+') { 509 | var checkDay = new Date(Y,a[1]-1,a[0]); 510 | checkDay.setDate(checkDay.getDate() + Number(day)); 511 | checkDay = checkDay.getDate()+'.'+(checkDay.getMonth()+1); 512 | if (d == checkDay) { 513 | hL[di] = holidays[day]; 514 | return holidays[day]; 515 | } 516 | } 517 | } 518 | hL[di] = false; 519 | return false; 520 | } 521 | 522 | // 523 | // class timeSeries 524 | // 525 | 526 | function timeSeries(arg) { 527 | // after succesful api login startRequests will be called by server 528 | start_.push(startRequests); 529 | // format defines the labeling format of the time axis 530 | var format = d3.time.format.multi([ 531 | [".%L", function(d) { return d.getMilliseconds(); }], 532 | [":%S", function(d) { return d.getSeconds(); }], 533 | ["%H:%M", function(d) { return d.getMinutes(); }], 534 | ["%H:00", function(d) { return d.getHours(); }], 535 | ["%d.%m.", function(d) { return d.getDay() && d.getDate() != 1; }], 536 | ["%d.%m.", function(d) { return d.getDate() != 1; }], 537 | ["%b", function(d) { return d.getMonth(); }], 538 | ["%Y", function() { return true; }] 539 | ]); 540 | // tick_sizing defines the ticks for different scales 541 | // rework has to be done here: I want colored bands depending on 542 | // hours, days weeks, month, ... 543 | var tick_sizing = [ 544 | [ 1.2, d3.time.seconds, 15, 'm' ], 545 | [ 3.6, d3.time.minutes, 1, 'm' ], 546 | [ 14.4, d3.time.minutes, 5, 'h' ], 547 | [ 43.2, d3.time.minutes, 15, 'h' ], 548 | [ 86.4, d3.time.minutes, 30, 'h' ], 549 | [ 172.8, d3.time.hours, 1, 'h' ], 550 | [ 432.0, d3.time.hours, 2, 'd' ], 551 | [ 604.8, d3.time.hours, 6, 'd' ], 552 | [ 1209.6, d3.time.hours, 12, 'd' ], 553 | [ 2592.0, d3.time.days, 1, 'd' ], 554 | [ 5184.0, d3.time.days, 2, 'd' ], 555 | [10368.0, d3.time.days, 7, 'd' ], 556 | [200000.0,d3.time.months, 1, 'M' ], 557 | ]; 558 | var sizing = []; 559 | var units; 560 | // TODO: use locale somehow.... 561 | var days = [ 'Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag' ]; 562 | var colors = { 0: 'steelblue', 1: 'red', 2: 'green'}; 563 | // sizex and sizey define the parent element size 564 | var sizex, sizey; 565 | // the elements margins 566 | // given as parameter? calculated automatically depending on labeling? 567 | // for now I leave this as is 568 | var margin = { top: 16, right: 12, bottom: 12, left: 50 }; 569 | // the inner diagram dimensions 570 | var width, height; 571 | var zoom_scale = 1; 572 | var zooming = false; 573 | var hideLegend = typeof arg.hideLegend !== 'undefined' ? arg.hideLegend : false; 574 | var preFetch = typeof arg.preFetch !== 'undefined' ? arg.preFetch: 1; 575 | 576 | // items contains for every itemid an object containing name, itemid and value_type 577 | var items = {}; 578 | // just an array given with itemids 579 | // for now there's just one itemid allowed, this will change in future 580 | var itemids = typeof arg.itemids !== 'undefined' ? arg.itemids : null; 581 | var itemValueType = -1; 582 | 583 | // if mouseLocksOnNearest is set, the mouseover tooltip will lock on the nearest (distance) datum 584 | // otherwise the closest item on the time axis is used 585 | var mouseLocksOnNearest = typeof arg.mouseLocksOnNearest !== 'undefined' ? arg.mouseLocksOnNearest : 0; 586 | 587 | // interpolation 588 | // see http://www.d3noob.org/2013/01/smoothing-out-lines-in-d3js.html 589 | var interpolate = typeof arg.interpolate !== 'undefined' ? arg.interpolate : 'linear'; 590 | 591 | // initial viewport is now minus 24h 592 | // this should be an argument and if set the viewport should be moved (moving 24h window mode) 593 | // if the viewport is modified, should we go back to this mode? after what time? 594 | // or should we go back after now hits the right margin? 595 | var xRange = last(24); 596 | var yRange = [0, 500]; // initial value used for a very short moment 597 | // the x and y objects are needed to convert vales to svg coordinates and back 598 | var x = d3.time.scale(), 599 | y = d3.scale.linear(); 600 | var xAxis = d3.svg.axis().scale(x), 601 | yAxis = d3.svg.axis().scale(y).tickFormat(d3.format('s')); 602 | var followViewSeconds; 603 | var lastRequest; 604 | 605 | // svg is bound to given html element arg.bindTo 606 | var svg = d3.select(arg.bindTo) 607 | .append("svg"); 608 | var bands = svg.append("g") 609 | .attr("class", "timebands"); 610 | // the 'future fog' element is separated from the 'past' by now 611 | var future = svg.append("rect") 612 | .attr("class", "future"); 613 | 614 | // the axis objects 615 | var xA = svg.append("g") 616 | .attr("class", "x axis") 617 | .call(xAxis); 618 | var yA = svg.append("g") 619 | .attr("class", "y axis") 620 | .attr("transform", "translate(" + margin.left + ",0)") 621 | .call(yAxis); 622 | 623 | // the value's cache 624 | var visibleValues = []; 625 | var timer = 0; 626 | 627 | // if zoom_scale is lower than this use trends 628 | var trends_scale = 0.3; 629 | var activeScale = 0; 630 | var trendsSupported = true; 631 | // Zabbix value_types: 632 | // 0 - numeric float; 633 | // 1 - character; 634 | // 2 - log; 635 | // 3 - numeric unsigned; 636 | // 4 - text. 637 | var data_type = [ 'n', 'c', 'c', 'n', 'c' ]; 638 | // a value cache for every scale: 639 | var scales = [ 640 | { 'method': 'history.get', 641 | 'values': [], // the value cache for this scale 642 | 'range': [], // this is an array of ranges (intervals) kept in the value's cache 643 | // it uses the functions addition and substraction 644 | 'requestedRange': [],// the intervals for every request made until response (unused) 645 | 'resolution': 60, 646 | 'valueline': d3.svg.line() 647 | .x(function(d) { return Math.round(x(d[0]*1000)); }) 648 | .y(function(d) { return Math.round(Number(y(d[1]))); }) 649 | .interpolate(interpolate) 650 | }, 651 | { 'method': 'trend.get', 652 | 'values': [], 653 | 'range': [], 654 | 'resolution': 3600, 655 | 'requestedRange': [], 656 | 'valueline': d3.svg.line() 657 | .x(function(d) { return Math.round(x(d[0]*1000)); }) 658 | .y(function(d) { return Math.round(Number(y(d[1]))); }) 659 | .interpolate('cardinal') 660 | } 661 | ]; 662 | 663 | // this is the graph: path is an array of svg elements and valueline is its description 664 | // for each itemid there's one graph in the array 665 | var path = []; 666 | var path_minmax = []; 667 | var gant = []; 668 | var gantLayers = 0; 669 | var gantKeyWords = ['started', 'ended']; 670 | // info consists of several elements: tooltip, circle and line 671 | var yLabel = yA.append("text") 672 | .style("text-anchor", "end") 673 | .attr("transform", "translate(-40,"+margin.top+") rotate(-90)") 674 | // TODO: rename plotArea, it's only path 675 | var plotArea = svg.append("g").attr("class", "path"); 676 | var gantChart = svg.append("g").attr("class", "gant"); 677 | var gantInfo = false; // holds info for current tooltip 678 | var legendBox = svg.append("rect") 679 | .attr('class', 'legendback'); 680 | var legend = svg.append("g").attr("class", "legend"); 681 | var hideROverlap = svg.append("rect"); 682 | var info = svg.append("g").attr("class", "info"); 683 | var infoBox = info.append("rect").attr("class", "info"); 684 | var zoom = d3.behavior.zoom() 685 | .scaleExtent([.0005, 1000]) 686 | .on("zoom", zoomed) 687 | .on("zoomend", function() { 688 | startRequests(); 689 | dragging = false; 690 | }); 691 | // the diagram has two behaviors: drag and zoom 692 | // drag shouldn't be used here as panning is part of zooming, but 693 | // I didn't got it working.... 694 | var dragging = false; 695 | var drag = d3.behavior.drag() 696 | .on("dragstart", function() { 697 | dragging = true; 698 | d3.event.sourceEvent.stopPropagation(); 699 | }) 700 | .on("drag", dragged); 701 | // this is just for the tooltip 702 | // mind the difference 'mouseleave' and 'mouseout'! 703 | svg 704 | .on('mousemove', plotInfo) 705 | .on('mouseleave', clearInfo) 706 | .call(zoom) 707 | .call(drag); 708 | 709 | // plot empty diagram for the first time (just plot everything) 710 | plotSVG(); 711 | startFollowView(); 712 | // jquery here for the resize event, just plot everything 713 | $(window).resize(function() { 714 | plotSVG(); 715 | }); 716 | 717 | // return uniq colors for a given text label 718 | function getColor(str) { 719 | if (colors[str]) { 720 | return colors[str]; 721 | } else { 722 | var hash = 5381; 723 | for (i = 0; i < str.length; i++) { 724 | char = str.charCodeAt(i); 725 | hash = ((hash << 5) + hash) + char; /* hash * 33 + c */ 726 | } 727 | colors[str] = '#' + Math.abs(hash).toString(16).substring(0, 6); 728 | return colors[str]; 729 | } 730 | } 731 | 732 | // this first api call (item.get) is needed to figure out if 733 | // item is integer, float, text, ... 734 | // if the variable 'history' isn't correctly provided history.get won't return anything 735 | // here we get the item name as well. 736 | // TODO: do some regexp magic to construct the visible name from name and key 737 | // (in the zabbix gui it's done with php) 738 | function getItemValueTypes() { 739 | req('item.get', { 740 | 'output': [ 'name', 'value_type', 'units', 'key_' ], 741 | 'itemids': itemids 742 | }, function(r) { 743 | if (r.result.length>0) { 744 | r.result.forEach(function(o, i) { 745 | items[o.itemid] = { 746 | 'index': i, 'name': o.name, 'key': o.key_, 747 | 'units': o.units, 'value_type': o.value_type, 748 | 'data_type': data_type[o.value_type] }; 749 | scales.forEach(function(o,j) { 750 | scales[j].values[i] = []; 751 | }); 752 | gant[i] = []; 753 | if (data_type[o.value_type] == 'n') { 754 | path_minmax[i] = plotArea.append("path") 755 | .attr("class", "path"+i+" minmax") 756 | .style('fill', colors[i]); 757 | path[i] = plotArea.append("path") 758 | .attr("class", "path"+i+" normal") 759 | .style('stroke', colors[i]); 760 | } 761 | if (units === undefined) units = o.units; 762 | else if (units != o.units) 763 | console.log('Error: items have different units.'); 764 | }); 765 | itemValueType = Number(r.result[0].value_type); 766 | if (units !== undefined) yLabel.text("["+units+"]"); 767 | startRequests(); 768 | } else { 769 | console.log('Error: Look if item exists.'); 770 | } 771 | }, 0); 772 | } 773 | 774 | // do the api call for values not in the cache 775 | function startRequests() { 776 | if (itemValueType == -1) { 777 | getItemValueTypes(); 778 | return; 779 | } 780 | if (trendsSupported && zoom_scale < trends_scale && data_type[itemValueType] == 'n') activeScale = 1; 781 | else activeScale = 0; 782 | // substraction returns an array of ranges (intervals) 783 | // if you're having several holes in your data cache, this will just 784 | // request the minimum needed. 785 | var now = new Date(); 786 | scales[activeScale].requestedRange.forEach(function(o,i) { 787 | if ((+now-o[2])>5000) { // remove requested ranges after 5s 788 | scales[activeScale].requestedRange.splice(i,1); 789 | } 790 | }); 791 | // prefetching 792 | var x0 = +xRange[0] - (+xRange[1] - xRange[0]) * preFetch; 793 | var x1 = +xRange[1] + (+xRange[1] - xRange[0]) * preFetch; 794 | /* var dontRequest = addition(scales[activeScale].range, []); 795 | console.log(''+scales[activeScale].range); // requestedRanges unused so far, there's still a bug here 796 | console.log(''+scales[activeScale].requestedRange); 797 | console.log(''+dontRequest); 798 | console.log(''+substraction( [ [xRange[0], xRange[1]] ], dontRequest));*/ 799 | substraction( [[ x0, x1 ]], scales[activeScale].range).forEach(function(r, i) { 800 | req(scales[activeScale].method, { 801 | "output": 'extend', 802 | "history": itemValueType, 803 | "itemids": itemids, 804 | "sortfield": "clock", 805 | "sortorder": "ASC", 806 | "time_from": Math.round(r[0]/1000), 807 | "time_till": Math.round(r[1]/1000) 808 | }, onData, onError); 809 | scales[activeScale].requestedRange.push([r[0], r[1], now]); 810 | }); 811 | lastRequest = new Date(); 812 | } 813 | 814 | // callback for history.get 815 | function onData(response, status) { 816 | // remove doublicates from array 817 | function uniq(a) { 818 | var seen = {}; 819 | return a.filter(function(item) { 820 | return seen.hasOwnProperty(item[0]) ? false : (seen[item[0]] = true); 821 | }); 822 | } 823 | var receivedScale; 824 | if (response.result.length > 0 && response.result[0] !== undefined) { 825 | var cast; 826 | var data_type = items[response.result[0].itemid].data_type; 827 | if (data_type == 'n') 828 | cast = function(v) { return Number(v); } // normal charts 829 | else 830 | cast = function(v) { return String(v); } // gant graphs 831 | if (response.result[0].value !== undefined) { 832 | receivedScale = 0; 833 | response.result.forEach(function(o) { 834 | var i = items[o.itemid].index; 835 | scales[receivedScale].values[i].push([Number(o.clock), cast(o.value)]); 836 | }); 837 | // console.log('history values: ' + response.result.length); 838 | } else if (response.result.length > 0 && response.result[0].value_avg !== undefined) { 839 | receivedScale = 1; 840 | response.result.forEach(function(o) { 841 | var i = items[o.itemid].index; 842 | scales[receivedScale].values[i].push([Number(o.clock), Number(o.value_avg), Number(o.value_min), Number(o.value_max), ]); 843 | }); 844 | // console.log('trend values: ' + response.result.length); 845 | } else return; 846 | var received = [ 847 | new Date (response.result[0].clock*1000), 848 | new Date (response.result[response.result.length-1].clock*1000) 849 | ]; 850 | scales[receivedScale].range = addition(scales[receivedScale].range, [ received ] ); 851 | // TODO: merging the new data can be done in a smarter way. 852 | itemids.forEach(function(item, i) { 853 | scales[receivedScale].values[i] = uniq(scales[receivedScale].values[i].sort(function(a,b) { 854 | return a[0] - b[0]; 855 | })); 856 | }); 857 | // replot all 858 | if (data_type == 'c') 859 | updateGants(); 860 | plotSVG(); 861 | } else { 862 | // no data ? 863 | // console.log('unknown response: '); 864 | // console.log(response); 865 | } 866 | } 867 | 868 | function onError(response) { 869 | if (trendsSupported && activeScale==1 && (response === undefined || response.status==500)) { 870 | d3.select(arg.bindTo) 871 | .append("div") 872 | .attr('class', 'warning') 873 | .html("The zabbix api doesn't support 'trend.get()'.
"+ 874 | "Upgrade to Zabbix 3 or see "+ 875 | "https://support.zabbix.com/browse/ZBXNEXT-1193"); 876 | trendsSupported = false; 877 | } 878 | } 879 | 880 | function updateGants() { 881 | // addGant() adds a layer to every bar to use a minimal number of rows without overlapping 882 | function addGant(a, begin, end, name) { 883 | var layer = -1; 884 | for (i=0; i -1) { 906 | name = v[1].replace(left,''); 907 | begin = true; 908 | } else if (v[1].search(right) > -1) { 909 | name = v[1].replace(right,''); 910 | end = true; 911 | } 912 | if (!gantNames.hasOwnProperty(name)) { 913 | if (begin) gantNames[name] = v[0]; 914 | else if (end) gantNames[name] = v[0]; 915 | else addGant(gant[i], v[0], v[0], name); 916 | } else { 917 | if (begin) { // TODO: this is wrong 918 | addGant(gant[i], v[0], gantNames[name], name); 919 | } else if (end) { 920 | addGant(gant[i], gantNames[name], v[0], name); 921 | } 922 | delete gantNames[name]; 923 | } 924 | }); 925 | }); 926 | gantLayers = layers.length; 927 | } 928 | 929 | // create arrays containing just visible values 930 | // and return maximum on y-axis 931 | function updateVisibleValues(x0, x1) { 932 | var max = Number.NEGATIVE_INFINITY; 933 | var maxIndex = 1; 934 | if (activeScale > 0) maxIndex = 3; 935 | path.forEach(function(p, i) { 936 | // only use values in current viewport 937 | if (scales[activeScale].values[i].length == 0) return 0; 938 | var left = getIndex(scales[activeScale].values[i], x0); 939 | var right = getIndex(scales[activeScale].values[i], x1); 940 | if (left[0] == -1) left = left[1]; else left = left[0]; 941 | if (right[1] == -1) right = right[0]; else right = right[1]; 942 | visibleValues[i] = scales[activeScale].values[i].slice(left,right+1); 943 | // TODO: project next points out of view on y-axis 944 | var ax = visibleValues[i][0][0]; 945 | // var ay = visibleValues[i][0][1]; 946 | // var bx = visibleValues[i][1][0]; 947 | // var by = visibleValues[i][1][1]; 948 | // don't modify visibleValues: it is a reference on the original data! 949 | // if (bx > ax) visibleValues[i][0] = [ x0, ay + (x0-ax)/(bx-ax)*(by-ay) ]; 950 | visibleValues[i].forEach(function(o) { 951 | var b = Number(o[maxIndex]); 952 | if (b > max) max = b; 953 | }); 954 | }); 955 | return max; 956 | } 957 | 958 | // rebuild axis objects 959 | function plotAxis() { 960 | x.range([margin.left, width + margin.left]).domain(xRange); 961 | var x0 = Math.round(xRange[0]/1000) 962 | var x1 = Math.round(xRange[1]/1000) 963 | var cacheRate = []; 964 | cacheRate[0] = Math.round(iLength(intersection(scales[0].range,[[xRange[0],xRange[1]]])) * 100 / iLength([[xRange[0],xRange[1]]])); 965 | cacheRate[1] = Math.round(iLength(intersection(scales[1].range,[[xRange[0],xRange[1]]])) * 100 / iLength([[xRange[0],xRange[1]]])); 966 | if (trendsSupported && zoom_scale < trends_scale) activeScale = 1; 967 | else activeScale = 0; 968 | // during dragging show original data as long as it fills more than 10% of viewport 969 | // else 5% more data in next lower resolution is needed to fall back to it 970 | var cor = 1.05; 971 | if (dragging) cor = 10; 972 | if (activeScale == 0 && cacheRate[0] * cor < cacheRate[1]) activeScale = 1; 973 | if (activeScale == 1 && cacheRate[1] * cor < cacheRate[0]) activeScale = 0; 974 | var max = updateVisibleValues(x0, x1); 975 | yRange = [0,max]; 976 | y.range([height - margin.bottom, margin.top]).domain(yRange).nice(); 977 | 978 | var Range = (xRange[1]-xRange[0]) / 1000; 979 | sizing = []; 980 | tick_sizing.forEach(function(ts) { 981 | if (ts[0] >= Range/width && sizing.length == 0) 982 | sizing = ts; 983 | }); 984 | xAxis 985 | .orient("bottom") 986 | .tickFormat(format) 987 | .tickSize(-height + margin.bottom + margin.top) 988 | .ticks(sizing[1], sizing[2]); 989 | yAxis 990 | .orient("left") 991 | .tickSize(-width+1); 992 | xA .attr("transform", "translate(0," + (height - margin.bottom) + ")") 993 | .call(xAxis) 994 | .selectAll("text") 995 | .style("text-anchor", "end") 996 | .attr("dx", "-0.2em") 997 | .attr("dy", ".15em") 998 | .attr("transform", "rotate(-90)"); 999 | yA.call(yAxis); 1000 | } 1001 | 1002 | function createBands(size) { 1003 | var incr, firstFull; 1004 | var intervals = []; 1005 | var dst = 0; 1006 | var firstOffset = 0; 1007 | if (size == 'm') incr = 60000; 1008 | if (size == 'h') incr = 3600000; 1009 | if (size == 'd' || size == 'M') { 1010 | incr = 86400000; 1011 | firstFull = Math.floor(xRange[0]/incr)*incr; 1012 | var d = new Date(firstFull); 1013 | firstFull = firstFull - d.getHours()*3600000; 1014 | } else { 1015 | firstFull = Math.floor(xRange[0]/incr)*incr; 1016 | } 1017 | if (size == 'd') { 1018 | var time = new Date(firstFull); 1019 | firstOffset = time.getTimezoneOffset(); 1020 | dst = (xRange[1].getTimezoneOffset() - firstOffset); 1021 | } 1022 | if (size != 'M') while (firstFull < xRange[1]) { 1023 | var a = firstFull; 1024 | var b = firstFull + incr; 1025 | if (a < xRange[0]) a = +xRange[0]; 1026 | if (b > xRange[1]) b = +xRange[1]; 1027 | if (a < b) intervals.push([a,b]); 1028 | firstFull = firstFull + incr; 1029 | } else { 1030 | var last = xRange[0]; 1031 | while (firstFull < xRange[1]) { 1032 | firstFull = firstFull + incr; 1033 | var a = new Date(firstFull); 1034 | if (a.getDate() == 1) { 1035 | if (last < a) intervals.push([last,a]); 1036 | last = a; 1037 | } 1038 | } 1039 | if (last < +xRange[1]) intervals.push([last,+xRange[1]]); 1040 | } 1041 | if (dst!=0) { 1042 | return intervals.map(function(o,i) { 1043 | var t0 = new Date(o[0]); 1044 | var t1 = new Date(o[1]); 1045 | var o0 = (t0.getTimezoneOffset() - firstOffset) * 60000; 1046 | var o1 = (t1.getTimezoneOffset() - firstOffset) * 60000; 1047 | return [ o[0]+o0, o[1]+o1 ]; 1048 | }); 1049 | } 1050 | return intervals; 1051 | } 1052 | 1053 | function plotBands(clss, timeInterval, intervals, y, h) { 1054 | function labelDay(d) { 1055 | var a = new Date(d[0]); 1056 | var name = isHoliday(a); 1057 | if (!name) name = days[a.getDay()]; 1058 | var pixels = x(d[1]) - x(d[0]); 1059 | if (pixels > 160) return name + ', ' + a.getDate() + '.' + (a.getMonth()+1) + '.' + (a.getFullYear()); 1060 | if (pixels > 120) return name + ', ' + a.getDate() + '.' + (a.getMonth()+1) + '.'; 1061 | if (pixels > 80) return name; 1062 | if (pixels > 20) return name.substr(0,2); 1063 | if (pixels > 10) return name.substr(0,1); 1064 | else return ''; 1065 | } 1066 | var bandClass = { 1067 | 'm': function(d,i) { 1068 | var a = new Date(d[0]); 1069 | if (a.getMinutes() % 2 == 0) return clss+' minutes_even'; else return clss+' minutes_odd'; 1070 | }, 1071 | 'h': function(d,i) { 1072 | var a = new Date(d[0]); 1073 | if (a.getHours() % 2 == 0) return clss+' hours_even'; else return clss+' hours_odd'; 1074 | }, 1075 | 'd': function(d,i) { 1076 | var a = new Date(d[0]); 1077 | var day = a.getDay(); 1078 | if (isHoliday(a) || day == 0 || day == 6 ) { 1079 | return clss+' weekend'; 1080 | } else { 1081 | if (day%2 == 0) return clss+' workday_even'; 1082 | else return clss+' workday_odd'; 1083 | } 1084 | }, 1085 | 'M': function(d,i) { 1086 | var a = new Date(d[0]); 1087 | if (a.getMonth() % 2 == 0) return clss+' months_even'; 1088 | else return clss+' months_odd'; 1089 | } 1090 | } 1091 | var timeBands = bands.selectAll('.'+clss) 1092 | .data(intervals, function(d) { return (d[0]+','+d[1]); }) 1093 | .attr('x', function(d) { return Math.round(x(d[0])); }) 1094 | .attr('y', y) 1095 | .attr('width', function(d) { return Math.round(x(d[1])-x(d[0])); }) 1096 | .attr('height', h); 1097 | var bandsEnter = timeBands.enter() 1098 | .append("rect") 1099 | .attr('x', function(d) { return Math.round(x(d[0])); }) 1100 | .attr('y', y) 1101 | .attr('width', function(d) { return Math.round(x(d[1])-x(d[0])); }) 1102 | .attr('height', h) 1103 | .attr('class', bandClass[timeInterval]); 1104 | timeBands.exit().remove(); 1105 | if (clss == 'daybands') { 1106 | var timeDesc = bands.selectAll(".days") 1107 | .data(intervals, function(d) { return ( d[0]+','+d[1] ); }) 1108 | .attr('x', function(d) { return Math.round(x(d[0])); }) 1109 | .attr('y', function(d) { return margin.top - 4; }) 1110 | .text(labelDay); 1111 | var descEnter = timeDesc.enter() 1112 | .append("text") 1113 | .attr('x', function(d) { return Math.round(x(d[0])); }) 1114 | .attr('y', function(d) { return margin.top - 4; }) 1115 | .attr('class', 'days') 1116 | .text(labelDay); 1117 | timeDesc.exit().remove(); 1118 | } 1119 | } 1120 | 1121 | function plotTimeBands() { 1122 | var timeInterval = sizing[3]; 1123 | var intervals = createBands(timeInterval); 1124 | plotBands('bands', timeInterval, intervals, margin.top, height - margin.bottom - margin.top); 1125 | if (timeInterval == 'M' || timeInterval == 'Y') intervals = []; 1126 | else if (timeInterval != 'd') intervals = createBands('d'); 1127 | plotBands('daybands', 'd', intervals, 0, margin.top); 1128 | } 1129 | 1130 | function startFollowView() { 1131 | followViewSeconds = Math.floor((xRange[1]-xRange[0])/width); 1132 | setTimeout(followView, followViewSeconds); 1133 | } 1134 | 1135 | function followView() { 1136 | if (!followViewSeconds) return; 1137 | var length = xRange[1] - xRange[0]; 1138 | xRange[1] = new Date(); 1139 | xRange[0] = xRange[1] - length; 1140 | plotSVG(); 1141 | setTimeout(followView, followViewSeconds); 1142 | } 1143 | 1144 | function plotFuture(t) { 1145 | now = new Date(); 1146 | if (now < xRange[0]) now = xRange[0]; 1147 | // plot 'fog of future' if it's in viewport 1148 | // and upate it 1149 | if (x(now) <= width + margin.left) { 1150 | future 1151 | .attr("x", Math.round(x(now))) 1152 | .attr("y", margin.top) 1153 | .attr("width", width + margin.left - Math.round(x(now))) 1154 | .attr("height", height - margin.bottom - margin.top); 1155 | if (now - lastRequest > 10000) startRequests(); 1156 | if (timer > 0 && t == 1 || timer == 0 && t == 0) { 1157 | timer = setTimeout(function() { plotFuture(1); }, 1000); 1158 | } 1159 | } else if (timer > 0) { 1160 | if (!zooming) { 1161 | startFollowView(); 1162 | } 1163 | future 1164 | .attr("x", width + margin.left) 1165 | .attr("width", 0); 1166 | timer = 0; 1167 | } 1168 | } 1169 | 1170 | // reassign an new path description to the path object 1171 | function plotPath() { 1172 | var x0 = Math.round(xRange[0]/1000); 1173 | var x1 = Math.round(xRange[1]/1000); 1174 | // set the data resolution depending on zoom level (zoom_scale) 1175 | // TODO: use real pixels per datum not zoom_scale 1176 | path.forEach(function(p, i) { 1177 | p.attr("d", scales[activeScale].valueline(visibleValues[i])); 1178 | if (activeScale > 0) { 1179 | var min = visibleValues[i].map(function(arr) { 1180 | return [ arr[0], arr[2] ]; 1181 | }); 1182 | var max = visibleValues[i].map(function(arr) { 1183 | return [ arr[0], arr[3] ]; 1184 | }); 1185 | path_minmax[i].attr("d", scales[1].valueline(min.concat(max.reverse()))); 1186 | } else { 1187 | path_minmax[i].attr("d", scales[0].valueline([])); 1188 | } 1189 | }); 1190 | hideROverlap 1191 | .attr('fill', '#ffffff') 1192 | .attr('x', width + margin.left + 1) 1193 | .attr('y', 0) 1194 | .attr('width', margin.right-1) 1195 | .attr('height', height - margin.top); 1196 | } 1197 | 1198 | function plotGant() { 1199 | function setGant(selection) { 1200 | selection 1201 | .attr("x", function(d,i) { 1202 | return Math.round(x(d[0]*1000)); 1203 | }) 1204 | .attr("y", function(d,i) { 1205 | return margin.top + gantHeight*d[3] + 4; 1206 | }) 1207 | .attr("width", function(d,i) { 1208 | var width = Math.ceil(x(d[1]*1000)-x(d[0]*1000)); 1209 | if (width<0) return 0; 1210 | return width; 1211 | }) 1212 | .attr("height", function(d,i) { 1213 | return gantHeight - 2; 1214 | }) 1215 | .attr("opacity", function(d,i) { 1216 | var width = x(d[1]*1000)-x(d[0]*1000); 1217 | if (width >= 1 || width < 0) return 1; 1218 | return width; 1219 | }); 1220 | } 1221 | var gantHeight = Math.round((height - margin.top - margin.bottom - 8)/gantLayers); 1222 | if (gantHeight > 20) gantHeight = 20; 1223 | var x0 = Math.round(xRange[0]/1000); 1224 | var x1 = Math.round(xRange[1]/1000); 1225 | // TODO: only one item used for gants 1226 | var visibleGant = []; 1227 | gant[0].forEach(function(g,i) { 1228 | var g0 = g[0]; 1229 | var g1 = g[1]; 1230 | if (g[1] < x0) return; 1231 | if (g[0] > x1) return; 1232 | if (g[0] < x0) g0 = x0; 1233 | if (g[1] > x1) g1 = x1; 1234 | visibleGant.push([g0, g1, g[2], g[3]]); 1235 | }); 1236 | var gC = gantChart.selectAll("rect").data(visibleGant, function(o,i) { 1237 | return o[2]+o[0]; 1238 | }) 1239 | .call(setGant) 1240 | gC .enter() 1241 | .append("rect") 1242 | .attr("class", "gant") 1243 | .attr("fill", function(d,i) { 1244 | return getColor(d[2]); 1245 | }) 1246 | .call(setGant) 1247 | .on('mouseover', function(d,i) { 1248 | gantInfo = d; 1249 | }) 1250 | .on('mouseout', function(d,i) { 1251 | gantInfo = false; 1252 | clearInfo(); 1253 | }); 1254 | gC .exit().remove(); 1255 | } 1256 | 1257 | function plotLegend() { 1258 | if (hideLegend) return; 1259 | var text = []; 1260 | //TODO: items[index].name + items[index].key 1261 | for (var index in items) { 1262 | text.push(items[index].name); 1263 | console.log(items[index].name + ':' + items[index].key); 1264 | } 1265 | var lE = legend.selectAll(".legendtext").data(text, function(o,i) { 1266 | return i; 1267 | }) 1268 | .attr("x", function() { 1269 | return margin.left + width - 8; 1270 | }) 1271 | .attr("y", function(d,i) { 1272 | return margin.top + 20 + 20*i; 1273 | }); 1274 | lE .enter() 1275 | .append("text") 1276 | .style("text-anchor", "end") 1277 | .style("fill", function(d,i) { 1278 | return colors[i]; 1279 | }) 1280 | .attr("class", "legendtext") 1281 | .attr("x", function() { 1282 | return margin.left + width - 8; 1283 | }) 1284 | .attr("y", function(d,i) { 1285 | return margin.top + 20 + 20*i; 1286 | }) 1287 | .text(function(d) { 1288 | return d; 1289 | }); 1290 | lE.exit().remove(); 1291 | var b = { 'x': 0, 'y': 0, 'width': 0, 'height': 0 }; 1292 | lE.each(function() { 1293 | var bb = this.getBBox(); 1294 | if (b.x == 0) { 1295 | b.x = bb.x; 1296 | b.y = bb.y; 1297 | b.height = bb.height; 1298 | } else { 1299 | b.height = b.height + 20; 1300 | } 1301 | if (bb.x < b.x) b.x = bb.x; 1302 | if (b.width < bb.width) b.width = bb.width; 1303 | }); 1304 | legendBox 1305 | .attr("x", b.x - 2) 1306 | .attr("y", b.y - 2) 1307 | .attr("width", b.width+4) 1308 | .attr("height", b.height+4); 1309 | } 1310 | 1311 | // replot tooltip 1312 | function plotInfo() { 1313 | if (zooming) return; 1314 | if (this.style === undefined) return; // hack: if this is not the svg: skip 1315 | mx = d3.mouse(this)[0]; 1316 | my = d3.mouse(this)[1]; 1317 | mv = Math.round(+x.invert(mx)/1000); 1318 | var selected = []; 1319 | var textGenerator; 1320 | if (mouseLocksOnNearest) { 1321 | visibleValues.forEach(function(v, i) { // TODO: this is still insane for much data 1322 | selected[i] = [Number.POSITIVE_INFINITY]; 1323 | v.map(function(o) { 1324 | return [ Math.abs(+x(o[0]*1000)-mx) + Math.abs(+y(o[1])-my), o[0], o[1], i ]; 1325 | }).forEach(function(o) { 1326 | if (o[0] < selected[i][0]) selected[i] = o; 1327 | }); 1328 | selected[i] = [ selected[i][1], selected[i][2] ]; 1329 | }); 1330 | } else { 1331 | visibleValues.forEach(function(v, i) { 1332 | var index = getIndex(v, mv); 1333 | index = index[2]; 1334 | selected[i] = [ v[index][0], v[index][1] ]; 1335 | }); 1336 | } 1337 | var infoCircle = svg.select(".info").selectAll("circle") 1338 | .data(selected, function(s,i) { return i; }) 1339 | .attr("cx", function(s) { return x(s[0]*1000); }) 1340 | .attr("cy", function(s) { return y(s[1]); }); 1341 | infoCircle.enter() 1342 | .insert("circle") 1343 | .attr("r", 5) 1344 | .attr("cx", function(s) { return x(s[0]*1000); }) 1345 | .attr("cy", function(s) { return y(s[1]); }) 1346 | .style("fill", function(s,i) { return colors[i]; }); 1347 | var infoLine = svg.select(".info").selectAll("line") 1348 | .data(selected, function(s,i) { return i; }) 1349 | .attr("x1", function(s,i) { return x(s[0]*1000); }) 1350 | .attr("x2", function(s,i) { return x(s[0]*1000); }) 1351 | infoLine.enter() 1352 | .insert("line") 1353 | .attr("x1", function(s,i) { return x(s[0]*1000); }) 1354 | .attr("y1", margin.top) 1355 | .attr("x2", function(s,i) { return x(s[0]*1000); }) 1356 | .attr("y2", height-margin.top); 1357 | // define text callback for numeric diagram and gant 1358 | // TODO: if you have both types mixed this won't work 1359 | if (gantInfo) { 1360 | selected.push(gantInfo); 1361 | textGenerator = function(s,i) { 1362 | var date = new Date(s[0]*1000); 1363 | return s[2] + ': ' + date + '(' + Math.ceil((s[1]-s[0])/60) + 'min)'; 1364 | } 1365 | } else { 1366 | textGenerator = function(s,i) { 1367 | var date = new Date(Math.round(s[0]/60)*60000); 1368 | return format(date) + ', ' + s[1]; 1369 | } 1370 | } 1371 | var infoText = svg.select(".info").selectAll("text") 1372 | .data(selected, function(s,i) { return i; }) 1373 | .attr("x", mx+14) 1374 | .attr("y", function(s,i) { return my+12 + 12*i; }) 1375 | .text(textGenerator); 1376 | infoText.enter() 1377 | .insert("text") 1378 | .attr("x", mx+14) 1379 | .attr("y", function(s,i) { return my+12 + 12*i; }) 1380 | .style("fill", function(s,i) { return colors[i]; }) 1381 | .text(textGenerator); 1382 | var b = { 'x': 0, 'y': 0, 'width': 0, 'height': 0 }; 1383 | infoText.each(function() { 1384 | var bb = this.getBBox(); 1385 | if (b.x == 0) { 1386 | b.x = bb.x; 1387 | b.y = bb.y; 1388 | b.height = bb.height; 1389 | } else { 1390 | b.height = b.height + 12; 1391 | } 1392 | if (b.width < bb.width) b.width = bb.width; 1393 | }); 1394 | infoBox.attr('x', b.x).attr('y', b.y) 1395 | .attr('width', b.width).attr('height', b.height) 1396 | .style('display', 'block'); 1397 | } 1398 | 1399 | function clearInfo() { 1400 | svg.select(".info").selectAll("circle").remove(); 1401 | svg.select(".info").selectAll("text").remove(); 1402 | svg.select(".info").selectAll("line").remove(); 1403 | svg.select(".info").selectAll("rect").style('display', 'none'); 1404 | } 1405 | 1406 | // replot all function 1407 | function plotSVG() { 1408 | sizex = $(arg.bindTo).width(); 1409 | sizey = $(arg.bindTo).height(); 1410 | svg .attr("width", sizex) 1411 | .attr("height", sizey); 1412 | width = sizex - margin.left - margin.right, 1413 | height = sizey - margin.top - margin.bottom; 1414 | plotAxis(); 1415 | plotFuture(0); 1416 | plotTimeBands(); 1417 | // don't plot graph without values 1418 | if (scales[0].values[0] !== undefined) { 1419 | plotPath(); 1420 | plotGant(); 1421 | plotLegend(); // TODO: plot only once 1422 | plotInfo(); 1423 | } 1424 | var now = new Date(); 1425 | if (now - lastRequest > 10000) startRequests() 1426 | } 1427 | 1428 | // rearrange xaxis during dragging (panning done here) 1429 | function dragged(d) { 1430 | var move = +x.invert(d3.event.x) - x.invert(d3.event.x - d3.event.dx); 1431 | xRange = [ 1432 | new Date(+xRange[0] - move), 1433 | new Date(+xRange[1] - move) 1434 | ]; 1435 | zooming = true; 1436 | followViewSeconds = false; 1437 | plotAxis(); 1438 | zooming = false; 1439 | } 1440 | // zoom (without panning) 1441 | function zoomed() { 1442 | var mx = d3.mouse(this); 1443 | var mouse_time = x.invert(mx[0]) 1444 | zoom_scale = d3.event.scale; 1445 | var add = 86400000 / zoom_scale; 1446 | xRange = [ 1447 | new Date(+mouse_time - add * (mx[0] - margin.left) / width ), 1448 | new Date(+mouse_time + add * (width + margin.left - mx[0]) / width ) 1449 | ]; 1450 | zooming = true; 1451 | followViewSeconds = false; 1452 | plotSVG(); 1453 | zooming = false; 1454 | } 1455 | 1456 | function last(minutes) { 1457 | var t1 = new Date(); 1458 | var t2 = new Date(); 1459 | t1.setMinutes(t1.getMinutes() - minutes * 60); 1460 | return [ t1, t2 ]; 1461 | } 1462 | 1463 | function today() { 1464 | var now = new Date(); 1465 | var xRange = [ 1466 | new Date(now.getFullYear(), now.getMonth(), now.getDate()), 1467 | new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1) 1468 | ]; 1469 | return xRange; 1470 | } 1471 | } 1472 | 1473 | // 1474 | // class itemGauge 1475 | // 1476 | 1477 | function itemGauge(arg) { 1478 | var bindToId = arg.bindToId; 1479 | var itemid = typeof arg.itemid !== 'undefined' ? arg.itemid : 0; 1480 | var refresh = 30000; 1481 | var size = typeof arg.size !== 'undefined' ? arg.size : 120; 1482 | var config = { size: size }; 1483 | 1484 | if (typeof arg.label !== 'undefined') config.label = arg.label; 1485 | if (typeof arg.min !== 'undefined') config.min = arg.min; 1486 | if (typeof arg.max !== 'undefined') config.max = arg.max; 1487 | if (typeof arg.minorTicks !== 'undefined') config.minorTicks = arg.minorTicks; 1488 | if (typeof arg.greenZones !== 'undefined') config.greenZones = arg.greenZones; 1489 | if (typeof arg.yellowZones !== 'undefined') config.yellowZones = arg.yellowZones; 1490 | if (typeof arg.redZones !== 'undefined') config.redZones = arg.redZones; 1491 | 1492 | start_.push(startRequests); 1493 | 1494 | var method = 'item.get'; 1495 | 1496 | var params = { 1497 | 'itemids': itemid, 1498 | 'output': [ 'itemid', 'lastvalue', 'delay'], 1499 | 'limit': 1 1500 | }; 1501 | 1502 | function startRequests() { 1503 | req(method, params, successMethod, errorMethod); 1504 | setTimeout(startRequests, refresh); 1505 | } 1506 | 1507 | function errorMethod() { 1508 | console.log('itemGauge request failed'); 1509 | d3.select (bindToId).selectAll ("div.alert").style ('opacity', '0.5'); 1510 | } 1511 | 1512 | function successMethod(response, status) { 1513 | var result = response.result[0]; 1514 | gauge.redraw(result.lastvalue); 1515 | refresh = result.delay == 0 ? refresh : 1000 * result.delay; 1516 | } 1517 | 1518 | var gauge = new Gauge(bindToId, config); 1519 | gauge.render(); 1520 | } 1521 | -------------------------------------------------------------------------------- /lib/gauge.js: -------------------------------------------------------------------------------- 1 | function Gauge(placeholderName, configuration) 2 | { 3 | this.placeholderName = placeholderName; 4 | 5 | var self = this; // for internal d3 functions 6 | 7 | this.configure = function(configuration) 8 | { 9 | this.config = configuration; 10 | 11 | this.config.size = this.config.size * 0.9; 12 | 13 | this.config.raduis = this.config.size * 0.97 / 2; 14 | this.config.cx = this.config.size / 2; 15 | this.config.cy = this.config.size / 2; 16 | 17 | this.config.min = undefined != configuration.min ? configuration.min : 0; 18 | this.config.max = undefined != configuration.max ? configuration.max : 100; 19 | this.config.range = this.config.max - this.config.min; 20 | 21 | this.config.majorTicks = configuration.majorTicks || 5; 22 | this.config.minorTicks = configuration.minorTicks || 2; 23 | 24 | this.config.greenColor = configuration.greenColor || "#109618"; 25 | this.config.yellowColor = configuration.yellowColor || "#FF9900"; 26 | this.config.redColor = configuration.redColor || "#DC3912"; 27 | 28 | this.config.transitionDuration = configuration.transitionDuration || 500; 29 | } 30 | 31 | this.render = function() 32 | { 33 | this.body = d3.select(this.placeholderName) 34 | .append("svg:svg") 35 | .attr("class", "gauge") 36 | .attr("width", this.config.size) 37 | .attr("height", this.config.size); 38 | 39 | this.body.append("svg:circle") 40 | .attr("cx", this.config.cx) 41 | .attr("cy", this.config.cy) 42 | .attr("r", this.config.raduis) 43 | .style("fill", "#ccc") 44 | .style("stroke", "#000") 45 | .style("stroke-width", "0.5px"); 46 | 47 | this.body.append("svg:circle") 48 | .attr("cx", this.config.cx) 49 | .attr("cy", this.config.cy) 50 | .attr("r", 0.9 * this.config.raduis) 51 | .style("fill", "#fff") 52 | .style("stroke", "#e0e0e0") 53 | .style("stroke-width", "2px"); 54 | 55 | for (var index in this.config.greenZones) 56 | { 57 | this.drawBand(this.config.greenZones[index].from, this.config.greenZones[index].to, self.config.greenColor); 58 | } 59 | 60 | for (var index in this.config.yellowZones) 61 | { 62 | this.drawBand(this.config.yellowZones[index].from, this.config.yellowZones[index].to, self.config.yellowColor); 63 | } 64 | 65 | for (var index in this.config.redZones) 66 | { 67 | this.drawBand(this.config.redZones[index].from, this.config.redZones[index].to, self.config.redColor); 68 | } 69 | 70 | if (undefined != this.config.label) 71 | { 72 | var fontSize = Math.round(this.config.size / 9); 73 | this.body.append("svg:text") 74 | .attr("x", this.config.cx) 75 | .attr("y", this.config.cy / 2 + fontSize / 2) 76 | .attr("dy", fontSize / 2) 77 | .attr("text-anchor", "middle") 78 | .text(this.config.label) 79 | .style("font-size", fontSize + "px") 80 | .style("fill", "#333") 81 | .style("stroke-width", "0px"); 82 | } 83 | 84 | var fontSize = Math.round(this.config.size / 16); 85 | var majorDelta = this.config.range / (this.config.majorTicks - 1); 86 | for (var major = this.config.min; major <= this.config.max; major += majorDelta) 87 | { 88 | var minorDelta = majorDelta / this.config.minorTicks; 89 | for (var minor = major + minorDelta; minor < Math.min(major + majorDelta, this.config.max); minor += minorDelta) 90 | { 91 | var point1 = this.valueToPoint(minor, 0.75); 92 | var point2 = this.valueToPoint(minor, 0.85); 93 | 94 | this.body.append("svg:line") 95 | .attr("x1", point1.x) 96 | .attr("y1", point1.y) 97 | .attr("x2", point2.x) 98 | .attr("y2", point2.y) 99 | .style("stroke", "#666") 100 | .style("stroke-width", "1px"); 101 | } 102 | 103 | var point1 = this.valueToPoint(major, 0.7); 104 | var point2 = this.valueToPoint(major, 0.85); 105 | 106 | this.body.append("svg:line") 107 | .attr("x1", point1.x) 108 | .attr("y1", point1.y) 109 | .attr("x2", point2.x) 110 | .attr("y2", point2.y) 111 | .style("stroke", "#333") 112 | .style("stroke-width", "2px"); 113 | 114 | if (major == this.config.min || major == this.config.max) 115 | { 116 | var point = this.valueToPoint(major, 0.63); 117 | 118 | this.body.append("svg:text") 119 | .attr("x", point.x) 120 | .attr("y", point.y) 121 | .attr("dy", fontSize / 3) 122 | .attr("text-anchor", major == this.config.min ? "start" : "end") 123 | .text(major) 124 | .style("font-size", fontSize + "px") 125 | .style("fill", "#333") 126 | .style("stroke-width", "0px"); 127 | } 128 | } 129 | 130 | var pointerContainer = this.body.append("svg:g").attr("class", "pointerContainer"); 131 | 132 | var midValue = (this.config.min + this.config.max) / 2; 133 | 134 | var pointerPath = this.buildPointerPath(midValue); 135 | 136 | var pointerLine = d3.svg.line() 137 | .x(function(d) { return d.x }) 138 | .y(function(d) { return d.y }) 139 | .interpolate("basis"); 140 | 141 | pointerContainer.selectAll("path") 142 | .data([pointerPath]) 143 | .enter() 144 | .append("svg:path") 145 | .attr("d", pointerLine) 146 | .style("fill", "#dc3912") 147 | .style("stroke", "#c63310") 148 | .style("fill-opacity", 0.7) 149 | 150 | pointerContainer.append("svg:circle") 151 | .attr("cx", this.config.cx) 152 | .attr("cy", this.config.cy) 153 | .attr("r", 0.12 * this.config.raduis) 154 | .style("fill", "#4684EE") 155 | .style("stroke", "#666") 156 | .style("opacity", 1); 157 | 158 | var fontSize = Math.round(this.config.size / 10); 159 | pointerContainer.selectAll("text") 160 | .data([midValue]) 161 | .enter() 162 | .append("svg:text") 163 | .attr("x", this.config.cx) 164 | .attr("y", this.config.size - this.config.cy / 4 - fontSize) 165 | .attr("dy", fontSize / 2) 166 | .attr("text-anchor", "middle") 167 | .style("font-size", fontSize + "px") 168 | .style("fill", "#000") 169 | .style("stroke-width", "0px"); 170 | 171 | this.redraw(this.config.min, 0); 172 | } 173 | 174 | this.buildPointerPath = function(value) 175 | { 176 | var delta = this.config.range / 13; 177 | 178 | var head = valueToPoint(value, 0.85); 179 | var head1 = valueToPoint(value - delta, 0.12); 180 | var head2 = valueToPoint(value + delta, 0.12); 181 | 182 | var tailValue = value - (this.config.range * (1/(270/360)) / 2); 183 | var tail = valueToPoint(tailValue, 0.28); 184 | var tail1 = valueToPoint(tailValue - delta, 0.12); 185 | var tail2 = valueToPoint(tailValue + delta, 0.12); 186 | 187 | return [head, head1, tail2, tail, tail1, head2, head]; 188 | 189 | function valueToPoint(value, factor) 190 | { 191 | var point = self.valueToPoint(value, factor); 192 | point.x -= self.config.cx; 193 | point.y -= self.config.cy; 194 | return point; 195 | } 196 | } 197 | 198 | this.drawBand = function(start, end, color) 199 | { 200 | if (0 >= end - start) return; 201 | 202 | this.body.append("svg:path") 203 | .style("fill", color) 204 | .attr("d", d3.svg.arc() 205 | .startAngle(this.valueToRadians(start)) 206 | .endAngle(this.valueToRadians(end)) 207 | .innerRadius(0.65 * this.config.raduis) 208 | .outerRadius(0.85 * this.config.raduis)) 209 | .attr("transform", function() { return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(270)" }); 210 | } 211 | 212 | this.redraw = function(value, transitionDuration) 213 | { 214 | var pointerContainer = this.body.select(".pointerContainer"); 215 | 216 | pointerContainer.selectAll("text").text(Math.round(value)); 217 | 218 | var pointer = pointerContainer.selectAll("path"); 219 | pointer.transition() 220 | .duration(undefined != transitionDuration ? transitionDuration : this.config.transitionDuration) 221 | //.delay(0) 222 | //.ease("linear") 223 | //.attr("transform", function(d) 224 | .attrTween("transform", function() 225 | { 226 | var pointerValue = value; 227 | if (value > self.config.max) pointerValue = self.config.max + 0.02*self.config.range; 228 | else if (value < self.config.min) pointerValue = self.config.min - 0.02*self.config.range; 229 | var targetRotation = (self.valueToDegrees(pointerValue) - 90); 230 | var currentRotation = self._currentRotation || targetRotation; 231 | self._currentRotation = targetRotation; 232 | 233 | return function(step) 234 | { 235 | var rotation = currentRotation + (targetRotation-currentRotation)*step; 236 | return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(" + rotation + ")"; 237 | } 238 | }); 239 | } 240 | 241 | this.valueToDegrees = function(value) 242 | { 243 | // thanks @closealert 244 | //return value / this.config.range * 270 - 45; 245 | return value / this.config.range * 270 - (this.config.min / this.config.range * 270 + 45); 246 | } 247 | 248 | this.valueToRadians = function(value) 249 | { 250 | return this.valueToDegrees(value) * Math.PI / 180; 251 | } 252 | 253 | this.valueToPoint = function(value, factor) 254 | { 255 | return { x: this.config.cx - this.config.raduis * factor * Math.cos(this.valueToRadians(value)), 256 | y: this.config.cy - this.config.raduis * factor * Math.sin(this.valueToRadians(value)) }; 257 | } 258 | 259 | // initialization 260 | this.configure(configuration); 261 | } 262 | -------------------------------------------------------------------------------- /lib/jquery-2.1.3.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v2.1.3 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ 2 | !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=hb(),z=hb(),A=hb(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},eb=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fb){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function gb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+rb(o[l]);w=ab.test(a)&&pb(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function hb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ib(a){return a[u]=!0,a}function jb(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function kb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function lb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function nb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function ob(a){return ib(function(b){return b=+b,ib(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pb(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=gb.support={},f=gb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=gb.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",eb,!1):e.attachEvent&&e.attachEvent("onunload",eb)),p=!f(g),c.attributes=jb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=jb(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=jb(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(jb(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),jb(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&jb(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return lb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?lb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},gb.matches=function(a,b){return gb(a,null,null,b)},gb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return gb(b,n,null,[a]).length>0},gb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},gb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},gb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},gb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=gb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=gb.selectors={cacheLength:50,createPseudo:ib,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||gb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&gb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=gb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||gb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ib(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ib(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ib(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ib(function(a){return function(b){return gb(a,b).length>0}}),contains:ib(function(a){return a=a.replace(cb,db),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ib(function(a){return W.test(a||"")||gb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:ob(function(){return[0]}),last:ob(function(a,b){return[b-1]}),eq:ob(function(a,b,c){return[0>c?c+b:c]}),even:ob(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:ob(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:ob(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:ob(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function tb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ub(a,b,c){for(var d=0,e=b.length;e>d;d++)gb(a,b[d],c);return c}function vb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wb(a,b,c,d,e,f){return d&&!d[u]&&(d=wb(d)),e&&!e[u]&&(e=wb(e,f)),ib(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ub(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:vb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=vb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=vb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sb(function(a){return a===b},h,!0),l=sb(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sb(tb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wb(i>1&&tb(m),i>1&&rb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xb(a.slice(i,e)),f>e&&xb(a=a.slice(e)),f>e&&rb(a))}m.push(c)}return tb(m)}function yb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=vb(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&gb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ib(f):f}return h=gb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,yb(e,d)),f.selector=a}return f},i=gb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&pb(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&rb(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&pb(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=jb(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),jb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||kb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&jb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||kb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),jb(function(a){return null==a.getAttribute("disabled")})||kb(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),gb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c) 3 | },removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("