From 08ee35214b01c7dc14ade66a3a0ca74bf91c67cf Mon Sep 17 00:00:00 2001 From: jepefe Date: Mon, 20 Jan 2014 00:12:22 +0000 Subject: [PATCH] Revert "Merge branch 'master' into instanttim" This reverts commit e33c0e145e72c57db6da47d5a8d7be77986f6228, reversing changes made to a01550ef345af29c4a112b9f8beaebb6921dbbfb. --- .../flexnetdc.py | 0 .../fmmx.py | 0 .../fxinv.py | 0 .../mate3.py | 0 .../monitomate.py | 0 .../monitormate.sh | 0 .../config/config.sample.php | 17 +- {web => Web Server/config}/database.sql | 197 +- Web Server/config/labels.sample.js | 22 + .../ui-bg_diagonals-thick_18_b81900_40x40.png | Bin .../ui-bg_diagonals-thick_20_666666_40x40.png | Bin .../images/ui-bg_flat_10_000000_40x100.png | Bin .../images/ui-bg_glass_100_f6f6f6_1x400.png | Bin .../images/ui-bg_glass_100_fdf5ce_1x400.png | Bin .../images/ui-bg_glass_65_ffffff_1x400.png | Bin .../ui-bg_gloss-wave_35_f6a828_500x100.png | Bin .../ui-bg_highlight-soft_100_eeeeee_1x100.png | Bin .../ui-bg_highlight-soft_75_ffe45c_1x100.png | Bin .../css/images/ui-icons_222222_256x240.png | Bin .../css/images/ui-icons_228ef1_256x240.png | Bin .../css/images/ui-icons_ef8c08_256x240.png | Bin .../css/images/ui-icons_ffd27a_256x240.png | Bin .../css/images/ui-icons_ffffff_256x240.png | Bin {web => Web Server}/css/jquery-ui.css | 0 Web Server/css/monitormate.css | 91 + .../jquery.flot.threshold.multiple.js | 33 +- web/flot/API.txt => Web Server/flot/API.md | 980 +- Web Server/flot/CONTRIBUTING.md | 98 + web/flot/FAQ.txt => Web Server/flot/FAQ.md | 27 +- {web => Web Server}/flot/LICENSE.txt | 2 +- {web => Web Server}/flot/Makefile | 3 + Web Server/flot/NEWS.md | 978 ++ .../PLUGINS.txt => Web Server/flot/PLUGINS.md | 90 +- Web Server/flot/README.md | 110 + {web => Web Server}/flot/excanvas.js | 0 {web => Web Server}/flot/excanvas.min.js | 0 .../flot/jquery.colorhelpers.js | 5 +- Web Server/flot/jquery.colorhelpers.min.js | 1 + Web Server/flot/jquery.flot.canvas.js | 345 + Web Server/flot/jquery.flot.canvas.min.js | 1 + .../flot/jquery.flot.categories.js | 67 +- Web Server/flot/jquery.flot.categories.min.js | 1 + .../flot/jquery.flot.crosshair.js | 93 +- Web Server/flot/jquery.flot.crosshair.min.js | 1 + Web Server/flot/jquery.flot.errorbars.js | 353 + Web Server/flot/jquery.flot.errorbars.min.js | 1 + Web Server/flot/jquery.flot.fillbetween.js | 226 + .../flot/jquery.flot.fillbetween.min.js | 1 + {web => Web Server}/flot/jquery.flot.image.js | 79 +- Web Server/flot/jquery.flot.image.min.js | 1 + {web => Web Server}/flot/jquery.flot.js | 2020 ++-- Web Server/flot/jquery.flot.min.js | 2 + .../flot/jquery.flot.navigate.js | 184 +- Web Server/flot/jquery.flot.navigate.min.js | 1 + Web Server/flot/jquery.flot.pie.js | 817 ++ Web Server/flot/jquery.flot.pie.min.js | 1 + Web Server/flot/jquery.flot.resize.js | 60 + Web Server/flot/jquery.flot.resize.min.js | 1 + .../flot/jquery.flot.selection.js | 136 +- Web Server/flot/jquery.flot.selection.min.js | 1 + {web => Web Server}/flot/jquery.flot.stack.js | 72 +- Web Server/flot/jquery.flot.stack.min.js | 1 + .../flot/jquery.flot.symbol.js | 21 +- Web Server/flot/jquery.flot.symbol.min.js | 1 + .../flot/jquery.flot.threshold.js | 82 +- Web Server/flot/jquery.flot.threshold.min.js | 1 + Web Server/flot/jquery.flot.time.js | 431 + Web Server/flot/jquery.flot.time.min.js | 1 + Web Server/flot/jquery.js | 9472 +++++++++++++++++ Web Server/flot/jquery.min.js | 5 + Web Server/getstatus.php | 181 + Web Server/images/favicon16.png | Bin 0 -> 19923 bytes {web => Web Server}/js/jquery-1.7.1.js | 0 {web => Web Server}/js/jquery-ui-1.8.17.js | 0 Web Server/js/monitormate.js | 1605 +++ Web Server/monitormate.html | 45 + Web Server/regstatus.php | 303 + web/css/monitorsolar.css | 247 - web/flot/.gitignore | 2 - web/flot/NEWS.txt | 557 - web/flot/README.txt | 90 - web/flot/examples/ajax.html | 143 - web/flot/examples/annotating.html | 75 - web/flot/examples/arrow-down.gif | Bin 916 -> 0 bytes web/flot/examples/arrow-left.gif | Bin 891 -> 0 bytes web/flot/examples/arrow-right.gif | Bin 897 -> 0 bytes web/flot/examples/arrow-up.gif | Bin 916 -> 0 bytes web/flot/examples/basic.html | 38 - web/flot/examples/categories.html | 40 - web/flot/examples/data-eu-gdp-growth-1.json | 4 - web/flot/examples/data-eu-gdp-growth-2.json | 4 - web/flot/examples/data-eu-gdp-growth-3.json | 4 - web/flot/examples/data-eu-gdp-growth-4.json | 4 - web/flot/examples/data-eu-gdp-growth-5.json | 4 - web/flot/examples/data-eu-gdp-growth.json | 4 - web/flot/examples/data-japan-gdp-growth.json | 4 - web/flot/examples/data-usa-gdp-growth.json | 4 - web/flot/examples/graph-types.html | 75 - web/flot/examples/hs-2004-27-a-large_web.jpg | Bin 34489 -> 0 bytes web/flot/examples/image.html | 45 - web/flot/examples/index.html | 44 - web/flot/examples/interacting-axes.html | 79 - web/flot/examples/interacting.html | 98 - web/flot/examples/layout.css | 6 - web/flot/examples/multiple-axes.html | 60 - web/flot/examples/navigate.html | 118 - web/flot/examples/percentiles.html | 57 - web/flot/examples/pie.html | 756 -- web/flot/examples/realtime.html | 83 - web/flot/examples/resize.html | 61 - web/flot/examples/selection.html | 114 - web/flot/examples/setting-options.html | 61 - web/flot/examples/stacking.html | 77 - web/flot/examples/symbols.html | 49 - web/flot/examples/thresholding.html | 54 - web/flot/examples/time.html | 71 - web/flot/examples/tracking.html | 95 - web/flot/examples/turning-series.html | 98 - web/flot/examples/visitors.html | 90 - web/flot/examples/zooming.html | 98 - web/flot/jquery.flot.fillbetween.js | 183 - web/flot/jquery.flot.pie.js | 751 -- web/flot/jquery.flot.resize.js | 60 - web/flot/jquery.js | 8316 --------------- web/getstatus.php | 132 - web/js/monitormate3.js | 1607 --- web/monitormate3.html | 89 - web/regstatus.php | 224 - 128 files changed, 17638 insertions(+), 16504 deletions(-) rename {datastream scripts => Data Stream Host}/flexnetdc.py (100%) rename {datastream scripts => Data Stream Host}/fmmx.py (100%) rename {datastream scripts => Data Stream Host}/fxinv.py (100%) rename {datastream scripts => Data Stream Host}/mate3.py (100%) rename {datastream scripts => Data Stream Host}/monitomate.py (100%) rename {datastream scripts => Data Stream Host}/monitormate.sh (100%) rename web/monitormate3cfg.php => Web Server/config/config.sample.php (70%) rename {web => Web Server/config}/database.sql (84%) mode change 100755 => 100644 create mode 100644 Web Server/config/labels.sample.js rename {web => Web Server}/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png (100%) rename {web => Web Server}/css/images/ui-bg_diagonals-thick_20_666666_40x40.png (100%) rename {web => Web Server}/css/images/ui-bg_flat_10_000000_40x100.png (100%) rename {web => Web Server}/css/images/ui-bg_glass_100_f6f6f6_1x400.png (100%) rename {web => Web Server}/css/images/ui-bg_glass_100_fdf5ce_1x400.png (100%) rename {web => Web Server}/css/images/ui-bg_glass_65_ffffff_1x400.png (100%) rename {web => Web Server}/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png (100%) rename {web => Web Server}/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png (100%) rename {web => Web Server}/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png (100%) rename {web => Web Server}/css/images/ui-icons_222222_256x240.png (100%) rename {web => Web Server}/css/images/ui-icons_228ef1_256x240.png (100%) rename {web => Web Server}/css/images/ui-icons_ef8c08_256x240.png (100%) rename {web => Web Server}/css/images/ui-icons_ffd27a_256x240.png (100%) rename {web => Web Server}/css/images/ui-icons_ffffff_256x240.png (100%) rename {web => Web Server}/css/jquery-ui.css (100%) create mode 100644 Web Server/css/monitormate.css rename {web/flot => Web Server/flot-plugins}/jquery.flot.threshold.multiple.js (98%) mode change 100644 => 100755 rename web/flot/API.txt => Web Server/flot/API.md (61%) create mode 100644 Web Server/flot/CONTRIBUTING.md rename web/flot/FAQ.txt => Web Server/flot/FAQ.md (76%) rename {web => Web Server}/flot/LICENSE.txt (95%) rename {web => Web Server}/flot/Makefile (82%) create mode 100644 Web Server/flot/NEWS.md rename web/flot/PLUGINS.txt => Web Server/flot/PLUGINS.md (74%) create mode 100644 Web Server/flot/README.md rename {web => Web Server}/flot/excanvas.js (100%) rename {web => Web Server}/flot/excanvas.min.js (100%) rename {web => Web Server}/flot/jquery.colorhelpers.js (97%) create mode 100644 Web Server/flot/jquery.colorhelpers.min.js create mode 100644 Web Server/flot/jquery.flot.canvas.js create mode 100644 Web Server/flot/jquery.flot.canvas.min.js rename {web => Web Server}/flot/jquery.flot.categories.js (76%) create mode 100644 Web Server/flot/jquery.flot.categories.min.js rename {web => Web Server}/flot/jquery.flot.crosshair.js (65%) create mode 100644 Web Server/flot/jquery.flot.crosshair.min.js create mode 100644 Web Server/flot/jquery.flot.errorbars.js create mode 100644 Web Server/flot/jquery.flot.errorbars.min.js create mode 100644 Web Server/flot/jquery.flot.fillbetween.js create mode 100644 Web Server/flot/jquery.flot.fillbetween.min.js rename {web => Web Server}/flot/jquery.flot.image.js (79%) create mode 100644 Web Server/flot/jquery.flot.image.min.js rename {web => Web Server}/flot/jquery.flot.js (62%) create mode 100644 Web Server/flot/jquery.flot.min.js rename {web => Web Server}/flot/jquery.flot.navigate.js (57%) create mode 100644 Web Server/flot/jquery.flot.navigate.min.js create mode 100644 Web Server/flot/jquery.flot.pie.js create mode 100644 Web Server/flot/jquery.flot.pie.min.js create mode 100644 Web Server/flot/jquery.flot.resize.js create mode 100644 Web Server/flot/jquery.flot.resize.min.js rename {web => Web Server}/flot/jquery.flot.selection.js (75%) create mode 100644 Web Server/flot/jquery.flot.selection.min.js rename {web => Web Server}/flot/jquery.flot.stack.js (82%) create mode 100644 Web Server/flot/jquery.flot.stack.min.js rename {web => Web Server}/flot/jquery.flot.symbol.js (87%) create mode 100644 Web Server/flot/jquery.flot.symbol.min.js rename {web => Web Server}/flot/jquery.flot.threshold.js (69%) create mode 100644 Web Server/flot/jquery.flot.threshold.min.js create mode 100644 Web Server/flot/jquery.flot.time.js create mode 100644 Web Server/flot/jquery.flot.time.min.js create mode 100644 Web Server/flot/jquery.js create mode 100644 Web Server/flot/jquery.min.js create mode 100644 Web Server/getstatus.php create mode 100644 Web Server/images/favicon16.png rename {web => Web Server}/js/jquery-1.7.1.js (100%) rename {web => Web Server}/js/jquery-ui-1.8.17.js (100%) create mode 100644 Web Server/js/monitormate.js create mode 100644 Web Server/monitormate.html create mode 100644 Web Server/regstatus.php delete mode 100644 web/css/monitorsolar.css delete mode 100644 web/flot/.gitignore delete mode 100644 web/flot/NEWS.txt delete mode 100644 web/flot/README.txt delete mode 100644 web/flot/examples/ajax.html delete mode 100644 web/flot/examples/annotating.html delete mode 100644 web/flot/examples/arrow-down.gif delete mode 100644 web/flot/examples/arrow-left.gif delete mode 100644 web/flot/examples/arrow-right.gif delete mode 100644 web/flot/examples/arrow-up.gif delete mode 100644 web/flot/examples/basic.html delete mode 100644 web/flot/examples/categories.html delete mode 100644 web/flot/examples/data-eu-gdp-growth-1.json delete mode 100644 web/flot/examples/data-eu-gdp-growth-2.json delete mode 100644 web/flot/examples/data-eu-gdp-growth-3.json delete mode 100644 web/flot/examples/data-eu-gdp-growth-4.json delete mode 100644 web/flot/examples/data-eu-gdp-growth-5.json delete mode 100644 web/flot/examples/data-eu-gdp-growth.json delete mode 100644 web/flot/examples/data-japan-gdp-growth.json delete mode 100644 web/flot/examples/data-usa-gdp-growth.json delete mode 100644 web/flot/examples/graph-types.html delete mode 100644 web/flot/examples/hs-2004-27-a-large_web.jpg delete mode 100644 web/flot/examples/image.html delete mode 100644 web/flot/examples/index.html delete mode 100644 web/flot/examples/interacting-axes.html delete mode 100644 web/flot/examples/interacting.html delete mode 100644 web/flot/examples/layout.css delete mode 100644 web/flot/examples/multiple-axes.html delete mode 100644 web/flot/examples/navigate.html delete mode 100644 web/flot/examples/percentiles.html delete mode 100644 web/flot/examples/pie.html delete mode 100644 web/flot/examples/realtime.html delete mode 100644 web/flot/examples/resize.html delete mode 100644 web/flot/examples/selection.html delete mode 100644 web/flot/examples/setting-options.html delete mode 100644 web/flot/examples/stacking.html delete mode 100644 web/flot/examples/symbols.html delete mode 100644 web/flot/examples/thresholding.html delete mode 100644 web/flot/examples/time.html delete mode 100644 web/flot/examples/tracking.html delete mode 100644 web/flot/examples/turning-series.html delete mode 100644 web/flot/examples/visitors.html delete mode 100644 web/flot/examples/zooming.html delete mode 100644 web/flot/jquery.flot.fillbetween.js delete mode 100644 web/flot/jquery.flot.pie.js delete mode 100644 web/flot/jquery.flot.resize.js delete mode 100644 web/flot/jquery.js delete mode 100644 web/getstatus.php delete mode 100644 web/js/monitormate3.js delete mode 100644 web/monitormate3.html delete mode 100644 web/regstatus.php diff --git a/datastream scripts/flexnetdc.py b/Data Stream Host/flexnetdc.py similarity index 100% rename from datastream scripts/flexnetdc.py rename to Data Stream Host/flexnetdc.py diff --git a/datastream scripts/fmmx.py b/Data Stream Host/fmmx.py similarity index 100% rename from datastream scripts/fmmx.py rename to Data Stream Host/fmmx.py diff --git a/datastream scripts/fxinv.py b/Data Stream Host/fxinv.py similarity index 100% rename from datastream scripts/fxinv.py rename to Data Stream Host/fxinv.py diff --git a/datastream scripts/mate3.py b/Data Stream Host/mate3.py similarity index 100% rename from datastream scripts/mate3.py rename to Data Stream Host/mate3.py diff --git a/datastream scripts/monitomate.py b/Data Stream Host/monitomate.py similarity index 100% rename from datastream scripts/monitomate.py rename to Data Stream Host/monitomate.py diff --git a/datastream scripts/monitormate.sh b/Data Stream Host/monitormate.sh similarity index 100% rename from datastream scripts/monitormate.sh rename to Data Stream Host/monitormate.sh diff --git a/web/monitormate3cfg.php b/Web Server/config/config.sample.php similarity index 70% rename from web/monitormate3cfg.php rename to Web Server/config/config.sample.php index 79ff58b..8c73774 100644 --- a/web/monitormate3cfg.php +++ b/Web Server/config/config.sample.php @@ -12,11 +12,18 @@ GNU General Public License at for more details. */ -$dbhost="localhost"; //Database host -$dbuser=""; //Database user + +// DATABASE +$dbhost=""; //Database host +$dbuser=""; //Database user $dbpass=""; //Database password $dbname=""; //Database name -$reg_interval=10; //Time between each record in minutes -$token=""; //Token -$timezone="" //See http://www.php.net/manual/en/timezones.php, example for Spain: Europe/Madrid + +//UPDATES +$reg_interval=10; //Time between each record in minutes +$token=""; //Token + +// GENERAL +$timezone=""; //See http://www.php.net/manual/en/timezones.php, example for Spain: Europe/Madrid + ?> diff --git a/web/database.sql b/Web Server/config/database.sql old mode 100755 new mode 100644 similarity index 84% rename from web/database.sql rename to Web Server/config/database.sql index 30df9f5..de3a403 --- a/web/database.sql +++ b/Web Server/config/database.sql @@ -1,88 +1,109 @@ - -CREATE TABLE IF NOT EXISTS `monitormate3_flexnet` ( - `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', - `address` int(11) NOT NULL DEFAULT '0', - `device_id` int(11) DEFAULT NULL, - `shunt_a_amps` float DEFAULT NULL, - `shunt_b_amps` float DEFAULT NULL, - `shunt_c_amps` float DEFAULT NULL, - `accumulated_ah_shunt_a` int(11) DEFAULT NULL, - `accumulated_kwh_shunt_a` float DEFAULT NULL, - `accumulated_ah_shunt_b` int(11) DEFAULT NULL, - `accumulated_kwh_shunt_b` float DEFAULT NULL, - `accumulated_ah_shunt_c` int(11) DEFAULT NULL, - `accumulated_kwh_shunt_c` float DEFAULT NULL, - `days_since_full` float DEFAULT NULL, - `today_min_soc` int(11) DEFAULT NULL, - `today_net_input_ah` int(11) DEFAULT NULL, - `today_net_output_ah` int(11) DEFAULT NULL, - `today_net_input_kwh` float DEFAULT NULL, - `today_net_output_kwh` float DEFAULT NULL, - `charge_factor_corrected_net_batt_ah` float DEFAULT NULL, - `charge_factor_corrected_net_batt_kwh` float DEFAULT NULL, - `charge_params_met` varchar(3) DEFAULT NULL, - `relay_mode` varchar(10) DEFAULT NULL, - `relay_status` varchar(10) DEFAULT NULL, - `battery_volt` float DEFAULT NULL, - `soc` int(11) DEFAULT NULL, - `shunt_enabled_a` varchar(3) DEFAULT NULL, - `shunt_enabled_b` varchar(3) DEFAULT NULL, - `shunt_enabled_c` varchar(3) DEFAULT NULL, - `battery_temp` int(11) DEFAULT NULL, - PRIMARY KEY (`date`,`address`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - - - -CREATE TABLE IF NOT EXISTS `monitormate3_fmmx` ( - `date` datetime NOT NULL, - `address` int(11) NOT NULL, - `device_id` int(11) NOT NULL, - `charge_current` int(11) NOT NULL, - `pv_current` int(11) NOT NULL, - `pv_voltage` float NOT NULL, - `daily_kwh` float NOT NULL, - `aux_mode` varchar(15) NOT NULL, - `error_modes` varchar(45) NOT NULL, - `charge_mode` varchar(10) NOT NULL, - `battery_volts` float NOT NULL, - `daily_ah` int(11) NOT NULL, - PRIMARY KEY (`date`,`address`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - - - -CREATE TABLE IF NOT EXISTS `monitormate3_fxinv` ( - `date` datetime NOT NULL, - `address` int(11) NOT NULL, - `device_id` int(11) NOT NULL, - `inverter_current` int(11) NOT NULL, - `charge_current` int(11) NOT NULL, - `buy_current` int(11) NOT NULL, - `ac_input_voltage` int(11) NOT NULL, - `ac_output_voltage` int(11) NOT NULL, - `sell_current` int(11) NOT NULL, - `operational_mode` varchar(14) NOT NULL, - `error_modes` varchar(100) NOT NULL, - `ac_mode` varchar(8) NOT NULL, - `battery_volt` float NOT NULL, - `misc` varchar(13) NOT NULL, - `warning_modes` varchar(100) NOT NULL, - PRIMARY KEY (`date`,`address`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - - - -CREATE TABLE IF NOT EXISTS `monitormate3_summary` ( - `date` date NOT NULL, - `kwh_in` float NOT NULL, - `kwh_out` float NOT NULL, - `ah_in` int(11) NOT NULL, - `ah_out` int(11) NOT NULL, - `max_temp` int(11) NOT NULL, - `min_temp` int(11) NOT NULL, - `max_soc` int(11) NOT NULL, - `min_soc` int(11) NOT NULL, - `max_pv_voltage` int(11) NOT NULL, - PRIMARY KEY (`date`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- +-- Table setup SQL for importing into your database to use Monitor Mate. +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `monitormate3_flexnet` +-- + +CREATE TABLE IF NOT EXISTS `monitormate3_flexnet` ( + `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `address` int(11) NOT NULL DEFAULT '0', + `device_id` int(11) DEFAULT NULL, + `shunt_a_amps` float DEFAULT NULL, + `shunt_b_amps` float DEFAULT NULL, + `shunt_c_amps` float DEFAULT NULL, + `accumulated_ah_shunt_a` int(11) DEFAULT NULL, + `accumulated_kwh_shunt_a` float DEFAULT NULL, + `accumulated_ah_shunt_b` int(11) DEFAULT NULL, + `accumulated_kwh_shunt_b` float DEFAULT NULL, + `accumulated_ah_shunt_c` int(11) DEFAULT NULL, + `accumulated_kwh_shunt_c` float DEFAULT NULL, + `days_since_full` float DEFAULT NULL, + `today_min_soc` int(11) DEFAULT NULL, + `today_net_input_ah` int(11) DEFAULT NULL, + `today_net_output_ah` int(11) DEFAULT NULL, + `today_net_input_kwh` float DEFAULT NULL, + `today_net_output_kwh` float DEFAULT NULL, + `charge_factor_corrected_net_batt_ah` float DEFAULT NULL, + `charge_factor_corrected_net_batt_kwh` float DEFAULT NULL, + `charge_params_met` varchar(3) DEFAULT NULL, + `relay_mode` varchar(10) DEFAULT NULL, + `relay_status` varchar(10) DEFAULT NULL, + `battery_volt` float DEFAULT NULL, + `soc` int(11) DEFAULT NULL, + `shunt_enabled_a` varchar(3) DEFAULT NULL, + `shunt_enabled_b` varchar(3) DEFAULT NULL, + `shunt_enabled_c` varchar(3) DEFAULT NULL, + `battery_temp` int(11) DEFAULT NULL, + PRIMARY KEY (`date`,`address`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `monitormate3_fmmx` +-- + +CREATE TABLE IF NOT EXISTS `monitormate3_fmmx` ( + `date` datetime NOT NULL, + `address` int(11) NOT NULL, + `device_id` int(11) NOT NULL, + `charge_current` int(11) NOT NULL, + `pv_current` int(11) NOT NULL, + `pv_voltage` float NOT NULL, + `daily_kwh` float NOT NULL, + `aux_mode` varchar(15) NOT NULL, + `error_modes` varchar(45) NOT NULL, + `charge_mode` varchar(10) NOT NULL, + `battery_volts` float NOT NULL, + `daily_ah` int(11) NOT NULL, + PRIMARY KEY (`date`,`address`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `monitormate3_fxinv` +-- + +CREATE TABLE IF NOT EXISTS `monitormate3_fxinv` ( + `date` datetime NOT NULL, + `address` int(11) NOT NULL, + `device_id` int(11) NOT NULL, + `inverter_current` int(11) NOT NULL, + `charge_current` int(11) NOT NULL, + `buy_current` int(11) NOT NULL, + `ac_input_voltage` int(11) NOT NULL, + `ac_output_voltage` int(11) NOT NULL, + `sell_current` int(11) NOT NULL, + `operational_mode` varchar(14) NOT NULL, + `error_modes` varchar(100) NOT NULL, + `ac_mode` varchar(8) NOT NULL, + `battery_volt` float NOT NULL, + `misc` varchar(13) NOT NULL, + `warning_modes` varchar(100) NOT NULL, + PRIMARY KEY (`date`,`address`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `monitormate3_summary` +-- + +CREATE TABLE IF NOT EXISTS `monitormate3_summary` ( + `date` date NOT NULL, + `kwh_in` float NOT NULL, + `kwh_out` float NOT NULL, + `ah_in` int(11) NOT NULL, + `ah_out` int(11) NOT NULL, + `max_temp` int(11) NOT NULL, + `min_temp` int(11) NOT NULL, + `max_soc` int(11) NOT NULL, + `min_soc` int(11) NOT NULL, + `max_pv_voltage` int(11) NOT NULL, + PRIMARY KEY (`date`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/Web Server/config/labels.sample.js b/Web Server/config/labels.sample.js new file mode 100644 index 0000000..6d8d518 --- /dev/null +++ b/Web Server/config/labels.sample.js @@ -0,0 +1,22 @@ +/* +This is a configuration file to apply custom labels to devices and shunts +*/ + +// if you leave it blank a name will be automatically generated. +var deviceLabel = new Array(); +deviceLabel[1] = ""; +deviceLabel[2] = ""; +deviceLabel[3] = ""; +deviceLabel[4] = ""; +deviceLabel[5] = ""; +deviceLabel[6] = ""; +deviceLabel[7] = ""; +deviceLabel[8] = ""; +deviceLabel[9] = ""; +deviceLabel[10] = ""; + +// no automatic names can be generated for shunts, please don't leave these blank. +var shuntLabel = new Array(); +shuntLabel[1] = "Shunt A"; +shuntLabel[2] = "Shunt B"; +shuntLabel[3] = "Shunt C"; diff --git a/web/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png b/Web Server/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png similarity index 100% rename from web/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png rename to Web Server/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png diff --git a/web/css/images/ui-bg_diagonals-thick_20_666666_40x40.png b/Web Server/css/images/ui-bg_diagonals-thick_20_666666_40x40.png similarity index 100% rename from web/css/images/ui-bg_diagonals-thick_20_666666_40x40.png rename to Web Server/css/images/ui-bg_diagonals-thick_20_666666_40x40.png diff --git a/web/css/images/ui-bg_flat_10_000000_40x100.png b/Web Server/css/images/ui-bg_flat_10_000000_40x100.png similarity index 100% rename from web/css/images/ui-bg_flat_10_000000_40x100.png rename to Web Server/css/images/ui-bg_flat_10_000000_40x100.png diff --git a/web/css/images/ui-bg_glass_100_f6f6f6_1x400.png b/Web Server/css/images/ui-bg_glass_100_f6f6f6_1x400.png similarity index 100% rename from web/css/images/ui-bg_glass_100_f6f6f6_1x400.png rename to Web Server/css/images/ui-bg_glass_100_f6f6f6_1x400.png diff --git a/web/css/images/ui-bg_glass_100_fdf5ce_1x400.png b/Web Server/css/images/ui-bg_glass_100_fdf5ce_1x400.png similarity index 100% rename from web/css/images/ui-bg_glass_100_fdf5ce_1x400.png rename to Web Server/css/images/ui-bg_glass_100_fdf5ce_1x400.png diff --git a/web/css/images/ui-bg_glass_65_ffffff_1x400.png b/Web Server/css/images/ui-bg_glass_65_ffffff_1x400.png similarity index 100% rename from web/css/images/ui-bg_glass_65_ffffff_1x400.png rename to Web Server/css/images/ui-bg_glass_65_ffffff_1x400.png diff --git a/web/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png b/Web Server/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png similarity index 100% rename from web/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png rename to Web Server/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png diff --git a/web/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/Web Server/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png similarity index 100% rename from web/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png rename to Web Server/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png diff --git a/web/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png b/Web Server/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png similarity index 100% rename from web/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png rename to Web Server/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png diff --git a/web/css/images/ui-icons_222222_256x240.png b/Web Server/css/images/ui-icons_222222_256x240.png similarity index 100% rename from web/css/images/ui-icons_222222_256x240.png rename to Web Server/css/images/ui-icons_222222_256x240.png diff --git a/web/css/images/ui-icons_228ef1_256x240.png b/Web Server/css/images/ui-icons_228ef1_256x240.png similarity index 100% rename from web/css/images/ui-icons_228ef1_256x240.png rename to Web Server/css/images/ui-icons_228ef1_256x240.png diff --git a/web/css/images/ui-icons_ef8c08_256x240.png b/Web Server/css/images/ui-icons_ef8c08_256x240.png similarity index 100% rename from web/css/images/ui-icons_ef8c08_256x240.png rename to Web Server/css/images/ui-icons_ef8c08_256x240.png diff --git a/web/css/images/ui-icons_ffd27a_256x240.png b/Web Server/css/images/ui-icons_ffd27a_256x240.png similarity index 100% rename from web/css/images/ui-icons_ffd27a_256x240.png rename to Web Server/css/images/ui-icons_ffd27a_256x240.png diff --git a/web/css/images/ui-icons_ffffff_256x240.png b/Web Server/css/images/ui-icons_ffffff_256x240.png similarity index 100% rename from web/css/images/ui-icons_ffffff_256x240.png rename to Web Server/css/images/ui-icons_ffffff_256x240.png diff --git a/web/css/jquery-ui.css b/Web Server/css/jquery-ui.css similarity index 100% rename from web/css/jquery-ui.css rename to Web Server/css/jquery-ui.css diff --git a/Web Server/css/monitormate.css b/Web Server/css/monitormate.css new file mode 100644 index 0000000..6a960f6 --- /dev/null +++ b/Web Server/css/monitormate.css @@ -0,0 +1,91 @@ +body { + margin: 20px; + font-family: Verdana; + font-size: 11px; +} + +/* + STYLE +*/ + +#left-col table { + width: 100%; + table-layout: fixed; +} + +caption { + font-weight: bold; + font-size: 12px; + text-align: left; + padding: 4px; + border-bottom: 1px solid black; +} + +caption div { + display: inline-block; + float: right; +} + +th.subhead { + text-align: right; + color: #aaaaaa; +} + +td { + padding-left: 4px; +} + +td.label { + text-align: right; + color: #aaaaaa; +} + +/* + LAYOUT +*/ + +#left-col { + float: left; + width: 320px; /* if you change this, change top chart's left margin. */ + margin-bottom: 20px; +} + +.top-chart { + height: 260px; + min-width: 380px; /* should be double-wide width minus left margin (below) - */ + margin-left: 340px; /* shoud be 20px more than the left-col width. */ + margin-right: 0px; + margin-bottom: 20px; +} + + +.bottom-wide-chart { + clear: both; + width: 100%; + min-width: 720px; + height: 260px; + margin-top: 20px; +} + +.bottom-double-charts { + position: relative; + height: 10px; /* just so you can see it */ + margin: 20px 0px; + min-width: 720px; +} + +.left-chart { + position: absolute; + left: 0px; + width: 48%; + height: 260px; + margin-right: 20px; +} + +.right-chart { + position: absolute; + right: 0px; + width: 48%; + height: 260px; + margin-left: 20px; +} diff --git a/web/flot/jquery.flot.threshold.multiple.js b/Web Server/flot-plugins/jquery.flot.threshold.multiple.js old mode 100644 new mode 100755 similarity index 98% rename from web/flot/jquery.flot.threshold.multiple.js rename to Web Server/flot-plugins/jquery.flot.threshold.multiple.js index a74306d..627eba3 --- a/web/flot/jquery.flot.threshold.multiple.js +++ b/Web Server/flot-plugins/jquery.flot.threshold.multiple.js @@ -36,23 +36,23 @@ Internally, the plugin works by splitting the data into different series, one fo } } } - + function Graph(dataset, constraints) { this._constraints = _getSortedConstraints(dataset,constraints); this._dataset = dataset; this._plotData = []; - + this.getPlotData = function() { - + if(this._constraints.length == 0)return []; - + for ( var i = this._constraints.length - 1; i >= 0 ; i--) { var constraint = this._constraints[i]; if(null != constraint.threshold){ var set = new Resolve(this._dataset).using(constraint.threshold, constraint.evaluate); this._plotData.push( { data : set, - color : constraint.color, + color : constraint.color }); } } @@ -64,7 +64,7 @@ Internally, the plugin works by splitting the data into different series, one fo this._data = []; this._getPointOnThreshold = _getPointOnThreshold; this.using = using ; - + function using(threshold, evaluate) { var count = 0; for ( var i = 0; i < this._originalPoints.length; i++) { @@ -85,7 +85,7 @@ Internally, the plugin works by splitting the data into different series, one fo } return this._data; } - + function _getPointOnThreshold(threshold, prevP, currP) { var currentX = currP[0]; var currentY = currP[1]; @@ -102,11 +102,11 @@ Internally, the plugin works by splitting the data into different series, one fo } function _getSortedConstraints(originalpoints,constraints){ - + var dataRange = _findMaxAndMin(originalpoints); - + if(undefined == dataRange)return []; - + var max = dataRange.max; var min = dataRange.min; var thresholdRanges = []; @@ -137,22 +137,22 @@ Internally, the plugin works by splitting the data into different series, one fo QuickSort(arr,function(p1,p2){return p1 < p2;}); return { min:arr[0],max:arr[arr.length-1]}; } - + } function QuickSort(dataset,comparator){ sort(dataset, 0, dataset.length-1, comparator); - + function sort(array, left, right,comparator){ if(right > left){ var pivotIndex = Math.floor(( left + right )/2); var pivotNewIndex = partition(array,left,right,pivotIndex,comparator); sort(array, left, pivotNewIndex - 1,comparator); sort(array, pivotNewIndex + 1, right,comparator); - + } } - + function partition(array,left,right,pivotIndex,comparator){ var pivot = array[pivotIndex]; swap(array,pivotIndex,right); @@ -164,10 +164,10 @@ Internally, the plugin works by splitting the data into different series, one fo } } swap(array,storeIndex,right); - + return storeIndex; } - + function swap(array,index1,index2){ var temp = array[index1]; array[index1] = array[index2]; @@ -187,4 +187,3 @@ $.plot.plugins.push({ })(jQuery); - diff --git a/web/flot/API.txt b/Web Server/flot/API.md similarity index 61% rename from web/flot/API.txt rename to Web Server/flot/API.md index 61d2f34..e08b44c 100644 --- a/web/flot/API.txt +++ b/Web Server/flot/API.md @@ -1,19 +1,49 @@ -Flot Reference --------------- +# Flot Reference # + +**Table of Contents** + +[Introduction](#introduction) +| [Data Format](#data-format) +| [Plot Options](#plot-options) +| [Customizing the legend](#customizing-the-legend) +| [Customizing the axes](#customizing-the-axes) +| [Multiple axes](#multiple-axes) +| [Time series data](#time-series-data) +| [Customizing the data series](#customizing-the-data-series) +| [Customizing the grid](#customizing-the-grid) +| [Specifying gradients](#specifying-gradients) +| [Plot Methods](#plot-methods) +| [Hooks](#hooks) +| [Plugins](#plugins) +| [Version number](#version-number) + +--- + +## Introduction ## Consider a call to the plot function: - var plot = $.plot(placeholder, data, options) +```js +var plot = $.plot(placeholder, data, options) +``` The placeholder is a jQuery object or DOM element or jQuery expression that the plot will be put into. This placeholder needs to have its -width and height set as explained in the README (go read that now if +width and height set as explained in the [README](README.md) (go read that now if you haven't, it's short). The plot will modify some properties of the placeholder so it's recommended you simply pass in a div that you don't use for anything else. Make sure you check any fancy styling you apply to the div, e.g. background images have been reported to be a problem on IE 7. +The plot function can also be used as a jQuery chainable property. This form +naturally can't return the plot object directly, but you can still access it +via the 'plot' data key, like this: + +```js +var plot = $("#placeholder").plot(data, options).data("plot"); +``` + The format of the data is documented below, as is the available options. The plot object returned from the call has some methods you can call. These are documented separately below. @@ -23,21 +53,26 @@ objects you pass in to the plot function or get out of it since they're not necessarily deep-copied. -Data Format ------------ +## Data Format ## The data is an array of data series: - [ series1, series2, ... ] +```js +[ series1, series2, ... ] +``` A series can either be raw data or an object with properties. The raw data format is an array of points: - [ [x1, y1], [x2, y2], ... ] +```js +[ [x1, y1], [x2, y2], ... ] +``` E.g. - [ [1, 3], [2, 14.01], [3.5, 3.14] ] +```js +[ [1, 3], [2, 14.01], [3.5, 3.14] ] +``` Note that to simplify the internal logic in Flot both the x and y values must be numbers (even if specifying time series, see below for @@ -58,7 +93,8 @@ area/bar (defaults to 0). The format of a single series object is as follows: - { +```js +{ color: color or number data: rawdata label: string @@ -70,16 +106,20 @@ The format of a single series object is as follows: clickable: boolean hoverable: boolean shadowSize: number - } + highlightColor: color or number +} +``` You don't have to specify any of them except the data, the rest are options that will get default values. Typically you'd only specify label and data, like this: - { +```js +{ label: "y = 3", data: [[0, 3], [10, 3]] - } +} +``` The label is used for the legend, if you don't specify one, the series will not show up in the legend. @@ -108,30 +148,34 @@ override the default options for the plot for that data series. Here's a complete example of a simple data specification: - [ { label: "Foo", data: [ [10, 1], [17, -14], [30, 5] ] }, - { label: "Bar", data: [ [11, 13], [19, 11], [30, -7] ] } ] +```js +[ { label: "Foo", data: [ [10, 1], [17, -14], [30, 5] ] }, + { label: "Bar", data: [ [11, 13], [19, 11], [30, -7] ] } +] +``` -Plot Options ------------- +## Plot Options ## All options are completely optional. They are documented individually below, to change them you just specify them in an object, e.g. - var options = { +```js +var options = { series: { - lines: { show: true }, - points: { show: true } + lines: { show: true }, + points: { show: true } } - }; - - $.plot(placeholder, data, options); +}; + +$.plot(placeholder, data, options); +``` -Customizing the legend -====================== +## Customizing the legend ## - legend: { +```js +legend: { show: boolean labelFormatter: null or (fn: string, series object -> string) labelBoxBorderColor: color @@ -141,7 +185,9 @@ Customizing the legend backgroundColor: null or color backgroundOpacity: number between 0 and 1 container: null or jQuery object/DOM element/jQuery expression - } + sorted: null/false, true, "ascending", "descending", "reverse", or a comparator +} +``` The legend is generated as a table with the data series labels and small label boxes with the color of the series. If you want to format @@ -149,10 +195,15 @@ the labels in some way, e.g. make them to links, you can pass in a function for "labelFormatter". Here's an example that makes them clickable: - labelFormatter: function(label, series) { +```js +labelFormatter: function(label, series) { // series is the series object for the label return '' + label + ''; - } +} +``` + +To prevent a series from showing up in the legend, simply have the function +return null. "noColumns" is the number of columns to divide the legend table into. "position" specifies the overall placement of the legend within the @@ -167,14 +218,34 @@ specify "container" as a jQuery object/expression to put the legend table into. The "position" and "margin" etc. options will then be ignored. Note that Flot will overwrite the contents of the container. +Legend entries appear in the same order as their series by default. If "sorted" +is "reverse" then they appear in the opposite order from their series. To sort +them alphabetically, you can specify true, "ascending" or "descending", where +true and "ascending" are equivalent. + +You can also provide your own comparator function that accepts two +objects with "label" and "color" properties, and returns zero if they +are equal, a positive value if the first is greater than the second, +and a negative value if the first is less than the second. + +```js +sorted: function(a, b) { + // sort alphabetically in ascending order + return a.label == b.label ? 0 : ( + a.label > b.label ? 1 : -1 + ) +} +``` -Customizing the axes -==================== - xaxis, yaxis: { +## Customizing the axes ## + +```js +xaxis, yaxis: { show: null or true/false position: "bottom" or "top" or "left" or "right" - mode: null or "time" + mode: null or "time" ("time" requires jquery.flot.time.js plugin) + timezone: null, "browser" or timezone (only makes sense for mode: "time") color: null or color spec tickColor: null or color spec @@ -200,7 +271,8 @@ Customizing the axes tickLength: null or number alignTicksWithAxis: null or number - } +} +``` All axes have the same kind of options. The following describes how to configure one axis, see below for what to do if you've got more than @@ -214,28 +286,50 @@ false. The "position" option specifies where the axis is placed, bottom or top for x axes, left or right for y axes. The "mode" option determines how the data is interpreted, the default of null means as decimal -numbers. Use "time" for time series data, see the time series data -section. - -The "color" option determines the color of the labels and ticks for -the axis (default is the grid color). For more fine-grained control -you can also set the color of the ticks separately with "tickColor" -(otherwise it's autogenerated as the base color with some -transparency). - -You can customize the font used to draw the labels with CSS or -directly with "font". The default value of null means that the font is -read from the font style on the placeholder element (80% the size of -that to be precise). If you set it directly with "font: { ... }", the -format is like this: - - { - size: 11, - style: "italic", - weight: "bold", - family: "sans-serif", - variant: "small-caps" - } +numbers. Use "time" for time series data; see the time series data +section. The time plugin (jquery.flot.time.js) is required for time +series support. + +The "color" option determines the color of the line and ticks for the axis, and +defaults to the grid color with transparency. For more fine-grained control you +can also set the color of the ticks separately with "tickColor". + +You can customize the font and color used to draw the axis tick labels with CSS +or directly via the "font" option. When "font" is null - the default - each +tick label is given the 'flot-tick-label' class. For compatibility with Flot +0.7 and earlier the labels are also given the 'tickLabel' class, but this is +deprecated and scheduled to be removed with the release of version 1.0.0. + +To enable more granular control over styles, labels are divided between a set +of text containers, with each holding the labels for one axis. These containers +are given the classes 'flot-[x|y]-axis', and 'flot-[x|y]#-axis', where '#' is +the number of the axis when there are multiple axes. For example, the x-axis +labels for a simple plot with only a single x-axis might look like this: + +```html +
+
January 2013
+ ... +
+``` + +For direct control over label styles you can also provide "font" as an object +with this format: + +```js +{ + size: 11, + lineHeight: 13, + style: "italic", + weight: "bold", + family: "sans-serif", + variant: "small-caps", + color: "#545454" +} +``` + +The size and lineHeight must be expressed in pixels; CSS units such as 'em' +or 'smaller' are not allowed. The options "min"/"max" are the precise minimum/maximum value on the scale. If you don't specify either of them, a value will automatically @@ -260,18 +354,22 @@ other means. When Flot draws the plot, each value is first put through the transform function. Here's an example, the x axis can be turned into a natural logarithm axis with the following code: - xaxis: { +```js +xaxis: { transform: function (v) { return Math.log(v); }, inverseTransform: function (v) { return Math.exp(v); } - } +} +``` Similarly, for reversing the y axis so the values appear in inverse order: - - yaxis: { + +```js +yaxis: { transform: function (v) { return -v; }, inverseTransform: function (v) { return -v; } - } +} +``` Note that for finding extrema, Flot assumes that the transform function does not reorder values (it should be monotone). @@ -306,11 +404,15 @@ see the next section. If you want to completely override the tick algorithm, you can specify an array for "ticks", either like this: - ticks: [0, 1.2, 2.4] +```js +ticks: [0, 1.2, 2.4] +``` Or like this where the labels are also customized: - ticks: [[0, "zero"], [1.2, "one mark"], [2.4, "two marks"]] +```js +ticks: [[0, "zero"], [1.2, "one mark"], [2.4, "two marks"]] +``` You can mix the two if you like. @@ -320,16 +422,17 @@ min and max and should return a ticks array. Here's a simplistic tick generator that spits out intervals of pi, suitable for use on the x axis for trigonometric functions: - function piTickGenerator(axis) { +```js +function piTickGenerator(axis) { var res = [], i = Math.floor(axis.min / Math.PI); do { - var v = i * Math.PI; - res.push([v, i + "\u03c0"]); - ++i; + var v = i * Math.PI; + res.push([v, i + "\u03c0"]); + ++i; } while (v < axis.max); - return res; - } +} +``` You can control how the ticks look like with "tickDecimals", the number of decimals to display (default is auto-detected). @@ -339,9 +442,11 @@ provide a function to "tickFormatter". The function is passed two parameters, the tick value and an axis object with information, and should return a string. The default formatter looks like this: - function formatter(val, axis) { +```js +function formatter(val, axis) { return val.toFixed(axis.tickDecimals); - } +} +``` The axis object has "min" and "max" with the range of the axis, "tickDecimals" with the number of decimals to round the value to and @@ -349,14 +454,16 @@ The axis object has "min" and "max" with the range of the axis, by the automatic axis scaling algorithm (or specified by you). Here's an example of a custom formatter: - function suffixFormatter(val, axis) { +```js +function suffixFormatter(val, axis) { if (val > 1000000) - return (val / 1000000).toFixed(axis.tickDecimals) + " MB"; + return (val / 1000000).toFixed(axis.tickDecimals) + " MB"; else if (val > 1000) - return (val / 1000).toFixed(axis.tickDecimals) + " kB"; + return (val / 1000).toFixed(axis.tickDecimals) + " kB"; else - return val.toFixed(axis.tickDecimals) + " B"; - } + return val.toFixed(axis.tickDecimals) + " B"; +} +``` "labelWidth" and "labelHeight" specifies a fixed size of the tick labels in pixels. They're useful in case you need to align several @@ -379,8 +486,7 @@ ends. The trade-off is that the forced ticks won't necessarily be at natural places. -Multiple axes -============= +## Multiple axes ## If you need more than one x axis or y axis, you need to specify for each data series which axis they are to use, as described under the @@ -390,16 +496,20 @@ that a series should be plotted against the second y axis. To actually configure that axis, you can't use the xaxis/yaxis options directly - instead there are two arrays in the options: - xaxes: [] - yaxes: [] +```js +xaxes: [] +yaxes: [] +``` Here's an example of configuring a single x axis and two y axes (we can leave options of the first y axis empty as the defaults are fine): - { +```js +{ xaxes: [ { position: "top" } ], yaxes: [ { }, { position: "right", min: 20 } ] - } +} +``` The arrays get their default values from the xaxis/yaxis settings, so say you want to have all y axes start at zero, you can simply specify @@ -410,9 +520,11 @@ either accept an xaxis/yaxis parameter to specify which axis number to use (starting from 1), or lets you specify the coordinate directly as x2/x3/... or x2axis/x3axis/... instead of "x" or "xaxis". - -Time series data -================ + +## Time series data ## + +Please note that it is now required to include the time plugin, +jquery.flot.time.js, for time series support. Time series are a bit more difficult than scalar data because calendars don't follow a simple base 10 system. For many cases, Flot @@ -428,35 +540,64 @@ in milliseconds, so remember to multiply by 1000! You can see a timestamp like this - alert((new Date()).getTime()) +```js +alert((new Date()).getTime()) +``` -Normally you want the timestamps to be displayed according to a -certain time zone, usually the time zone in which the data has been -produced. However, Flot always displays timestamps according to UTC. -It has to as the only alternative with core Javascript is to interpret -the timestamps according to the time zone that the visitor is in, -which means that the ticks will shift unpredictably with the time zone -and daylight savings of each visitor. +There are different schools of thought when it comes to display of +timestamps. Many will want the timestamps to be displayed according to +a certain time zone, usually the time zone in which the data has been +produced. Some want the localized experience, where the timestamps are +displayed according to the local time of the visitor. Flot supports +both. Optionally you can include a third-party library to get +additional timezone support. -So given that there's no good support for custom time zones in -Javascript, you'll have to take care of this server-side. +Default behavior is that Flot always displays timestamps according to +UTC. The reason being that the core Javascript Date object does not +support other fixed time zones. Often your data is at another time +zone, so it may take a little bit of tweaking to work around this +limitation. The easiest way to think about it is to pretend that the data production time zone is UTC, even if it isn't. So if you have a datapoint at 2002-02-20 08:00, you can generate a timestamp for eight o'clock UTC even if it really happened eight o'clock UTC+0200. -In PHP you can get an appropriate timestamp with -'strtotime("2002-02-20 UTC") * 1000', in Python with -'calendar.timegm(datetime_object.timetuple()) * 1000', in .NET with -something like: +In PHP you can get an appropriate timestamp with: + +```php +strtotime("2002-02-20 UTC") * 1000 +``` + +In Python you can get it with something like: - public static int GetJavascriptTimestamp(System.DateTime input) - { +```python +calendar.timegm(datetime_object.timetuple()) * 1000 +``` +In Ruby you can get it using the `#to_i` method on the +[`Time`](http://apidock.com/ruby/Time/to_i) object. If you're using the +`active_support` gem (default for Ruby on Rails applications) `#to_i` is also +available on the `DateTime` and `ActiveSupport::TimeWithZone` objects. You +simply need to multiply the result by 1000: + +```ruby +Time.now.to_i * 1000 # => 1383582043000 +# ActiveSupport examples: +DateTime.now.to_i * 1000 # => 1383582043000 +ActiveSupport::TimeZone.new('Asia/Shanghai').now.to_i * 1000 +# => 1383582043000 +``` + +In .NET you can get it with something like: + +```aspx +public static int GetJavascriptTimestamp(System.DateTime input) +{ System.TimeSpan span = new System.TimeSpan(System.DateTime.Parse("1/1/1970").Ticks); System.DateTime time = input.Subtract(span); return (long)(time.Ticks / 10000); - } +} +``` Javascript also has some support for parsing date strings, so it is possible to generate the timestamps manually client-side. @@ -469,6 +610,18 @@ programming environments have some means of getting the timezone offset for a specific date (note that you need to get the offset for each individual timestamp to account for daylight savings). +The alternative with core Javascript is to interpret the timestamps +according to the time zone that the visitor is in, which means that +the ticks will shift with the time zone and daylight savings of each +visitor. This behavior is enabled by setting the axis option +"timezone" to the value "browser". + +If you need more time zone functionality than this, there is still +another option. If you include the "timezone-js" library + in the page and set axis.timezone +to a value recognized by said library, Flot will use timezone-js to +interpret the timestamps according to that time zone. + Once you've gotten the timestamps into the data and specified "time" as the axis mode, Flot will automatically generate relevant ticks and format them. As always, you can tweak the ticks via the "ticks" option @@ -478,104 +631,135 @@ Date objects. Tick generation and formatting can also be controlled separately through the following axis options: - minTickSize: array - timeformat: null or format string - monthNames: null or array of size 12 of strings - twelveHourClock: boolean +```js +minTickSize: array +timeformat: null or format string +monthNames: null or array of size 12 of strings +dayNames: null or array of size 7 of strings +twelveHourClock: boolean +``` Here "timeformat" is a format string to use. You might use it like this: - xaxis: { - mode: "time" - timeformat: "%y/%m/%d" - } - -This will result in tick labels like "2000/12/24". The following -specifiers are supported - - %h: hours - %H: hours (left-padded with a zero) - %M: minutes (left-padded with a zero) - %S: seconds (left-padded with a zero) - %d: day of month (1-31), use %0d for zero-padding - %m: month (1-12), use %0m for zero-padding - %y: year (four digits) - %b: month name (customizable) - %p: am/pm, additionally switches %h/%H to 12 hour instead of 24 - %P: AM/PM (uppercase version of %p) - -Inserting a zero like %0m or %0d means that the specifier will be -left-padded with a zero if it's only single-digit. So %y-%0m-%0d -results in unambigious ISO timestamps like 2007-05-10 (for May 10th). +```js +xaxis: { + mode: "time", + timeformat: "%Y/%m/%d" +} +``` + +This will result in tick labels like "2000/12/24". A subset of the +standard strftime specifiers are supported (plus the nonstandard %q): + +```js +%a: weekday name (customizable) +%b: month name (customizable) +%d: day of month, zero-padded (01-31) +%e: day of month, space-padded ( 1-31) +%H: hours, 24-hour time, zero-padded (00-23) +%I: hours, 12-hour time, zero-padded (01-12) +%m: month, zero-padded (01-12) +%M: minutes, zero-padded (00-59) +%q: quarter (1-4) +%S: seconds, zero-padded (00-59) +%y: year (two digits) +%Y: year (four digits) +%p: am/pm +%P: AM/PM (uppercase version of %p) +%w: weekday as number (0-6, 0 being Sunday) +``` + +Flot 0.8 switched from %h to the standard %H hours specifier. The %h specifier +is still available, for backwards-compatibility, but is deprecated and +scheduled to be removed permanently with the release of version 1.0. You can customize the month names with the "monthNames" option. For instance, for Danish you might specify: - monthNames: ["jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"] +```js +monthNames: ["jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"] +``` + +Similarly you can customize the weekday names with the "dayNames" +option. An example in French: + +```js +dayNames: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"] +``` If you set "twelveHourClock" to true, the autogenerated timestamps -will use 12 hour AM/PM timestamps instead of 24 hour. - -The format string and month names are used by a very simple built-in -format function that takes a date object, a format string (and -optionally an array of month names) and returns the formatted string. -If needed, you can access it as $.plot.formatDate(date, formatstring, -monthNames) or even replace it with another more advanced function -from a date library if you're feeling adventurous. +will use 12 hour AM/PM timestamps instead of 24 hour. This only +applies if you have not set "timeformat". Use the "%I" and "%p" or +"%P" options if you want to build your own format string with 12-hour +times. + +If the Date object has a strftime property (and it is a function), it +will be used instead of the built-in formatter. Thus you can include +a strftime library such as http://hacks.bluesmoon.info/strftime/ for +more powerful date/time formatting. If everything else fails, you can control the formatting by specifying a custom tick formatter function as usual. Here's a simple example which will format December 24 as 24/12: - tickFormatter: function (val, axis) { +```js +tickFormatter: function (val, axis) { var d = new Date(val); return d.getUTCDate() + "/" + (d.getUTCMonth() + 1); - } +} +``` Note that for the time mode "tickSize" and "minTickSize" are a bit special in that they are arrays on the form "[value, unit]" where unit is one of "second", "minute", "hour", "day", "month" and "year". So you can specify - minTickSize: [1, "month"] +```js +minTickSize: [1, "month"] +``` to get a tick interval size of at least 1 month and correspondingly, if axis.tickSize is [2, "day"] in the tick formatter, the ticks have been produced with two days in-between. +## Customizing the data series ## -Customizing the data series -=========================== - - series: { +```js +series: { lines, points, bars: { - show: boolean - lineWidth: number - fill: boolean or number - fillColor: null or color/gradient + show: boolean + lineWidth: number + fill: boolean or number + fillColor: null or color/gradient + } + + lines, bars: { + zero: boolean } points: { - radius: number - symbol: "circle" or function + radius: number + symbol: "circle" or function } bars: { - barWidth: number - align: "left" or "center" - horizontal: boolean + barWidth: number + align: "left", "right" or "center" + horizontal: boolean } lines: { - steps: boolean + steps: boolean } shadowSize: number - } - - colors: [ color1, color2, ... ] + highlightColor: color or number +} + +colors: [ color1, color2, ... ] +``` The options inside "series: {}" are copied to each of the series. So you can specify that all series should have bars by putting it in the @@ -590,12 +774,14 @@ lines: { show: false }). You can specify the various types independently of each other, and Flot will happily draw each of them in turn (this is probably only useful for lines and points), e.g. - var options = { +```js +var options = { series: { - lines: { show: true, fill: true, fillColor: "rgba(255, 255, 255, 0.8)" }, - points: { show: true, fill: false } + lines: { show: true, fill: true, fillColor: "rgba(255, 255, 255, 0.8)" }, + points: { show: true, fill: false } } - }; +}; +``` "lineWidth" is the thickness of the line or outline in pixels. You can set it to 0 to prevent a line or outline from being drawn; this will @@ -615,12 +801,19 @@ the y axis if "horizontal" is true), contrary to most other measures that are specified in pixels. For instance, for time series the unit is milliseconds so 24 * 60 * 60 * 1000 produces bars with the width of a day. "align" specifies whether a bar should be left-aligned -(default) or centered on top of the value it represents. When -"horizontal" is on, the bars are drawn horizontally, i.e. from the y -axis instead of the x axis; note that the bar end points are still +(default), right-aligned or centered on top of the value it represents. +When "horizontal" is on, the bars are drawn horizontally, i.e. from the +y axis instead of the x axis; note that the bar end points are still defined in the same way so you'll probably want to swap the coordinates if you've been plotting vertical bars first. +Area and bar charts normally start from zero, regardless of the data's range. +This is because they convey information through size, and starting from a +different value would distort their meaning. In cases where the fill is purely +for decorative purposes, however, "zero" allows you to override this behavior. +It defaults to true for filled lines and bars; setting it to false tells the +series to use the same automatic scaling as an un-filled line. + For lines, "steps" specifies whether two adjacent data points are connected with a straight (possibly diagonal) line or with first a horizontal and then a vertical line. Note that this transforms the @@ -630,13 +823,15 @@ For points, you can specify the radius and the symbol. The only built-in symbol type is circles, for other types you can use a plugin or define them yourself by specifying a callback: - function cross(ctx, x, y, radius, shadow) { - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.moveTo(x - size, y - size); - ctx.lineTo(x + size, y + size); - ctx.moveTo(x - size, y + size); - ctx.lineTo(x + size, y - size); - } +```js +function cross(ctx, x, y, radius, shadow) { + var size = radius * Math.sqrt(Math.PI) / 2; + ctx.moveTo(x - size, y - size); + ctx.lineTo(x + size, y + size); + ctx.moveTo(x - size, y + size); + ctx.lineTo(x + size, y - size); +} +``` The parameters are the drawing context, x and y coordinates of the center of the point, a radius which corresponds to what the circle @@ -649,39 +844,46 @@ ensures that all symbols have approximately the same visual weight. "shadowSize" is the default size of shadows in pixels. Set it to 0 to remove shadows. +"highlightColor" is the default color of the translucent overlay used +to highlight the series when the mouse hovers over it. + The "colors" array specifies a default color theme to get colors for the data series from. You can specify as many colors as you like, like this: - colors: ["#d18b2c", "#dba255", "#919733"] +```js +colors: ["#d18b2c", "#dba255", "#919733"] +``` If there are more data series than colors, Flot will try to generate extra colors by lightening and darkening colors in the theme. -Customizing the grid -==================== +## Customizing the grid ## - grid: { +```js +grid: { show: boolean aboveData: boolean color: color backgroundColor: color/gradient or null + margin: number or margin object labelMargin: number axisMargin: number markings: array of markings or (fn: axes -> array of markings) - borderWidth: number - borderColor: color or null + borderWidth: number or object with "top", "right", "bottom" and "left" properties with different widths + borderColor: color or null or object with "top", "right", "bottom" and "left" properties with different colors minBorderMargin: number or null clickable: boolean hoverable: boolean autoHighlight: boolean mouseActiveRadius: number - } +} - interaction: { +interaction: { redrawOverlayInterval: number or -1 - } +} +``` The grid is the thing with the axes and a number of ticks. Many of the things in the grid are configured under the individual axes, but not @@ -694,17 +896,32 @@ You can turn off the whole grid including tick labels by setting "show" to false. "aboveData" determines whether the grid is drawn above the data or below (below is default). +"margin" is the space in pixels between the canvas edge and the grid, +which can be either a number or an object with individual margins for +each side, in the form: + +```js +margin: { + top: top margin in pixels + left: left margin in pixels + bottom: bottom margin in pixels + right: right margin in pixels +} +``` + "labelMargin" is the space in pixels between tick labels and axis line, and "axisMargin" is the space in pixels between axes when there are two next to each other. "borderWidth" is the width of the border around the plot. Set it to 0 -to disable the border. You can also set "borderColor" if you want the -border to have a different color than the grid lines. -"minBorderMargin" controls the default minimum margin around the -border - it's used to make sure that points aren't accidentally -clipped by the canvas edge so by default the value is computed from -the point radius. +to disable the border. Set it to an object with "top", "right", +"bottom" and "left" properties to use different widths. You can +also set "borderColor" if you want the border to have a different color +than the grid lines. Set it to an object with "top", "right", "bottom" +and "left" properties to use different colors. "minBorderMargin" controls +the default minimum margin around the border - it's used to make sure +that points aren't accidentally clipped by the canvas edge so by default +the value is computed from the point radius. "markings" is used to draw simple lines and rectangular areas in the background of the plot. You can either specify an array of ranges on @@ -716,7 +933,9 @@ the axes for the plot in an object as the first parameter. You can set the color of markings by specifying "color" in the ranges object. Here's an example array: - markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ] +```js +markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ] +``` If you leave out one of the values, that value is assumed to go to the border of the plot. So for example if you only specify { xaxis: { @@ -725,20 +944,23 @@ bottom of the plot in the x range 0-2. A line is drawn if from and to are the same, e.g. - markings: [ { yaxis: { from: 1, to: 1 } }, ... ] +```js +markings: [ { yaxis: { from: 1, to: 1 } }, ... ] +``` would draw a line parallel to the x axis at y = 1. You can control the line width with "lineWidth" in the range object. An example function that makes vertical stripes might look like this: - markings: function (axes) { +```js +markings: function (axes) { var markings = []; for (var x = Math.floor(axes.xaxis.min); x < axes.xaxis.max; x += 2) - markings.push({ xaxis: { from: x, to: x + 1 } }); + markings.push({ xaxis: { from: x, to: x + 1 } }); return markings; - } - +} +``` If you set "clickable" to true, the plot will listen for click events on the plot area and fire a "plotclick" event on the placeholder with @@ -755,32 +977,38 @@ the highlight/unhighlight plot methods described elsewhere. You can use "plotclick" and "plothover" events like this: - $.plot($("#placeholder"), [ d ], { grid: { clickable: true } }); +```js +$.plot($("#placeholder"), [ d ], { grid: { clickable: true } }); - $("#placeholder").bind("plotclick", function (event, pos, item) { - alert("You clicked at " + pos.x + ", " + pos.y); - // axis coordinates for other axes, if present, are in pos.x2, pos.x3, ... - // if you need global screen coordinates, they are pos.pageX, pos.pageY +$("#placeholder").bind("plotclick", function (event, pos, item) { + alert("You clicked at " + pos.x + ", " + pos.y); + // axis coordinates for other axes, if present, are in pos.x2, pos.x3, ... + // if you need global screen coordinates, they are pos.pageX, pos.pageY - if (item) { - highlight(item.series, item.datapoint); - alert("You clicked a point!"); - } - }); + if (item) { + highlight(item.series, item.datapoint); + alert("You clicked a point!"); + } +}); +``` The item object in this example is either null or a nearby object on the form: - item: { - datapoint: the point, e.g. [0, 2] - dataIndex: the index of the point in the data array - series: the series object - seriesIndex: the index of the series - pageX, pageY: the global screen coordinates of the point - } +```js +item: { + datapoint: the point, e.g. [0, 2] + dataIndex: the index of the point in the data array + series: the series object + seriesIndex: the index of the series + pageX, pageY: the global screen coordinates of the point +} +``` For instance, if you have specified the data like this - $.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...); +```js +$.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...); +``` and the mouse is near the point (7, 3), "datapoint" is [7, 3], "dataIndex" will be 1, "series" is a normalized series object with @@ -800,7 +1028,11 @@ radius, Flot chooses the closest item. For bars, the top-most bar If you want to disable interactivity for a specific data series, you can set "hoverable" and "clickable" to false in the options for that -series, like this { data: [...], label: "Foo", clickable: false }. +series, like this: + +```js +{ data: [...], label: "Foo", clickable: false } +``` "redrawOverlayInterval" specifies the maximum time to delay a redraw of interactive things (this works as a rate limiting device). The @@ -808,48 +1040,54 @@ default is capped to 60 frames per second. You can set it to -1 to disable the rate limiting. -Specifying gradients -==================== +## Specifying gradients ## A gradient is specified like this: - { colors: [ color1, color2, ... ] } +```js +{ colors: [ color1, color2, ... ] } +``` For instance, you might specify a background on the grid going from black to gray like this: - grid: { +```js +grid: { backgroundColor: { colors: ["#000", "#999"] } - } +} +``` For the series you can specify the gradient as an object that specifies the scaling of the brightness and the opacity of the series color, e.g. - { colors: [{ opacity: 0.8 }, { brightness: 0.6, opacity: 0.8 } ] } +```js +{ colors: [{ opacity: 0.8 }, { brightness: 0.6, opacity: 0.8 } ] } +``` where the first color simply has its alpha scaled, whereas the second is also darkened. For instance, for bars the following makes the bars gradually disappear, without outline: - bars: { - show: true, - lineWidth: 0, - fill: true, - fillColor: { colors: [ { opacity: 0.8 }, { opacity: 0.1 } ] } - } - +```js +bars: { + show: true, + lineWidth: 0, + fill: true, + fillColor: { colors: [ { opacity: 0.8 }, { opacity: 0.1 } ] } +} +``` + Flot currently only supports vertical gradients drawn from top to bottom because that's what works with IE. -Plot Methods ------------- +## Plot Methods ## The Plot object returned from the plot function has some methods you can call: - - highlight(series, datapoint) + - highlight(series, datapoint) Highlight a specific datapoint in the data series. You can either specify the actual objects, e.g. if you got them from a @@ -857,8 +1095,7 @@ can call: highlight(1, 3) to highlight the fourth point in the second series (remember, zero-based indexing). - - - unhighlight(series, datapoint) or unhighlight() + - unhighlight(series, datapoint) or unhighlight() Remove the highlighting of the point, same parameters as highlight. @@ -866,8 +1103,7 @@ can call: If you call unhighlight with no parameters, e.g. as plot.unhighlight(), all current highlights are removed. - - - setData(data) + - setData(data) You can use this to reset the data used. Note that axis scaling, ticks, legend etc. will not be recomputed (use setupGrid() to do @@ -879,8 +1115,7 @@ can call: for large datasets, almost all the time is consumed in draw() plotting the data so in this case don't bother. - - - setupGrid() + - setupGrid() Recalculate and set axis scaling, ticks, legend etc. @@ -889,12 +1124,12 @@ can call: the labels and the legend, but not the actual tick lines because they're drawn on the canvas. You need to call draw() to get the canvas redrawn. - - - draw() + + - draw() Redraws the plot canvas. - - triggerRedrawOverlay() + - triggerRedrawOverlay() Schedules an update of an overlay canvas used for drawing interactive things like a selection and point highlights. This @@ -903,41 +1138,42 @@ can call: redraws (e.g. from a mousemove). You can get to the overlay by setting up a drawOverlay hook. - - width()/height() + - width()/height() Gets the width and height of the plotting area inside the grid. This is smaller than the canvas or placeholder dimensions as some extra space is needed (e.g. for labels). - - offset() + - offset() Returns the offset of the plotting area inside the grid relative to the document, useful for instance for calculating mouse positions (event.pageX/Y minus this offset is the pixel position inside the plot). - - pointOffset({ x: xpos, y: ypos }) + - pointOffset({ x: xpos, y: ypos }) Returns the calculated offset of the data point at (x, y) in data - space within the placeholder div. If you are working with multiple axes, you - can specify the x and y axis references, e.g. + space within the placeholder div. If you are working with multiple + axes, you can specify the x and y axis references, e.g. + ```js o = pointOffset({ x: xpos, y: ypos, xaxis: 2, yaxis: 3 }) // o.left and o.top now contains the offset within the div + ```` - - resize() + - resize() Tells Flot to resize the drawing canvas to the size of the placeholder. You need to run setupGrid() and draw() afterwards as canvas resizing is a destructive operation. This is used internally by the resize plugin. - - shutdown() + - shutdown() Cleans up any event handlers Flot has currently registered. This is used internally. - There are also some members that let you peek inside the internal workings of Flot which is useful in some cases. Note that if you change something in the objects returned, you're changing the objects used by @@ -950,9 +1186,11 @@ Flot to keep track of its state, so be careful. options. So for instance to find out what color Flot has assigned to the data series, you could do this: - var series = plot.getData(); - for (var i = 0; i < series.length; ++i) + ```js + var series = plot.getData(); + for (var i = 0; i < series.length; ++i) alert(series[i].color); + ``` A notable other interesting field besides color is datapoints which has a field "points" with the normalized data points in a @@ -1006,8 +1244,7 @@ Flot to keep track of its state, so be careful. setupGrid() or triggerRedrawOverlay() to see the change. -Hooks -===== +## Hooks ## In addition to the public methods, the Plot object also has some hooks that can be used to modify the plotting process. You can install a @@ -1040,6 +1277,7 @@ You can add them through the "hooks" option, and they are also available after the plot is constructed as the "hooks" attribute on the returned plot object, e.g. +```js // define a simple draw hook function hellohook(plot, canvascontext) { alert("hello!"); }; @@ -1048,6 +1286,7 @@ plot object, e.g. // we can now find it again in plot.hooks.draw[0] unless a plugin // has added other hooks +``` The available hooks are described below. All hook callbacks get the plot object as first parameter. You can find some examples of defined @@ -1055,147 +1294,183 @@ hooks in the plugins bundled with Flot. - processOptions [phase 1] - function(plot, options) + ```function(plot, options)``` - Called after Flot has parsed and merged options. Useful in the - instance where customizations beyond simple merging of default - values is needed. A plugin might use it to detect that it has been - enabled and then turn on or off other options. + Called after Flot has parsed and merged options. Useful in the + instance where customizations beyond simple merging of default + values is needed. A plugin might use it to detect that it has been + enabled and then turn on or off other options. - processRawData [phase 3] - function(plot, series, data, datapoints) + ```function(plot, series, data, datapoints)``` - Called before Flot copies and normalizes the raw data for the given - series. If the function fills in datapoints.points with normalized - points and sets datapoints.pointsize to the size of the points, - Flot will skip the copying/normalization step for this series. + Called before Flot copies and normalizes the raw data for the given + series. If the function fills in datapoints.points with normalized + points and sets datapoints.pointsize to the size of the points, + Flot will skip the copying/normalization step for this series. - In any case, you might be interested in setting datapoints.format, - an array of objects for specifying how a point is normalized and - how it interferes with axis scaling. + In any case, you might be interested in setting datapoints.format, + an array of objects for specifying how a point is normalized and + how it interferes with axis scaling. It accepts the following options: + + ```js + { + x, y: boolean, + number: boolean, + required: boolean, + defaultValue: value, + autoscale: boolean + } + ``` + + "x" and "y" specify whether the value is plotted against the x or y axis, + and is currently used only to calculate axis min-max ranges. The default + format array, for example, looks like this: - The default format array for points is something along the lines of: + ```js + [ + { x: true, number: true, required: true }, + { y: true, number: true, required: true } + ] + ``` - [ - { x: true, number: true, required: true }, - { y: true, number: true, required: true } - ] + This indicates that a point, i.e. [0, 25], consists of two values, with the + first being plotted on the x axis and the second on the y axis. - The first object means that for the first coordinate it should be - taken into account when scaling the x axis, that it must be a - number, and that it is required - so if it is null or cannot be - converted to a number, the whole point will be zeroed out with - nulls. Beyond these you can also specify "defaultValue", a value to - use if the coordinate is null. This is for instance handy for bars - where one can omit the third coordinate (the bottom of the bar) - which then defaults to 0. + If "number" is true, then the value must be numeric, and is set to null if + it cannot be converted to a number. + "defaultValue" provides a fallback in case the original value is null. This + is for instance handy for bars, where one can omit the third coordinate + (the bottom of the bar), which then defaults to zero. + + If "required" is true, then the value must exist (be non-null) for the + point as a whole to be valid. If no value is provided, then the entire + point is cleared out with nulls, turning it into a gap in the series. + + "autoscale" determines whether the value is considered when calculating an + automatic min-max range for the axes that the value is plotted against. - processDatapoints [phase 3] - function(plot, series, datapoints) - - Called after normalization of the given series but before finding - min/max of the data points. This hook is useful for implementing data - transformations. "datapoints" contains the normalized data points in - a flat array as datapoints.points with the size of a single point - given in datapoints.pointsize. Here's a simple transform that - multiplies all y coordinates by 2: + ```function(plot, series, datapoints)``` + + Called after normalization of the given series but before finding + min/max of the data points. This hook is useful for implementing data + transformations. "datapoints" contains the normalized data points in + a flat array as datapoints.points with the size of a single point + given in datapoints.pointsize. Here's a simple transform that + multiplies all y coordinates by 2: - function multiply(plot, series, datapoints) { - var points = datapoints.points, ps = datapoints.pointsize; - for (var i = 0; i < points.length; i += ps) - points[i + 1] *= 2; - } + ```js + function multiply(plot, series, datapoints) { + var points = datapoints.points, ps = datapoints.pointsize; + for (var i = 0; i < points.length; i += ps) + points[i + 1] *= 2; + } + ``` + + Note that you must leave datapoints in a good condition as Flot + doesn't check it or do any normalization on it afterwards. + + - processOffset [phase 4] + + ```function(plot, offset)``` + + Called after Flot has initialized the plot's offset, but before it + draws any axes or plot elements. This hook is useful for customizing + the margins between the grid and the edge of the canvas. "offset" is + an object with attributes "top", "bottom", "left" and "right", + corresponding to the margins on the four sides of the plot. - Note that you must leave datapoints in a good condition as Flot - doesn't check it or do any normalization on it afterwards. + - drawBackground [phase 5] + ```function(plot, canvascontext)``` + + Called before all other drawing operations. Used to draw backgrounds + or other custom elements before the plot or axes have been drawn. - drawSeries [phase 5] - function(plot, canvascontext, series) + ```function(plot, canvascontext, series)``` + + Hook for custom drawing of a single series. Called just before the + standard drawing routine has been called in the loop that draws + each series. - Hook for custom drawing of a single series. Called just before the - standard drawing routine has been called in the loop that draws - each series. - - - draw [phase 5] - function(plot, canvascontext) - - Hook for drawing on the canvas. Called after the grid is drawn - (unless it's disabled or grid.aboveData is set) and the series have - been plotted (in case any points, lines or bars have been turned - on). For examples of how to draw things, look at the source code. - - - - bindEvents [phase 6] + ```function(plot, canvascontext)``` - function(plot, eventHolder) + Hook for drawing on the canvas. Called after the grid is drawn + (unless it's disabled or grid.aboveData is set) and the series have + been plotted (in case any points, lines or bars have been turned + on). For examples of how to draw things, look at the source code. - Called after Flot has setup its event handlers. Should set any - necessary event handlers on eventHolder, a jQuery object with the - canvas, e.g. + - bindEvents [phase 6] - function (plot, eventHolder) { - eventHolder.mousedown(function (e) { - alert("You pressed the mouse at " + e.pageX + " " + e.pageY); - }); - } + ```function(plot, eventHolder)``` - Interesting events include click, mousemove, mouseup/down. You can - use all jQuery events. Usually, the event handlers will update the - state by drawing something (add a drawOverlay hook and call - triggerRedrawOverlay) or firing an externally visible event for - user code. See the crosshair plugin for an example. - - Currently, eventHolder actually contains both the static canvas - used for the plot itself and the overlay canvas used for - interactive features because some versions of IE get the stacking - order wrong. The hook only gets one event, though (either for the - overlay or for the static canvas). + Called after Flot has setup its event handlers. Should set any + necessary event handlers on eventHolder, a jQuery object with the + canvas, e.g. - Note that custom plot events generated by Flot are not generated on - eventHolder, but on the div placeholder supplied as the first - argument to the plot call. You can get that with - plot.getPlaceholder() - that's probably also the one you should use - if you need to fire a custom event. + ```js + function (plot, eventHolder) { + eventHolder.mousedown(function (e) { + alert("You pressed the mouse at " + e.pageX + " " + e.pageY); + }); + } + ``` + Interesting events include click, mousemove, mouseup/down. You can + use all jQuery events. Usually, the event handlers will update the + state by drawing something (add a drawOverlay hook and call + triggerRedrawOverlay) or firing an externally visible event for + user code. See the crosshair plugin for an example. + + Currently, eventHolder actually contains both the static canvas + used for the plot itself and the overlay canvas used for + interactive features because some versions of IE get the stacking + order wrong. The hook only gets one event, though (either for the + overlay or for the static canvas). + + Note that custom plot events generated by Flot are not generated on + eventHolder, but on the div placeholder supplied as the first + argument to the plot call. You can get that with + plot.getPlaceholder() - that's probably also the one you should use + if you need to fire a custom event. - drawOverlay [phase 7] - function (plot, canvascontext) - - The drawOverlay hook is used for interactive things that need a - canvas to draw on. The model currently used by Flot works the way - that an extra overlay canvas is positioned on top of the static - canvas. This overlay is cleared and then completely redrawn - whenever something interesting happens. This hook is called when - the overlay canvas is to be redrawn. + ```function (plot, canvascontext)``` - "canvascontext" is the 2D context of the overlay canvas. You can - use this to draw things. You'll most likely need some of the - metrics computed by Flot, e.g. plot.width()/plot.height(). See the - crosshair plugin for an example. + The drawOverlay hook is used for interactive things that need a + canvas to draw on. The model currently used by Flot works the way + that an extra overlay canvas is positioned on top of the static + canvas. This overlay is cleared and then completely redrawn + whenever something interesting happens. This hook is called when + the overlay canvas is to be redrawn. + "canvascontext" is the 2D context of the overlay canvas. You can + use this to draw things. You'll most likely need some of the + metrics computed by Flot, e.g. plot.width()/plot.height(). See the + crosshair plugin for an example. - shutdown [phase 8] - function (plot, eventHolder) + ```function (plot, eventHolder)``` - Run when plot.shutdown() is called, which usually only happens in - case a plot is overwritten by a new plot. If you're writing a - plugin that adds extra DOM elements or event handlers, you should - add a callback to clean up after you. Take a look at the section in - PLUGINS.txt for more info. + Run when plot.shutdown() is called, which usually only happens in + case a plot is overwritten by a new plot. If you're writing a + plugin that adds extra DOM elements or event handlers, you should + add a callback to clean up after you. Take a look at the section in + the [PLUGINS](PLUGINS.md) document for more info. -Plugins -------- +## Plugins ## Plugins extend the functionality of Flot. To use a plugin, simply include its Javascript file after Flot in the HTML page. @@ -1214,11 +1489,10 @@ from the "option" attribute of the plugin. The init function gets a reference to the plot object created and uses this to register hooks and add new public methods if needed. -See the PLUGINS.txt file for details on how to write a plugin. As the +See the [PLUGINS](PLUGINS.md) document for details on how to write a plugin. As the above description hints, it's actually pretty easy. -Version number --------------- +## Version number ## -The version number of Flot is available in $.plot.version. +The version number of Flot is available in ```$.plot.version```. diff --git a/Web Server/flot/CONTRIBUTING.md b/Web Server/flot/CONTRIBUTING.md new file mode 100644 index 0000000..3e6e43a --- /dev/null +++ b/Web Server/flot/CONTRIBUTING.md @@ -0,0 +1,98 @@ +## Contributing to Flot ## + +We welcome all contributions, but following these guidelines results in less +work for us, and a faster and better response. + +### Issues ### + +Issues are not a way to ask general questions about Flot. If you see unexpected +behavior but are not 100% certain that it is a bug, please try posting to the +[forum](http://groups.google.com/group/flot-graphs) first, and confirm that +what you see is really a Flot problem before creating a new issue for it. When +reporting a bug, please include a working demonstration of the problem, if +possible, or at least a clear description of the options you're using and the +environment (browser and version, jQuery version, other libraries) that you're +running under. + +If you have suggestions for new features, or changes to existing ones, we'd +love to hear them! Please submit each suggestion as a separate new issue. + +If you would like to work on an existing issue, please make sure it is not +already assigned to someone else. If an issue is assigned to someone, that +person has already started working on it. So, pick unassigned issues to prevent +duplicated effort. + +### Pull Requests ### + +To make merging as easy as possible, please keep these rules in mind: + + 1. Submit new features or architectural changes to the *<version>-work* + branch for the next major release. Submit bug fixes to the master branch. + + 2. Divide larger changes into a series of small, logical commits with + descriptive messages. + + 3. Rebase, if necessary, before submitting your pull request, to reduce the + work we need to do to merge it. + + 4. Format your code according to the style guidelines below. + +### Flot Style Guidelines ### + +Flot follows the [jQuery Core Style Guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines), +with the following updates and exceptions: + +#### Spacing #### + +Use four-space indents, no tabs. Do not add horizontal space around parameter +lists, loop definitions, or array/object indices. For example: + +```js + for ( var i = 0; i < data.length; i++ ) { // This block is wrong! + if ( data[ i ] > 1 ) { + data[ i ] = 2; + } + } + + for (var i = 0; i < data.length; i++) { // This block is correct! + if (data[i] > 1) { + data[i] = 2; + } + } +``` + +#### Comments #### + +Use [jsDoc](http://usejsdoc.org) comments for all file and function headers. +Use // for all inline and block comments, regardless of length. + +All // comment blocks should have an empty line above *and* below them. For +example: + +```js + var a = 5; + + // We're going to loop here + // TODO: Make this loop faster, better, stronger! + + for (var x = 0; x < 10; x++) {} +``` + +#### Wrapping #### + +Block comments should be wrapped at 80 characters. + +Code should attempt to wrap at 80 characters, but may run longer if wrapping +would hurt readability more than having to scroll horizontally. This is a +judgement call made on a situational basis. + +Statements containing complex logic should not be wrapped arbitrarily if they +do not exceed 80 characters. For example: + +```js + if (a == 1 && // This block is wrong! + b == 2 && + c == 3) {} + + if (a == 1 && b == 2 && c == 3) {} // This block is correct! +``` diff --git a/web/flot/FAQ.txt b/Web Server/flot/FAQ.md similarity index 76% rename from web/flot/FAQ.txt rename to Web Server/flot/FAQ.md index 2bde48a..9131e04 100644 --- a/web/flot/FAQ.txt +++ b/Web Server/flot/FAQ.md @@ -1,9 +1,8 @@ -Frequently asked questions --------------------------- +## Frequently asked questions ## -Q: How much data can Flot cope with? +#### How much data can Flot cope with? #### -A: Flot will happily draw everything you send to it so the answer +Flot will happily draw everything you send to it so the answer depends on the browser. The excanvas emulation used for IE (built with VML) makes IE by far the slowest browser so be sure to test with that if IE users are in your target group (for large plots in IE, you can @@ -15,36 +14,36 @@ downsampling/aggregation as this is near the resolution limit of the chart anyway. If you downsample server-side, you also save bandwidth. -Q: Flot isn't working when I'm using JSON data as source! +#### Flot isn't working when I'm using JSON data as source! #### -A: Actually, Flot loves JSON data, you just got the format wrong. +Actually, Flot loves JSON data, you just got the format wrong. Double check that you're not inputting strings instead of numbers, like [["0", "-2.13"], ["5", "4.3"]]. This is most common mistake, and the error might not show up immediately because Javascript can do some conversion automatically. -Q: Can I export the graph? +#### Can I export the graph? #### -A: You can grab the image rendered by the canvas element used by Flot +You can grab the image rendered by the canvas element used by Flot as a PNG or JPEG (remember to set a background). Note that it won't include anything not drawn in the canvas (such as the legend). And it doesn't work with excanvas which uses VML, but you could try Flashcanvas. -Q: The bars are all tiny in time mode? +#### The bars are all tiny in time mode? #### -A: It's not really possible to determine the bar width automatically. +It's not really possible to determine the bar width automatically. So you have to set the width with the barWidth option which is NOT in pixels, but in the units of the x axis (or the y axis for horizontal bars). For time mode that's milliseconds so the default value of 1 makes the bars 1 millisecond wide. -Q: Can I use Flot with libraries like Mootools or Prototype? +#### Can I use Flot with libraries like Mootools or Prototype? #### -A: Yes, Flot supports it out of the box and it's easy! Just use jQuery +Yes, Flot supports it out of the box and it's easy! Just use jQuery instead of $, e.g. call jQuery.plot instead of $.plot and use jQuery(something) instead of $(something). As a convenience, you can put in a DOM element for the graph placeholder where the examples and @@ -56,9 +55,9 @@ libraries, see the documentation in jQuery ("Using jQuery with other libraries") for details. -Q: Flot doesn't work with [insert name of Javascript UI framework]! +#### Flot doesn't work with [insert name of Javascript UI framework]! #### -A: Flot is using standard HTML to make charts. If this is not working, +Flot is using standard HTML to make charts. If this is not working, it's probably because the framework you're using is doing something weird with the DOM or with the CSS that is interfering with Flot. diff --git a/web/flot/LICENSE.txt b/Web Server/flot/LICENSE.txt similarity index 95% rename from web/flot/LICENSE.txt rename to Web Server/flot/LICENSE.txt index 9cd1e6b..67f4625 100644 --- a/web/flot/LICENSE.txt +++ b/Web Server/flot/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2007-2011 IOLA and Ole Laursen +Copyright (c) 2007-2013 IOLA and Ole Laursen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/web/flot/Makefile b/Web Server/flot/Makefile similarity index 82% rename from web/flot/Makefile rename to Web Server/flot/Makefile index b300f1a..2e070d0 100644 --- a/web/flot/Makefile +++ b/Web Server/flot/Makefile @@ -7,3 +7,6 @@ all: $(patsubst %.js,%.min.js,$(filter-out %.min.js,$(wildcard *.js))) %.min.js: %.js yui-compressor $< -o $@ + +test: + ./node_modules/.bin/jshint *jquery.flot.js diff --git a/Web Server/flot/NEWS.md b/Web Server/flot/NEWS.md new file mode 100644 index 0000000..a99b65e --- /dev/null +++ b/Web Server/flot/NEWS.md @@ -0,0 +1,978 @@ +## Flot 0.8.2 ## + +### Changes ### + + - Added a plot.destroy method as a way to free memory when emptying the plot + placeholder and then re-using it for some other purpose. + (patch by Thodoris Greasidis, issue #1129, pull request #1130) + + - Added a table of contents and PLUGINS link to the API documentation. + (patches by Brian Peiris, pull requests #1064 and #1127) + + - Added Ruby code examples for time conversion. + (patch by Mike Połtyn, pull request #1182) + + - Minor improvements to API.md and README.md. + (patches by Patrik Ragnarsson, pull requests #1085 and #1086) + + - Updated inlined jQuery Resize to the latest version to fix errors. + (reported by Matthew Sabol and sloker, issues #997 ad #1081) + +### Bug fixes ### + + - Fixed an unexpected change in behavior that resulted in duplicate tick + labels when using a plugin, like flot-tickrotor, that overrode tick labels. + (patch by Mark Cote, pull request #1091) + + - Fixed a regression from 0.7 where axis labels were given the wrong width, + causing them to overlap at certain scales and ignore the labelWidth option. + (patch by Benjamin Gram, pull request #1177) + + - Fixed a bug where the second axis in an xaxes/yaxes array incorrectly had + its 'innermost' property set to false or undefined, even if it was on the + other side of the plot from the first axis. This resulted in the axis bar + being visible when it shouldn't have been, which was especially obvious + when the grid had a left/right border width of zero. + (reported by Teq1, fix researched by ryleyb, issue #1056) + + - Fixed an error when using a placeholder that has no font-size property. + (patch by Craig Oldford, pull request #1135) + + - Fixed a regression from 0.7 where nulls at the end of a series were ignored + for purposes of determing the range of the x-axis. + (reported by Munsifali Rashid, issue #1095) + + - If a font size is provided, base the default lineHeight on that size rather + that the font size of the plot placeholder, which may be very different. + (reported by Daniel Hoffmann Bernardes, issue #1131, pull request #1199) + + - Fix broken highlighting for right-aligned bars. + (reported by BeWiBu and Mihai Stanciu, issues #975 and #1093, with further + assistance by Eric Byers, pull request #1120) + + - Prevent white circles from sometimes showing up inside of pie charts. + (reported by Pierre Dubois and Jack Klink, issues #1128 and #1073) + + - Label formatting no longer breaks when a page contains multiple pie charts. + (reported by Brend Wanders, issue #1055) + + - When using multiple axes on opposite sides of the plot, the innermost axis + coming later in the list no longer has its bar drawn incorrectly. + (reported by ryleyb, issue #1056) + + - When removing series labels and redrawing the plot, the legend now updates + correctly even when using an external container. + (patch by Luis Silva, issue #1159, pull request #1160) + + - The pie plugin no longer ignores the value of the left offset option. + (reported by melanker, issue #1136) + + - Fixed a regression from 0.7, where extra padding was added unnecessarily to + sides of the plot where there was no last tick label. + (reported by sknob001, issue #1048, pull request #1200) + + - Fixed incorrect tooltip behavior in the interacting example. + (patch by cleroux, issue #686, pull request #1074) + + - Fixed an error in CSS color extraction with elements outside the DOM. + (patch by execjosh, pull request #1084) + + - Fixed :not selector error when using jQuery without Sizzle. + (patch by Anthony Ryan, pull request #1180) + + - Worked around a browser issue that caused bars to appear un-filled. + (reported by irbian, issue #915) + +## Flot 0.8.1 ## + +### Bug fixes ### + + - Fixed a regression in the time plugin, introduced in 0.8, that caused dates + to align to the minute rather than to the highest appropriate unit. This + caused many x-axes in 0.8 to have different ticks than they did in 0.7. + (reported by Tom Sheppard, patch by Daniel Shapiro, issue #1017, pull + request #1023) + + - Fixed a regression in text rendering, introduced in 0.8, that caused axis + labels with the same text as another label on the same axis to disappear. + More generally, it's again possible to have the same text in two locations. + (issue #1032) + + - Fixed a regression in text rendering, introduced in 0.8, where axis labels + were no longer assigned an explicit width, and their text could not wrap. + (reported by sabregreen, issue #1019) + + - Fixed a regression in the pie plugin, introduced in 0.8, that prevented it + from accepting data in the format '[[x, y]]'. + (patch by Nicolas Morel, pull request #1024) + + - The 'zero' series option and 'autoscale' format option are no longer + ignored when the series contains a null value. + (reported by Daniel Shapiro, issue #1033) + + - Avoid triggering the time-mode plugin exception when there are zero series. + (reported by Daniel Rothig, patch by Mark Raymond, issue #1016) + + - When a custom color palette has fewer colors than the default palette, Flot + no longer fills out the colors with the remainder of the default. + (patch by goorpy, issue #1031, pull request #1034) + + - Fixed missing update for bar highlights after a zoom or other redraw. + (reported by Paolo Valleri, issue #1030) + + - Fixed compatibility with jQuery versions earlier than 1.7. + (patch by Lee Willis, issue #1027, pull request #1027) + + - The mouse wheel no longer scrolls the page when using the navigate plugin. + (patch by vird, pull request #1020) + + - Fixed missing semicolons in the core library. + (reported by Michal Zglinski) + + +## Flot 0.8.0 ## + +### API changes ### + +Support for time series has been moved into a plugin, jquery.flot.time.js. +This results in less code if time series are not used. The functionality +remains the same (plus timezone support, as described below); however, the +plugin must be included if axis.mode is set to "time". + +When the axis mode is "time", the axis option "timezone" can be set to null, +"browser", or a particular timezone (e.g. "America/New_York") to control how +the dates are displayed. If null, the dates are displayed as UTC. If +"browser", the dates are displayed in the time zone of the user's browser. + +Date/time formatting has changed and now follows a proper subset of the +standard strftime specifiers, plus one nonstandard specifier for quarters. +Additionally, if a strftime function is found in the Date object's prototype, +it will be used instead of the built-in formatter. + +Axis tick labels now use the class 'flot-tick-label' instead of 'tickLabel'. +The text containers for each axis now use the classes 'flot-[x|y]-axis' and +'flot-[x|y]#-axis' instead of '[x|y]Axis' and '[x|y]#Axis'. For compatibility +with Flot 0.7 and earlier text will continue to use the old classes as well, +but they are considered deprecated and will be removed in a future version. + +In previous versions the axis 'color' option was used to set the color of tick +marks and their label text. It now controls the color of the axis line, which +previously could not be changed separately, and continues to act as a default +for the tick-mark color. The color of tick label text is now set either by +overriding the 'flot-tick-label' CSS rule or via the axis 'font' option. + +A new plugin, jquery.flot.canvas.js, allows axis tick labels to be rendered +directly to the canvas, rather than using HTML elements. This feature can be +toggled with a simple option, making it easy to create interactive plots in the +browser using HTML, then re-render them to canvas for export as an image. + +The plugin tries to remain as faithful as possible to the original HTML render, +and goes so far as to automatically extract styles from CSS, to avoid having to +provide a separate set of styles when rendering to canvas. Due to limitations +of the canvas text API, the plugin cannot reproduce certain features, including +HTML markup embedded in labels, and advanced text styles such as 'em' units. + +The plugin requires support for canvas text, which may not be present in some +older browsers, even if they support the canvas tag itself. To use the plugin +with these browsers try using a shim such as canvas-text or FlashCanvas. + +The base and overlay canvas are now using the CSS classes "flot-base" and +"flot-overlay" to prevent accidental clashes (issue 540). + +### Changes ### + + - Addition of nonstandard %q specifier to date/time formatting. (patch + by risicle, issue 49) + + - Date/time formatting follows proper subset of strftime specifiers, and + support added for Date.prototype.strftime, if found. (patch by Mark Cote, + issues 419 and 558) + + - Fixed display of year ticks. (patch by Mark Cote, issue 195) + + - Support for time series moved to plugin. (patch by Mark Cote) + + - Display time series in different time zones. (patch by Knut Forkalsrud, + issue 141) + + - Added a canvas plugin to enable rendering axis tick labels to the canvas. + (sponsored by YCharts.com, implementation by Ole Laursen and David Schnur) + + - Support for setting the interval between redraws of the overlay canvas with + redrawOverlayInterval. (suggested in issue 185) + + - Support for multiple thresholds in thresholds plugin. (patch by Arnaud + Bellec, issue 523) + + - Support for plotting categories/textual data directly with new categories + plugin. + + - Tick generators now get the whole axis rather than just min/max. + + - Added processOffset and drawBackground hooks. (suggested in issue 639) + + - Added a grid "margin" option to set the space between the canvas edge and + the grid. + + - Prevent the pie example page from generating single-slice pies. (patch by + Shane Reustle) + + - In addition to "left" and "center", bars now recognize "right" as an + alignment option. (patch by Michael Mayer, issue 520) + + - Switched from toFixed to a much faster default tickFormatter. (patch by + Clemens Stolle) + + - Added to a more helpful error when using a time-mode axis without including + the flot.time plugin. (patch by Yael Elmatad) + + - Added a legend "sorted" option to control sorting of legend entries + independent of their series order. (patch by Tom Cleaveland) + + - Added a series "highlightColor" option to control the color of the + translucent overlay that identifies the dataset when the mouse hovers over + it. (patch by Eric Wendelin and Nate Abele, issues 168 and 299) + + - Added a plugin jquery.flot.errorbars, with an accompanying example, that + adds the ability to plot error bars, commonly used in many kinds of + statistical data visualizations. (patch by Rui Pereira, issue 215) + + - The legend now omits entries whose labelFormatter returns null. (patch by + Tom Cleaveland, Christopher Lambert, and Simon Strandgaard) + + - Added support for high pixel density (retina) displays, resulting in much + crisper charts on such devices. (patch by Olivier Guerriat, additional + fixes by Julien Thomas, maimairel, and Lau Bech Lauritzen) + + - Added the ability to control pie shadow position and alpha via a new pie + 'shadow' option. (patch by Julien Thomas, pull request #78) + + - Added the ability to set width and color for individual sides of the grid. + (patch by Ara Anjargolian, additional fixes by Karl Swedberg, pull requests #855 + and #880) + + - The selection plugin's getSelection now returns null when the selection + has been cleared. (patch by Nick Campbell, pull request #852) + + - Added a new option called 'zero' to bars and filled lines series, to control + whether the y-axis minimum is scaled to fit the data or set to zero. + (patch by David Schnur, issues #316, #529, and #856, pull request #911) + + - The plot function is now also a jQuery chainable property. + (patch by David Schnur, issues #734 and #816, pull request #953) + + - When only a single pie slice is beneath the combine threshold it is no longer + replaced by an 'other' slice. (suggested by Devin Bayer, issue #638) + + - Added lineJoin and minSize options to the selection plugin to control the + corner style and minimum size of the selection, respectively. + (patch by Ruth Linehan, pull request #963) + +### Bug fixes ### + + - Fix problem with null values and pie plugin. (patch by gcruxifix, + issue 500) + + - Fix problem with threshold plugin and bars. (based on patch by + kaarlenkaski, issue 348) + + - Fix axis box calculations so the boxes include the outermost part of the + labels too. + + - Fix problem with event clicking and hovering in IE 8 by updating Excanvas + and removing previous work-around. (test case by Ara Anjargolian) + + - Fix issues with blurry 1px border when some measures aren't integer. + (reported by Ara Anjargolian) + + - Fix bug with formats in the data processor. (reported by Peter Hull, + issue 534) + + - Prevent i from being declared global in extractRange. (reported by + Alexander Obukhov, issue 627) + + - Throw errors in a more cross-browser-compatible manner. (patch by + Eddie Kay) + + - Prevent pie slice outlines from being drawn when the stroke width is zero. + (reported by Chris Minett, issue 585) + + - Updated the navigate plugin's inline copy of jquery.mousewheel to fix + Webkit zoom problems. (reported by Hau Nguyen, issue 685) + + - Axis labels no longer appear as decimals rather than integers in certain + cases. (patch by Clemens Stolle, issue 541) + + - Automatic color generation no longer produces only whites and blacks when + there are many series. (patch by David Schnur and Tom Cleaveland) + + - Fixed an error when custom tick labels weren't provided as strings. (patch + by Shad Downey) + + - Prevented the local insertSteps and fmt variables from becoming global. + (first reported by Marc Bennewitz and Szymon Barglowski, patch by Nick + Campbell, issues #825 and #831, pull request #851) + + - Prevented several threshold plugin variables from becoming global. (patch + by Lasse Dahl Ebert) + + - Fixed various jQuery 1.8 compatibility issues. (issues #814 and #819, + pull request #877) + + - Pie charts with a slice equal to or approaching 100% of the pie no longer + appear invisible. (patch by David Schnur, issues #444, #658, #726, #824 + and #850, pull request #879) + + - Prevented several local variables from becoming global. (patch by aaa707) + + - Ensure that the overlay and primary canvases remain aligned. (issue #670, + pull request #901) + + - Added support for jQuery 1.9 by removing and replacing uses of $.browser. + (analysis and patch by Anthony Ryan, pull request #905) + + - Pie charts no longer disappear when redrawn during a resize or update. + (reported by Julien Bec, issue #656, pull request #910) + + - Avoided floating-point precision errors when calculating pie percentages. + (patch by James Ward, pull request #918) + + - Fixed compatibility with jQuery 1.2.6, which has no 'mouseleave' shortcut. + (reported by Bevan, original pull request #920, replaced by direct patch) + + - Fixed sub-pixel rendering issues with crosshair and selection lines. + (patches by alanayoub and Daniel Shapiro, pull requests #17 and #925) + + - Fixed rendering issues when using the threshold plugin with several series. + (patch by Ivan Novikov, pull request #934) + + - Pie charts no longer disappear when redrawn after calling setData(). + (reported by zengge1984 and pareeohnos, issues #810 and #945) + + - Added a work-around for the problem where points with a lineWidth of zero + still showed up with a visible line. (reported by SalvoSav, issue #842, + patch by Jamie Hamel-Smith, pull request #937) + + - Pie charts now accept values in string form, like other plot types. + (reported by laerdal.no, issue #534) + + - Avoid rounding errors in the threshold plugin. + (reported by jerikojerk, issue #895) + + - Fixed an error when using the navigate plugin with jQuery 1.9.x or later. + (reported by Paolo Valleri, issue #964) + + - Fixed inconsistencies between the highlight and unhighlight functions. + (reported by djamshed, issue #987) + + - Fixed recalculation of tickSize and tickDecimals on calls to setupGrid. + (patch by thecountofzero, pull request #861, issues #860, #1000) + + +## Flot 0.7 ## + +### API changes ### + +Multiple axes support. Code using dual axes should be changed from using +x2axis/y2axis in the options to using an array (although backwards- +compatibility hooks are in place). For instance, + +```js +{ + xaxis: { ... }, x2axis: { ... }, + yaxis: { ... }, y2axis: { ... } +} +``` + +becomes + +```js +{ + xaxes: [ { ... }, { ... } ], + yaxes: [ { ... }, { ... } ] +} +``` + +Note that if you're just using one axis, continue to use the xaxis/yaxis +directly (it now sets the default settings for the arrays). Plugins touching +the axes must be ported to take the extra axes into account, check the source +to see some examples. + +A related change is that the visibility of axes is now auto-detected. So if +you were relying on an axis to show up even without any data in the chart, you +now need to set the axis "show" option explicitly. + +"tickColor" on the grid options is now deprecated in favour of a corresponding +option on the axes, so: + +```js +{ grid: { tickColor: "#000" }} +``` + +becomes + +```js +{ xaxis: { tickColor: "#000"}, yaxis: { tickColor: "#000"} } +``` + +But if you just configure a base color Flot will now autogenerate a tick color +by adding transparency. Backwards-compatibility hooks are in place. + +Final note: now that IE 9 is coming out with canvas support, you may want to +adapt the excanvas include to skip loading it in IE 9 (the examples have been +adapted thanks to Ryley Breiddal). An alternative to excanvas using Flash has +also surfaced, if your graphs are slow in IE, you may want to give it a spin: + + http://code.google.com/p/flashcanvas/ + +### Changes ### + + - Support for specifying a bottom for each point for line charts when filling + them, this means that an arbitrary bottom can be used instead of just the x + axis. (based on patches patiently provided by Roman V. Prikhodchenko) + + - New fillbetween plugin that can compute a bottom for a series from another + series, useful for filling areas between lines. + + See new example percentiles.html for a use case. + + - More predictable handling of gaps for the stacking plugin, now all + undefined ranges are skipped. + + - Stacking plugin can stack horizontal bar charts. + + - Navigate plugin now redraws the plot while panning instead of only after + the fact. (raised by lastthemy, issue 235) + + Can be disabled by setting the pan.frameRate option to null. + + - Date formatter now accepts %0m and %0d to get a zero-padded month or day. + (issue raised by Maximillian Dornseif) + + - Revamped internals to support an unlimited number of axes, not just dual. + (sponsored by Flight Data Services, www.flightdataservices.com) + + - New setting on axes, "tickLength", to control the size of ticks or turn + them off without turning off the labels. + + - Axis labels are now put in container divs with classes, for instance labels + in the x axes can be reached via ".xAxis .tickLabel". + + - Support for setting the color of an axis. (sponsored by Flight Data + Services, www.flightdataservices.com) + + - Tick color is now auto-generated as the base color with some transparency, + unless you override it. + + - Support for aligning ticks in the axes with "alignTicksWithAxis" to ensure + that they appear next to each other rather than in between, at the expense + of possibly awkward tick steps. (sponsored by Flight Data Services, + www.flightdataservices.com) + + - Support for customizing the point type through a callback when plotting + points and new symbol plugin with some predefined point types. (sponsored + by Utility Data Corporation) + + - Resize plugin for automatically redrawing when the placeholder changes + size, e.g. on window resizes. (sponsored by Novus Partners) + + A resize() method has been added to plot object facilitate this. + + - Support Infinity/-Infinity for plotting asymptotes by hacking it into + +/-Number.MAX_VALUE. (reported by rabaea.mircea) + + - Support for restricting navigate plugin to not pan/zoom an axis. (based on + patch by kkaefer) + + - Support for providing the drag cursor for the navigate plugin as an option. + (based on patch by Kelly T. Moore) + + - Options for controlling whether an axis is shown or not (suggestion by Timo + Tuominen) and whether to reserve space for it even if it isn't shown. + + - New attribute $.plot.version with the Flot version as a string. + + - The version comment is now included in the minified jquery.flot.min.js. + + - New options.grid.minBorderMargin for adjusting the minimum margin provided + around the border (based on patch by corani, issue 188). + + - Refactor replot behaviour so Flot tries to reuse the existing canvas, + adding shutdown() methods to the plot. (based on patch by Ryley Breiddal, + issue 269) + + This prevents a memory leak in Chrome and hopefully makes replotting faster + for those who are using $.plot instead of .setData()/.draw(). Also update + jQuery to 1.5.1 to prevent IE leaks fixed in jQuery. + + - New real-time line chart example. + + - New hooks: drawSeries, shutdown. + +### Bug fixes ### + + - Fixed problem with findNearbyItem and bars on top of each other. (reported + by ragingchikn, issue 242) + + - Fixed problem with ticks and the border. (based on patch from + ultimatehustler69, issue 236) + + - Fixed problem with plugins adding options to the series objects. + + - Fixed a problem introduced in 0.6 with specifying a gradient with: + + ```{brightness: x, opacity: y }``` + + - Don't use $.browser.msie, check for getContext on the created canvas element + instead and try to use excanvas if it's not found. + + Fixes IE 9 compatibility. + + - highlight(s, index) was looking up the point in the original s.data instead + of in the computed datapoints array, which breaks with plugins that modify + the datapoints, such as the stacking plugin. (reported by curlypaul924, + issue 316) + + - More robust handling of axis from data passed in from getData(). (reported) + by Morgan) + + - Fixed problem with turning off bar outline. (fix by Jordi Castells, + issue 253) + + - Check the selection passed into setSelection in the selection + plugin, to guard against errors when synchronizing plots (fix by Lau + Bech Lauritzen). + + - Fix bug in crosshair code with mouseout resetting the crosshair even + if it is locked (fix by Lau Bech Lauritzen and Banko Adam). + + - Fix bug with points plotting using line width from lines rather than + points. + + - Fix bug with passing non-array 0 data (for plugins that don't expect + arrays, patch by vpapp1). + + - Fix errors in JSON in examples so they work with jQuery 1.4.2 + (fix reported by honestbleeps, issue 357). + + - Fix bug with tooltip in interacting.html, this makes the tooltip + much smoother (fix by bdkahn). Fix related bug inside highlighting + handler in Flot. + + - Use closure trick to make inline colorhelpers plugin respect + jQuery.noConflict(true), renaming the global jQuery object (reported + by Nick Stielau). + + - Listen for mouseleave events and fire a plothover event with empty + item when it occurs to drop highlights when the mouse leaves the + plot (reported by by outspirit). + + - Fix bug with using aboveData with a background (reported by + amitayd). + + - Fix possible excanvas leak (report and suggested fix by tom9729). + + - Fix bug with backwards compatibility for shadowSize = 0 (report and + suggested fix by aspinak). + + - Adapt examples to skip loading excanvas (fix by Ryley Breiddal). + + - Fix bug that prevent a simple f(x) = -x transform from working + correctly (fix by Mike, issue 263). + + - Fix bug in restoring cursor in navigate plugin (reported by Matteo + Gattanini, issue 395). + + - Fix bug in picking items when transform/inverseTransform is in use + (reported by Ofri Raviv, and patches and analysis by Jan and Tom + Paton, issue 334 and 467). + + - Fix problem with unaligned ticks and hover/click events caused by + padding on the placeholder by hardcoding the placeholder padding to + 0 (reported by adityadineshsaxena, Matt Sommer, Daniel Atos and some + other people, issue 301). + + - Update colorhelpers plugin to avoid dying when trying to parse an + invalid string (reported by cadavor, issue 483). + + + +## Flot 0.6 ## + +### API changes ### + +Selection support has been moved to a plugin. Thus if you're passing +selection: { mode: something }, you MUST include the file +jquery.flot.selection.js after jquery.flot.js. This reduces the size of +base Flot and makes it easier to customize the selection as well as +improving code clarity. The change is based on a patch from andershol. + +In the global options specified in the $.plot command, "lines", "points", +"bars" and "shadowSize" have been moved to a sub-object called "series": + +```js +$.plot(placeholder, data, { lines: { show: true }}) +``` + +should be changed to + +```js + $.plot(placeholder, data, { series: { lines: { show: true }}}) +``` + +All future series-specific options will go into this sub-object to +simplify plugin writing. Backward-compatibility code is in place, so +old code should not break. + +"plothover" no longer provides the original data point, but instead a +normalized one, since there may be no corresponding original point. + +Due to a bug in previous versions of jQuery, you now need at least +jQuery 1.2.6. But if you can, try jQuery 1.3.2 as it got some improvements +in event handling speed. + +## Changes ## + + - Added support for disabling interactivity for specific data series. + (request from Ronald Schouten and Steve Upton) + + - Flot now calls $() on the placeholder and optional legend container passed + in so you can specify DOM elements or CSS expressions to make it easier to + use Flot with libraries like Prototype or Mootools or through raw JSON from + Ajax responses. + + - A new "plotselecting" event is now emitted while the user is making a + selection. + + - The "plothover" event is now emitted immediately instead of at most 10 + times per second, you'll have to put in a setTimeout yourself if you're + doing something really expensive on this event. + + - The built-in date formatter can now be accessed as $.plot.formatDate(...) + (suggestion by Matt Manela) and even replaced. + + - Added "borderColor" option to the grid. (patches from Amaury Chamayou and + Mike R. Williamson) + + - Added support for gradient backgrounds for the grid. (based on patch from + Amaury Chamayou, issue 90) + + The "setting options" example provides a demonstration. + + - Gradient bars. (suggestion by stefpet) + + - Added a "plotunselected" event which is triggered when the selection is + removed, see "selection" example. (suggestion by Meda Ugo) + + - The option legend.margin can now specify horizontal and vertical margins + independently. (suggestion by someone who's annoyed) + + - Data passed into Flot is now copied to a new canonical format to enable + further processing before it hits the drawing routines. As a side-effect, + this should make Flot more robust in the face of bad data. (issue 112) + + - Step-wise charting: line charts have a new option "steps" that when set to + true connects the points with horizontal/vertical steps instead of diagonal + lines. + + - The legend labelFormatter now passes the series in addition to just the + label. (suggestion by Vincent Lemeltier) + + - Horizontal bars (based on patch by Jason LeBrun). + + - Support for partial bars by specifying a third coordinate, i.e. they don't + have to start from the axis. This can be used to make stacked bars. + + - New option to disable the (grid.show). + + - Added pointOffset method for converting a point in data space to an offset + within the placeholder. + + - Plugin system: register an init method in the $.flot.plugins array to get + started, see PLUGINS.txt for details on how to write plugins (it's easy). + There are also some extra methods to enable access to internal state. + + - Hooks: you can register functions that are called while Flot is crunching + the data and doing the plot. This can be used to modify Flot without + changing the source, useful for writing plugins. Some hooks are defined, + more are likely to come. + + - Threshold plugin: you can set a threshold and a color, and the data points + below that threshold will then get the color. Useful for marking data + below 0, for instance. + + - Stack plugin: you can specify a stack key for each series to have them + summed. This is useful for drawing additive/cumulative graphs with bars and + (currently unfilled) lines. + + - Crosshairs plugin: trace the mouse position on the axes, enable with + crosshair: { mode: "x"} (see the new tracking example for a use). + + - Image plugin: plot prerendered images. + + - Navigation plugin for panning and zooming a plot. + + - More configurable grid. + + - Axis transformation support, useful for non-linear plots, e.g. log axes and + compressed time axes (like omitting weekends). + + - Support for twelve-hour date formatting (patch by Forrest Aldridge). + + - The color parsing code in Flot has been cleaned up and split out so it's + now available as a separate jQuery plugin. It's included inline in the Flot + source to make dependency managing easier. This also makes it really easy + to use the color helpers in Flot plugins. + +## Bug fixes ## + + - Fixed two corner-case bugs when drawing filled curves. (report and analysis + by Joshua Varner) + + - Fix auto-adjustment code when setting min to 0 for an axis where the + dataset is completely flat on that axis. (report by chovy) + + - Fixed a bug with passing in data from getData to setData when the secondary + axes are used. (reported by nperelman, issue 65) + + - Fixed so that it is possible to turn lines off when no other chart type is + shown (based on problem reported by Glenn Vanderburg), and fixed so that + setting lineWidth to 0 also hides the shadow. (based on problem reported by + Sergio Nunes) + + - Updated mousemove position expression to the latest from jQuery. (reported + by meyuchas) + + - Use CSS borders instead of background in legend. (issues 25 and 45) + + - Explicitly convert axis min/max to numbers. + + - Fixed a bug with drawing marking lines with different colors. (reported by + Khurram) + + - Fixed a bug with returning y2 values in the selection event. (fix by + exists, issue 75) + + - Only set position relative on placeholder if it hasn't already a position + different from static. (reported by kyberneticist, issue 95) + + - Don't round markings to prevent sub-pixel problems. (reported by + Dan Lipsitt) + + - Make the grid border act similarly to a regular CSS border, i.e. prevent + it from overlapping the plot itself. This also fixes a problem with anti- + aliasing when the width is 1 pixel. (reported by Anthony Ettinger) + + - Imported version 3 of excanvas and fixed two issues with the newer version. + Hopefully, this will make Flot work with IE8. (nudge by Fabien Menager, + further analysis by Booink, issue 133) + + - Changed the shadow code for lines to hopefully look a bit better with + vertical lines. + + - Round tick positions to avoid possible problems with fractions. (suggestion + by Fred, issue 130) + + - Made the heuristic for determining how many ticks to aim for a bit smarter. + + - Fix for uneven axis margins (report and patch by Paul Kienzle) and snapping + to ticks. (report and patch by lifthrasiir) + + - Fixed bug with slicing in findNearbyItems. (patch by zollman) + + - Make heuristic for x axis label widths more dynamic. (patch by + rickinhethuis) + + - Make sure points on top take precedence when finding nearby points when + hovering. (reported by didroe, issue 224) + + + +## Flot 0.5 ## + +Timestamps are now in UTC. Also "selected" event -> becomes "plotselected" +with new data, the parameters for setSelection are now different (but +backwards compatibility hooks are in place), coloredAreas becomes markings +with a new interface (but backwards compatibility hooks are in place). + +### API changes ### + +Timestamps in time mode are now displayed according to UTC instead of the time +zone of the visitor. This affects the way the timestamps should be input; +you'll probably have to offset the timestamps according to your local time +zone. It also affects any custom date handling code (which basically now +should use the equivalent UTC date mehods, e.g. .setUTCMonth() instead of +.setMonth(). + +Markings, previously coloredAreas, are now specified as ranges on the axes, +like ```{ xaxis: { from: 0, to: 10 }}```. Furthermore with markings you can +now draw horizontal/vertical lines by setting from and to to the same +coordinate. (idea from line support patch by by Ryan Funduk) + +Interactivity: added a new "plothover" event and this and the "plotclick" +event now returns the closest data item (based on patch by /david, patch by +Mark Byers for bar support). See the revamped "interacting with the data" +example for some hints on what you can do. + +Highlighting: you can now highlight points and datapoints are autohighlighted +when you hover over them (if hovering is turned on). + +Support for dual axis has been added (based on patch by someone who's annoyed +and /david). For each data series you can specify which axes it belongs to, +and there are two more axes, x2axis and y2axis, to customize. This affects the +"selected" event which has been renamed to "plotselected" and spews out +```{ xaxis: { from: -10, to: 20 } ... },``` setSelection in which the +parameters are on a new form (backwards compatible hooks are in place so old +code shouldn't break) and markings (formerly coloredAreas). + +## Changes ## + + - Added support for specifying the size of tick labels (axis.labelWidth, + axis.labelHeight). Useful for specifying a max label size to keep multiple + plots aligned. + + - The "fill" option can now be a number that specifies the opacity of the + fill. + + - You can now specify a coordinate as null (like [2, null]) and Flot will + take the other coordinate into account when scaling the axes. (based on + patch by joebno) + + - New option for bars "align". Set it to "center" to center the bars on the + value they represent. + + - setSelection now takes a second parameter which you can use to prevent the + method from firing the "plotselected" handler. + + - Improved the handling of axis auto-scaling with bars. + +## Bug fixes ## + + - Fixed a bug in calculating spacing around the plot. (reported by + timothytoe) + + - Fixed a bug in finding max values for all-negative data sets. + + - Prevent the possibility of eternal looping in tick calculations. + + - Fixed a bug when borderWidth is set to 0. (reported by Rob/sanchothefat) + + - Fixed a bug with drawing bars extending below 0. (reported by James Hewitt, + patch by Ryan Funduk). + + - Fixed a bug with line widths of bars. (reported by MikeM) + + - Fixed a bug with 'nw' and 'sw' legend positions. + + - Fixed a bug with multi-line x-axis tick labels. (reported by Luca Ciano, + IE-fix help by Savage Zhang) + + - Using the "container" option in legend now overwrites the container element + instead of just appending to it, fixing the infinite legend bug. (reported + by several people, fix by Brad Dewey) + + + +## Flot 0.4 ## + +### API changes ### + +Deprecated axis.noTicks in favor of just specifying the number as axis.ticks. +So ```xaxis: { noTicks: 10 }``` becomes ```xaxis: { ticks: 10 }```. + +Time series support. Specify axis.mode: "time", put in Javascript timestamps +as data, and Flot will automatically spit out sensible ticks. Take a look at +the two new examples. The format can be customized with axis.timeformat and +axis.monthNames, or if that fails with axis.tickFormatter. + +Support for colored background areas via grid.coloredAreas. Specify an array +of { x1, y1, x2, y2 } objects or a function that returns these given +{ xmin, xmax, ymin, ymax }. + +More members on the plot object (report by Chris Davies and others). +"getData" for inspecting the assigned settings on data series (e.g. color) and +"setData", "setupGrid" and "draw" for updating the contents without a total +replot. + +The default number of ticks to aim for is now dependent on the size of the +plot in pixels. Support for customizing tick interval sizes directly with +axis.minTickSize and axis.tickSize. + +Cleaned up the automatic axis scaling algorithm and fixed how it interacts +with ticks. Also fixed a couple of tick-related corner case bugs (one reported +by mainstreetmark, another reported by timothytoe). + +The option axis.tickFormatter now takes a function with two parameters, the +second parameter is an optional object with information about the axis. It has +min, max, tickDecimals, tickSize. + +## Changes ## + + - Added support for segmented lines. (based on patch from Michael MacDonald) + + - Added support for ignoring null and bad values. (suggestion from Nick + Konidaris and joshwaihi) + + - Added support for changing the border width. (thanks to joebno and safoo) + + - Label colors can be changed via CSS by selecting the tickLabel class. + +## Bug fixes ## + + - Fixed a bug in handling single-item bar series. (reported by Emil Filipov) + + - Fixed erratic behaviour when interacting with the plot with IE 7. (reported + by Lau Bech Lauritzen). + + - Prevent IE/Safari text selection when selecting stuff on the canvas. + + + +## Flot 0.3 ## + +This is mostly a quick-fix release because jquery.js wasn't included in the +previous zip/tarball. + +## Changes ## + + - Include jquery.js in the zip/tarball. + + - Support clicking on the plot. Turn it on with grid: { clickable: true }, + then you get a "plotclick" event on the graph placeholder with the position + in units of the plot. + +## Bug fixes ## + + - Fixed a bug in dealing with data where min = max. (thanks to Michael + Messinides) + + + +## Flot 0.2 ## + +The API should now be fully documented. + +### API changes ### + +Moved labelMargin option to grid from x/yaxis. + +## Changes ## + + - Added support for putting a background behind the default legend. The + default is the partly transparent background color. Added backgroundColor + and backgroundOpacity to the legend options to control this. + + - The ticks options can now be a callback function that takes one parameter, + an object with the attributes min and max. The function should return a + ticks array. + + - Added labelFormatter option in legend, useful for turning the legend + labels into links. + + - Reduced the size of the code. (patch by Guy Fraser) + + + +## Flot 0.1 ## + +First public release. diff --git a/web/flot/PLUGINS.txt b/Web Server/flot/PLUGINS.md similarity index 74% rename from web/flot/PLUGINS.txt rename to Web Server/flot/PLUGINS.md index af3d90b..b5bf300 100644 --- a/web/flot/PLUGINS.txt +++ b/Web Server/flot/PLUGINS.md @@ -1,18 +1,19 @@ -Writing plugins ---------------- +## Writing plugins ## All you need to do to make a new plugin is creating an init function and a set of options (if needed), stuffing it into an object and putting it in the $.plot.plugins array. For example: - function myCoolPluginInit(plot) { +```js +function myCoolPluginInit(plot) { plot.coolstring = "Hello!"; - }; +}; - $.plot.plugins.push({ init: myCoolPluginInit, options: { ... } }); +$.plot.plugins.push({ init: myCoolPluginInit, options: { ... } }); - // if $.plot is called, it will return a plot object with the - // attribute "coolstring" +// if $.plot is called, it will return a plot object with the +// attribute "coolstring" +``` Now, given that the plugin might run in many different places, it's a good idea to avoid leaking names. The usual trick here is wrap the @@ -21,53 +22,56 @@ this: (function () { inner code ... })(). To make it even more robust in case $ is not bound to jQuery but some other Javascript library, we can write it as - (function ($) { +```js +(function ($) { // plugin definition // ... - })(jQuery); +})(jQuery); +``` There's a complete example below, but you should also check out the plugins bundled with Flot. -Complete example ----------------- +## Complete example ## Here is a simple debug plugin which alerts each of the series in the plot. It has a single option that control whether it is enabled and how much info to output: - (function ($) { +```js +(function ($) { function init(plot) { - var debugLevel = 1; - - function checkDebugEnabled(plot, options) { - if (options.debug) { - debugLevel = options.debug; - - plot.hooks.processDatapoints.push(alertSeries); + var debugLevel = 1; + + function checkDebugEnabled(plot, options) { + if (options.debug) { + debugLevel = options.debug; + plot.hooks.processDatapoints.push(alertSeries); + } + } + + function alertSeries(plot, series, datapoints) { + var msg = "series " + series.label; + if (debugLevel > 1) { + msg += " with " + series.data.length + " points"; + alert(msg); + } } - } - - function alertSeries(plot, series, datapoints) { - var msg = "series " + series.label; - if (debugLevel > 1) - msg += " with " + series.data.length + " points"; - alert(msg); - } - - plot.hooks.processOptions.push(checkDebugEnabled); + + plot.hooks.processOptions.push(checkDebugEnabled); } var options = { debug: 0 }; - + $.plot.plugins.push({ init: init, options: options, name: "simpledebug", version: "0.1" }); - })(jQuery); +})(jQuery); +``` We also define "name" and "version". It's not used by Flot, but might be helpful for other plugins in resolving dependencies. @@ -75,7 +79,9 @@ be helpful for other plugins in resolving dependencies. Put the above in a file named "jquery.flot.debug.js", include it in an HTML page and then it can be used with: - $.plot($("#placeholder"), [...], { debug: 2 }); +```js + $.plot($("#placeholder"), [...], { debug: 2 }); +``` This simple plugin illustrates a couple of points: @@ -88,8 +94,7 @@ The two last points are important because there may be multiple plots on the same page, and you'd want to make sure they are not mixed up. -Shutting down a plugin ----------------------- +## Shutting down a plugin ## Each plot object has a shutdown hook which is run when plot.shutdown() is called. This usually mostly happens in case another plot is made on @@ -107,8 +112,7 @@ garbage collected yet, and worse, if your handler eventually runs, it may overwrite stuff on a completely different plot. -Some hints on the options -------------------------- +## Some hints on the options ## Plugins should always support appropriate options to enable/disable them because the plugin user may have several plots on the same page @@ -120,14 +124,16 @@ If the plugin needs options that are specific to each series, like the points or lines options in core Flot, you can put them in "series" in the options object, e.g. - var options = { +```js +var options = { series: { - downsample: { - algorithm: null, - maxpoints: 1000 - } + downsample: { + algorithm: null, + maxpoints: 1000 + } } - } +} +``` Then they will be copied by Flot into each series, providing default values in case none are specified. diff --git a/Web Server/flot/README.md b/Web Server/flot/README.md new file mode 100644 index 0000000..a8f7064 --- /dev/null +++ b/Web Server/flot/README.md @@ -0,0 +1,110 @@ +# Flot [![Build status](https://travis-ci.org/flot/flot.png)](https://travis-ci.org/flot/flot) + +## About ## + +Flot is a Javascript plotting library for jQuery. +Read more at the website: + +Take a look at the the examples in examples/index.html; they should give a good +impression of what Flot can do, and the source code of the examples is probably +the fastest way to learn how to use Flot. + + +## Installation ## + +Just include the Javascript file after you've included jQuery. + +Generally, all browsers that support the HTML5 canvas tag are +supported. + +For support for Internet Explorer < 9, you can use [Excanvas] +[excanvas], a canvas emulator; this is used in the examples bundled +with Flot. You just include the excanvas script like this: + +```html + +``` + +If it's not working on your development IE 6.0, check that it has +support for VML which Excanvas is relying on. It appears that some +stripped down versions used for test environments on virtual machines +lack the VML support. + +You can also try using [Flashcanvas][flashcanvas], which uses Flash to +do the emulation. Although Flash can be a bit slower to load than VML, +if you've got a lot of points, the Flash version can be much faster +overall. Flot contains some wrapper code for activating Excanvas which +Flashcanvas is compatible with. + +You need at least jQuery 1.2.6, but try at least 1.3.2 for interactive +charts because of performance improvements in event handling. + + +## Basic usage ## + +Create a placeholder div to put the graph in: + +```html +
+``` + +You need to set the width and height of this div, otherwise the plot +library doesn't know how to scale the graph. You can do it inline like +this: + +```html +
+``` + +You can also do it with an external stylesheet. Make sure that the +placeholder isn't within something with a display:none CSS property - +in that case, Flot has trouble measuring label dimensions which +results in garbled looks and might have trouble measuring the +placeholder dimensions which is fatal (it'll throw an exception). + +Then when the div is ready in the DOM, which is usually on document +ready, run the plot function: + +```js +$.plot($("#placeholder"), data, options); +``` + +Here, data is an array of data series and options is an object with +settings if you want to customize the plot. Take a look at the +examples for some ideas of what to put in or look at the +[API reference](API.md). Here's a quick example that'll draw a line +from (0, 0) to (1, 1): + +```js +$.plot($("#placeholder"), [ [[0, 0], [1, 1]] ], { yaxis: { max: 1 } }); +``` + +The plot function immediately draws the chart and then returns a plot +object with a couple of methods. + + +## What's with the name? ## + +First: it's pronounced with a short o, like "plot". Not like "flawed". + +So "Flot" rhymes with "plot". + +And if you look up "flot" in a Danish-to-English dictionary, some of +the words that come up are "good-looking", "attractive", "stylish", +"smart", "impressive", "extravagant". One of the main goals with Flot +is pretty looks. + + +## Notes about the examples ## + +In order to have a useful, functional example of time-series plots using time +zones, date.js from [timezone-js][timezone-js] (released under the Apache 2.0 +license) and the [Olson][olson] time zone database (released to the public +domain) have been included in the examples directory. They are used in +examples/axes-time-zones/index.html. + + +[excanvas]: http://code.google.com/p/explorercanvas/ +[flashcanvas]: http://code.google.com/p/flashcanvas/ +[timezone-js]: https://github.com/mde/timezone-js +[olson]: http://ftp.iana.org/time-zones diff --git a/web/flot/excanvas.js b/Web Server/flot/excanvas.js similarity index 100% rename from web/flot/excanvas.js rename to Web Server/flot/excanvas.js diff --git a/web/flot/excanvas.min.js b/Web Server/flot/excanvas.min.js similarity index 100% rename from web/flot/excanvas.min.js rename to Web Server/flot/excanvas.min.js diff --git a/web/flot/jquery.colorhelpers.js b/Web Server/flot/jquery.colorhelpers.js similarity index 97% rename from web/flot/jquery.colorhelpers.js rename to Web Server/flot/jquery.colorhelpers.js index d3524d7..b2f6dc4 100644 --- a/web/flot/jquery.colorhelpers.js +++ b/Web Server/flot/jquery.colorhelpers.js @@ -74,14 +74,15 @@ // if it's "transparent" $.color.extract = function (elem, css) { var c; + do { c = elem.css(css).toLowerCase(); // keep going until we find an element that has color, or - // we hit the body + // we hit the body or root (have no parent) if (c != '' && c != 'transparent') break; elem = elem.parent(); - } while (!$.nodeName(elem.get(0), "body")); + } while (elem.length && !$.nodeName(elem.get(0), "body")); // catch Safari's way of signalling transparent if (c == "rgba(0, 0, 0, 0)") diff --git a/Web Server/flot/jquery.colorhelpers.min.js b/Web Server/flot/jquery.colorhelpers.min.js new file mode 100644 index 0000000..7f42659 --- /dev/null +++ b/Web Server/flot/jquery.colorhelpers.min.js @@ -0,0 +1 @@ +(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); \ No newline at end of file diff --git a/Web Server/flot/jquery.flot.canvas.js b/Web Server/flot/jquery.flot.canvas.js new file mode 100644 index 0000000..d94b961 --- /dev/null +++ b/Web Server/flot/jquery.flot.canvas.js @@ -0,0 +1,345 @@ +/* Flot plugin for drawing all elements of a plot on the canvas. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +Flot normally produces certain elements, like axis labels and the legend, using +HTML elements. This permits greater interactivity and customization, and often +looks better, due to cross-browser canvas text inconsistencies and limitations. + +It can also be desirable to render the plot entirely in canvas, particularly +if the goal is to save it as an image, or if Flot is being used in a context +where the HTML DOM does not exist, as is the case within Node.js. This plugin +switches out Flot's standard drawing operations for canvas-only replacements. + +Currently the plugin supports only axis labels, but it will eventually allow +every element of the plot to be rendered directly to canvas. + +The plugin supports these options: + +{ + canvas: boolean +} + +The "canvas" option controls whether full canvas drawing is enabled, making it +possible to toggle on and off. This is useful when a plot uses HTML text in the +browser, but needs to redraw with canvas text when exporting as an image. + +*/ + +(function($) { + + var options = { + canvas: true + }; + + var render, getTextInfo, addText; + + // Cache the prototype hasOwnProperty for faster access + + var hasOwnProperty = Object.prototype.hasOwnProperty; + + function init(plot, classes) { + + var Canvas = classes.Canvas; + + // We only want to replace the functions once; the second time around + // we would just get our new function back. This whole replacing of + // prototype functions is a disaster, and needs to be changed ASAP. + + if (render == null) { + getTextInfo = Canvas.prototype.getTextInfo, + addText = Canvas.prototype.addText, + render = Canvas.prototype.render; + } + + // Finishes rendering the canvas, including overlaid text + + Canvas.prototype.render = function() { + + if (!plot.getOptions().canvas) { + return render.call(this); + } + + var context = this.context, + cache = this._textCache; + + // For each text layer, render elements marked as active + + context.save(); + context.textBaseline = "middle"; + + for (var layerKey in cache) { + if (hasOwnProperty.call(cache, layerKey)) { + var layerCache = cache[layerKey]; + for (var styleKey in layerCache) { + if (hasOwnProperty.call(layerCache, styleKey)) { + var styleCache = layerCache[styleKey], + updateStyles = true; + for (var key in styleCache) { + if (hasOwnProperty.call(styleCache, key)) { + + var info = styleCache[key], + positions = info.positions, + lines = info.lines; + + // Since every element at this level of the cache have the + // same font and fill styles, we can just change them once + // using the values from the first element. + + if (updateStyles) { + context.fillStyle = info.font.color; + context.font = info.font.definition; + updateStyles = false; + } + + for (var i = 0, position; position = positions[i]; i++) { + if (position.active) { + for (var j = 0, line; line = position.lines[j]; j++) { + context.fillText(lines[j].text, line[0], line[1]); + } + } else { + positions.splice(i--, 1); + } + } + + if (positions.length == 0) { + delete styleCache[key]; + } + } + } + } + } + } + } + + context.restore(); + }; + + // Creates (if necessary) and returns a text info object. + // + // When the canvas option is set, the object looks like this: + // + // { + // width: Width of the text's bounding box. + // height: Height of the text's bounding box. + // positions: Array of positions at which this text is drawn. + // lines: [{ + // height: Height of this line. + // widths: Width of this line. + // text: Text on this line. + // }], + // font: { + // definition: Canvas font property string. + // color: Color of the text. + // }, + // } + // + // The positions array contains objects that look like this: + // + // { + // active: Flag indicating whether the text should be visible. + // lines: Array of [x, y] coordinates at which to draw the line. + // x: X coordinate at which to draw the text. + // y: Y coordinate at which to draw the text. + // } + + Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { + + if (!plot.getOptions().canvas) { + return getTextInfo.call(this, layer, text, font, angle, width); + } + + var textStyle, layerCache, styleCache, info; + + // Cast the value to a string, in case we were given a number + + text = "" + text; + + // If the font is a font-spec object, generate a CSS definition + + if (typeof font === "object") { + textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; + } else { + textStyle = font; + } + + // Retrieve (or create) the cache for the text's layer and styles + + layerCache = this._textCache[layer]; + + if (layerCache == null) { + layerCache = this._textCache[layer] = {}; + } + + styleCache = layerCache[textStyle]; + + if (styleCache == null) { + styleCache = layerCache[textStyle] = {}; + } + + info = styleCache[text]; + + if (info == null) { + + var context = this.context; + + // If the font was provided as CSS, create a div with those + // classes and examine it to generate a canvas font spec. + + if (typeof font !== "object") { + + var element = $("
 
") + .css("position", "absolute") + .addClass(typeof font === "string" ? font : null) + .appendTo(this.getTextLayer(layer)); + + font = { + lineHeight: element.height(), + style: element.css("font-style"), + variant: element.css("font-variant"), + weight: element.css("font-weight"), + family: element.css("font-family"), + color: element.css("color") + }; + + // Setting line-height to 1, without units, sets it equal + // to the font-size, even if the font-size is abstract, + // like 'smaller'. This enables us to read the real size + // via the element's height, working around browsers that + // return the literal 'smaller' value. + + font.size = element.css("line-height", 1).height(); + + element.remove(); + } + + textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; + + // Create a new info object, initializing the dimensions to + // zero so we can count them up line-by-line. + + info = styleCache[text] = { + width: 0, + height: 0, + positions: [], + lines: [], + font: { + definition: textStyle, + color: font.color + } + }; + + context.save(); + context.font = textStyle; + + // Canvas can't handle multi-line strings; break on various + // newlines, including HTML brs, to build a list of lines. + // Note that we could split directly on regexps, but IE < 9 is + // broken; revisit when we drop IE 7/8 support. + + var lines = (text + "").replace(/
|\r\n|\r/g, "\n").split("\n"); + + for (var i = 0; i < lines.length; ++i) { + + var lineText = lines[i], + measured = context.measureText(lineText); + + info.width = Math.max(measured.width, info.width); + info.height += font.lineHeight; + + info.lines.push({ + text: lineText, + width: measured.width, + height: font.lineHeight + }); + } + + context.restore(); + } + + return info; + }; + + // Adds a text string to the canvas text overlay. + + Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { + + if (!plot.getOptions().canvas) { + return addText.call(this, layer, x, y, text, font, angle, width, halign, valign); + } + + var info = this.getTextInfo(layer, text, font, angle, width), + positions = info.positions, + lines = info.lines; + + // Text is drawn with baseline 'middle', which we need to account + // for by adding half a line's height to the y position. + + y += info.height / lines.length / 2; + + // Tweak the initial y-position to match vertical alignment + + if (valign == "middle") { + y = Math.round(y - info.height / 2); + } else if (valign == "bottom") { + y = Math.round(y - info.height); + } else { + y = Math.round(y); + } + + // FIXME: LEGACY BROWSER FIX + // AFFECTS: Opera < 12.00 + + // Offset the y coordinate, since Opera is off pretty + // consistently compared to the other browsers. + + if (!!(window.opera && window.opera.version().split(".")[0] < 12)) { + y -= 2; + } + + // Determine whether this text already exists at this position. + // If so, mark it for inclusion in the next render pass. + + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = true; + return; + } + } + + // If the text doesn't exist at this position, create a new entry + + position = { + active: true, + lines: [], + x: x, + y: y + }; + + positions.push(position); + + // Fill in the x & y positions of each line, adjusting them + // individually for horizontal alignment. + + for (var i = 0, line; line = lines[i]; i++) { + if (halign == "center") { + position.lines.push([Math.round(x - line.width / 2), y]); + } else if (halign == "right") { + position.lines.push([Math.round(x - line.width), y]); + } else { + position.lines.push([Math.round(x), y]); + } + y += line.height; + } + }; + } + + $.plot.plugins.push({ + init: init, + options: options, + name: "canvas", + version: "1.0" + }); + +})(jQuery); diff --git a/Web Server/flot/jquery.flot.canvas.min.js b/Web Server/flot/jquery.flot.canvas.min.js new file mode 100644 index 0000000..826d217 --- /dev/null +++ b/Web Server/flot/jquery.flot.canvas.min.js @@ -0,0 +1 @@ +(function($){var options={canvas:true};var render,getTextInfo,addText;var hasOwnProperty=Object.prototype.hasOwnProperty;function init(plot,classes){var Canvas=classes.Canvas;if(render==null){getTextInfo=Canvas.prototype.getTextInfo,addText=Canvas.prototype.addText,render=Canvas.prototype.render}Canvas.prototype.render=function(){if(!plot.getOptions().canvas){return render.call(this)}var context=this.context,cache=this._textCache;context.save();context.textBaseline="middle";for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layerCache=cache[layerKey];for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey],updateStyles=true;for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var info=styleCache[key],positions=info.positions,lines=info.lines;if(updateStyles){context.fillStyle=info.font.color;context.font=info.font.definition;updateStyles=false}for(var i=0,position;position=positions[i];i++){if(position.active){for(var j=0,line;line=position.lines[j];j++){context.fillText(lines[j].text,line[0],line[1])}}else{positions.splice(i--,1)}}if(positions.length==0){delete styleCache[key]}}}}}}}context.restore()};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){if(!plot.getOptions().canvas){return getTextInfo.call(this,layer,text,font,angle,width)}var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var context=this.context;if(typeof font!=="object"){var element=$("
 
").css("position","absolute").addClass(typeof font==="string"?font:null).appendTo(this.getTextLayer(layer));font={lineHeight:element.height(),style:element.css("font-style"),variant:element.css("font-variant"),weight:element.css("font-weight"),family:element.css("font-family"),color:element.css("color")};font.size=element.css("line-height",1).height();element.remove()}textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family;info=styleCache[text]={width:0,height:0,positions:[],lines:[],font:{definition:textStyle,color:font.color}};context.save();context.font=textStyle;var lines=(text+"").replace(/
|\r\n|\r/g,"\n").split("\n");for(var i=0;iindex)index=categories[v];return index+1}function categoriesTickGenerator(axis){var res=[];for(var label in axis.categories){var v=axis.categories[label];if(v>=axis.min&&v<=axis.max)res.push([v,label])}res.sort(function(a,b){return a[0]-b[0]});return res}function setupCategoriesForAxis(series,axis,datapoints){if(series[axis].options.mode!="categories")return;if(!series[axis].categories){var c={},o=series[axis].options.categories||{};if($.isArray(o)){for(var i=0;i ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) + continue; + if (err[e].err == 'y') + if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) + continue; + + // prevent errorbars getting out of the canvas + var drawUpper = true, + drawLower = true; + + if (upper > minmax[1]) { + drawUpper = false; + upper = minmax[1]; + } + if (lower < minmax[0]) { + drawLower = false; + lower = minmax[0]; + } + + //sanity check, in case some inverted axis hack is applied to flot + if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) { + //swap coordinates + var tmp = lower; + lower = upper; + upper = tmp; + tmp = drawLower; + drawLower = drawUpper; + drawUpper = tmp; + tmp = minmax[0]; + minmax[0] = minmax[1]; + minmax[1] = tmp; + } + + // convert to pixels + x = ax[0].p2c(x), + y = ax[1].p2c(y), + upper = ax[e].p2c(upper); + lower = ax[e].p2c(lower); + minmax[0] = ax[e].p2c(minmax[0]); + minmax[1] = ax[e].p2c(minmax[1]); + + //same style as points by default + var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth, + sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize; + + //shadow as for points + if (lw > 0 && sw > 0) { + var w = sw / 2; + ctx.lineWidth = w; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax); + + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax); + } + + ctx.strokeStyle = err[e].color? err[e].color: s.color; + ctx.lineWidth = lw; + //draw it + drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax); + } + } + } + } + + function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){ + + //shadow offset + y += offset; + upper += offset; + lower += offset; + + // error bar - avoid plotting over circles + if (err.err == 'x'){ + if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]); + else drawUpper = false; + if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] ); + else drawLower = false; + } + else { + if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] ); + else drawUpper = false; + if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] ); + else drawLower = false; + } + + //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps + //this is a way to get errorbars on lines without visible connecting dots + radius = err.radius != null? err.radius: radius; + + // upper cap + if (drawUpper) { + if (err.upperCap == '-'){ + if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] ); + else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] ); + } else if ($.isFunction(err.upperCap)){ + if (err.err=='x') err.upperCap(ctx, upper, y, radius); + else err.upperCap(ctx, x, upper, radius); + } + } + // lower cap + if (drawLower) { + if (err.lowerCap == '-'){ + if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] ); + else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] ); + } else if ($.isFunction(err.lowerCap)){ + if (err.err=='x') err.lowerCap(ctx, lower, y, radius); + else err.lowerCap(ctx, x, lower, radius); + } + } + } + + function drawPath(ctx, pts){ + ctx.beginPath(); + ctx.moveTo(pts[0][0], pts[0][1]); + for (var p=1; p < pts.length; p++) + ctx.lineTo(pts[p][0], pts[p][1]); + ctx.stroke(); + } + + function draw(plot, ctx){ + var plotOffset = plot.getPlotOffset(); + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + $.each(plot.getData(), function (i, s) { + if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) + drawSeriesErrors(plot, ctx, s); + }); + ctx.restore(); + } + + function init(plot) { + plot.hooks.processRawData.push(processRawData); + plot.hooks.draw.push(draw); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'errorbars', + version: '1.0' + }); +})(jQuery); diff --git a/Web Server/flot/jquery.flot.errorbars.min.js b/Web Server/flot/jquery.flot.errorbars.min.js new file mode 100644 index 0000000..a7bd042 --- /dev/null +++ b/Web Server/flot/jquery.flot.errorbars.min.js @@ -0,0 +1 @@ +(function($){var options={series:{points:{errorbars:null,xerr:{err:"x",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null},yerr:{err:"y",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null}}}};function processRawData(plot,series,data,datapoints){if(!series.points.errorbars)return;var format=[{x:true,number:true,required:true},{y:true,number:true,required:true}];var errors=series.points.errorbars;if(errors=="x"||errors=="xy"){if(series.points.xerr.asymmetric){format.push({x:true,number:true,required:true});format.push({x:true,number:true,required:true})}else format.push({x:true,number:true,required:true})}if(errors=="y"||errors=="xy"){if(series.points.yerr.asymmetric){format.push({y:true,number:true,required:true});format.push({y:true,number:true,required:true})}else format.push({y:true,number:true,required:true})}datapoints.format=format}function parseErrors(series,i){var points=series.datapoints.points;var exl=null,exu=null,eyl=null,eyu=null;var xerr=series.points.xerr,yerr=series.points.yerr;var eb=series.points.errorbars;if(eb=="x"||eb=="xy"){if(xerr.asymmetric){exl=points[i+2];exu=points[i+3];if(eb=="xy")if(yerr.asymmetric){eyl=points[i+4];eyu=points[i+5]}else eyl=points[i+4]}else{exl=points[i+2];if(eb=="xy")if(yerr.asymmetric){eyl=points[i+3];eyu=points[i+4]}else eyl=points[i+3]}}else if(eb=="y")if(yerr.asymmetric){eyl=points[i+2];eyu=points[i+3]}else eyl=points[i+2];if(exu==null)exu=exl;if(eyu==null)eyu=eyl;var errRanges=[exl,exu,eyl,eyu];if(!xerr.show){errRanges[0]=null;errRanges[1]=null}if(!yerr.show){errRanges[2]=null;errRanges[3]=null}return errRanges}function drawSeriesErrors(plot,ctx,s){var points=s.datapoints.points,ps=s.datapoints.pointsize,ax=[s.xaxis,s.yaxis],radius=s.points.radius,err=[s.points.xerr,s.points.yerr];var invertX=false;if(ax[0].p2c(ax[0].max)ax[1].max||yax[0].max)continue;if(err[e].err=="y")if(x>ax[0].max||xax[1].max)continue;var drawUpper=true,drawLower=true;if(upper>minmax[1]){drawUpper=false;upper=minmax[1]}if(lower0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w+w/2,minmax);ctx.strokeStyle="rgba(0,0,0,0.2)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w/2,minmax)}ctx.strokeStyle=err[e].color?err[e].color:s.color;ctx.lineWidth=lw;drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,0,minmax)}}}}function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){y+=offset;upper+=offset;lower+=offset;if(err.err=="x"){if(upper>x+radius)drawPath(ctx,[[upper,y],[Math.max(x+radius,minmax[0]),y]]);else drawUpper=false;if(lowery+radius)drawPath(ctx,[[x,Math.max(y+radius,minmax[1])],[x,lower]]);else drawLower=false}radius=err.radius!=null?err.radius:radius;if(drawUpper){if(err.upperCap=="-"){if(err.err=="x")drawPath(ctx,[[upper,y-radius],[upper,y+radius]]);else drawPath(ctx,[[x-radius,upper],[x+radius,upper]])}else if($.isFunction(err.upperCap)){if(err.err=="x")err.upperCap(ctx,upper,y,radius);else err.upperCap(ctx,x,upper,radius)}}if(drawLower){if(err.lowerCap=="-"){if(err.err=="x")drawPath(ctx,[[lower,y-radius],[lower,y+radius]]);else drawPath(ctx,[[x-radius,lower],[x+radius,lower]])}else if($.isFunction(err.lowerCap)){if(err.err=="x")err.lowerCap(ctx,lower,y,radius);else err.lowerCap(ctx,x,lower,radius)}}}function drawPath(ctx,pts){ctx.beginPath();ctx.moveTo(pts[0][0],pts[0][1]);for(var p=1;p= allseries.length ) { + return null; + } + return allseries[ s.fillBetween ]; + } + + return null; + } + + function computeFillBottoms( plot, s, datapoints ) { + + if ( s.fillBetween == null ) { + return; + } + + var other = findBottomSeries( s, plot.getData() ); + + if ( !other ) { + return; + } + + var ps = datapoints.pointsize, + points = datapoints.points, + otherps = other.datapoints.pointsize, + otherpoints = other.datapoints.points, + newpoints = [], + px, py, intery, qx, qy, bottom, + withlines = s.lines.show, + withbottom = ps > 2 && datapoints.format[2].y, + withsteps = withlines && s.lines.steps, + fromgap = true, + i = 0, + j = 0, + l, m; + + while ( true ) { + + if ( i >= points.length ) { + break; + } + + l = newpoints.length; + + if ( points[ i ] == null ) { + + // copy gaps + + for ( m = 0; m < ps; ++m ) { + newpoints.push( points[ i + m ] ); + } + + i += ps; + + } else if ( j >= otherpoints.length ) { + + // for lines, we can't use the rest of the points + + if ( !withlines ) { + for ( m = 0; m < ps; ++m ) { + newpoints.push( points[ i + m ] ); + } + } + + i += ps; + + } else if ( otherpoints[ j ] == null ) { + + // oops, got a gap + + for ( m = 0; m < ps; ++m ) { + newpoints.push( null ); + } + + fromgap = true; + j += otherps; + + } else { + + // cases where we actually got two points + + px = points[ i ]; + py = points[ i + 1 ]; + qx = otherpoints[ j ]; + qy = otherpoints[ j + 1 ]; + bottom = 0; + + if ( px === qx ) { + + for ( m = 0; m < ps; ++m ) { + newpoints.push( points[ i + m ] ); + } + + //newpoints[ l + 1 ] += qy; + bottom = qy; + + i += ps; + j += otherps; + + } else if ( px > qx ) { + + // we got past point below, might need to + // insert interpolated extra point + + if ( withlines && i > 0 && points[ i - ps ] != null ) { + intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px ); + newpoints.push( qx ); + newpoints.push( intery ); + for ( m = 2; m < ps; ++m ) { + newpoints.push( points[ i + m ] ); + } + bottom = qy; + } + + j += otherps; + + } else { // px < qx + + // if we come from a gap, we just skip this point + + if ( fromgap && withlines ) { + i += ps; + continue; + } + + for ( m = 0; m < ps; ++m ) { + newpoints.push( points[ i + m ] ); + } + + // we might be able to interpolate a point below, + // this can give us a better y + + if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) { + bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx ); + } + + //newpoints[l + 1] += bottom; + + i += ps; + } + + fromgap = false; + + if ( l !== newpoints.length && withbottom ) { + newpoints[ l + 2 ] = bottom; + } + } + + // maintain the line steps invariant + + if ( withsteps && l !== newpoints.length && l > 0 && + newpoints[ l ] !== null && + newpoints[ l ] !== newpoints[ l - ps ] && + newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) { + for (m = 0; m < ps; ++m) { + newpoints[ l + ps + m ] = newpoints[ l + m ]; + } + newpoints[ l + 1 ] = newpoints[ l - ps + 1 ]; + } + } + + datapoints.points = newpoints; + } + + plot.hooks.processDatapoints.push( computeFillBottoms ); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: "fillbetween", + version: "1.0" + }); + +})(jQuery); diff --git a/Web Server/flot/jquery.flot.fillbetween.min.js b/Web Server/flot/jquery.flot.fillbetween.min.js new file mode 100644 index 0000000..5bdad05 --- /dev/null +++ b/Web Server/flot/jquery.flot.fillbetween.min.js @@ -0,0 +1 @@ +(function($){var options={series:{fillBetween:null}};function init(plot){function findBottomSeries(s,allseries){var i;for(i=0;i=allseries.length){return null}return allseries[s.fillBetween]}return null}function computeFillBottoms(plot,s,datapoints){if(s.fillBetween==null){return}var other=findBottomSeries(s,plot.getData());if(!other){return}var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,withbottom=ps>2&&datapoints.format[2].y,withsteps=withlines&&s.lines.steps,fromgap=true,i=0,j=0,l,m;while(true){if(i>=points.length){break}l=newpoints.length;if(points[i]==null){for(m=0;m=otherpoints.length){if(!withlines){for(m=0;mqx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+1]-py)*(qx-px)/(points[i-ps]-px);newpoints.push(qx);newpoints.push(intery);for(m=2;m0&&otherpoints[j-otherps]!=null){bottom=qy+(otherpoints[j-otherps+1]-qy)*(px-qx)/(otherpoints[j-otherps]-qx)}i+=ps}fromgap=false;if(l!==newpoints.length&&withbottom){newpoints[l+2]=bottom}}if(withsteps&&l!==newpoints.length&&l>0&&newpoints[l]!==null&&newpoints[l]!==newpoints[l-ps]&&newpoints[l+1]!==newpoints[l-ps+1]){for(m=0;m').load(handler).error(handler).attr('src', url); }); - } + }; function drawSeries(plot, ctx, series) { var plotOffset = plot.getPlotOffset(); diff --git a/Web Server/flot/jquery.flot.image.min.js b/Web Server/flot/jquery.flot.image.min.js new file mode 100644 index 0000000..6060024 --- /dev/null +++ b/Web Server/flot/jquery.flot.image.min.js @@ -0,0 +1 @@ +(function($){var options={series:{images:{show:false,alpha:1,anchor:"corner"}}};$.plot.image={};$.plot.image.loadDataImages=function(series,options,callback){var urls=[],points=[];var defaultShow=options.series.images.show;$.each(series,function(i,s){if(!(defaultShow||s.images.show))return;if(s.data)s=s.data;$.each(s,function(i,p){if(typeof p[0]=="string"){urls.push(p[0]);points.push(p)}})});$.plot.image.load(urls,function(loadedImages){$.each(points,function(i,p){var url=p[0];if(loadedImages[url])p[0]=loadedImages[url]});callback()})};$.plot.image.load=function(urls,callback){var missing=urls.length,loaded={};if(missing==0)callback({});$.each(urls,function(i,url){var handler=function(){--missing;loaded[url]=this;if(missing==0)callback(loaded)};$("").load(handler).error(handler).attr("src",url)})};function drawSeries(plot,ctx,series){var plotOffset=plot.getPlotOffset();if(!series.images||!series.images.show)return;var points=series.datapoints.points,ps=series.datapoints.pointsize;for(var i=0;ix2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}if(series.images.anchor=="center"){tmp=.5*(x2-x1)/(img.width-1);x1-=tmp;x2+=tmp;tmp=.5*(y2-y1)/(img.height-1);y1-=tmp;y2+=tmp}if(x1==x2||y1==y2||x1>=xaxis.max||x2<=xaxis.min||y1>=yaxis.max||y2<=yaxis.min)continue;var sx1=0,sy1=0,sx2=img.width,sy2=img.height;if(x1xaxis.max){sx2+=(sx2-sx1)*(xaxis.max-x2)/(x2-x1);x2=xaxis.max}if(y1yaxis.max){sy1+=(sy1-sy2)*(yaxis.max-y2)/(y2-y1);y2=yaxis.max}x1=xaxis.p2c(x1);x2=xaxis.p2c(x2);y1=yaxis.p2c(y1);y2=yaxis.p2c(y2);if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}tmp=ctx.globalAlpha;ctx.globalAlpha*=series.images.alpha;ctx.drawImage(img,sx1,sy1,sx2-sx1,sy2-sy1,x1+plotOffset.left,y1+plotOffset.top,x2-x1,y2-y1);ctx.globalAlpha=tmp}}function processRawData(plot,series,data,datapoints){if(!series.images.show)return;datapoints.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.drawSeries.push(drawSeries)}$.plot.plugins.push({init:init,options:options,name:"image",version:"1.1"})})(jQuery); \ No newline at end of file diff --git a/web/flot/jquery.flot.js b/Web Server/flot/jquery.flot.js similarity index 62% rename from web/flot/jquery.flot.js rename to Web Server/flot/jquery.flot.js index 0db9fe1..c410659 100644 --- a/web/flot/jquery.flot.js +++ b/Web Server/flot/jquery.flot.js @@ -1,16 +1,17 @@ -/*! Javascript plotting library for jQuery, v. 0.7. - * - * Released under the MIT license by IOLA, December 2007. - * - */ +/* Javascript plotting library for jQuery, version 0.8.2. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +*/ // first an inline dependency, jquery.colorhelpers.js, we inline it here // for convenience /* Plugin for jQuery for working with colors. - * + * * Version 1.1. - * + * * Inspiration from jQuery color animation plugin by John Resig. * * Released under the MIT license by Ole Laursen, October 2009. @@ -27,17 +28,473 @@ * * V. 1.1: Fix error handling so e.g. parsing an empty string does * produce a color rather than just crashing. - */ -(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); + */ +(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); // the actual Flot code (function($) { + + // Cache the prototype hasOwnProperty for faster access + + var hasOwnProperty = Object.prototype.hasOwnProperty; + + /////////////////////////////////////////////////////////////////////////// + // The Canvas object is a wrapper around an HTML5 tag. + // + // @constructor + // @param {string} cls List of classes to apply to the canvas. + // @param {element} container Element onto which to append the canvas. + // + // Requiring a container is a little iffy, but unfortunately canvas + // operations don't work unless the canvas is attached to the DOM. + + function Canvas(cls, container) { + + var element = container.children("." + cls)[0]; + + if (element == null) { + + element = document.createElement("canvas"); + element.className = cls; + + $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) + .appendTo(container); + + // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas + + if (!element.getContext) { + if (window.G_vmlCanvasManager) { + element = window.G_vmlCanvasManager.initElement(element); + } else { + throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); + } + } + } + + this.element = element; + + var context = this.context = element.getContext("2d"); + + // Determine the screen's ratio of physical to device-independent + // pixels. This is the ratio between the canvas width that the browser + // advertises and the number of pixels actually present in that space. + + // The iPhone 4, for example, has a device-independent width of 320px, + // but its screen is actually 640px wide. It therefore has a pixel + // ratio of 2, while most normal devices have a ratio of 1. + + var devicePixelRatio = window.devicePixelRatio || 1, + backingStoreRatio = + context.webkitBackingStorePixelRatio || + context.mozBackingStorePixelRatio || + context.msBackingStorePixelRatio || + context.oBackingStorePixelRatio || + context.backingStorePixelRatio || 1; + + this.pixelRatio = devicePixelRatio / backingStoreRatio; + + // Size the canvas to match the internal dimensions of its container + + this.resize(container.width(), container.height()); + + // Collection of HTML div layers for text overlaid onto the canvas + + this.textContainer = null; + this.text = {}; + + // Cache of text fragments and metrics, so we can avoid expensively + // re-calculating them when the plot is re-rendered in a loop. + + this._textCache = {}; + } + + // Resizes the canvas to the given dimensions. + // + // @param {number} width New width of the canvas, in pixels. + // @param {number} width New height of the canvas, in pixels. + + Canvas.prototype.resize = function(width, height) { + + if (width <= 0 || height <= 0) { + throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); + } + + var element = this.element, + context = this.context, + pixelRatio = this.pixelRatio; + + // Resize the canvas, increasing its density based on the display's + // pixel ratio; basically giving it more pixels without increasing the + // size of its element, to take advantage of the fact that retina + // displays have that many more pixels in the same advertised space. + + // Resizing should reset the state (excanvas seems to be buggy though) + + if (this.width != width) { + element.width = width * pixelRatio; + element.style.width = width + "px"; + this.width = width; + } + + if (this.height != height) { + element.height = height * pixelRatio; + element.style.height = height + "px"; + this.height = height; + } + + // Save the context, so we can reset in case we get replotted. The + // restore ensure that we're really back at the initial state, and + // should be safe even if we haven't saved the initial state yet. + + context.restore(); + context.save(); + + // Scale the coordinate space to match the display density; so even though we + // may have twice as many pixels, we still want lines and other drawing to + // appear at the same size; the extra pixels will just make them crisper. + + context.scale(pixelRatio, pixelRatio); + }; + + // Clears the entire canvas area, not including any overlaid HTML text + + Canvas.prototype.clear = function() { + this.context.clearRect(0, 0, this.width, this.height); + }; + + // Finishes rendering the canvas, including managing the text overlay. + + Canvas.prototype.render = function() { + + var cache = this._textCache; + + // For each text layer, add elements marked as active that haven't + // already been rendered, and remove those that are no longer active. + + for (var layerKey in cache) { + if (hasOwnProperty.call(cache, layerKey)) { + + var layer = this.getTextLayer(layerKey), + layerCache = cache[layerKey]; + + layer.hide(); + + for (var styleKey in layerCache) { + if (hasOwnProperty.call(layerCache, styleKey)) { + var styleCache = layerCache[styleKey]; + for (var key in styleCache) { + if (hasOwnProperty.call(styleCache, key)) { + + var positions = styleCache[key].positions; + + for (var i = 0, position; position = positions[i]; i++) { + if (position.active) { + if (!position.rendered) { + layer.append(position.element); + position.rendered = true; + } + } else { + positions.splice(i--, 1); + if (position.rendered) { + position.element.detach(); + } + } + } + + if (positions.length == 0) { + delete styleCache[key]; + } + } + } + } + } + + layer.show(); + } + } + }; + + // Creates (if necessary) and returns the text overlay container. + // + // @param {string} classes String of space-separated CSS classes used to + // uniquely identify the text layer. + // @return {object} The jQuery-wrapped text-layer div. + + Canvas.prototype.getTextLayer = function(classes) { + + var layer = this.text[classes]; + + // Create the text layer if it doesn't exist + + if (layer == null) { + + // Create the text layer container, if it doesn't exist + + if (this.textContainer == null) { + this.textContainer = $("
") + .css({ + position: "absolute", + top: 0, + left: 0, + bottom: 0, + right: 0, + 'font-size': "smaller", + color: "#545454" + }) + .insertAfter(this.element); + } + + layer = this.text[classes] = $("
") + .addClass(classes) + .css({ + position: "absolute", + top: 0, + left: 0, + bottom: 0, + right: 0 + }) + .appendTo(this.textContainer); + } + + return layer; + }; + + // Creates (if necessary) and returns a text info object. + // + // The object looks like this: + // + // { + // width: Width of the text's wrapper div. + // height: Height of the text's wrapper div. + // element: The jQuery-wrapped HTML div containing the text. + // positions: Array of positions at which this text is drawn. + // } + // + // The positions array contains objects that look like this: + // + // { + // active: Flag indicating whether the text should be visible. + // rendered: Flag indicating whether the text is currently visible. + // element: The jQuery-wrapped HTML div containing the text. + // x: X coordinate at which to draw the text. + // y: Y coordinate at which to draw the text. + // } + // + // Each position after the first receives a clone of the original element. + // + // The idea is that that the width, height, and general 'identity' of the + // text is constant no matter where it is placed; the placements are a + // secondary property. + // + // Canvas maintains a cache of recently-used text info objects; getTextInfo + // either returns the cached element or creates a new entry. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {string} text Text string to retrieve info for. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which to rotate the text, in degrees. + // Angle is currently unused, it will be implemented in the future. + // @param {number=} width Maximum width of the text before it wraps. + // @return {object} a text info object. + + Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { + + var textStyle, layerCache, styleCache, info; + + // Cast the value to a string, in case we were given a number or such + + text = "" + text; + + // If the font is a font-spec object, generate a CSS font definition + + if (typeof font === "object") { + textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; + } else { + textStyle = font; + } + + // Retrieve (or create) the cache for the text's layer and styles + + layerCache = this._textCache[layer]; + + if (layerCache == null) { + layerCache = this._textCache[layer] = {}; + } + + styleCache = layerCache[textStyle]; + + if (styleCache == null) { + styleCache = layerCache[textStyle] = {}; + } + + info = styleCache[text]; + + // If we can't find a matching element in our cache, create a new one + + if (info == null) { + + var element = $("
").html(text) + .css({ + position: "absolute", + 'max-width': width, + top: -9999 + }) + .appendTo(this.getTextLayer(layer)); + + if (typeof font === "object") { + element.css({ + font: textStyle, + color: font.color + }); + } else if (typeof font === "string") { + element.addClass(font); + } + + info = styleCache[text] = { + width: element.outerWidth(true), + height: element.outerHeight(true), + element: element, + positions: [] + }; + + element.detach(); + } + + return info; + }; + + // Adds a text string to the canvas text overlay. + // + // The text isn't drawn immediately; it is marked as rendering, which will + // result in its addition to the canvas on the next render pass. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {number} x X coordinate at which to draw the text. + // @param {number} y Y coordinate at which to draw the text. + // @param {string} text Text string to draw. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which to rotate the text, in degrees. + // Angle is currently unused, it will be implemented in the future. + // @param {number=} width Maximum width of the text before it wraps. + // @param {string=} halign Horizontal alignment of the text; either "left", + // "center" or "right". + // @param {string=} valign Vertical alignment of the text; either "top", + // "middle" or "bottom". + + Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { + + var info = this.getTextInfo(layer, text, font, angle, width), + positions = info.positions; + + // Tweak the div's position to match the text's alignment + + if (halign == "center") { + x -= info.width / 2; + } else if (halign == "right") { + x -= info.width; + } + + if (valign == "middle") { + y -= info.height / 2; + } else if (valign == "bottom") { + y -= info.height; + } + + // Determine whether this text already exists at this position. + // If so, mark it for inclusion in the next render pass. + + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = true; + return; + } + } + + // If the text doesn't exist at this position, create a new entry + + // For the very first position we'll re-use the original element, + // while for subsequent ones we'll clone it. + + position = { + active: true, + rendered: false, + element: positions.length ? info.element.clone() : info.element, + x: x, + y: y + }; + + positions.push(position); + + // Move the element to its final position within the container + + position.element.css({ + top: Math.round(y), + left: Math.round(x), + 'text-align': halign // In case the text wraps + }); + }; + + // Removes one or more text strings from the canvas text overlay. + // + // If no parameters are given, all text within the layer is removed. + // + // Note that the text is not immediately removed; it is simply marked as + // inactive, which will result in its removal on the next render pass. + // This avoids the performance penalty for 'clear and redraw' behavior, + // where we potentially get rid of all text on a layer, but will likely + // add back most or all of it later, as when redrawing axes, for example. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {number=} x X coordinate of the text. + // @param {number=} y Y coordinate of the text. + // @param {string=} text Text string to remove. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which the text is rotated, in degrees. + // Angle is currently unused, it will be implemented in the future. + + Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { + if (text == null) { + var layerCache = this._textCache[layer]; + if (layerCache != null) { + for (var styleKey in layerCache) { + if (hasOwnProperty.call(layerCache, styleKey)) { + var styleCache = layerCache[styleKey]; + for (var key in styleCache) { + if (hasOwnProperty.call(styleCache, key)) { + var positions = styleCache[key].positions; + for (var i = 0, position; position = positions[i]; i++) { + position.active = false; + } + } + } + } + } + } + } else { + var positions = this.getTextInfo(layer, text, font, angle).positions; + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = false; + } + } + } + }; + + /////////////////////////////////////////////////////////////////////////// + // The top-level container for the entire plot. + function Plot(placeholder, data_, options_, plugins) { // data is on the form: // [ series1, series2 ... ] // where series is either just the data as [ [x1, y1], [x2, y2], ... ] // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } - + var series = [], options = { // the color theme used for graphs @@ -51,13 +508,14 @@ position: "ne", // position of default legend container within plot margin: 5, // distance from grid edge to default legend container within plot backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85 // set to 0 to avoid background + backgroundOpacity: 0.85, // set to 0 to avoid background + sorted: null // default to no legend sorting }, xaxis: { show: null, // null = auto-detect, true = always, false = never position: "bottom", // or "top" mode: null, // null or "time" - font: null, // null (derived from CSS in placeholder) or object like { size: 11, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } + font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } color: null, // base color, labels, ticks tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" transform: null, // null or f: number -> number to transform axis @@ -72,14 +530,9 @@ reserveSpace: null, // whether to reserve space even if axis isn't shown tickLength: null, // size in pixels of ticks, or "full" for whole line alignTicksWithAxis: null, // axis number or null for no sync - - // mode specific options tickDecimals: null, // no. of decimals, null means auto tickSize: null, // number or [number, "unit"] - minTickSize: null, // number or [number, "unit"] - monthNames: null, // list of names of months - timeformat: null, // format string to use - twelveHourClock: false // 12 or 24 time in time mode + minTickSize: null // number or [number, "unit"] }, yaxis: { autoscaleMargin: 0.02, @@ -98,11 +551,13 @@ }, lines: { // we don't put in show: false so we can see - // whether lines were actively disabled + // whether lines were actively disabled lineWidth: 2, // in pixels fill: false, fillColor: null, steps: false + // Omit 'zero', so we can later default its value to + // match that of the 'fill' option. }, bars: { show: false, @@ -110,10 +565,12 @@ barWidth: 1, // in units of the x axis fill: true, fillColor: null, - align: "left", // or "center" - horizontal: false + align: "left", // "left", "right", or "center" + horizontal: false, + zero: true }, - shadowSize: 3 + shadowSize: 3, + highlightColor: null }, grid: { show: true, @@ -122,6 +579,7 @@ backgroundColor: null, // null for transparent, else color borderColor: null, // set if different from the grid color tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" + margin: 0, // distance from the canvas edge to the grid labelMargin: 5, // in pixels axisMargin: 8, // in pixels borderWidth: 2, // in pixels @@ -140,18 +598,19 @@ }, hooks: {} }, - canvas = null, // the canvas for the plot itself + surface = null, // the canvas for the plot itself overlay = null, // canvas for interactive stuff on top of plot eventHolder = null, // jQuery object that events should be bound to ctx = null, octx = null, xaxes = [], yaxes = [], plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - canvasWidth = 0, canvasHeight = 0, plotWidth = 0, plotHeight = 0, hooks = { processOptions: [], processRawData: [], processDatapoints: [], + processOffset: [], + drawBackground: [], drawSeries: [], draw: [], bindEvents: [], @@ -165,7 +624,7 @@ plot.setupGrid = setupGrid; plot.draw = draw; plot.getPlaceholder = function() { return placeholder; }; - plot.getCanvas = function() { return canvas; }; + plot.getCanvas = function() { return surface.element; }; plot.getPlotOffset = function() { return plotOffset; }; plot.width = function () { return plotWidth; }; plot.height = function () { return plotHeight; }; @@ -194,20 +653,38 @@ plot.triggerRedrawOverlay = triggerRedrawOverlay; plot.pointOffset = function(point) { return { - left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left), - top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top) + left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), + top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) }; }; plot.shutdown = shutdown; + plot.destroy = function () { + shutdown(); + placeholder.removeData("plot").empty(); + + series = []; + options = null; + surface = null; + overlay = null; + eventHolder = null; + ctx = null; + octx = null; + xaxes = []; + yaxes = []; + hooks = null; + highlights = []; + plot = null; + }; plot.resize = function () { - getCanvasDimensions(); - resizeCanvas(canvas); - resizeCanvas(overlay); + var width = placeholder.width(), + height = placeholder.height(); + surface.resize(width, height); + overlay.resize(width, height); }; // public attributes plot.hooks = hooks; - + // initialize initPlugins(plot); parseOptions(options_); @@ -225,40 +702,109 @@ } function initPlugins() { + + // References to key classes, allowing plugins to modify them + + var classes = { + Canvas: Canvas + }; + for (var i = 0; i < plugins.length; ++i) { var p = plugins[i]; - p.init(plot); + p.init(plot, classes); if (p.options) $.extend(true, options, p.options); } } - + function parseOptions(opts) { - var i; - + $.extend(true, options, opts); - + + // $.extend merges arrays, rather than replacing them. When less + // colors are provided than the size of the default palette, we + // end up with those colors plus the remaining defaults, which is + // not expected behavior; avoid it by replacing them here. + + if (opts && opts.colors) { + options.colors = opts.colors; + } + if (options.xaxis.color == null) - options.xaxis.color = options.grid.color; + options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); if (options.yaxis.color == null) - options.yaxis.color = options.grid.color; - - if (options.xaxis.tickColor == null) // backwards-compatibility - options.xaxis.tickColor = options.grid.tickColor; - if (options.yaxis.tickColor == null) // backwards-compatibility - options.yaxis.tickColor = options.grid.tickColor; + options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + + if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility + options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; + if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility + options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; if (options.grid.borderColor == null) options.grid.borderColor = options.grid.color; if (options.grid.tickColor == null) options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - // fill in defaults in axes, copy at least always the - // first as the rest of the code assumes it'll be there - for (i = 0; i < Math.max(1, options.xaxes.length); ++i) - options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]); - for (i = 0; i < Math.max(1, options.yaxes.length); ++i) - options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]); + + // Fill in defaults for axis options, including any unspecified + // font-spec fields, if a font-spec was provided. + + // If no x/y axis options were provided, create one of each anyway, + // since the rest of the code assumes that they exist. + + var i, axisOptions, axisCount, + fontSize = placeholder.css("font-size"), + fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, + fontDefaults = { + style: placeholder.css("font-style"), + size: Math.round(0.8 * fontSizeDefault), + variant: placeholder.css("font-variant"), + weight: placeholder.css("font-weight"), + family: placeholder.css("font-family") + }; + + axisCount = options.xaxes.length || 1; + for (i = 0; i < axisCount; ++i) { + + axisOptions = options.xaxes[i]; + if (axisOptions && !axisOptions.tickColor) { + axisOptions.tickColor = axisOptions.color; + } + + axisOptions = $.extend(true, {}, options.xaxis, axisOptions); + options.xaxes[i] = axisOptions; + + if (axisOptions.font) { + axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); + if (!axisOptions.font.color) { + axisOptions.font.color = axisOptions.color; + } + if (!axisOptions.font.lineHeight) { + axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); + } + } + } + + axisCount = options.yaxes.length || 1; + for (i = 0; i < axisCount; ++i) { + + axisOptions = options.yaxes[i]; + if (axisOptions && !axisOptions.tickColor) { + axisOptions.tickColor = axisOptions.color; + } + + axisOptions = $.extend(true, {}, options.yaxis, axisOptions); + options.yaxes[i] = axisOptions; + + if (axisOptions.font) { + axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); + if (!axisOptions.font.color) { + axisOptions.font.color = axisOptions.color; + } + if (!axisOptions.font.lineHeight) { + axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); + } + } + } // backwards compatibility, to be removed in future if (options.xaxis.noTicks && options.xaxis.ticks == null) @@ -285,6 +831,8 @@ $.extend(true, options.series.bars, options.bars); if (options.shadowSize != null) options.series.shadowSize = options.shadowSize; + if (options.highlightColor != null) + options.series.highlightColor = options.highlightColor; // save options on axes for future reference for (i = 0; i < options.xaxes.length; ++i) @@ -305,7 +853,7 @@ fillInSeriesOptions(); processData(); } - + function parseData(d) { var res = []; for (var i = 0; i < d.length; ++i) { @@ -326,7 +874,7 @@ return res; } - + function axisNumber(obj, coord) { var a = obj[coord + "axis"]; if (typeof a == "object") // if we got a real axis, extract number @@ -340,9 +888,9 @@ // return flat array without annoying null entries return $.grep(xaxes.concat(yaxes), function (a) { return a; }); } - + function canvasToAxisCoords(pos) { - // return an object with x/y corresponding to all used axes + // return an object with x/y corresponding to all used axes var res = {}, i, axis; for (i = 0; i < xaxes.length; ++i) { axis = xaxes[i]; @@ -355,7 +903,7 @@ if (axis && axis.used) res["y" + axis.n] = axis.c2p(pos.top); } - + if (res.x1 !== undefined) res.x = res.x1; if (res.y1 !== undefined) @@ -363,7 +911,7 @@ return res; } - + function axisToCanvasCoords(pos) { // get canvas coords from the first pair of x/y found in pos var res = {}, i, axis, key; @@ -381,7 +929,7 @@ } } } - + for (i = 0; i < yaxes.length; ++i) { axis = yaxes[i]; if (axis && axis.used) { @@ -395,10 +943,10 @@ } } } - + return res; } - + function getOrCreateAxis(axes, number) { if (!axes[number - 1]) axes[number - 1] = { @@ -406,64 +954,69 @@ direction: axes == xaxes ? "x" : "y", options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) }; - + return axes[number - 1]; } function fillInSeriesOptions() { - var i; - - // collect what we already got of colors - var neededColors = series.length, - usedColors = [], - assignedColors = []; + + var neededColors = series.length, maxIndex = -1, i; + + // Subtract the number of series that already have fixed colors or + // color indexes from the number that we still need to generate. + for (i = 0; i < series.length; ++i) { var sc = series[i].color; if (sc != null) { - --neededColors; - if (typeof sc == "number") - assignedColors.push(sc); - else - usedColors.push($.color.parse(series[i].color)); + neededColors--; + if (typeof sc == "number" && sc > maxIndex) { + maxIndex = sc; + } } } - - // we might need to generate more colors if higher indices - // are assigned - for (i = 0; i < assignedColors.length; ++i) { - neededColors = Math.max(neededColors, assignedColors[i] + 1); + + // If any of the series have fixed color indexes, then we need to + // generate at least as many colors as the highest index. + + if (neededColors <= maxIndex) { + neededColors = maxIndex + 1; } - // produce colors as needed - var colors = [], variation = 0; - i = 0; - while (colors.length < neededColors) { - var c; - if (options.colors.length == i) // check degenerate case - c = $.color.make(100, 100, 100); - else - c = $.color.parse(options.colors[i]); - - // vary color if needed - var sign = variation % 2 == 1 ? -1 : 1; - c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2); - - // FIXME: if we're getting to close to something else, - // we should probably skip this one - colors.push(c); - - ++i; - if (i >= options.colors.length) { - i = 0; - ++variation; + // Generate all the colors, using first the option colors and then + // variations on those colors once they're exhausted. + + var c, colors = [], colorPool = options.colors, + colorPoolSize = colorPool.length, variation = 0; + + for (i = 0; i < neededColors; i++) { + + c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); + + // Each time we exhaust the colors in the pool we adjust + // a scaling factor used to produce more variations on + // those colors. The factor alternates negative/positive + // to produce lighter/darker colors. + + // Reset the variation after every few cycles, or else + // it will end up producing only white or black colors. + + if (i % colorPoolSize == 0 && i) { + if (variation >= 0) { + if (variation < 0.5) { + variation = -variation - 0.2; + } else variation = 0; + } else variation = -variation; } + + colors[i] = c.scale('rgb', 1 + variation); } - // fill in the options + // Finalize the series options, filling in their colors + var colori = 0, s; for (i = 0; i < series.length; ++i) { s = series[i]; - + // assign colors if (s.color == null) { s.color = colors[colori].toString(); @@ -484,18 +1037,26 @@ s.lines.show = true; } + // If nothing was provided for lines.zero, default it to match + // lines.fill, since areas by default should extend to zero. + + if (s.lines.zero == null) { + s.lines.zero = !!s.lines.fill; + } + // setup axes s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); } } - + function processData() { var topSentry = Number.POSITIVE_INFINITY, bottomSentry = Number.NEGATIVE_INFINITY, fakeInfinity = Number.MAX_VALUE, i, j, k, m, length, - s, points, ps, x, y, axis, val, f, p; + s, points, ps, x, y, axis, val, f, p, + data, format; function updateAxis(axis, min, max) { if (min < axis.datamin && min != -fakeInfinity) @@ -510,19 +1071,20 @@ axis.datamax = bottomSentry; axis.used = false; }); - + for (i = 0; i < series.length; ++i) { s = series[i]; s.datapoints = { points: [] }; - + executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); } - + // first pass: clean and copy data for (i = 0; i < series.length; ++i) { s = series[i]; - var data = s.data, format = s.datapoints.format; + data = s.data; + format = s.datapoints.format; if (!format) { format = []; @@ -531,13 +1093,14 @@ format.push({ y: true, number: true, required: true }); if (s.bars.show || (s.lines.show && s.lines.fill)) { - format.push({ y: true, number: true, required: false, defaultValue: 0 }); + var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); + format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); if (s.bars.horizontal) { delete format[format.length - 1].y; format[format.length - 1].x = true; } } - + s.datapoints.format = format; } @@ -545,13 +1108,13 @@ continue; // already filled in s.datapoints.pointsize = format.length; - + ps = s.datapoints.pointsize; points = s.datapoints.points; - insertSteps = s.lines.show && s.lines.steps; + var insertSteps = s.lines.show && s.lines.steps; s.xaxis.used = s.yaxis.used = true; - + for (j = k = 0; j < data.length; ++j, k += ps) { p = data[j]; @@ -575,26 +1138,30 @@ if (val == null) { if (f.required) nullify = true; - + if (f.defaultValue != null) val = f.defaultValue; } } - + points[k + m] = val; } } - + if (nullify) { for (m = 0; m < ps; ++m) { val = points[k + m]; if (val != null) { f = format[m]; // extract min/max info - if (f.x) - updateAxis(s.xaxis, val, val); - if (f.y) - updateAxis(s.yaxis, val, val); + if (f.autoscale !== false) { + if (f.x) { + updateAxis(s.xaxis, val, val); + } + if (f.y) { + updateAxis(s.yaxis, val, val); + } + } } points[k + m] = null; } @@ -624,20 +1191,20 @@ // give the hooks a chance to run for (i = 0; i < series.length; ++i) { s = series[i]; - + executeHooks(hooks.processDatapoints, [ s, s.datapoints]); } // second pass: find datamax/datamin for auto-scaling for (i = 0; i < series.length; ++i) { s = series[i]; - points = s.datapoints.points, + points = s.datapoints.points; ps = s.datapoints.pointsize; format = s.datapoints.format; var xmin = topSentry, ymin = topSentry, xmax = bottomSentry, ymax = bottomSentry; - + for (j = 0; j < points.length; j += ps) { if (points[j] == null) continue; @@ -645,9 +1212,9 @@ for (m = 0; m < ps; ++m) { val = points[j + m]; f = format[m]; - if (!f || val == fakeInfinity || val == -fakeInfinity) + if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) continue; - + if (f.x) { if (val < xmin) xmin = val; @@ -662,10 +1229,22 @@ } } } - + if (s.bars.show) { // make sure we got room for the bar on the dancing floor - var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; + var delta; + + switch (s.bars.align) { + case "left": + delta = 0; + break; + case "right": + delta = -s.bars.barWidth; + break; + default: + delta = -s.bars.barWidth / 2; + } + if (s.bars.horizontal) { ymin += delta; ymax += delta + s.bars.barWidth; @@ -675,7 +1254,7 @@ xmax += delta + s.bars.barWidth; } } - + updateAxis(s.xaxis, xmin, xmax); updateAxis(s.yaxis, ymin, ymax); } @@ -688,102 +1267,35 @@ }); } - function makeCanvas(skipPositioning, cls) { - var c = document.createElement('canvas'); - c.className = cls; - c.width = canvasWidth; - c.height = canvasHeight; - - if (!skipPositioning) - $(c).css({ position: 'absolute', left: 0, top: 0 }); - - $(c).appendTo(placeholder); - - if (!c.getContext) // excanvas hack - c = window.G_vmlCanvasManager.initElement(c); - - // used for resetting in case we get replotted - c.getContext("2d").save(); - - return c; - } - - function getCanvasDimensions() { - canvasWidth = placeholder.width(); - canvasHeight = placeholder.height(); - - if (canvasWidth <= 0 || canvasHeight <= 0) - throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; - } - - function resizeCanvas(c) { - // resizing should reset the state (excanvas seems to be - // buggy though) - if (c.width != canvasWidth) - c.width = canvasWidth; - - if (c.height != canvasHeight) - c.height = canvasHeight; + function setupCanvases() { - // so try to get back to the initial state (even if it's - // gone now, this should be safe according to the spec) - var cctx = c.getContext("2d"); - cctx.restore(); + // Make sure the placeholder is clear of everything except canvases + // from a previous plot in this container that we'll try to re-use. - // and save again - cctx.save(); - } - - function setupCanvases() { - var reused, - existingCanvas = placeholder.children("canvas.flot-base"), - existingOverlay = placeholder.children("canvas.flot-overlay"); - - if (existingCanvas.length == 0 || existingOverlay == 0) { - // init everything - - placeholder.html(""); // make sure placeholder is clear - - placeholder.css({ padding: 0 }); // padding messes up the positioning - - if (placeholder.css("position") == 'static') - placeholder.css("position", "relative"); // for positioning labels and overlay - - getCanvasDimensions(); - - canvas = makeCanvas(true, "flot-base"); - overlay = makeCanvas(false, "flot-overlay"); // overlay canvas for interactive features - - reused = false; - } - else { - // reuse existing elements + placeholder.css("padding", 0) // padding messes up the positioning + .children().filter(function(){ + return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); + }).remove(); - canvas = existingCanvas.get(0); - overlay = existingOverlay.get(0); + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay - reused = true; - } + surface = new Canvas("flot-base", placeholder); + overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features - ctx = canvas.getContext("2d"); - octx = overlay.getContext("2d"); + ctx = surface.context; + octx = overlay.context; // define which element we're listening for events on - eventHolder = $(overlay); + eventHolder = $(overlay.element).unbind(); - if (reused) { - // run shutdown in the old plot object - placeholder.data("plot").shutdown(); + // If we're re-using a plot object, shut down the old one - // reset reused canvases - plot.resize(); - - // make sure overlay pixels are cleared (canvas is cleared when we redraw) - octx.clearRect(0, 0, canvasWidth, canvasHeight); - - // then whack any remaining obvious garbage left - eventHolder.unbind(); - placeholder.children().not([canvas, overlay]).remove(); + var existing = placeholder.data("plot"); + + if (existing) { + existing.shutdown(); + overlay.clear(); } // save in case we get replotted @@ -794,7 +1306,14 @@ // bind events if (options.grid.hoverable) { eventHolder.mousemove(onMouseMove); - eventHolder.mouseleave(onMouseLeave); + + // Use bind, rather than .mouseleave, because we officially + // still support jQuery 1.2.6, which doesn't define a shortcut + // for mouseenter or mouseleave. This was a bug/oversight that + // was fixed somewhere around 1.3.x. We can return to using + // .mouseleave when we drop support for 1.2.6. + + eventHolder.bind("mouseleave", onMouseLeave); } if (options.grid.clickable) @@ -806,23 +1325,23 @@ function shutdown() { if (redrawTimeout) clearTimeout(redrawTimeout); - + eventHolder.unbind("mousemove", onMouseMove); eventHolder.unbind("mouseleave", onMouseLeave); eventHolder.unbind("click", onClick); - + executeHooks(hooks.shutdown, [eventHolder]); } function setTransformationHelpers(axis) { // set helper functions on the axis, assumes plot area // has been computed already - + function identity(x) { return x; } - + var s, m, t = axis.options.transform || identity, it = axis.options.inverseTransform; - + // precompute how much the axis is scaling a point // in canvas space if (axis.direction == "x") { @@ -848,56 +1367,31 @@ } function measureTickLabels(axis) { - var opts = axis.options, ticks = axis.ticks || [], - axisw = opts.labelWidth || 0, axish = opts.labelHeight || 0, - f = axis.font; - ctx.save(); - ctx.font = f.style + " " + f.variant + " " + f.weight + " " + f.size + "px '" + f.family + "'"; + var opts = axis.options, + ticks = axis.ticks || [], + labelWidth = opts.labelWidth || 0, + labelHeight = opts.labelHeight || 0, + maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null), + legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, + font = opts.font || "flot-tick-label tickLabel"; for (var i = 0; i < ticks.length; ++i) { + var t = ticks[i]; - - t.lines = []; - t.width = t.height = 0; if (!t.label) continue; - // accept various kinds of newlines, including HTML ones - // (you can actually split directly on regexps in Javascript, - // but IE is unfortunately broken) - var lines = t.label.replace(/
|\r\n|\r/g, "\n").split("\n"); - for (var j = 0; j < lines.length; ++j) { - var line = { text: lines[j] }, - m = ctx.measureText(line.text); - - line.width = m.width; - // m.height might not be defined, not in the - // standard yet - line.height = m.height != null ? m.height : f.size; - - // add a bit of margin since font rendering is - // not pixel perfect and cut off letters look - // bad, this also doubles as spacing between - // lines - line.height += Math.round(f.size * 0.15); - - t.width = Math.max(line.width, t.width); - t.height += line.height; - - t.lines.push(line); - } + var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - if (opts.labelWidth == null) - axisw = Math.max(axisw, t.width); - if (opts.labelHeight == null) - axish = Math.max(axish, t.height); + labelWidth = Math.max(labelWidth, info.width); + labelHeight = Math.max(labelHeight, info.height); } - ctx.restore(); - axis.labelWidth = Math.ceil(axisw); - axis.labelHeight = Math.ceil(axish); + axis.labelWidth = opts.labelWidth || labelWidth; + axis.labelHeight = opts.labelHeight || labelHeight; } function allocateAxisBoxFirstPhase(axis) { @@ -910,42 +1404,55 @@ var lw = axis.labelWidth, lh = axis.labelHeight, pos = axis.options.position, + isXAxis = axis.direction === "x", tickLength = axis.options.tickLength, axisMargin = options.grid.axisMargin, padding = options.grid.labelMargin, - all = axis.direction == "x" ? xaxes : yaxes, - index; - - // determine axis margin - var samePosition = $.grep(all, function (a) { - return a && a.options.position == pos && a.reserveSpace; + innermost = true, + outermost = true, + first = true, + found = false; + + // Determine the axis's position in its direction and on its side + + $.each(isXAxis ? xaxes : yaxes, function(i, a) { + if (a && a.reserveSpace) { + if (a === axis) { + found = true; + } else if (a.options.position === pos) { + if (found) { + outermost = false; + } else { + innermost = false; + } + } + if (!found) { + first = false; + } + } }); - if ($.inArray(axis, samePosition) == samePosition.length - 1) - axisMargin = 0; // outermost - // determine tick length - if we're innermost, we can use "full" + // The outermost axis on each side has no margin + + if (outermost) { + axisMargin = 0; + } + + // The ticks for the first axis in each direction stretch across + if (tickLength == null) { - var sameDirection = $.grep(all, function (a) { - return a && a.reserveSpace; - }); - - var innermost = $.inArray(axis, sameDirection) == 0; - if (innermost) - tickLength = "full"; - else - tickLength = 5; + tickLength = first ? "full" : 5; } - + if (!isNaN(+tickLength)) padding += +tickLength; - // compute box - if (axis.direction == "x") { + if (isXAxis) { lh += padding; - + if (pos == "bottom") { plotOffset.bottom += lh + axisMargin; - axis.box = { top: canvasHeight - plotOffset.bottom, height: lh }; + axis.box = { top: surface.height - plotOffset.bottom, height: lh }; } else { axis.box = { top: plotOffset.top + axisMargin, height: lh }; @@ -954,14 +1461,14 @@ } else { lw += padding; - + if (pos == "left") { axis.box = { left: plotOffset.left + axisMargin, width: lw }; plotOffset.left += lw + axisMargin; } else { plotOffset.right += lw + axisMargin; - axis.box = { left: canvasWidth - plotOffset.right, width: lw }; + axis.box = { left: surface.width - plotOffset.right, width: lw }; } } @@ -977,20 +1484,20 @@ // dimension, we can set the remaining dimension coordinates if (axis.direction == "x") { axis.box.left = plotOffset.left - axis.labelWidth / 2; - axis.box.width = canvasWidth - plotOffset.left - plotOffset.right + axis.labelWidth; + axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; } else { axis.box.top = plotOffset.top - axis.labelHeight / 2; - axis.box.height = canvasHeight - plotOffset.bottom - plotOffset.top + axis.labelHeight; + axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; } } function adjustLayoutForThingsStickingOut() { // possibly adjust plot offset to ensure everything stays // inside the canvas and isn't clipped off - + var minMargin = options.grid.minBorderMargin, - margins = { x: 0, y: 0 }, i, axis; + axis, i; // check stuff from the plot (FIXME: this should just read // a value from the series, otherwise it's impossible to @@ -1001,50 +1508,74 @@ minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); } - margins.x = margins.y = Math.ceil(minMargin); - + var margins = { + left: minMargin, + right: minMargin, + top: minMargin, + bottom: minMargin + }; + // check axis labels, note we don't check the actual // labels but instead use the overall width/height to not // jump as much around with replots $.each(allAxes(), function (_, axis) { - var dir = axis.direction; - if (axis.reserveSpace) - margins[dir] = Math.ceil(Math.max(margins[dir], (dir == "x" ? axis.labelWidth : axis.labelHeight) / 2)); + var lastTick = axis.ticks[axis.ticks.length - 1]; + if (axis.reserveSpace && lastTick) { + if (axis.direction === "x") { + margins.left = Math.max(margins.left, axis.labelWidth / 2); + if (lastTick.v <= axis.max) { + margins.right = Math.max(margins.right, axis.labelWidth / 2); + } + } else { + margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2); + if (lastTick.v <= axis.max) { + margins.top = Math.max(margins.top, axis.labelHeight / 2); + } + } + } }); - plotOffset.left = Math.max(margins.x, plotOffset.left); - plotOffset.right = Math.max(margins.x, plotOffset.right); - plotOffset.top = Math.max(margins.y, plotOffset.top); - plotOffset.bottom = Math.max(margins.y, plotOffset.bottom); + plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left)); + plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right)); + plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top)); + plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom)); } - + function setupGrid() { var i, axes = allAxes(), showGrid = options.grid.show; - // init plot offset - for (var a in plotOffset) - plotOffset[a] = showGrid ? options.grid.borderWidth : 0; - + // Initialize the plot's offset from the edge of the canvas + + for (var a in plotOffset) { + var margin = options.grid.margin || 0; + plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; + } + + executeHooks(hooks.processOffset, [plotOffset]); + + // If the grid is visible, add its border width to the offset + + for (var a in plotOffset) { + if(typeof(options.grid.borderWidth) == "object") { + plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; + } + else { + plotOffset[a] += showGrid ? options.grid.borderWidth : 0; + } + } + // init axes $.each(axes, function (_, axis) { axis.show = axis.options.show; if (axis.show == null) axis.show = axis.used; // by default an axis is visible if it's got data - + axis.reserveSpace = axis.show || axis.options.reserveSpace; setRange(axis); }); - + if (showGrid) { - // determine from the placeholder the font size ~ height of font ~ 1 em - var fontDefaults = { - style: placeholder.css("font-style"), - size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)), - variant: placeholder.css("font-variant"), - weight: placeholder.css("font-weight"), - family: placeholder.css("font-family") - }; var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); @@ -1053,9 +1584,7 @@ setupTickGeneration(axis); setTicks(axis); snapRangeToTicks(axis, axis.ticks); - // find labelWidth/Height for axis - axis.font = $.extend({}, fontDefaults, axis.options.font); measureTickLabels(axis); }); @@ -1073,18 +1602,22 @@ allocateAxisBoxSecondPhase(axis); }); } - - plotWidth = canvasWidth - plotOffset.left - plotOffset.right; - plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; + + plotWidth = surface.width - plotOffset.left - plotOffset.right; + plotHeight = surface.height - plotOffset.bottom - plotOffset.top; // now we got the proper plot dimensions, we can compute the scaling $.each(axes, function (_, axis) { setTransformationHelpers(axis); }); - + + if (showGrid) { + drawAxisLabels(); + } + insertLegend(); } - + function setRange(axis) { var opts = axis.options, min = +(opts.min != null ? opts.min : axis.datamin), @@ -1126,7 +1659,7 @@ function setupTickGeneration(axis) { var opts = axis.options; - + // estimate number of ticks var noTicks; if (typeof opts.ticks == "number" && opts.ticks > 0) @@ -1134,209 +1667,65 @@ else // heuristic based on the model a*sqrt(x) fitted to // some data points that seemed reasonable - noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight); + noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); var delta = (axis.max - axis.min) / noTicks, - size, generator, unit, formatter, i, magn, norm; - - if (opts.mode == "time") { - // pretty handling of time - - // map of app. size of time units in milliseconds - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; + dec = -Math.floor(Math.log(delta) / Math.LN10), + maxDec = opts.tickDecimals; + if (maxDec != null && dec > maxDec) { + dec = maxDec; + } - // the allowed tick sizes, after 1 year we use - // an integer algorithm - var spec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"], [3, "month"], [6, "month"], - [1, "year"] - ]; - - var minSize = 0; - if (opts.minTickSize != null) { - if (typeof opts.tickSize == "number") - minSize = opts.tickSize; - else - minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; + var magn = Math.pow(10, -dec), + norm = delta / magn, // norm is between 1.0 and 10.0 + size; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; } + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } - for (var i = 0; i < spec.length - 1; ++i) - if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] - + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 - && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) - break; - size = spec[i][0]; - unit = spec[i][1]; - - // special-case the possibility of several years - if (unit == "year") { - magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); - norm = (delta / timeUnitSize.year) / magn; - if (norm < 1.5) - size = 1; - else if (norm < 3) - size = 2; - else if (norm < 7.5) - size = 5; - else - size = 10; + size *= magn; - size *= magn; - } + if (opts.minTickSize != null && size < opts.minTickSize) { + size = opts.minTickSize; + } - axis.tickSize = opts.tickSize || [size, unit]; - - generator = function(axis) { - var ticks = [], - tickSize = axis.tickSize[0], unit = axis.tickSize[1], - d = new Date(axis.min); - - var step = tickSize * timeUnitSize[unit]; - - if (unit == "second") - d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); - if (unit == "minute") - d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); - if (unit == "hour") - d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); - if (unit == "month") - d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); - if (unit == "year") - d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); - - // reset smaller components - d.setUTCMilliseconds(0); - if (step >= timeUnitSize.minute) - d.setUTCSeconds(0); - if (step >= timeUnitSize.hour) - d.setUTCMinutes(0); - if (step >= timeUnitSize.day) - d.setUTCHours(0); - if (step >= timeUnitSize.day * 4) - d.setUTCDate(1); - if (step >= timeUnitSize.year) - d.setUTCMonth(0); - - - var carry = 0, v = Number.NaN, prev; - do { - prev = v; - v = d.getTime(); - ticks.push(v); - if (unit == "month") { - if (tickSize < 1) { - // a bit complicated - we'll divide the month - // up but we need to take care of fractions - // so we don't end up in the middle of a day - d.setUTCDate(1); - var start = d.getTime(); - d.setUTCMonth(d.getUTCMonth() + 1); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getUTCHours(); - d.setUTCHours(0); - } - else - d.setUTCMonth(d.getUTCMonth() + tickSize); - } - else if (unit == "year") { - d.setUTCFullYear(d.getUTCFullYear() + tickSize); - } - else - d.setTime(v + step); - } while (v < axis.max && v != prev); + axis.delta = delta; + axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + axis.tickSize = opts.tickSize || size; - return ticks; - }; + // Time mode was moved to a plug-in in 0.8, but since so many people use this + // we'll add an especially friendly make sure they remembered to include it. - formatter = function (v, axis) { - var d = new Date(v); - - // first check global format - if (opts.timeformat != null) - return $.plot.formatDate(d, opts.timeformat, opts.monthNames); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - var suffix = (opts.twelveHourClock) ? " %p" : ""; - - if (t < timeUnitSize.minute) - fmt = "%h:%M:%S" + suffix; - else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) - fmt = "%h:%M" + suffix; - else - fmt = "%b %d %h:%M" + suffix; - } - else if (t < timeUnitSize.month) - fmt = "%b %d"; - else if (t < timeUnitSize.year) { - if (span < timeUnitSize.year) - fmt = "%b"; - else - fmt = "%b %y"; - } - else - fmt = "%y"; - - return $.plot.formatDate(d, fmt, opts.monthNames); - }; + if (opts.mode == "time" && !axis.tickGenerator) { + throw new Error("Time mode requires the flot.time plugin."); } - else { - // pretty rounding of base-10 numbers - var maxDec = opts.tickDecimals; - var dec = -Math.floor(Math.log(delta) / Math.LN10); - if (maxDec != null && dec > maxDec) - dec = maxDec; - - magn = Math.pow(10, -dec); - norm = delta / magn; // norm is between 1.0 and 10.0 - - if (norm < 1.5) - size = 1; - else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } - else if (norm < 7.5) - size = 5; - else - size = 10; - size *= magn; - - if (opts.minTickSize != null && size < opts.minTickSize) - size = opts.minTickSize; + // Flot supports base-10 axes; any other mode else is handled by a plug-in, + // like flot.time.js. - axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); - axis.tickSize = opts.tickSize || size; + if (!axis.tickGenerator) { - generator = function (axis) { - var ticks = []; + axis.tickGenerator = function (axis) { + + var ticks = [], + start = floorInBase(axis.min, axis.tickSize), + i = 0, + v = Number.NaN, + prev; - // spew out all possible ticks - var start = floorInBase(axis.min, axis.tickSize), - i = 0, v = Number.NaN, prev; do { prev = v; v = start + i * axis.tickSize; @@ -1346,24 +1735,42 @@ return ticks; }; - formatter = function (v, axis) { - return v.toFixed(axis.tickDecimals); + axis.tickFormatter = function (value, axis) { + + var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; + var formatted = "" + Math.round(value * factor) / factor; + + // If tickDecimals was specified, ensure that we have exactly that + // much precision; otherwise default to the value's own precision. + + if (axis.tickDecimals != null) { + var decimal = formatted.indexOf("."); + var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; + if (precision < axis.tickDecimals) { + return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); + } + } + + return formatted; }; } + if ($.isFunction(opts.tickFormatter)) + axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; + if (opts.alignTicksWithAxis != null) { var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; if (otherAxis && otherAxis.used && otherAxis != axis) { // consider snapping min/max to outermost nice ticks - var niceTicks = generator(axis); + var niceTicks = axis.tickGenerator(axis); if (niceTicks.length > 0) { if (opts.min == null) axis.min = Math.min(axis.min, niceTicks[0]); if (opts.max == null && niceTicks.length > 1) axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); } - - generator = function (axis) { + + axis.tickGenerator = function (axis) { // copy ticks, scaled to this axis var ticks = [], v, i; for (i = 0; i < otherAxis.ticks.length; ++i) { @@ -1373,12 +1780,12 @@ } return ticks; }; - + // we might need an extra decimal since forced // ticks don't necessarily fit naturally if (!axis.mode && opts.tickDecimals == null) { - var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1), - ts = generator(axis); + var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), + ts = axis.tickGenerator(axis); // only proceed if the tick interval rounded // with an extra decimal doesn't give us a @@ -1388,14 +1795,8 @@ } } } - - axis.tickGenerator = generator; - if ($.isFunction(opts.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; - else - axis.tickFormatter = formatter; } - + function setTicks(axis) { var oticks = axis.options.ticks, ticks = []; if (oticks == null || (typeof oticks == "number" && oticks > 0)) @@ -1437,19 +1838,21 @@ axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); } } - + function draw() { - ctx.clearRect(0, 0, canvasWidth, canvasHeight); + + surface.clear(); + + executeHooks(hooks.drawBackground, [ctx]); var grid = options.grid; // draw background, if any if (grid.show && grid.backgroundColor) drawBackground(); - + if (grid.show && !grid.aboveData) { drawGrid(); - drawAxisLabels(); } for (var i = 0; i < series.length; ++i) { @@ -1458,17 +1861,23 @@ } executeHooks(hooks.draw, [ctx]); - + if (grid.show && grid.aboveData) { drawGrid(); - drawAxisLabels(); } + + surface.render(); + + // A draw implies that either the axes or data have changed, so we + // should probably update the overlay highlights as well. + + triggerRedrawOverlay(); } function extractRange(ranges, coord) { var axis, from, to, key, axes = allAxes(); - for (i = 0; i < axes.length; ++i) { + for (var i = 0; i < axes.length; ++i) { axis = axes[i]; if (axis.direction == coord) { key = coord + axis.n + "axis"; @@ -1495,10 +1904,10 @@ from = to; to = tmp; } - + return { from: from, to: to, axis: axis }; } - + function drawBackground() { ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); @@ -1509,8 +1918,8 @@ } function drawGrid() { - var i; - + var i, axes, bw, bc; + ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); @@ -1518,14 +1927,14 @@ var markings = options.grid.markings; if (markings) { if ($.isFunction(markings)) { - var axes = plot.getAxes(); + axes = plot.getAxes(); // xmin etc. is backwards compatibility, to be // removed in the future axes.xmin = axes.xaxis.min; axes.xmax = axes.xaxis.max; axes.ymin = axes.yaxis.min; axes.ymax = axes.yaxis.max; - + markings = markings(axes); } @@ -1562,7 +1971,7 @@ xrange.to = xrange.axis.p2c(xrange.to); yrange.from = yrange.axis.p2c(yrange.from); yrange.to = yrange.axis.p2c(yrange.to); - + if (xrange.from == xrange.to || yrange.from == yrange.to) { // draw line ctx.beginPath(); @@ -1581,17 +1990,17 @@ } } } - + // draw the ticks - var axes = allAxes(), bw = options.grid.borderWidth; + axes = allAxes(); + bw = options.grid.borderWidth; for (var j = 0; j < axes.length; ++j) { var axis = axes[j], box = axis.box, t = axis.tickLength, x, y, xoff, yoff; if (!axis.show || axis.ticks.length == 0) continue; - - ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString(); + ctx.lineWidth = 1; // find the edges @@ -1609,19 +2018,23 @@ else x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); } - + // draw tick bar if (!axis.innermost) { + ctx.strokeStyle = axis.options.color; ctx.beginPath(); xoff = yoff = 0; if (axis.direction == "x") - xoff = plotWidth; + xoff = plotWidth + 1; else - yoff = plotHeight; - + yoff = plotHeight + 1; + if (ctx.lineWidth == 1) { - x = Math.floor(x) + 0.5; - y = Math.floor(y) + 0.5; + if (axis.direction == "x") { + y = Math.floor(y) + 0.5; + } else { + x = Math.floor(x) + 0.5; + } } ctx.moveTo(x, y); @@ -1630,29 +2043,33 @@ } // draw ticks + + ctx.strokeStyle = axis.options.tickColor; + ctx.beginPath(); for (i = 0; i < axis.ticks.length; ++i) { var v = axis.ticks[i].v; - + xoff = yoff = 0; - if (v < axis.min || v > axis.max + if (isNaN(v) || v < axis.min || v > axis.max // skip those lying on the axes if we got a border - || (t == "full" && bw > 0 + || (t == "full" + && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) && (v == axis.min || v == axis.max))) continue; if (axis.direction == "x") { x = axis.p2c(v); yoff = t == "full" ? -plotHeight : t; - + if (axis.position == "top") yoff = -yoff; } else { y = axis.p2c(v); xoff = t == "full" ? -plotWidth : t; - + if (axis.position == "left") xoff = -xoff; } @@ -1667,88 +2084,117 @@ ctx.moveTo(x, y); ctx.lineTo(x + xoff, y + yoff); } - + ctx.stroke(); } - - + + // draw border if (bw) { - ctx.lineWidth = bw; - ctx.strokeStyle = options.grid.borderColor; - ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + // If either borderWidth or borderColor is an object, then draw the border + // line by line instead of as one rectangle + bc = options.grid.borderColor; + if(typeof bw == "object" || typeof bc == "object") { + if (typeof bw !== "object") { + bw = {top: bw, right: bw, bottom: bw, left: bw}; + } + if (typeof bc !== "object") { + bc = {top: bc, right: bc, bottom: bc, left: bc}; + } + + if (bw.top > 0) { + ctx.strokeStyle = bc.top; + ctx.lineWidth = bw.top; + ctx.beginPath(); + ctx.moveTo(0 - bw.left, 0 - bw.top/2); + ctx.lineTo(plotWidth, 0 - bw.top/2); + ctx.stroke(); + } + + if (bw.right > 0) { + ctx.strokeStyle = bc.right; + ctx.lineWidth = bw.right; + ctx.beginPath(); + ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); + ctx.lineTo(plotWidth + bw.right / 2, plotHeight); + ctx.stroke(); + } + + if (bw.bottom > 0) { + ctx.strokeStyle = bc.bottom; + ctx.lineWidth = bw.bottom; + ctx.beginPath(); + ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); + ctx.lineTo(0, plotHeight + bw.bottom / 2); + ctx.stroke(); + } + + if (bw.left > 0) { + ctx.strokeStyle = bc.left; + ctx.lineWidth = bw.left; + ctx.beginPath(); + ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); + ctx.lineTo(0- bw.left/2, 0); + ctx.stroke(); + } + } + else { + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + } } ctx.restore(); } function drawAxisLabels() { - ctx.save(); $.each(allAxes(), function (_, axis) { + var box = axis.box, + legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, + font = axis.options.font || "flot-tick-label tickLabel", + tick, x, y, halign, valign; + + // Remove text before checking for axis.show and ticks.length; + // otherwise plugins, like flot-tickrotor, that draw their own + // tick labels will end up with both theirs and the defaults. + + surface.removeText(layer); + if (!axis.show || axis.ticks.length == 0) return; - - var box = axis.box, f = axis.font; - // placeholder.append('
') // debug - - ctx.fillStyle = axis.options.color; - // Important: Don't use quotes around axis.font.family! Just around single - // font names like 'Times New Roman' that have a space or special character in it. - ctx.font = f.style + " " + f.variant + " " + f.weight + " " + f.size + "px " + f.family; - ctx.textAlign = "start"; - // middle align the labels - top would be more - // natural, but browsers can differ a pixel or two in - // where they consider the top to be, so instead we - // middle align to minimize variation between browsers - // and compensate when calculating the coordinates - ctx.textBaseline = "middle"; - + for (var i = 0; i < axis.ticks.length; ++i) { - var tick = axis.ticks[i]; + + tick = axis.ticks[i]; if (!tick.label || tick.v < axis.min || tick.v > axis.max) continue; - var x, y, offset = 0, line; - for (var k = 0; k < tick.lines.length; ++k) { - line = tick.lines[k]; - - if (axis.direction == "x") { - x = plotOffset.left + axis.p2c(tick.v) - line.width/2; - if (axis.position == "bottom") - y = box.top + box.padding; - else - y = box.top + box.height - box.padding - tick.height; - } - else { - y = plotOffset.top + axis.p2c(tick.v) - tick.height/2; - if (axis.position == "left") - x = box.left + box.width - box.padding - line.width; - else - x = box.left + box.padding; + if (axis.direction == "x") { + halign = "center"; + x = plotOffset.left + axis.p2c(tick.v); + if (axis.position == "bottom") { + y = box.top + box.padding; + } else { + y = box.top + box.height - box.padding; + valign = "bottom"; } - - // account for middle aligning and line number - y += line.height/2 + offset; - offset += line.height; - - if ($.browser.opera) { - // FIXME: UGLY BROWSER DETECTION - // round the coordinates since Opera - // otherwise switches to more ugly - // rendering (probably non-hinted) and - // offset the y coordinates since it seems - // to be off pretty consistently compared - // to the other browsers - x = Math.floor(x); - y = Math.ceil(y - 2); + } else { + valign = "middle"; + y = plotOffset.top + axis.p2c(tick.v); + if (axis.position == "left") { + x = box.left + box.width - box.padding; + halign = "right"; + } else { + x = box.left + box.padding; } - ctx.fillText(line.text, x, y); } + + surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); } }); - - ctx.restore(); } function drawSeries(series) { @@ -1759,18 +2205,18 @@ if (series.points.show) drawSeriesPoints(series); } - + function drawSeriesLines(series) { function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize, prevx = null, prevy = null; - + ctx.beginPath(); for (var i = ps; i < points.length; i += ps) { var x1 = points[i - ps], y1 = points[i - ps + 1], x2 = points[i], y2 = points[i + 1]; - + if (x1 == null || x2 == null) continue; @@ -1833,7 +2279,7 @@ if (x1 != prevx || y1 != prevy) ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); - + prevx = x2; prevy = y2; ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); @@ -1885,7 +2331,7 @@ continue; // clip x values - + // clip with xmin if (x1 <= x2 && x1 < axisx.min) { if (x2 < axisx.min) @@ -1920,7 +2366,7 @@ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); areaOpen = true; } - + // now first check the case where both is outside if (y1 >= axisy.max && y2 >= axisy.max) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); @@ -1932,7 +2378,7 @@ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); continue; } - + // else it's a bit more complicated, there might // be a flat maxed out rectangle first, then a // triangular cutout or reverse; to find these @@ -1941,7 +2387,7 @@ // clip the y values, without shortcutting, we // go through all cases in turn - + // clip with ymin if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; @@ -1968,7 +2414,7 @@ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); // it goes to (x1, y1), but we fill that below } - + // fill triangular section, this sometimes result // in redundant points if (x1, y1) hasn't changed // from previous line to, but we just ignore that @@ -2022,7 +2468,7 @@ var x = points[i], y = points[i + 1]; if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) continue; - + ctx.beginPath(); x = axisx.p2c(x); y = axisy.p2c(y) + offset; @@ -2031,7 +2477,7 @@ else symbol(ctx, x, y, radius, shadow); ctx.closePath(); - + if (fillStyle) { ctx.fillStyle = fillStyle; ctx.fill(); @@ -2039,7 +2485,7 @@ ctx.stroke(); } } - + ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); @@ -2047,6 +2493,15 @@ sw = series.shadowSize, radius = series.points.radius, symbol = series.points.symbol; + + // If the user sets the line width to 0, we change it to a very + // small value. A line width of 0 seems to force the default of 1. + // Doing the conditional here allows the shadow setting to still be + // optional even with a lineWidth of 0. + + if( lw == 0 ) + lw = 0.0001; + if (lw > 0 && sw > 0) { // draw shadow in two steps var w = sw / 2; @@ -2068,7 +2523,7 @@ ctx.restore(); } - function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { + function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { var left, right, bottom, top, drawLeft, drawRight, drawTop, drawBottom, tmp; @@ -2110,12 +2565,12 @@ drawTop = false; } } - + // clip if (right < axisx.min || left > axisx.max || top < axisy.min || bottom > axisy.max) return; - + if (left < axisx.min) { left = axisx.min; drawLeft = false; @@ -2130,7 +2585,7 @@ bottom = axisy.min; drawBottom = false; } - + if (top > axisy.max) { top = axisy.max; drawTop = false; @@ -2140,16 +2595,11 @@ bottom = axisy.p2c(bottom); right = axisx.p2c(right); top = axisy.p2c(top); - + // fill the bar if (fillStyleCallback) { - c.beginPath(); - c.moveTo(left, bottom); - c.lineTo(left, top); - c.lineTo(right, top); - c.lineTo(right, bottom); c.fillStyle = fillStyleCallback(bottom, top); - c.fill(); + c.fillRect(left, top, right - left, bottom - top) } // draw outline @@ -2157,35 +2607,35 @@ c.beginPath(); // FIXME: inline moveTo is buggy with excanvas - c.moveTo(left, bottom + offset); + c.moveTo(left, bottom); if (drawLeft) - c.lineTo(left, top + offset); + c.lineTo(left, top); else - c.moveTo(left, top + offset); + c.moveTo(left, top); if (drawTop) - c.lineTo(right, top + offset); + c.lineTo(right, top); else - c.moveTo(right, top + offset); + c.moveTo(right, top); if (drawRight) - c.lineTo(right, bottom + offset); + c.lineTo(right, bottom); else - c.moveTo(right, bottom + offset); + c.moveTo(right, bottom); if (drawBottom) - c.lineTo(left, bottom + offset); + c.lineTo(left, bottom); else - c.moveTo(left, bottom + offset); + c.moveTo(left, bottom); c.stroke(); } } - + function drawSeriesBars(series) { - function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { + function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize; - + for (var i = 0; i < points.length; i += ps) { if (points[i] == null) continue; - drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); + drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); } } @@ -2195,9 +2645,22 @@ // FIXME: figure out a way to add shadows (for instance along the right edge) ctx.lineWidth = series.bars.lineWidth; ctx.strokeStyle = series.color; - var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + + var barLeft; + + switch (series.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -series.bars.barWidth; + break; + default: + barLeft = -series.bars.barWidth / 2; + } + var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; - plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); + plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); ctx.restore(); } @@ -2208,27 +2671,66 @@ if (filloptions.fillColor) return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); - + var c = $.color.parse(seriesColor); c.a = typeof fill == "number" ? fill : 0.4; c.normalize(); return c.toString(); } - + function insertLegend() { - placeholder.find(".legend").remove(); - if (!options.legend.show) + if (options.legend.container != null) { + $(options.legend.container).html(""); + } else { + placeholder.find(".legend").remove(); + } + + if (!options.legend.show) { return; - - var fragments = [], rowStarted = false, + } + + var fragments = [], entries = [], rowStarted = false, lf = options.legend.labelFormatter, s, label; + + // Build a list of legend entries, with each having a label and a color + for (var i = 0; i < series.length; ++i) { s = series[i]; - label = s.label; - if (!label) - continue; - + if (s.label) { + label = lf ? lf(s.label, s) : s.label; + if (label) { + entries.push({ + label: label, + color: s.color + }); + } + } + } + + // Sort the legend using either the default or a custom comparator + + if (options.legend.sorted) { + if ($.isFunction(options.legend.sorted)) { + entries.sort(options.legend.sorted); + } else if (options.legend.sorted == "reverse") { + entries.reverse(); + } else { + var ascending = options.legend.sorted != "descending"; + entries.sort(function(a, b) { + return a.label == b.label ? 0 : ( + (a.label < b.label) != ascending ? 1 : -1 // Logical XOR + ); + }); + } + } + + // Generate markup for the list of entries, in their final order + + for (var i = 0; i < entries.length; ++i) { + + var entry = entries[i]; + if (i % options.legend.noColumns == 0) { if (rowStarted) fragments.push(''); @@ -2236,16 +2738,15 @@ rowStarted = true; } - if (lf) - label = lf(label, s); - fragments.push( - '
' + - '' + label + ''); + '
' + + '' + entry.label + '' + ); } + if (rowStarted) fragments.push(''); - + if (fragments.length == 0) return; @@ -2289,43 +2790,43 @@ // interactive features - + var highlights = [], redrawTimeout = null; - + // returns the data item the mouse is over, or null if none is found function findNearbyItem(mouseX, mouseY, seriesFilter) { var maxDistance = options.grid.mouseActiveRadius, smallestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false, i, j; + item = null, foundPoint = false, i, j, ps; for (i = series.length - 1; i >= 0; --i) { if (!seriesFilter(series[i])) continue; - + var s = series[i], axisx = s.xaxis, axisy = s.yaxis, points = s.datapoints.points, - ps = s.datapoints.pointsize, mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster my = axisy.c2p(mouseY), maxx = maxDistance / axisx.scale, maxy = maxDistance / axisy.scale; + ps = s.datapoints.pointsize; // with inverse transforms, we can't use the maxx/maxy // optimization, sadly if (axisx.options.inverseTransform) maxx = Number.MAX_VALUE; if (axisy.options.inverseTransform) maxy = Number.MAX_VALUE; - + if (s.lines.show || s.points.show) { for (j = 0; j < points.length; j += ps) { var x = points[j], y = points[j + 1]; if (x == null) continue; - + // For points and lines, the cursor must be within a // certain distance to the data point if (x - mx > maxx || x - mx < -maxx || @@ -2346,19 +2847,32 @@ } } } - + if (s.bars.show && !item) { // no other point can be nearby - var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, - barRight = barLeft + s.bars.barWidth; - + + var barLeft, barRight; + + switch (s.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -s.bars.barWidth; + break; + default: + barLeft = -s.bars.barWidth / 2; + } + + barRight = barLeft + s.bars.barWidth; + for (j = 0; j < points.length; j += ps) { var x = points[j], y = points[j + 1], b = points[j + 2]; if (x == null) continue; - + // for a bar graph, the cursor must be inside the bar - if (series[i].bars.horizontal ? - (mx <= Math.max(b, x) && mx >= Math.min(b, x) && + if (series[i].bars.horizontal ? + (mx <= Math.max(b, x) && mx >= Math.min(b, x) && my >= y + barLeft && my <= y + barRight) : (mx >= x + barLeft && mx <= x + barRight && my >= Math.min(b, y) && my <= Math.max(b, y))) @@ -2371,13 +2885,13 @@ i = item[0]; j = item[1]; ps = series[i].datapoints.pointsize; - + return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), dataIndex: j, series: series[i], seriesIndex: i }; } - + return null; } @@ -2413,8 +2927,8 @@ if (item) { // fill in mouse pos for any listeners out there - item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); - item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); + item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); + item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); } if (options.grid.autoHighlight) { @@ -2427,11 +2941,11 @@ h.point[1] == item.datapoint[1])) unhighlight(h.series, h.point); } - + if (item) highlight(item.series, item.datapoint, eventname); } - + placeholder.trigger(eventname, [ pos, item ]); } @@ -2441,7 +2955,7 @@ drawOverlay(); return; } - + if (!redrawTimeout) redrawTimeout = setTimeout(drawOverlay, t); } @@ -2451,9 +2965,9 @@ // draw highlights octx.save(); - octx.clearRect(0, 0, canvasWidth, canvasHeight); + overlay.clear(); octx.translate(plotOffset.left, plotOffset.top); - + var i, hi; for (i = 0; i < highlights.length; ++i) { hi = highlights[i]; @@ -2464,10 +2978,10 @@ drawPointHighlight(hi.series, hi.point); } octx.restore(); - + executeHooks(hooks.drawOverlay, [octx]); } - + function highlight(s, point, auto) { if (typeof s == "number") s = series[s]; @@ -2486,18 +3000,21 @@ else if (!auto) highlights[i].auto = false; } - + function unhighlight(s, point) { if (s == null && point == null) { highlights = []; triggerRedrawOverlay(); + return; } - + if (typeof s == "number") s = series[s]; - if (typeof point == "number") - point = s.data[point]; + if (typeof point == "number") { + var ps = s.datapoints.pointsize; + point = s.datapoints.points.slice(ps * point, ps * (point + 1)); + } var i = indexOfHighlight(s, point); if (i != -1) { @@ -2506,7 +3023,7 @@ triggerRedrawOverlay(); } } - + function indexOfHighlight(s, p) { for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; @@ -2516,21 +3033,22 @@ } return -1; } - + function drawPointHighlight(series, point) { var x = point[0], y = point[1], - axisx = series.xaxis, axisy = series.yaxis; - + axisx = series.xaxis, axisy = series.yaxis, + highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); + if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) return; - + var pointRadius = series.points.radius + series.points.lineWidth / 2; octx.lineWidth = pointRadius; - octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); - var radius = 1.5 * pointRadius, - x = axisx.p2c(x), - y = axisy.p2c(y); - + octx.strokeStyle = highlightColor; + var radius = 1.5 * pointRadius; + x = axisx.p2c(x); + y = axisy.p2c(y); + octx.beginPath(); if (series.points.symbol == "circle") octx.arc(x, y, radius, 0, 2 * Math.PI, false); @@ -2541,12 +3059,26 @@ } function drawBarHighlight(series, point) { + var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), + fillStyle = highlightColor, + barLeft; + + switch (series.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -series.bars.barWidth; + break; + default: + barLeft = -series.bars.barWidth / 2; + } + octx.lineWidth = series.bars.lineWidth; - octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); - var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString(); - var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + octx.strokeStyle = highlightColor; + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, - 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); + function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); } function getColorOrGradient(spec, bottom, top, defaultColor) { @@ -2557,7 +3089,7 @@ // supports a simple vertical gradient properly, so that's // what we support too var gradient = ctx.createLinearGradient(0, top, 0, bottom); - + for (var i = 0, l = spec.colors.length; i < l; ++i) { var c = spec.colors[i]; if (typeof c != "string") { @@ -2570,12 +3102,14 @@ } gradient.addColorStop(i / (l - 1), c); } - + return gradient; } } } + // Add the plot function to the top level of the jQuery object + $.plot = function(placeholder, data, options) { //var t0 = new Date(); var plot = new Plot($(placeholder), data, options, $.plot.plugins); @@ -2583,69 +3117,21 @@ return plot; }; - $.plot.version = "0.7"; - + $.plot.version = "0.8.2"; + $.plot.plugins = []; - // returns a string with the date d formatted according to fmt - $.plot.formatDate = function(d, fmt, monthNames) { - var leftPad = function(n) { - n = "" + n; - return n.length == 1 ? "0" + n : n; - }; - - var r = []; - var escape = false, padNext = false; - var hours = d.getUTCHours(); - var isAM = hours < 12; - if (monthNames == null) - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - - if (fmt.search(/%p|%P/) != -1) { - if (hours > 12) { - hours = hours - 12; - } else if (hours == 0) { - hours = 12; - } - } - for (var i = 0; i < fmt.length; ++i) { - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'h': c = "" + hours; break; - case 'H': c = leftPad(hours); break; - case 'M': c = leftPad(d.getUTCMinutes()); break; - case 'S': c = leftPad(d.getUTCSeconds()); break; - case 'd': c = "" + d.getUTCDate(); break; - case 'm': c = "" + (d.getUTCMonth() + 1); break; - case 'y': c = "" + d.getUTCFullYear(); break; - case 'b': c = "" + monthNames[d.getUTCMonth()]; break; - case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; - case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; - case '0': c = ""; padNext = true; break; - } - if (c && padNext) { - c = leftPad(c); - padNext = false; - } - r.push(c); - if (!padNext) - escape = false; - } - else { - if (c == "%") - escape = true; - else - r.push(c); - } - } - return r.join(""); + // Also add the plot function as a chainable property + + $.fn.plot = function(data, options) { + return this.each(function() { + $.plot(this, data, options); + }); }; - + // round to nearby lower multiple of base function floorInBase(n, base) { return base * Math.floor(n / base); } - + })(jQuery); diff --git a/Web Server/flot/jquery.flot.min.js b/Web Server/flot/jquery.flot.min.js new file mode 100644 index 0000000..9620fc0 --- /dev/null +++ b/Web Server/flot/jquery.flot.min.js @@ -0,0 +1,2 @@ +(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function($){var hasOwnProperty=Object.prototype.hasOwnProperty;function Canvas(cls,container){var element=container.children("."+cls)[0];if(element==null){element=document.createElement("canvas");element.className=cls;$(element).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(container);if(!element.getContext){if(window.G_vmlCanvasManager){element=window.G_vmlCanvasManager.initElement(element)}else{throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")}}}this.element=element;var context=this.context=element.getContext("2d");var devicePixelRatio=window.devicePixelRatio||1,backingStoreRatio=context.webkitBackingStorePixelRatio||context.mozBackingStorePixelRatio||context.msBackingStorePixelRatio||context.oBackingStorePixelRatio||context.backingStorePixelRatio||1;this.pixelRatio=devicePixelRatio/backingStoreRatio;this.resize(container.width(),container.height());this.textContainer=null;this.text={};this._textCache={}}Canvas.prototype.resize=function(width,height){if(width<=0||height<=0){throw new Error("Invalid dimensions for plot, width = "+width+", height = "+height)}var element=this.element,context=this.context,pixelRatio=this.pixelRatio;if(this.width!=width){element.width=width*pixelRatio;element.style.width=width+"px";this.width=width}if(this.height!=height){element.height=height*pixelRatio;element.style.height=height+"px";this.height=height}context.restore();context.save();context.scale(pixelRatio,pixelRatio)};Canvas.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)};Canvas.prototype.render=function(){var cache=this._textCache;for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layer=this.getTextLayer(layerKey),layerCache=cache[layerKey];layer.hide();for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){if(position.active){if(!position.rendered){layer.append(position.element);position.rendered=true}}else{positions.splice(i--,1);if(position.rendered){position.element.detach()}}}if(positions.length==0){delete styleCache[key]}}}}}layer.show()}}};Canvas.prototype.getTextLayer=function(classes){var layer=this.text[classes];if(layer==null){if(this.textContainer==null){this.textContainer=$("
").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)}layer=this.text[classes]=$("
").addClass(classes).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)}return layer};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px/"+font.lineHeight+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var element=$("
").html(text).css({position:"absolute","max-width":width,top:-9999}).appendTo(this.getTextLayer(layer));if(typeof font==="object"){element.css({font:textStyle,color:font.color})}else if(typeof font==="string"){element.addClass(font)}info=styleCache[text]={width:element.outerWidth(true),height:element.outerHeight(true),element:element,positions:[]};element.detach()}return info};Canvas.prototype.addText=function(layer,x,y,text,font,angle,width,halign,valign){var info=this.getTextInfo(layer,text,font,angle,width),positions=info.positions;if(halign=="center"){x-=info.width/2}else if(halign=="right"){x-=info.width}if(valign=="middle"){y-=info.height/2}else if(valign=="bottom"){y-=info.height}for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=true;return}}position={active:true,rendered:false,element:positions.length?info.element.clone():info.element,x:x,y:y};positions.push(position);position.element.css({top:Math.round(y),left:Math.round(x),"text-align":halign})};Canvas.prototype.removeText=function(layer,x,y,text,font,angle){if(text==null){var layerCache=this._textCache[layer];if(layerCache!=null){for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){position.active=false}}}}}}}else{var positions=this.getTextInfo(layer,text,font,angle).positions;for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=false}}}};function Plot(placeholder,data_,options_,plugins){var series=[],options={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:.85,sorted:null},xaxis:{show:null,position:"bottom",mode:null,font:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null},yaxis:{autoscaleMargin:.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false,zero:true},shadowSize:3,highlightColor:null},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,margin:0,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},interaction:{redrawOverlayInterval:1e3/60},hooks:{}},surface=null,overlay=null,eventHolder=null,ctx=null,octx=null,xaxes=[],yaxes=[],plotOffset={left:0,right:0,top:0,bottom:0},plotWidth=0,plotHeight=0,hooks={processOptions:[],processRawData:[],processDatapoints:[],processOffset:[],drawBackground:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},plot=this;plot.setData=setData;plot.setupGrid=setupGrid;plot.draw=draw;plot.getPlaceholder=function(){return placeholder};plot.getCanvas=function(){return surface.element};plot.getPlotOffset=function(){return plotOffset};plot.width=function(){return plotWidth};plot.height=function(){return plotHeight};plot.offset=function(){var o=eventHolder.offset();o.left+=plotOffset.left;o.top+=plotOffset.top;return o};plot.getData=function(){return series};plot.getAxes=function(){var res={},i;$.each(xaxes.concat(yaxes),function(_,axis){if(axis)res[axis.direction+(axis.n!=1?axis.n:"")+"axis"]=axis});return res};plot.getXAxes=function(){return xaxes};plot.getYAxes=function(){return yaxes};plot.c2p=canvasToAxisCoords;plot.p2c=axisToCanvasCoords;plot.getOptions=function(){return options};plot.highlight=highlight;plot.unhighlight=unhighlight;plot.triggerRedrawOverlay=triggerRedrawOverlay;plot.pointOffset=function(point){return{left:parseInt(xaxes[axisNumber(point,"x")-1].p2c(+point.x)+plotOffset.left,10),top:parseInt(yaxes[axisNumber(point,"y")-1].p2c(+point.y)+plotOffset.top,10)}};plot.shutdown=shutdown;plot.destroy=function(){shutdown();placeholder.removeData("plot").empty();series=[];options=null;surface=null;overlay=null;eventHolder=null;ctx=null;octx=null;xaxes=[];yaxes=[];hooks=null;highlights=[];plot=null};plot.resize=function(){var width=placeholder.width(),height=placeholder.height();surface.resize(width,height);overlay.resize(width,height)};plot.hooks=hooks;initPlugins(plot);parseOptions(options_);setupCanvases();setData(data_);setupGrid();draw();bindEvents();function executeHooks(hook,args){args=[plot].concat(args);for(var i=0;imaxIndex){maxIndex=sc}}}if(neededColors<=maxIndex){neededColors=maxIndex+1}var c,colors=[],colorPool=options.colors,colorPoolSize=colorPool.length,variation=0;for(i=0;i=0){if(variation<.5){variation=-variation-.2}else variation=0}else variation=-variation}colors[i]=c.scale("rgb",1+variation)}var colori=0,s;for(i=0;iaxis.datamax&&max!=fakeInfinity)axis.datamax=max}$.each(allAxes(),function(_,axis){axis.datamin=topSentry;axis.datamax=bottomSentry;axis.used=false});for(i=0;i0&&points[k-ps]!=null&&points[k-ps]!=points[k]&&points[k-ps+1]!=points[k+1]){for(m=0;mxmax)xmax=val}if(f.y){if(valymax)ymax=val}}}if(s.bars.show){var delta;switch(s.bars.align){case"left":delta=0;break;case"right":delta=-s.bars.barWidth;break;default:delta=-s.bars.barWidth/2}if(s.bars.horizontal){ymin+=delta;ymax+=delta+s.bars.barWidth}else{xmin+=delta;xmax+=delta+s.bars.barWidth}}updateAxis(s.xaxis,xmin,xmax);updateAxis(s.yaxis,ymin,ymax)}$.each(allAxes(),function(_,axis){if(axis.datamin==topSentry)axis.datamin=null;if(axis.datamax==bottomSentry)axis.datamax=null})}function setupCanvases(){placeholder.css("padding",0).children().filter(function(){return!$(this).hasClass("flot-overlay")&&!$(this).hasClass("flot-base")}).remove();if(placeholder.css("position")=="static")placeholder.css("position","relative");surface=new Canvas("flot-base",placeholder);overlay=new Canvas("flot-overlay",placeholder);ctx=surface.context;octx=overlay.context;eventHolder=$(overlay.element).unbind();var existing=placeholder.data("plot");if(existing){existing.shutdown();overlay.clear()}placeholder.data("plot",plot)}function bindEvents(){if(options.grid.hoverable){eventHolder.mousemove(onMouseMove);eventHolder.bind("mouseleave",onMouseLeave)}if(options.grid.clickable)eventHolder.click(onClick);executeHooks(hooks.bindEvents,[eventHolder])}function shutdown(){if(redrawTimeout)clearTimeout(redrawTimeout);eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mouseleave",onMouseLeave);eventHolder.unbind("click",onClick);executeHooks(hooks.shutdown,[eventHolder])}function setTransformationHelpers(axis){function identity(x){return x}var s,m,t=axis.options.transform||identity,it=axis.options.inverseTransform;if(axis.direction=="x"){s=axis.scale=plotWidth/Math.abs(t(axis.max)-t(axis.min));m=Math.min(t(axis.max),t(axis.min))}else{s=axis.scale=plotHeight/Math.abs(t(axis.max)-t(axis.min));s=-s;m=Math.max(t(axis.max),t(axis.min))}if(t==identity)axis.p2c=function(p){return(p-m)*s};else axis.p2c=function(p){return(t(p)-m)*s};if(!it)axis.c2p=function(c){return m+c/s};else axis.c2p=function(c){return it(m+c/s)}}function measureTickLabels(axis){var opts=axis.options,ticks=axis.ticks||[],labelWidth=opts.labelWidth||0,labelHeight=opts.labelHeight||0,maxWidth=labelWidth||(axis.direction=="x"?Math.floor(surface.width/(ticks.length||1)):null),legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=opts.font||"flot-tick-label tickLabel";for(var i=0;i=0;--i)allocateAxisBoxFirstPhase(allocatedAxes[i]);adjustLayoutForThingsStickingOut();$.each(allocatedAxes,function(_,axis){allocateAxisBoxSecondPhase(axis)})}plotWidth=surface.width-plotOffset.left-plotOffset.right;plotHeight=surface.height-plotOffset.bottom-plotOffset.top;$.each(axes,function(_,axis){setTransformationHelpers(axis)});if(showGrid){drawAxisLabels()}insertLegend()}function setRange(axis){var opts=axis.options,min=+(opts.min!=null?opts.min:axis.datamin),max=+(opts.max!=null?opts.max:axis.datamax),delta=max-min;if(delta==0){var widen=max==0?1:.01;if(opts.min==null)min-=widen;if(opts.max==null||opts.min!=null)max+=widen}else{var margin=opts.autoscaleMargin;if(margin!=null){if(opts.min==null){min-=delta*margin;if(min<0&&axis.datamin!=null&&axis.datamin>=0)min=0}if(opts.max==null){max+=delta*margin;if(max>0&&axis.datamax!=null&&axis.datamax<=0)max=0}}}axis.min=min;axis.max=max}function setupTickGeneration(axis){var opts=axis.options;var noTicks;if(typeof opts.ticks=="number"&&opts.ticks>0)noTicks=opts.ticks;else noTicks=.3*Math.sqrt(axis.direction=="x"?surface.width:surface.height);var delta=(axis.max-axis.min)/noTicks,dec=-Math.floor(Math.log(delta)/Math.LN10),maxDec=opts.tickDecimals;if(maxDec!=null&&dec>maxDec){dec=maxDec}var magn=Math.pow(10,-dec),norm=delta/magn,size;if(norm<1.5){size=1}else if(norm<3){size=2;if(norm>2.25&&(maxDec==null||dec+1<=maxDec)){size=2.5;++dec}}else if(norm<7.5){size=5}else{size=10}size*=magn;if(opts.minTickSize!=null&&size0){if(opts.min==null)axis.min=Math.min(axis.min,niceTicks[0]);if(opts.max==null&&niceTicks.length>1)axis.max=Math.max(axis.max,niceTicks[niceTicks.length-1])}axis.tickGenerator=function(axis){var ticks=[],v,i;for(i=0;i1&&/\..*0$/.test((ts[1]-ts[0]).toFixed(extraDec))))axis.tickDecimals=extraDec}}}}function setTicks(axis){var oticks=axis.options.ticks,ticks=[];if(oticks==null||typeof oticks=="number"&&oticks>0)ticks=axis.tickGenerator(axis);else if(oticks){if($.isFunction(oticks))ticks=oticks(axis);else ticks=oticks}var i,v;axis.ticks=[];for(i=0;i1)label=t[1]}else v=+t;if(label==null)label=axis.tickFormatter(v,axis);if(!isNaN(v))axis.ticks.push({v:v,label:label})}}function snapRangeToTicks(axis,ticks){if(axis.options.autoscaleMargin&&ticks.length>0){if(axis.options.min==null)axis.min=Math.min(axis.min,ticks[0].v);if(axis.options.max==null&&ticks.length>1)axis.max=Math.max(axis.max,ticks[ticks.length-1].v)}}function draw(){surface.clear();executeHooks(hooks.drawBackground,[ctx]);var grid=options.grid;if(grid.show&&grid.backgroundColor)drawBackground();if(grid.show&&!grid.aboveData){drawGrid()}for(var i=0;ito){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function drawBackground(){ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.fillStyle=getColorOrGradient(options.grid.backgroundColor,plotHeight,0,"rgba(255, 255, 255, 0)");ctx.fillRect(0,0,plotWidth,plotHeight);ctx.restore()}function drawGrid(){var i,axes,bw,bc;ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var markings=options.grid.markings;if(markings){if($.isFunction(markings)){axes=plot.getAxes();axes.xmin=axes.xaxis.min;axes.xmax=axes.xaxis.max;axes.ymin=axes.yaxis.min;axes.ymax=axes.yaxis.max;markings=markings(axes)}for(i=0;ixrange.axis.max||yrange.toyrange.axis.max)continue;xrange.from=Math.max(xrange.from,xrange.axis.min);xrange.to=Math.min(xrange.to,xrange.axis.max); +yrange.from=Math.max(yrange.from,yrange.axis.min);yrange.to=Math.min(yrange.to,yrange.axis.max);if(xrange.from==xrange.to&&yrange.from==yrange.to)continue;xrange.from=xrange.axis.p2c(xrange.from);xrange.to=xrange.axis.p2c(xrange.to);yrange.from=yrange.axis.p2c(yrange.from);yrange.to=yrange.axis.p2c(yrange.to);if(xrange.from==xrange.to||yrange.from==yrange.to){ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=m.lineWidth||options.grid.markingsLineWidth;ctx.moveTo(xrange.from,yrange.from);ctx.lineTo(xrange.to,yrange.to);ctx.stroke()}else{ctx.fillStyle=m.color||options.grid.markingsColor;ctx.fillRect(xrange.from,yrange.to,xrange.to-xrange.from,yrange.from-yrange.to)}}}axes=allAxes();bw=options.grid.borderWidth;for(var j=0;jaxis.max||t=="full"&&(typeof bw=="object"&&bw[axis.position]>0||bw>0)&&(v==axis.min||v==axis.max))continue;if(axis.direction=="x"){x=axis.p2c(v);yoff=t=="full"?-plotHeight:t;if(axis.position=="top")yoff=-yoff}else{y=axis.p2c(v);xoff=t=="full"?-plotWidth:t;if(axis.position=="left")xoff=-xoff}if(ctx.lineWidth==1){if(axis.direction=="x")x=Math.floor(x)+.5;else y=Math.floor(y)+.5}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff)}ctx.stroke()}if(bw){bc=options.grid.borderColor;if(typeof bw=="object"||typeof bc=="object"){if(typeof bw!=="object"){bw={top:bw,right:bw,bottom:bw,left:bw}}if(typeof bc!=="object"){bc={top:bc,right:bc,bottom:bc,left:bc}}if(bw.top>0){ctx.strokeStyle=bc.top;ctx.lineWidth=bw.top;ctx.beginPath();ctx.moveTo(0-bw.left,0-bw.top/2);ctx.lineTo(plotWidth,0-bw.top/2);ctx.stroke()}if(bw.right>0){ctx.strokeStyle=bc.right;ctx.lineWidth=bw.right;ctx.beginPath();ctx.moveTo(plotWidth+bw.right/2,0-bw.top);ctx.lineTo(plotWidth+bw.right/2,plotHeight);ctx.stroke()}if(bw.bottom>0){ctx.strokeStyle=bc.bottom;ctx.lineWidth=bw.bottom;ctx.beginPath();ctx.moveTo(plotWidth+bw.right,plotHeight+bw.bottom/2);ctx.lineTo(0,plotHeight+bw.bottom/2);ctx.stroke()}if(bw.left>0){ctx.strokeStyle=bc.left;ctx.lineWidth=bw.left;ctx.beginPath();ctx.moveTo(0-bw.left/2,plotHeight+bw.bottom);ctx.lineTo(0-bw.left/2,0);ctx.stroke()}}else{ctx.lineWidth=bw;ctx.strokeStyle=options.grid.borderColor;ctx.strokeRect(-bw/2,-bw/2,plotWidth+bw,plotHeight+bw)}}ctx.restore()}function drawAxisLabels(){$.each(allAxes(),function(_,axis){var box=axis.box,legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=axis.options.font||"flot-tick-label tickLabel",tick,x,y,halign,valign;surface.removeText(layer);if(!axis.show||axis.ticks.length==0)return;for(var i=0;iaxis.max)continue;if(axis.direction=="x"){halign="center";x=plotOffset.left+axis.p2c(tick.v);if(axis.position=="bottom"){y=box.top+box.padding}else{y=box.top+box.height-box.padding;valign="bottom"}}else{valign="middle";y=plotOffset.top+axis.p2c(tick.v);if(axis.position=="left"){x=box.left+box.width-box.padding;halign="right"}else{x=box.left+box.padding}}surface.addText(layer,x,y,tick.label,font,null,null,halign,valign)}})}function drawSeries(series){if(series.lines.show)drawSeriesLines(series);if(series.bars.show)drawSeriesBars(series);if(series.points.show)drawSeriesPoints(series)}function drawSeriesLines(series){function plotLine(datapoints,xoffset,yoffset,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,prevx=null,prevy=null;ctx.beginPath();for(var i=ps;i=y2&&y1>axisy.max){if(y2>axisy.max)continue;x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max){if(y1>axisy.max)continue;x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1<=x2&&x1=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(x1!=prevx||y1!=prevy)ctx.moveTo(axisx.p2c(x1)+xoffset,axisy.p2c(y1)+yoffset);prevx=x2;prevy=y2;ctx.lineTo(axisx.p2c(x2)+xoffset,axisy.p2c(y2)+yoffset)}ctx.stroke()}function plotLineArea(datapoints,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,bottom=Math.min(Math.max(0,axisy.min),axisy.max),i=0,top,areaOpen=false,ypos=1,segmentStart=0,segmentEnd=0;while(true){if(ps>0&&i>points.length+ps)break;i+=ps;var x1=points[i-ps],y1=points[i-ps+ypos],x2=points[i],y2=points[i+ypos];if(areaOpen){if(ps>0&&x1!=null&&x2==null){segmentEnd=i;ps=-ps;ypos=2;continue}if(ps<0&&i==segmentStart+ps){ctx.fill();areaOpen=false;ps=-ps;ypos=1;i=segmentStart=segmentEnd+ps;continue}}if(x1==null||x2==null)continue;if(x1<=x2&&x1=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(!areaOpen){ctx.beginPath();ctx.moveTo(axisx.p2c(x1),axisy.p2c(bottom));areaOpen=true}if(y1>=axisy.max&&y2>=axisy.max){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.max));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.max));continue}else if(y1<=axisy.min&&y2<=axisy.min){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.min));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.min));continue}var x1old=x1,x2old=x2;if(y1<=y2&&y1=axisy.min){x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else if(y2<=y1&&y2=axisy.min){x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}if(y1>=y2&&y1>axisy.max&&y2<=axisy.max){x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max&&y1<=axisy.max){x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1!=x1old){ctx.lineTo(axisx.p2c(x1old),axisy.p2c(y1))}ctx.lineTo(axisx.p2c(x1),axisy.p2c(y1));ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));if(x2!=x2old){ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));ctx.lineTo(axisx.p2c(x2old),axisy.p2c(y2))}}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineJoin="round";var lw=series.lines.lineWidth,sw=series.shadowSize;if(lw>0&&sw>0){ctx.lineWidth=sw;ctx.strokeStyle="rgba(0,0,0,0.1)";var angle=Math.PI/18;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/2),Math.cos(angle)*(lw/2+sw/2),series.xaxis,series.yaxis);ctx.lineWidth=sw/2;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/4),Math.cos(angle)*(lw/2+sw/4),series.xaxis,series.yaxis)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;var fillStyle=getFillStyle(series.lines,series.color,0,plotHeight);if(fillStyle){ctx.fillStyle=fillStyle;plotLineArea(series.datapoints,series.xaxis,series.yaxis)}if(lw>0)plotLine(series.datapoints,0,0,series.xaxis,series.yaxis);ctx.restore()}function drawSeriesPoints(series){function plotPoints(datapoints,radius,fillStyle,offset,shadow,axisx,axisy,symbol){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;iaxisx.max||yaxisy.max)continue;ctx.beginPath();x=axisx.p2c(x);y=axisy.p2c(y)+offset;if(symbol=="circle")ctx.arc(x,y,radius,0,shadow?Math.PI:Math.PI*2,false);else symbol(ctx,x,y,radius,shadow);ctx.closePath();if(fillStyle){ctx.fillStyle=fillStyle;ctx.fill()}ctx.stroke()}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var lw=series.points.lineWidth,sw=series.shadowSize,radius=series.points.radius,symbol=series.points.symbol;if(lw==0)lw=1e-4;if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";plotPoints(series.datapoints,radius,null,w+w/2,true,series.xaxis,series.yaxis,symbol);ctx.strokeStyle="rgba(0,0,0,0.2)";plotPoints(series.datapoints,radius,null,w/2,true,series.xaxis,series.yaxis,symbol)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;plotPoints(series.datapoints,radius,getFillStyle(series.points,series.color),0,false,series.xaxis,series.yaxis,symbol);ctx.restore()}function drawBar(x,y,b,barLeft,barRight,fillStyleCallback,axisx,axisy,c,horizontal,lineWidth){var left,right,bottom,top,drawLeft,drawRight,drawTop,drawBottom,tmp;if(horizontal){drawBottom=drawRight=drawTop=true;drawLeft=false;left=b;right=x;top=y+barLeft;bottom=y+barRight;if(rightaxisx.max||topaxisy.max)return;if(leftaxisx.max){right=axisx.max;drawRight=false}if(bottomaxisy.max){top=axisy.max;drawTop=false}left=axisx.p2c(left);bottom=axisy.p2c(bottom);right=axisx.p2c(right);top=axisy.p2c(top);if(fillStyleCallback){c.fillStyle=fillStyleCallback(bottom,top);c.fillRect(left,top,right-left,bottom-top)}if(lineWidth>0&&(drawLeft||drawRight||drawTop||drawBottom)){c.beginPath();c.moveTo(left,bottom);if(drawLeft)c.lineTo(left,top);else c.moveTo(left,top);if(drawTop)c.lineTo(right,top);else c.moveTo(right,top);if(drawRight)c.lineTo(right,bottom);else c.moveTo(right,bottom);if(drawBottom)c.lineTo(left,bottom);else c.moveTo(left,bottom);c.stroke()}}function drawSeriesBars(series){function plotBars(datapoints,barLeft,barRight,fillStyleCallback,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i");fragments.push("");rowStarted=true}fragments.push('
'+''+entry.label+"")}if(rowStarted)fragments.push("");if(fragments.length==0)return;var table=''+fragments.join("")+"
";if(options.legend.container!=null)$(options.legend.container).html(table);else{var pos="",p=options.legend.position,m=options.legend.margin;if(m[0]==null)m=[m,m];if(p.charAt(0)=="n")pos+="top:"+(m[1]+plotOffset.top)+"px;";else if(p.charAt(0)=="s")pos+="bottom:"+(m[1]+plotOffset.bottom)+"px;";if(p.charAt(1)=="e")pos+="right:"+(m[0]+plotOffset.right)+"px;";else if(p.charAt(1)=="w")pos+="left:"+(m[0]+plotOffset.left)+"px;";var legend=$('
'+table.replace('style="','style="position:absolute;'+pos+";")+"
").appendTo(placeholder);if(options.legend.backgroundOpacity!=0){var c=options.legend.backgroundColor;if(c==null){c=options.grid.backgroundColor;if(c&&typeof c=="string")c=$.color.parse(c);else c=$.color.extract(legend,"background-color");c.a=1;c=c.toString()}var div=legend.children();$('
').prependTo(legend).css("opacity",options.legend.backgroundOpacity)}}}var highlights=[],redrawTimeout=null;function findNearbyItem(mouseX,mouseY,seriesFilter){var maxDistance=options.grid.mouseActiveRadius,smallestDistance=maxDistance*maxDistance+1,item=null,foundPoint=false,i,j,ps;for(i=series.length-1;i>=0;--i){if(!seriesFilter(series[i]))continue;var s=series[i],axisx=s.xaxis,axisy=s.yaxis,points=s.datapoints.points,mx=axisx.c2p(mouseX),my=axisy.c2p(mouseY),maxx=maxDistance/axisx.scale,maxy=maxDistance/axisy.scale;ps=s.datapoints.pointsize;if(axisx.options.inverseTransform)maxx=Number.MAX_VALUE;if(axisy.options.inverseTransform)maxy=Number.MAX_VALUE;if(s.lines.show||s.points.show){for(j=0;jmaxx||x-mx<-maxx||y-my>maxy||y-my<-maxy)continue;var dx=Math.abs(axisx.p2c(x)-mouseX),dy=Math.abs(axisy.p2c(y)-mouseY),dist=dx*dx+dy*dy;if(dist=Math.min(b,x)&&my>=y+barLeft&&my<=y+barRight:mx>=x+barLeft&&mx<=x+barRight&&my>=Math.min(b,y)&&my<=Math.max(b,y))item=[i,j/ps]}}}if(item){i=item[0];j=item[1];ps=series[i].datapoints.pointsize;return{datapoint:series[i].datapoints.points.slice(j*ps,(j+1)*ps),dataIndex:j,series:series[i],seriesIndex:i}}return null}function onMouseMove(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return s["hoverable"]!=false})}function onMouseLeave(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return false})}function onClick(e){triggerClickHoverEvent("plotclick",e,function(s){return s["clickable"]!=false})}function triggerClickHoverEvent(eventname,event,seriesFilter){var offset=eventHolder.offset(),canvasX=event.pageX-offset.left-plotOffset.left,canvasY=event.pageY-offset.top-plotOffset.top,pos=canvasToAxisCoords({left:canvasX,top:canvasY});pos.pageX=event.pageX;pos.pageY=event.pageY;var item=findNearbyItem(canvasX,canvasY,seriesFilter);if(item){item.pageX=parseInt(item.series.xaxis.p2c(item.datapoint[0])+offset.left+plotOffset.left,10);item.pageY=parseInt(item.series.yaxis.p2c(item.datapoint[1])+offset.top+plotOffset.top,10)}if(options.grid.autoHighlight){for(var i=0;iaxisx.max||yaxisy.max)return;var pointRadius=series.points.radius+series.points.lineWidth/2;octx.lineWidth=pointRadius;octx.strokeStyle=highlightColor;var radius=1.5*pointRadius;x=axisx.p2c(x);y=axisy.p2c(y);octx.beginPath();if(series.points.symbol=="circle")octx.arc(x,y,radius,0,2*Math.PI,false);else series.points.symbol(octx,x,y,radius,false);octx.closePath();octx.stroke()}function drawBarHighlight(series,point){var highlightColor=typeof series.highlightColor==="string"?series.highlightColor:$.color.parse(series.color).scale("a",.5).toString(),fillStyle=highlightColor,barLeft;switch(series.bars.align){case"left":barLeft=0;break;case"right":barLeft=-series.bars.barWidth;break;default:barLeft=-series.bars.barWidth/2}octx.lineWidth=series.bars.lineWidth;octx.strokeStyle=highlightColor;drawBar(point[0],point[1],point[2]||0,barLeft,barLeft+series.bars.barWidth,function(){return fillStyle},series.xaxis,series.yaxis,octx,series.bars.horizontal,series.bars.lineWidth)}function getColorOrGradient(spec,bottom,top,defaultColor){if(typeof spec=="string")return spec;else{var gradient=ctx.createLinearGradient(0,top,0,bottom);for(var i=0,l=spec.colors.length;i0&&L.which!=M.which)||E(L.target).is(M.not)){return }}switch(L.type){case"mousedown":E.extend(M,E(K).offset(),{elem:K,target:L.target,pageX:L.pageX,pageY:L.pageY});A.add(document,"mousemove mouseup",H,M);G(K,false);F.dragging=null;return false;case !F.dragging&&"mousemove":if(I(L.pageX-M.pageX)+I(L.pageY-M.pageY)0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY) pr[1]) { + max = pr[1]; + } + } + var range = max - min; if (zr && ((zr[0] != null && range < zr[0]) || @@ -262,8 +272,8 @@ Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-L plot.draw(); if (!args.preventEvent) - plot.getPlaceholder().trigger("plotzoom", [ plot ]); - } + plot.getPlaceholder().trigger("plotzoom", [ plot, args ]); + }; plot.pan = function (args) { var delta = { @@ -310,8 +320,8 @@ Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-L plot.draw(); if (!args.preventEvent) - plot.getPlaceholder().trigger("plotpan", [ plot ]); - } + plot.getPlaceholder().trigger("plotpan", [ plot, args ]); + }; function shutdown(plot, eventHolder) { eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick); diff --git a/Web Server/flot/jquery.flot.navigate.min.js b/Web Server/flot/jquery.flot.navigate.min.js new file mode 100644 index 0000000..a69a939 --- /dev/null +++ b/Web Server/flot/jquery.flot.navigate.min.js @@ -0,0 +1 @@ +(function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY)max){var tmp=min;min=max;max=tmp}if(pr){if(pr[0]!=null&&minpr[1]){max=pr[1]}}var range=max-min;if(zr&&(zr[0]!=null&&rangezr[1]))return;opts.min=min;opts.max=max});plot.setupGrid();plot.draw();if(!args.preventEvent)plot.getPlaceholder().trigger("plotzoom",[plot,args])};plot.pan=function(args){var delta={x:+args.left,y:+args.top};if(isNaN(delta.x))delta.x=0;if(isNaN(delta.y))delta.y=0;$.each(plot.getAxes(),function(_,axis){var opts=axis.options,min,max,d=delta[axis.direction];min=axis.c2p(axis.p2c(axis.min)+d),max=axis.c2p(axis.p2c(axis.max)+d);var pr=opts.panRange;if(pr===false)return;if(pr){if(pr[0]!=null&&pr[0]>min){d=pr[0]-min;min+=d;max+=d}if(pr[1]!=null&&pr[1] 1) { + options.series.pie.tilt = 1; + } else if (options.series.pie.tilt < 0) { + options.series.pie.tilt = 0; + } + } + }); + + plot.hooks.bindEvents.push(function(plot, eventHolder) { + var options = plot.getOptions(); + if (options.series.pie.show) { + if (options.grid.hoverable) { + eventHolder.unbind("mousemove").mousemove(onMouseMove); + } + if (options.grid.clickable) { + eventHolder.unbind("click").click(onClick); + } + } + }); + + plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) { + var options = plot.getOptions(); + if (options.series.pie.show) { + processDatapoints(plot, series, data, datapoints); + } + }); + + plot.hooks.drawOverlay.push(function(plot, octx) { + var options = plot.getOptions(); + if (options.series.pie.show) { + drawOverlay(plot, octx); + } + }); + + plot.hooks.draw.push(function(plot, newCtx) { + var options = plot.getOptions(); + if (options.series.pie.show) { + draw(plot, newCtx); + } + }); + + function processDatapoints(plot, series, datapoints) { + if (!processed) { + processed = true; + canvas = plot.getCanvas(); + target = $(canvas).parent(); + options = plot.getOptions(); + plot.setData(combine(plot.getData())); + } + } + + function combine(data) { + + var total = 0, + combined = 0, + numCombined = 0, + color = options.series.pie.combine.color, + newdata = []; + + // Fix up the raw data from Flot, ensuring the data is numeric + + for (var i = 0; i < data.length; ++i) { + + var value = data[i].data; + + // If the data is an array, we'll assume that it's a standard + // Flot x-y pair, and are concerned only with the second value. + + // Note how we use the original array, rather than creating a + // new one; this is more efficient and preserves any extra data + // that the user may have stored in higher indexes. + + if ($.isArray(value) && value.length == 1) { + value = value[0]; + } + + if ($.isArray(value)) { + // Equivalent to $.isNumeric() but compatible with jQuery < 1.7 + if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { + value[1] = +value[1]; + } else { + value[1] = 0; + } + } else if (!isNaN(parseFloat(value)) && isFinite(value)) { + value = [1, +value]; + } else { + value = [1, 0]; + } + + data[i].data = [value]; + } + + // Sum up all the slices, so we can calculate percentages for each + + for (var i = 0; i < data.length; ++i) { + total += data[i].data[0][1]; + } + + // Count the number of slices with percentages below the combine + // threshold; if it turns out to be just one, we won't combine. + + for (var i = 0; i < data.length; ++i) { + var value = data[i].data[0][1]; + if (value / total <= options.series.pie.combine.threshold) { + combined += value; + numCombined++; + if (!color) { + color = data[i].color; + } + } + } + + for (var i = 0; i < data.length; ++i) { + var value = data[i].data[0][1]; + if (numCombined < 2 || value / total > options.series.pie.combine.threshold) { + newdata.push({ + data: [[1, value]], + color: data[i].color, + label: data[i].label, + angle: value * Math.PI * 2 / total, + percent: value / (total / 100) + }); + } + } + + if (numCombined > 1) { + newdata.push({ + data: [[1, combined]], + color: color, + label: options.series.pie.combine.label, + angle: combined * Math.PI * 2 / total, + percent: combined / (total / 100) + }); + } + + return newdata; + } + + function draw(plot, newCtx) { + + if (!target) { + return; // if no series were passed + } + + var canvasWidth = plot.getPlaceholder().width(), + canvasHeight = plot.getPlaceholder().height(), + legendWidth = target.children().filter(".legend").children().width() || 0; + + ctx = newCtx; + + // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE! + + // When combining smaller slices into an 'other' slice, we need to + // add a new series. Since Flot gives plugins no way to modify the + // list of series, the pie plugin uses a hack where the first call + // to processDatapoints results in a call to setData with the new + // list of series, then subsequent processDatapoints do nothing. + + // The plugin-global 'processed' flag is used to control this hack; + // it starts out false, and is set to true after the first call to + // processDatapoints. + + // Unfortunately this turns future setData calls into no-ops; they + // call processDatapoints, the flag is true, and nothing happens. + + // To fix this we'll set the flag back to false here in draw, when + // all series have been processed, so the next sequence of calls to + // processDatapoints once again starts out with a slice-combine. + // This is really a hack; in 0.9 we need to give plugins a proper + // way to modify series before any processing begins. + + processed = false; + + // calculate maximum radius and center point + + maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; + centerTop = canvasHeight / 2 + options.series.pie.offset.top; + centerLeft = canvasWidth / 2; + + if (options.series.pie.offset.left == "auto") { + if (options.legend.position.match("w")) { + centerLeft += legendWidth / 2; + } else { + centerLeft -= legendWidth / 2; + } + if (centerLeft < maxRadius) { + centerLeft = maxRadius; + } else if (centerLeft > canvasWidth - maxRadius) { + centerLeft = canvasWidth - maxRadius; + } + } else { + centerLeft += options.series.pie.offset.left; + } + + var slices = plot.getData(), + attempts = 0; + + // Keep shrinking the pie's radius until drawPie returns true, + // indicating that all the labels fit, or we try too many times. + + do { + if (attempts > 0) { + maxRadius *= REDRAW_SHRINK; + } + attempts += 1; + clear(); + if (options.series.pie.tilt <= 0.8) { + drawShadow(); + } + } while (!drawPie() && attempts < REDRAW_ATTEMPTS) + + if (attempts >= REDRAW_ATTEMPTS) { + clear(); + target.prepend("
Could not draw pie with labels contained inside canvas
"); + } + + if (plot.setSeries && plot.insertLegend) { + plot.setSeries(slices); + plot.insertLegend(); + } + + // we're actually done at this point, just defining internal functions at this point + + function clear() { + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + target.children().filter(".pieLabel, .pieLabelBackground").remove(); + } + + function drawShadow() { + + var shadowLeft = options.series.pie.shadow.left; + var shadowTop = options.series.pie.shadow.top; + var edge = 10; + var alpha = options.series.pie.shadow.alpha; + var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; + + if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) { + return; // shadow would be outside canvas, so don't draw it + } + + ctx.save(); + ctx.translate(shadowLeft,shadowTop); + ctx.globalAlpha = alpha; + ctx.fillStyle = "#000"; + + // center and rotate to starting position + + ctx.translate(centerLeft,centerTop); + ctx.scale(1, options.series.pie.tilt); + + //radius -= edge; + + for (var i = 1; i <= edge; i++) { + ctx.beginPath(); + ctx.arc(0, 0, radius, 0, Math.PI * 2, false); + ctx.fill(); + radius -= i; + } + + ctx.restore(); + } + + function drawPie() { + + var startAngle = Math.PI * options.series.pie.startAngle; + var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; + + // center and rotate to starting position + + ctx.save(); + ctx.translate(centerLeft,centerTop); + ctx.scale(1, options.series.pie.tilt); + //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera + + // draw slices + + ctx.save(); + var currentAngle = startAngle; + for (var i = 0; i < slices.length; ++i) { + slices[i].startAngle = currentAngle; + drawSlice(slices[i].angle, slices[i].color, true); + } + ctx.restore(); + + // draw slice outlines + + if (options.series.pie.stroke.width > 0) { + ctx.save(); + ctx.lineWidth = options.series.pie.stroke.width; + currentAngle = startAngle; + for (var i = 0; i < slices.length; ++i) { + drawSlice(slices[i].angle, options.series.pie.stroke.color, false); + } + ctx.restore(); + } + + // draw donut hole + + drawDonutHole(ctx); + + ctx.restore(); + + // Draw the labels, returning true if they fit within the plot + + if (options.series.pie.label.show) { + return drawLabels(); + } else return true; + + function drawSlice(angle, color, fill) { + + if (angle <= 0 || isNaN(angle)) { + return; + } + + if (fill) { + ctx.fillStyle = color; + } else { + ctx.strokeStyle = color; + ctx.lineJoin = "round"; + } + + ctx.beginPath(); + if (Math.abs(angle - Math.PI * 2) > 0.000000001) { + ctx.moveTo(0, 0); // Center of the pie + } + + //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera + ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false); + ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false); + ctx.closePath(); + //ctx.rotate(angle); // This doesn't work properly in Opera + currentAngle += angle; + + if (fill) { + ctx.fill(); + } else { + ctx.stroke(); + } + } + + function drawLabels() { + + var currentAngle = startAngle; + var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius; + + for (var i = 0; i < slices.length; ++i) { + if (slices[i].percent >= options.series.pie.label.threshold * 100) { + if (!drawLabel(slices[i], currentAngle, i)) { + return false; + } + } + currentAngle += slices[i].angle; + } + + return true; + + function drawLabel(slice, startAngle, index) { + + if (slice.data[0][1] == 0) { + return true; + } + + // format label text + + var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; + + if (lf) { + text = lf(slice.label, slice); + } else { + text = slice.label; + } + + if (plf) { + text = plf(text, slice); + } + + var halfAngle = ((startAngle + slice.angle) + startAngle) / 2; + var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); + var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; + + var html = "" + text + ""; + target.append(html); + + var label = target.children("#pieLabel" + index); + var labelTop = (y - label.height() / 2); + var labelLeft = (x - label.width() / 2); + + label.css("top", labelTop); + label.css("left", labelLeft); + + // check to make sure that the label is not outside the canvas + + if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) { + return false; + } + + if (options.series.pie.label.background.opacity != 0) { + + // put in the transparent background separately to avoid blended labels and label boxes + + var c = options.series.pie.label.background.color; + + if (c == null) { + c = slice.color; + } + + var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;"; + $("
") + .css("opacity", options.series.pie.label.background.opacity) + .insertBefore(label); + } + + return true; + } // end individual label function + } // end drawLabels function + } // end drawPie function + } // end draw function + + // Placed here because it needs to be accessed from multiple locations + + function drawDonutHole(layer) { + if (options.series.pie.innerRadius > 0) { + + // subtract the center + + layer.save(); + var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; + layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color + layer.beginPath(); + layer.fillStyle = options.series.pie.stroke.color; + layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); + layer.fill(); + layer.closePath(); + layer.restore(); + + // add inner stroke + + layer.save(); + layer.beginPath(); + layer.strokeStyle = options.series.pie.stroke.color; + layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); + layer.stroke(); + layer.closePath(); + layer.restore(); + + // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. + } + } + + //-- Additional Interactive related functions -- + + function isPointInPoly(poly, pt) { + for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) + ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) + && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) + && (c = !c); + return c; + } + + function findNearbySlice(mouseX, mouseY) { + + var slices = plot.getData(), + options = plot.getOptions(), + radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius, + x, y; + + for (var i = 0; i < slices.length; ++i) { + + var s = slices[i]; + + if (s.pie.show) { + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(0, 0); // Center of the pie + //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. + ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false); + ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false); + ctx.closePath(); + x = mouseX - centerLeft; + y = mouseY - centerTop; + + if (ctx.isPointInPath) { + if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) { + ctx.restore(); + return { + datapoint: [s.percent, s.data], + dataIndex: 0, + series: s, + seriesIndex: i + }; + } + } else { + + // excanvas for IE doesn;t support isPointInPath, this is a workaround. + + var p1X = radius * Math.cos(s.startAngle), + p1Y = radius * Math.sin(s.startAngle), + p2X = radius * Math.cos(s.startAngle + s.angle / 4), + p2Y = radius * Math.sin(s.startAngle + s.angle / 4), + p3X = radius * Math.cos(s.startAngle + s.angle / 2), + p3Y = radius * Math.sin(s.startAngle + s.angle / 2), + p4X = radius * Math.cos(s.startAngle + s.angle / 1.5), + p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5), + p5X = radius * Math.cos(s.startAngle + s.angle), + p5Y = radius * Math.sin(s.startAngle + s.angle), + arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]], + arrPoint = [x, y]; + + // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? + + if (isPointInPoly(arrPoly, arrPoint)) { + ctx.restore(); + return { + datapoint: [s.percent, s.data], + dataIndex: 0, + series: s, + seriesIndex: i + }; + } + } + + ctx.restore(); + } + } + + return null; + } + + function onMouseMove(e) { + triggerClickHoverEvent("plothover", e); + } + + function onClick(e) { + triggerClickHoverEvent("plotclick", e); + } + + // trigger click or hover event (they send the same parameters so we share their code) + + function triggerClickHoverEvent(eventname, e) { + + var offset = plot.offset(); + var canvasX = parseInt(e.pageX - offset.left); + var canvasY = parseInt(e.pageY - offset.top); + var item = findNearbySlice(canvasX, canvasY); + + if (options.grid.autoHighlight) { + + // clear auto-highlights + + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.auto == eventname && !(item && h.series == item.series)) { + unhighlight(h.series); + } + } + } + + // highlight the slice + + if (item) { + highlight(item.series, eventname); + } + + // trigger any hover bind events + + var pos = { pageX: e.pageX, pageY: e.pageY }; + target.trigger(eventname, [pos, item]); + } + + function highlight(s, auto) { + //if (typeof s == "number") { + // s = series[s]; + //} + + var i = indexOfHighlight(s); + + if (i == -1) { + highlights.push({ series: s, auto: auto }); + plot.triggerRedrawOverlay(); + } else if (!auto) { + highlights[i].auto = false; + } + } + + function unhighlight(s) { + if (s == null) { + highlights = []; + plot.triggerRedrawOverlay(); + } + + //if (typeof s == "number") { + // s = series[s]; + //} + + var i = indexOfHighlight(s); + + if (i != -1) { + highlights.splice(i, 1); + plot.triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s) { + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.series == s) + return i; + } + return -1; + } + + function drawOverlay(plot, octx) { + + var options = plot.getOptions(); + + var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; + + octx.save(); + octx.translate(centerLeft, centerTop); + octx.scale(1, options.series.pie.tilt); + + for (var i = 0; i < highlights.length; ++i) { + drawHighlight(highlights[i].series); + } + + drawDonutHole(octx); + + octx.restore(); + + function drawHighlight(series) { + + if (series.angle <= 0 || isNaN(series.angle)) { + return; + } + + //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); + octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor + octx.beginPath(); + if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) { + octx.moveTo(0, 0); // Center of the pie + } + octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false); + octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false); + octx.closePath(); + octx.fill(); + } + } + } // end init (plugin body) + + // define pie specific options and their default values + + var options = { + series: { + pie: { + show: false, + radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) + innerRadius: 0, /* for donut */ + startAngle: 3/2, + tilt: 1, + shadow: { + left: 5, // shadow left offset + top: 15, // shadow top offset + alpha: 0.02 // shadow alpha + }, + offset: { + top: 0, + left: "auto" + }, + stroke: { + color: "#fff", + width: 1 + }, + label: { + show: "auto", + formatter: function(label, slice) { + return "
" + label + "
" + Math.round(slice.percent) + "%
"; + }, // formatter function + radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) + background: { + color: null, + opacity: 0 + }, + threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow) + }, + combine: { + threshold: -1, // percentage at which to combine little slices into one larger slice + color: null, // color to give the new slice (auto-generated if null) + label: "Other" // label to give the new slice + }, + highlight: { + //color: "#fff", // will add this functionality once parseColor is available + opacity: 0.5 + } + } + } + }; + + $.plot.plugins.push({ + init: init, + options: options, + name: "pie", + version: "1.1" + }); + +})(jQuery); diff --git a/Web Server/flot/jquery.flot.pie.min.js b/Web Server/flot/jquery.flot.pie.min.js new file mode 100644 index 0000000..88ffc9c --- /dev/null +++ b/Web Server/flot/jquery.flot.pie.min.js @@ -0,0 +1 @@ +(function($){var REDRAW_ATTEMPTS=10;var REDRAW_SHRINK=.95;function init(plot){var canvas=null,target=null,options=null,maxRadius=null,centerLeft=null,centerTop=null,processed=false,ctx=null;var highlights=[];plot.hooks.processOptions.push(function(plot,options){if(options.series.pie.show){options.grid.show=false;if(options.series.pie.label.show=="auto"){if(options.legend.show){options.series.pie.label.show=false}else{options.series.pie.label.show=true}}if(options.series.pie.radius=="auto"){if(options.series.pie.label.show){options.series.pie.radius=3/4}else{options.series.pie.radius=1}}if(options.series.pie.tilt>1){options.series.pie.tilt=1}else if(options.series.pie.tilt<0){options.series.pie.tilt=0}}});plot.hooks.bindEvents.push(function(plot,eventHolder){var options=plot.getOptions();if(options.series.pie.show){if(options.grid.hoverable){eventHolder.unbind("mousemove").mousemove(onMouseMove)}if(options.grid.clickable){eventHolder.unbind("click").click(onClick)}}});plot.hooks.processDatapoints.push(function(plot,series,data,datapoints){var options=plot.getOptions();if(options.series.pie.show){processDatapoints(plot,series,data,datapoints)}});plot.hooks.drawOverlay.push(function(plot,octx){var options=plot.getOptions();if(options.series.pie.show){drawOverlay(plot,octx)}});plot.hooks.draw.push(function(plot,newCtx){var options=plot.getOptions();if(options.series.pie.show){draw(plot,newCtx)}});function processDatapoints(plot,series,datapoints){if(!processed){processed=true;canvas=plot.getCanvas();target=$(canvas).parent();options=plot.getOptions();plot.setData(combine(plot.getData()))}}function combine(data){var total=0,combined=0,numCombined=0,color=options.series.pie.combine.color,newdata=[];for(var i=0;ioptions.series.pie.combine.threshold){newdata.push({data:[[1,value]],color:data[i].color,label:data[i].label,angle:value*Math.PI*2/total,percent:value/(total/100)})}}if(numCombined>1){newdata.push({data:[[1,combined]],color:color,label:options.series.pie.combine.label,angle:combined*Math.PI*2/total,percent:combined/(total/100)})}return newdata}function draw(plot,newCtx){if(!target){return}var canvasWidth=plot.getPlaceholder().width(),canvasHeight=plot.getPlaceholder().height(),legendWidth=target.children().filter(".legend").children().width()||0;ctx=newCtx;processed=false;maxRadius=Math.min(canvasWidth,canvasHeight/options.series.pie.tilt)/2;centerTop=canvasHeight/2+options.series.pie.offset.top;centerLeft=canvasWidth/2;if(options.series.pie.offset.left=="auto"){if(options.legend.position.match("w")){centerLeft+=legendWidth/2}else{centerLeft-=legendWidth/2}if(centerLeftcanvasWidth-maxRadius){centerLeft=canvasWidth-maxRadius}}else{centerLeft+=options.series.pie.offset.left}var slices=plot.getData(),attempts=0;do{if(attempts>0){maxRadius*=REDRAW_SHRINK}attempts+=1;clear();if(options.series.pie.tilt<=.8){drawShadow()}}while(!drawPie()&&attempts=REDRAW_ATTEMPTS){clear();target.prepend("
Could not draw pie with labels contained inside canvas
")}if(plot.setSeries&&plot.insertLegend){plot.setSeries(slices);plot.insertLegend()}function clear(){ctx.clearRect(0,0,canvasWidth,canvasHeight);target.children().filter(".pieLabel, .pieLabelBackground").remove()}function drawShadow(){var shadowLeft=options.series.pie.shadow.left;var shadowTop=options.series.pie.shadow.top;var edge=10;var alpha=options.series.pie.shadow.alpha;var radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius;if(radius>=canvasWidth/2-shadowLeft||radius*options.series.pie.tilt>=canvasHeight/2-shadowTop||radius<=edge){return}ctx.save();ctx.translate(shadowLeft,shadowTop);ctx.globalAlpha=alpha;ctx.fillStyle="#000";ctx.translate(centerLeft,centerTop);ctx.scale(1,options.series.pie.tilt);for(var i=1;i<=edge;i++){ctx.beginPath();ctx.arc(0,0,radius,0,Math.PI*2,false);ctx.fill();radius-=i}ctx.restore()}function drawPie(){var startAngle=Math.PI*options.series.pie.startAngle;var radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius;ctx.save();ctx.translate(centerLeft,centerTop);ctx.scale(1,options.series.pie.tilt);ctx.save();var currentAngle=startAngle;for(var i=0;i0){ctx.save();ctx.lineWidth=options.series.pie.stroke.width;currentAngle=startAngle;for(var i=0;i1e-9){ctx.moveTo(0,0)}ctx.arc(0,0,radius,currentAngle,currentAngle+angle/2,false);ctx.arc(0,0,radius,currentAngle+angle/2,currentAngle+angle,false);ctx.closePath();currentAngle+=angle;if(fill){ctx.fill()}else{ctx.stroke()}}function drawLabels(){var currentAngle=startAngle;var radius=options.series.pie.label.radius>1?options.series.pie.label.radius:maxRadius*options.series.pie.label.radius;for(var i=0;i=options.series.pie.label.threshold*100){if(!drawLabel(slices[i],currentAngle,i)){return false}}currentAngle+=slices[i].angle}return true;function drawLabel(slice,startAngle,index){if(slice.data[0][1]==0){return true}var lf=options.legend.labelFormatter,text,plf=options.series.pie.label.formatter;if(lf){text=lf(slice.label,slice)}else{text=slice.label}if(plf){text=plf(text,slice)}var halfAngle=(startAngle+slice.angle+startAngle)/2;var x=centerLeft+Math.round(Math.cos(halfAngle)*radius);var y=centerTop+Math.round(Math.sin(halfAngle)*radius)*options.series.pie.tilt;var html=""+text+"";target.append(html);var label=target.children("#pieLabel"+index);var labelTop=y-label.height()/2;var labelLeft=x-label.width()/2;label.css("top",labelTop);label.css("left",labelLeft);if(0-labelTop>0||0-labelLeft>0||canvasHeight-(labelTop+label.height())<0||canvasWidth-(labelLeft+label.width())<0){return false}if(options.series.pie.label.background.opacity!=0){var c=options.series.pie.label.background.color;if(c==null){c=slice.color}var pos="top:"+labelTop+"px;left:"+labelLeft+"px;";$("
").css("opacity",options.series.pie.label.background.opacity).insertBefore(label)}return true}}}}function drawDonutHole(layer){if(options.series.pie.innerRadius>0){layer.save();var innerRadius=options.series.pie.innerRadius>1?options.series.pie.innerRadius:maxRadius*options.series.pie.innerRadius;layer.globalCompositeOperation="destination-out";layer.beginPath();layer.fillStyle=options.series.pie.stroke.color;layer.arc(0,0,innerRadius,0,Math.PI*2,false);layer.fill();layer.closePath();layer.restore();layer.save();layer.beginPath();layer.strokeStyle=options.series.pie.stroke.color;layer.arc(0,0,innerRadius,0,Math.PI*2,false);layer.stroke();layer.closePath();layer.restore()}}function isPointInPoly(poly,pt){for(var c=false,i=-1,l=poly.length,j=l-1;++i1?options.series.pie.radius:maxRadius*options.series.pie.radius,x,y;for(var i=0;i1?options.series.pie.radius:maxRadius*options.series.pie.radius;octx.save();octx.translate(centerLeft,centerTop);octx.scale(1,options.series.pie.tilt);for(var i=0;i1e-9){octx.moveTo(0,0)}octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle/2,false);octx.arc(0,0,radius,series.startAngle+series.angle/2,series.startAngle+series.angle,false);octx.closePath();octx.fill()}}}var options={series:{pie:{show:false,radius:"auto",innerRadius:0,startAngle:3/2,tilt:1,shadow:{left:5,top:15,alpha:.02},offset:{top:0,left:"auto"},stroke:{color:"#fff",width:1},label:{show:"auto",formatter:function(label,slice){return"
"+label+"
"+Math.round(slice.percent)+"%
"},radius:1,background:{color:null,opacity:0},threshold:0},combine:{threshold:-1,color:null,label:"Other"},highlight:{opacity:.5}}}};$.plot.plugins.push({init:init,options:options,name:"pie",version:"1.1"})})(jQuery); \ No newline at end of file diff --git a/Web Server/flot/jquery.flot.resize.js b/Web Server/flot/jquery.flot.resize.js new file mode 100644 index 0000000..44e04f8 --- /dev/null +++ b/Web Server/flot/jquery.flot.resize.js @@ -0,0 +1,60 @@ +/* Flot plugin for automatically redrawing plots as the placeholder resizes. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +It works by listening for changes on the placeholder div (through the jQuery +resize event plugin) - if the size changes, it will redraw the plot. + +There are no options. If you need to disable the plugin for some plots, you +can just fix the size of their placeholders. + +*/ + +/* Inline dependency: + * jQuery resize event - v1.1 - 3/14/2010 + * http://benalman.com/projects/jquery-resize-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +(function($,t,n){function p(){for(var n=r.length-1;n>=0;n--){var o=$(r[n]);if(o[0]==t||o.is(":visible")){var h=o.width(),d=o.height(),v=o.data(a);!v||h===v.w&&d===v.h?i[f]=i[l]:(i[f]=i[c],o.trigger(u,[v.w=h,v.h=d]))}else v=o.data(a),v.w=0,v.h=0}s!==null&&(s=t.requestAnimationFrame(p))}var r=[],i=$.resize=$.extend($.resize,{}),s,o="setTimeout",u="resize",a=u+"-special-event",f="delay",l="pendingDelay",c="activeDelay",h="throttleWindow";i[l]=250,i[c]=20,i[f]=i[l],i[h]=!0,$.event.special[u]={setup:function(){if(!i[h]&&this[o])return!1;var t=$(this);r.push(this),t.data(a,{w:t.width(),h:t.height()}),r.length===1&&(s=n,p())},teardown:function(){if(!i[h]&&this[o])return!1;var t=$(this);for(var n=r.length-1;n>=0;n--)if(r[n]==this){r.splice(n,1);break}t.removeData(a),r.length||(cancelAnimationFrame(s),s=null)},add:function(t){function s(t,i,s){var o=$(this),u=o.data(a);u.w=i!==n?i:o.width(),u.h=s!==n?s:o.height(),r.apply(this,arguments)}if(!i[h]&&this[o])return!1;var r;if($.isFunction(t))return r=t,s;r=t.handler,t.handler=s}},t.requestAnimationFrame||(t.requestAnimationFrame=function(){return t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||t.msRequestAnimationFrame||function(e,n){return t.setTimeout(e,i[f])}}()),t.cancelAnimationFrame||(t.cancelAnimationFrame=function(){return t.webkitCancelRequestAnimationFrame||t.mozCancelRequestAnimationFrame||t.oCancelRequestAnimationFrame||t.msCancelRequestAnimationFrame||clearTimeout}())})(jQuery,this); + +(function ($) { + var options = { }; // no options + + function init(plot) { + function onResize() { + var placeholder = plot.getPlaceholder(); + + // somebody might have hidden us and we can't plot + // when we don't have the dimensions + if (placeholder.width() == 0 || placeholder.height() == 0) + return; + + plot.resize(); + plot.setupGrid(); + plot.draw(); + } + + function bindEvents(plot, eventHolder) { + plot.getPlaceholder().resize(onResize); + } + + function shutdown(plot, eventHolder) { + plot.getPlaceholder().unbind("resize", onResize); + } + + plot.hooks.bindEvents.push(bindEvents); + plot.hooks.shutdown.push(shutdown); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'resize', + version: '1.0' + }); +})(jQuery); diff --git a/Web Server/flot/jquery.flot.resize.min.js b/Web Server/flot/jquery.flot.resize.min.js new file mode 100644 index 0000000..2983842 --- /dev/null +++ b/Web Server/flot/jquery.flot.resize.min.js @@ -0,0 +1 @@ +(function($,t,n){function p(){for(var n=r.length-1;n>=0;n--){var o=$(r[n]);if(o[0]==t||o.is(":visible")){var h=o.width(),d=o.height(),v=o.data(a);!v||h===v.w&&d===v.h?i[f]=i[l]:(i[f]=i[c],o.trigger(u,[v.w=h,v.h=d]))}else v=o.data(a),v.w=0,v.h=0}s!==null&&(s=t.requestAnimationFrame(p))}var r=[],i=$.resize=$.extend($.resize,{}),s,o="setTimeout",u="resize",a=u+"-special-event",f="delay",l="pendingDelay",c="activeDelay",h="throttleWindow";i[l]=250,i[c]=20,i[f]=i[l],i[h]=!0,$.event.special[u]={setup:function(){if(!i[h]&&this[o])return!1;var t=$(this);r.push(this),t.data(a,{w:t.width(),h:t.height()}),r.length===1&&(s=n,p())},teardown:function(){if(!i[h]&&this[o])return!1;var t=$(this);for(var n=r.length-1;n>=0;n--)if(r[n]==this){r.splice(n,1);break}t.removeData(a),r.length||(cancelAnimationFrame(s),s=null)},add:function(t){function s(t,i,s){var o=$(this),u=o.data(a);u.w=i!==n?i:o.width(),u.h=s!==n?s:o.height(),r.apply(this,arguments)}if(!i[h]&&this[o])return!1;var r;if($.isFunction(t))return r=t,s;r=t.handler,t.handler=s}},t.requestAnimationFrame||(t.requestAnimationFrame=function(){return t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||t.msRequestAnimationFrame||function(e,n){return t.setTimeout(e,i[f])}}()),t.cancelAnimationFrame||(t.cancelAnimationFrame=function(){return t.webkitCancelRequestAnimationFrame||t.mozCancelRequestAnimationFrame||t.oCancelRequestAnimationFrame||t.msCancelRequestAnimationFrame||clearTimeout}())})(jQuery,this);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)return;plot.resize();plot.setupGrid();plot.draw()}function bindEvents(plot,eventHolder){plot.getPlaceholder().resize(onResize)}function shutdown(plot,eventHolder){plot.getPlaceholder().unbind("resize",onResize)}plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown)}$.plot.plugins.push({init:init,options:options,name:"resize",version:"1.0"})})(jQuery); \ No newline at end of file diff --git a/web/flot/jquery.flot.selection.js b/Web Server/flot/jquery.flot.selection.js similarity index 75% rename from web/flot/jquery.flot.selection.js rename to Web Server/flot/jquery.flot.selection.js index 7f7b326..f8fa668 100644 --- a/web/flot/jquery.flot.selection.js +++ b/Web Server/flot/jquery.flot.selection.js @@ -1,68 +1,80 @@ -/* -Flot plugin for selecting regions. - -The plugin defines the following options: - - selection: { - mode: null or "x" or "y" or "xy", - color: color - } - -Selection support is enabled by setting the mode to one of "x", "y" or -"xy". In "x" mode, the user will only be able to specify the x range, -similarly for "y" mode. For "xy", the selection becomes a rectangle -where both ranges can be specified. "color" is color of the selection -(if you need to change the color later on, you can get to it with -plot.getOptions().selection.color). - -When selection support is enabled, a "plotselected" event will be -emitted on the DOM element you passed into the plot function. The -event handler gets a parameter with the ranges selected on the axes, -like this: - - placeholder.bind("plotselected", function(event, ranges) { - alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) - // similar for yaxis - with multiple axes, the extra ones are in - // x2axis, x3axis, ... - }); - -The "plotselected" event is only fired when the user has finished -making the selection. A "plotselecting" event is fired during the -process with the same parameters as the "plotselected" event, in case -you want to know what's happening while it's happening, - -A "plotunselected" event with no arguments is emitted when the user -clicks the mouse to remove the selection. +/* Flot plugin for selecting regions of a plot. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +The plugin supports these options: + +selection: { + mode: null or "x" or "y" or "xy", + color: color, + shape: "round" or "miter" or "bevel", + minSize: number of pixels +} + +Selection support is enabled by setting the mode to one of "x", "y" or "xy". +In "x" mode, the user will only be able to specify the x range, similarly for +"y" mode. For "xy", the selection becomes a rectangle where both ranges can be +specified. "color" is color of the selection (if you need to change the color +later on, you can get to it with plot.getOptions().selection.color). "shape" +is the shape of the corners of the selection. + +"minSize" is the minimum size a selection can be in pixels. This value can +be customized to determine the smallest size a selection can be and still +have the selection rectangle be displayed. When customizing this value, the +fact that it refers to pixels, not axis units must be taken into account. +Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 +minute, setting "minSize" to 1 will not make the minimum selection size 1 +minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent +"plotunselected" events from being fired when the user clicks the mouse without +dragging. + +When selection support is enabled, a "plotselected" event will be emitted on +the DOM element you passed into the plot function. The event handler gets a +parameter with the ranges selected on the axes, like this: + + placeholder.bind( "plotselected", function( event, ranges ) { + alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) + // similar for yaxis - with multiple axes, the extra ones are in + // x2axis, x3axis, ... + }); + +The "plotselected" event is only fired when the user has finished making the +selection. A "plotselecting" event is fired during the process with the same +parameters as the "plotselected" event, in case you want to know what's +happening while it's happening, + +A "plotunselected" event with no arguments is emitted when the user clicks the +mouse to remove the selection. As stated above, setting "minSize" to 0 will +destroy this behavior. The plugin allso adds the following methods to the plot object: -- setSelection(ranges, preventEvent) +- setSelection( ranges, preventEvent ) - Set the selection rectangle. The passed in ranges is on the same - form as returned in the "plotselected" event. If the selection mode - is "x", you should put in either an xaxis range, if the mode is "y" - you need to put in an yaxis range and both xaxis and yaxis if the - selection mode is "xy", like this: + Set the selection rectangle. The passed in ranges is on the same form as + returned in the "plotselected" event. If the selection mode is "x", you + should put in either an xaxis range, if the mode is "y" you need to put in + an yaxis range and both xaxis and yaxis if the selection mode is "xy", like + this: - setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); + setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); - setSelection will trigger the "plotselected" event when called. If - you don't want that to happen, e.g. if you're inside a - "plotselected" handler, pass true as the second parameter. If you - are using multiple axes, you can specify the ranges on any of those, - e.g. as x2axis/x3axis/... instead of xaxis, the plugin picks the - first one it sees. - -- clearSelection(preventEvent) + setSelection will trigger the "plotselected" event when called. If you don't + want that to happen, e.g. if you're inside a "plotselected" handler, pass + true as the second parameter. If you are using multiple axes, you can + specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of + xaxis, the plugin picks the first one it sees. + +- clearSelection( preventEvent ) Clear the selection rectangle. Pass in true to avoid getting a "plotunselected" event. - getSelection() - Returns the current selection in the same format as the - "plotselected" event. If there's currently no selection, the - function returns null. + Returns the current selection in the same format as the "plotselected" + event. If there's currently no selection, the function returns null. */ @@ -146,6 +158,8 @@ The plugin allso adds the following methods to the plot object: function getSelection() { if (!selectionIsSane()) return null; + + if (!selection.show) return null; var r = {}, c1 = selection.first, c2 = selection.second; $.each(plot.getAxes(), function (name, axis) { @@ -274,7 +288,7 @@ The plugin allso adds the following methods to the plot object: } function selectionIsSane() { - var minSize = 5; + var minSize = plot.getOptions().selection.minSize; return Math.abs(selection.second.x - selection.first.x) >= minSize && Math.abs(selection.second.y - selection.first.y) >= minSize; } @@ -305,13 +319,13 @@ The plugin allso adds the following methods to the plot object: ctx.strokeStyle = c.scale('a', 0.8).toString(); ctx.lineWidth = 1; - ctx.lineJoin = "round"; + ctx.lineJoin = o.selection.shape; ctx.fillStyle = c.scale('a', 0.4).toString(); - var x = Math.min(selection.first.x, selection.second.x), - y = Math.min(selection.first.y, selection.second.y), - w = Math.abs(selection.second.x - selection.first.x), - h = Math.abs(selection.second.y - selection.first.y); + var x = Math.min(selection.first.x, selection.second.x) + 0.5, + y = Math.min(selection.first.y, selection.second.y) + 0.5, + w = Math.abs(selection.second.x - selection.first.x) - 1, + h = Math.abs(selection.second.y - selection.first.y) - 1; ctx.fillRect(x, y, w, h); ctx.strokeRect(x, y, w, h); @@ -335,7 +349,9 @@ The plugin allso adds the following methods to the plot object: options: { selection: { mode: null, // one of null, "x", "y" or "xy" - color: "#e8cfac" + color: "#e8cfac", + shape: "round", // one of "round", "miter", or "bevel" + minSize: 5 // minimum number of pixels } }, name: 'selection', diff --git a/Web Server/flot/jquery.flot.selection.min.js b/Web Server/flot/jquery.flot.selection.min.js new file mode 100644 index 0000000..6e31978 --- /dev/null +++ b/Web Server/flot/jquery.flot.selection.min.js @@ -0,0 +1 @@ +(function($){function init(plot){var selection={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false};var savedhandlers={};var mouseUpHandler=null;function onMouseMove(e){if(selection.active){updateSelection(e);plot.getPlaceholder().trigger("plotselecting",[getSelection()])}}function onMouseDown(e){if(e.which!=1)return;document.body.focus();if(document.onselectstart!==undefined&&savedhandlers.onselectstart==null){savedhandlers.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&savedhandlers.ondrag==null){savedhandlers.ondrag=document.ondrag;document.ondrag=function(){return false}}setSelectionPos(selection.first,e);selection.active=true;mouseUpHandler=function(e){onMouseUp(e)};$(document).one("mouseup",mouseUpHandler)}function onMouseUp(e){mouseUpHandler=null;if(document.onselectstart!==undefined)document.onselectstart=savedhandlers.onselectstart;if(document.ondrag!==undefined)document.ondrag=savedhandlers.ondrag;selection.active=false;updateSelection(e);if(selectionIsSane())triggerSelectedEvent();else{plot.getPlaceholder().trigger("plotunselected",[]);plot.getPlaceholder().trigger("plotselecting",[null])}return false}function getSelection(){if(!selectionIsSane())return null;if(!selection.show)return null;var r={},c1=selection.first,c2=selection.second;$.each(plot.getAxes(),function(name,axis){if(axis.used){var p1=axis.c2p(c1[axis.direction]),p2=axis.c2p(c2[axis.direction]);r[name]={from:Math.min(p1,p2),to:Math.max(p1,p2)}}});return r}function triggerSelectedEvent(){var r=getSelection();plot.getPlaceholder().trigger("plotselected",[r]);if(r.xaxis&&r.yaxis)plot.getPlaceholder().trigger("selected",[{x1:r.xaxis.from,y1:r.yaxis.from,x2:r.xaxis.to,y2:r.yaxis.to}])}function clamp(min,value,max){return valuemax?max:value}function setSelectionPos(pos,e){var o=plot.getOptions();var offset=plot.getPlaceholder().offset();var plotOffset=plot.getPlotOffset();pos.x=clamp(0,e.pageX-offset.left-plotOffset.left,plot.width());pos.y=clamp(0,e.pageY-offset.top-plotOffset.top,plot.height());if(o.selection.mode=="y")pos.x=pos==selection.first?0:plot.width();if(o.selection.mode=="x")pos.y=pos==selection.first?0:plot.height()}function updateSelection(pos){if(pos.pageX==null)return;setSelectionPos(selection.second,pos);if(selectionIsSane()){selection.show=true;plot.triggerRedrawOverlay()}else clearSelection(true)}function clearSelection(preventEvent){if(selection.show){selection.show=false;plot.triggerRedrawOverlay();if(!preventEvent)plot.getPlaceholder().trigger("plotunselected",[])}}function extractRange(ranges,coord){var axis,from,to,key,axes=plot.getAxes();for(var k in axes){axis=axes[k];if(axis.direction==coord){key=coord+axis.n+"axis";if(!ranges[key]&&axis.n==1)key=coord+"axis";if(ranges[key]){from=ranges[key].from;to=ranges[key].to;break}}}if(!ranges[key]){axis=coord=="x"?plot.getXAxes()[0]:plot.getYAxes()[0];from=ranges[coord+"1"];to=ranges[coord+"2"]}if(from!=null&&to!=null&&from>to){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function setSelection(ranges,preventEvent){var axis,range,o=plot.getOptions();if(o.selection.mode=="y"){selection.first.x=0;selection.second.x=plot.width()}else{range=extractRange(ranges,"x");selection.first.x=range.axis.p2c(range.from);selection.second.x=range.axis.p2c(range.to)}if(o.selection.mode=="x"){selection.first.y=0;selection.second.y=plot.height()}else{range=extractRange(ranges,"y");selection.first.y=range.axis.p2c(range.from);selection.second.y=range.axis.p2c(range.to)}selection.show=true;plot.triggerRedrawOverlay();if(!preventEvent&&selectionIsSane())triggerSelectedEvent()}function selectionIsSane(){var minSize=plot.getOptions().selection.minSize;return Math.abs(selection.second.x-selection.first.x)>=minSize&&Math.abs(selection.second.y-selection.first.y)>=minSize}plot.clearSelection=clearSelection;plot.setSelection=setSelection;plot.getSelection=getSelection;plot.hooks.bindEvents.push(function(plot,eventHolder){var o=plot.getOptions();if(o.selection.mode!=null){eventHolder.mousemove(onMouseMove);eventHolder.mousedown(onMouseDown)}});plot.hooks.drawOverlay.push(function(plot,ctx){if(selection.show&&selectionIsSane()){var plotOffset=plot.getPlotOffset();var o=plot.getOptions();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var c=$.color.parse(o.selection.color);ctx.strokeStyle=c.scale("a",.8).toString();ctx.lineWidth=1;ctx.lineJoin=o.selection.shape;ctx.fillStyle=c.scale("a",.4).toString();var x=Math.min(selection.first.x,selection.second.x)+.5,y=Math.min(selection.first.y,selection.second.y)+.5,w=Math.abs(selection.second.x-selection.first.x)-1,h=Math.abs(selection.second.y-selection.first.y)-1;ctx.fillRect(x,y,w,h);ctx.strokeRect(x,y,w,h);ctx.restore()}});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mousedown",onMouseDown);if(mouseUpHandler)$(document).unbind("mouseup",mouseUpHandler)})}$.plot.plugins.push({init:init,options:{selection:{mode:null,color:"#e8cfac",shape:"round",minSize:5}},name:"selection",version:"1.1"})})(jQuery); \ No newline at end of file diff --git a/web/flot/jquery.flot.stack.js b/Web Server/flot/jquery.flot.stack.js similarity index 82% rename from web/flot/jquery.flot.stack.js rename to Web Server/flot/jquery.flot.stack.js index a31d5dc..c01de67 100644 --- a/web/flot/jquery.flot.stack.js +++ b/Web Server/flot/jquery.flot.stack.js @@ -1,34 +1,38 @@ -/* -Flot plugin for stacking data sets, i.e. putting them on top of each -other, for accumulative graphs. - -The plugin assumes the data is sorted on x (or y if stacking -horizontally). For line charts, it is assumed that if a line has an -undefined gap (from a null point), then the line above it should have -the same gap - insert zeros instead of "null" if you want another -behaviour. This also holds for the start and end of the chart. Note -that stacking a mix of positive and negative values in most instances -doesn't make sense (so it looks weird). - -Two or more series are stacked when their "stack" attribute is set to -the same key (which can be any number or string or just "true"). To -specify the default stack, you can set - - series: { - stack: null or true or key (number/string) - } - -or specify it for a specific series - - $.plot($("#placeholder"), [{ data: [ ... ], stack: true }]) - -The stacking order is determined by the order of the data series in -the array (later series end up on top of the previous). - -Internally, the plugin modifies the datapoints in each series, adding -an offset to the y value. For line series, extra data points are -inserted through interpolation. If there's a second y value, it's also -adjusted (e.g for bar charts or filled areas). +/* Flot plugin for stacking data sets rather than overlyaing them. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +The plugin assumes the data is sorted on x (or y if stacking horizontally). +For line charts, it is assumed that if a line has an undefined gap (from a +null point), then the line above it should have the same gap - insert zeros +instead of "null" if you want another behaviour. This also holds for the start +and end of the chart. Note that stacking a mix of positive and negative values +in most instances doesn't make sense (so it looks weird). + +Two or more series are stacked when their "stack" attribute is set to the same +key (which can be any number or string or just "true"). To specify the default +stack, you can set the stack option like this: + + series: { + stack: null/false, true, or a key (number/string) + } + +You can also specify it for a single series, like this: + + $.plot( $("#placeholder"), [{ + data: [ ... ], + stack: true + }]) + +The stacking order is determined by the order of the data series in the array +(later series end up on top of the previous). + +Internally, the plugin modifies the datapoints in each series, adding an +offset to the y value. For line series, extra data points are inserted through +interpolation. If there's a second y value, it's also adjusted (e.g for bar +charts or filled areas). + */ (function ($) { @@ -38,7 +42,7 @@ adjusted (e.g for bar charts or filled areas). function init(plot) { function findMatchingSeries(s, allseries) { - var res = null + var res = null; for (var i = 0; i < allseries.length; ++i) { if (s == allseries[i]) break; @@ -51,7 +55,7 @@ adjusted (e.g for bar charts or filled areas). } function stackData(plot, s, datapoints) { - if (s.stack == null) + if (s.stack == null || s.stack === false) return; var other = findMatchingSeries(s, plot.getData()); @@ -71,7 +75,7 @@ adjusted (e.g for bar charts or filled areas). fromgap = true, keyOffset = horizontal ? 1 : 0, accumulateOffset = horizontal ? 0 : 1, - i = 0, j = 0, l; + i = 0, j = 0, l, m; while (true) { if (i >= points.length) diff --git a/Web Server/flot/jquery.flot.stack.min.js b/Web Server/flot/jquery.flot.stack.min.js new file mode 100644 index 0000000..57785eb --- /dev/null +++ b/Web Server/flot/jquery.flot.stack.min.js @@ -0,0 +1 @@ +(function($){var options={series:{stack:null}};function init(plot){function findMatchingSeries(s,allseries){var res=null;for(var i=0;i2&&(horizontal?datapoints.format[2].x:datapoints.format[2].y),withsteps=withlines&&s.lines.steps,fromgap=true,keyOffset=horizontal?1:0,accumulateOffset=horizontal?0:1,i=0,j=0,l,m;while(true){if(i>=points.length)break;l=newpoints.length;if(points[i]==null){for(m=0;m=otherpoints.length){if(!withlines){for(m=0;mqx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+accumulateOffset]-py)*(qx-px)/(points[i-ps+keyOffset]-px);newpoints.push(qx);newpoints.push(intery+qy);for(m=2;m0&&otherpoints[j-otherps]!=null)bottom=qy+(otherpoints[j-otherps+accumulateOffset]-qy)*(px-qx)/(otherpoints[j-otherps+keyOffset]-qx);newpoints[l+accumulateOffset]+=bottom;i+=ps}fromgap=false;if(l!=newpoints.length&&withbottom)newpoints[l+2]+=bottom}if(withsteps&&l!=newpoints.length&&l>0&&newpoints[l]!=null&&newpoints[l]!=newpoints[l-ps]&&newpoints[l+1]!=newpoints[l-ps+1]){for(m=0;m 0 && origpoints[i - ps] != null) { - var interx = (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]) * (below - y) + x; + var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]); prevp.push(interx); prevp.push(below); for (m = 2; m < ps; ++m) @@ -96,8 +103,11 @@ events. datapoints.points = newpoints; thresholded.datapoints.points = threspoints; - if (thresholded.datapoints.points.length > 0) - plot.getData().push(thresholded); + if (thresholded.datapoints.points.length > 0) { + var origIndex = $.inArray(s, plot.getData()); + // Insert newly-generated series right after original one (to prevent it from becoming top-most) + plot.getData().splice(origIndex + 1, 0, thresholded); + } // FIXME: there are probably some edge cases left in bars } diff --git a/Web Server/flot/jquery.flot.threshold.min.js b/Web Server/flot/jquery.flot.threshold.min.js new file mode 100644 index 0000000..a53849a --- /dev/null +++ b/Web Server/flot/jquery.flot.threshold.min.js @@ -0,0 +1 @@ +(function($){var options={series:{threshold:null}};function init(plot){function thresholdData(plot,s,datapoints,below,color){var ps=datapoints.pointsize,i,x,y,p,prevp,thresholded=$.extend({},s);thresholded.datapoints={points:[],pointsize:ps,format:datapoints.format};thresholded.label=null;thresholded.color=color;thresholded.threshold=null;thresholded.originSeries=s;thresholded.data=[];var origpoints=datapoints.points,addCrossingPoints=s.lines.show;var threspoints=[];var newpoints=[];var m;for(i=0;i0&&origpoints[i-ps]!=null){var interx=x+(below-y)*(x-origpoints[i-ps])/(y-origpoints[i-ps+1]);prevp.push(interx);prevp.push(below);for(m=2;m0){var origIndex=$.inArray(s,plot.getData());plot.getData().splice(origIndex+1,0,thresholded)}}function processThresholds(plot,s,datapoints){if(!s.threshold)return;if(s.threshold instanceof Array){s.threshold.sort(function(a,b){return a.below-b.below});$(s.threshold).each(function(i,th){thresholdData(plot,s,datapoints,th.below,th.color)})}else{thresholdData(plot,s,datapoints,s.threshold.below,s.threshold.color)}}plot.hooks.processDatapoints.push(processThresholds)}$.plot.plugins.push({init:init,options:options,name:"threshold",version:"1.2"})})(jQuery); \ No newline at end of file diff --git a/Web Server/flot/jquery.flot.time.js b/Web Server/flot/jquery.flot.time.js new file mode 100644 index 0000000..15f5281 --- /dev/null +++ b/Web Server/flot/jquery.flot.time.js @@ -0,0 +1,431 @@ +/* Pretty handling of time axes. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +Set axis.mode to "time" to enable. See the section "Time series data" in +API.txt for details. + +*/ + +(function($) { + + var options = { + xaxis: { + timezone: null, // "browser" for local to the client or timezone for timezone-js + timeformat: null, // format string to use + twelveHourClock: false, // 12 or 24 time in time mode + monthNames: null // list of names of months + } + }; + + // round to nearby lower multiple of base + + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + + // Returns a string with the date d formatted according to fmt. + // A subset of the Open Group's strftime format is supported. + + function formatDate(d, fmt, monthNames, dayNames) { + + if (typeof d.strftime == "function") { + return d.strftime(fmt); + } + + var leftPad = function(n, pad) { + n = "" + n; + pad = "" + (pad == null ? "0" : pad); + return n.length == 1 ? pad + n : n; + }; + + var r = []; + var escape = false; + var hours = d.getHours(); + var isAM = hours < 12; + + if (monthNames == null) { + monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + } + + if (dayNames == null) { + dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + } + + var hours12; + + if (hours > 12) { + hours12 = hours - 12; + } else if (hours == 0) { + hours12 = 12; + } else { + hours12 = hours; + } + + for (var i = 0; i < fmt.length; ++i) { + + var c = fmt.charAt(i); + + if (escape) { + switch (c) { + case 'a': c = "" + dayNames[d.getDay()]; break; + case 'b': c = "" + monthNames[d.getMonth()]; break; + case 'd': c = leftPad(d.getDate()); break; + case 'e': c = leftPad(d.getDate(), " "); break; + case 'h': // For back-compat with 0.7; remove in 1.0 + case 'H': c = leftPad(hours); break; + case 'I': c = leftPad(hours12); break; + case 'l': c = leftPad(hours12, " "); break; + case 'm': c = leftPad(d.getMonth() + 1); break; + case 'M': c = leftPad(d.getMinutes()); break; + // quarters not in Open Group's strftime specification + case 'q': + c = "" + (Math.floor(d.getMonth() / 3) + 1); break; + case 'S': c = leftPad(d.getSeconds()); break; + case 'y': c = leftPad(d.getFullYear() % 100); break; + case 'Y': c = "" + d.getFullYear(); break; + case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; + case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; + case 'w': c = "" + d.getDay(); break; + } + r.push(c); + escape = false; + } else { + if (c == "%") { + escape = true; + } else { + r.push(c); + } + } + } + + return r.join(""); + } + + // To have a consistent view of time-based data independent of which time + // zone the client happens to be in we need a date-like object independent + // of time zones. This is done through a wrapper that only calls the UTC + // versions of the accessor methods. + + function makeUtcWrapper(d) { + + function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { + sourceObj[sourceMethod] = function() { + return targetObj[targetMethod].apply(targetObj, arguments); + }; + }; + + var utc = { + date: d + }; + + // support strftime, if found + + if (d.strftime != undefined) { + addProxyMethod(utc, "strftime", d, "strftime"); + } + + addProxyMethod(utc, "getTime", d, "getTime"); + addProxyMethod(utc, "setTime", d, "setTime"); + + var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; + + for (var p = 0; p < props.length; p++) { + addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); + addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); + } + + return utc; + }; + + // select time zone strategy. This returns a date-like object tied to the + // desired timezone + + function dateGenerator(ts, opts) { + if (opts.timezone == "browser") { + return new Date(ts); + } else if (!opts.timezone || opts.timezone == "utc") { + return makeUtcWrapper(new Date(ts)); + } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { + var d = new timezoneJS.Date(); + // timezone-js is fickle, so be sure to set the time zone before + // setting the time. + d.setTimezone(opts.timezone); + d.setTime(ts); + return d; + } else { + return makeUtcWrapper(new Date(ts)); + } + } + + // map of app. size of time units in milliseconds + + var timeUnitSize = { + "second": 1000, + "minute": 60 * 1000, + "hour": 60 * 60 * 1000, + "day": 24 * 60 * 60 * 1000, + "month": 30 * 24 * 60 * 60 * 1000, + "quarter": 3 * 30 * 24 * 60 * 60 * 1000, + "year": 365.2425 * 24 * 60 * 60 * 1000 + }; + + // the allowed tick sizes, after 1 year we use + // an integer algorithm + + var baseSpec = [ + [1, "second"], [2, "second"], [5, "second"], [10, "second"], + [30, "second"], + [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], + [30, "minute"], + [1, "hour"], [2, "hour"], [4, "hour"], + [8, "hour"], [12, "hour"], + [1, "day"], [2, "day"], [3, "day"], + [0.25, "month"], [0.5, "month"], [1, "month"], + [2, "month"] + ]; + + // we don't know which variant(s) we'll need yet, but generating both is + // cheap + + var specMonths = baseSpec.concat([[3, "month"], [6, "month"], + [1, "year"]]); + var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], + [1, "year"]]); + + function init(plot) { + plot.hooks.processOptions.push(function (plot, options) { + $.each(plot.getAxes(), function(axisName, axis) { + + var opts = axis.options; + + if (opts.mode == "time") { + axis.tickGenerator = function(axis) { + + var ticks = []; + var d = dateGenerator(axis.min, opts); + var minSize = 0; + + // make quarter use a possibility if quarters are + // mentioned in either of these options + + var spec = (opts.tickSize && opts.tickSize[1] === + "quarter") || + (opts.minTickSize && opts.minTickSize[1] === + "quarter") ? specQuarters : specMonths; + + if (opts.minTickSize != null) { + if (typeof opts.tickSize == "number") { + minSize = opts.tickSize; + } else { + minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; + } + } + + for (var i = 0; i < spec.length - 1; ++i) { + if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] + + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 + && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { + break; + } + } + + var size = spec[i][0]; + var unit = spec[i][1]; + + // special-case the possibility of several years + + if (unit == "year") { + + // if given a minTickSize in years, just use it, + // ensuring that it's an integer + + if (opts.minTickSize != null && opts.minTickSize[1] == "year") { + size = Math.floor(opts.minTickSize[0]); + } else { + + var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); + var norm = (axis.delta / timeUnitSize.year) / magn; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + } + + // minimum size for years is 1 + + if (size < 1) { + size = 1; + } + } + + axis.tickSize = opts.tickSize || [size, unit]; + var tickSize = axis.tickSize[0]; + unit = axis.tickSize[1]; + + var step = tickSize * timeUnitSize[unit]; + + if (unit == "second") { + d.setSeconds(floorInBase(d.getSeconds(), tickSize)); + } else if (unit == "minute") { + d.setMinutes(floorInBase(d.getMinutes(), tickSize)); + } else if (unit == "hour") { + d.setHours(floorInBase(d.getHours(), tickSize)); + } else if (unit == "month") { + d.setMonth(floorInBase(d.getMonth(), tickSize)); + } else if (unit == "quarter") { + d.setMonth(3 * floorInBase(d.getMonth() / 3, + tickSize)); + } else if (unit == "year") { + d.setFullYear(floorInBase(d.getFullYear(), tickSize)); + } + + // reset smaller components + + d.setMilliseconds(0); + + if (step >= timeUnitSize.minute) { + d.setSeconds(0); + } + if (step >= timeUnitSize.hour) { + d.setMinutes(0); + } + if (step >= timeUnitSize.day) { + d.setHours(0); + } + if (step >= timeUnitSize.day * 4) { + d.setDate(1); + } + if (step >= timeUnitSize.month * 2) { + d.setMonth(floorInBase(d.getMonth(), 3)); + } + if (step >= timeUnitSize.quarter * 2) { + d.setMonth(floorInBase(d.getMonth(), 6)); + } + if (step >= timeUnitSize.year) { + d.setMonth(0); + } + + var carry = 0; + var v = Number.NaN; + var prev; + + do { + + prev = v; + v = d.getTime(); + ticks.push(v); + + if (unit == "month" || unit == "quarter") { + if (tickSize < 1) { + + // a bit complicated - we'll divide the + // month/quarter up but we need to take + // care of fractions so we don't end up in + // the middle of a day + + d.setDate(1); + var start = d.getTime(); + d.setMonth(d.getMonth() + + (unit == "quarter" ? 3 : 1)); + var end = d.getTime(); + d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); + carry = d.getHours(); + d.setHours(0); + } else { + d.setMonth(d.getMonth() + + tickSize * (unit == "quarter" ? 3 : 1)); + } + } else if (unit == "year") { + d.setFullYear(d.getFullYear() + tickSize); + } else { + d.setTime(v + step); + } + } while (v < axis.max && v != prev); + + return ticks; + }; + + axis.tickFormatter = function (v, axis) { + + var d = dateGenerator(v, axis.options); + + // first check global format + + if (opts.timeformat != null) { + return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); + } + + // possibly use quarters if quarters are mentioned in + // any of these places + + var useQuarters = (axis.options.tickSize && + axis.options.tickSize[1] == "quarter") || + (axis.options.minTickSize && + axis.options.minTickSize[1] == "quarter"); + + var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; + var span = axis.max - axis.min; + var suffix = (opts.twelveHourClock) ? " %p" : ""; + var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; + var fmt; + + if (t < timeUnitSize.minute) { + fmt = hourCode + ":%M:%S" + suffix; + } else if (t < timeUnitSize.day) { + if (span < 2 * timeUnitSize.day) { + fmt = hourCode + ":%M" + suffix; + } else { + fmt = "%b %d " + hourCode + ":%M" + suffix; + } + } else if (t < timeUnitSize.month) { + fmt = "%b %d"; + } else if ((useQuarters && t < timeUnitSize.quarter) || + (!useQuarters && t < timeUnitSize.year)) { + if (span < timeUnitSize.year) { + fmt = "%b"; + } else { + fmt = "%b %Y"; + } + } else if (useQuarters && t < timeUnitSize.year) { + if (span < timeUnitSize.year) { + fmt = "Q%q"; + } else { + fmt = "Q%q %Y"; + } + } else { + fmt = "%Y"; + } + + var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); + + return rt; + }; + } + }); + }); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'time', + version: '1.0' + }); + + // Time-axis support used to be in Flot core, which exposed the + // formatDate function on the plot object. Various plugins depend + // on the function, so we need to re-expose it here. + + $.plot.formatDate = formatDate; + +})(jQuery); diff --git a/Web Server/flot/jquery.flot.time.min.js b/Web Server/flot/jquery.flot.time.min.js new file mode 100644 index 0000000..aaf319c --- /dev/null +++ b/Web Server/flot/jquery.flot.time.min.js @@ -0,0 +1 @@ +(function($){var options={xaxis:{timezone:null,timeformat:null,twelveHourClock:false,monthNames:null}};function floorInBase(n,base){return base*Math.floor(n/base)}function formatDate(d,fmt,monthNames,dayNames){if(typeof d.strftime=="function"){return d.strftime(fmt)}var leftPad=function(n,pad){n=""+n;pad=""+(pad==null?"0":pad);return n.length==1?pad+n:n};var r=[];var escape=false;var hours=d.getHours();var isAM=hours<12;if(monthNames==null){monthNames=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}if(dayNames==null){dayNames=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]}var hours12;if(hours>12){hours12=hours-12}else if(hours==0){hours12=12}else{hours12=hours}for(var i=0;i=minSize){break}}var size=spec[i][0];var unit=spec[i][1];if(unit=="year"){if(opts.minTickSize!=null&&opts.minTickSize[1]=="year"){size=Math.floor(opts.minTickSize[0])}else{var magn=Math.pow(10,Math.floor(Math.log(axis.delta/timeUnitSize.year)/Math.LN10));var norm=axis.delta/timeUnitSize.year/magn;if(norm<1.5){size=1}else if(norm<3){size=2}else if(norm<7.5){size=5}else{size=10}size*=magn}if(size<1){size=1}}axis.tickSize=opts.tickSize||[size,unit];var tickSize=axis.tickSize[0];unit=axis.tickSize[1];var step=tickSize*timeUnitSize[unit];if(unit=="second"){d.setSeconds(floorInBase(d.getSeconds(),tickSize))}else if(unit=="minute"){d.setMinutes(floorInBase(d.getMinutes(),tickSize))}else if(unit=="hour"){d.setHours(floorInBase(d.getHours(),tickSize))}else if(unit=="month"){d.setMonth(floorInBase(d.getMonth(),tickSize))}else if(unit=="quarter"){d.setMonth(3*floorInBase(d.getMonth()/3,tickSize))}else if(unit=="year"){d.setFullYear(floorInBase(d.getFullYear(),tickSize))}d.setMilliseconds(0);if(step>=timeUnitSize.minute){d.setSeconds(0)}if(step>=timeUnitSize.hour){d.setMinutes(0)}if(step>=timeUnitSize.day){d.setHours(0)}if(step>=timeUnitSize.day*4){d.setDate(1)}if(step>=timeUnitSize.month*2){d.setMonth(floorInBase(d.getMonth(),3))}if(step>=timeUnitSize.quarter*2){d.setMonth(floorInBase(d.getMonth(),6))}if(step>=timeUnitSize.year){d.setMonth(0)}var carry=0;var v=Number.NaN;var prev;do{prev=v;v=d.getTime();ticks.push(v);if(unit=="month"||unit=="quarter"){if(tickSize<1){d.setDate(1);var start=d.getTime();d.setMonth(d.getMonth()+(unit=="quarter"?3:1));var end=d.getTime();d.setTime(v+carry*timeUnitSize.hour+(end-start)*tickSize);carry=d.getHours();d.setHours(0)}else{d.setMonth(d.getMonth()+tickSize*(unit=="quarter"?3:1))}}else if(unit=="year"){d.setFullYear(d.getFullYear()+tickSize)}else{d.setTime(v+step)}}while(v to avoid XSS via location.hash (#9521) + rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // The ready event handler and self cleanup method + DOMContentLoaded = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + } else if ( document.readyState === "complete" ) { + // we're here because readyState === "complete" in oldIE + // which is good enough for us to call the dom ready! + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context && context.nodeType ? context.ownerDocument || context : document ); + + // scripts is true for back-compat + selector = jQuery.parseHTML( match[1], doc, true ); + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + this.attr.call( selector, context, true ); + } + + return jQuery.merge( this, selector ); + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.8.3", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ), + "slice", core_slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ core_toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // scripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, scripts ) { + var parsed; + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + scripts = context; + context = 0; + } + context = context || document; + + // Single tag + if ( (parsed = rsingleTag.exec( data )) ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] ); + return jQuery.merge( [], + (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes ); + }, + + parseJSON: function( data ) { + if ( !data || typeof data !== "string") { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && core_rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var name, + i = 0, + length = obj.length, + isObj = length === undefined || jQuery.isFunction( obj ); + + if ( args ) { + if ( isObj ) { + for ( name in obj ) { + if ( callback.apply( obj[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( obj[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in obj ) { + if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var type, + ret = results || []; + + if ( arr != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + type = jQuery.type( arr ); + + if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) { + core_push.call( ret, arr ); + } else { + jQuery.merge( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, + ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, pass ) { + var exec, + bulk = key == null, + i = 0, + length = elems.length; + + // Sets many values + if ( key && typeof key === "object" ) { + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); + } + chainable = 1; + + // Sets one value + } else if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = pass === undefined && jQuery.isFunction( value ); + + if ( bulk ) { + // Bulk operations only iterate when executing function values + if ( exec ) { + exec = fn; + fn = function( elem, key, value ) { + return exec.call( jQuery( elem ), value ); + }; + + // Otherwise they run against the entire set + } else { + fn.call( elems, value ); + fn = null; + } + } + + if ( fn ) { + for (; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + } + + chainable = 1; + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready, 1 ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.split( core_rspace ), function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + return jQuery.inArray( fn, list ) > -1; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ]( jQuery.isFunction( fn ) ? + function() { + var returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + } : + newDefer[ action ] + ); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] = list.fire + deferred[ tuple[0] ] = list.fire; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + fragment, + eventName, + i, + isSupported, + clickFn, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
a"; + + // Support tests won't run in some limited or non-browser environments + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + if ( !all || !a || !all.length ) { + return {}; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form (#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: ( document.compatMode === "CSS1Compat" ), + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", clickFn = function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent("onclick"); + div.detachEvent( "onclick", clickFn ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + input.setAttribute( "checked", "checked" ); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for ( i in { + submit: true, + change: true, + focusin: true + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + // Run tests that need a body at doc ready + jQuery(function() { + var container, div, tds, marginDiv, + divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
t
"; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // NOTE: To any future maintainer, we've window.getComputedStyle + // because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = document.createElement("div"); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "
"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + container.style.zoom = 1; + } + + // Null elements to avoid leaks in IE + body.removeChild( container ); + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + fragment.removeChild( div ); + all = a = select = opt = input = fragment = div = null; + + return support; +})(); +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + deletedIds: [], + + // Remove at next major release (1.9/2.0) + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, part, attr, name, l, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attr = elem.attributes; + for ( l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( !name.indexOf( "data-" ) ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split( ".", 2 ); + parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + data = this.triggerHandler( "getData" + part, [ parts[0] ] ); + + // Try to fetch any internally stored data first + if ( data === undefined && elem ) { + data = jQuery.data( elem, key ); + data = dataAttr( elem, key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } + + parts[1] = value; + this.each(function() { + var self = jQuery( this ); + + self.triggerHandler( "setData" + part, parts ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + part, parts ); + }); + }, null, value, arguments.length > 1, null, false ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery.removeData( elem, type + "queue", true ); + jQuery.removeData( elem, key, true ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, fixSpecified, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea|)$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( core_rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var removes, className, elem, c, cl, i, l; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + if ( (value && typeof value === "string") || value === undefined ) { + removes = ( value || "" ).split( core_rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + if ( elem.nodeType === 1 && elem.className ) { + + className = (" " + elem.className + " ").replace( rclass, " " ); + + // loop over each item in the removal list + for ( c = 0, cl = removes.length; c < cl; c++ ) { + // Remove until there is nothing to remove, + while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) { + className = className.replace( " " + removes[ c ] + " " , " " ); + } + } + elem.className = value ? jQuery.trim( className ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( core_rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val, + self = jQuery(this); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9 + attrFn: {}, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, isBool, + i = 0; + + if ( value && elem.nodeType === 1 ) { + + attrNames = value.split( core_rspace ); + + for ( ; i < attrNames.length; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); + + // See #9699 for explanation of this approach (setting first, then removal) + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( isBool && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true, + coords: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.value = value + "" ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/, + rhoverHack = /(?:^|\s)hover(\.\S+|)\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var t, tns, type, origType, namespaces, origCount, + j, events, special, eventType, handleObj, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, "events", true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType, + type = event.type || event, + namespaces = []; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + for ( old = elem; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old === (elem.ownerDocument || document) ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related, + handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = core_slice.call( arguments ), + run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = []; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !(event.button && event.type === "click") ) { + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + + // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + selMatch = {}; + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8) + event.metaKey = !!event.metaKey; + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === "undefined" ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "_submit_attached" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "_submit_attached", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "_change_attached", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { // && selector != null + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +(function( window, undefined ) { + +var cachedruns, + assertGetIdNotName, + Expr, + getText, + isXML, + contains, + compile, + sortOrder, + hasDuplicate, + outermostContext, + + baseHasDuplicate = true, + strundefined = "undefined", + + expando = ( "sizcache" + Math.random() ).replace( ".", "" ), + + Token = String, + document = window.document, + docElem = document.documentElement, + dirruns = 0, + done = 0, + pop = [].pop, + push = [].push, + slice = [].slice, + // Use a stripped-down indexOf if a native one is unavailable + indexOf = [].indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + // Augment a function for special use by Sizzle + markFunction = function( fn, value ) { + fn[ expando ] = value == null || value; + return fn; + }, + + createCache = function() { + var cache = {}, + keys = []; + + return markFunction(function( key, value ) { + // Only keep the most recent entries + if ( keys.push( key ) > Expr.cacheLength ) { + delete cache[ keys.shift() ]; + } + + // Retrieve with (key + " ") to avoid collision with native Object.prototype properties (see Issue #157) + return (cache[ key + " " ] = value); + }, cache ); + }, + + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + + // Regex + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors) + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments not in parens/brackets, + // then attribute selectors and non-pseudos (denoted by :), + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)", + + // For matchExpr.POS and matchExpr.needsContext + pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), + rpseudo = new RegExp( pseudos ), + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/, + + rnot = /^:not/, + rsibling = /[\x20\t\r\n\f]*[+~]/, + rendsWithNot = /:not\($/, + + rheader = /h\d/i, + rinputs = /input|select|textarea|button/i, + + rbackslash = /\\(?!\\)/g, + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "POS": new RegExp( pos, "i" ), + "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + // For use in libraries implementing .is() + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" ) + }, + + // Support + + // Used for testing something on an element + assert = function( fn ) { + var div = document.createElement("div"); + + try { + return fn( div ); + } catch (e) { + return false; + } finally { + // release memory in IE + div = null; + } + }, + + // Check if getElementsByTagName("*") returns only elements + assertTagNameNoComments = assert(function( div ) { + div.appendChild( document.createComment("") ); + return !div.getElementsByTagName("*").length; + }), + + // Check if getAttribute returns normalized href attributes + assertHrefNotNormalized = assert(function( div ) { + div.innerHTML = ""; + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute("href") === "#"; + }), + + // Check if attributes should be retrieved by attribute nodes + assertAttributes = assert(function( div ) { + div.innerHTML = ""; + var type = typeof div.lastChild.getAttribute("multiple"); + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + }), + + // Check if getElementsByClassName can be trusted + assertUsableClassName = assert(function( div ) { + // Opera can't find a second classname (in 9.6) + div.innerHTML = ""; + if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = "e"; + return div.getElementsByClassName("e").length === 2; + }), + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + assertUsableName = assert(function( div ) { + // Inject content + div.id = expando + 0; + div.innerHTML = "
"; + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = document.getElementsByName && + // buggy browsers will return fewer than the correct 2 + document.getElementsByName( expando ).length === 2 + + // buggy browsers will return more than the correct 0 + document.getElementsByName( expando + 0 ).length; + assertGetIdNotName = !document.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + }); + +// If slice is not available, provide a backup +try { + slice.call( docElem.childNodes, 0 )[0].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, + results = []; + for ( ; (elem = this[i]); i++ ) { + results.push( elem ); + } + return results; + }; +} + +function Sizzle( selector, context, results, seed ) { + results = results || []; + context = context || document; + var match, elem, xml, m, + nodeType = context.nodeType; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( nodeType !== 1 && nodeType !== 9 ) { + return []; + } + + xml = isXML( context ); + + if ( !xml && !seed ) { + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) { + push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + return results; + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed, xml ); +} + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + return Sizzle( expr, null, null, [ elem ] ).length > 0; +}; + +// Returns a function to use in pseudos for input types +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +// Returns a function to use in pseudos for buttons +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +// Returns a function to use in pseudos for positionals +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + } else { + + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } + return ret; +}; + +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +// Element contains another +contains = Sizzle.contains = docElem.contains ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) ); + } : + docElem.compareDocumentPosition ? + function( a, b ) { + return b && !!( a.compareDocumentPosition( b ) & 16 ); + } : + function( a, b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + return false; + }; + +Sizzle.attr = function( elem, name ) { + var val, + xml = isXML( elem ); + + if ( !xml ) { + name = name.toLowerCase(); + } + if ( (val = Expr.attrHandle[ name ]) ) { + return val( elem ); + } + if ( xml || assertAttributes ) { + return elem.getAttribute( name ); + } + val = elem.getAttributeNode( name ); + return val ? + typeof elem[ name ] === "boolean" ? + elem[ name ] ? name : null : + val.specified ? val.value : null : + null; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + // IE6/7 return a modified href + attrHandle: assertHrefNotNormalized ? + {} : + { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute("type"); + } + }, + + find: { + "ID": assertGetIdNotName ? + function( id, context, xml ) { + if ( typeof context.getElementById !== strundefined && !xml ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + } : + function( id, context, xml ) { + if ( typeof context.getElementById !== strundefined && !xml ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? + [m] : + undefined : + []; + } + }, + + "TAG": assertTagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + var elem, + tmp = [], + i = 0; + + for ( ; (elem = results[i]); i++ ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }, + + "NAME": assertUsableName && function( tag, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }, + + "CLASS": assertUsableClassName && function( className, context, xml ) { + if ( typeof context.getElementsByClassName !== strundefined && !xml ) { + return context.getElementsByClassName( className ); + } + } + }, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( rbackslash, "" ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 3 xn-component of xn+y argument ([+-]?\d*n|) + 4 sign of xn-component + 5 x of xn-component + 6 sign of y-component + 7 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1] === "nth" ) { + // nth-child requires argument + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) ); + match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" ); + + // other types prohibit arguments + } else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var unquoted, excess; + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + if ( match[3] ) { + match[2] = match[3]; + } else if ( (unquoted = match[4]) ) { + // Only check arguments that contain a pseudo + if ( rpseudo.test(unquoted) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + unquoted = unquoted.slice( 0, excess ); + match[0] = match[0].slice( 0, excess ); + } + match[2] = unquoted; + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + "ID": assertGetIdNotName ? + function( id ) { + id = id.replace( rbackslash, "" ); + return function( elem ) { + return elem.getAttribute("id") === id; + }; + } : + function( id ) { + id = id.replace( rbackslash, "" ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === id; + }; + }, + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + nodeName = nodeName.replace( rbackslash, "" ).toLowerCase(); + + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ expando ][ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem, context ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.substr( result.length - check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, argument, first, last ) { + + if ( type === "nth" ) { + return function( elem ) { + var node, diff, + parent = elem.parentNode; + + if ( first === 1 && last === 0 ) { + return true; + } + + if ( parent ) { + diff = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + diff++; + if ( elem === node ) { + break; + } + } + } + } + + // Incorporate the offset (or cast to NaN), then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + }; + } + + return function( elem ) { + var node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + /* falls through */ + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + var nodeType; + elem = elem.firstChild; + while ( elem ) { + if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) { + return false; + } + elem = elem.nextSibling; + } + return true; + }, + + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "text": function( elem ) { + var type, attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + (type = elem.type) === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type ); + }, + + // Input types + "radio": createInputPseudo("radio"), + "checkbox": createInputPseudo("checkbox"), + "file": createInputPseudo("file"), + "password": createInputPseudo("password"), + "image": createInputPseudo("image"), + + "submit": createButtonPseudo("submit"), + "reset": createButtonPseudo("reset"), + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "focus": function( elem ) { + var doc = elem.ownerDocument; + return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + "active": function( elem ) { + return elem === elem.ownerDocument.activeElement; + }, + + // Positional types + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + for ( var i = 0; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + for ( var i = 1; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +function siblingCheck( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; +} + +sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + return ( !a.compareDocumentPosition || !b.compareDocumentPosition ? + a.compareDocumentPosition : + a.compareDocumentPosition(b) & 4 + ) ? -1 : 1; + } : + function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + +// Always assume the presence of duplicates if sort doesn't +// pass them to our comparison function (as in Google Chrome). +[0, 0].sort( sortOrder ); +baseHasDuplicate = !hasDuplicate; + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + i = 1, + j = 0; + + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem === results[ i - 1 ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ expando ][ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + tokens.push( matched = new Token( match.shift() ) ); + soFar = soFar.slice( matched.length ); + + // Cast descendant combinators to space + matched.type = match[0].replace( rtrim, " " ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + + tokens.push( matched = new Token( match.shift() ) ); + soFar = soFar.slice( matched.length ); + matched.type = type; + matched.matches = match; + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && combinator.dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( checkNonElements || elem.nodeType === 1 ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( !xml ) { + var cache, + dirkey = dirruns + " " + doneName + " ", + cachedkey = dirkey + cachedruns; + while ( (elem = elem[ dir ]) ) { + if ( checkNonElements || elem.nodeType === 1 ) { + if ( (cache = elem[ expando ]) === cachedkey ) { + return elem.sizset; + } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) { + if ( elem.sizset ) { + return elem; + } + } else { + elem[ expando ] = cachedkey; + if ( matcher( elem, context, xml ) ) { + elem.sizset = true; + return elem; + } + elem.sizset = false; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( checkNonElements || elem.nodeType === 1 ) { + if ( matcher( elem, context, xml ) ) { + return elem; + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && tokens.join("") + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Nested matchers should use non-integer dirruns + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = superMatcher.el; + } + + // Add elements passing elementMatchers directly to results + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + for ( j = 0; (matcher = elementMatchers[j]); j++ ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++superMatcher.el; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + for ( j = 0; (matcher = setMatchers[j]); j++ ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + superMatcher.el = 0; + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ expando ][ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed, xml ) { + var i, tokens, token, type, find, + match = tokenize( selector ), + j = match.length; + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && !xml && + Expr.relative[ tokens[1].type ] ) { + + context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().length ); + } + + // Fetch a seed set for right-to-left matching + for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( rbackslash, "" ), + rsibling.test( tokens[0].type ) && context.parentNode || context, + xml + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && tokens.join(""); + if ( !selector ) { + push.apply( results, slice.call( seed, 0 ) ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + xml, + results, + rsibling.test( selector ) + ); + return results; +} + +if ( document.querySelectorAll ) { + (function() { + var disconnectedMatch, + oldSelect = select, + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + + // qSa(:focus) reports false when true (Chrome 21), no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [ ":focus" ], + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + // A support test would require too much code (would include document ready) + // just skip matchesSelector for :active + rbuggyMatches = [ ":active" ], + matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector; + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here (do not put tests after this one) + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Opera 10-12/IE9 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = "

"; + if ( div.querySelectorAll("[test^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here (do not put tests after this one) + div.innerHTML = ""; + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push(":enabled", ":disabled"); + } + }); + + // rbuggyQSA always contains :focus, so no need for a length check + rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") ); + + select = function( selector, context, results, seed, xml ) { + // Only use querySelectorAll when not filtering, + // when this is not xml, + // and when no QSA bugs apply + if ( !seed && !xml && !rbuggyQSA.test( selector ) ) { + var groups, i, + old = true, + nid = expando, + newContext = context, + newSelector = context.nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + groups[i].join(""); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, slice.call( newContext.querySelectorAll( + newSelector + ), 0 ) ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + + return oldSelect( selector, context, results, seed, xml ); + }; + + if ( matches ) { + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + try { + matches.call( div, "[test!='']:sizzle" ); + rbuggyMatches.push( "!=", pseudos ); + } catch ( e ) {} + }); + + // rbuggyMatches always contains :active and :focus, so no need for a length check + rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") ); + + Sizzle.matchesSelector = function( elem, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyMatches always contains :active, so no need for an existence check + if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && !rbuggyQSA.test( expr ) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, null, null, [ elem ] ).length > 0; + }; + } + })(); +} + +// Deprecated +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Back-compat +function setFilters() {} +Expr.filters = setFilters.prototype = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, l, length, n, r, ret, + self = this; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + ret = this.pushStack( "", "find", selector ); + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[i]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, core_slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /]", "i"), + rcheckableType = /^(?:checkbox|radio)$/, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*\s*$/g, + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, +// unless wrapped in a div with non-breaking characters in front of it. +if ( !jQuery.support.htmlSerialize ) { + wrapMap._default = [ 1, "X
", "
" ]; +} + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + if ( !isDisconnected( this[0] ) ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this ); + }); + } + + if ( arguments.length ) { + var set = jQuery.clean( arguments ); + return this.pushStack( jQuery.merge( set, this ), "before", this.selector ); + } + }, + + after: function() { + if ( !isDisconnected( this[0] ) ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + } + + if ( arguments.length ) { + var set = jQuery.clean( arguments ); + return this.pushStack( jQuery.merge( this, set ), "after", this.selector ); + } + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( [ elem ] ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName( "*" ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + if ( !isDisconnected( this[0] ) ) { + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this), old = self.html(); + self.replaceWith( value.call( this, i, old ) ); + }); + } + + if ( typeof value !== "string" ) { + value = jQuery( value ).detach(); + } + + return this.each(function() { + var next = this.nextSibling, + parent = this.parentNode; + + jQuery( this ).remove(); + + if ( next ) { + jQuery(next).before( value ); + } else { + jQuery(parent).append( value ); + } + }); + } + + return this.length ? + this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : + this; + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = [].concat.apply( [], args ); + + var results, first, fragment, iNoClone, + i = 0, + value = args[0], + scripts = [], + l = this.length; + + // We can't cloneNode fragments that contain checked, in WebKit + if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) { + return this.each(function() { + jQuery(this).domManip( args, table, callback ); + }); + } + + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + args[0] = value.call( this, i, table ? self.html() : undefined ); + self.domManip( args, table, callback ); + }); + } + + if ( this[0] ) { + results = jQuery.buildFragment( args, this, scripts ); + fragment = results.fragment; + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + // Fragments from the fragment cache must always be cloned and never used in place. + for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) { + callback.call( + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : + this[i], + i === iNoClone ? + fragment : + jQuery.clone( fragment, true, true ) + ); + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + + if ( scripts.length ) { + jQuery.each( scripts, function( i, elem ) { + if ( elem.src ) { + if ( jQuery.ajax ) { + jQuery.ajax({ + url: elem.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.error("no ajax"); + } + } else { + jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + }); + } + } + + return this; + } +}); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function cloneFixAttributes( src, dest ) { + var nodeName; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + // clearAttributes removes the attributes, which we don't want, + // but also removes the attachEvent events, which we *do* want + if ( dest.clearAttributes ) { + dest.clearAttributes(); + } + + // mergeAttributes, in contrast, only merges back on the + // original attributes, not the events + if ( dest.mergeAttributes ) { + dest.mergeAttributes( src ); + } + + nodeName = dest.nodeName.toLowerCase(); + + if ( nodeName === "object" ) { + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + + // IE blanks contents when cloning scripts + } else if ( nodeName === "script" && dest.text !== src.text ) { + dest.text = src.text; + } + + // Event data gets referenced instead of copied if the expando + // gets copied too + dest.removeAttribute( jQuery.expando ); +} + +jQuery.buildFragment = function( args, context, scripts ) { + var fragment, cacheable, cachehit, + first = args[ 0 ]; + + // Set context from what may come in as undefined or a jQuery collection or a node + // Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 & + // also doubles as fix for #8950 where plain objects caused createDocumentFragment exception + context = context || document; + context = !context.nodeType && context[0] || context; + context = context.ownerDocument || context; + + // Only cache "small" (1/2 KB) HTML strings that are associated with the main document + // Cloning options loses the selected state, so don't cache them + // IE 6 doesn't like it when you put or elements in a fragment + // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache + // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 + if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document && + first.charAt(0) === "<" && !rnocache.test( first ) && + (jQuery.support.checkClone || !rchecked.test( first )) && + (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { + + // Mark cacheable and look for a hit + cacheable = true; + fragment = jQuery.fragments[ first ]; + cachehit = fragment !== undefined; + } + + if ( !fragment ) { + fragment = context.createDocumentFragment(); + jQuery.clean( args, context, fragment, scripts ); + + // Update the cache, but only store false + // unless this is a second parsing of the same content + if ( cacheable ) { + jQuery.fragments[ first ] = cachehit && fragment; + } + } + + return { fragment: fragment, cacheable: cacheable }; +}; + +jQuery.fragments = {}; + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + l = insert.length, + parent = this.length === 1 && this[0].parentNode; + + if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) { + insert[ original ]( this[0] ); + return this; + } else { + for ( ; i < l; i++ ) { + elems = ( i > 0 ? this.clone(true) : this ).get(); + jQuery( insert[i] )[ original ]( elems ); + ret = ret.concat( elems ); + } + + return this.pushStack( ret, name, insert.selector ); + } + }; +}); + +function getAll( elem ) { + if ( typeof elem.getElementsByTagName !== "undefined" ) { + return elem.getElementsByTagName( "*" ); + + } else if ( typeof elem.querySelectorAll !== "undefined" ) { + return elem.querySelectorAll( "*" ); + + } else { + return []; + } +} + +// Used in clean, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var srcElements, + destElements, + i, + clone; + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + // IE copies events bound via attachEvent when using cloneNode. + // Calling detachEvent on the clone will also remove the events + // from the original. In order to get around this, we use some + // proprietary methods to clear the events. Thanks to MooTools + // guys for this hotness. + + cloneFixAttributes( elem, clone ); + + // Using Sizzle here is crazy slow, so we use getElementsByTagName instead + srcElements = getAll( elem ); + destElements = getAll( clone ); + + // Weird iteration because IE will replace the length property + // with an element if you are cloning the body and one of the + // elements on the page has a name or id of "length" + for ( i = 0; srcElements[i]; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + cloneFixAttributes( srcElements[i], destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + cloneCopyEvent( elem, clone ); + + if ( deepDataAndEvents ) { + srcElements = getAll( elem ); + destElements = getAll( clone ); + + for ( i = 0; srcElements[i]; ++i ) { + cloneCopyEvent( srcElements[i], destElements[i] ); + } + } + } + + srcElements = destElements = null; + + // Return the cloned set + return clone; + }, + + clean: function( elems, context, fragment, scripts ) { + var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags, + safe = context === document && safeFragment, + ret = []; + + // Ensure that context is a document + if ( !context || typeof context.createDocumentFragment === "undefined" ) { + context = document; + } + + // Use the already-created safe fragment if context permits + for ( i = 0; (elem = elems[i]) != null; i++ ) { + if ( typeof elem === "number" ) { + elem += ""; + } + + if ( !elem ) { + continue; + } + + // Convert html string into DOM nodes + if ( typeof elem === "string" ) { + if ( !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + } else { + // Ensure a safe container in which to render the html + safe = safe || createSafeFragment( context ); + div = context.createElement("div"); + safe.appendChild( div ); + + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1>"); + + // Go to html and back, then peel off extra wrappers + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + depth = wrap[0]; + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + hasBody = rtbody.test(elem); + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare or + wrap[1] === "
" && !hasBody ? + div.childNodes : + []; + + for ( j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } + } + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } + + elem = div.childNodes; + + // Take out of fragment container (we need a fresh div each time) + div.parentNode.removeChild( div ); + } + } + + if ( elem.nodeType ) { + ret.push( elem ); + } else { + jQuery.merge( ret, elem ); + } + } + + // Fix #11356: Clear elements from safeFragment + if ( div ) { + elem = div = safe = null; + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + for ( i = 0; (elem = ret[i]) != null; i++ ) { + if ( jQuery.nodeName( elem, "input" ) ) { + fixDefaultChecked( elem ); + } else if ( typeof elem.getElementsByTagName !== "undefined" ) { + jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); + } + } + } + + // Append elements to a provided document fragment + if ( fragment ) { + // Special handling of each script element + handleScript = function( elem ) { + // Check if we consider it executable + if ( !elem.type || rscriptType.test( elem.type ) ) { + // Detach the script and store it in the scripts array (if provided) or the fragment + // Return truthy to indicate that it has been handled + return scripts ? + scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) : + fragment.appendChild( elem ); + } + }; + + for ( i = 0; (elem = ret[i]) != null; i++ ) { + // Check if we're done after handling an executable script + if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) { + // Append to fragment and handle embedded scripts + fragment.appendChild( elem ); + if ( typeof elem.getElementsByTagName !== "undefined" ) { + // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration + jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript ); + + // Splice the scripts into ret after their former ancestor and advance our index beyond them + ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); + i += jsTags.length; + } + } + } + } + + return ret; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var data, id, elem, type, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + jQuery.deletedIds.push( id ); + } + } + } + } + } +}); +// Limit scope pollution from any deprecated API +(function() { + +var matched, browser; + +// Use of jQuery.browser is frowned upon. +// More details: http://api.jquery.com/jQuery.browser +// jQuery.uaMatch maintained for back-compat +jQuery.uaMatch = function( ua ) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || + /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; +}; + +matched = jQuery.uaMatch( navigator.userAgent ); +browser = {}; + +if ( matched.browser ) { + browser[ matched.browser ] = true; + browser.version = matched.version; +} + +// Chrome is Webkit, but Webkit is also Safari. +if ( browser.chrome ) { + browser.webkit = true; +} else if ( browser.webkit ) { + browser.safari = true; +} + +jQuery.browser = browser; + +jQuery.sub = function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; +}; + +})(); +var curCSS, iframe, iframeDoc, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity=([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ], + + eventsToggle = jQuery.fn.toggle; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var elem, display, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + values[ index ] = jQuery._data( elem, "olddisplay" ); + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && elem.style.display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + display = curCSS( elem, "display" ); + + if ( !values[ index ] && display !== "none" ) { + jQuery._data( elem, "olddisplay", display ); + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state, fn2 ) { + var bool = typeof state === "boolean"; + + if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) { + return eventsToggle.apply( this, arguments ); + } + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, numeric, extra ) { + var val, num, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( numeric || extra !== undefined ) { + num = parseFloat( val ); + return numeric || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +// NOTE: To any future maintainer, we've window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + curCSS = function( elem, name ) { + var ret, width, minWidth, maxWidth, + computed = window.getComputedStyle( elem, null ), + style = elem.style; + + if ( computed ) { + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + curCSS = function( elem, name ) { + var left, rsLeft, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + // we use jQuery.css instead of curCSS here + // because of the reliableMarginRight CSS hook! + val += jQuery.css( elem, extra + cssExpand[ i ], true ); + } + + // From this point on we use curCSS for maximum performance (relevant in animations) + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0; + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0; + } + } else { + // at this point, extra isn't content, so add padding + val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0; + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0; + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + valueIsBorderBox = true, + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox + ) + ) + "px"; +} + + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + if ( elemdisplay[ nodeName ] ) { + return elemdisplay[ nodeName ]; + } + + var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ), + display = elem.css("display"); + elem.remove(); + + // If the simple way fails, + // get element's real default display by attaching it to a temp iframe + if ( display === "none" || display === "" ) { + // Use the already-created iframe if possible + iframe = document.body.appendChild( + iframe || jQuery.extend( document.createElement("iframe"), { + frameBorder: 0, + width: 0, + height: 0 + }) + ); + + // Create a cacheable copy of the iframe document on first call. + // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML + // document to it; WebKit & Firefox won't allow reusing the iframe document. + if ( !iframeDoc || !iframe.createElement ) { + iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; + iframeDoc.write(""); + iframeDoc.close(); + } + + elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) ); + + display = curCSS( elem, "display" ); + document.body.removeChild( iframe ); + } + + // Store the correct default display + elemdisplay[ nodeName ] = display; + + return display; +} + +jQuery.each([ "height", "width" ], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + // certain elements can have dimension info if we invisibly show them + // however, it must have a current display style that would benefit from this + if ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, "display" ) ) ) { + return jQuery.swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); + }); + } else { + return getWidthOrHeight( elem, name, extra ); + } + } + }, + + set: function( elem, value, extra ) { + return setPositiveNumber( elem, value, extra ? + augmentWidthOrHeight( + elem, + name, + extra, + jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box" + ) : 0 + ); + } + }; +}); + +if ( !jQuery.support.opacity ) { + jQuery.cssHooks.opacity = { + get: function( elem, computed ) { + // IE uses filters for opacity + return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? + ( 0.01 * parseFloat( RegExp.$1 ) ) + "" : + computed ? "1" : ""; + }, + + set: function( elem, value ) { + var style = elem.style, + currentStyle = elem.currentStyle, + opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", + filter = currentStyle && currentStyle.filter || style.filter || ""; + + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 + if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" && + style.removeAttribute ) { + + // Setting style.filter to null, "" & " " still leave "filter:" in the cssText + // if "filter:" is present at all, clearType is disabled, we want to avoid this + // style.removeAttribute is IE Only, but so apparently is this code path... + style.removeAttribute( "filter" ); + + // if there there is no filter style applied in a css rule, we are done + if ( currentStyle && !currentStyle.filter ) { + return; + } + } + + // otherwise, set new filter values + style.filter = ralpha.test( filter ) ? + filter.replace( ralpha, opacity ) : + filter + " " + opacity; + } + }; +} + +// These hooks cannot be added until DOM ready because the support test +// for it is not run until after DOM ready +jQuery(function() { + if ( !jQuery.support.reliableMarginRight ) { + jQuery.cssHooks.marginRight = { + get: function( elem, computed ) { + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + // Work around by temporarily setting element display to inline-block + return jQuery.swap( elem, { "display": "inline-block" }, function() { + if ( computed ) { + return curCSS( elem, "marginRight" ); + } + }); + } + }; + } + + // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 + // getComputedStyle returns percent when specified for top/left/bottom/right + // rather than make the css module depend on the offset module, we just check for it here + if ( !jQuery.support.pixelPosition && jQuery.fn.position ) { + jQuery.each( [ "top", "left" ], function( i, prop ) { + jQuery.cssHooks[ prop ] = { + get: function( elem, computed ) { + if ( computed ) { + var ret = curCSS( elem, prop ); + // if curCSS returns percentage, fallback to offset + return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret; + } + } + }; + }); + } + +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none"); + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} + +// These hooks are used by animate to expand properties +jQuery.each({ + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i, + + // assumes a single number if not a string + parts = typeof value === "string" ? value.split(" ") : [ value ], + expanded = {}; + + for ( i = 0; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +}); +var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + rselectTextarea = /^(?:select|textarea)/i; + +jQuery.fn.extend({ + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map(function(){ + return this.elements ? jQuery.makeArray( this.elements ) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + ( this.checked || rselectTextarea.test( this.nodeName ) || + rinput.test( this.type ) ); + }) + .map(function( i, elem ){ + var val = jQuery( this ).val(); + + return val == null ? + null : + jQuery.isArray( val ) ? + jQuery.map( val, function( val, i ){ + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }) : + { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }).get(); + } +}); + +//Serialize an array of form elements or a set of +//key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value ); + s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ).replace( r20, "+" ); +}; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( jQuery.isArray( obj ) ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} +var + // Document location + ajaxLocParts, + ajaxLocation, + + rhash = /#.*$/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + rquery = /\?/, + rscript = /)<[^<]*)*<\/script>/gi, + rts = /([?&])_=[^&]*/, + rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/, + + // Keep a copy of the old load method + _load = jQuery.fn.load, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = ["*/"] + ["*"]; + +// #8138, IE may throw an exception when accessing +// a field from window.location if document.domain has been set +try { + ajaxLocation = location.href; +} catch( e ) { + // Use the href attribute of an A element + // since IE will modify it given document.location + ajaxLocation = document.createElement( "a" ); + ajaxLocation.href = ""; + ajaxLocation = ajaxLocation.href; +} + +// Segment location into parts +ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, list, placeBefore, + dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ), + i = 0, + length = dataTypes.length; + + if ( jQuery.isFunction( func ) ) { + // For each dataType in the dataTypeExpression + for ( ; i < length; i++ ) { + dataType = dataTypes[ i ]; + // We control if we're asked to add before + // any existing element + placeBefore = /^\+/.test( dataType ); + if ( placeBefore ) { + dataType = dataType.substr( 1 ) || "*"; + } + list = structure[ dataType ] = structure[ dataType ] || []; + // then we add to the structure accordingly + list[ placeBefore ? "unshift" : "push" ]( func ); + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, + dataType /* internal */, inspected /* internal */ ) { + + dataType = dataType || options.dataTypes[ 0 ]; + inspected = inspected || {}; + + inspected[ dataType ] = true; + + var selection, + list = structure[ dataType ], + i = 0, + length = list ? list.length : 0, + executeOnly = ( structure === prefilters ); + + for ( ; i < length && ( executeOnly || !selection ); i++ ) { + selection = list[ i ]( options, originalOptions, jqXHR ); + // If we got redirected to another dataType + // we try there if executing only and not done already + if ( typeof selection === "string" ) { + if ( !executeOnly || inspected[ selection ] ) { + selection = undefined; + } else { + options.dataTypes.unshift( selection ); + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, selection, inspected ); + } + } + } + // If we're only executing or nothing was selected + // we try the catchall dataType if not done already + if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, "*", inspected ); + } + // unnecessary when only executing (prefilters) + // but it'll be ignored by the caller in that case + return selection; +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } +} + +jQuery.fn.load = function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + } + + // Don't do a request if no elements are being requested + if ( !this.length ) { + return this; + } + + var selector, type, response, + self = this, + off = url.indexOf(" "); + + if ( off >= 0 ) { + selector = url.slice( off, url.length ); + url = url.slice( 0, off ); + } + + // If it's a function + if ( jQuery.isFunction( params ) ) { + + // We assume that it's the callback + callback = params; + params = undefined; + + // Otherwise, build a param string + } else if ( params && typeof params === "object" ) { + type = "POST"; + } + + // Request the remote document + jQuery.ajax({ + url: url, + + // if "type" variable is undefined, then "GET" method will be used + type: type, + dataType: "html", + data: params, + complete: function( jqXHR, status ) { + if ( callback ) { + self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] ); + } + } + }).done(function( responseText ) { + + // Save response for use in complete callback + response = arguments; + + // See if a selector was specified + self.html( selector ? + + // Create a dummy div to hold the results + jQuery("
") + + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append( responseText.replace( rscript, "" ) ) + + // Locate the specified elements + .find( selector ) : + + // If not, just inject the full result + responseText ); + + }); + + return this; +}; + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ + jQuery.fn[ o ] = function( f ){ + return this.on( o, f ); + }; +}); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax({ + type: method, + url: url, + data: data, + success: callback, + dataType: type + }); + }; +}); + +jQuery.extend({ + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + if ( settings ) { + // Building a settings object + ajaxExtend( target, jQuery.ajaxSettings ); + } else { + // Extending ajaxSettings + settings = target; + target = jQuery.ajaxSettings; + } + ajaxExtend( target, settings ); + return target; + }, + + ajaxSettings: { + url: ajaxLocation, + isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + processData: true, + async: true, + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + text: "text/plain", + json: "application/json, text/javascript", + "*": allTypes + }, + + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText" + }, + + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + context: true, + url: true + } + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var // ifModified key + ifModifiedKey, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + parts, + // To know if global events are to be dispatched + fireGlobals, + // Loop variable + i, + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + // Callbacks context + callbackContext = s.context || s, + // Context for global events + // It's the callbackContext if one was provided in the options + // and if it's a DOM node or a jQuery collection + globalEventContext = callbackContext !== s && + ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? + jQuery( callbackContext ) : jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + // The jqXHR state + state = 0, + // Default abort message + strAbort = "canceled", + // Fake xhr + jqXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( !state ) { + var lname = name.toLowerCase(); + name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( state === 2 ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match === undefined ? null : match; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( !state ) { + s.mimeType = type; + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + statusText = statusText || strAbort; + if ( transport ) { + transport.abort( statusText ); + } + done( 0, statusText ); + return this; + } + }; + + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Called once + if ( state === 2 ) { + return; + } + + // State is "done" now + state = 2; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + + modified = jqXHR.getResponseHeader("Last-Modified"); + if ( modified ) { + jQuery.lastModified[ ifModifiedKey ] = modified; + } + modified = jqXHR.getResponseHeader("Etag"); + if ( modified ) { + jQuery.etag[ ifModifiedKey ] = modified; + } + } + + // If not modified + if ( status === 304 ) { + + statusText = "notmodified"; + isSuccess = true; + + // If we have data + } else { + + isSuccess = ajaxConvert( s, response ); + statusText = isSuccess.state; + success = isSuccess.data; + error = isSuccess.error; + isSuccess = !error; + } + } else { + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if ( !statusText || status ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + // Attach deferreds + deferred.promise( jqXHR ); + jqXHR.success = jqXHR.done; + jqXHR.error = jqXHR.fail; + jqXHR.complete = completeDeferred.add; + + // Status-dependent callbacks + jqXHR.statusCode = function( map ) { + if ( map ) { + var tmp; + if ( state < 2 ) { + for ( tmp in map ) { + statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; + } + } else { + tmp = map[ jqXHR.status ]; + jqXHR.always( tmp ); + } + } + return this; + }; + + // Remove hash character (#7531: and string promotion) + // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) + // We also use the url parameter if available + s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace ); + + // A cross-domain request is in order when we have a protocol:host:port mismatch + if ( s.crossDomain == null ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( parts && + ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] || + ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != + ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) + ); + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( state === 2 ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + fireGlobals = s.global; + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Get ifModifiedKey before adding the anti-cache parameter + ifModifiedKey = s.url; + + // Add anti-cache in url if needed + if ( s.cache === false ) { + + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts, "$1_=" + ts ); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + ifModifiedKey = ifModifiedKey || s.url; + if ( jQuery.lastModified[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); + } + if ( jQuery.etag[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); + } + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? + s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { + // Abort if not done already and return + return jqXHR.abort(); + + } + + // aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + for ( i in { success: 1, error: 1, complete: 1 } ) { + jqXHR[ i ]( s[ i ] ); + } + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout( function(){ + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + state = 1; + transport.send( requestHeaders, done ); + } catch (e) { + // Propagate exception as error if not done + if ( state < 2 ) { + done( -1, e ); + // Simply rethrow otherwise + } else { + throw e; + } + } + } + + return jqXHR; + }, + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {} + +}); + +/* Handles responses to an ajax request: + * - sets all responseXXX fields accordingly + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes, + responseFields = s.responseFields; + + // Fill responseXXX fields + for ( type in responseFields ) { + if ( type in responses ) { + jqXHR[ responseFields[type] ] = responses[ type ]; + } + } + + // Remove auto dataType and get content-type in the process + while( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +// Chain conversions given the request and the original response +function ajaxConvert( s, response ) { + + var conv, conv2, current, tmp, + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(), + prev = dataTypes[ 0 ], + converters = {}, + i = 0; + + // Apply the dataFilter if provided + if ( s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + // Convert to each sequential dataType, tolerating list modification + for ( ; (current = dataTypes[++i]); ) { + + // There's only work to do if current dataType is non-auto + if ( current !== "*" ) { + + // Convert response if prev dataType is non-auto and differs from current + if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split(" "); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.splice( i--, 0, current ); + } + + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s["throws"] ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; + } + } + } + } + + // Update prev for next iteration + prev = current; + } + } + + return { state: "success", data: response }; +} +var oldCallbacks = [], + rquestion = /\?/, + rjsonp = /(=)\?(?=&|$)|\?\?/, + nonce = jQuery.now(); + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); + this[ callback ] = true; + return callback; + } +}); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var callbackName, overwritten, responseContainer, + data = s.data, + url = s.url, + hasCallback = s.jsonp !== false, + replaceInUrl = hasCallback && rjsonp.test( url ), + replaceInData = hasCallback && !replaceInUrl && typeof data === "string" && + !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && + rjsonp.test( data ); + + // Handle iff the expected data type is "jsonp" or we have a parameter to set + if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) { + + // Get callback name, remembering preexisting value associated with it + callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? + s.jsonpCallback() : + s.jsonpCallback; + overwritten = window[ callbackName ]; + + // Insert callback into url or form data + if ( replaceInUrl ) { + s.url = url.replace( rjsonp, "$1" + callbackName ); + } else if ( replaceInData ) { + s.data = data.replace( rjsonp, "$1" + callbackName ); + } else if ( hasCallback ) { + s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; + } + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( !responseContainer ) { + jQuery.error( callbackName + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Install callback + window[ callbackName ] = function() { + responseContainer = arguments; + }; + + // Clean-up function (fires after converters) + jqXHR.always(function() { + // Restore preexisting value + window[ callbackName ] = overwritten; + + // Save back as free + if ( s[ callbackName ] ) { + // make sure that re-using the options doesn't screw things around + s.jsonpCallback = originalSettings.jsonpCallback; + + // save the callback name for future use + oldCallbacks.push( callbackName ); + } + + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( overwritten ) ) { + overwritten( responseContainer[ 0 ] ); + } + + responseContainer = overwritten = undefined; + }); + + // Delegate to script + return "script"; + } +}); +// Install script dataType +jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /javascript|ecmascript/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +}); + +// Handle cache's special case and global +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; + } +}); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; + + return { + + send: function( _, callback ) { + + script = document.createElement( "script" ); + + script.async = "async"; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _, isAbort ) { + + if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( head && script.parentNode ) { + head.removeChild( script ); + } + + // Dereference the script + script = undefined; + + // Callback if not abort + if ( !isAbort ) { + callback( 200, "success" ); + } + } + }; + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + }, + + abort: function() { + if ( script ) { + script.onload( 0, 1 ); + } + } + }; + } +}); +var xhrCallbacks, + // #5280: Internet Explorer will keep connections alive if we don't abort on unload + xhrOnUnloadAbort = window.ActiveXObject ? function() { + // Abort all pending requests + for ( var key in xhrCallbacks ) { + xhrCallbacks[ key ]( 0, 1 ); + } + } : false, + xhrId = 0; + +// Functions to create xhrs +function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch( e ) {} +} + +function createActiveXHR() { + try { + return new window.ActiveXObject( "Microsoft.XMLHTTP" ); + } catch( e ) {} +} + +// Create the request object +// (This is still attached to ajaxSettings for backward compatibility) +jQuery.ajaxSettings.xhr = window.ActiveXObject ? + /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function() { + return !this.isLocal && createStandardXHR() || createActiveXHR(); + } : + // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; + +// Determine support properties +(function( xhr ) { + jQuery.extend( jQuery.support, { + ajax: !!xhr, + cors: !!xhr && ( "withCredentials" in xhr ) + }); +})( jQuery.ajaxSettings.xhr() ); + +// Create transport if the browser can provide an xhr +if ( jQuery.support.ajax ) { + + jQuery.ajaxTransport(function( s ) { + // Cross domain only allowed if supported through XMLHttpRequest + if ( !s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + send: function( headers, complete ) { + + // Get a new xhr + var handle, i, + xhr = s.xhr(); + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open( s.type, s.url, s.async, s.username, s.password ); + } else { + xhr.open( s.type, s.url, s.async ); + } + + // Apply custom fields if provided + if ( s.xhrFields ) { + for ( i in s.xhrFields ) { + xhr[ i ] = s.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( s.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( s.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !s.crossDomain && !headers["X-Requested-With"] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + } catch( _ ) {} + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send( ( s.hasContent && s.data ) || null ); + + // Listener + callback = function( _, isAbort ) { + + var status, + statusText, + responseHeaders, + responses, + xml; + + // Firefox throws exceptions when accessing properties + // of an xhr when a network error occurred + // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) + try { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = undefined; + + // Do not keep as active anymore + if ( handle ) { + xhr.onreadystatechange = jQuery.noop; + if ( xhrOnUnloadAbort ) { + delete xhrCallbacks[ handle ]; + } + } + + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + status = xhr.status; + responseHeaders = xhr.getAllResponseHeaders(); + responses = {}; + xml = xhr.responseXML; + + // Construct response list + if ( xml && xml.documentElement /* #4958 */ ) { + responses.xml = xml; + } + + // When requesting binary data, IE6-9 will throw an exception + // on any attempt to access responseText (#11426) + try { + responses.text = xhr.responseText; + } catch( e ) { + } + + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch( e ) { + // We normalize with Webkit giving an empty statusText + statusText = ""; + } + + // Filter status for non standard behaviors + + // If the request is local and we have data: assume a success + // (success with no data won't get notified, that's the best we + // can do given current implementations) + if ( !status && s.isLocal && !s.crossDomain ) { + status = responses.text ? 200 : 404; + // IE - #1450: sometimes returns 1223 when it should be 204 + } else if ( status === 1223 ) { + status = 204; + } + } + } + } catch( firefoxAccessException ) { + if ( !isAbort ) { + complete( -1, firefoxAccessException ); + } + } + + // Call complete if needed + if ( responses ) { + complete( status, statusText, responses, responseHeaders ); + } + }; + + if ( !s.async ) { + // if we're in sync mode we fire the callback + callback(); + } else if ( xhr.readyState === 4 ) { + // (IE6 & IE7) if it's in cache and has been + // retrieved directly we need to fire the callback + setTimeout( callback, 0 ); + } else { + handle = ++xhrId; + if ( xhrOnUnloadAbort ) { + // Create the active xhrs callbacks list if needed + // and attach the unload handler + if ( !xhrCallbacks ) { + xhrCallbacks = {}; + jQuery( window ).unload( xhrOnUnloadAbort ); + } + // Add to list of active xhrs callbacks + xhrCallbacks[ handle ] = callback; + } + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback(0,1); + } + } + }; + } + }); +} +var fxNow, timerId, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ), + rrun = /queueHooks$/, + animationPrefilters = [ defaultPrefilter ], + tweeners = { + "*": [function( prop, value ) { + var end, unit, + tween = this.createTween( prop, value ), + parts = rfxnum.exec( value ), + target = tween.cur(), + start = +target || 0, + scale = 1, + maxIterations = 20; + + if ( parts ) { + end = +parts[2]; + unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + + // We need to compute starting value + if ( unit !== "px" && start ) { + // Iteratively approximate from a nonzero starting point + // Prefer the current property, because this process will be trivial if it uses the same units + // Fallback to end or a simple constant + start = jQuery.css( tween.elem, prop, true ) || end || 1; + + do { + // If previous iteration zeroed out, double until we get *something* + // Use a string for doubling factor so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + start = start / scale; + jQuery.style( tween.elem, prop, start + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // And breaking the loop if scale is unchanged or perfect, or if we've just had enough + } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); + } + + tween.unit = unit; + tween.start = start; + // If a +=/-= token was provided, we're doing a relative animation + tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end; + } + return tween; + }] + }; + +// Animations created synchronously will run synchronously +function createFxNow() { + setTimeout(function() { + fxNow = undefined; + }, 0 ); + return ( fxNow = jQuery.now() ); +} + +function createTweens( animation, props ) { + jQuery.each( props, function( prop, value ) { + var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( collection[ index ].call( animation, prop, value ) ) { + + // we're done with this property + return; + } + } + }); +} + +function Animation( elem, properties, options ) { + var result, + index = 0, + tweenerIndex = 0, + length = animationPrefilters.length, + deferred = jQuery.Deferred().always( function() { + // don't match elem in the :animated selector + delete tick.elem; + }), + tick = function() { + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ]); + + if ( percent < 1 && length ) { + return remaining; + } else { + deferred.resolveWith( elem, [ animation ] ); + return false; + } + }, + animation = deferred.promise({ + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { specialEasing: {} }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end, easing ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + // if we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + + for ( ; index < length ; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // resolve when we played the last frame + // otherwise, reject + if ( gotoEnd ) { + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + }), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length ; index++ ) { + result = animationPrefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + return result; + } + } + + createTweens( animation, props ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + jQuery.fx.timer( + jQuery.extend( tick, { + anim: animation, + queue: animation.opts.queue, + elem: elem + }) + ); + + // attach callbacks from options + return animation.progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( jQuery.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // not quite $.extend, this wont overwrite keys already present. + // also - reusing 'index' from above because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.split(" "); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length ; index++ ) { + prop = props[ index ]; + tweeners[ prop ] = tweeners[ prop ] || []; + tweeners[ prop ].unshift( callback ); + } + }, + + prefilter: function( callback, prepend ) { + if ( prepend ) { + animationPrefilters.unshift( callback ); + } else { + animationPrefilters.push( callback ); + } + } +}); + +function defaultPrefilter( elem, props, opts ) { + var index, prop, value, length, dataShow, toggle, tween, hooks, oldfire, + anim = this, + style = elem.style, + orig = {}, + handled = [], + hidden = elem.nodeType && isHidden( elem ); + + // handle queue: false promises + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always(function() { + // doing this makes sure that the complete handler will be called + // before this completes + anim.always(function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + }); + }); + } + + // height/width overflow pass + if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height animated + if ( jQuery.css( elem, "display" ) === "inline" && + jQuery.css( elem, "float" ) === "none" ) { + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) { + style.display = "inline-block"; + + } else { + style.zoom = 1; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + if ( !jQuery.support.shrinkWrapBlocks ) { + anim.done(function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + }); + } + } + + + // show/hide pass + for ( index in props ) { + value = props[ index ]; + if ( rfxtypes.exec( value ) ) { + delete props[ index ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + continue; + } + handled.push( index ); + } + } + + length = handled.length; + if ( length ) { + dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} ); + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + + // store state if its toggle - enables .stop().toggle() to "reverse" + if ( toggle ) { + dataShow.hidden = !hidden; + } + if ( hidden ) { + jQuery( elem ).show(); + } else { + anim.done(function() { + jQuery( elem ).hide(); + }); + } + anim.done(function() { + var prop; + jQuery.removeData( elem, "fxshow", true ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + }); + for ( index = 0 ; index < length ; index++ ) { + prop = handled[ index ]; + tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 ); + orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop ); + + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = tween.start; + if ( hidden ) { + tween.end = tween.start; + tween.start = prop === "width" || prop === "height" ? 1 : 0; + } + } + } + } +} + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || "swing"; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + if ( tween.elem[ tween.prop ] != null && + (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) { + return tween.elem[ tween.prop ]; + } + + // passing any value as a 4th parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails + // so, simple values such as "10px" are parsed to Float. + // complex values such as "rotate(1rad)" are returned as is. + result = jQuery.css( tween.elem, tween.prop, false, "" ); + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + // use step hook for back compat - use cssHook if its there - use .style if its + // available and use plain properties where available + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Remove in 2.0 - this supports IE8's panic based approach +// to setting things on disconnected nodes + +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.each([ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" || + // special check for .toggle( handler, handler, ... ) + ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +}); + +jQuery.fn.extend({ + fadeTo: function( speed, to, easing, callback ) { + + // show any hidden elements after setting opacity to 0 + return this.filter( isHidden ).css( "opacity", 0 ).show() + + // animate to the value specified + .end().animate({ opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations resolve immediately + if ( empty ) { + anim.stop( true ); + } + }; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each(function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = jQuery._data( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // start the next in the queue if the last step wasn't forced + // timers currently will call their complete callbacks, which will dequeue + // but only if they were gotoEnd + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + }); + } +}); + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + attrs = { height: type }, + i = 0; + + // if we include width, step value is 1 to do all cssExpand values, + // if we don't include width, step value is 2 to skip over Left and Right + includeWidth = includeWidth? 1 : 0; + for( ; i < 4 ; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show"), + slideUp: genFx("hide"), + slideToggle: genFx("toggle"), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; + + // normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p*Math.PI ) / 2; + } +}; + +jQuery.timers = []; +jQuery.fx = Tween.prototype.init; +jQuery.fx.tick = function() { + var timer, + timers = jQuery.timers, + i = 0; + + fxNow = jQuery.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + if ( timer() && jQuery.timers.push( timer ) && !timerId ) { + timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval ); + } +}; + +jQuery.fx.interval = 13; + +jQuery.fx.stop = function() { + clearInterval( timerId ); + timerId = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + // Default speed + _default: 400 +}; + +// Back Compat <1.8 extension point +jQuery.fx.step = {}; + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} +var rroot = /^(?:body|html)$/i; + +jQuery.fn.offset = function( options ) { + if ( arguments.length ) { + return options === undefined ? + this : + this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + var docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, + box = { top: 0, left: 0 }, + elem = this[ 0 ], + doc = elem && elem.ownerDocument; + + if ( !doc ) { + return; + } + + if ( (body = doc.body) === elem ) { + return jQuery.offset.bodyOffset( elem ); + } + + docElem = doc.documentElement; + + // Make sure it's not a disconnected DOM node + if ( !jQuery.contains( docElem, elem ) ) { + return box; + } + + // If we don't have gBCR, just use 0,0 rather than error + // BlackBerry 5, iOS 3 (original iPhone) + if ( typeof elem.getBoundingClientRect !== "undefined" ) { + box = elem.getBoundingClientRect(); + } + win = getWindow( doc ); + clientTop = docElem.clientTop || body.clientTop || 0; + clientLeft = docElem.clientLeft || body.clientLeft || 0; + scrollTop = win.pageYOffset || docElem.scrollTop; + scrollLeft = win.pageXOffset || docElem.scrollLeft; + return { + top: box.top + scrollTop - clientTop, + left: box.left + scrollLeft - clientLeft + }; +}; + +jQuery.offset = { + + bodyOffset: function( body ) { + var top = body.offsetTop, + left = body.offsetLeft; + + if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.css(body, "marginTop") ) || 0; + left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is either absolute or fixed + if ( calculatePosition ) { + curPosition = curElem.position(); + curTop = curPosition.top; + curLeft = curPosition.left; + } else { + curTop = parseFloat( curCSSTop ) || 0; + curLeft = parseFloat( curCSSLeft ) || 0; + } + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if ( options.top != null ) { + props.top = ( options.top - curOffset.top ) + curTop; + } + if ( options.left != null ) { + props.left = ( options.left - curOffset.left ) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + + position: function() { + if ( !this[0] ) { + return; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; + offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; + parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || document.body; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) { + var top = /Y/.test( prop ); + + jQuery.fn[ method ] = function( val ) { + return jQuery.access( this, function( elem, method, val ) { + var win = getWindow( elem ); + + if ( val === undefined ) { + return win ? (prop in win) ? win[ prop ] : + win.document.documentElement[ method ] : + elem[ method ]; + } + + if ( win ) { + win.scrollTo( + !top ? val : jQuery( win ).scrollLeft(), + top ? val : jQuery( win ).scrollTop() + ); + + } else { + elem[ method ] = val; + } + }, method, val, arguments.length, null ); + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} +// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods +jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { + jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { + // margin is only for outerHeight, outerWidth + jQuery.fn[ funcName ] = function( margin, value ) { + var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), + extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); + + return jQuery.access( this, function( elem, type, value ) { + var doc; + + if ( jQuery.isWindow( elem ) ) { + // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there + // isn't a whole lot we can do. See pull request at this URL for discussion: + // https://github.com/jquery/jquery/pull/764 + return elem.document.documentElement[ "client" + name ]; + } + + // Get document width or height + if ( elem.nodeType === 9 ) { + doc = elem.documentElement; + + // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest + // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it. + return Math.max( + elem.body[ "scroll" + name ], doc[ "scroll" + name ], + elem.body[ "offset" + name ], doc[ "offset" + name ], + doc[ "client" + name ] + ); + } + + return value === undefined ? + // Get width or height on the element, requesting but not forcing parseFloat + jQuery.css( elem, type, value, extra ) : + + // Set width or height on the element + jQuery.style( elem, type, value, extra ); + }, type, chainable ? margin : undefined, chainable, null ); + }; + }); +}); +// Expose jQuery to the global object +window.jQuery = window.$ = jQuery; + +// Expose jQuery as an AMD module, but only for AMD loaders that +// understand the issues with loading multiple versions of jQuery +// in a page that all might call define(). The loader will indicate +// they have special allowances for multiple jQuery versions by +// specifying define.amd.jQuery = true. Register as a named module, +// since jQuery can be concatenated with other files that may use define, +// but not use a proper concatenation script that understands anonymous +// AMD modules. A named AMD is safest and most robust way to register. +// Lowercase jquery is used because AMD module names are derived from +// file names, and jQuery is normally delivered in a lowercase file name. +// Do this after creating the global so that if an AMD module wants to call +// noConflict to hide this version of jQuery, it will work. +if ( typeof define === "function" && define.amd && define.amd.jQuery ) { + define( "jquery", [], function () { return jQuery; } ); +} + +})( window ); diff --git a/Web Server/flot/jquery.min.js b/Web Server/flot/jquery.min.js new file mode 100644 index 0000000..ee48790 --- /dev/null +++ b/Web Server/flot/jquery.min.js @@ -0,0 +1,5 @@ +(function(window,undefined){var rootjQuery,readyList,document=window.document,location=window.location,navigator=window.navigator,_jQuery=window.jQuery,_$=window.$,core_push=Array.prototype.push,core_slice=Array.prototype.slice,core_indexOf=Array.prototype.indexOf,core_toString=Object.prototype.toString,core_hasOwn=Object.prototype.hasOwnProperty,core_trim=String.prototype.trim,jQuery=function(selector,context){return new jQuery.fn.init(selector,context,rootjQuery)},core_pnum=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,core_rnotwhite=/\S/,core_rspace=/\s+/,rtrim=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,rquickExpr=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,rsingleTag=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,rvalidchars=/^[\],:{}\s]*$/,rvalidbraces=/(?:^|:|,)(?:\s*\[)+/g,rvalidescape=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,rvalidtokens=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,rmsPrefix=/^-ms-/,rdashAlpha=/-([\da-z])/gi,fcamelCase=function(all,letter){return(letter+"").toUpperCase()},DOMContentLoaded=function(){if(document.addEventListener){document.removeEventListener("DOMContentLoaded",DOMContentLoaded,false);jQuery.ready()}else if(document.readyState==="complete"){document.detachEvent("onreadystatechange",DOMContentLoaded);jQuery.ready()}},class2type={};jQuery.fn=jQuery.prototype={constructor:jQuery,init:function(selector,context,rootjQuery){var match,elem,ret,doc;if(!selector){return this}if(selector.nodeType){this.context=this[0]=selector;this.length=1;return this}if(typeof selector==="string"){if(selector.charAt(0)==="<"&&selector.charAt(selector.length-1)===">"&&selector.length>=3){match=[null,selector,null]}else{match=rquickExpr.exec(selector)}if(match&&(match[1]||!context)){if(match[1]){context=context instanceof jQuery?context[0]:context;doc=context&&context.nodeType?context.ownerDocument||context:document;selector=jQuery.parseHTML(match[1],doc,true);if(rsingleTag.test(match[1])&&jQuery.isPlainObject(context)){this.attr.call(selector,context,true)}return jQuery.merge(this,selector)}else{elem=document.getElementById(match[2]);if(elem&&elem.parentNode){if(elem.id!==match[2]){return rootjQuery.find(selector)}this.length=1;this[0]=elem}this.context=document;this.selector=selector;return this}}else if(!context||context.jquery){return(context||rootjQuery).find(selector)}else{return this.constructor(context).find(selector)}}else if(jQuery.isFunction(selector)){return rootjQuery.ready(selector)}if(selector.selector!==undefined){this.selector=selector.selector;this.context=selector.context}return jQuery.makeArray(selector,this)},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return core_slice.call(this)},get:function(num){return num==null?this.toArray():num<0?this[this.length+num]:this[num]},pushStack:function(elems,name,selector){var ret=jQuery.merge(this.constructor(),elems);ret.prevObject=this;ret.context=this.context;if(name==="find"){ret.selector=this.selector+(this.selector?" ":"")+selector}else if(name){ret.selector=this.selector+"."+name+"("+selector+")"}return ret},each:function(callback,args){return jQuery.each(this,callback,args)},ready:function(fn){jQuery.ready.promise().done(fn);return this},eq:function(i){i=+i;return i===-1?this.slice(i):this.slice(i,i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(core_slice.apply(this,arguments),"slice",core_slice.call(arguments).join(","))},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem)}))},end:function(){return this.prevObject||this.constructor(null)},push:core_push,sort:[].sort,splice:[].splice};jQuery.fn.init.prototype=jQuery.fn;jQuery.extend=jQuery.fn.extend=function(){var options,name,src,copy,copyIsArray,clone,target=arguments[0]||{},i=1,length=arguments.length,deep=false;if(typeof target==="boolean"){deep=target;target=arguments[1]||{};i=2}if(typeof target!=="object"&&!jQuery.isFunction(target)){target={}}if(length===i){target=this;--i}for(;i0){return}readyList.resolveWith(document,[jQuery]);if(jQuery.fn.trigger){jQuery(document).trigger("ready").off("ready")}},isFunction:function(obj){return jQuery.type(obj)==="function"},isArray:Array.isArray||function(obj){return jQuery.type(obj)==="array"},isWindow:function(obj){return obj!=null&&obj==obj.window},isNumeric:function(obj){return!isNaN(parseFloat(obj))&&isFinite(obj)},type:function(obj){return obj==null?String(obj):class2type[core_toString.call(obj)]||"object"},isPlainObject:function(obj){if(!obj||jQuery.type(obj)!=="object"||obj.nodeType||jQuery.isWindow(obj)){return false}try{if(obj.constructor&&!core_hasOwn.call(obj,"constructor")&&!core_hasOwn.call(obj.constructor.prototype,"isPrototypeOf")){return false}}catch(e){return false}var key;for(key in obj){}return key===undefined||core_hasOwn.call(obj,key)},isEmptyObject:function(obj){var name;for(name in obj){return false}return true},error:function(msg){throw new Error(msg)},parseHTML:function(data,context,scripts){var parsed;if(!data||typeof data!=="string"){return null}if(typeof context==="boolean"){scripts=context;context=0}context=context||document;if(parsed=rsingleTag.exec(data)){return[context.createElement(parsed[1])]}parsed=jQuery.buildFragment([data],context,scripts?null:[]);return jQuery.merge([],(parsed.cacheable?jQuery.clone(parsed.fragment):parsed.fragment).childNodes)},parseJSON:function(data){if(!data||typeof data!=="string"){return null}data=jQuery.trim(data);if(window.JSON&&window.JSON.parse){return window.JSON.parse(data)}if(rvalidchars.test(data.replace(rvalidescape,"@").replace(rvalidtokens,"]").replace(rvalidbraces,""))){return new Function("return "+data)()}jQuery.error("Invalid JSON: "+data)},parseXML:function(data){var xml,tmp;if(!data||typeof data!=="string"){return null}try{if(window.DOMParser){tmp=new DOMParser;xml=tmp.parseFromString(data,"text/xml")}else{xml=new ActiveXObject("Microsoft.XMLDOM");xml.async="false";xml.loadXML(data)}}catch(e){xml=undefined}if(!xml||!xml.documentElement||xml.getElementsByTagName("parsererror").length){jQuery.error("Invalid XML: "+data)}return xml},noop:function(){},globalEval:function(data){if(data&&core_rnotwhite.test(data)){(window.execScript||function(data){window["eval"].call(window,data)})(data)}},camelCase:function(string){return string.replace(rmsPrefix,"ms-").replace(rdashAlpha,fcamelCase)},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toLowerCase()===name.toLowerCase()},each:function(obj,callback,args){var name,i=0,length=obj.length,isObj=length===undefined||jQuery.isFunction(obj);if(args){if(isObj){for(name in obj){if(callback.apply(obj[name],args)===false){break}}}else{for(;i0&&elems[0]&&elems[length-1]||length===0||jQuery.isArray(elems));if(isArray){for(;i-1){list.splice(index,1);if(firing){if(index<=firingLength){firingLength--}if(index<=firingIndex){firingIndex--}}}})}return this},has:function(fn){return jQuery.inArray(fn,list)>-1},empty:function(){list=[];return this},disable:function(){list=stack=memory=undefined;return this},disabled:function(){return!list},lock:function(){stack=undefined;if(!memory){self.disable()}return this},locked:function(){return!stack},fireWith:function(context,args){args=args||[];args=[context,args.slice?args.slice():args];if(list&&(!fired||stack)){if(firing){stack.push(args)}else{fire(args)}}return this},fire:function(){self.fireWith(this,arguments);return this},fired:function(){return!!fired}};return self};jQuery.extend({Deferred:function(func){var tuples=[["resolve","done",jQuery.Callbacks("once memory"),"resolved"],["reject","fail",jQuery.Callbacks("once memory"),"rejected"],["notify","progress",jQuery.Callbacks("memory")]],state="pending",promise={state:function(){return state},always:function(){deferred.done(arguments).fail(arguments);return this},then:function(){var fns=arguments;return jQuery.Deferred(function(newDefer){jQuery.each(tuples,function(i,tuple){var action=tuple[0],fn=fns[i];deferred[tuple[1]](jQuery.isFunction(fn)?function(){var returned=fn.apply(this,arguments);if(returned&&jQuery.isFunction(returned.promise)){returned.promise().done(newDefer.resolve).fail(newDefer.reject).progress(newDefer.notify)}else{newDefer[action+"With"](this===deferred?newDefer:this,[returned])}}:newDefer[action])});fns=null}).promise()},promise:function(obj){return obj!=null?jQuery.extend(obj,promise):promise}},deferred={};promise.pipe=promise.then;jQuery.each(tuples,function(i,tuple){var list=tuple[2],stateString=tuple[3];promise[tuple[1]]=list.add;if(stateString){list.add(function(){state=stateString},tuples[i^1][2].disable,tuples[2][2].lock)}deferred[tuple[0]]=list.fire;deferred[tuple[0]+"With"]=list.fireWith});promise.promise(deferred);if(func){func.call(deferred,deferred)}return deferred},when:function(subordinate){var i=0,resolveValues=core_slice.call(arguments),length=resolveValues.length,remaining=length!==1||subordinate&&jQuery.isFunction(subordinate.promise)?length:0,deferred=remaining===1?subordinate:jQuery.Deferred(),updateFunc=function(i,contexts,values){return function(value){contexts[i]=this;values[i]=arguments.length>1?core_slice.call(arguments):value;if(values===progressValues){deferred.notifyWith(contexts,values)}else if(!--remaining){deferred.resolveWith(contexts,values)}}},progressValues,progressContexts,resolveContexts;if(length>1){progressValues=new Array(length);progressContexts=new Array(length);resolveContexts=new Array(length);for(;i
a";all=div.getElementsByTagName("*");a=div.getElementsByTagName("a")[0];if(!all||!a||!all.length){return{}}select=document.createElement("select");opt=select.appendChild(document.createElement("option"));input=div.getElementsByTagName("input")[0];a.style.cssText="top:1px;float:left;opacity:.5";support={leadingWhitespace:div.firstChild.nodeType===3,tbody:!div.getElementsByTagName("tbody").length,htmlSerialize:!!div.getElementsByTagName("link").length,style:/top/.test(a.getAttribute("style")),hrefNormalized:a.getAttribute("href")==="/a",opacity:/^0.5/.test(a.style.opacity),cssFloat:!!a.style.cssFloat,checkOn:input.value==="on",optSelected:opt.selected,getSetAttribute:div.className!=="t",enctype:!!document.createElement("form").enctype,html5Clone:document.createElement("nav").cloneNode(true).outerHTML!=="<:nav>",boxModel:document.compatMode==="CSS1Compat",submitBubbles:true,changeBubbles:true,focusinBubbles:false,deleteExpando:true,noCloneEvent:true,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableMarginRight:true,boxSizingReliable:true,pixelPosition:false};input.checked=true;support.noCloneChecked=input.cloneNode(true).checked;select.disabled=true;support.optDisabled=!opt.disabled;try{delete div.test}catch(e){support.deleteExpando=false}if(!div.addEventListener&&div.attachEvent&&div.fireEvent){div.attachEvent("onclick",clickFn=function(){support.noCloneEvent=false});div.cloneNode(true).fireEvent("onclick");div.detachEvent("onclick",clickFn)}input=document.createElement("input");input.value="t";input.setAttribute("type","radio");support.radioValue=input.value==="t";input.setAttribute("checked","checked");input.setAttribute("name","t");div.appendChild(input);fragment=document.createDocumentFragment();fragment.appendChild(div.lastChild);support.checkClone=fragment.cloneNode(true).cloneNode(true).lastChild.checked;support.appendChecked=input.checked;fragment.removeChild(input);fragment.appendChild(div);if(div.attachEvent){for(i in{submit:true,change:true,focusin:true}){eventName="on"+i;isSupported=eventName in div;if(!isSupported){div.setAttribute(eventName,"return;");isSupported=typeof div[eventName]==="function"}support[i+"Bubbles"]=isSupported}}jQuery(function(){var container,div,tds,marginDiv,divReset="padding:0;margin:0;border:0;display:block;overflow:hidden;",body=document.getElementsByTagName("body")[0];if(!body){return}container=document.createElement("div");container.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";body.insertBefore(container,body.firstChild);div=document.createElement("div");container.appendChild(div);div.innerHTML="
t
";tds=div.getElementsByTagName("td");tds[0].style.cssText="padding:0;margin:0;border:0;display:none";isSupported=tds[0].offsetHeight===0;tds[0].style.display="";tds[1].style.display="none";support.reliableHiddenOffsets=isSupported&&tds[0].offsetHeight===0;div.innerHTML="";div.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";support.boxSizing=div.offsetWidth===4;support.doesNotIncludeMarginInBodyOffset=body.offsetTop!==1;if(window.getComputedStyle){support.pixelPosition=(window.getComputedStyle(div,null)||{}).top!=="1%";support.boxSizingReliable=(window.getComputedStyle(div,null)||{width:"4px"}).width==="4px";marginDiv=document.createElement("div");marginDiv.style.cssText=div.style.cssText=divReset;marginDiv.style.marginRight=marginDiv.style.width="0";div.style.width="1px";div.appendChild(marginDiv);support.reliableMarginRight=!parseFloat((window.getComputedStyle(marginDiv,null)||{}).marginRight)}if(typeof div.style.zoom!=="undefined"){div.innerHTML="";div.style.cssText=divReset+"width:1px;padding:1px;display:inline;zoom:1";support.inlineBlockNeedsLayout=div.offsetWidth===3;div.style.display="block";div.style.overflow="visible";div.innerHTML="
";div.firstChild.style.width="5px";support.shrinkWrapBlocks=div.offsetWidth!==3;container.style.zoom=1}body.removeChild(container);container=div=tds=marginDiv=null});fragment.removeChild(div);all=a=select=opt=input=fragment=div=null;return support}();var rbrace=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,rmultiDash=/([A-Z])/g;jQuery.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(jQuery.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},hasData:function(elem){elem=elem.nodeType?jQuery.cache[elem[jQuery.expando]]:elem[jQuery.expando];return!!elem&&!isEmptyDataObject(elem)},data:function(elem,name,data,pvt){if(!jQuery.acceptData(elem)){return}var thisCache,ret,internalKey=jQuery.expando,getByName=typeof name==="string",isNode=elem.nodeType,cache=isNode?jQuery.cache:elem,id=isNode?elem[internalKey]:elem[internalKey]&&internalKey;if((!id||!cache[id]||!pvt&&!cache[id].data)&&getByName&&data===undefined){return}if(!id){if(isNode){elem[internalKey]=id=jQuery.deletedIds.pop()||jQuery.guid++}else{id=internalKey}}if(!cache[id]){cache[id]={};if(!isNode){cache[id].toJSON=jQuery.noop}}if(typeof name==="object"||typeof name==="function"){if(pvt){cache[id]=jQuery.extend(cache[id],name)}else{cache[id].data=jQuery.extend(cache[id].data,name)}}thisCache=cache[id];if(!pvt){if(!thisCache.data){thisCache.data={}}thisCache=thisCache.data}if(data!==undefined){thisCache[jQuery.camelCase(name)]=data}if(getByName){ret=thisCache[name];if(ret==null){ret=thisCache[jQuery.camelCase(name)]}}else{ret=thisCache}return ret},removeData:function(elem,name,pvt){if(!jQuery.acceptData(elem)){return}var thisCache,i,l,isNode=elem.nodeType,cache=isNode?jQuery.cache:elem,id=isNode?elem[jQuery.expando]:jQuery.expando;if(!cache[id]){return}if(name){thisCache=pvt?cache[id]:cache[id].data;if(thisCache){if(!jQuery.isArray(name)){if(name in thisCache){name=[name]}else{name=jQuery.camelCase(name);if(name in thisCache){name=[name]}else{name=name.split(" ")}}}for(i=0,l=name.length;i1,null,false)},removeData:function(key){return this.each(function(){jQuery.removeData(this,key)})}});function dataAttr(elem,key,data){if(data===undefined&&elem.nodeType===1){var name="data-"+key.replace(rmultiDash,"-$1").toLowerCase();data=elem.getAttribute(name);if(typeof data==="string"){try{data=data==="true"?true:data==="false"?false:data==="null"?null:+data+""===data?+data:rbrace.test(data)?jQuery.parseJSON(data):data}catch(e){}jQuery.data(elem,key,data)}else{data=undefined}}return data}function isEmptyDataObject(obj){var name;for(name in obj){if(name==="data"&&jQuery.isEmptyObject(obj[name])){continue}if(name!=="toJSON"){return false}}return true}jQuery.extend({queue:function(elem,type,data){var queue;if(elem){type=(type||"fx")+"queue";queue=jQuery._data(elem,type);if(data){if(!queue||jQuery.isArray(data)){queue=jQuery._data(elem,type,jQuery.makeArray(data))}else{queue.push(data)}}return queue||[]}},dequeue:function(elem,type){type=type||"fx";var queue=jQuery.queue(elem,type),startLength=queue.length,fn=queue.shift(),hooks=jQuery._queueHooks(elem,type),next=function(){jQuery.dequeue(elem,type)};if(fn==="inprogress"){fn=queue.shift();startLength--}if(fn){if(type==="fx"){queue.unshift("inprogress")}delete hooks.stop;fn.call(elem,next,hooks)}if(!startLength&&hooks){hooks.empty.fire()}},_queueHooks:function(elem,type){var key=type+"queueHooks";return jQuery._data(elem,key)||jQuery._data(elem,key,{empty:jQuery.Callbacks("once memory").add(function(){jQuery.removeData(elem,type+"queue",true);jQuery.removeData(elem,key,true)})})}});jQuery.fn.extend({queue:function(type,data){var setter=2;if(typeof type!=="string"){data=type;type="fx";setter--}if(arguments.length1)},removeAttr:function(name){return this.each(function(){jQuery.removeAttr(this,name)})},prop:function(name,value){return jQuery.access(this,jQuery.prop,name,value,arguments.length>1)},removeProp:function(name){name=jQuery.propFix[name]||name;return this.each(function(){try{this[name]=undefined;delete this[name]}catch(e){}})},addClass:function(value){var classNames,i,l,elem,setClass,c,cl;if(jQuery.isFunction(value)){return this.each(function(j){jQuery(this).addClass(value.call(this,j,this.className))})}if(value&&typeof value==="string"){classNames=value.split(core_rspace);for(i=0,l=this.length;i=0){className=className.replace(" "+removes[c]+" "," ")}}elem.className=value?jQuery.trim(className):""}}}return this},toggleClass:function(value,stateVal){var type=typeof value,isBool=typeof stateVal==="boolean";if(jQuery.isFunction(value)){return this.each(function(i){jQuery(this).toggleClass(value.call(this,i,this.className,stateVal),stateVal)})}return this.each(function(){if(type==="string"){var className,i=0,self=jQuery(this),state=stateVal,classNames=value.split(core_rspace);while(className=classNames[i++]){state=isBool?state:!self.hasClass(className);self[state?"addClass":"removeClass"](className)}}else if(type==="undefined"||type==="boolean"){if(this.className){jQuery._data(this,"__className__",this.className)}this.className=this.className||value===false?"":jQuery._data(this,"__className__")||""}})},hasClass:function(selector){var className=" "+selector+" ",i=0,l=this.length;for(;i=0){return true}}return false},val:function(value){var hooks,ret,isFunction,elem=this[0];if(!arguments.length){if(elem){hooks=jQuery.valHooks[elem.type]||jQuery.valHooks[elem.nodeName.toLowerCase()];if(hooks&&"get"in hooks&&(ret=hooks.get(elem,"value"))!==undefined){return ret}ret=elem.value;return typeof ret==="string"?ret.replace(rreturn,""):ret==null?"":ret}return}isFunction=jQuery.isFunction(value);return this.each(function(i){var val,self=jQuery(this);if(this.nodeType!==1){return}if(isFunction){val=value.call(this,i,self.val())}else{val=value}if(val==null){val=""}else if(typeof val==="number"){val+=""}else if(jQuery.isArray(val)){val=jQuery.map(val,function(value){return value==null?"":value+""})}hooks=jQuery.valHooks[this.type]||jQuery.valHooks[this.nodeName.toLowerCase()];if(!hooks||!("set"in hooks)||hooks.set(this,val,"value")===undefined){this.value=val}})}});jQuery.extend({valHooks:{option:{get:function(elem){var val=elem.attributes.value;return!val||val.specified?elem.value:elem.text}},select:{get:function(elem){var value,option,options=elem.options,index=elem.selectedIndex,one=elem.type==="select-one"||index<0,values=one?null:[],max=one?index+1:options.length,i=index<0?max:one?index:0;for(;i=0});if(!values.length){elem.selectedIndex=-1}return values}}},attrFn:{},attr:function(elem,name,value,pass){var ret,hooks,notxml,nType=elem.nodeType;if(!elem||nType===3||nType===8||nType===2){return}if(pass&&jQuery.isFunction(jQuery.fn[name])){return jQuery(elem)[name](value)}if(typeof elem.getAttribute==="undefined"){return jQuery.prop(elem,name,value)}notxml=nType!==1||!jQuery.isXMLDoc(elem);if(notxml){name=name.toLowerCase();hooks=jQuery.attrHooks[name]||(rboolean.test(name)?boolHook:nodeHook)}if(value!==undefined){if(value===null){jQuery.removeAttr(elem,name);return}else if(hooks&&"set"in hooks&¬xml&&(ret=hooks.set(elem,value,name))!==undefined){return ret}else{elem.setAttribute(name,value+"");return value}}else if(hooks&&"get"in hooks&¬xml&&(ret=hooks.get(elem,name))!==null){return ret}else{ret=elem.getAttribute(name);return ret===null?undefined:ret}},removeAttr:function(elem,value){var propName,attrNames,name,isBool,i=0;if(value&&elem.nodeType===1){attrNames=value.split(core_rspace);for(;i=0}}})});var rformElems=/^(?:textarea|input|select)$/i,rtypenamespace=/^([^\.]*|)(?:\.(.+)|)$/,rhoverHack=/(?:^|\s)hover(\.\S+|)\b/,rkeyEvent=/^key/,rmouseEvent=/^(?:mouse|contextmenu)|click/,rfocusMorph=/^(?:focusinfocus|focusoutblur)$/,hoverHack=function(events){return jQuery.event.special.hover?events:events.replace(rhoverHack,"mouseenter$1 mouseleave$1")};jQuery.event={add:function(elem,types,handler,data,selector){var elemData,eventHandle,events,t,tns,type,namespaces,handleObj,handleObjIn,handlers,special;if(elem.nodeType===3||elem.nodeType===8||!types||!handler||!(elemData=jQuery._data(elem))){return}if(handler.handler){handleObjIn=handler;handler=handleObjIn.handler;selector=handleObjIn.selector}if(!handler.guid){handler.guid=jQuery.guid++}events=elemData.events;if(!events){elemData.events=events={}}eventHandle=elemData.handle;if(!eventHandle){elemData.handle=eventHandle=function(e){return typeof jQuery!=="undefined"&&(!e||jQuery.event.triggered!==e.type)?jQuery.event.dispatch.apply(eventHandle.elem,arguments):undefined};eventHandle.elem=elem}types=jQuery.trim(hoverHack(types)).split(" ");for(t=0;t=0){type=type.slice(0,-1);exclusive=true}if(type.indexOf(".")>=0){namespaces=type.split(".");type=namespaces.shift();namespaces.sort()}if((!elem||jQuery.event.customEvent[type])&&!jQuery.event.global[type]){return}event=typeof event==="object"?event[jQuery.expando]?event:new jQuery.Event(type,event):new jQuery.Event(type);event.type=type;event.isTrigger=true;event.exclusive=exclusive;event.namespace=namespaces.join(".");event.namespace_re=event.namespace?new RegExp("(^|\\.)"+namespaces.join("\\.(?:.*\\.|)")+"(\\.|$)"):null;ontype=type.indexOf(":")<0?"on"+type:"";if(!elem){cache=jQuery.cache;for(i in cache){if(cache[i].events&&cache[i].events[type]){jQuery.event.trigger(event,data,cache[i].handle.elem,true)}}return}event.result=undefined;if(!event.target){event.target=elem}data=data!=null?jQuery.makeArray(data):[];data.unshift(event);special=jQuery.event.special[type]||{};if(special.trigger&&special.trigger.apply(elem,data)===false){return}eventPath=[[elem,special.bindType||type]];if(!onlyHandlers&&!special.noBubble&&!jQuery.isWindow(elem)){bubbleType=special.delegateType||type;cur=rfocusMorph.test(bubbleType+type)?elem:elem.parentNode;for(old=elem;cur;cur=cur.parentNode){eventPath.push([cur,bubbleType]);old=cur}if(old===(elem.ownerDocument||document)){eventPath.push([old.defaultView||old.parentWindow||window,bubbleType])}}for(i=0;i=0:jQuery.find(sel,this,null,[cur]).length}if(selMatch[sel]){matches.push(handleObj)}}if(matches.length){handlerQueue.push({elem:cur,matches:matches})}}}}if(handlers.length>delegateCount){handlerQueue.push({elem:this,matches:handlers.slice(delegateCount)})}for(i=0;i0?this.on(name,null,data,fn):this.trigger(name)};if(rkeyEvent.test(name)){jQuery.event.fixHooks[name]=jQuery.event.keyHooks}if(rmouseEvent.test(name)){jQuery.event.fixHooks[name]=jQuery.event.mouseHooks}});(function(window,undefined){var cachedruns,assertGetIdNotName,Expr,getText,isXML,contains,compile,sortOrder,hasDuplicate,outermostContext,baseHasDuplicate=true,strundefined="undefined",expando=("sizcache"+Math.random()).replace(".",""),Token=String,document=window.document,docElem=document.documentElement,dirruns=0,done=0,pop=[].pop,push=[].push,slice=[].slice,indexOf=[].indexOf||function(elem){var i=0,len=this.length;for(;iExpr.cacheLength){delete cache[keys.shift()]}return cache[key+" "]=value},cache)},classCache=createCache(),tokenCache=createCache(),compilerCache=createCache(),whitespace="[\\x20\\t\\r\\n\\f]",characterEncoding="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",identifier=characterEncoding.replace("w","w#"),operators="([*^$|!~]?=)",attributes="\\["+whitespace+"*("+characterEncoding+")"+whitespace+"*(?:"+operators+whitespace+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+identifier+")|)|)"+whitespace+"*\\]",pseudos=":("+characterEncoding+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+attributes+")|[^:]|\\\\.)*|.*))\\)|)",pos=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+whitespace+"*((?:-\\d)?\\d*)"+whitespace+"*\\)|)(?=[^-]|$)",rtrim=new RegExp("^"+whitespace+"+|((?:^|[^\\\\])(?:\\\\.)*)"+whitespace+"+$","g"),rcomma=new RegExp("^"+whitespace+"*,"+whitespace+"*"),rcombinators=new RegExp("^"+whitespace+"*([\\x20\\t\\r\\n\\f>+~])"+whitespace+"*"),rpseudo=new RegExp(pseudos),rquickExpr=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,rnot=/^:not/,rsibling=/[\x20\t\r\n\f]*[+~]/,rendsWithNot=/:not\($/,rheader=/h\d/i,rinputs=/input|select|textarea|button/i,rbackslash=/\\(?!\\)/g,matchExpr={ID:new RegExp("^#("+characterEncoding+")"),CLASS:new RegExp("^\\.("+characterEncoding+")"),NAME:new RegExp("^\\[name=['\"]?("+characterEncoding+")['\"]?\\]"),TAG:new RegExp("^("+characterEncoding.replace("w","w*")+")"),ATTR:new RegExp("^"+attributes),PSEUDO:new RegExp("^"+pseudos),POS:new RegExp(pos,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+whitespace+"*(even|odd|(([+-]|)(\\d*)n|)"+whitespace+"*(?:([+-]|)"+whitespace+"*(\\d+)|))"+whitespace+"*\\)|)","i"),needsContext:new RegExp("^"+whitespace+"*[>+~]|"+pos,"i")},assert=function(fn){var div=document.createElement("div");try{return fn(div)}catch(e){return false}finally{div=null}},assertTagNameNoComments=assert(function(div){div.appendChild(document.createComment(""));return!div.getElementsByTagName("*").length}),assertHrefNotNormalized=assert(function(div){div.innerHTML="";return div.firstChild&&typeof div.firstChild.getAttribute!==strundefined&&div.firstChild.getAttribute("href")==="#"}),assertAttributes=assert(function(div){div.innerHTML="";var type=typeof div.lastChild.getAttribute("multiple");return type!=="boolean"&&type!=="string"}),assertUsableClassName=assert(function(div){div.innerHTML="";if(!div.getElementsByClassName||!div.getElementsByClassName("e").length){return false}div.lastChild.className="e";return div.getElementsByClassName("e").length===2}),assertUsableName=assert(function(div){div.id=expando+0;div.innerHTML="
";docElem.insertBefore(div,docElem.firstChild);var pass=document.getElementsByName&&document.getElementsByName(expando).length===2+document.getElementsByName(expando+0).length;assertGetIdNotName=!document.getElementById(expando);docElem.removeChild(div);return pass});try{slice.call(docElem.childNodes,0)[0].nodeType}catch(e){slice=function(i){var elem,results=[];for(;elem=this[i];i++){results.push(elem)}return results}}function Sizzle(selector,context,results,seed){results=results||[];context=context||document;var match,elem,xml,m,nodeType=context.nodeType;if(!selector||typeof selector!=="string"){return results}if(nodeType!==1&&nodeType!==9){return[]}xml=isXML(context);if(!xml&&!seed){if(match=rquickExpr.exec(selector)){if(m=match[1]){if(nodeType===9){elem=context.getElementById(m);if(elem&&elem.parentNode){if(elem.id===m){results.push(elem);return results}}else{return results}}else{if(context.ownerDocument&&(elem=context.ownerDocument.getElementById(m))&&contains(context,elem)&&elem.id===m){results.push(elem);return results}}}else if(match[2]){push.apply(results,slice.call(context.getElementsByTagName(selector),0));return results}else if((m=match[3])&&assertUsableClassName&&context.getElementsByClassName){push.apply(results,slice.call(context.getElementsByClassName(m),0));return results}}}return select(selector.replace(rtrim,"$1"),context,results,seed,xml)}Sizzle.matches=function(expr,elements){return Sizzle(expr,null,null,elements)};Sizzle.matchesSelector=function(elem,expr){return Sizzle(expr,null,null,[elem]).length>0};function createInputPseudo(type){return function(elem){var name=elem.nodeName.toLowerCase();return name==="input"&&elem.type===type}}function createButtonPseudo(type){return function(elem){var name=elem.nodeName.toLowerCase();return(name==="input"||name==="button")&&elem.type===type}}function createPositionalPseudo(fn){return markFunction(function(argument){argument=+argument;return markFunction(function(seed,matches){var j,matchIndexes=fn([],seed.length,argument),i=matchIndexes.length;while(i--){if(seed[j=matchIndexes[i]]){seed[j]=!(matches[j]=seed[j])}}})})}getText=Sizzle.getText=function(elem){var node,ret="",i=0,nodeType=elem.nodeType;if(nodeType){if(nodeType===1||nodeType===9||nodeType===11){if(typeof elem.textContent==="string"){return elem.textContent}else{for(elem=elem.firstChild;elem;elem=elem.nextSibling){ret+=getText(elem)}}}else if(nodeType===3||nodeType===4){return elem.nodeValue}}else{for(;node=elem[i];i++){ret+=getText(node)}}return ret};isXML=Sizzle.isXML=function(elem){var documentElement=elem&&(elem.ownerDocument||elem).documentElement;return documentElement?documentElement.nodeName!=="HTML":false};contains=Sizzle.contains=docElem.contains?function(a,b){var adown=a.nodeType===9?a.documentElement:a,bup=b&&b.parentNode;return a===bup||!!(bup&&bup.nodeType===1&&adown.contains&&adown.contains(bup))}:docElem.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode){if(b===a){return true}}return false};Sizzle.attr=function(elem,name){var val,xml=isXML(elem);if(!xml){name=name.toLowerCase()}if(val=Expr.attrHandle[name]){return val(elem)}if(xml||assertAttributes){return elem.getAttribute(name)}val=elem.getAttributeNode(name);return val?typeof elem[name]==="boolean"?elem[name]?name:null:val.specified?val.value:null:null};Expr=Sizzle.selectors={cacheLength:50,createPseudo:markFunction,match:matchExpr,attrHandle:assertHrefNotNormalized?{}:{href:function(elem){return elem.getAttribute("href",2)},type:function(elem){return elem.getAttribute("type")}},find:{ID:assertGetIdNotName?function(id,context,xml){if(typeof context.getElementById!==strundefined&&!xml){var m=context.getElementById(id);return m&&m.parentNode?[m]:[]}}:function(id,context,xml){if(typeof context.getElementById!==strundefined&&!xml){var m=context.getElementById(id);return m?m.id===id||typeof m.getAttributeNode!==strundefined&&m.getAttributeNode("id").value===id?[m]:undefined:[]}},TAG:assertTagNameNoComments?function(tag,context){if(typeof context.getElementsByTagName!==strundefined){return context.getElementsByTagName(tag)}}:function(tag,context){var results=context.getElementsByTagName(tag);if(tag==="*"){var elem,tmp=[],i=0;for(;elem=results[i];i++){if(elem.nodeType===1){tmp.push(elem)}}return tmp}return results},NAME:assertUsableName&&function(tag,context){if(typeof context.getElementsByName!==strundefined){return context.getElementsByName(name)}},CLASS:assertUsableClassName&&function(className,context,xml){if(typeof context.getElementsByClassName!==strundefined&&!xml){return context.getElementsByClassName(className)}}},relative:{">":{dir:"parentNode",first:true}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:true},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(match){match[1]=match[1].replace(rbackslash,"");match[3]=(match[4]||match[5]||"").replace(rbackslash,"");if(match[2]==="~="){match[3]=" "+match[3]+" "}return match.slice(0,4)},CHILD:function(match){match[1]=match[1].toLowerCase();if(match[1]==="nth"){if(!match[2]){Sizzle.error(match[0])}match[3]=+(match[3]?match[4]+(match[5]||1):2*(match[2]==="even"||match[2]==="odd"));match[4]=+(match[6]+match[7]||match[2]==="odd")}else if(match[2]){Sizzle.error(match[0])}return match},PSEUDO:function(match){var unquoted,excess;if(matchExpr["CHILD"].test(match[0])){return null}if(match[3]){match[2]=match[3]}else if(unquoted=match[4]){if(rpseudo.test(unquoted)&&(excess=tokenize(unquoted,true))&&(excess=unquoted.indexOf(")",unquoted.length-excess)-unquoted.length)){unquoted=unquoted.slice(0,excess);match[0]=match[0].slice(0,excess)}match[2]=unquoted}return match.slice(0,3)}},filter:{ID:assertGetIdNotName?function(id){id=id.replace(rbackslash,"");return function(elem){return elem.getAttribute("id")===id}}:function(id){id=id.replace(rbackslash,"");return function(elem){var node=typeof elem.getAttributeNode!==strundefined&&elem.getAttributeNode("id");return node&&node.value===id}},TAG:function(nodeName){if(nodeName==="*"){return function(){return true}}nodeName=nodeName.replace(rbackslash,"").toLowerCase();return function(elem){return elem.nodeName&&elem.nodeName.toLowerCase()===nodeName}},CLASS:function(className){var pattern=classCache[expando][className+" "];return pattern||(pattern=new RegExp("(^|"+whitespace+")"+className+"("+whitespace+"|$)"))&&classCache(className,function(elem){return pattern.test(elem.className||typeof elem.getAttribute!==strundefined&&elem.getAttribute("class")||"")})},ATTR:function(name,operator,check){return function(elem,context){var result=Sizzle.attr(elem,name);if(result==null){return operator==="!="}if(!operator){return true}result+="";return operator==="="?result===check:operator==="!="?result!==check:operator==="^="?check&&result.indexOf(check)===0:operator==="*="?check&&result.indexOf(check)>-1:operator==="$="?check&&result.substr(result.length-check.length)===check:operator==="~="?(" "+result+" ").indexOf(check)>-1:operator==="|="?result===check||result.substr(0,check.length+1)===check+"-":false}},CHILD:function(type,argument,first,last){if(type==="nth"){return function(elem){var node,diff,parent=elem.parentNode;if(first===1&&last===0){return true}if(parent){diff=0;for(node=parent.firstChild;node;node=node.nextSibling){if(node.nodeType===1){diff++; +if(elem===node){break}}}}diff-=last;return diff===first||diff%first===0&&diff/first>=0}}return function(elem){var node=elem;switch(type){case"only":case"first":while(node=node.previousSibling){if(node.nodeType===1){return false}}if(type==="first"){return true}node=elem;case"last":while(node=node.nextSibling){if(node.nodeType===1){return false}}return true}}},PSEUDO:function(pseudo,argument){var args,fn=Expr.pseudos[pseudo]||Expr.setFilters[pseudo.toLowerCase()]||Sizzle.error("unsupported pseudo: "+pseudo);if(fn[expando]){return fn(argument)}if(fn.length>1){args=[pseudo,pseudo,"",argument];return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase())?markFunction(function(seed,matches){var idx,matched=fn(seed,argument),i=matched.length;while(i--){idx=indexOf.call(seed,matched[i]);seed[idx]=!(matches[idx]=matched[i])}}):function(elem){return fn(elem,0,args)}}return fn}},pseudos:{not:markFunction(function(selector){var input=[],results=[],matcher=compile(selector.replace(rtrim,"$1"));return matcher[expando]?markFunction(function(seed,matches,context,xml){var elem,unmatched=matcher(seed,null,xml,[]),i=seed.length;while(i--){if(elem=unmatched[i]){seed[i]=!(matches[i]=elem)}}}):function(elem,context,xml){input[0]=elem;matcher(input,null,xml,results);return!results.pop()}}),has:markFunction(function(selector){return function(elem){return Sizzle(selector,elem).length>0}}),contains:markFunction(function(text){return function(elem){return(elem.textContent||elem.innerText||getText(elem)).indexOf(text)>-1}}),enabled:function(elem){return elem.disabled===false},disabled:function(elem){return elem.disabled===true},checked:function(elem){var nodeName=elem.nodeName.toLowerCase();return nodeName==="input"&&!!elem.checked||nodeName==="option"&&!!elem.selected},selected:function(elem){if(elem.parentNode){elem.parentNode.selectedIndex}return elem.selected===true},parent:function(elem){return!Expr.pseudos["empty"](elem)},empty:function(elem){var nodeType;elem=elem.firstChild;while(elem){if(elem.nodeName>"@"||(nodeType=elem.nodeType)===3||nodeType===4){return false}elem=elem.nextSibling}return true},header:function(elem){return rheader.test(elem.nodeName)},text:function(elem){var type,attr;return elem.nodeName.toLowerCase()==="input"&&(type=elem.type)==="text"&&((attr=elem.getAttribute("type"))==null||attr.toLowerCase()===type)},radio:createInputPseudo("radio"),checkbox:createInputPseudo("checkbox"),file:createInputPseudo("file"),password:createInputPseudo("password"),image:createInputPseudo("image"),submit:createButtonPseudo("submit"),reset:createButtonPseudo("reset"),button:function(elem){var name=elem.nodeName.toLowerCase();return name==="input"&&elem.type==="button"||name==="button"},input:function(elem){return rinputs.test(elem.nodeName)},focus:function(elem){var doc=elem.ownerDocument;return elem===doc.activeElement&&(!doc.hasFocus||doc.hasFocus())&&!!(elem.type||elem.href||~elem.tabIndex)},active:function(elem){return elem===elem.ownerDocument.activeElement},first:createPositionalPseudo(function(){return[0]}),last:createPositionalPseudo(function(matchIndexes,length){return[length-1]}),eq:createPositionalPseudo(function(matchIndexes,length,argument){return[argument<0?argument+length:argument]}),even:createPositionalPseudo(function(matchIndexes,length){for(var i=0;i=0;){matchIndexes.push(i)}return matchIndexes}),gt:createPositionalPseudo(function(matchIndexes,length,argument){for(var i=argument<0?argument+length:argument;++i1?function(elem,context,xml){var i=matchers.length;while(i--){if(!matchers[i](elem,context,xml)){return false}}return true}:matchers[0]}function condense(unmatched,map,filter,context,xml){var elem,newUnmatched=[],i=0,len=unmatched.length,mapped=map!=null;for(;i-1){seed[temp]=!(results[temp]=elem)}}}}else{matcherOut=condense(matcherOut===results?matcherOut.splice(preexisting,matcherOut.length):matcherOut);if(postFinder){postFinder(null,results,matcherOut,xml)}else{push.apply(results,matcherOut)}}})}function matcherFromTokens(tokens){var checkContext,matcher,j,len=tokens.length,leadingRelative=Expr.relative[tokens[0].type],implicitRelative=leadingRelative||Expr.relative[" "],i=leadingRelative?1:0,matchContext=addCombinator(function(elem){return elem===checkContext},implicitRelative,true),matchAnyContext=addCombinator(function(elem){return indexOf.call(checkContext,elem)>-1},implicitRelative,true),matchers=[function(elem,context,xml){return!leadingRelative&&(xml||context!==outermostContext)||((checkContext=context).nodeType?matchContext(elem,context,xml):matchAnyContext(elem,context,xml))}];for(;i1&&elementMatcher(matchers),i>1&&tokens.slice(0,i-1).join("").replace(rtrim,"$1"),matcher,i0,byElement=elementMatchers.length>0,superMatcher=function(seed,context,xml,results,expandContext){var elem,j,matcher,setMatched=[],matchedCount=0,i="0",unmatched=seed&&[],outermost=expandContext!=null,contextBackup=outermostContext,elems=seed||byElement&&Expr.find["TAG"]("*",expandContext&&context.parentNode||context),dirrunsUnique=dirruns+=contextBackup==null?1:Math.E;if(outermost){outermostContext=context!==document&&context;cachedruns=superMatcher.el}for(;(elem=elems[i])!=null;i++){if(byElement&&elem){for(j=0;matcher=elementMatchers[j];j++){if(matcher(elem,context,xml)){results.push(elem);break}}if(outermost){dirruns=dirrunsUnique;cachedruns=++superMatcher.el}}if(bySet){if(elem=!matcher&&elem){matchedCount--}if(seed){unmatched.push(elem)}}}matchedCount+=i;if(bySet&&i!==matchedCount){for(j=0;matcher=setMatchers[j];j++){matcher(unmatched,setMatched,context,xml)}if(seed){if(matchedCount>0){while(i--){if(!(unmatched[i]||setMatched[i])){setMatched[i]=pop.call(results)}}}setMatched=condense(setMatched)}push.apply(results,setMatched);if(outermost&&!seed&&setMatched.length>0&&matchedCount+setMatchers.length>1){Sizzle.uniqueSort(results)}}if(outermost){dirruns=dirrunsUnique;outermostContext=contextBackup}return unmatched};superMatcher.el=0;return bySet?markFunction(superMatcher):superMatcher}compile=Sizzle.compile=function(selector,group){var i,setMatchers=[],elementMatchers=[],cached=compilerCache[expando][selector+" "];if(!cached){if(!group){group=tokenize(selector)}i=group.length;while(i--){cached=matcherFromTokens(group[i]);if(cached[expando]){setMatchers.push(cached)}else{elementMatchers.push(cached)}}cached=compilerCache(selector,matcherFromGroupMatchers(elementMatchers,setMatchers))}return cached};function multipleContexts(selector,contexts,results){var i=0,len=contexts.length;for(;i2&&(token=tokens[0]).type==="ID"&&context.nodeType===9&&!xml&&Expr.relative[tokens[1].type]){context=Expr.find["ID"](token.matches[0].replace(rbackslash,""),context,xml)[0];if(!context){return results}selector=selector.slice(tokens.shift().length)}for(i=matchExpr["POS"].test(selector)?-1:tokens.length-1;i>=0;i--){token=tokens[i];if(Expr.relative[type=token.type]){break}if(find=Expr.find[type]){if(seed=find(token.matches[0].replace(rbackslash,""),rsibling.test(tokens[0].type)&&context.parentNode||context,xml)){tokens.splice(i,1);selector=seed.length&&tokens.join("");if(!selector){push.apply(results,slice.call(seed,0));return results}break}}}}}compile(selector,match)(seed,context,xml,results,rsibling.test(selector));return results}if(document.querySelectorAll){(function(){var disconnectedMatch,oldSelect=select,rescape=/'|\\/g,rattributeQuotes=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,rbuggyQSA=[":focus"],rbuggyMatches=[":active"],matches=docElem.matchesSelector||docElem.mozMatchesSelector||docElem.webkitMatchesSelector||docElem.oMatchesSelector||docElem.msMatchesSelector;assert(function(div){div.innerHTML="";if(!div.querySelectorAll("[selected]").length){rbuggyQSA.push("\\["+whitespace+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)")}if(!div.querySelectorAll(":checked").length){rbuggyQSA.push(":checked")}});assert(function(div){div.innerHTML="

";if(div.querySelectorAll("[test^='']").length){rbuggyQSA.push("[*^$]="+whitespace+"*(?:\"\"|'')")}div.innerHTML="";if(!div.querySelectorAll(":enabled").length){rbuggyQSA.push(":enabled",":disabled")}});rbuggyQSA=new RegExp(rbuggyQSA.join("|"));select=function(selector,context,results,seed,xml){if(!seed&&!xml&&!rbuggyQSA.test(selector)){var groups,i,old=true,nid=expando,newContext=context,newSelector=context.nodeType===9&&selector;if(context.nodeType===1&&context.nodeName.toLowerCase()!=="object"){groups=tokenize(selector);if(old=context.getAttribute("id")){nid=old.replace(rescape,"\\$&")}else{context.setAttribute("id",nid)}nid="[id='"+nid+"'] ";i=groups.length;while(i--){groups[i]=nid+groups[i].join("")}newContext=rsibling.test(selector)&&context.parentNode||context;newSelector=groups.join(",")}if(newSelector){try{push.apply(results,slice.call(newContext.querySelectorAll(newSelector),0));return results}catch(qsaError){}finally{if(!old){context.removeAttribute("id")}}}}return oldSelect(selector,context,results,seed,xml)};if(matches){assert(function(div){disconnectedMatch=matches.call(div,"div");try{matches.call(div,"[test!='']:sizzle");rbuggyMatches.push("!=",pseudos)}catch(e){}});rbuggyMatches=new RegExp(rbuggyMatches.join("|"));Sizzle.matchesSelector=function(elem,expr){expr=expr.replace(rattributeQuotes,"='$1']");if(!isXML(elem)&&!rbuggyMatches.test(expr)&&!rbuggyQSA.test(expr)){try{var ret=matches.call(elem,expr);if(ret||disconnectedMatch||elem.document&&elem.document.nodeType!==11){return ret}}catch(e){}}return Sizzle(expr,null,null,[elem]).length>0}}})()}Expr.pseudos["nth"]=Expr.pseudos["eq"];function setFilters(){}Expr.filters=setFilters.prototype=Expr.pseudos;Expr.setFilters=new setFilters;Sizzle.attr=jQuery.attr;jQuery.find=Sizzle;jQuery.expr=Sizzle.selectors;jQuery.expr[":"]=jQuery.expr.pseudos;jQuery.unique=Sizzle.uniqueSort;jQuery.text=Sizzle.getText;jQuery.isXMLDoc=Sizzle.isXML;jQuery.contains=Sizzle.contains})(window);var runtil=/Until$/,rparentsprev=/^(?:parents|prev(?:Until|All))/,isSimple=/^.[^:#\[\.,]*$/,rneedsContext=jQuery.expr.match.needsContext,guaranteedUnique={children:true,contents:true,next:true,prev:true};jQuery.fn.extend({find:function(selector){var i,l,length,n,r,ret,self=this;if(typeof selector!=="string"){return jQuery(selector).filter(function(){for(i=0,l=self.length;i0){for(n=length;n=0:jQuery.filter(selector,this).length>0:this.filter(selector).length>0)},closest:function(selectors,context){var cur,i=0,l=this.length,ret=[],pos=rneedsContext.test(selectors)||typeof selectors!=="string"?jQuery(selectors,context||this.context):0;for(;i-1:jQuery.find.matchesSelector(cur,selectors)){ret.push(cur);break}cur=cur.parentNode}}ret=ret.length>1?jQuery.unique(ret):ret;return this.pushStack(ret,"closest",selectors)},index:function(elem){if(!elem){return this[0]&&this[0].parentNode?this.prevAll().length:-1}if(typeof elem==="string"){return jQuery.inArray(this[0],jQuery(elem))}return jQuery.inArray(elem.jquery?elem[0]:elem,this)},add:function(selector,context){var set=typeof selector==="string"?jQuery(selector,context):jQuery.makeArray(selector&&selector.nodeType?[selector]:selector),all=jQuery.merge(this.get(),set);return this.pushStack(isDisconnected(set[0])||isDisconnected(all[0])?all:jQuery.unique(all))},addBack:function(selector){return this.add(selector==null?this.prevObject:this.prevObject.filter(selector))}});jQuery.fn.andSelf=jQuery.fn.addBack;function isDisconnected(node){return!node||!node.parentNode||node.parentNode.nodeType===11}function sibling(cur,dir){do{cur=cur[dir]}while(cur&&cur.nodeType!==1);return cur}jQuery.each({parent:function(elem){var parent=elem.parentNode;return parent&&parent.nodeType!==11?parent:null},parents:function(elem){return jQuery.dir(elem,"parentNode")},parentsUntil:function(elem,i,until){return jQuery.dir(elem,"parentNode",until)},next:function(elem){return sibling(elem,"nextSibling")},prev:function(elem){return sibling(elem,"previousSibling")},nextAll:function(elem){return jQuery.dir(elem,"nextSibling")},prevAll:function(elem){return jQuery.dir(elem,"previousSibling")},nextUntil:function(elem,i,until){return jQuery.dir(elem,"nextSibling",until)},prevUntil:function(elem,i,until){return jQuery.dir(elem,"previousSibling",until)},siblings:function(elem){return jQuery.sibling((elem.parentNode||{}).firstChild,elem)},children:function(elem){return jQuery.sibling(elem.firstChild)},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.merge([],elem.childNodes)}},function(name,fn){jQuery.fn[name]=function(until,selector){var ret=jQuery.map(this,fn,until);if(!runtil.test(name)){selector=until}if(selector&&typeof selector==="string"){ret=jQuery.filter(selector,ret)}ret=this.length>1&&!guaranteedUnique[name]?jQuery.unique(ret):ret;if(this.length>1&&rparentsprev.test(name)){ret=ret.reverse()}return this.pushStack(ret,name,core_slice.call(arguments).join(","))}});jQuery.extend({filter:function(expr,elems,not){if(not){expr=":not("+expr+")"}return elems.length===1?jQuery.find.matchesSelector(elems[0],expr)?[elems[0]]:[]:jQuery.find.matches(expr,elems)},dir:function(elem,dir,until){var matched=[],cur=elem[dir];while(cur&&cur.nodeType!==9&&(until===undefined||cur.nodeType!==1||!jQuery(cur).is(until))){if(cur.nodeType===1){matched.push(cur)}cur=cur[dir]}return matched},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType===1&&n!==elem){r.push(n)}}return r}});function winnow(elements,qualifier,keep){qualifier=qualifier||0;if(jQuery.isFunction(qualifier)){return jQuery.grep(elements,function(elem,i){var retVal=!!qualifier.call(elem,i,elem);return retVal===keep})}else if(qualifier.nodeType){return jQuery.grep(elements,function(elem,i){return elem===qualifier===keep})}else if(typeof qualifier==="string"){var filtered=jQuery.grep(elements,function(elem){return elem.nodeType===1});if(isSimple.test(qualifier)){return jQuery.filter(qualifier,filtered,!keep)}else{qualifier=jQuery.filter(qualifier,filtered)}}return jQuery.grep(elements,function(elem,i){return jQuery.inArray(elem,qualifier)>=0===keep})}function createSafeFragment(document){var list=nodeNames.split("|"),safeFrag=document.createDocumentFragment();if(safeFrag.createElement){while(list.length){safeFrag.createElement(list.pop())}}return safeFrag}var nodeNames="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|"+"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",rinlinejQuery=/ jQuery\d+="(?:null|\d+)"/g,rleadingWhitespace=/^\s+/,rxhtmlTag=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,rtagName=/<([\w:]+)/,rtbody=/]","i"),rcheckableType=/^(?:checkbox|radio)$/,rchecked=/checked\s*(?:[^=]|=\s*.checked.)/i,rscriptType=/\/(java|ecma)script/i,rcleanScript=/^\s*\s*$/g,wrapMap={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},safeFragment=createSafeFragment(document),fragmentDiv=safeFragment.appendChild(document.createElement("div"));wrapMap.optgroup=wrapMap.option;wrapMap.tbody=wrapMap.tfoot=wrapMap.colgroup=wrapMap.caption=wrapMap.thead;wrapMap.th=wrapMap.td;if(!jQuery.support.htmlSerialize){wrapMap._default=[1,"X
","
"]}jQuery.fn.extend({text:function(value){return jQuery.access(this,function(value){return value===undefined?jQuery.text(this):this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(value))},null,value,arguments.length)},wrapAll:function(html){if(jQuery.isFunction(html)){return this.each(function(i){jQuery(this).wrapAll(html.call(this,i))})}if(this[0]){var wrap=jQuery(html,this[0].ownerDocument).eq(0).clone(true);if(this[0].parentNode){wrap.insertBefore(this[0])}wrap.map(function(){var elem=this;while(elem.firstChild&&elem.firstChild.nodeType===1){elem=elem.firstChild}return elem}).append(this)}return this},wrapInner:function(html){if(jQuery.isFunction(html)){return this.each(function(i){jQuery(this).wrapInner(html.call(this,i))})}return this.each(function(){var self=jQuery(this),contents=self.contents();if(contents.length){contents.wrapAll(html)}else{self.append(html)}})},wrap:function(html){var isFunction=jQuery.isFunction(html);return this.each(function(i){jQuery(this).wrapAll(isFunction?html.call(this,i):html)})},unwrap:function(){return this.parent().each(function(){if(!jQuery.nodeName(this,"body")){jQuery(this).replaceWith(this.childNodes)}}).end()},append:function(){return this.domManip(arguments,true,function(elem){if(this.nodeType===1||this.nodeType===11){this.appendChild(elem)}})},prepend:function(){return this.domManip(arguments,true,function(elem){if(this.nodeType===1||this.nodeType===11){this.insertBefore(elem,this.firstChild)}})},before:function(){if(!isDisconnected(this[0])){return this.domManip(arguments,false,function(elem){this.parentNode.insertBefore(elem,this)})}if(arguments.length){var set=jQuery.clean(arguments);return this.pushStack(jQuery.merge(set,this),"before",this.selector)}},after:function(){if(!isDisconnected(this[0])){return this.domManip(arguments,false,function(elem){this.parentNode.insertBefore(elem,this.nextSibling)})}if(arguments.length){var set=jQuery.clean(arguments);return this.pushStack(jQuery.merge(this,set),"after",this.selector)}},remove:function(selector,keepData){var elem,i=0;for(;(elem=this[i])!=null;i++){if(!selector||jQuery.filter(selector,[elem]).length){if(!keepData&&elem.nodeType===1){jQuery.cleanData(elem.getElementsByTagName("*"));jQuery.cleanData([elem])}if(elem.parentNode){elem.parentNode.removeChild(elem)}}}return this},empty:function(){var elem,i=0;for(;(elem=this[i])!=null;i++){if(elem.nodeType===1){jQuery.cleanData(elem.getElementsByTagName("*"))}while(elem.firstChild){elem.removeChild(elem.firstChild)}}return this},clone:function(dataAndEvents,deepDataAndEvents){dataAndEvents=dataAndEvents==null?false:dataAndEvents;deepDataAndEvents=deepDataAndEvents==null?dataAndEvents:deepDataAndEvents;return this.map(function(){return jQuery.clone(this,dataAndEvents,deepDataAndEvents)})},html:function(value){return jQuery.access(this,function(value){var elem=this[0]||{},i=0,l=this.length;if(value===undefined){return elem.nodeType===1?elem.innerHTML.replace(rinlinejQuery,""):undefined}if(typeof value==="string"&&!rnoInnerhtml.test(value)&&(jQuery.support.htmlSerialize||!rnoshimcache.test(value))&&(jQuery.support.leadingWhitespace||!rleadingWhitespace.test(value))&&!wrapMap[(rtagName.exec(value)||["",""])[1].toLowerCase()]){value=value.replace(rxhtmlTag,"<$1>");try{for(;i1&&typeof value==="string"&&rchecked.test(value)){return this.each(function(){jQuery(this).domManip(args,table,callback)})}if(jQuery.isFunction(value)){return this.each(function(i){var self=jQuery(this);args[0]=value.call(this,i,table?self.html():undefined);self.domManip(args,table,callback)})}if(this[0]){results=jQuery.buildFragment(args,this,scripts);fragment=results.fragment;first=fragment.firstChild;if(fragment.childNodes.length===1){fragment=first}if(first){table=table&&jQuery.nodeName(first,"tr");for(iNoClone=results.cacheable||l-1;i0?this.clone(true):this).get();jQuery(insert[i])[original](elems);ret=ret.concat(elems)}return this.pushStack(ret,name,insert.selector)}}});function getAll(elem){if(typeof elem.getElementsByTagName!=="undefined"){return elem.getElementsByTagName("*")}else if(typeof elem.querySelectorAll!=="undefined"){return elem.querySelectorAll("*")}else{return[]}}function fixDefaultChecked(elem){if(rcheckableType.test(elem.type)){elem.defaultChecked=elem.checked}}jQuery.extend({clone:function(elem,dataAndEvents,deepDataAndEvents){var srcElements,destElements,i,clone;if(jQuery.support.html5Clone||jQuery.isXMLDoc(elem)||!rnoshimcache.test("<"+elem.nodeName+">")){clone=elem.cloneNode(true)}else{fragmentDiv.innerHTML=elem.outerHTML;fragmentDiv.removeChild(clone=fragmentDiv.firstChild)}if((!jQuery.support.noCloneEvent||!jQuery.support.noCloneChecked)&&(elem.nodeType===1||elem.nodeType===11)&&!jQuery.isXMLDoc(elem)){cloneFixAttributes(elem,clone);srcElements=getAll(elem);destElements=getAll(clone);for(i=0;srcElements[i];++i){if(destElements[i]){cloneFixAttributes(srcElements[i],destElements[i])}}}if(dataAndEvents){cloneCopyEvent(elem,clone);if(deepDataAndEvents){srcElements=getAll(elem);destElements=getAll(clone);for(i=0;srcElements[i];++i){cloneCopyEvent(srcElements[i],destElements[i])}}}srcElements=destElements=null;return clone},clean:function(elems,context,fragment,scripts){var i,j,elem,tag,wrap,depth,div,hasBody,tbody,len,handleScript,jsTags,safe=context===document&&safeFragment,ret=[];if(!context||typeof context.createDocumentFragment==="undefined"){context=document}for(i=0;(elem=elems[i])!=null;i++){if(typeof elem==="number"){elem+=""}if(!elem){continue}if(typeof elem==="string"){if(!rhtml.test(elem)){elem=context.createTextNode(elem)}else{safe=safe||createSafeFragment(context);div=context.createElement("div");safe.appendChild(div);elem=elem.replace(rxhtmlTag,"<$1>");tag=(rtagName.exec(elem)||["",""])[1].toLowerCase();wrap=wrapMap[tag]||wrapMap._default;depth=wrap[0];div.innerHTML=wrap[1]+elem+wrap[2];while(depth--){div=div.lastChild +}if(!jQuery.support.tbody){hasBody=rtbody.test(elem);tbody=tag==="table"&&!hasBody?div.firstChild&&div.firstChild.childNodes:wrap[1]===""&&!hasBody?div.childNodes:[];for(j=tbody.length-1;j>=0;--j){if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length){tbody[j].parentNode.removeChild(tbody[j])}}}if(!jQuery.support.leadingWhitespace&&rleadingWhitespace.test(elem)){div.insertBefore(context.createTextNode(rleadingWhitespace.exec(elem)[0]),div.firstChild)}elem=div.childNodes;div.parentNode.removeChild(div)}}if(elem.nodeType){ret.push(elem)}else{jQuery.merge(ret,elem)}}if(div){elem=div=safe=null}if(!jQuery.support.appendChecked){for(i=0;(elem=ret[i])!=null;i++){if(jQuery.nodeName(elem,"input")){fixDefaultChecked(elem)}else if(typeof elem.getElementsByTagName!=="undefined"){jQuery.grep(elem.getElementsByTagName("input"),fixDefaultChecked)}}}if(fragment){handleScript=function(elem){if(!elem.type||rscriptType.test(elem.type)){return scripts?scripts.push(elem.parentNode?elem.parentNode.removeChild(elem):elem):fragment.appendChild(elem)}};for(i=0;(elem=ret[i])!=null;i++){if(!(jQuery.nodeName(elem,"script")&&handleScript(elem))){fragment.appendChild(elem);if(typeof elem.getElementsByTagName!=="undefined"){jsTags=jQuery.grep(jQuery.merge([],elem.getElementsByTagName("script")),handleScript);ret.splice.apply(ret,[i+1,0].concat(jsTags));i+=jsTags.length}}}}return ret},cleanData:function(elems,acceptData){var data,id,elem,type,i=0,internalKey=jQuery.expando,cache=jQuery.cache,deleteExpando=jQuery.support.deleteExpando,special=jQuery.event.special;for(;(elem=elems[i])!=null;i++){if(acceptData||jQuery.acceptData(elem)){id=elem[internalKey];data=id&&cache[id];if(data){if(data.events){for(type in data.events){if(special[type]){jQuery.event.remove(elem,type)}else{jQuery.removeEvent(elem,type,data.handle)}}}if(cache[id]){delete cache[id];if(deleteExpando){delete elem[internalKey]}else if(elem.removeAttribute){elem.removeAttribute(internalKey)}else{elem[internalKey]=null}jQuery.deletedIds.push(id)}}}}}});(function(){var matched,browser;jQuery.uaMatch=function(ua){ua=ua.toLowerCase();var match=/(chrome)[ \/]([\w.]+)/.exec(ua)||/(webkit)[ \/]([\w.]+)/.exec(ua)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua)||/(msie) ([\w.]+)/.exec(ua)||ua.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)||[];return{browser:match[1]||"",version:match[2]||"0"}};matched=jQuery.uaMatch(navigator.userAgent);browser={};if(matched.browser){browser[matched.browser]=true;browser.version=matched.version}if(browser.chrome){browser.webkit=true}else if(browser.webkit){browser.safari=true}jQuery.browser=browser;jQuery.sub=function(){function jQuerySub(selector,context){return new jQuerySub.fn.init(selector,context)}jQuery.extend(true,jQuerySub,this);jQuerySub.superclass=this;jQuerySub.fn=jQuerySub.prototype=this();jQuerySub.fn.constructor=jQuerySub;jQuerySub.sub=this.sub;jQuerySub.fn.init=function init(selector,context){if(context&&context instanceof jQuery&&!(context instanceof jQuerySub)){context=jQuerySub(context)}return jQuery.fn.init.call(this,selector,context,rootjQuerySub)};jQuerySub.fn.init.prototype=jQuerySub.fn;var rootjQuerySub=jQuerySub(document);return jQuerySub}})();var curCSS,iframe,iframeDoc,ralpha=/alpha\([^)]*\)/i,ropacity=/opacity=([^)]*)/,rposition=/^(top|right|bottom|left)$/,rdisplayswap=/^(none|table(?!-c[ea]).+)/,rmargin=/^margin/,rnumsplit=new RegExp("^("+core_pnum+")(.*)$","i"),rnumnonpx=new RegExp("^("+core_pnum+")(?!px)[a-z%]+$","i"),rrelNum=new RegExp("^([-+])=("+core_pnum+")","i"),elemdisplay={BODY:"block"},cssShow={position:"absolute",visibility:"hidden",display:"block"},cssNormalTransform={letterSpacing:0,fontWeight:400},cssExpand=["Top","Right","Bottom","Left"],cssPrefixes=["Webkit","O","Moz","ms"],eventsToggle=jQuery.fn.toggle;function vendorPropName(style,name){if(name in style){return name}var capName=name.charAt(0).toUpperCase()+name.slice(1),origName=name,i=cssPrefixes.length;while(i--){name=cssPrefixes[i]+capName;if(name in style){return name}}return origName}function isHidden(elem,el){elem=el||elem;return jQuery.css(elem,"display")==="none"||!jQuery.contains(elem.ownerDocument,elem)}function showHide(elements,show){var elem,display,values=[],index=0,length=elements.length;for(;index1)},show:function(){return showHide(this,true)},hide:function(){return showHide(this)},toggle:function(state,fn2){var bool=typeof state==="boolean";if(jQuery.isFunction(state)&&jQuery.isFunction(fn2)){return eventsToggle.apply(this,arguments)}return this.each(function(){if(bool?state:isHidden(this)){jQuery(this).show()}else{jQuery(this).hide()}})}});jQuery.extend({cssHooks:{opacity:{get:function(elem,computed){if(computed){var ret=curCSS(elem,"opacity");return ret===""?"1":ret}}}},cssNumber:{fillOpacity:true,fontWeight:true,lineHeight:true,opacity:true,orphans:true,widows:true,zIndex:true,zoom:true},cssProps:{"float":jQuery.support.cssFloat?"cssFloat":"styleFloat"},style:function(elem,name,value,extra){if(!elem||elem.nodeType===3||elem.nodeType===8||!elem.style){return}var ret,type,hooks,origName=jQuery.camelCase(name),style=elem.style;name=jQuery.cssProps[origName]||(jQuery.cssProps[origName]=vendorPropName(style,origName));hooks=jQuery.cssHooks[name]||jQuery.cssHooks[origName];if(value!==undefined){type=typeof value;if(type==="string"&&(ret=rrelNum.exec(value))){value=(ret[1]+1)*ret[2]+parseFloat(jQuery.css(elem,name));type="number"}if(value==null||type==="number"&&isNaN(value)){return}if(type==="number"&&!jQuery.cssNumber[origName]){value+="px"}if(!hooks||!("set"in hooks)||(value=hooks.set(elem,value,extra))!==undefined){try{style[name]=value}catch(e){}}}else{if(hooks&&"get"in hooks&&(ret=hooks.get(elem,false,extra))!==undefined){return ret}return style[name]}},css:function(elem,name,numeric,extra){var val,num,hooks,origName=jQuery.camelCase(name);name=jQuery.cssProps[origName]||(jQuery.cssProps[origName]=vendorPropName(elem.style,origName));hooks=jQuery.cssHooks[name]||jQuery.cssHooks[origName];if(hooks&&"get"in hooks){val=hooks.get(elem,true,extra)}if(val===undefined){val=curCSS(elem,name)}if(val==="normal"&&name in cssNormalTransform){val=cssNormalTransform[name]}if(numeric||extra!==undefined){num=parseFloat(val);return numeric||jQuery.isNumeric(num)?num||0:val}return val},swap:function(elem,options,callback){var ret,name,old={};for(name in options){old[name]=elem.style[name];elem.style[name]=options[name]}ret=callback.call(elem);for(name in options){elem.style[name]=old[name]}return ret}});if(window.getComputedStyle){curCSS=function(elem,name){var ret,width,minWidth,maxWidth,computed=window.getComputedStyle(elem,null),style=elem.style;if(computed){ret=computed.getPropertyValue(name)||computed[name];if(ret===""&&!jQuery.contains(elem.ownerDocument,elem)){ret=jQuery.style(elem,name)}if(rnumnonpx.test(ret)&&rmargin.test(name)){width=style.width;minWidth=style.minWidth;maxWidth=style.maxWidth;style.minWidth=style.maxWidth=style.width=ret;ret=computed.width;style.width=width;style.minWidth=minWidth;style.maxWidth=maxWidth}}return ret}}else if(document.documentElement.currentStyle){curCSS=function(elem,name){var left,rsLeft,ret=elem.currentStyle&&elem.currentStyle[name],style=elem.style;if(ret==null&&style&&style[name]){ret=style[name]}if(rnumnonpx.test(ret)&&!rposition.test(name)){left=style.left;rsLeft=elem.runtimeStyle&&elem.runtimeStyle.left;if(rsLeft){elem.runtimeStyle.left=elem.currentStyle.left}style.left=name==="fontSize"?"1em":ret;ret=style.pixelLeft+"px";style.left=left;if(rsLeft){elem.runtimeStyle.left=rsLeft}}return ret===""?"auto":ret}}function setPositiveNumber(elem,value,subtract){var matches=rnumsplit.exec(value);return matches?Math.max(0,matches[1]-(subtract||0))+(matches[2]||"px"):value}function augmentWidthOrHeight(elem,name,extra,isBorderBox){var i=extra===(isBorderBox?"border":"content")?4:name==="width"?1:0,val=0;for(;i<4;i+=2){if(extra==="margin"){val+=jQuery.css(elem,extra+cssExpand[i],true)}if(isBorderBox){if(extra==="content"){val-=parseFloat(curCSS(elem,"padding"+cssExpand[i]))||0}if(extra!=="margin"){val-=parseFloat(curCSS(elem,"border"+cssExpand[i]+"Width"))||0}}else{val+=parseFloat(curCSS(elem,"padding"+cssExpand[i]))||0;if(extra!=="padding"){val+=parseFloat(curCSS(elem,"border"+cssExpand[i]+"Width"))||0}}}return val}function getWidthOrHeight(elem,name,extra){var val=name==="width"?elem.offsetWidth:elem.offsetHeight,valueIsBorderBox=true,isBorderBox=jQuery.support.boxSizing&&jQuery.css(elem,"boxSizing")==="border-box";if(val<=0||val==null){val=curCSS(elem,name);if(val<0||val==null){val=elem.style[name]}if(rnumnonpx.test(val)){return val}valueIsBorderBox=isBorderBox&&(jQuery.support.boxSizingReliable||val===elem.style[name]);val=parseFloat(val)||0}return val+augmentWidthOrHeight(elem,name,extra||(isBorderBox?"border":"content"),valueIsBorderBox)+"px"}function css_defaultDisplay(nodeName){if(elemdisplay[nodeName]){return elemdisplay[nodeName]}var elem=jQuery("<"+nodeName+">").appendTo(document.body),display=elem.css("display");elem.remove();if(display==="none"||display===""){iframe=document.body.appendChild(iframe||jQuery.extend(document.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!iframeDoc||!iframe.createElement){iframeDoc=(iframe.contentWindow||iframe.contentDocument).document;iframeDoc.write("");iframeDoc.close()}elem=iframeDoc.body.appendChild(iframeDoc.createElement(nodeName));display=curCSS(elem,"display");document.body.removeChild(iframe)}elemdisplay[nodeName]=display;return display}jQuery.each(["height","width"],function(i,name){jQuery.cssHooks[name]={get:function(elem,computed,extra){if(computed){if(elem.offsetWidth===0&&rdisplayswap.test(curCSS(elem,"display"))){return jQuery.swap(elem,cssShow,function(){return getWidthOrHeight(elem,name,extra)})}else{return getWidthOrHeight(elem,name,extra)}}},set:function(elem,value,extra){return setPositiveNumber(elem,value,extra?augmentWidthOrHeight(elem,name,extra,jQuery.support.boxSizing&&jQuery.css(elem,"boxSizing")==="border-box"):0)}}});if(!jQuery.support.opacity){jQuery.cssHooks.opacity={get:function(elem,computed){return ropacity.test((computed&&elem.currentStyle?elem.currentStyle.filter:elem.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":computed?"1":""},set:function(elem,value){var style=elem.style,currentStyle=elem.currentStyle,opacity=jQuery.isNumeric(value)?"alpha(opacity="+value*100+")":"",filter=currentStyle&¤tStyle.filter||style.filter||"";style.zoom=1;if(value>=1&&jQuery.trim(filter.replace(ralpha,""))===""&&style.removeAttribute){style.removeAttribute("filter");if(currentStyle&&!currentStyle.filter){return}}style.filter=ralpha.test(filter)?filter.replace(ralpha,opacity):filter+" "+opacity}}}jQuery(function(){if(!jQuery.support.reliableMarginRight){jQuery.cssHooks.marginRight={get:function(elem,computed){return jQuery.swap(elem,{display:"inline-block"},function(){if(computed){return curCSS(elem,"marginRight")}})}}}if(!jQuery.support.pixelPosition&&jQuery.fn.position){jQuery.each(["top","left"],function(i,prop){jQuery.cssHooks[prop]={get:function(elem,computed){if(computed){var ret=curCSS(elem,prop);return rnumnonpx.test(ret)?jQuery(elem).position()[prop]+"px":ret}}}})}});if(jQuery.expr&&jQuery.expr.filters){jQuery.expr.filters.hidden=function(elem){return elem.offsetWidth===0&&elem.offsetHeight===0||!jQuery.support.reliableHiddenOffsets&&(elem.style&&elem.style.display||curCSS(elem,"display"))==="none"};jQuery.expr.filters.visible=function(elem){return!jQuery.expr.filters.hidden(elem)}}jQuery.each({margin:"",padding:"",border:"Width"},function(prefix,suffix){jQuery.cssHooks[prefix+suffix]={expand:function(value){var i,parts=typeof value==="string"?value.split(" "):[value],expanded={};for(i=0;i<4;i++){expanded[prefix+cssExpand[i]+suffix]=parts[i]||parts[i-2]||parts[0]}return expanded}};if(!rmargin.test(prefix)){jQuery.cssHooks[prefix+suffix].set=setPositiveNumber}});var r20=/%20/g,rbracket=/\[\]$/,rCRLF=/\r?\n/g,rinput=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,rselectTextarea=/^(?:select|textarea)/i;jQuery.fn.extend({serialize:function(){return jQuery.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?jQuery.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||rselectTextarea.test(this.nodeName)||rinput.test(this.type))}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:jQuery.isArray(val)?jQuery.map(val,function(val,i){return{name:elem.name,value:val.replace(rCRLF,"\r\n")}}):{name:elem.name,value:val.replace(rCRLF,"\r\n")}}).get()}});jQuery.param=function(a,traditional){var prefix,s=[],add=function(key,value){value=jQuery.isFunction(value)?value():value==null?"":value;s[s.length]=encodeURIComponent(key)+"="+encodeURIComponent(value)};if(traditional===undefined){traditional=jQuery.ajaxSettings&&jQuery.ajaxSettings.traditional}if(jQuery.isArray(a)||a.jquery&&!jQuery.isPlainObject(a)){jQuery.each(a,function(){add(this.name,this.value)})}else{for(prefix in a){buildParams(prefix,a[prefix],traditional,add)}}return s.join("&").replace(r20,"+")};function buildParams(prefix,obj,traditional,add){var name;if(jQuery.isArray(obj)){jQuery.each(obj,function(i,v){if(traditional||rbracket.test(prefix)){add(prefix,v)}else{buildParams(prefix+"["+(typeof v==="object"?i:"")+"]",v,traditional,add)}})}else if(!traditional&&jQuery.type(obj)==="object"){for(name in obj){buildParams(prefix+"["+name+"]",obj[name],traditional,add)}}else{add(prefix,obj)}}var ajaxLocParts,ajaxLocation,rhash=/#.*$/,rheaders=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,rlocalProtocol=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,rnoContent=/^(?:GET|HEAD)$/,rprotocol=/^\/\//,rquery=/\?/,rscript=/)<[^<]*)*<\/script>/gi,rts=/([?&])_=[^&]*/,rurl=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,_load=jQuery.fn.load,prefilters={},transports={},allTypes=["*/"]+["*"];try{ajaxLocation=location.href}catch(e){ajaxLocation=document.createElement("a");ajaxLocation.href="";ajaxLocation=ajaxLocation.href}ajaxLocParts=rurl.exec(ajaxLocation.toLowerCase())||[];function addToPrefiltersOrTransports(structure){return function(dataTypeExpression,func){if(typeof dataTypeExpression!=="string"){func=dataTypeExpression;dataTypeExpression="*"}var dataType,list,placeBefore,dataTypes=dataTypeExpression.toLowerCase().split(core_rspace),i=0,length=dataTypes.length;if(jQuery.isFunction(func)){for(;i=0){selector=url.slice(off,url.length);url=url.slice(0,off)}if(jQuery.isFunction(params)){callback=params;params=undefined}else if(params&&typeof params==="object"){type="POST"}jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(jqXHR,status){if(callback){self.each(callback,response||[jqXHR.responseText,status,jqXHR])}}}).done(function(responseText){response=arguments;self.html(selector?jQuery("
").append(responseText.replace(rscript,"")).find(selector):responseText)});return this};jQuery.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(i,o){jQuery.fn[o]=function(f){return this.on(o,f)}});jQuery.each(["get","post"],function(i,method){jQuery[method]=function(url,data,callback,type){if(jQuery.isFunction(data)){type=type||callback;callback=data;data=undefined}return jQuery.ajax({type:method,url:url,data:data,success:callback,dataType:type})}});jQuery.extend({getScript:function(url,callback){return jQuery.get(url,undefined,callback,"script")},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json")},ajaxSetup:function(target,settings){if(settings){ajaxExtend(target,jQuery.ajaxSettings)}else{settings=target;target=jQuery.ajaxSettings}ajaxExtend(target,settings);return target},ajaxSettings:{url:ajaxLocation,isLocal:rlocalProtocol.test(ajaxLocParts[1]),global:true,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:true,async:true,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":allTypes},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":window.String,"text html":true,"text json":jQuery.parseJSON,"text xml":jQuery.parseXML},flatOptions:{context:true,url:true}},ajaxPrefilter:addToPrefiltersOrTransports(prefilters),ajaxTransport:addToPrefiltersOrTransports(transports),ajax:function(url,options){if(typeof url==="object"){options=url;url=undefined}options=options||{};var ifModifiedKey,responseHeadersString,responseHeaders,transport,timeoutTimer,parts,fireGlobals,i,s=jQuery.ajaxSetup({},options),callbackContext=s.context||s,globalEventContext=callbackContext!==s&&(callbackContext.nodeType||callbackContext instanceof jQuery)?jQuery(callbackContext):jQuery.event,deferred=jQuery.Deferred(),completeDeferred=jQuery.Callbacks("once memory"),statusCode=s.statusCode||{},requestHeaders={},requestHeadersNames={},state=0,strAbort="canceled",jqXHR={readyState:0,setRequestHeader:function(name,value){if(!state){var lname=name.toLowerCase();name=requestHeadersNames[lname]=requestHeadersNames[lname]||name;requestHeaders[name]=value}return this},getAllResponseHeaders:function(){return state===2?responseHeadersString:null},getResponseHeader:function(key){var match;if(state===2){if(!responseHeaders){responseHeaders={};while(match=rheaders.exec(responseHeadersString)){responseHeaders[match[1].toLowerCase()]=match[2]}}match=responseHeaders[key.toLowerCase()]}return match===undefined?null:match},overrideMimeType:function(type){if(!state){s.mimeType=type}return this},abort:function(statusText){statusText=statusText||strAbort;if(transport){transport.abort(statusText)}done(0,statusText);return this}};function done(status,nativeStatusText,responses,headers){var isSuccess,success,error,response,modified,statusText=nativeStatusText;if(state===2){return}state=2;if(timeoutTimer){clearTimeout(timeoutTimer)}transport=undefined;responseHeadersString=headers||"";jqXHR.readyState=status>0?4:0;if(responses){response=ajaxHandleResponses(s,jqXHR,responses)}if(status>=200&&status<300||status===304){if(s.ifModified){modified=jqXHR.getResponseHeader("Last-Modified");if(modified){jQuery.lastModified[ifModifiedKey]=modified}modified=jqXHR.getResponseHeader("Etag");if(modified){jQuery.etag[ifModifiedKey]=modified}}if(status===304){statusText="notmodified";isSuccess=true}else{isSuccess=ajaxConvert(s,response);statusText=isSuccess.state;success=isSuccess.data;error=isSuccess.error;isSuccess=!error}}else{error=statusText;if(!statusText||status){statusText="error";if(status<0){status=0}}}jqXHR.status=status;jqXHR.statusText=(nativeStatusText||statusText)+"";if(isSuccess){deferred.resolveWith(callbackContext,[success,statusText,jqXHR])}else{deferred.rejectWith(callbackContext,[jqXHR,statusText,error])}jqXHR.statusCode(statusCode);statusCode=undefined;if(fireGlobals){globalEventContext.trigger("ajax"+(isSuccess?"Success":"Error"),[jqXHR,s,isSuccess?success:error])}completeDeferred.fireWith(callbackContext,[jqXHR,statusText]);if(fireGlobals){globalEventContext.trigger("ajaxComplete",[jqXHR,s]);if(!--jQuery.active){jQuery.event.trigger("ajaxStop")}}}deferred.promise(jqXHR);jqXHR.success=jqXHR.done;jqXHR.error=jqXHR.fail;jqXHR.complete=completeDeferred.add;jqXHR.statusCode=function(map){if(map){var tmp;if(state<2){for(tmp in map){statusCode[tmp]=[statusCode[tmp],map[tmp]]}}else{tmp=map[jqXHR.status];jqXHR.always(tmp)}}return this};s.url=((url||s.url)+"").replace(rhash,"").replace(rprotocol,ajaxLocParts[1]+"//");s.dataTypes=jQuery.trim(s.dataType||"*").toLowerCase().split(core_rspace);if(s.crossDomain==null){parts=rurl.exec(s.url.toLowerCase());s.crossDomain=!!(parts&&(parts[1]!==ajaxLocParts[1]||parts[2]!==ajaxLocParts[2]||(parts[3]||(parts[1]==="http:"?80:443))!=(ajaxLocParts[3]||(ajaxLocParts[1]==="http:"?80:443))))}if(s.data&&s.processData&&typeof s.data!=="string"){s.data=jQuery.param(s.data,s.traditional)}inspectPrefiltersOrTransports(prefilters,s,options,jqXHR);if(state===2){return jqXHR}fireGlobals=s.global;s.type=s.type.toUpperCase();s.hasContent=!rnoContent.test(s.type);if(fireGlobals&&jQuery.active++===0){jQuery.event.trigger("ajaxStart")}if(!s.hasContent){if(s.data){s.url+=(rquery.test(s.url)?"&":"?")+s.data;delete s.data}ifModifiedKey=s.url;if(s.cache===false){var ts=jQuery.now(),ret=s.url.replace(rts,"$1_="+ts);s.url=ret+(ret===s.url?(rquery.test(s.url)?"&":"?")+"_="+ts:"")}}if(s.data&&s.hasContent&&s.contentType!==false||options.contentType){jqXHR.setRequestHeader("Content-Type",s.contentType)}if(s.ifModified){ifModifiedKey=ifModifiedKey||s.url;if(jQuery.lastModified[ifModifiedKey]){jqXHR.setRequestHeader("If-Modified-Since",jQuery.lastModified[ifModifiedKey])}if(jQuery.etag[ifModifiedKey]){jqXHR.setRequestHeader("If-None-Match",jQuery.etag[ifModifiedKey])}}jqXHR.setRequestHeader("Accept",s.dataTypes[0]&&s.accepts[s.dataTypes[0]]?s.accepts[s.dataTypes[0]]+(s.dataTypes[0]!=="*"?", "+allTypes+"; q=0.01":""):s.accepts["*"]);for(i in s.headers){jqXHR.setRequestHeader(i,s.headers[i])}if(s.beforeSend&&(s.beforeSend.call(callbackContext,jqXHR,s)===false||state===2)){return jqXHR.abort()}strAbort="abort";for(i in{success:1,error:1,complete:1}){jqXHR[i](s[i])}transport=inspectPrefiltersOrTransports(transports,s,options,jqXHR);if(!transport){done(-1,"No Transport")}else{jqXHR.readyState=1;if(fireGlobals){globalEventContext.trigger("ajaxSend",[jqXHR,s])}if(s.async&&s.timeout>0){timeoutTimer=setTimeout(function(){jqXHR.abort("timeout")},s.timeout)}try{state=1;transport.send(requestHeaders,done)}catch(e){if(state<2){done(-1,e)}else{throw e}}}return jqXHR},active:0,lastModified:{},etag:{}});function ajaxHandleResponses(s,jqXHR,responses){var ct,type,finalDataType,firstDataType,contents=s.contents,dataTypes=s.dataTypes,responseFields=s.responseFields;for(type in responseFields){if(type in responses){jqXHR[responseFields[type]]=responses[type]}}while(dataTypes[0]==="*"){dataTypes.shift();if(ct===undefined){ct=s.mimeType||jqXHR.getResponseHeader("content-type")}}if(ct){for(type in contents){if(contents[type]&&contents[type].test(ct)){dataTypes.unshift(type);break}}}if(dataTypes[0]in responses){finalDataType=dataTypes[0]}else{for(type in responses){if(!dataTypes[0]||s.converters[type+" "+dataTypes[0]]){finalDataType=type;break}if(!firstDataType){firstDataType=type}}finalDataType=finalDataType||firstDataType}if(finalDataType){if(finalDataType!==dataTypes[0]){dataTypes.unshift(finalDataType)}return responses[finalDataType]}}function ajaxConvert(s,response){var conv,conv2,current,tmp,dataTypes=s.dataTypes.slice(),prev=dataTypes[0],converters={},i=0;if(s.dataFilter){response=s.dataFilter(response,s.dataType)}if(dataTypes[1]){for(conv in s.converters){converters[conv.toLowerCase()]=s.converters[conv]}}for(;current=dataTypes[++i];){if(current!=="*"){if(prev!=="*"&&prev!==current){conv=converters[prev+" "+current]||converters["* "+current];if(!conv){for(conv2 in converters){tmp=conv2.split(" ");if(tmp[1]===current){conv=converters[prev+" "+tmp[0]]||converters["* "+tmp[0]];if(conv){if(conv===true){conv=converters[conv2]}else if(converters[conv2]!==true){current=tmp[0];dataTypes.splice(i--,0,current)}break}}}}if(conv!==true){if(conv&&s["throws"]){response=conv(response)}else{try{response=conv(response)}catch(e){return{state:"parsererror",error:conv?e:"No conversion from "+prev+" to "+current}}}}}prev=current}}return{state:"success",data:response}}var oldCallbacks=[],rquestion=/\?/,rjsonp=/(=)\?(?=&|$)|\?\?/,nonce=jQuery.now();jQuery.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var callback=oldCallbacks.pop()||jQuery.expando+"_"+nonce++;this[callback]=true;return callback}});jQuery.ajaxPrefilter("json jsonp",function(s,originalSettings,jqXHR){var callbackName,overwritten,responseContainer,data=s.data,url=s.url,hasCallback=s.jsonp!==false,replaceInUrl=hasCallback&&rjsonp.test(url),replaceInData=hasCallback&&!replaceInUrl&&typeof data==="string"&&!(s.contentType||"").indexOf("application/x-www-form-urlencoded")&&rjsonp.test(data);if(s.dataTypes[0]==="jsonp"||replaceInUrl||replaceInData){callbackName=s.jsonpCallback=jQuery.isFunction(s.jsonpCallback)?s.jsonpCallback():s.jsonpCallback;overwritten=window[callbackName];if(replaceInUrl){s.url=url.replace(rjsonp,"$1"+callbackName)}else if(replaceInData){s.data=data.replace(rjsonp,"$1"+callbackName)}else if(hasCallback){s.url+=(rquestion.test(url)?"&":"?")+s.jsonp+"="+callbackName}s.converters["script json"]=function(){if(!responseContainer){jQuery.error(callbackName+" was not called")}return responseContainer[0]};s.dataTypes[0]="json";window[callbackName]=function(){responseContainer=arguments};jqXHR.always(function(){window[callbackName]=overwritten;if(s[callbackName]){s.jsonpCallback=originalSettings.jsonpCallback;oldCallbacks.push(callbackName)}if(responseContainer&&jQuery.isFunction(overwritten)){overwritten(responseContainer[0])}responseContainer=overwritten=undefined});return"script"}});jQuery.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(text){jQuery.globalEval(text);return text}}});jQuery.ajaxPrefilter("script",function(s){if(s.cache===undefined){s.cache=false}if(s.crossDomain){s.type="GET";s.global=false}});jQuery.ajaxTransport("script",function(s){if(s.crossDomain){var script,head=document.head||document.getElementsByTagName("head")[0]||document.documentElement;return{send:function(_,callback){script=document.createElement("script");script.async="async";if(s.scriptCharset){script.charset=s.scriptCharset}script.src=s.url;script.onload=script.onreadystatechange=function(_,isAbort){if(isAbort||!script.readyState||/loaded|complete/.test(script.readyState)){script.onload=script.onreadystatechange=null;if(head&&script.parentNode){head.removeChild(script)}script=undefined;if(!isAbort){callback(200,"success")}}};head.insertBefore(script,head.firstChild)},abort:function(){if(script){script.onload(0,1)}}}}});var xhrCallbacks,xhrOnUnloadAbort=window.ActiveXObject?function(){for(var key in xhrCallbacks){xhrCallbacks[key](0,1)}}:false,xhrId=0;function createStandardXHR(){try{return new window.XMLHttpRequest}catch(e){}}function createActiveXHR(){try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(e){}}jQuery.ajaxSettings.xhr=window.ActiveXObject?function(){return!this.isLocal&&createStandardXHR()||createActiveXHR()}:createStandardXHR;(function(xhr){jQuery.extend(jQuery.support,{ajax:!!xhr,cors:!!xhr&&"withCredentials"in xhr})})(jQuery.ajaxSettings.xhr());if(jQuery.support.ajax){jQuery.ajaxTransport(function(s){if(!s.crossDomain||jQuery.support.cors){var callback;return{send:function(headers,complete){var handle,i,xhr=s.xhr();if(s.username){xhr.open(s.type,s.url,s.async,s.username,s.password)}else{xhr.open(s.type,s.url,s.async)}if(s.xhrFields){for(i in s.xhrFields){xhr[i]=s.xhrFields[i]}}if(s.mimeType&&xhr.overrideMimeType){xhr.overrideMimeType(s.mimeType)}if(!s.crossDomain&&!headers["X-Requested-With"]){headers["X-Requested-With"]="XMLHttpRequest"}try{for(i in headers){xhr.setRequestHeader(i,headers[i])}}catch(_){}xhr.send(s.hasContent&&s.data||null);callback=function(_,isAbort){var status,statusText,responseHeaders,responses,xml;try{if(callback&&(isAbort||xhr.readyState===4)){callback=undefined;if(handle){xhr.onreadystatechange=jQuery.noop;if(xhrOnUnloadAbort){delete xhrCallbacks[handle]}}if(isAbort){if(xhr.readyState!==4){xhr.abort()}}else{status=xhr.status;responseHeaders=xhr.getAllResponseHeaders();responses={};xml=xhr.responseXML;if(xml&&xml.documentElement){responses.xml=xml}try{responses.text=xhr.responseText}catch(e){}try{statusText=xhr.statusText}catch(e){statusText=""}if(!status&&s.isLocal&&!s.crossDomain){status=responses.text?200:404}else if(status===1223){status=204}}}}catch(firefoxAccessException){if(!isAbort){complete(-1,firefoxAccessException)}}if(responses){complete(status,statusText,responses,responseHeaders)}};if(!s.async){callback()}else if(xhr.readyState===4){setTimeout(callback,0)}else{handle=++xhrId;if(xhrOnUnloadAbort){if(!xhrCallbacks){xhrCallbacks={};jQuery(window).unload(xhrOnUnloadAbort)}xhrCallbacks[handle]=callback}xhr.onreadystatechange=callback}},abort:function(){if(callback){callback(0,1)}}}}})}var fxNow,timerId,rfxtypes=/^(?:toggle|show|hide)$/,rfxnum=new RegExp("^(?:([-+])=|)("+core_pnum+")([a-z%]*)$","i"),rrun=/queueHooks$/,animationPrefilters=[defaultPrefilter],tweeners={"*":[function(prop,value){var end,unit,tween=this.createTween(prop,value),parts=rfxnum.exec(value),target=tween.cur(),start=+target||0,scale=1,maxIterations=20;if(parts){end=+parts[2];unit=parts[3]||(jQuery.cssNumber[prop]?"":"px");if(unit!=="px"&&start){start=jQuery.css(tween.elem,prop,true)||end||1;do{scale=scale||".5";start=start/scale;jQuery.style(tween.elem,prop,start+unit)}while(scale!==(scale=tween.cur()/target)&&scale!==1&&--maxIterations)}tween.unit=unit;tween.start=start;tween.end=parts[1]?start+(parts[1]+1)*end:end}return tween}]};function createFxNow(){setTimeout(function(){fxNow=undefined},0);return fxNow=jQuery.now()}function createTweens(animation,props){jQuery.each(props,function(prop,value){var collection=(tweeners[prop]||[]).concat(tweeners["*"]),index=0,length=collection.length;for(;index-1,props={},curPosition={},curTop,curLeft;if(calculatePosition){curPosition=curElem.position();curTop=curPosition.top;curLeft=curPosition.left}else{curTop=parseFloat(curCSSTop)||0;curLeft=parseFloat(curCSSLeft)||0}if(jQuery.isFunction(options)){options=options.call(elem,i,curOffset)}if(options.top!=null){props.top=options.top-curOffset.top+curTop}if(options.left!=null){props.left=options.left-curOffset.left+curLeft}if("using"in options){options.using.call(elem,props)}else{curElem.css(props)}}};jQuery.fn.extend({position:function(){if(!this[0]){return}var elem=this[0],offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=rroot.test(offsetParent[0].nodeName)?{top:0,left:0}:offsetParent.offset();offset.top-=parseFloat(jQuery.css(elem,"marginTop"))||0;offset.left-=parseFloat(jQuery.css(elem,"marginLeft"))||0;parentOffset.top+=parseFloat(jQuery.css(offsetParent[0],"borderTopWidth"))||0;parentOffset.left+=parseFloat(jQuery.css(offsetParent[0],"borderLeftWidth"))||0;return{top:offset.top-parentOffset.top,left:offset.left-parentOffset.left}},offsetParent:function(){return this.map(function(){var offsetParent=this.offsetParent||document.body;while(offsetParent&&(!rroot.test(offsetParent.nodeName)&&jQuery.css(offsetParent,"position")==="static")){offsetParent=offsetParent.offsetParent}return offsetParent||document.body})}});jQuery.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(method,prop){var top=/Y/.test(prop);jQuery.fn[method]=function(val){return jQuery.access(this,function(elem,method,val){var win=getWindow(elem);if(val===undefined){return win?prop in win?win[prop]:win.document.documentElement[method]:elem[method]}if(win){win.scrollTo(!top?val:jQuery(win).scrollLeft(),top?val:jQuery(win).scrollTop())}else{elem[method]=val}},method,val,arguments.length,null)}});function getWindow(elem){return jQuery.isWindow(elem)?elem:elem.nodeType===9?elem.defaultView||elem.parentWindow:false}jQuery.each({Height:"height",Width:"width"},function(name,type){jQuery.each({padding:"inner"+name,content:type,"":"outer"+name},function(defaultExtra,funcName){jQuery.fn[funcName]=function(margin,value){var chainable=arguments.length&&(defaultExtra||typeof margin!=="boolean"),extra=defaultExtra||(margin===true||value===true?"margin":"border");return jQuery.access(this,function(elem,type,value){var doc;if(jQuery.isWindow(elem)){return elem.document.documentElement["client"+name]}if(elem.nodeType===9){doc=elem.documentElement;return Math.max(elem.body["scroll"+name],doc["scroll"+name],elem.body["offset"+name],doc["offset"+name],doc["client"+name])}return value===undefined?jQuery.css(elem,type,value,extra):jQuery.style(elem,type,value,extra)},type,chainable?margin:undefined,chainable,null)}})});window.jQuery=window.$=jQuery;if(typeof define==="function"&&define.amd&&define.amd.jQuery){define("jquery",[],function(){return jQuery})}})(window); \ No newline at end of file diff --git a/Web Server/getstatus.php b/Web Server/getstatus.php new file mode 100644 index 0000000..9c3e7ac --- /dev/null +++ b/Web Server/getstatus.php @@ -0,0 +1,181 @@ +$result_fmmx,"flexnet"=>$result_flexnet,"fxinv"=>$result_fxinv); + $allday_data["summary"] = mysql_fetch_assoc($result_summary); + + foreach ($allday_querys as $i) { + while($row = mysql_fetch_assoc($i)){ + $allday_data[$row["device_id"]][$row["address"]][] = $row; + } + } + + //var_dump($result_fmmx); + //echo $query_fmmx; + //$allday = array("summary"=>$result_summary,"fmmx"=>$result_fmmx,"flexnet"=>$result_flexnet,"fxinv"=>$result_fxinv); + + $json_summary = json_encode($allday_data); + echo $json_summary; +} + + +function db_connection() { + global $dbpass; + global $dbuser; + global $dbname; + global $dbhost; + $connection = mysql_connect($dbhost, $dbuser, $dbpass); + mysql_select_db($dbname, $connection); + return $connection; +} + +?> \ No newline at end of file diff --git a/Web Server/images/favicon16.png b/Web Server/images/favicon16.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4ecc6cc6f6329ef0be90aa133508f2a1a0a8e2 GIT binary patch literal 19923 zcmeI42{@Er-^Xv1vLs6?MJ7v1#>^OF8H|0aENQGQ8Z$R$Ff(R`L5en1^4B6lT9i_j zB!mjti&j#$JQOM^B!qfrsNvsV&oj^af8O`K-s_sVt}#F7{C?ka?$3SB@0@eZbMV!!!-#N_1wAgV?*HMO_* zrZ6aUZweJ+ZE6ak(kPxpUl0I-nlqi5SnSC2wt_lQ(Q8N}U!rp9lHwfwhI8jCc9uolAK%X>RWdR;@I0g> zuYbGOhvG+=@#I}i+us9jU-(aFgf|EZUlI;BAuKe68ti{mBqG*O7NOTS@1CS9H8fQ1 z+tBLprY2*cR9a>4Ca3W4{k>5Y$|m7a3u2X3C6@A6z<_$``>%z=pNN){u9~C)%g+Fx zUlZTT09^vWo#dOJMZ?n<8NqxY`GV(E>^3Z%pU@t@!V(hO1GK_{2?tyZf5%}!XQR2~ zy3*}J5Tk_k;(V>w0E?wdmmUJ3!1YI8lWZRvYUW00&fk}OKIq&sAlxnCqL~vUA?Ew= zIyHN-=huPK+Bi#P;ITl|@R&J52+#p?+!Ik4v2b-G*<(?FV{78I=nYN-EeDm)%K)N4 zXPlMzQi0NWkeBx-Uhj1DT^}nT5UOEzAGhJ42NyS7dRp<65GSwdoVL)~e&7LN z{y>iEZHH~*r-4$*bNO*6*NAneIMtlM($lhfgsQ3*2c@OdEE%v?HKa%!u|KePvGQn* ziNqj;_#x}Pp!={AKYz_J0YoykW~YVfyjW9VKFQ4yhN}-QS2hh^Ep8-+*s!{?dNre? z|A6m8h~>J9wZ@oL#xW+%31a6adtK1sViyFwotu-s$kcWDEOG)PkmU)R>lSuLt(G@g zavm^`H64_{zI=(1sPb!ZQ}l~+FM}#YiVxgBXJrh?2g_ZLG2f|IWCyh6tehUEfcN#||tz0wu2a-$9YS5u0N z6^&w4~AMlr$jxvBFD6p^}Po z+2T{R!W9$V>C^_RyVr7$tc*RCQYJqN-qr;8!r?k2GV96nLRA5U9^{(q{9=auLD`0Q zCm8ThqoY^(yu%K3_!FQMSn2k7zeL^6`MY$=q*Febir+&l^|vxXuQ83gLB z+G+*0a@CCTHNKU$69)vYluIO+5|gWyx0UI6Ng*;;a)Za30aQ-Z6{Y7ANr$#qJgUHT&htOztw@#_Ii%h>e<0{~-cOT6*WhcEXp*N*m!1G$^V5V1BU(-RSNqgTy$&8+@zNHfylb8Pij#bx zvF_$}>{@9{(EjDgQBv_+Sp1t#xz^h3imPq2N`paj$4bg{f(+)Ba21z?M zWv4Qq<}r}F58kyKd3<(xqNr4aVtKqoy%qj7JWb=ijAQhNXw&E|(LT{)RX%G@5KC|3 zLlCJ6Z|n5CMY}z^1-mhxin&L}VqZS+$cc7|Q44dD-B{{y{l2S@>n@jS*LYXCi~`aM zQa34!BteoPIpmFz97)BEG3nc#c4a)joRvvV?Z39_vbw|Bth?CVNg?~cnh%Q&WAepX zVm>^w_2|jz$!WSia>Fj?<8^nEx3>#v)O*w8Sns+<3WTTZ)Nc$*9az`Z|BsOJ$q121XHi4=1XJp1MVV3=A)?ml zgvgpZX`X4v(lnmsrK#8y*NfKes~fCyN|Zys*XTLDAYo&jbE3R<*uAEn1w$gEKBM&$ zt3;%ZtQEOb>3PKI2xg<+1Hyx`Q{!qM-EuN-XWqzkaznWeyCiM>BR7S(9Y0xowCHGo zO;$ZAX=##neqerj{;THaF9Vxpn$26{UN*jH%W1l~_$Dm>l}A{>JoBGLmb}_t!w8gv$%ygD*$t@LcK`1m~Mw5E{kmF*$z1MNrdJQ{_EnuKo$NRcN1j(c7O$i1&+L4EG4|pm>w*&> zPQ=yPtaDo@wI%SKAz$8!u)N3MO!cG}&Dc!KvB zt9XB+Z;ee7Bk;z+>wUGBm%VKyHt8nR)@-V8Ju_x3uJS^mxBl=$x3i6kqS9GQG1Bu7 zIUO#l_|~~$pv(7r-~GOavfq=Xb+;GpEp#kwJ8oZ#t{u94<+e>QWw2|I`Fdp8#if1g zvFq#9^wk>Ux;G{rsXLq|omVRhzPa(m<5j0)rNQ9xu`iMr2oBbFOzc)~*=FCozGQPd zY|+|G_1$_X@Ld@Z{p{j1^-au#yVS2;H==IHdp$-TKqh*+)g@~Weaq-@P4be;&Gpo- z&q>VPXe$v7;hep``sOyxIdPC*LJc9xA*P7LXlN_q{eJ)bPKla*weMOgrdFM zGE8MGE8u*~y*v>^$0*f@P}&Y`UT?X_KRW5JibG4f$G<7R+~^h`qTY5Zx^Pob-`By? zH+t&>R3E6yXDu(d5U3mmR^E5MmX?9u`esKS5w$Ci7BHUp0yXTD*`&U89Jjr{$ft32 zII)fJXpD3tG|sZBAhOcjBI%OlwhjX1l{L<#B6>YInowXA+m~ z^5+%E8g_+s*SNmQjeHb*_L| zZ`>8XMKbY`UZ)JXbA`4OAR(y!OMyQ2PBbk+mY_7>LnF<$63KLE@RuX`xCqYu!0#@DVK zUN85e5L>J;5bQJYy>ss!QJ_PpZz2Ej9{!x8rBxQCp6aEZ~+w1X(mj;|LTFP(8?x$~t8LfWBc66^Hi$84wT9i%2$5_*h(` z03i|#6r52ua2u*A=uNZ=p@CaMY_a$dAG|I>!4M;?AB1KJ_<;-@B*@Q~Oh*S9C`{?1 zSvI>ErU038VfYv*7_%FMxY*c3Oer)Ff>K98@oS7Wbp=0&%o$0?22(+p14yR?(D0ynAgd>eb9J81Z~$lEav{ZXpHqnX^PD<&zc|&u z#mFFf{-z`Bn%{9D_o`QYgNd>ESwa%4Qw1*V^Fl zL^Au-y-pwYtN+ZXnUMm`a10Q`9@VVz3`HQY2u(Bsjz%D$a2+%pK1*cU^A`;p)(j%x z7`VArG_+@{{K@xM9SVWy8O)W+jOQ#30v_#2q50t$7@{A}3xrY0Uiz?^!daeELl62-z@9xnqzNo(5D97_L2Vr7)ZmpGN9s5lxO zWKUrOg`abnlhimliybqS3eY$_JG)`<>~Ree^kIK0o#_@Q@<$l)uQ!l8%G3I@!hhce z(;FoJv#BvHHRbht5jw?_!Nk!(V=va2_?J_5TJp~^ziR8l*h_&g@qgeh&~br(?;hZ^ zJv4NYCVB@!e_a~c z+bPzrz-)SGTCgV+J4OHNm5{ZVu{NdXS*y{{-3Ygd^q25IO~fDSg#Ye_WV&;+!sk+_ z{M|jtbSrbn%(V8C{4Bt;7C(JXnnIgWd?_>=3IW6*^$lKmFwt#*9d( z`r?9T2hXaV70uq*qgfj%)}lU@H8BW%*k8(i^><-!he&c;)NVY-xc@@5_SVVqO{tZ~Ab-2ZuF z=8FFF+8jk*BODam7(86uP~!OTaB)y@WAJcsLy6tP2L(3<4;MF-I6gdF z92DFbJY3vR;`s1zaZqq$@NjWMiQ~h=#X-T1!NbK3C5{gd7Y7A51`iiElsG;-TpSeK z7(86uP~!OTaB)y@WAJcsLy6>+q72O9wJ3jhH6IRKbw1AzSm0HF2)K%X}Nz%i^}{)&CwclsRw@LjYv zGsXrrpFHu1w8U7vDs0jVS&p{-oKPOJY@V~bse5T9p;lm2H0uut-^Z$&6oCO$g;iR!@)qU|xCO1WeVjdE&JeRBF< zy%idZ0zWNSBmGvsXJyHi*i@WUcJ1eV+e}yO-y$THH(W5mf6G>AAJ)R4^6IDKj(az& z8dVop$P6phX7E*h-x_cBxKwq+@agvb6^U{kyTgw#4@redOJz7L-r!b{=5L&(bzz9F z_SK-zIa#fNo5mQ22g-S+NB1|E9i7*p-Y(QIm}mYle5Bks1n@n);dG1feM^l9GS>-rJzaD9)o-I&kUj)_~2^<-_oK6!vCeWT*R)~|fImO{z= m+28U-ihWV&@7G@m07)lrDjqQEw`Ko)g0;D=S(eGpeg6esRIQu< literal 0 HcmV?d00001 diff --git a/web/js/jquery-1.7.1.js b/Web Server/js/jquery-1.7.1.js similarity index 100% rename from web/js/jquery-1.7.1.js rename to Web Server/js/jquery-1.7.1.js diff --git a/web/js/jquery-ui-1.8.17.js b/Web Server/js/jquery-ui-1.8.17.js similarity index 100% rename from web/js/jquery-ui-1.8.17.js rename to Web Server/js/jquery-ui-1.8.17.js diff --git a/Web Server/js/monitormate.js b/Web Server/js/monitormate.js new file mode 100644 index 0000000..f03d54c --- /dev/null +++ b/Web Server/js/monitormate.js @@ -0,0 +1,1605 @@ +/* +Copyright (C) 2011 Jesus Perez +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License at +for more details. +*/ + +var month_names = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); +var chart_content = { + multichart1: "battery_volts", + multichart2: "charge_current", + multichart3: "charge_power", +}; + +var status_content = { + status_top: "summary", + status_bottom: "none", +}; + +var json_status = null; +var full_day_data; +var available_years; +var available_months = []; +var available_month_days; + +// +// this is the crazy way to get shit started in jQuery. Seriously. +// +$( document ).ready(function() { + on_load(); +}); + + +function on_load() { + // moved this from html + get_years(); + + addDateTooltip("years_chart", "y"); + $("#years_chart").bind("plotclick", function (event, pos, item) { + if (item) { + get_months(get_formatted_date(item.datapoint[0])); + } + }); + + // no reason to get url vars anymore... + // get_months(getUrlVars()["date"]); + get_months(); + + addDateTooltip("months_chart", "m"); + + $("#months_chart").bind("plotclick", function (event, pos, item) { + if (item) { + get_month_days(get_formatted_date(item.datapoint[0])); + } + }); + + // no reason to get url vars anymore... + // get_month_days(getUrlVars()["date"]); + get_month_days(); + + addDateTooltip("month_days_chart", "d"); + + $("#month_days_chart").bind("plotclick", function (event, pos, item) { + if (item) { + get_day(get_formatted_date(item.datapoint[0])); + set_chart("multichart1", chart_content["multichart1"]); + set_chart("multichart2", chart_content["multichart2"]); + set_chart("multichart3", chart_content["multichart3"]); + set_status("status_top", status_content["status_top"]); + set_status("status_bottom", status_content["status_bottom"]); + + } + }); + + // no reason to get url vars anymore... + // get_day(getUrlVars()["date"]); + get_day(); + + populate_status(); + get_cookies(); + get_status(); + populate_select("multichart1_select"); + populate_select("multichart2_select"); + populate_select("multichart3_select"); + set_chart("multichart3", chart_content["multichart3"]); + set_chart("multichart2", chart_content["multichart2"]); + set_chart("multichart1", chart_content["multichart1"]); + + setInterval("get_status()", 5000); + +} + + +function getUrlVars() { + var vars = {}; + var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) { + vars[key] = value; + }); + return vars; +} + + +function set_cookies(name, value) { + expire_date = (new Date(new Date().getFullYear() + 1, new Date().getMonth() + 1, new Date().getDate())).toGMTString(); + document.cookie = name + "=" + value + "; expires=" + expire_date; +} + + +function get_cookies() { + if (document.cookie.match('(^|;) ?' + "multichart1" + '=([^;]*)(;|$)')) chart_content["multichart1"] = unescape(document.cookie.match('(^|;) ?' + "multichart1" + '=([^;]*)(;|$)')[2]); + if (document.cookie.match('(^|;) ?' + "multichart2" + '=([^;]*)(;|$)')) chart_content["multichart2"] = unescape(document.cookie.match('(^|;) ?' + "multichart2" + '=([^;]*)(;|$)')[2]); + if (document.cookie.match('(^|;) ?' + "multichart3" + '=([^;]*)(;|$)')) chart_content["multichart3"] = unescape(document.cookie.match('(^|;) ?' + "multichart3" + '=([^;]*)(;|$)')[2]); + if (document.cookie.match('(^|;) ?' + "status_top" + '=([^;]*)(;|$)')) status_content["status_top"] = unescape(document.cookie.match('(^|;) ?' + "status_top" + '=([^;]*)(;|$)')[2]); + if (document.cookie.match('(^|;) ?' + "status_bottom" + '=([^;]*)(;|$)')) status_content["status_bottom"] = unescape(document.cookie.match('(^|;) ?' + "status_bottom" + '=([^;]*)(;|$)')[2]); +} + + +function days_in_month(Year, Month) { + return (new Date((new Date(Year, Month + 1, 1)) - 1)).getDate(); +} + + +function get_formatted_date(date) { + if (!date) { + d = new Date(); + } else { + d = new Date(date) + } + var day = d.getDate(); + var month = d.getMonth() + 1; + var year = d.getFullYear(); + date = year + "-" + month + "-" + day; + return date; +} + +// +// Put the items in the pop-up selection menu for the charts +// +function populate_select(pselect) { + var select_items = []; + + if (full_day_data[4]) { /* FlexNet available */ + for (i in full_day_data[4]) { + select_items.push(''); + select_items.push(''); + } + } + + if (full_day_data[3]) { /* FM/MX charge controller available */ + select_items.push(''); + select_items.push(''); + select_items.push(''); + select_items.push(''); + select_items.push(''); + } + + $('#' + pselect).html(select_items.join('')); +} + + +// +// Put the items in the pop-up selection menu for the status sidebar +// +function populate_status() { + var tabs = []; + + // i is the ID + for (var i in full_day_data) { + if (i == "summary") { + name = "Summary"; + val = "summary"; + //tabs.push(""); + tabs.splice(0, 0, ""); + tabs.splice(1, 0, ''); + } else { + // j is the port number + for (var j in full_day_data[i]) { + var name = ''; + var val = ''; + + + if (deviceLabel[j] != "") { + // Assign name from cfg file + name = deviceLabel[j]; + } else { + + // Assign default name based on ID type + // 2 is an inverter (FX) + // 3 is a Charge Controller (FM/MX) + // 4 is a FLEXnet DC + switch (i) { + case "2": + name = "FX Inverter - Port " + full_day_data[i][j][0].address; + break; + case "3": + name = "FM/MX - Port " + full_day_data[i][j][0].address; + break; + case "4": + name = "FLEXnet DC - Port " + full_day_data[i][j][0].address; + break; + } + } + val = i + ":" + j; + tabs.push(""); + } + } + } + tabs.push(""); + + $("#status_top_select").html(tabs.join('')); + $("#status_bottom_select").html(tabs.join('')); +} + + +function set_status(div, value) { + var data = ''; + var device_id; + var address; + var device; + var value = value; + var div = div; + + // TODO: I hate the use of "value", don't all variables have a value?? + + if (value == "none") { + for (var i in json_status) { + switch (json_status[i].device_id) { + case "3": + value = json_status[i].device_id + ":" + Math.round(json_status[i].address); + break; + } + } + } + + if (value != "summary") { + device_id = value.split(/[:]/)[0]; + address = value.split(/[:]/)[1]; + device = json_status["device" + address]; + } else { + device_id = "summary" + address = "summary" + device = full_day_data["summary"]; + } + + var content = ''; + switch (device_id) { + + case "summary": + content = '
\ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
Summary
' + device.date + '
kWh In:' + device.kwh_in + ' kWh
kWh Out:' + device.kwh_out + ' kWh
Ah In:' + device.ah_in + ' Ah
Ah Out:' + device.ah_out + ' Ah
Max SOC:' + device.max_soc + ' %
Min SOC:' + device.min_soc + ' %
Max Temp:' + device.max_temp + ' °C (' + ((device.max_temp * 1.8) + 32).toFixed(1) + ' °F)
Min Temp:' + device.min_temp + ' °C (' + ((device.min_temp * 1.8) + 32).toFixed(1) + ' °F)
Max PV Voltage:' + device.max_pv_voltage + ' V
'; + break; + + case "2": // fx inverter + content = '\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
FX Inverter
Port ' + device.address + '
AC Output Voltage:' + device.ac_output_voltage + ' V
Inverter Current:' + device.inverter_current + ' A
Charge Current:' + device.charge_current + ' A
AC Input Voltage:' + device.ac_input_voltage + ' V
Buy Current:' + device.buy_current + ' A
Sell Current:' + device.sell_current + ' A
AC Mode:' + device.ac_mode + '
Operational Mode:' + device.operational_mode + '
Error Modes:' + device.error_modes + '
Warning Modes:' + device.warning_modes + '
Misc:' + device.misc + '
'; + break; + + case "3": // charge controller + content = '\ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
FX/MX Charge Controller
Port ' + device.address + '
Charge Current:' + device.charge_current + ' A
Charge Mode:' + device.charge_mode + '
PV Current:' + device.pv_current + ' A
PV Voltage:' + device.pv_voltage + ' V
Daily kWh:' + device.daily_kwh + ' kWh
Daily Ah:' + device.daily_ah + ' Ah
Battery Voltage:' + device.battery_volts + ' V
Error Modes:' + device.error_modes + '
Aux Mode:' + device.aux_mode + '
'; + break; + + case "4": // flexnet dc + content = '\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
FLEXnet DC
Port ' + device.address + '
SOC:' + device.soc + '%
Battery Voltage:' + device.battery_volt + ' V
Battery Temperature:' + device.battery_temp + ' °C (' + ((device.battery_temp * 1.8) + 32).toFixed(1) + ' °F)
Charge Parameters Met:' + device.charge_params_met + '
Days Since Full:' + (Math.round(device.days_since_full * 100) / 100) + ' Days
Charge Corrected Net:' + device.charge_factor_corrected_net_batt_ah + ' Ah, ' + device.charge_factor_corrected_net_batt_kwh + ' kWh
Relay Mode:' + device.relay_mode + '
Relay Status:' + device.relay_status + '
Shunts
' + shuntLabel[1] + ':' + device.shunt_a_amps + ' A
' + shuntLabel[2] + ':' + device.shunt_b_amps + ' A
' + shuntLabel[3] + ':' + device.shunt_c_amps + ' A
Today\'s Net
Input Ah:' + device.today_net_input_ah + ' Ah, ' + device.today_net_input_kwh + ' kWh
Output Ah:' + device.today_net_output_ah + ' Ah ' + device.today_net_output_kwh + ' kWh
Accumulation
' + shuntLabel[1] + ':' + device.accumulated_ah_shunt_a + ' Ah, ' + device.accumulated_kwh_shunt_a + ' kWh
' + shuntLabel[2] + ':' + device.accumulated_ah_shunt_b + ' Ah, ' + device.accumulated_kwh_shunt_b + ' kWh
' + shuntLabel[3] + ':' + device.accumulated_ah_shunt_c + ' Ah, ' + device.accumulated_kwh_shunt_c + ' kWh
'; + break; + + } + + status_content[div] = value; + $('#' + div).html(content); + $('#' + div + '_select').val(value); + set_cookies(div, value); +} + + +function addTooltip(chart_id, units, description) { + var previousPoint = null; + var description = description; + $("#" + chart_id).bind("plothover", function (event, pos, item) { + $("#x").text(pos.x.toFixed(2)); + $("#y").text(pos.y.toFixed(2)); + + if (item) { + if (previousPoint != item.dataIndex) { + previousPoint = item.dataIndex; + + $("#tooltip").remove(); + var x = item.datapoint[0].toFixed(2), + y = item.datapoint[1].toFixed(2); + + + var hour = new Date(item.datapoint[0]).getUTCHours(); + var minutes = new Date(item.datapoint[0]).getMinutes(); + if (minutes < 10) { //format minutes + minutes = "0" + minutes; + } + y = item.datapoint[1].toFixed(2); + + var full_hour = hour + ":" + minutes; + fdescription = description; + if (!description) { + fdescription = item.series.label; + } + showTooltip(item.pageX, item.pageY, fdescription + " " + y + units + " - " + full_hour); + } + } else { + $("#tooltip").remove(); + previousPoint = null; + } + + }); + + +} + + +function addDateTooltip(chart_id, description) { + + var previousPoint = null; + var description = description; + $("#" + chart_id).bind("plothover", function (event, pos, item) { + $("#x").text(pos.x.toFixed(2)); + $("#y").text(pos.y.toFixed(2)); + + if (item) { + if (previousPoint != item.dataIndex) { + previousPoint = item.dataIndex; + + $("#tooltip").remove(); + var x = item.datapoint[0].toFixed(2), + y = item.datapoint[1].toFixed(2); + + + var hour = new Date(item.datapoint[0]).getUTCHours(); + var minutes = new Date(item.datapoint[0]).getMinutes(); + if (minutes < 10) { //format minutes + minutes = "0" + minutes; + } + y = item.datapoint[1].toFixed(2); + + var full_hour = hour + ":" + minutes; + if (description == "y") { + + date = new Date(item.datapoint[0]).getFullYear(); + } + else if (description == "m") { + date = (new Date(item.datapoint[0]).getFullYear()) + "-" + ((new Date(item.datapoint[0]).getMonth()) + 1); + + i + } else { + date = get_formatted_date(item.datapoint[0]); + } + showTooltip(item.pageX, item.pageY, date + "-> " + y + " " + item.series.label); + } + } else { + $("#tooltip").remove(); + previousPoint = null; + } + + }); +} + + +function showTooltip(x, y, contents) { + $('
' + contents + '
').css({ + position: 'absolute', + display: 'none', + top: y + 15, + left: x + 10, + border: '1px solid #fdd', + padding: '2px', + 'background-color': '#fee', + opacity: 0.80 + }).appendTo("body").fadeIn(200); +} + +// +// Getters for SQL data in order to make the charts +// + +function get_status() { + + if (json_status) { + $.getJSON("./matelog", function (data) { + json_status = data; + }); + } else { + $.ajax({ + async: false, + type: 'GET', + dataType: 'json', + url: 'matelog', + success: function (data) { + json_status = data; + } + }); + } + set_status("status_top", status_content["status_top"]); + set_status("status_bottom", status_content["status_bottom"]); +} + + +function get_years() { + + years_data_kwhin = []; + years_data_kwhout = []; + date = get_formatted_date(); + //Get all years in database + status = $.ajax({ + async: false, + type: 'GET', + dataType: 'json', + url: 'getstatus.php?q=years&date=' + date, + success: function (data) { + available_years = data; + } + }) + + + + //Fill array with series + for (i = 0; i < available_years.length; i++) { + + comp_date = available_years[i].date.split(/[- :]/); + comp_date = new Date(comp_date[0], 0, 1); + comp_date = (new Date(((comp_date).getFullYear()), 0, 1)); + + staggerDateLeft = new Date(comp_date.setMonth(comp_date.getMonth() - 6)); + staggerDateRight = new Date(comp_date.setMonth(comp_date.getMonth() + 6)); + + years_data_kwhin[i] = [staggerDateLeft, (Math.round(available_years[i].kwh_in * 100) / 100)]; + years_data_kwhout[i] = [staggerDateRight, (Math.round(available_years[i].kwh_out * 100) / 100)]; + + } + + //Chart options + years_options = { + + bars: { + show: true, + clickable: true, + barWidth: 60 * 60 * 1000 * 24 * (365 / 2), + align: "left" + }, + //Necesario para que las barras tengan el ancho adecuado + xaxis: { + tickSize: [1, "year"], + mode: "time", + timeformat: "%Y", + + min: years_data_kwhin[0][0], + max: (new Date(((new Date(years_data_kwhin[years_data_kwhin.length - 1][0])).getFullYear() + ((years_data_kwhin.length < 6) ? (6 - years_data_kwhin.length) : 0)), 11, 31)), + + clickable: true, + hoverable: true + }, + + grid: { + hoverable: true, + clickable: true + } + }; + + + $.plot($("#years_chart"), + [{ + label: 'kWh in', + data: years_data_kwhin + }, { + label: 'kWh out', + data: years_data_kwhout + }], years_options); + +} + + +function get_months(date) { + + var months_data_kwhin = []; + var months_data_kwhout = []; + var months_options; + + if (!date) { + date = get_formatted_date(); + } + + $.ajax({ + async: false, + type: 'GET', + dataType: 'json', + url: 'getstatus.php?q=months&date=' + date, + success: function (data) { + available_months = data; + + } + }); + + + //Fill array with series + for (i = 0; i < available_months.length; i++) { + + split_date = available_months[i].date.split(/[- :]/); + month_date = (new Date(split_date[0], split_date[1] - 1, 1)); + + staggerDateLeft = new Date(month_date.setDate(month_date.getDate() - 10)); + staggerDateRight = new Date(month_date.setDate(month_date.getDate() + 10)); + + + months_data_kwhin[i] = [staggerDateLeft, (Math.round(available_months[i].kwh_in * 100) / 100)]; + months_data_kwhout[i] = [staggerDateRight, (Math.round(available_months[i].kwh_out * 100) / 100)]; + + } + + // Determine the min and max for the chart + // why the heck am i getting a min and max and basing this all on the data + // instead of the clock or data that's passed to this function?? + minYearData = new Date(months_data_kwhin[0][0]).getFullYear(); + maxYearData = new Date(months_data_kwhin[0][0]).getFullYear(); + + chartMin = new Date(minYearData - 1, 11, 1); // December(11) 1st of the previous year (to provide proper margins) + chartMax = new Date(maxYearData, 11, 31); // December(11) 31st of the year. + + months_options = { + bars: { + show: true, + clickable: true, + barWidth: 60 * 60 * 1000 * 24 * 10, /* room for 2 bars and a space for each of the 12 months */ + lineWidth: 1, + align: "left" + }, + xaxis: { + tickSize: [1, "month"], + monthNames: month_names, + mode: "time", + timeformat: "%b", + + min: chartMin, + max: chartMax, + + clickable: true, + hoverable: true + }, + grid: { + hoverable: true, + clickable: true + } + }; + + + $.plot($("#months_chart"), [{ + label: 'kWh in', + data: months_data_kwhin + }, { + label: 'kWh out', + data: months_data_kwhout + }], months_options); + + + + +} + + +function get_month_days(date) { + + var month_days_data_kwhin = []; + var month_days_data_kwhout = []; + var month_days_options; + + if (!date) { + date = get_formatted_date(); + } + + $.ajax({ + async: false, + type: 'GET', + dataType: 'json', + url: 'getstatus.php?q=month_days&date=' + date, + success: function (data) { + available_month_days = data; + } + }); + + //Fill array with series + for (i = 0; i < available_month_days.length; i++) { + + split_date = available_month_days[i].date.split(/[- :]/); + month_days_date = (new Date(split_date[0], split_date[1] - 1, split_date[2])); + + staggerDateLeft = new Date(month_days_date.setHours(month_days_date.getHours() - 8)); + staggerDateRight = new Date(month_days_date.setHours(month_days_date.getHours() + 8)); + + month_days_data_kwhin[i] = [staggerDateLeft, (Math.round(available_month_days[i].kwh_in * 100) / 100)]; + month_days_data_kwhout[i] = [staggerDateRight, (Math.round(available_month_days[i].kwh_out * 100) / 100)]; + } + + // Determine the min and max for the chart + // why the heck am i getting a min and max and basing this all on the data + // instead of the clock or data that's passed to this function?? + chartMin = new Date(split_date[0], split_date[1] - 1, 0); + chartMax = new Date(split_date[0], split_date[1] - 1, days_in_month(split_date[0], split_date[1] - 1), 23, 59); + + month_days_options = { + + bars: { + show: true, + clickable: true, + barWidth: 60 * 60 * 1000 * (24/3), /* room for 2 bars and a space for each of the ~30 days */ + lineWidth: 1, + align: "right" + }, + + legend: { + backgroundOpacity: 0 + }, + + xaxis: { + tickSize: [1, "day"], + mode: "time", + timeformat: "%d", + min: chartMin, + max: chartMax, + clickable: true, + hoverable: true + }, + + grid: { + hoverable: true, + clickable: true + } + }; + + + $.plot($("#month_days_chart"), [{ + label: 'kWh in', + data: month_days_data_kwhin + }, { + label: 'kWh out', + data: month_days_data_kwhout + }], month_days_options); + + + +} + + +function get_day(date) { + var chart_data; + + if (!date) { + date = get_formatted_date(); + } + + $.ajax({ + async: false, + type: 'GET', + dataType: 'json', + url: 'getstatus.php?q=day&date=' + date, + success: function (data) { + full_day_data = data; + + } + }); + + + + + + +} + + +function set_chart(chart_id, content) { + var chart_data = 0; + var units; + var tooltip_desc; + if (!content) { + content = chart_content[chart_id]; + } + chart_content[chart_id] = content; + switch (content) { + case "charge_power": + chart_data = charge_power(); + units = "W"; + break; + case "battery_volts": + chart_data = battery_volts(); + units = "V"; + break; + case "charge_current": + chart_data = charge_current(); + units = "A"; + break; + case "array_volts": + chart_data = array_volts(); + units = "V"; + break; + case "array_current": + chart_data = array_current(); + units = "A"; + break; + case "flexnet_shunts": + chart_data = flexnet_shunts(); + units = "A"; + break; + case "flexnet_soc": + chart_data = flexnet_soc(); + units = "%"; + tooltip_desc = " "; + break; + default: + chart_data = null; + break; + + } + $('#' + chart_id + '_select').val(content); + set_cookies(chart_id, content); + + $.plot($('#' + chart_id), chart_data[0], chart_data[1]); + addTooltip(chart_id, units, tooltip_desc); + +} + + +function flexnet_shunts() { + + var day_data_shunt_a = []; + var day_data_shunt_b = []; + var day_data_shunt_c = []; + + + for (var i in full_day_data["4"]) { //Device id 4 are FlexNetDC charge controllers + for (y = 0; y < full_day_data["4"][i].length; y++) { + split_date = full_day_data["4"][i][y].date.split(/[- :]/); + day_date = (new Date((new Date(split_date[0], split_date[1] - 1, split_date[2], split_date[3], split_date[4]).getTime() - ((new Date().getTimezoneOffset()) * 60000)))); + total_shunt_a = 0; + total_shunt_b = 0; + total_shunt_c = 0; + + for (var j in full_day_data["4"]) { //Get wh for all FlexNetDC devices + total_shunt_a += (full_day_data["4"][j][y].shunt_a_amps) * 1; + total_shunt_b += (full_day_data["4"][j][y].shunt_b_amps) * 1; + total_shunt_c += (full_day_data["4"][j][y].shunt_c_amps) * 1; + + + + } + day_data_shunt_a[y] = [day_date, total_shunt_a]; + day_data_shunt_b[y] = [day_date, total_shunt_b]; + day_data_shunt_c[y] = [day_date, total_shunt_c]; + + + } + break; //Only one iteration + } + + shunts_options = { + lines: { + show: true, + clickable: true, + hoverable: true + }, + shadowSize: 0, + legend: { + position: "nw" + }, + points: { + show: true, + clickable: true, + hoverable: true, + radius: 2, + lineWidth: 1, + }, + xaxis: { + tickDecimals: 1, + minTickSize: [1, "hour"], + mode: "time", + timeformat: "%I%p", + clickable: true, + hoverable: true + }, + grid: { + hoverable: true, + clickable: true + } + + + + } + + var return_var = [ + [{ + label: shuntLabel[1], + data: day_data_shunt_a + }, { + label: shuntLabel[2], + data: day_data_shunt_b + }, { + label: shuntLabel[3], + data: day_data_shunt_c + }], shunts_options]; + + return return_var; + + + + +} + + +function flexnet_soc() { + day_data_soc = []; + + if (full_day_data["4"]) { + for (var i in full_day_data["4"]) { + for (j = 0; j < full_day_data["4"][i].length; j++) { + split_date = full_day_data["4"][i][j].date.split(/[- :]/); + day_date = (new Date((new Date(split_date[0], split_date[1] - 1, split_date[2], split_date[3], split_date[4]).getTime() - ((new Date().getTimezoneOffset()) * 60000)))); + day_data_soc[j] = [day_date, full_day_data["4"][i][j].soc]; + } + } + } + + day_options = { + lines: { + show: true, + clickable: true, + hoverable: true + }, + shadowSize: 0, + legend: { + position: "nw" + }, + points: { + show: true, + clickable: true, + hoverable: true, + radius: 2, + lineWidth: 1, + }, + xaxis: { + tickDecimals: 1, + minTickSize: [1, "hour"], + mode: "time", + timeformat: "%I%p", + clickable: true, + hoverable: true + }, + yaxis: { + tickSize: 10, + min: 50, + max: 105, + }, + grid: { + hoverable: true, + clickable: true + } + } + + // why curly braces inside brackets? i don't know! + day_data = [{ + data: day_data_soc, + color: "rgb(255,206,0)", // default yellow + constraints: [ + { + threshold: 59, // red stuff + color: "rgb(229, 0, 20)", + evaluate : function(y,threshold){ return y <= threshold; } + },{ + threshold: 90, // green! + color: "rgb(0, 201, 20)", + evaluate : function(y,threshold){ return y >= threshold; } + } + ] + }]; + + return [day_data, day_options]; +} + + +function charge_power() { + + var total_day_data_watts = []; + var day_data_watts = new Array(); + var all_devices_data = []; + var charge_mode_fl = []; + var charge_mode_ab = []; + var charge_mode_eq = []; + var count; + + for (var i in full_day_data["3"]) { //Device id 3 are FM/MX charge controllers + for (y = 0; y < full_day_data["3"][i].length; y++) { + split_date = full_day_data["3"][i][y].date.split(/[- :]/); + day_date = (new Date((new Date(split_date[0], split_date[1] - 1, split_date[2], split_date[3], split_date[4]).getTime() - ((new Date().getTimezoneOffset()) * 60000)))); + + total_watts = 0; + for (var j in full_day_data["3"]) { //Get wh for all FM/MX devices + total_watts += (full_day_data["3"][j][y].charge_current) * 1 * full_day_data["3"][i][y].battery_volts; + } + //[day_date, full_day_data["3"][j][y].charge_current*1]; + if (!day_data_watts[i]) { + day_data_watts[i] = [] + }; + + chrg_mode = full_day_data["3"][i][y].charge_mode + + if (chrg_mode == "Float") { + if (!charge_mode_fl[i]) { + charge_mode_fl[i] = [] + }; + charge_mode_fl[i][y] = [day_date, (full_day_data["3"][i][y].charge_current * 1 * full_day_data["3"][i][y].battery_volts)]; + + } + else if (chrg_mode == "Absorption") { + if (!charge_mode_ab[i]) { + charge_mode_ab[i] = [] + }; + charge_mode_ab[i][y] = [day_date, (full_day_data["3"][i][y].charge_current * 1 * full_day_data["3"][i][y].battery_volts)]; + + } + else if (chrg_mode == "EQ") { + if (!charge_mode_eq[i]) { + charge_mode_eq[i] = [] + }; + charge_mode_eq[i][y] = [day_date, (full_day_data["3"][i][y].charge_current * 1 * full_day_data["3"][i][y].battery_volts)]; + + } + + day_data_watts[i][y] = [day_date, (full_day_data["3"][i][y].charge_current * 1 * full_day_data["3"][i][y].battery_volts)]; + total_day_data_watts[y] = [day_date, total_watts]; + } + } + + day_options = { + lines: { + show: true, + clickable: true, + hoverable: true + }, + shadowSize: 0, + legend: { + position: "nw" + }, + points: { + show: true, + clickable: true, + hoverable: true, + radius: 2, + lineWidth: 1, + }, + xaxis: { + tickDecimals: 1, + minTickSize: [1, "hour"], + mode: "time", + timeformat: "%I%p", + clickable: true, + hoverable: true + }, + grid: { + hoverable: true, + clickable: true + } + + + + } + + if (day_data_watts.length > 1) { + for (var i in day_data_watts) { + + if (deviceLabel[i] != "") { + // Assign name from cfg file + chartLabel = deviceLabel[i]; + } else { + chartLabel = 'FM/MX - Port ' + i; + } + + temp = { + label: chartLabel, + data: day_data_watts[i] + }; + all_devices_data.push(temp); + } + } + temp = { + label: 'Total', + data: total_day_data_watts + }; + all_devices_data.push(temp); + + // + // Show Charge Mode - checkbox to show the charge mode + // + if ($("#chk_mode_bc").is(':checked')) { + count = 0; + for (var i in charge_mode_fl) { + count += 1; + if (charge_mode_fl[i]) { + if (count > 1) { + temp = { + color: 4, + // label: "Float", + legend: { + show: false + }, + lines: { + show: false + }, + data: charge_mode_fl[i] + }; + } else { + temp = { + color: 4, + label: "Float", + lines: { + show: false + }, + data: charge_mode_fl[i] + }; + } + all_devices_data.push(temp); + } + } + + count = 0; + for (var i in charge_mode_ab) { + count += 1; + if (charge_mode_ab[i]) { + if (count > 1) { + temp = { + color: 5, + // label: "Absorption", + legend: { + show: false + }, + lines: { + show: false + }, + data: charge_mode_ab[i] + }; + } else { + temp = { + color: 5, + label: "Absorption", + lines: { + show: false + }, + data: charge_mode_ab[i] + }; + } + all_devices_data.push(temp); + } + } + + count = 0; + for (var i in charge_mode_eq) { + count += 1; + if (charge_mode_eq[i]) { + if (count > 1) { + temp = { + color: 6, + // label:"Equalization", + legend: { + show: false + }, + lines: { + show: false + }, + data: charge_mode_eq[i] + }; + } else { + temp = { + color: 6, + label: "Equalization", + lines: { + show: false + }, + data: charge_mode_eq[i] + }; + } + all_devices_data.push(temp); + } + } + } + + var return_var = [all_devices_data, day_options]; + return return_var; +} + + +function charge_current() { + var total_day_data_amps = []; + var day_data_amps = new Array(); + var all_devices_data_amps = []; + var charge_mode_fl = []; + var charge_mode_ab = []; + var charge_mode_eq = []; + var count; + + + + for (var i in full_day_data["3"]) { //Device id 3 are FM/MX charge controllers + for (y = 0; y < full_day_data["3"][i].length; y++) { + split_date = full_day_data["3"][i][y].date.split(/[- :]/); + day_date = (new Date((new Date(split_date[0], split_date[1] - 1, split_date[2], split_date[3], split_date[4]).getTime() - ((new Date().getTimezoneOffset()) * 60000)))); + + total_amps = 0; + for (var j in full_day_data["3"]) { //Get wh for all FM/MX devices + total_amps += (full_day_data["3"][j][y].charge_current) * 1; + } + //[day_date, full_day_data["3"][j][y].charge_current*1]; + if (!day_data_amps[i]) { + day_data_amps[i] = [] + }; + day_data_amps[i][y] = [day_date, full_day_data["3"][i][y].charge_current * 1]; + chrg_mode = full_day_data["3"][i][y].charge_mode + + if (chrg_mode == "Float") { + if (!charge_mode_fl[i]) { + charge_mode_fl[i] = [] + }; + charge_mode_fl[i][y] = [day_date, full_day_data["3"][i][y].charge_current * 1]; + + } + else if (chrg_mode == "Absorption") { + if (!charge_mode_ab[i]) { + charge_mode_ab[i] = [] + }; + charge_mode_ab[i][y] = [day_date, full_day_data["3"][i][y].charge_current * 1]; + + } + else if (chrg_mode == "EQ") { + if (!charge_mode_eq[i]) { + charge_mode_eq[i] = [] + }; + charge_mode_eq[i][y] = [day_date, full_day_data["3"][i][y].charge_current * 1]; + + } + total_day_data_amps[y] = [day_date, total_amps]; + } + } + + day_options = { + lines: { + show: true, + clickable: true, + hoverable: true + }, + shadowSize: 0, + legend: { + position: "nw" + }, + points: { + show: true, + clickable: true, + hoverable: true, + radius: 2, + lineWidth: 1, + }, + xaxis: { + tickDecimals: 1, + minTickSize: [1, "hour"], + mode: "time", + timeformat: "%I%p", + clickable: true, + hoverable: true + }, + grid: { + hoverable: true, + clickable: true + } + + + + + + + + } + if (day_data_amps.length > 1) { + for (var i in day_data_amps) { + + if (deviceLabel[i] != "") { + // Assign name from cfg file + chartLabel = deviceLabel[i]; + } else { + chartLabel = 'FM/MX - Port ' + i; + } + + temp = { + label: chartLabel, + data: day_data_amps[i] + }; + all_devices_data_amps.push(temp); + } + } + + temp = { + label: 'Total', + data: total_day_data_amps + }; + all_devices_data_amps.push(temp); + + + + if ($("#chk_mode_bc").is(':checked')) { + count = 0; + for (var i in charge_mode_fl) { + count += 1; + + if (charge_mode_fl[i]) { + if (count > 1) { + temp = { + color: 4, + // label:"Float", + legend: { + show: false + }, + lines: { + show: false + }, + data: charge_mode_fl[i] + }; + } else { + + temp = { + color: 4, + label: "Float", + lines: { + show: false + }, + data: charge_mode_fl[i] + }; + + + } + all_devices_data_amps.push(temp); + + } + } + + count = 0; + for (var i in charge_mode_ab) { + count += 1; + + if (charge_mode_ab[i]) { + if (count > 1) { + temp = { + color: 5, + //label:"Absorption", + // legend:{show:false}, + lines: { + show: false + }, + data: charge_mode_ab[i] + }; + } else { + + temp = { + color: 5, + label: "Absorption", + lines: { + show: false + }, + data: charge_mode_ab[i] + }; + + + } + all_devices_data_amps.push(temp); + + } + } + + count = 0; + for (var i in charge_mode_eq) { + count += 1; + + if (charge_mode_eq[i]) { + if (count > 1) { + temp = { + color: 6, + // label:"Equalization", + // legend:{show:false}, + lines: { + show: false + }, + data: charge_mode_eq[i] + }; + } else { + + temp = { + color: 6, + label: "Equalization", + lines: { + show: false + }, + data: charge_mode_eq[i] + }; + + + } + all_devices_data_amps.push(temp); + + } + } + + + } + + var return_var = [all_devices_data_amps, day_options]; + + return return_var; + +} + + +function array_volts() { + var total_day_data__array_volts = []; + var day_data_array_volts = new Array(); + var all_devices_data_array_volts = [] + + + + for (var i in full_day_data["3"]) { //Device id 3 are FM/MX charge controllers + for (y = 0; y < full_day_data["3"][i].length; y++) { + split_date = full_day_data["3"][i][y].date.split(/[- :]/); + day_date = (new Date((new Date(split_date[0], split_date[1] - 1, split_date[2], split_date[3], split_date[4]).getTime() - ((new Date().getTimezoneOffset()) * 60000)))); + + + //[day_date, full_day_data["3"][j][y].charge_current*1]; + if (!day_data_array_volts[i]) { + day_data_array_volts[i] = [] + }; + day_data_array_volts[i][y] = [day_date, full_day_data["3"][i][y].pv_voltage * 1]; + } + } + + day_options = { + lines: { + show: true, + clickable: true, + hoverable: true + }, + shadowSize: 0, + legend: { + position: "nw" + }, + points: { + show: true, + clickable: true, + hoverable: true, + radius: 2, + lineWidth: 1, + }, + xaxis: { + tickDecimals: 1, + minTickSize: [1, "hour"], + mode: "time", + timeformat: "%I%p", + clickable: true, + hoverable: true + }, + grid: { + hoverable: true, + clickable: true + } + + + + } + + + for (var i in day_data_array_volts) { + + if (deviceLabel[i] != "") { + // Assign name from cfg file + chartLabel = deviceLabel[i]; + } else { + chartLabel = 'FM/MX - Port ' + i; + } + + temp = { + label: chartLabel, + data: day_data_array_volts[i] + }; + all_devices_data_array_volts.push(temp); + + } + + var return_var = [all_devices_data_array_volts, day_options]; + return return_var; + +} + + +function array_current() { + var total_day_data__array_amps = []; + var day_data_array_amps = new Array(); + var all_devices_data_array_amps = [] + + + + for (var i in full_day_data["3"]) { //Device id 3 are FM/MX charge controllers + for (y = 0; y < full_day_data["3"][i].length; y++) { + split_date = full_day_data["3"][i][y].date.split(/[- :]/); + day_date = (new Date((new Date(split_date[0], split_date[1] - 1, split_date[2], split_date[3], split_date[4]).getTime() - ((new Date().getTimezoneOffset()) * 60000)))); + + + //[day_date, full_day_data["3"][j][y].charge_current*1]; + if (!day_data_array_amps[i]) { + day_data_array_amps[i] = [] + }; + day_data_array_amps[i][y] = [day_date, full_day_data["3"][i][y].pv_current * 1]; + } + } + + day_options = { + lines: { + show: true, + clickable: true, + hoverable: true + }, + shadowSize: 0, + legend: { + position: "nw" + }, + points: { + show: true, + clickable: true, + hoverable: true, + radius: 2, + lineWidth: 1, + }, + xaxis: { + tickDecimals: 1, + minTickSize: [1, "hour"], + mode: "time", + timeformat: "%I%p", + clickable: true, + hoverable: true + }, + grid: { + hoverable: true, + clickable: true + } + + + + } + + + for (var i in day_data_array_amps) { + + if (deviceLabel[i] != "") { + // Assign name from cfg file + chartLabel = deviceLabel[i]; + } else { + chartLabel = 'FM/MX - Port ' + i; + } + + temp = { + label: chartLabel, + data: day_data_array_amps[i] + }; + all_devices_data_array_amps.push(temp); + + } + + var return_var = [all_devices_data_array_amps, day_options]; + + + + return return_var; +} + + +function battery_volts() { + day_data_volts = []; + + + if (full_day_data["4"]) { + for (var i in full_day_data["4"]) { + for (j = 0; j < full_day_data["4"][i].length; j++) { + split_date = full_day_data["4"][i][j].date.split(/[- :]/); + day_date = (new Date((new Date(split_date[0], split_date[1] - 1, split_date[2], split_date[3], split_date[4]).getTime() - ((new Date().getTimezoneOffset()) * 60000)))); + day_data_volts[j] = [day_date, full_day_data["4"][i][j].battery_volt]; + + } + } + } else { + for (var i in full_day_data["3"]) { + for (j = 0; j < full_day_data["3"][i].length; j++) { + split_date = full_day_data["3"][i][j].date.split(/[- :]/); + day_date = (new Date((new Date(split_date[0], split_date[1] - 1, split_date[2], split_date[3], split_date[4]).getTime() - ((new Date().getTimezoneOffset()) * 60000)))); + day_data_volts[j] = [day_date, full_day_data["3"][i][j].battery_volts]; + + + } + + } + } + + + + + day_options = { + lines: { + show: true, + clickable: true, + hoverable: true + }, + shadowSize: 0, + legend: { + position: "nw" + }, + points: { + show: true, + clickable: true, + hoverable: true, + radius: 2, + lineWidth: 1, + }, + xaxis: { + tickDecimals: 1, + minTickSize: [1, "hour"], + mode: "time", + timeformat: "%I%p", + clickable: true, + hoverable: true + }, + grid: { + hoverable: true, + clickable: true + } + + } + + var return_var = [ + [{ + label: "Volts", + data: day_data_volts + }], day_options]; + + return return_var; + + + +} diff --git a/Web Server/monitormate.html b/Web Server/monitormate.html new file mode 100644 index 0000000..521af17 --- /dev/null +++ b/Web Server/monitormate.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + MonitorMate + + +
+ + +
+
+
+ + +
+ + +
+ + + + +
+ +
+
+
+
+
+ + diff --git a/Web Server/regstatus.php b/Web Server/regstatus.php new file mode 100644 index 0000000..ee07f51 --- /dev/null +++ b/Web Server/regstatus.php @@ -0,0 +1,303 @@ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License at +for more details. +*/ + +if(isset($_POST)){ + ob_start(); //Redirect output to internal buffer + require_once './config/config.php'; + ob_end_clean(); + date_default_timezone_set($timezone); + + if ($_POST["token"] == $token) { + if (!(date('i',time())%$reg_interval)) { + if (isset($_POST["datetime"])) { + $date_time = date('Y-m-d G:i',$_POST["datetime"]); //Date from remote host + } else { + $date_time = date('Y-m-d G:i',time()); //Date from localhost + } + + $flexnet_available = 0; + $fmmx_total = array( + "total_daily_kwh" => 0, + "total_daily_ah" => 0, + ); + + $summary = array( + "date" => date('Y-m-d ', strtotime($date_time)), + "kwh_in" => 0, + "kwh_out" => 0, + "ah_in" => 0, + "ah_out" => 0, + "max_temp" => 0, + "min_temp" => 0, + "min_soc" => 0, + "max_soc" => 0, + "max_pv_voltage" => 0, + ); + + $devices_array = json_decode($_POST["devices"],True); + + foreach ($devices_array as $i) { + switch ($i["device_id"]) { + case '4': //Flexnet device id + $flexnet_available = $i; + register_flexnet($i, $date_time); + break; + case '3': //FM/MX device id + $fmmx_total["total_daily_kwh"] = $fmmx_total["total_daily_kwh"] + $i["daily_kwh"]; + $fmmx_total["total_daily_ah"] = $fmmx_total["total_daily_ah"] + $i["daily_ah"]; + register_fmmx($i, $date_time); + break; + case '2': //FX device id + register_fxinv($i, $date_time); + break; + + default: + break; + } + } + + if ($flexnet_available != 0) { + $summary["kwh_in"] = $flexnet_available["today_net_input_kwh"]; + $summary["kwh_out"] = $flexnet_available["today_net_output_kwh"]; + $summary["ah_in"] = $flexnet_available["today_net_input_ah"]; + $summary["ah_out"] = $flexnet_available["today_net_output_ah"]; + $summary["min_soc"] = $flexnet_available['today_min_soc']; + } else { + $summary["kwh_in"] = $fmmx_total["total_daily_kwh"]; + $summary["ah_in"] = $fmmx_total["total_daily_ah"]; + } + + register_summary($summary); + $file = "matelog"; + $data = $_POST["devices"]; + file_put_contents($file, $data); + + } else { + $file = "matelog"; + $data = $_POST["devices"]; + file_put_contents($file, $data); + } + } +} + + +function register_flexnet($device_array,$date_time) { + + $query = "INSERT INTO monitormate3_flexnet + ( + date, + address, + device_id, + shunt_a_amps, + shunt_b_amps, + shunt_c_amps, + accumulated_ah_shunt_a, + accumulated_kwh_shunt_a, + accumulated_ah_shunt_b, + accumulated_kwh_shunt_b, + accumulated_ah_shunt_c, + accumulated_kwh_shunt_c, + days_since_full, + today_min_soc, + today_net_input_ah, + today_net_output_ah, + today_net_input_kwh, + today_net_output_kwh, + charge_factor_corrected_net_batt_ah, + charge_factor_corrected_net_batt_kwh, + charge_params_met, + relay_mode, + relay_status, + battery_volt, + soc, + shunt_enabled_a, + shunt_enabled_b, + shunt_enabled_c, + battery_temp + ) VALUES ( + '".$date_time."', + '".$device_array['address']."', + '".$device_array['device_id']."', + '".$device_array['shunt_a_amps']."', + '".$device_array['shunt_b_amps']."', + '".$device_array['shunt_c_amps']."', + '".$device_array['accumulated_ah_shunt_a']."', + '".$device_array['accumulated_kwh_shunt_a']."', + '".$device_array['accumulated_ah_shunt_b']."', + '".$device_array['accumulated_kwh_shunt_b']."', + '".$device_array['accumulated_ah_shunt_c']."', + '".$device_array['accumulated_kwh_shunt_c']."', + '".$device_array['days_since_full']."', + '".$device_array['today_min_soc']."', + '".$device_array['today_net_input_ah']."', + '".$device_array['today_net_output_ah']."', + '".$device_array['today_net_input_kwh']."', + '".$device_array['today_net_output_kwh']."', + '".$device_array['charge_factor_corrected_net_batt_ah']."', + '".$device_array['charge_factor_corrected_net_batt_kwh']."', + '".$device_array['charge_params_met']."', + '".$device_array['relay_mode']."', + '".$device_array['relay_status']."', + '".$device_array['battery_volt']."', + '".$device_array['soc']."', + '".$device_array['shunt_enabled_a']."', + '".$device_array['shunt_enabled_b']."', + '".$device_array['shunt_enabled_c']."', + '".$device_array['battery_temp']."' + )"; + + $connection = db_connection(); + mysql_query($query,$connection); +} + + +function register_fmmx($device_array,$date_time){ + $query = "INSERT INTO monitormate3_fmmx + ( + date, + address, + device_id, + charge_current, + pv_current, + pv_voltage, + daily_kwh, + aux_mode, + error_modes, + charge_mode, + battery_volts, + daily_ah + ) VALUES ( + '".$date_time."', + '".$device_array['address']."', + '".$device_array['device_id']."', + '".$device_array['charge_current']."', + '".$device_array['pv_current']."', + '".$device_array['pv_voltage']."', + '".$device_array['daily_kwh']."', + '".$device_array['aux_mode']."', + '".implode(",", $device_array['error_modes'])."', + '".$device_array['charge_mode']."', + '".$device_array['battery_volts']."', + '".$device_array['daily_ah']."' + )"; + + $connection = db_connection(); + mysql_query($query,$connection); +} + + +function register_fxinv($device_array,$date_time){ + $query = "INSERT INTO monitormate3_fxinv + ( + date, + address,device_id, + inverter_current, + charge_current, + buy_current, + ac_input_voltage, + ac_output_voltage, + sell_current, + operational_mode, + error_modes, + ac_mode, + battery_volt, + misc, + warning_modes + ) VALUES ( + '".$date_time."', + '".$device_array['address']."', + '".$device_array['device_id']."', + '".$device_array['inverter_current']."', + '".$device_array['charge_current']."', + '".$device_array['buy_current']."', + '".$device_array['ac_input_voltage']."', + '".$device_array['ac_output_voltage']."', + '".$device_array['sell_current']."', + '".$device_array['operational_mode']."', + '".implode(",", $device_array['error_modes'])."', + '".$device_array['ac_mode']."', + '".$device_array['battery_volt']."', + '".$device_array['misc']."', + '".implode(",", $device_array['warning_modes'])."' + )"; + + $connection = db_connection(); + mysql_query($query,$connection); +} + + +function register_summary($summary) { + + $connection = db_connection(); + + $max_tempq = mysql_query("select max(battery_temp) from monitormate3_flexnet where date(date) ='".$summary['date']."'",$connection); + $max_temp = mysql_fetch_row($max_tempq); + if ($max_temp != NULL) { + $summary['max_temp'] = $max_temp[0]; + } + + $min_tempq = mysql_query("select min(battery_temp) from monitormate3_flexnet where date(date) ='".$summary['date']."'",$connection); + $min_temp = mysql_fetch_row($min_tempq); + if($min_temp != NULL){ + $summary['min_temp'] = $min_temp[0]; + } + + $max_pv_voltageq = mysql_query("select max(pv_voltage) from monitormate3_fmmx where date(date) ='".$summary['date']."'",$connection); + $max_pv_voltage = mysql_fetch_row($max_pv_voltageq); + if($max_pv_voltage != NULL){ + $summary['max_pv_voltage'] = $max_pv_voltage[0]; + } + + $max_socq = mysql_query("select max(soc) from monitormate3_flexnet where date(date) ='".$summary['date']."'",$connection); + $max_soc = mysql_fetch_row($max_socq); + if($max_soc != NULL){ + $summary['max_soc'] = $max_soc[0]; + } + + $not_first_record = mysql_query("select date from monitormate3_summary where date(date) ='".$summary['date']."'",$connection); + $not_first_record = mysql_fetch_row($not_first_record); + + $query = "INSERT INTO monitormate3_summary(date, kwh_in, kwh_out, ah_in, ah_out, max_temp, min_temp, max_soc, min_soc, max_pv_voltage) VALUES ('" + .$summary['date']."','".$summary['kwh_in']."','".$summary['kwh_out']."','".$summary['ah_in']."','" + .$summary['ah_out']."','".$summary['max_temp']."','".$summary['min_temp']."','".$summary['max_soc']."','" + .$summary['min_soc']."','".$summary['max_pv_voltage']."')"; + + $update_query = "UPDATE monitormate3_summary SET kwh_in=".$summary['kwh_in'].", kwh_out=".$summary['kwh_out'].", + ah_in=".$summary['ah_in'].", ah_out=".$summary['ah_out'].", max_temp=".$summary['max_temp'].", min_temp=".$summary['min_temp']. + ", max_soc=".$summary['max_soc'].", min_soc=".$summary['min_soc'].", max_pv_voltage=".$summary['max_pv_voltage']." where date(date)='".$summary['date']."'"; + + $file22 = "matelog22"; + file_put_contents($file22, $update_query); + + if ($not_first_record) { + mysql_query($update_query,$connection); + } else { + mysql_query($query,$connection); + } +} + + +function db_connection() { + global $dbpass; + global $dbuser; + global $dbname; + global $dbhost; + $connection = mysql_connect($dbhost, $dbuser, $dbpass); + mysql_select_db($dbname, $connection); + return $connection; +} + +?> + diff --git a/web/css/monitorsolar.css b/web/css/monitorsolar.css deleted file mode 100644 index ba47a7d..0000000 --- a/web/css/monitorsolar.css +++ /dev/null @@ -1,247 +0,0 @@ - -{ - padding:0; - margin:0; -} - -body { - font-size: 99%; -} -img { - border-width: 0; -} - -#solar { - - width: 100%; - height:100%; - } - -#status_box{ - width: 100%; - height: 440px; - margin-left: 10px; - margin-right: 10px; - margin-bottom: 10px; - - - -} - -.status_tab{ - font: Verdana,"BitStream vera Sans",Tahoma,Helvetica,Sans-serif; - font-size:15px; - float:left; - padding: 3px; - border:solid; - border-top: none; - border-bottom: none; - border-color: #545454; - border-width:1px; - text-align: left; - -} - -#status_box_data{ - width: 35%; - height: 440px; - float: left; - font: Verdana,"BitStream vera Sans",Tahoma,Helvetica,Sans-serif; - font-size:14px; - border:solid; - border-top:none; - border-color: #545454; - border-width:2px; - text-align: left; - - } - -.status_content{ - height: 191px; - width: 100%; - overflow: auto; -} - -.status_title{ - -float: center; - width: 100%; - height: 25px; - background-color: #EDC240; - background-color: rgba(237, 194, 64, 0.5); - text-align: center; - font: 20px "Lucida Grande", Verdana, Arial, sans-serif, bold; - border-top:solid; - border-bottom:solid; - border-width:2px; - border-color: #545454; -} - - -.titulografica{ - -float: center; - width: 100%; - height: 25px; - background-color: #EDC240; - background-color: rgba(237, 194, 64, 0.5); - text-align: center; - font: 20px "Lucida Grande", Verdana, Arial, sans-serif, bold; - border:solid; - #border-bottom:solid; - border-width:2px; - border-color: #545454; - -} - -#right_little_chart{ -width: 50%; -height: 100%; - -text-align:left; - float: right; -} - -#right_little_chart_select_div{ - height: : 100%; - width: 50%; - float:right; -} -#left_little_chart_select_div{ - height: : 100%; - width: 50%; - float:left; -} - -#right_little_chartamparray{ -width: 50%; -height: 100%; - -text-align:left; - float: right; -} - - -#left_little_chart{ -width:50%; -height: 100%; -float: center; -text-align:left; -float: left; - -} -#left_little_chartarray{ -width:50%; -height: 100%; -float: center; -text-align:left; -float: left; - -} - -#month_days_chart{ -width: 100%; -height: 50%; -float: right; -text-align:left; -} -#datebars{ -width: 60%; -height: 100%; -float: left; -text-align:left; -margin-left: 10px; - -} -#months_chart{ -width: 50%; -height: 50%; -float: left; -text-align:left; -} -#years_chart{ -width: 50%; -height: 50%; -float: left; -text-align:left; -} - - -#little_charts{ -width: 95%; -height: 350px; -float: center; - - -} - -#little_chartsarray{ -width: 95%; -height: 350px; -float: center; - -} - -#big_chart{ -width: 95%; -height: 400px; -text-align:left; - - - -} -.display{ -width: 100%; -height: 16px; -text-align:left; - -} -.lbldisplay{ -width: 50%; -height: 100%; -text-align:left; -float : left; -} -.valordisplay{ -width: 50%; -height: 100%; -text-align:left; -float : left; -} - -ul{ - width:760px; - margin-bottom:20px; - overflow:hidden; - border-top:1px solid #ccc; - align:left; -} - -.spanlbl{ -width: 50%; -text-align:left; -float : left; -} - - -.spanval{ -width: 50%; -text-align:left; -float : right; -} - -li{ - line-height:1.5em; - text-align:left; - border-bottom:1px solid #ccc; - float:left; - font: Verdana,"BitStream vera Sans",Tahoma,Helvetica,Sans-serif; - font-size:15px; - font-weight:bold; -} - -.dialogodiv li { width:50%; - align:left; -} - - diff --git a/web/flot/.gitignore b/web/flot/.gitignore deleted file mode 100644 index 4d70bc4..0000000 --- a/web/flot/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.min.js -!excanvas.min.js diff --git a/web/flot/NEWS.txt b/web/flot/NEWS.txt deleted file mode 100644 index 0837919..0000000 --- a/web/flot/NEWS.txt +++ /dev/null @@ -1,557 +0,0 @@ -Flot x.x --------- - -API changes: - -Axis labels are now drawn with canvas text with some parsing to -support newlines. This solves various issues but also means that they -no longer support HTML markup, can be accessed as DOM elements or -styled directly with CSS. Some older browsers lack this function of -the canvas API (this doesn't affect IE); if this is a problem, either -continue using an older version of Flot or try an emulation helper -such as canvas-text or Flashcanvas. - -The base and overlay canvas are now using the CSS classes "flot-base" -and "flot-overlay" to prevent accidental clashes (issue 540). - - -Changes: - -- Canvas text support for labels (sponsored by YCharts.com). - -- Support for setting the interval between redraws of the overlay - canvas with redrawOverlayInterval (suggested in issue 185). - -- Support for multiple thresholds in thresholds plugin (patch by - Arnaud Bellec, issue 523). - -- Support for plotting categories/textual data directly with new - categories plugin. - -- Tick generators now get the whole axis rather than just min/max. - -Bug fixes - -- Fix problem with null values and pie plugin (patch by gcruxifix, - issue 500). -- Fix problem with threshold plugin and bars (based on patch by - kaarlenkaski, issue 348). -- Fix axis box calculations so the boxes include the outermost part of - the labels too. -- Fix problem with event clicking and hovering in IE 8 by updating - Excanvas and removing previous work-around (test case by Ara - Anjargolian). -- Fix issues with blurry 1px border when some measures aren't integer - (reported by Ara Anjargolian). -- Fix bug with formats in the data processor (reported by Peter Hull, - issue 534). - - -Flot 0.7 --------- - -API changes: - -Multiple axes support. Code using dual axes should be changed from -using x2axis/y2axis in the options to using an array (although -backwards-compatibility hooks are in place). For instance, - - { - xaxis: { ... }, x2axis: { ... }, - yaxis: { ... }, y2axis: { ... } - } - -becomes - - { - xaxes: [ { ... }, { ... } ], - yaxes: [ { ... }, { ... } ] - } - -Note that if you're just using one axis, continue to use the -xaxis/yaxis directly (it now sets the default settings for the -arrays). Plugins touching the axes must be ported to take the extra -axes into account, check the source to see some examples. - -A related change is that the visibility of axes is now auto-detected. -So if you were relying on an axis to show up even without any data in -the chart, you now need to set the axis "show" option explicitly. - -"tickColor" on the grid options is now deprecated in favour of a -corresponding option on the axes, so { grid: { tickColor: "#000" }} -becomes { xaxis: { tickColor: "#000"}, yaxis: { tickColor: "#000"} }, -but if you just configure a base color Flot will now autogenerate a -tick color by adding transparency. Backwards-compatibility hooks are -in place. - -Final note: now that IE 9 is coming out with canvas support, you may -want to adapt the excanvas include to skip loading it in IE 9 (the -examples have been adapted thanks to Ryley Breiddal). An alternative -to excanvas using Flash has also surfaced, if your graphs are slow in -IE, you may want to give it a spin: - - http://code.google.com/p/flashcanvas/ - - -Changes: - -- Support for specifying a bottom for each point for line charts when - filling them, this means that an arbitrary bottom can be used - instead of just the x axis (based on patches patiently provided by - Roman V. Prikhodchenko). -- New fillbetween plugin that can compute a bottom for a series from - another series, useful for filling areas between lines (see new - example percentiles.html for a use case). -- More predictable handling of gaps for the stacking plugin, now all - undefined ranges are skipped. -- Stacking plugin can stack horizontal bar charts. -- Navigate plugin now redraws the plot while panning instead of only - after the fact (can be disabled by setting the pan.frameRate option - to null), raised by lastthemy (issue 235). -- Date formatter now accepts %0m and %0d to get a zero-padded month or - day (issue raised by Maximillian Dornseif). -- Revamped internals to support an unlimited number of axes, not just - dual (sponsored by Flight Data Services, - www.flightdataservices.com). -- New setting on axes, "tickLength", to control the size of ticks or - turn them off without turning off the labels. -- Axis labels are now put in container divs with classes, for instance - labels in the x axes can be reached via ".xAxis .tickLabel". -- Support for setting the color of an axis (sponsored by Flight Data - Services, www.flightdataservices.com). -- Tick color is now auto-generated as the base color with some - transparency (unless you override it). -- Support for aligning ticks in the axes with "alignTicksWithAxis" to - ensure that they appear next to each other rather than in between, - at the expense of possibly awkward tick steps (sponsored by Flight - Data Services, www.flightdataservices.com). -- Support for customizing the point type through a callback when - plotting points and new symbol plugin with some predefined point - types (sponsored by Utility Data Corporation). -- Resize plugin for automatically redrawing when the placeholder - changes size, e.g. on window resizes (sponsored by Novus Partners). - A resize() method has been added to plot object facilitate this. -- Support Infinity/-Infinity for plotting asymptotes by hacking it - into +/-Number.MAX_VALUE (reported by rabaea.mircea). -- Support for restricting navigate plugin to not pan/zoom an axis (based - on patch by kkaefer). -- Support for providing the drag cursor for the navigate plugin as an - option (based on patch by Kelly T. Moore). -- Options for controlling whether an axis is shown or not (suggestion - by Timo Tuominen) and whether to reserve space for it even if it - isn't shown. -- New attribute $.plot.version with the Flot version as a string. -- The version comment is now included in the minified jquery.flot.min.js. -- New options.grid.minBorderMargin for adjusting the minimum margin - provided around the border (based on patch by corani, issue 188). -- Refactor replot behaviour so Flot tries to reuse the existing - canvas, adding shutdown() methods to the plot (based on patch by - Ryley Breiddal, issue 269). This prevents a memory leak in Chrome - and hopefully makes replotting faster for those who are using $.plot - instead of .setData()/.draw(). Also update jQuery to 1.5.1 to - prevent IE leaks fixed in jQuery. -- New real-time line chart example. - -- New hooks: drawSeries, shutdown - -Bug fixes: - -- Fixed problem with findNearbyItem and bars on top of each other - (reported by ragingchikn, issue 242). -- Fixed problem with ticks and the border (based on patch from - ultimatehustler69, issue 236). -- Fixed problem with plugins adding options to the series objects. -- Fixed a problem introduced in 0.6 with specifying a gradient with { - brightness: x, opacity: y }. -- Don't use $.browser.msie, check for getContext on the created canvas - element instead and try to use excanvas if it's not found (fixes IE - 9 compatibility). -- highlight(s, index) was looking up the point in the original s.data - instead of in the computed datapoints array, which breaks with - plugins that modify the datapoints (such as the stacking plugin). - Issue 316 reported by curlypaul924. -- More robust handling of axis from data passed in from getData() - (problem reported by Morgan). -- Fixed problem with turning off bar outline (issue 253, fix by Jordi - Castells). -- Check the selection passed into setSelection in the selection - plugin, to guard against errors when synchronizing plots (fix by Lau - Bech Lauritzen). -- Fix bug in crosshair code with mouseout resetting the crosshair even - if it is locked (fix by Lau Bech Lauritzen and Banko Adam). -- Fix bug with points plotting using line width from lines rather than - points. -- Fix bug with passing non-array 0 data (for plugins that don't expect - arrays, patch by vpapp1). -- Fix errors in JSON in examples so they work with jQuery 1.4.2 - (fix reported by honestbleeps, issue 357). -- Fix bug with tooltip in interacting.html, this makes the tooltip - much smoother (fix by bdkahn). Fix related bug inside highlighting - handler in Flot. -- Use closure trick to make inline colorhelpers plugin respect - jQuery.noConflict(true), renaming the global jQuery object (reported - by Nick Stielau). -- Listen for mouseleave events and fire a plothover event with empty - item when it occurs to drop highlights when the mouse leaves the - plot (reported by by outspirit). -- Fix bug with using aboveData with a background (reported by - amitayd). -- Fix possible excanvas leak (report and suggested fix by tom9729). -- Fix bug with backwards compatibility for shadowSize = 0 (report and - suggested fix by aspinak). -- Adapt examples to skip loading excanvas (fix by Ryley Breiddal). -- Fix bug that prevent a simple f(x) = -x transform from working - correctly (fix by Mike, issue 263). -- Fix bug in restoring cursor in navigate plugin (reported by Matteo - Gattanini, issue 395). -- Fix bug in picking items when transform/inverseTransform is in use - (reported by Ofri Raviv, and patches and analysis by Jan and Tom - Paton, issue 334 and 467). -- Fix problem with unaligned ticks and hover/click events caused by - padding on the placeholder by hardcoding the placeholder padding to - 0 (reported by adityadineshsaxena, Matt Sommer, Daniel Atos and some - other people, issue 301). -- Update colorhelpers plugin to avoid dying when trying to parse an - invalid string (reported by cadavor, issue 483). - - -Flot 0.6 --------- - -API changes: - -1. Selection support has been moved to a plugin. Thus if you're -passing selection: { mode: something }, you MUST include the file -jquery.flot.selection.js after jquery.flot.js. This reduces the size -of base Flot and makes it easier to customize the selection as well as -improving code clarity. The change is based on a patch from andershol. - -2. In the global options specified in the $.plot command, -"lines", "points", "bars" and "shadowSize" have been moved to a -sub-object called "series", i.e. - - $.plot(placeholder, data, { lines: { show: true }}) - -should be changed to - - $.plot(placeholder, data, { series: { lines: { show: true }}}) - -All future series-specific options will go into this sub-object to -simplify plugin writing. Backward-compatibility code is in place, so -old code should not break. - -3. "plothover" no longer provides the original data point, but instead -a normalized one, since there may be no corresponding original point. - -4. Due to a bug in previous versions of jQuery, you now need at least -jQuery 1.2.6. But if you can, try jQuery 1.3.2 as it got some -improvements in event handling speed. - - -Changes: - -- Added support for disabling interactivity for specific data series - (request from Ronald Schouten and Steve Upton). - -- Flot now calls $() on the placeholder and optional legend container - passed in so you can specify DOM elements or CSS expressions to make - it easier to use Flot with libraries like Prototype or Mootools or - through raw JSON from Ajax responses. - -- A new "plotselecting" event is now emitted while the user is making - a selection. - -- The "plothover" event is now emitted immediately instead of at most - 10 times per second, you'll have to put in a setTimeout yourself if - you're doing something really expensive on this event. - -- The built-in date formatter can now be accessed as - $.plot.formatDate(...) (suggestion by Matt Manela) and even - replaced. - -- Added "borderColor" option to the grid (patch from Amaury Chamayou - and patch from Mike R. Williamson). - -- Added support for gradient backgrounds for the grid, take a look at - the "setting options" example (based on patch from Amaury Chamayou, - issue 90). - -- Gradient bars (suggestion by stefpet). - -- Added a "plotunselected" event which is triggered when the selection - is removed, see "selection" example (suggestion by Meda Ugo); - -- The option legend.margin can now specify horizontal and vertical - margins independently (suggestion by someone who's annoyed). - -- Data passed into Flot is now copied to a new canonical format to - enable further processing before it hits the drawing routines. As a - side-effect, this should make Flot more robust in the face of bad - data (and fixes issue 112). - -- Step-wise charting: line charts have a new option "steps" that when - set to true connects the points with horizontal/vertical steps - instead of diagonal lines. - -- The legend labelFormatter now passes the series in addition to just - the label (suggestion by Vincent Lemeltier). - -- Horizontal bars (based on patch by Jason LeBrun). - -- Support for partial bars by specifying a third coordinate, i.e. they - don't have to start from the axis. This can be used to make stacked - bars. - -- New option to disable the (grid.show). - -- Added pointOffset method for converting a point in data space to an - offset within the placeholder. - -- Plugin system: register an init method in the $.flot.plugins array - to get started, see PLUGINS.txt for details on how to write plugins - (it's easy). There are also some extra methods to enable access to - internal state. - -- Hooks: you can register functions that are called while Flot is - crunching the data and doing the plot. This can be used to modify - Flot without changing the source, useful for writing plugins. Some - hooks are defined, more are likely to come. - -- Threshold plugin: you can set a threshold and a color, and the data - points below that threshold will then get the color. Useful for - marking data below 0, for instance. - -- Stack plugin: you can specify a stack key for each series to have - them summed. This is useful for drawing additive/cumulative graphs - with bars and (currently unfilled) lines. - -- Crosshairs plugin: trace the mouse position on the axes, enable with - crosshair: { mode: "x"} (see the new tracking example for a use). - -- Image plugin: plot prerendered images. - -- Navigation plugin for panning and zooming a plot. - -- More configurable grid. - -- Axis transformation support, useful for non-linear plots, e.g. log - axes and compressed time axes (like omitting weekends). - -- Support for twelve-hour date formatting (patch by Forrest Aldridge). - -- The color parsing code in Flot has been cleaned up and split out so - it's now available as a separate jQuery plugin. It's included inline - in the Flot source to make dependency managing easier. This also - makes it really easy to use the color helpers in Flot plugins. - -Bug fixes: - -- Fixed two corner-case bugs when drawing filled curves (report and - analysis by Joshua Varner). -- Fix auto-adjustment code when setting min to 0 for an axis where the - dataset is completely flat on that axis (report by chovy). -- Fixed a bug with passing in data from getData to setData when the - secondary axes are used (issue 65, reported by nperelman). -- Fixed so that it is possible to turn lines off when no other chart - type is shown (based on problem reported by Glenn Vanderburg), and - fixed so that setting lineWidth to 0 also hides the shadow (based on - problem reported by Sergio Nunes). -- Updated mousemove position expression to the latest from jQuery (bug - reported by meyuchas). -- Use CSS borders instead of background in legend (fix printing issue 25 - and 45). -- Explicitly convert axis min/max to numbers. -- Fixed a bug with drawing marking lines with different colors - (reported by Khurram). -- Fixed a bug with returning y2 values in the selection event (fix - by exists, issue 75). -- Only set position relative on placeholder if it hasn't already a - position different from static (reported by kyberneticist, issue 95). -- Don't round markings to prevent sub-pixel problems (reported by Dan - Lipsitt). -- Make the grid border act similarly to a regular CSS border, i.e. - prevent it from overlapping the plot itself. This also fixes a - problem with anti-aliasing when the width is 1 pixel (reported by - Anthony Ettinger). -- Imported version 3 of excanvas and fixed two issues with the newer - version. Hopefully, this will make Flot work with IE8 (nudge by - Fabien Menager, further analysis by Booink, issue 133). -- Changed the shadow code for lines to hopefully look a bit better - with vertical lines. -- Round tick positions to avoid possible problems with fractions - (suggestion by Fred, issue 130). -- Made the heuristic for determining how many ticks to aim for a bit - smarter. -- Fix for uneven axis margins (report and patch by Paul Kienzle) and - snapping to ticks (concurrent report and patch by lifthrasiir). -- Fixed bug with slicing in findNearbyItems (patch by zollman). -- Make heuristic for x axis label widths more dynamic (patch by - rickinhethuis). -- Make sure points on top take precedence when finding nearby points - when hovering (reported by didroe, issue 224). - -Flot 0.5 --------- - -Backwards API change summary: Timestamps are now in UTC. Also -"selected" event -> becomes "plotselected" with new data, the -parameters for setSelection are now different (but backwards -compatibility hooks are in place), coloredAreas becomes markings with -a new interface (but backwards compatibility hooks are in place). - - -Interactivity: added a new "plothover" event and this and the -"plotclick" event now returns the closest data item (based on patch by -/david, patch by Mark Byers for bar support). See the revamped -"interacting with the data" example for some hints on what you can do. - -Highlighting: you can now highlight points and datapoints are -autohighlighted when you hover over them (if hovering is turned on). - -Support for dual axis has been added (based on patch by someone who's -annoyed and /david). For each data series you can specify which axes -it belongs to, and there are two more axes, x2axis and y2axis, to -customize. This affects the "selected" event which has been renamed to -"plotselected" and spews out { xaxis: { from: -10, to: 20 } ... }, -setSelection in which the parameters are on a new form (backwards -compatible hooks are in place so old code shouldn't break) and -markings (formerly coloredAreas). - -Timestamps in time mode are now displayed according to -UTC instead of the time zone of the visitor. This affects the way the -timestamps should be input; you'll probably have to offset the -timestamps according to your local time zone. It also affects any -custom date handling code (which basically now should use the -equivalent UTC date mehods, e.g. .setUTCMonth() instead of -.setMonth(). - -Added support for specifying the size of tick labels (axis.labelWidth, -axis.labelHeight). Useful for specifying a max label size to keep -multiple plots aligned. - -Markings, previously coloredAreas, are now specified as ranges on the -axes, like { xaxis: { from: 0, to: 10 }}. Furthermore with markings -you can now draw horizontal/vertical lines by setting from and to to -the same coordinate (idea from line support patch by by Ryan Funduk). - -The "fill" option can now be a number that specifies the opacity of -the fill. - -You can now specify a coordinate as null (like [2, null]) and Flot -will take the other coordinate into account when scaling the axes -(based on patch by joebno). - -New option for bars "align". Set it to "center" to center the bars on -the value they represent. - -setSelection now takes a second parameter which you can use to prevent -the method from firing the "plotselected" handler. - -Using the "container" option in legend now overwrites the container -element instead of just appending to it (fixes infinite legend bug, -reported by several people, fix by Brad Dewey). - -Fixed a bug in calculating spacing around the plot (reported by -timothytoe). Fixed a bug in finding max values for all-negative data -sets. Prevent the possibility of eternal looping in tick calculations. -Fixed a bug when borderWidth is set to 0 (reported by -Rob/sanchothefat). Fixed a bug with drawing bars extending below 0 -(reported by James Hewitt, patch by Ryan Funduk). Fixed a -bug with line widths of bars (reported by MikeM). Fixed a bug with -'nw' and 'sw' legend positions. Improved the handling of axis -auto-scaling with bars. Fixed a bug with multi-line x-axis tick -labels (reported by Luca Ciano). IE-fix help by Savage Zhang. - - -Flot 0.4 --------- - -API changes: deprecated axis.noTicks in favor of just specifying the -number as axis.ticks. So "xaxis: { noTicks: 10 }" becomes -"xaxis: { ticks: 10 }" - -Time series support. Specify axis.mode: "time", put in Javascript -timestamps as data, and Flot will automatically spit out sensible -ticks. Take a look at the two new examples. The format can be -customized with axis.timeformat and axis.monthNames, or if that fails -with axis.tickFormatter. - -Support for colored background areas via grid.coloredAreas. Specify an -array of { x1, y1, x2, y2 } objects or a function that returns these -given { xmin, xmax, ymin, ymax }. - -More members on the plot object (report by Chris Davies and others). -"getData" for inspecting the assigned settings on data series (e.g. -color) and "setData", "setupGrid" and "draw" for updating the contents -without a total replot. - -The default number of ticks to aim for is now dependent on the size of -the plot in pixels. Support for customizing tick interval sizes -directly with axis.minTickSize and axis.tickSize. - -Cleaned up the automatic axis scaling algorithm and fixed how it -interacts with ticks. Also fixed a couple of tick-related corner case -bugs (one reported by mainstreetmark, another reported by timothytoe). - -The option axis.tickFormatter now takes a function with two -parameters, the second parameter is an optional object with -information about the axis. It has min, max, tickDecimals, tickSize. - -Added support for segmented lines (based on patch from Michael -MacDonald) and for ignoring null and bad values (suggestion from Nick -Konidaris and joshwaihi). - -Added support for changing the border width (joebno and safoo). -Label colors can be changed via CSS by selecting the tickLabel class. - -Fixed a bug in handling single-item bar series (reported by Emil -Filipov). Fixed erratic behaviour when interacting with the plot -with IE 7 (reported by Lau Bech Lauritzen). Prevent IE/Safari text -selection when selecting stuff on the canvas. - - - -Flot 0.3 --------- - -This is mostly a quick-fix release because jquery.js wasn't included -in the previous zip/tarball. - -Support clicking on the plot. Turn it on with grid: { clickable: true }, -then you get a "plotclick" event on the graph placeholder with the -position in units of the plot. - -Fixed a bug in dealing with data where min = max, thanks to Michael -Messinides. - -Include jquery.js in the zip/tarball. - - -Flot 0.2 --------- - -Added support for putting a background behind the default legend. The -default is the partly transparent background color. Added -backgroundColor and backgroundOpacity to the legend options to control -this. - -The ticks options can now be a callback function that takes one -parameter, an object with the attributes min and max. The function -should return a ticks array. - -Added labelFormatter option in legend, useful for turning the legend -labels into links. - -Fixed a couple of bugs. - -The API should now be fully documented. - -Patch from Guy Fraser to make parts of the code smaller. - -API changes: Moved labelMargin option to grid from x/yaxis. - - -Flot 0.1 --------- - -First public release. diff --git a/web/flot/README.txt b/web/flot/README.txt deleted file mode 100644 index 1e49787..0000000 --- a/web/flot/README.txt +++ /dev/null @@ -1,90 +0,0 @@ -About ------ - -Flot is a Javascript plotting library for jQuery. Read more at the -website: - - http://code.google.com/p/flot/ - -Take a look at the examples linked from above, they should give a good -impression of what Flot can do and the source code of the examples is -probably the fastest way to learn how to use Flot. - - -Installation ------------- - -Just include the Javascript file after you've included jQuery. - -Generally, all browsers that support the HTML5 canvas tag are -supported. - -For support for Internet Explorer < 9, you can use Excanvas, a canvas -emulator; this is used in the examples bundled with Flot. You just -include the excanvas script like this: - - - -If it's not working on your development IE 6.0, check that it has -support for VML which Excanvas is relying on. It appears that some -stripped down versions used for test environments on virtual machines -lack the VML support. - -You can also try using Flashcanvas (see -http://code.google.com/p/flashcanvas/), which uses Flash to do the -emulation. Although Flash can be a bit slower to load than VML, if -you've got a lot of points, the Flash version can be much faster -overall. Flot contains some wrapper code for activating Excanvas which -Flashcanvas is compatible with. - -You need at least jQuery 1.2.6, but try at least 1.3.2 for interactive -charts because of performance improvements in event handling. - - -Basic usage ------------ - -Create a placeholder div to put the graph in: - -
- -You need to set the width and height of this div, otherwise the plot -library doesn't know how to scale the graph. You can do it inline like -this: - -
- -You can also do it with an external stylesheet. Make sure that the -placeholder isn't within something with a display:none CSS property - -in that case, Flot has trouble measuring label dimensions which -results in garbled looks and might have trouble measuring the -placeholder dimensions which is fatal (it'll throw an exception). - -Then when the div is ready in the DOM, which is usually on document -ready, run the plot function: - - $.plot($("#placeholder"), data, options); - -Here, data is an array of data series and options is an object with -settings if you want to customize the plot. Take a look at the -examples for some ideas of what to put in or look at the reference -in the file "API.txt". Here's a quick example that'll draw a line from -(0, 0) to (1, 1): - - $.plot($("#placeholder"), [ [[0, 0], [1, 1]] ], { yaxis: { max: 1 } }); - -The plot function immediately draws the chart and then returns a plot -object with a couple of methods. - - -What's with the name? ---------------------- - -First: it's pronounced with a short o, like "plot". Not like "flawed". - -So "Flot" rhymes with "plot". - -And if you look up "flot" in a Danish-to-English dictionary, some up -the words that come up are "good-looking", "attractive", "stylish", -"smart", "impressive", "extravagant". One of the main goals with Flot -is pretty looks. diff --git a/web/flot/examples/ajax.html b/web/flot/examples/ajax.html deleted file mode 100644 index eb0ef9a..0000000 --- a/web/flot/examples/ajax.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

Example of loading data dynamically with AJAX. Percentage change in GDP (source: Eurostat). Click the buttons below.

- -

The data is fetched over HTTP, in this case directly from text - files. Usually the URL would point to some web server handler - (e.g. a PHP page or Java/.NET/Python/Ruby on Rails handler) that - extracts it from a database and serializes it to JSON.

- -

- - - data - - -

- -

- - - data - - -

- -

- - - data - - -

- -

If you combine AJAX with setTimeout, you can poll the server - for new data.

- -

- -

- - - - - diff --git a/web/flot/examples/annotating.html b/web/flot/examples/annotating.html deleted file mode 100644 index 72c212b..0000000 --- a/web/flot/examples/annotating.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

Flot has support for simple background decorations such as - lines and rectangles. They can be useful for marking up certain - areas. You can easily add any HTML you need with standard DOM - manipulation, e.g. for labels. For drawing custom shapes there is - also direct access to the canvas.

- - - - - diff --git a/web/flot/examples/arrow-down.gif b/web/flot/examples/arrow-down.gif deleted file mode 100644 index e239d11aa65b7f9e65978ea1306ee0d8562ff66e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmV;F18e+8Nk%w1VG{ro0Ouh9000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LW3IP8AEC2ui022Tc000Pa0RIUbNU)&6fAjPm6sT|B!A!#L6}%@b z(?VdGGWn8t&sRoZ3)f}LNNZ%KbqoKEGC6Wo%74sQW@PrVp3F@3UjA#vvX#z%*jQrw q$&V$!pZ{)+yoK~%xRK$S{@avklhl70K}r>g@~Y3P2*K(M2mm{w^}*Nx diff --git a/web/flot/examples/arrow-left.gif b/web/flot/examples/arrow-left.gif deleted file mode 100644 index 93ffd5a9e0ddd08a790c3da95a228b2235aae7f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 891 zcmV->1BCoXNk%w1VG{ro0Ow}_000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LW3IP8AEC2ui022Tc000PB0RIUbNU)&6g9sBUT!?TU!-4C7Wzxs6 z96ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LW3IP8AEC2ui022Tc000PH0RIUbNU)&6g9sBU95`>`L4BEkt#e3F zSf)&)nAM|r@1n<6*!V5<<+0?ga0xq>EV)VIL0T(gwrV&~Wz0-1Cl(|b^CUu>7eR_l XXc41EhY(#-{6}-C)2C1s1_S^*=cTQ_ diff --git a/web/flot/examples/arrow-up.gif b/web/flot/examples/arrow-up.gif deleted file mode 100644 index 7d196267ebff0e127db6ad9a4fc42a75ff7a348e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmV;F18e+8Nk%w1VG{ro0Ox@K000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^8LW3IP8AEC2ui022Tc000Pa0RIUbNU)&6gYyn59N221!gZO}IW!0? z6R?Q|H)Yy{abUPiAj5V1cMD{uTOIkC1j!F0ww0N%VQdAnp~HGGLDqA~EN3QW4oCSM qwGg67S}P?UteDByy?^(7fxLJyldz`*?+MF - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

Simple example. You don't need to specify much to get an - attractive look. Put in a placeholder, make sure you set its - dimensions (otherwise the plot library will barf) and call the - plot function with the data. The axes are automatically - scaled.

- - - - - diff --git a/web/flot/examples/categories.html b/web/flot/examples/categories.html deleted file mode 100644 index 05d8e17..0000000 --- a/web/flot/examples/categories.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

With the categories plugin you can plot categories/textual data - easily.

- - - - - diff --git a/web/flot/examples/data-eu-gdp-growth-1.json b/web/flot/examples/data-eu-gdp-growth-1.json deleted file mode 100644 index 51952cf..0000000 --- a/web/flot/examples/data-eu-gdp-growth-1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Europe (EU27)", - "data": [[1999, 3.0], [2000, 3.9]] -} diff --git a/web/flot/examples/data-eu-gdp-growth-2.json b/web/flot/examples/data-eu-gdp-growth-2.json deleted file mode 100644 index 82004d6..0000000 --- a/web/flot/examples/data-eu-gdp-growth-2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Europe (EU27)", - "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2]] -} diff --git a/web/flot/examples/data-eu-gdp-growth-3.json b/web/flot/examples/data-eu-gdp-growth-3.json deleted file mode 100644 index 8684479..0000000 --- a/web/flot/examples/data-eu-gdp-growth-3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Europe (EU27)", - "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5]] -} diff --git a/web/flot/examples/data-eu-gdp-growth-4.json b/web/flot/examples/data-eu-gdp-growth-4.json deleted file mode 100644 index b363578..0000000 --- a/web/flot/examples/data-eu-gdp-growth-4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Europe (EU27)", - "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1]] -} diff --git a/web/flot/examples/data-eu-gdp-growth-5.json b/web/flot/examples/data-eu-gdp-growth-5.json deleted file mode 100644 index a7e1e13..0000000 --- a/web/flot/examples/data-eu-gdp-growth-5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Europe (EU27)", - "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1], [2007, 2.9], [2008, 0.9]] -} diff --git a/web/flot/examples/data-eu-gdp-growth.json b/web/flot/examples/data-eu-gdp-growth.json deleted file mode 100644 index a7e1e13..0000000 --- a/web/flot/examples/data-eu-gdp-growth.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Europe (EU27)", - "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1], [2007, 2.9], [2008, 0.9]] -} diff --git a/web/flot/examples/data-japan-gdp-growth.json b/web/flot/examples/data-japan-gdp-growth.json deleted file mode 100644 index 855477c..0000000 --- a/web/flot/examples/data-japan-gdp-growth.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Japan", - "data": [[1999, -0.1], [2000, 2.9], [2001, 0.2], [2002, 0.3], [2003, 1.4], [2004, 2.7], [2005, 1.9], [2006, 2.0], [2007, 2.3], [2008, -0.7]] -} diff --git a/web/flot/examples/data-usa-gdp-growth.json b/web/flot/examples/data-usa-gdp-growth.json deleted file mode 100644 index 33f66c6..0000000 --- a/web/flot/examples/data-usa-gdp-growth.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "USA", - "data": [[1999, 4.4], [2000, 3.7], [2001, 0.8], [2002, 1.6], [2003, 2.5], [2004, 3.6], [2005, 2.9], [2006, 2.8], [2007, 2.0], [2008, 1.1]] -} diff --git a/web/flot/examples/graph-types.html b/web/flot/examples/graph-types.html deleted file mode 100644 index dd21a31..0000000 --- a/web/flot/examples/graph-types.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

Flot supports lines, points, filled areas, bars and any - combinations of these, in the same plot and even on the same data - series.

- - - - - diff --git a/web/flot/examples/hs-2004-27-a-large_web.jpg b/web/flot/examples/hs-2004-27-a-large_web.jpg deleted file mode 100644 index a1d5c05837576a37a8a7631d8549472154a74807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34489 zcmb4qcQjmI^zY~)NR-h#qnAXCPNEwVL>t3I?`5C-=w;Lx z34(}-5GBv=y|vzY|Gs_xyL;_@_PXcpeLlO~e@p*10r&N^A=&^UA|in9?E?J612h14 z$jK?l$?i~4P*76dp`vD_p}u>Unw6g69wR3kHy0-x2M3RUq!16EI6nu6u$+kagNHIQ zGTcH6D)Q3Gl8GU1}B@8Ww3@4qoa1Z~8X?pu0oDbGwN60K{}eBy>do zJ^d`Gk#r-DQGJ?!>-Z;G2y|0=x|yw!Tk5}^CvyWQ%#)%SmPC88td zeMmyD#$Y6EN6P1`{%-|9eaj-IBcTI423!FE+TkR3dE^09xF@W(%uh&`)YQl~|HoWe z9?ojOk}&-V82}C-$H5zE_q?9m30kEh2e5~GJ#_w<9a+@I@$4SuW|5Vs{VK&;B~EvQ6v{Yx{_Tj-PZ6K*C6D z!dJ;AAKKa0A4&Woh?sq|J8Xr^ijGq5q{Iz3zynEVndQZV&31)1BFyeNa{#0Wvfb3= z1WHff6%A?r6R!Ku!uV`~sc_DqRW4Tm)b^XtB*05$u-8_;8n9*)Av*358Ue5--T&C( zTQEvaPj4`Y4Y|(?|IWn7h-DbTA)W;RtcgFW7t#X6YzxV8UW@<$%>#2Bv8O-R2Y}-3 zdnZeK0$^kU0JwbsfCi$qXdVKK56P0o1^Kf$wLbtNTU!7tt;S`zCwuQP$rIY6nCF?v z!&>TufL%{>Tcs0Ly8$a6;LI7*`qtFYIlzM$a0&2J{Mb-bhRmi@PF7-vhS$M|r)`K# zW&6V|g6^;}M=YjZjH!8@u#2&OBF65d-Fur#T8X;ii{uSJ!cD>mOqT%6r=tFd`GkIe zVKlpR_Ih0O3F%^lNyuhbKyqXzTqYAsS@sVA61e$NW<3D4tr3eu9gDt%0P0CvQV#_< ztcW;vDEcViFV(TO2_g$V;s^aCHx(~ahXJ*{INqydIQ`lK4XDp>50Oj*&=XbTsRF>9 z0ka(n0c8oxu|x^s46Cy6dk5@~HI2rJjxki1;Zcl_83o=^jKV#K%}+?k{{cv9Y={wn z5GrhKzAQ7>08FAy#D&0JO!ho(DU_6$1$HKNz$i=NY6|#uEagDpyw4Tcz`Y81{+1XH z01P|>RN9h0q3jPvemjh8L}sxtSU;c?%*@zN6Y1>`<>u;%6;q-SvKXjZ_&}eplC0_( zUtB$MWx+V(NOaQp|*4>>WA5;W?GarrHMOUpn!u&l;(o5%w|^o zL4>H~s8AS5k&G5ozC0CMVYM)v3?`>5cC04n5D-dXu$#>m1k@6Lb+?9Xeb?-1gy+Fa zwT7k>66?=ov7=-o>Ue*XacBl}Dg)5b`)?)vB$-4zt>@#8Ls0T4GZW*w2CC@CG->Zp%x1s=f`?|X(v1=is8KIuW$dBgeR>;^YlwUvr>dpi2as`u0{|3D0BL~I z9THzJ#nNYfK)XS^yaVEzO$BmB;zoiz;9f-GYJk?06>1V}q7V7et}w8|6(E|gad6MJ z0!Bioc2H?*>JVg)ee04+MT8F{?QSY6@?sq(*_dX$C>-93SHc2KO{rE22LlKmABo*0 zFaX5y;|!obhb>Y0&xXC)taY577^hr=y75ajn^dS$MqUlk3pLDf6muJu(S{g+(l1|u z{Ae$unT>+9suUK0d5UVBT@66S7$Q}0E{x_#Ur*JINH%>3TZk88+52feeh7YIq7hg{ zIURuUAokd+9{^LWjGEshTE7T|AP%0&V0Os=Lkh%L6w*GRIxA`~P_MWO?>$jjTROUq z4+D`IGYp05p@$RNiDaNRb;709`Res@YI=3)3^!Q5n!E@;w(Q=RS6BD0iwybzn92AH zG2bwA1wJ&M-&$Rozcqfn$UrDaF1Wz{V(@mM9Bvwu4+H z=EG|OhhFY_$kU3@>DBi0ZTQxnQ6R(X{{b8}d@M#ib99Mj>h14xNk{_BBVQXu z5qr9kIdc8JqSO9Qx6H;D5XFC={7d6ssy1Q(Fy;XjX(oY(YQU4oFvgw$;4LgfkYxY^ zANt;gGFOiQPhtR}T9kfCm4u)GAgF4D576-3izpj=)=|y2#1_PTiUpuA0q>6f5FK_n zCB_7iWsx9aOsFLW^K1aX6mj=XvbPsh$YWEgxUCr9QL6`RH`?0)$wc$U_BI_2F$=Vb&hNv7%@f(@m2U}p1eQ3b&A?{IzGrJ(p z7JN1RhAmBAcMR@3eV(Zp}U z3G^dd_-%2~k^WKoES;_qVTIO)&m;!(SSrNdW6LrWRc8G$x%W3>=+tP}Wt?}h{XS8d zQ;XQueZj*^P-Rm`IMsVoN4<; zXUiIJ-|Qb?z&em>getZq+-;q>0OXx03RJ;)?N{0Phpg4c~JZF;#7%uR)e4TfiZaq-<@qLvPkFIJP~IO0_op(s(j58NBH! z_sWi;+kKBd%fxfKrtf~>Rv~t!Z3B?y<7V355V(rI!&#;|zXJ1EI%D<%Bn+AgF=D3f zep4XNJj}d`(Q&BxHULIEZjx#TG{SI|hXk{!iO7W?jsiUq1+FvZp3_(qz1g+R062dp zpg|0ky>hPZ>o!a5+mB{AMU*a8lFlA+nDY;nwo|VU+_jUrH7{XRjFO1s^B+|F5Btew zW*}tkUP5hj=HXRx7m5+YWwjU~zjK@jT_?T4_N2N%eCWEw0#ncUO5~yy%NkDJ<@ht! z?=6XVQSBlQo;cMMX*s_)?m7{2J0W(@*`m72N`Ki8IUiH z;u_hzAae9y+SLb#GvV|a9v_IqSV-{PX)ubed*Ptk-Z*!H2d{rSu_e(7_i94!=a75U z-s1!JZ*NGVzRicc;gq(v92rd14;{{kyEW8%XlyG(Ji`&mwr`icsy%NojND^7) zXH`qzl`@G(!opv6{M>wQ2LLBFta1$!t@(g|2HsVUgae?ggomhP*U4qTi{>I9G0TELI_i9SdW98RE-~HbI3jLjMUp z`Xj%tk~djksp(wl{s{UAibmHvlojyI)*uL&irmbbRWFti;qWt@nN3=C;Xl9!GSF&W z9R4F;Qr;`}($9C>8gOM61A3;t$^B*UyX-s(;|(K2iEGnFW+5$lh+k=c>|(0*2Dr{s z=tPy<4l1tU{{TxhQ?3d6h^KXGZs(Hp7l|%)4p^_}EMDW$AUSfT262s|gi!;#vat2CQtk46s(Vt+SE!&v&ApeODsN z=`J^CR{;4}4_ zLLk0QZ9ci;C7ekmyxz`CQ8$cdPuxoj3UNrS$62@?+9NKB%z3I~X>0CKPHjPGc<>=h z#^npRrX}~4e*iv1WkA05;01Nu1&`{$ESb53*E@z#Stk0`fK-q0N&pprrShc~KqvwM z;O1$du>}Gce>L(q72aRpY647u5RMRMYK#z24HVaNv)Z)R=oSN=H9!NwZ9^5P@Xk}&UoqjO7z+`noo5UNXKV>b zCUpCDMC)%!VmCd^U?a#NGpppB+kMTJEw*a$*xcRFZvLyDNAaMZJr2R zS0QZ+8AIe;Y5JOZN zWUqk`l_k2B%4~N3Hkqtds^)M_lWKzg3a)H{Oux&5-*VC`uDLe$hwa)W4}WS3HmTOO*bWE+SxiB zdCUQ%s^J}x6DiZp`VpsTx)8|LJ}BaKLMahmkRgy641V@A)#r>{IoLkKLzzw9Ncowm zeAfllRw&B_0}sTmnj-g=h~S5RfJ)Lnl0ep4To0KudlZ1^GqEs07@)dcb1M0g>$~3v zWizVzI{~RQ??i36ZHW%piAV2kx_b9sMTX>&PR2% z`if1x#bn^WjGYxy1fa=nDinnKkZ%N4XmYy|V#C8D>72DJ4Bhw;$KoWN_D z7~w80mez3~qI^=IqY!R2N|_O;3C@`IGAHoWS_72am7ZbB+?cl>w#HcK+KI7B#OW)$ z;Jg%WG}C)nJ&Y!$F-SSBz{edmdl%%74)wh|=1>SiWev)c>6fRQy;Byj)$5OwuxKOr zBfv#8jL%K9c+Sj*&qHh(DBphqe6?D)`pwprl+yYi!0v4QnYa&7LTE1?^mN_?VFFqY zA!uc)SQaj+)2RHhaY;|R~-^i$Yau3I9oypZT>vEuoH0( zg1g`P2+T}zV9)HtsX}=x*6~YodO4B>x;f{R?XhWi;XV6yn*HqJD!o-&P=+HC}xw)iAQhRs-^ZbRlUzLZ4X(H zliHUCBDKEZCx9CqaDoqGlUjjd?2}g1Zp>rn_15woXoJxcmCph%EJTn{fZ&N`9R%VH z`v_U05p0gkWJuyIt-nXa)He0S_e<~6_b%%MuWP!pM19Ky09tSE}6Ug!4(2?x<15x9xP|anGmb(40MFKNjpCTZLu{z_D$1rlQ$63JVwkr zr^wNc*B=A(KiQl3|IwZ?xGs@n&Ky<4dkBkpAF1NaoIr>O@Je%Mj($#8i@HVNV3Bq` zZG5KS3SlMxL*;_^3km7*J~+5_Mxrh((nB>|U-leGt1g+UDL$uvYpxc1Ok zGLz|%>(?WkK#d==BBBaWT%4~pggm+%;%RIaI$)NzfTXDoRYM}A?1)dXk3w&Ans?|= zg-k(9)%rY$tju~cH47ZdN7TI2*T&4bH#2qH7JXDa^V0JnjI^}*dc0@;K;lL_ecN8- zj}hWTynW=^mpUwE%>byGfIq|k12h`Vc)?Ox2j#LlWGn;gQr@^8Ou{fbseT`!DDB2s zT#o}I6jch$wM9=IMkUmU?l~Hl_v1(_%(Mh68L)gaNKiyMUr%qmZ}(VDOh{BL^ZDH* zk##kxv8Kq4a}hObV(CMO;KCMM?z-n|8|_?MeW#&h=-SK@qsQAWx!}Nb`dH=rPNp#s z%o*%5hR!08e(SVfc%@(5stWl>1l#Sa8^`2zteM%0B5{yhG)<#7z0PCobB$yC?ja^V z^Vd8L4>y>8-52jBa7?4|4=_%~?l6QE2SPmG(HCDmlS!Xg?J{i_bX&jE$apzkn{a5F z11bxGivq=P^a*-snL+;u?7|&vb*7h)+cx#|$R0Mat&EWDA*z3F2LV54#{YJrf@_*W z8cp0p$B_0%OPI+eI)Ph=-{qVc8iPmtuobAX5$HL7-dRlF8(T*tkwxY9dN@Sq4BtvEmOc;nKv-!BR{zmb_ zD;%R744P&r=8SV|>(i~Iaz38+N-mk*7T~C+Ze=q~n`<#%%V^2EA{yVsj|Muh^EbMc zpM1O&Ye|+zA>nfd1ja*O;AWnc{wx6m~PGZlC*+MKvj5 z_#lSJ>XI_Izl<%%`p^ATR3Qg$4lio=(wAJdBa1upkexS zo6Q;M%_-EJogZ>ek)5Gfn(CA)+9*G3D%T*}U?ib(Tc@z!Uy$82^nW9idrlU9Nu$Ff z*e+=FCDFoeWd#p#F&p^O7};%!q|fZ`Ip-@|L-8khJK;?>k}>nte|0B$ zW;oIHz{*By2;{Iv1~rW@ZPdCtF)&%84E;g+4?xzkf|EE5e=r&nNnCXRZh(hm-nAX3 z>1KFxSL*ZnO=|PN00Y|uL(mecxUhx{x+ylgGC+3DVRA-^;&Aisv^N8Jf1Wd54M=<{ zFEb`OYBob}@jN4YM3=1Ho5ZwFJN0A%M?Cotkm?@M1-mN&rxl%F0_8PDhQ7kG) zQ&z=he{OO!i>Ly{jAFipQ$#4Q`w4}cN4jqb_U+6Z*5YuTwlnufo=j>rzgCewwwx_< z8ZkQow1i|XvmC1)Z8?v2+y)_~7Jx1MA`qwYJA*D`v(m zO4^~?bT-DL?Udk8vwPPn%`x>t{)(^EMTTA=C0Hg^=cBy})Aimy9$O9WlS=8#O3y%} zyMt6X75vx4O)_As@5aR-3ca7$YxkWLj9)1jJWk)J2<jyKK=f-b{kHNWaXUxbXqaWF=SHK0-SL-c;fN zzdH83{*cAw4L18ePn~o^qkMnj?u@;BjjLJ9ernLH>Be5K_7wSww004|TGmRxNNA0v zk$A`2GCVSTM&Od?Lz{JY?Y=^tfVn)o-X(ND^1yY^w&;Trcv%BidUxa5WS}r$p4^FY zDe!5)M3^n_${-=O3ZvBBWW7F6Kzj7+C87GsKR^R(E2<#?hw#EeT*MA2gwJV7qMnQn zhoyG2(dxkF?+%<@hKrk#Z}j5pbRgZ_C(#RT54d@IJ?{dY#GA^f6at#rA+_(m zlikua=f2e;d%%>_pI)qJZjkuwFp%75 zwya^#$0Vp|5lg1G(=f@0mU)I_Vzga{9*WYX%UDY4TRyT(LV3uHA1ap2o`c`&;+GP( zjY9agNoL+^jrr~wqsV+D__ES_-$KuLvPrHx%GJK!4E4rw<((_7It*ko`qt&8OHge7 zuxg``0(>Nyu@5##IhtEq$Lz4SR&j79lO!tTqeJNbhNSt9EQn{Nyx(ncxim%!L`v(W zd;5tl@4B5bv}U4)w^tIka~* z_>6O#xV-vhN_I0h`>2QdCBtjkf2Mlrw(Hf zm39#+66jc-yLyGTpLKGXxKVCXB7FlkO=5Qju{BMQt&T_Z*zUEN$;<^ z=C9f7LaMw*Qts{PKX+XC#*|;-jCI;7F#r4QA3$dR9{_R9Tzvh=lFfH!&jC-nG<*5} z`(ZPaOI}EhsW@Er2eO0H7SH?4X9%qC3GRIMr9O$q3kS|51mNsV){M!*@mHC8u=_Pf z;68Y9&w;sn_flnd@S&uZ$ zTGq^fjC;{Al??QzF$>6Rusge1S7(vbbokm@nZE|A-*;zbrVV=-&FBN4EK-w{Wz2jH z*~TFknC|GE6O1)4DLl4PFqdq5d$LYg?OZ1h9KNE=%sw{HcG&?iWZDq@D@`s9oBU8x zwjga#0wXNjrYO_((tO}yw0};Z|K6?*#Z!NJ3zmFGNNu3u;Uj%lsMJYZv3J#|A zwjbhXoc8lio2HF3M&!q4B{{$qmCVV0Qn!B7IK7!~&2c;R>G^OLY@O3v@BHgpp?uGF zXK{?bo*Vdm7WbMfeT(KMyP~r1ePHukw(e=!Z_N$EGULpjyXiYw?i;ZaZ%Ir}k?9(AYodsJxp84TTA!qV8Fr@_nPwPW1D4zd3Y1xjjR>@m4)KXzpg7xI0q3&R$ zv+hoA-sb78U~Ft@+2!nESybZLg+^os7hR$(_*24P+F~o!e2j1Q%YT5XA!L;v;!%z^ zeR|t+y^-pqIR2?k@*_sYt+RM+gNGdTtrOTG6Q%k`-zPHlzPemHS5bgm3QcnaiA zL%zIMKU1O^^UM;jnc#yS8wW<9CG0>!k7~deLabYse`S1_VDf83x+EZZf%MCwawg`C zMs@)Zm+9ErO6Un(yM7b6_L(8k)i5dJNMwOV=37Ky%!)UEStFm36>F+-?)+U_b0ScNm8eSpS8KpNShY$goDw|`ud?GQp8kt{Wv=;1)!wiDGN;A{D zDG*%o?X;sK#p~|R#5~T|2GyPxW+{jnl{Jrwm|A%`Z}>hF^A?{G1CCLG4)A(%omq{~ zX2YEQhoG&%vWg^(BAl<8>wiB zt2lg2QN+noNAuWAm0T>6O}0(!T494lOM1J!^z4y?(YKG~y;JYpM(zqhBjLJGtv3Ce zENzeJQvV#eIB2s{S7;{0L|elDTV6@=TX)0s#{ITxL<;9)`%)))h%$%bFY7v?1?fC@ z$z|w%b94FUgcR596trdDjFLlTx0_;#Li*s*8e3NbWLOl%rDIYfp)ea1!o1)CG3=5O z{?#fZq2m{%^g`U{XuKBo+d(J0o-NOe^ZH0I__Jk!(YjU8fe+54q{L7k8u{4keP2FR zG6y5S?$-WM%GW6?r*&t;`SzdRgxf?80#rnw?#vx~u(nQAW54>Y?&Um0*LQYubk)mQ zIpzLFRwbF|WIK6vjijkH?owuvke5m;XWA*NiX7knFY@WPbU-b(S^C_?Wvl~O}I z!(efBI*NXSK=IMwWS#3kNf0CxqM;xe#4+ zA8H_~){B7O=u4L$qnF$yvIy1HTCg3pc6rVtoG1ln2OrxfA~;!Mq19+%iZ#>S)`5KU zRRfvb$o*eq?h;z)%$8*1Ka*_EMWT2R)-j}YV4Gdv`|J_yUTg0p@7=M7usu#TAJ2qZ z6~Wsgi;%iiYe#XnS@I5>t(-UUH~zP$mmCflZOb&Hn9+mjnd%npl$_;~E;^$(@Lb_( z`miyR_34MOUCaV~qUXWqz1$9N!`Zfld&X?;beoC%^6H9E`v9Sc?@*S927eya+O{p1 zGSPAN`5@S$^sU}&j_w~3eaV$qxxYIn67>gJP-Xx$kGv=0C#qWJR%@M$^m(-Qvf?w`nJzZ>j${W0FhbbeznNK~j#)oQ}+n zmYPshI!wynd(AmGWg=;1b6(%)g}ZDpLrh7WHMp^IXBO?X{uC^8p(s#Kh@)>Jpo_V2 z(^cH$G+-x9V{8fQM_eDo{WJV4mi}jyg?5wUji1)jf^^yXC`*5+rn_`uwNs14jc%pu z1G#bFeZd$lPh4T;nP7v-(2~I@J*-HK%$A%I8>{htc&*-I{pTiGqBWo=Y28T zRFKSV4IgcJ3Z$W|`I7KWVC(^Cc8VZVz$z-+MFdPS zm@|dO9wzUsG3L0VFEbgl9{yp2CPyHwS&h1H2=ESdLP1G46xu47mJ*xSW5pHChgM@T z5JQWpro`8TCOtO`VyY8LVs{-}|FAc>yjDU5W#y~a6EQ-5eq82@R(@Zr1@ac7{ywvJ zSwrgT_GkLL|6e_-6@IvkKMXU@R8pG^1`R%FTVt^N0Cwzfa0s0;D$jcB-^SN8J$vLP z6RfBr9cy9vS~10SCb&vF&B$s@oND!b2BNw6K?`GsK^|?s36|}&_c3Z5x9{pdW&GDn zb2Yy1HCLK?qvxrA7puM)ZoZ_VqJlFs4GO~;H&TtFxQrgdOvk>w%R}IQc<1##Mj3*ZNKYhw~pR%$j6Q)B!fvvcJ3^|HG(WbMLow?zVUO^z@>E zp0s-}`pR6HdracwSJAAx=tlCJd0#BoRKGs!SEc5+Hbq(a<7*r*zK3)jeY(ew5XC42 z@XfRw2L!MMH_XABUVL|V7Db0-{{t}m13Z7UA?O9p7+*l@Hz`Jiu@3Sz+veFZeXe05Rnp(o>R5Xwvxqaw#`9gQR`e0t2dJv>OYtt7Rnk^H ze8o0gx;=}amNf=}DH^kRqP%lh?@GyM*^3y`3zhfiqIXVegD zQ}SBAJgGGbs^5;}^fen$mv8Lxq?3}DnVmff(7XjCM+Mp|ATJ#qXo*EhZI|g&8&B6M zzlU{bhAclrzBSC=UfXD`zwM4rQAu%@?%t^}Y$_h2Wi0n58De~k%=^B}h8g+H9p5 z_iGQCe9Mlg@D`q;tNmnd1KHCT>Sy$GckM*Dl{{a2@gw)eOVftY#W;V@sy(Rw`u9;0 z!ETFN19QW6t{NNf~cezcFiSAu7eMl_L zRcuRpS=25}KVcLcy=hjfMm?v$(&YkSa05#Idiy;(fK{&=h!9K<`#roRHpROd;)Cf|W=na;l+thl5+7yD%&3a1xJMQ&je{e+Q6 z(2$IULA{GRSy_j4+HZ09_NvD;TkSoLOLvsgvPXaA;O^K}Y2VC9+I zS(ft_{nac*xj-1RI?D(;FVpz7iD1mk)LJPj3Kb3#WgZth2hOgBiQl$49A_Hh5jJE$ zolAg^m_AXzBKYcQQR$lH8167#GxTmoqPHW{$4@{pZEAR!sU_N#zGt2A;-K_wl$m9v z#C14?_2XrZy$s9*w1Q<8i>%&2&mqg2PMllGF`WWahw{}5Et~k8k0CPqVq=Ul8Bb@B zf*%DhtBCDOO_>ZPfffq>Goe$H%A!>B+F5OQ?Esn_5_bTskY?^#{rN6HwaMpNEZ z{}C!-OW$WOvnnx}DbD#L@IYo_dw%ly%^!kIYSYt4MOFL$P1>+8rE)D@#R z*jC=Je#0C}D#Dulz#XlwzqXWf_CA4nz={ETwYZ7abjpP-Skeq(7B(`$3$tsd8qw-(|y%ZHk`i68|Y$=kh zch^9^xM>T0=p2N~dlYLCHB=g<>z)sBKSqF3o#!QzR!%(l9*EFAnHMK`3ta!#VyHyF z=V!$LvY^V&CtVP)Ca|!UE)2|zl7HCyD_vA`!La15zyRwI*X%jc`Z-n%U(IST$~)I= zPdc&2n>PhsoX|voM=w=j0qgC$R4UQ#qywx!Lk zoN`8kT*e!KQoLJ5v~V?w5sx|$uBWC#T4FDX+FxErXUd`Coq!Y~&*s|?%(R9Dj+VTu_4A9b_lePCJ@&{Z5 za+Vl9VIE|j$x?dk>=lUETxiVzt-|WTbjeZMf>5w9yg;qzr} zdcKTy`;PYVMiDjBZCmZiV3#_S}Jyfem*Y%BGn^L6bqnc+J93IV}>_> zaT~W30XOtdQVwsCv1kw|QA~E_zvR>Muk)Vt(N2I_o3@673CKKB9TKF#&t2Pn$tj)N zno)B85HiTJnrLBY^rvP}a{|%l_J^U0S^U=+-C|i@jQB%i}2=~9S=dahSPvnG>CR5^{u4uDn>ED&M@lkWGviWgIPy>dZQ}C*Ij4; zKHODot>@SzCR-5s{6#JsiosxDyt4`A=Q>aShab{pbz%ZaH5mM*0jLW53v%)@St{5} z5JP*&4XjOz^W0DdalQzQIX2a>5bW_Z2}rS!Yr2Y%RA!J!$fU}v5gvywXfEPPYItW} zUJt}IyBW!$+gsH=oZg1ZTk)2~^3{yB2%gbnljjRJBU|C-C}GMKL#2MKxB?4 zk`O(PMzEiO9@JLR&&@=CZKV_}p~NgSzFdIdz|x)^H!){lgZ6X97KcpNgzvGuyHkFF zu4pVP7)fx8kVMEuwnwpi*qwPR@Z4XkM7L(*pwC4H(bpw+l*8`jRyw zRw|gz>;=v6xu4i9QLm@4^dqU8$F7815QEmA?#PrZo%G+<_Jw(uCu@3B^Ic*y8$e^_ zX2MlmZ}+SoS*^t7FdqB~hQ>Xu!MU6j?Df@E%v#gZKx6{+zg#GQt=`u)eiCo;kdf;n z-*%tiSAUp{sVtFKJGynkvem^i#VL4e3^=`6O=P6>kK}9R3<4#qJ zvwPc@{+KW`(lEn)aKLIA`|R>#owCoN1(F5YCX4PS9$gQP*?LX;y)0kqBX?I{Eauhx z4Us{lSNC?>?*}7YwN;M)I@VHIHuYCqEjQOg`>i2|-q)*lnQb1|ty<}^cut>QcX1oc zzfz5SO<^e9Dz|!-`qw%eY9P}6H7zfDDEMM*(8Q>AcYm*m_@F~^WYSu8OvAMPM5-MR>3KS5uUQ)sk;F4p(vWvf7p8Sl+|UR7rz-rj5R}{1E7p z$-|dA|JM>1E4ci+xSq#F{C#VBur|`ISk2hW4~(#MzL-fH;>9m9;0=r^LxM5n%NMdYl+doTx%uTeI=K`}s%#?hDP zp97i~j%`PYD2sdhR9kJvZDZ1Ges|02( zJv0(S6|Y!Bg8X5&7AR)DlX&cmNckI}*l^1H`hU#F#)-vI$=BieHW9P+jzA7`uF1lx z9ab&t%N#9Ly#y_DQpC}a$ZO+?QfI73Mdxy1cEAFyyy)tW&zf!}df3U4jjeUZ%Ef3x#t4w~Wy$|9tNCsy-rT4@eC=9^&KesHwEcY}cHtBw{qs(b^m%< zxPB})KjWJWy6qFH`18!?S*zot^wqup3~Fd-lc!9xeb>9Vmn&ALjxyakMI-gj75@Q@ zG9J$kD$b$DL}2kU8XD35l(!OQ-VaAe{4{|wbMfjT$r|+kH=YCl4enOO6aphbSX7v`E z3)WpO{c|r~bLD z(GN41F}$Togn3Wb`MASsK69=TedB3v(@RW&Fmxurnl6A+31tiJoD?_=^5 zC#L@!g2Ti=520BmYi`SI`rNpT>=)&F4VWhDOUBQb2JF>eitiC+>LVZSp9pPb*~MyFV1VviVpMy8dQyw%n_}}Fc@G8dAhA}ogCQoKlG`!uWn-k z_irAvgGXHr^~@m=&;9`>M6c;fl00#?mDnFm98P~6*H9W=FEyDk-kwDZnz0QhIih0Z z({8MwJ{tSR-e|>_xinhHaO{yOvNzr`+5gM>4~pA_Nr)>q-EDz(S4nrg*z~E1S2Czg zTDa$W%rY@yN*X4n=F^kViMnmhKYVGDw8T*k=9o9AkV1$X62xn?gGm- z){(?~ovM{{iZR{mp`{y>u92P9OK$4mFPAKKxnt4prT?MAJz9=FP)sr& zge;lbtFRnBWs}RNM+sa#Qb1&aJgaU;&Qu=HDuR9UvE2S`rx4DPiqra8#?ytm_L&~P zU@KTkY?1?umKqEY|c_+nNrlsV4JPOJ)Hxq`Ob*l&W zwx!Fr67WbYCyU4B5!=I@kEk}M1$TGZ!?yQs!>7l8QRz6#t`#{>q4HJq>LGt}-}5d> zw$}-$i2}0k`fiZPwTB%m#@986DB!~HVEVU#R< z-sSs(6|6JWhkR@+oH98H%RA|88+$FM+a=W){noMRnNP;S1)U;bvMy(%@BZL9LNk8i z#BVpC|F>hAGE)8A5Pa&pI6XJ_EU^ob_g8U39k^x~9rCpRR-!CW@BG%V_qUa@6L-1M zw=wYr@3`5U0u}w&AQ8kz-oJ6inEV3c`n#=<{=0tko6{gqo4&0;mA2qUO=@G;|L>b0 z%ulyA6c+zlOKlfiYezfKNZb*sTz~poF{Y$e*v#UaKb1`vp{%RIrL4BR^>Nwr4e`lu zk{>M={==3@t-YOBfcU(91e2ac3OCOOOHV_)({pNPYW0pLeOv@i7x0HW1}}0ir8TAx z{r|`Wf1aLub)*=;f7uv~KEV07-2UqN`%rhN`gPX}rPG$}OPzwni8g6daTn|7fva)4 zm5uJUle%L^pbiB-RFhqKvh9d}vWe%AvE+Z#w3?k=+z$5FZH^AI%foZn<=qM0ulq7H zDO+HI!7ziBY3=(e5Z`E4!(!sGpEBp)69ai2>U=f&KUAkeLUu2Qi8InN8fIj&0;NMO zL_e&1VhCQlxdK{3zchb!*qZMOyU~3rUN;wzon?&gC%PSb55h?pF=DRhf1>lP(AUvo zpsnP!Z}u-QgtKO0=gjj6TkPv|-kx(u`gJqP{>kL?Cd6eXmEj8SrBf^JF^EIihTkA- zfhJVD_8%Z!?T_<6Ktz4jZAWL@uEGuo#1N)Irp~oLE=2PX;?4yaV5pizUZe%_A~<4O zUtYAo@8xUIlI>)#Jnt!3lsjOD8wlNBm?UE1gPlliNzEJot(Z1d9>K8p!_X8 z$_8c}1$|L(EoJdiYx=7P1cymlWJl^PXO-^FzLK3sztj)kW$gu$I|2*U_G)lqD5c zmU)_q!f{cJUNUdgb<*tpr0|UWrd@UV-I(st)9mS-rzDl}$H$a+!hWl*z~%&cW6d2igK%Nk0(=4SH7BS+3YT} z_afCLoKiE_)KZN(TRF{i@s9=cP*c1)cxGCUQgX~{wmUVUH8%3jS2;OWcREPTtsTooTvA z$5#a=$kbM5jC;2w$&}-gOpN6>RB`nUxU;pZ;N{m}OTeUgQ(0zdTgkqr=4)_>)s7Aw zNYgzHU+KZasI#~uCaiU8=z9q&0Zmdcm7OwpIp)C-$aGzkCz3RW=K|O!(&g6#w&?)w zkrHYAl;7ZnLJxvoUPix?UJ(>Of_U5T63%%-w{}pQwM>_WJH6)I8QdR_WkkDoMBW?U(B50CuyzL$c=!dEuyCv@@@O@FXm^yJ{)G0b1f7Iw z6^yc;b9))Z*ZqrE_VQTG4Y$Z7+6~{3UjR~SuoTs>yLnU(d{I3oi|4|mMG|I((*+Y zc8ZP}DJ*01Tx()0NV8Q`W|dK@q^J$FpWx}!e^Je*bZOF-f*yUQD_+ ziovX6t8+MZnntLxT0%DWH&NRr8f@16n~b(m#^ZvfL}m1cbu)3A>Q4kop(#Gn8#Kwb zj_=ar-_=LLJmcK`6Q_NW)aC4SwPne6`n@yPzy=v`Esq}~pFcqzrZR`RUt&D&mE;o(p9 zPxQB^`*CTjZJt+G*Xdt#(@(o}uxsuZR(Vy=2&hT`-R{7(fZoapsQjhOK zN^LIWp9OxO&~(%NQ>KSisUO$@{QHqq45hOK4$vii@}x~{S?j<&9(qYvp#>|nHe z`)73DG{5LrE3-wz+jKV5x4ODEK)48=ZAOZAyUr~M{yjwLFyi=@}nT~;M(%)eKk z?sR&yP5%H=O8!iD>7GBy=4d;OZ%Mq zdn}vVx@1*X{)$Oi7)=(QNUdV6?r5tBjWml@U~2}f=z~_Ub4IUYrVnB@9ZgNq$sCF8;rGr`bF80gU01IM&dP$Z3G}}W(NTJli|~0J zOL3B>ZPRMErP#N-vxipxJ#t5@siPP=PR>rFNxhm!UXyWxPshm}MxLbNyB0nUlDXLO zo|En}*6HIFozCfL${CTzuPI5jM&q52SE5cIpnW_`C!D80-tB4jte&svzxBE)=oxDC zO=?fK#>Z}MTOL~Z{-zpwom+WHCVb~m=N)NIQ2zjSJs(v*j&D{Suzl9-^LqY)xz?3$ z^(VtOTIS_^r&OmsOfRKuc4+G^pQnnNcO|jt>3)|ebqZ6dCziK4^*T@WJ!fqH0I5Ie z@Qlf0ui9S@UZbSbmrwqoMfSHz8N*IeidgMirNQh^N-ir(F>;2Q!t!O(^s&^Ra#DGDf9r{BYqr|2gxt`dT6C%>cWI&SSAg~2?@J7+vPdCo~_uGp=^PfJzx6>hG#H6}TA ze$Isd0BRk{Rje`or>f)nR*ZR+za!RaQj99vXD8}<&sV6^(2w-#Ds|+~cf#>M?W}rq zBDl54tNSm!YJsTRjalw9=<&&8vs68S#{PeMDB3 z$+|t)oj&Bx2la_TY4-mB{)y%J65(dPcdXWWnyvdea&M@e`7_0AS0M@~QQ)Mg&r@SH zW1XyyDYv*#;TL#0*4lQuF`nkm)J!F2C|nt8VA*VEt5nRZnVj0o4C(S=tiFaBd77)i zl4jb8eX=80J_ zdD$8_Kse@(jWl4N29cHs)Os2*wUeSTif1N+xSbHT%+{l-GE`|97{g7pZ>JQu5vH(} zpviDb%5JtgMrw4_u8mwDCUKKeHLF=><0l$VEYk2}X%?k}q-AhdG@p~|ze;aTDpE_e zpDUwpRUcE(&NI=3E~Iq-0Off904CjDv?V|2W>D%o5>Zr+jeQBpTK<*0Ij5$bMi%AC z=C$j`r_gn=lWS3)jJky3QCK`Z4LIr2d!Ejci_*<}8s(0!MU+w)XiaZXXPI_-q-|lVld#y`XvC8qfIXaG)P8ycp9)3={o*0qy zUAo#+PFkM|&b?cUhO5%KJSkCqW`>;gRa(E`>(bHg=o}ig7lO}ATy7aoal(6>YIOFL z^q$#h*H@;Lv{G9j(bCn`g49uc(R9+~4MrMzl8&!zr%`xxb*_{4l(iwKnwJ)Buljsr z2x`W=UjG0ly*841P3kT##-oZ!biF+FX0BiD`8>TSN^)~*+jiONbx@YJr*mIKc1D^V zI{myOox1DH$mG+~l75-rrKHs%OQ+OPO-Bc2hK{0&ZaF#H+M&}rO&+IB(kVeVwF$>#pCEsb-s zGu@W>c+!)xDGk#fZVkAG)g1bc-5N?%VDij#u&`+aMOmU$j8=|VMl#8Bx-hnximhB% zY0aw~8o9bLo}rP0PCK01@_nrAl;Y9H)Hu}@mz}9C%r85eYNTZtM)Qwf}s zY*G?gF1?Vf85KI1zh(}s(sJCzyWk-;WJ_Y(tQo)Qu&8KM(-|qdd$zz$+sQVsnkl7z zXom6O60A&>des?AEv%?*_ZuB}*o$xg#eQnWmmMI!0*iY|;rW!Mto|ZL@Kj?CIbM5spf4iXxHCDFraLx)kxWR^pRjt~h+wJq<>;0>W)b4kGgUi>CFK;(uG@&;- z-EenDLVc;ZJmQP#wBEdujbnC>*{wY}c3VMj{A>+vL?s?zAC)b#I8t$&h#(XNlDy-H43O3zbA zs*g)*YAQS=cztJG)AciYl9aCh0Ho#j>v6t#*{mN-^t)*9WX)%fEN)*9OZ7Qv$ER~u7xTH{L#v!{q|!NcI^9iK)aKs`#+9olGaByezfW1E zdz?S2%eg0J+IJV7jz838ZZveq3tURkN_DuCq(riXNON&!J-NX$ZXpR|=UZHDGW&+A zvP)Z-wNW{cnkIXbbYjrwM?R!{GNkjloTTiDSj{wXbu-D?si|9@cC_ugDmmpLMnp-b z!(;;0mJVj^`!7;iGPh(_ik<F;1fpDiLAV(>)sqPYDO;e(YmTx4tg`AAOfxzxx56 z>G&QN7R22Tcap_5OC>6M6TaVpuV2_BM5?opP2F-zcZTqaiqALwixQjLw~%iYSQAZG zz-7|FMR!_AX*RtX?Y=YlE;m*oMQ4JOUBuRGD}3?>u#&&7at_zxg2bjsM-%FZj-niF{@1K(VaRYPVF~t zj)>AnBqYwbM%A#faDEP*G-Db@@nwrnNJzHRL_8fMEmbroh>VeFA|R#O>{M zeh)jRPCBUc^ya^%?9Q{yW_xyYy7Q9lZwU0Hr~6B`X}6V6W5IU`TwNV~Cs^Y;blda6 z=lLYZuj1+SKc~i8dY0v!9>nyaIJVtM{{Sb?X!Wc2QpBFgp8ki|rxh$S$$HFsagMfz zmKK&J$%5%;-A$d7>E{P0@b1ai3%=FTv^beMY{VV4F|p zgB=cubrb!|O*6kortFt=pV1y!o^AbSsV}8OYox{byIBI7+wK z9xk(jbe)pc`RBu3XsJa>HD(gjtvY6X%*J}VsS{TQr16ZIt#=s8FJn3Ca<(#@yO*-+ zSS>kO)ui4IEjIh9qteiYq|HVvZre(aGp(QfUf&0@;^3tJ%P^@7s-U>lNDN8WJ_W@h;wEYGThE=)43T<;>2dT zb*F}T`i{=7rdgzLYD$WTdyTvk780Hx0Ar}~LRJ!$yfBJnQ%a1rE&Liux-gUDkTRBV zg~biF*)6KrVWli`WahHD9PJ|~mrHOeFZe4Jf;LN$H-_?54J;7XwL{O5IaelH5!05W z)FPXcbdo1qN?KEJn(dr-LEj}WrL_E+w{I=yk>^hs9{D4WV{Vl3sys9EqmwE6}ExXu;@t2z`>_^)G zEY@;0m-0>hSQ>cokgBolB~3lB7F1fQ8gI!9*>P)ixnfh<`vjJ9JMQD~DyQfnt+v1{ zKS1vcBI_@Z8x?Jl+IxM5o#8YQU(7vH#@0?~M%}ZoPtnp=Ri?Xj5!1F#cl5~{ zZxd3cG*i!%QksOVBy(iAr!`!d>iBfy#de4isrjtljHQ<;QLwX zXlpG!1e9<80NcUh`8oZ2c~=PJ~?3lB9G?LUNm(?cm2(Qy*4q6wg%aTrW77sKLgU(rcp) zJy}VqeT=_;9f`P{v+5UpX||cavp1bbTl!Pg)NQTKPyYZE>-ri!34W!i2rlK_UQTEF z%;hfDseBf6&E~b~x=k$z#p+3_*&H2zRXuq)Mm?HqW#qknv1(<#yq<2hthF1>lJ>mt z&sX|XQ!@RxYR#h_>=&sNqG#;X<;ygpot#>&JEN~wR~C*hDAe+o!* z)^_yCFY3wCM>|;QX*Vs8OGi()1i}$=!P6&_E4vg$ zIm-t;oxRO98Znj_3DKOMQ1~KJDkZ#-?K=lAs9n~w0P*38yI&*YdLivViFn3Iww}Q& z@=erQc1E;HO|8k#8AQ2Z@Jnqrf+@Ub@=fE&uGU#DPiB$?UAo zvaT77)f(P*`w^*JrSeGA2{Sk9%a_TMy1GW0FJ$i9LwM%wmw=^XYaYc;kS+ZMQflxu zo<8EQu+wO)sZ}?~(!Y{Z&IfqGQE~DOy4;lAh38}w4Lal=%JwJyrAAx@R9g)M7jIFGM zZ{0)gq9f()V{WmMR-<@oHvD{>TWZc-JA5;?IA36ls{ESit-TWMGJCR!uTMdz8nT}F zXBLjHYdYMnn%H9M<30_cHt=#eWVlJ;lNy!V8B<4IoUGxCv!_Yah_6FT-Dh)_55d<3 zYRtD9L2zoSJDI7dpo@Bs(Pdn8EUXV>K!*p`8ajYm!qD%KIcYqlK!hmTIvlazoX0O`qeyb<(`jh zWdz|XCmrSTy>qU4OEoCq=jvyr8C+E}x-d^sUmwZK)%4J6Qc<_Ho#%Sz^>FnKPfpS2 z$y2*KW}02JaZWG3xHQTe?o>=+pGK+_!KZjzmI+CBDjvL3`5biHcPB{8TJ2LTn@`6S z;?rsDbIo?k2WF%0bkR{lfg9?o3<5JlDEHtqnr@(`Jw$yD#IZfIaUahc*zoUM5UYHVw{_Q*v?OY zZ6|h1cHK}=+7oXUCv{=HWh~3u5_uh!m8Vkr zsT*m_IWS83`CXzt)OP5~w>u-PO9z;>GN|t9;oVkZ+;5*Qy*)WkFJqy{vMKUJbyOqTvmi~k zY?EuH$h9uxSUP4sxU^c8qk7Id;`Ti)D6LI3d(q?R>Tr5p9;eomI&)a9o|kJPQ9Jc? z`{Ue>M4VsLjaWu%6k1eLMv4kbjM&ty^q*{-*xCyg>vCZTsih{Va#p8mmL49+;bflO z<#u%aM{X7mSElPyN-j@3o*tT{nYxos(Tm2{S<~phbGck@xHNj2lF)Y(#nI~~t3L;) zqtz&<9a~n6`RChS*#6Icq^rThtJBR+==QX=`fIC2YObGBKK&i1$u*0|htEq+N}4I9 zi$|vVPUTV9<<;q2FMaksT*+K|mN_*ejBIA~T$-hiCw`Al`-#L-(Uz7tI(-?PJn8h# z_6MWg6>DSA{iP_mcPm^ReNLn_=_bh?dqDKy=)k?vIYwr2GeVC8x-a;h zPVL~0_Q6m~dZ%RCOWF?aJBc*kpc|XICCO5G6zc5}C%1-1pzdnCvRhQS6rD0wB|r{T zgxjihOjmC^3gXjzluK2+78(ZoR@oDHw5c*Ea_w8$Nz`)4tb7IJ3UlsN&yzN*lvJKF zl4M~8g)ae%VYM~LvXdf{YM1gSoQD_{vMBlcj_Z);G%d<0I(8S}DzFcKNTQwJvDR~z zN|5b5hE-^*{1LK4ypV%uGj!jco!Oa%#+p}0U)v-v@-QW_=;J^J}> zD|ICo?6_(^>=-2F@@o~_R%W#xmW4^>38<7-U)Ay4f8ztRGM1~xkRUXmlbT2!(P>!7 zdTC{w$8&PCW7+Ao6ycJ3ojy{*%b}~c@3r1#LPPVLNS);S} z(nmK_2dul7;R)Qg%Gu+?nq5|+cay89{YFZoCv5Y-bGA-0Uy^&$csqX_w_}&`*!nFm z)Grc}SFxw=DNei7K6W~JRYx$##d zrv2MIuCMhMH&Nr0re)gaeoq-5k5kvE!lQ%8>N>vlmuDWQP~`rm*@U%QLiXkDdYY}h z=@?H@Rt)DmGrKunmlZ8W%)7D|Vyu|e4dgc)R*96MJui=$vyeWD#DYz zbL5tqeum_(#M^b?DLX{%7ej5;a!vg^WCQxQ$d>F`dtgc5*yDZ1o;a#qHX&QdB;Rmx-SyrIVrVw$C4nw`~E0t)TNkzEvN7i<3F<&*yFq1v~= z4M%T2N480B`C~v@mKAOnR_I^cXjy z2@yiKm6KRX-?A1RHMm98nnW9?>^7EnB{bGYH@}$TBzt&x`Z!sn^&_PlW-``{4?n2tUaXzG9sb!I@p}DCwIuTB=9HM6utk`? z7*5+F$0kQ|vpTp;ipb|Ap5}AbidZJ1+M!9sM|_w|sI|F+)oh5`THB;!Jxk=qQ^_sZ z(Q=tBl2XRuod{EsM9OL>F0QTKN{ZFjRYwk_uaVAen95cWF94qdYw0Dv)CD(4=bY`1 zT9>h#@&a|P$&0)H07ObllA*VhL!>E7@Gk6nK~US?@Mh(B=gD)H z^kXSB{{Y|+aa@wxs$nOytdR31$rWyuXV~Yd8EHZ>f>Mt1@=JA$5aq`L!cuOL5^C?1 zH74$s#5~IO@=d(mh4$SOxar^*e;kgYjU7Xr(2bWcS1`GC~4=| zC|{HbdsrP%JNr--cjChMb!4SW*=gNy!UWQ7qT=oNCwKlw96v=zPMW?=Q>AA#XZE9L z$+dPvS|{)9JFk;8@r}xgzDTNrxlz1+rIRSdCAnX@#{yKbUCKO(JAQ`M=W!&_Q(Ecc zEbGzKsd_mg{JOJ6JQA#X5So-c6$`V^(AK9-o{o%`r4t&LUA;f`BiPWT6nQ;7Dpq>h zDY&DbPP{uKQmQ+2bpDicboy_(T0N~kmAyNPIol(*GS>#AYh{xeX-O+RT^PqnzVw^E zOuCQTea&L{G56b!pG=!xPjga7Oed0FpLR_&p=zGzURr}q>mdkf-ktvd>$uZS4qpTR0DUuG z+=Vq0Ejg`8)TuPg&aG~|8QAkWi74tiXJ1#;C`Yw3$JCpXJN!KU*AJVi>MmN; zWj>~iwH;-%gIlQ0BhxNC)zPihZc2l@7duo$nGRgo4o%$<+p+gv#&UGa60f?IvkA)aV{-fu-GQ5QF6Car zcx=t!iniJ_Y))3ylP7M9fxBLe+Z%sPWX-%|`2f2MRPrYzl`NRbS6l@-Hr=g=zC-bT zN#)oI^Zr8Qo$$g>B`h@ z$ZmlH-#dw_Uct7~X=FaGNYG9xXCiMP^3zBkzz;2WA&rPE8Dd-RJ^?SJ1l0ove_~sE z5l1y+;6Y?lOE?c`7xo+YDg?1(etFUA}{6()@1)+F-MJQezkr@J(cx9(=mUkNV0f&Qf( zG`_1E{X#mMGv{Tf?{UXfteKblZuoshp0{%Tq+jm#e@i}RPpO)Aq;xL3Q7y&#(pw)* zqtvh4wU@~+UMgx?UQeEf>N=3vxB7&nvWX`uW|z)3$L>Su1U$&7WI0gwAPe z%1^{ymC_ZE4CS}=pvUk z7#0>e8uGuAp}oejb;y<;!jAdCMLa(!R_XX2{Nz0pIIAgIA=kSLb#xJSzQD31K6ePD z>x>Nx3jv(i2H9n%v4Sl<$9=g7*-Gk|<8N;!P-|mYV5rtP!ZnUeW4^|#QZ*FjG44`XwTKJw!7mb`75~PjMZSi=BSn|zFi&E zQ<;L5v^OGR{g&ntotSqH@NPzJXyxpeC0m)cOL*L|n@!60V4i&#xqA-pWld`-WSL9p zGL)6>PwwHBoo(DirR>A^Y>8?zrnQvPPqUEV(Go53S9Iot%O32eiJX2)1 z{E+Y~X@k9cWMw^yYc=4Be^yuySS7qx5SxA5s^DI3O%bRG*nTn&w*a=Ep?c&> z#P7P+7*xJ^67YuA+u$ym#z@g-s9C&&O+15k{1Hc+`wHw_Z;<3D_23rc_D)x#2HRK% z`33EX<6sr}7=qeFQrsvQ&{J1bCaaO`tH@H=s5uS6;n4B_009LKt>8=Ge+3C(tP+>8 z?S=v5sPW*8*=vCko^ALQdEBIfZV^x`Y^Q{o8*ay%u!mNz_8%DxH-Jj^WkGFMBUyGz z!^Y*u7zI*(5Y}$kCu)-Ca%K0jvUsF}d_D!2p9NkBrn8-ki*0g4LEQM*Bcd}~54x7d z3Rts5?5XLrCBL5qsdpGwRX@2(R%W#wFu6S7(X^ksF;neDEVt~E7PL-~`}?{64BNee zIW?@~qB3c2Xtku{{Y>Ukr?AuJ`3UQb-De@*?8QiF{1V2cbw^W!XzDtoa6)#CwC673 zV#@Vk(uQ{?Qu@9;9IIa&l^sW0*^G47>AV!5vna`18hI3sm8n=Xj<1$-SzXjA9kA6_ zRJBt~!MR8Q^AAObW50!wQgPocFbIrv8mI^ zaW$mnW^LdlBR2hn)e`2Zk=)a!Cetn^n`?4Ztqr8{WULHLDLj#7Nv+J}(WG7OLt^f- zC#i}8yOMTHrmEKFPHVKy&%jJ+El+!p-VB_-xk=bi@=N3%b`^anVrZrA!!54>mNE`Y z1r^7%WjFT^+#$~W0wQv@X7^qm>r?$rR*tlflGfOZ*(VM4W`Mv$05#m731WMVew+My#@Qg zt=7Q7Yj%OId(j0w?y@fBa6#g;jOKJ~6_W@Nrh5_;*p#u{GDmVbX01CPX zziKlYx{w!t=lPc!HRjLXr1RJ_MV!l zI$WA}(ymT-g|!Vn=WJ3sBKW+TO9@xp!ZM|IlDnj_c1&GUZOJ5Bc%`urt;%vqTHGT0 zvG81F+`Sm5Tb87-fY*epV_3~aTjb}BbFoeFV2!_XG-$PO=;Ttj{zn;EF?HxeRU>F> zDs;~^H}Y*6_OFs-Q>Sv9AMR|9Nb5^^Y*N-t&NZ&y{{W-e5}o~wzjZaa9sa>vu>FsY@H89M#$| zb4-Uaqjx?G-o)nL zgC6Q*Zv0pSljN5q{{R5?7bQU6Ee_sLEwF+76%8lAeC{PG`;j?w$x!7w#|BPnn46Vd zWX|$UTsJ#)R1QzJ?3X6Q^S%huo4Te%uJ2%Nt}qQIx6c4gw1X`Q+OI(5&<5Pm7SKtM z+iu$?tcT4!3DSFp!dsD`+u1$Ay0q2@SCSrOjhB*!+T9{#4dgF&_add?{DyW1Z3^F? zxf%}`NZOS@ZyUvhPTbV9W9T9zrGg2!Jz z#a+(%TUcAWz(YxInh&Kt=x=JUrv5-K@?2$%k;egezu>7`IV@8Jwb_^7B}PrJV8*H7 zrKxmeo8N+>5x4Z=F~^HC?z+Ktt1s?FYip6J-}o|&v1O;7@I`E@pA4I)Tnf^xlHYZr zYT~Z2R9|fonniG3OlcWcqem-PIpZg@!Y{k(-ooReGUmZQtI3acD~42OTaa(xiWhyo z-5Dw81GTstcR73mlBzQAc7@#k042etr0Vwu^S(^mTg_!qDLs)t z)ws1}4dhMm*fnu}LaP>Kp=;Q(vOHEDU--z@J-;01NJN58;95%rbQcaA*^MO z`Sb)^u-{;I^1_IvcnyK7NHJX4QJ&qww)6Z43}4@X1-MXPL{ObN8+a9Y01jvcvj*RS zDGz|)U_F8S4-NVY$)-xe!R`PSA2}FZEr1Xtm52EZQ}P3OK&v5uPk$vt+XGwM$hpfJ zTYw?v$sNDWN{UgH)dJ)&_Z#k|#sU+qxejsxYPJHCrW*B}yTa_9`uOr@!*4JgHYcBw3p9z&2Qu1b`xGxx30^GiwZ)1#;-O-e!`>88Ho!;hg zt!ky3;J)klF;_xOrc5Pobg)!qOFWsUbwlFTLbOigR(u&o>nx=&Yz~|MMlkwy0AicCh+`(Xytce zantKbmUgmO^aD>>?8mV5y z%1vU(Zz=c{03HJIu(|X+cq$OsSXn@_=yiLsdkpyq z7qQ`NODSV;1zhYrH)vMf;1`Q1cHbU~_LjoO0^}jPrGcz87g(V!z3Nvl@i)X-y>hsmV{D3RB+gxH@-o?BDKFh!WzPF@Co_IuN*H!QLrv9 zmFA1@Mr*|bd~yZ%d;Ax7?HH^g)Vp`d6{>jnEpdY{+|ALXbuKqRQ?}?y+*U#|a&OS& z)-#GBq@6OYSNs#(OAgRev@vyE!%nY*H??d%(#RKgm65kicp-eGIX|TXPRX>}3$!b& zARSx7z}v02CAO~>;1rWsSpu#0*l%1th24yY`$8TQpiOr>*oQ0w*rBIZo1t5yHthxH z!UCMLf>W;M#lIx%mg5Z*Z)+7RO|qJDU-C_2kOOVJuuD9HUT}pQOZycSEFxa}55H}& zOr>uuBEOaBhSxj__Et-3_X~&ImcZXEhTj_s=?z>{-!x6wJFDN&xgv#h4wAsUAZqi- zQg0(}n0OPb$piAm&f>d}Y^=Iq?m~6jb`C4GWKOTq8X2@INju)fZnO3k(pUK+oAiZ* zvB>2sM656lHpl}7wUI->a+o}dumf3OC$O@BeBe(U4R7EZ&Pb_uVYtNY1sMhy@G=3D zL*y_3{{Vq}j6@a11%R=PnnM7v0P+YJC3pe*1hed3Myq!6Q1-CMvE*wZ1GMRY-=O!& zL2PL){RVD={{R6GoPycpOEMa_gaO9k94*1DQ``|y_gF!75cnN7w*Yqe47jVH<#`&v zArCgQK;AGPkrfoBL&`JxF7aD|S6plz)=pBePlvEfTz*-0pWBl(>v|h}0ZNCB(J_`S zA>FJOQw`#oS9?(|q{D6%1X^m|SD`!a;7z2d6K^7{Ta|FKQf{4Ki_NmreWVRvC93j6 zbxAUmli~b?Qf~v4o$f2mq2Yy6Zjv3lB`cOLy^869ZeLJh(!7w>HJ3tc6{B^)Iach1 z)4LOHfV-{imfKrbf^GO?lTNWi4O5lGR@UVAUIDdyfoZL)j!KlB8K&vF!1i^QV+|Ib zF=9`YzQE$G3f99DL(8-_+DQkU!P{7QE+&rL)e2ATK;>@l1i2*_GUCHga zGMWl)+ozFAdGL`ASBuD?HFcE04vyKNGsYoB9wG{SOQEHHGIGR1hvXCzW@>Etgf z0ZWlpVe%CI)Hj@yw<5cUL#7u>xfRg4z*Dg`e2a~NvM6+`mL~qKfnZJN(G*i28r)ln zV{ga>uK|V_71&bdm;*umh@}2U-H_v8#Qj00Ci00Be278@IkhH{}a+LF^ldHVCcu3qlIy1MF4WStp7NCILgN z&|e{UB8Ij@%HD;#2GwN*I|V?PDsKw~50*Dvf9OfR1AW3D@)b$oFMzxRB(H*z3i-%JyS@eQ*$eub*d64f zNXr$#+`gkO{gPw4w8o|_%ZnwuYll1Z;~vBDOVPSwpSr0oiM!Qi-i6{ z>=L_F9N;N^$7xk;DSsl#6y4p6eeetJE^-4(m@4oXpuflu;FjoUIA=W0JDH^ z0@w!A*fs|27R&&5Pa+Rx6r=$_y_F3=K$};vlgQVQ8mfQXYdCC{!30j2 zyx}e9UQ;VADf&IX}zTvxD3%eU`M}MmF2A0P0=+4tsvK#WB-GdgI z-(Yy$l+wtq@A3s-)3HA+8?=`M-Qg?(G+9EwdE{8eMBP#Xfn_`02}ucicmliCQ7z%! zhjtt1f?Tm+JI44QbG%@TUPmUvski>$87B=`o?WK0OWy1XO*_DzN%P<_c?xeicoW7` z&#-%L7~lb0-^iXvc-TqfU>jB`Rc<@;u?oC$2&-zE*iX+O+T+NrT@goiFu}YD;5)5l zWQs3=W58a11pWewbkh}u^0fxj-IwWrh0f=FA z0uDie06l|o008VRxF|Ub0BayL0eD`6(NKMY{=l~i0rD9$rKj7n}vWWR%9+WkqLTs`(UZ%1bNAK^B$XM7-o{d%#tIxgG&? zV6wLWOEk9+?l$Sz>eb@7Di+(IRmiJ%W5!?h0QlVvW$sE|usrtoDl3v3&yn!FlD!3r z!&ppq;OtiR9lk&-&n2uP6N|?MJRpN?V<9c}5^sJ+wqP~og*&jjK&8j{2yc8bC&T1c z_k402yn#;Jt;VqOO0~f1^J#hlpK9_X=uI(u0B0AXGm z1^5Q?s0O`1$XZNPx%?n%H_KoAd*y9W3Yz!kuOyABE)3=PAOTp(FkC%WDS zo&c&zaK|T%A{E_~os=9efm+yY-Vm*AxezZr3?ou>Hym%0iCe4%WE;Bh8&v@6whZsU za4XI5QA>;ivT*akQA+Z{9>TS63YdCg?Z6bNvGI0C!B1tJkUs7KQn7KkCbnQz+oYY? zJZ8e0yjBR!(M8@j1hYbK{{Rq`?{tVNPmVEYyPa-L)7*Sll4*zCf8-m%GRxZ(7xdFZ zR~2o$!xi}0GLbFT5D#TSUg#^}9o6^>RYPJ=@(%eROT$C4W90x*i`+7m&?;{219gBW z_!^++jNc#bEP8XrS0ELrGn>KtEdzI(~`T(Mp#Lq51v; zYb9xGzHv!`W;_y9zJfB*;x9SMbpAOP3^0002%0${h!zylYs4S+ZgK)?nb!;c_c z59}%qZ~zDvb_)dBU^)zl5kn8~AmkyTSZq1~@DIRZ!*B)cIS-ZqG}R8ph4&AJP}BIZ zdj%!C2=WkC=x^X#@;0sElq}fOp{=d#O`t0LioJ^LecVt(utHgU2c!%;$mGeo+%OC^ zNl^YT7X0B+;??fk#Y79oq`>X=`t@gR$6`l6B;LGtfeZCZD47F@j|wn#URal z;1YHNwOiy@mYBb!U+%CeyZZ^es}o;fyaiHriK-p_guftGYhb!d^$+;1J2%sLqUn8IuhX4cvKmY&$ K0Y!iSAOG38#4Yy# diff --git a/web/flot/examples/image.html b/web/flot/examples/image.html deleted file mode 100644 index 073ad43..0000000 --- a/web/flot/examples/image.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

The Cat's Eye Nebula (picture from Hubble).

- -

With the image plugin, you can plot images. This is for example - useful for getting ticks on complex prerendered visualizations. - Instead of inputting data points, you put in the images and where - their two opposite corners are supposed to be in plot space.

- -

Images represent a little further complication because you need - to make sure they are loaded before you can use them (Flot skips - incomplete images). The plugin comes with a couple of helpers - for doing that.

- - - - - diff --git a/web/flot/examples/index.html b/web/flot/examples/index.html deleted file mode 100644 index ef85363..0000000 --- a/web/flot/examples/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - Flot Examples - - - -

Flot Examples

- -

Here are some examples for Flot, the Javascript charting library for jQuery:

- - - -

Being interactive:

- - - -

Various features:

- - - - diff --git a/web/flot/examples/interacting-axes.html b/web/flot/examples/interacting-axes.html deleted file mode 100644 index 9995c26..0000000 --- a/web/flot/examples/interacting-axes.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

With multiple axes, you sometimes need to interact with them. A - simple way to do this is to draw the plot, deduce the axis - placements and insert a couple of divs on top to catch events. - Try clicking an axis.

- -

- - - - diff --git a/web/flot/examples/interacting.html b/web/flot/examples/interacting.html deleted file mode 100644 index 7db829d..0000000 --- a/web/flot/examples/interacting.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

One of the goals of Flot is to support user interactions. Try - pointing and clicking on the points.

- -

- - - -

- -

A tooltip is easy to build with a bit of jQuery code and the - data returned from the plot.

- -

- - - - - diff --git a/web/flot/examples/layout.css b/web/flot/examples/layout.css deleted file mode 100644 index 7ef7dd4..0000000 --- a/web/flot/examples/layout.css +++ /dev/null @@ -1,6 +0,0 @@ -body { - font-family: sans-serif; - font-size: 16px; - margin: 50px; - max-width: 800px; -} diff --git a/web/flot/examples/multiple-axes.html b/web/flot/examples/multiple-axes.html deleted file mode 100644 index 4b32e64..0000000 --- a/web/flot/examples/multiple-axes.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

Multiple axis support showing the raw oil price in US $/barrel of - crude oil vs. the exchange rate from US $ to €.

- -

As illustrated, you can put in multiple axes if you - need to. For each data series, simply specify the axis number. - In the options, you can then configure where you want the extra - axes to appear.

- -

Position axis or .

- - - - diff --git a/web/flot/examples/navigate.html b/web/flot/examples/navigate.html deleted file mode 100644 index c916ef2..0000000 --- a/web/flot/examples/navigate.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - Flot Examples - - - - - - - - -

Flot Examples

- -
- -

- -

With the navigate plugin it is easy to add panning and zooming. - Drag to pan, double click to zoom (or use the mouse scrollwheel).

- -

The plugin fires events (useful for synchronizing several - plots) and adds a couple of public methods so you can easily build - a little user interface around it, like the little buttons at the - top right in the plot.

- - - - - - diff --git a/web/flot/examples/percentiles.html b/web/flot/examples/percentiles.html deleted file mode 100644 index 9f2ba3a..0000000 --- a/web/flot/examples/percentiles.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

Height in centimeters of individuals from the US (2003-2006) as function of - age in years (source: CDC). - The 15%-85%, 25%-75% and 50% percentiles are indicated.

- -

For each point of a filled curve, you can specify an arbitrary - bottom. As this example illustrates, this can be useful for - plotting percentiles. If you have the data sets available without - appropriate fill bottoms, you can use the fillbetween plugin to - compute the data point bottoms automatically.

- - - - - diff --git a/web/flot/examples/pie.html b/web/flot/examples/pie.html deleted file mode 100644 index 77fb141..0000000 --- a/web/flot/examples/pie.html +++ /dev/null @@ -1,756 +0,0 @@ - - - - - Flot Pie Examples - - - - - - - - - -

Flot Pie Examples

- -

Default with Legend

-
- - -

Default without Legend

-
- - -

Graph2

-
- - -

Graph3

-
- - -

Graph4

-
- - -

Graph5

-
- - -

Graph6

-
- - -

Graph7

-
- - -

Graph8

-
- - -

Graph9

-
- - -

Donut

-
- - -

Interactive

-
- - -

Pie Options

-
    -
  • option: default value - Description of option
  • -
  • show: false - Enable the plugin and draw as a pie.
  • -
  • radius: 'auto' - Sets the radius of the pie. If value is between 0 and 1 (inclusive) then it will use that as a percentage of the available space (size of the container), otherwise it will use the value as a direct pixel length. If set to 'auto', it will be set to 1 if the legend is enabled and 3/4 if not.
  • -
  • innerRadius: 0 - Sets the radius of the donut hole. If value is between 0 and 1 (inclusive) then it will use that as a percentage of the radius, otherwise it will use the value as a direct pixel length.
  • -
  • startAngle: 3/2 - Factor of PI used for the starting angle (in radians) It can range between 0 and 2 (where 0 and 2 have the same result).
  • -
  • tilt: 1 - Percentage of tilt ranging from 0 and 1, where 1 has no change (fully vertical) and 0 is completely flat (fully horizontal -- in which case nothing actually gets drawn).
  • -
  • offset:
      -
    • top: 0 - Pixel distance to move the pie up and down (relative to the center).
    • -
    • left: 'auto' - Pixel distance to move the pie left and right (relative to the center).
    • -
    -
  • stroke:
      -
    • color: '#FFF' - Color of the border of each slice. Hexadecimal color definitions are prefered (other formats may or may not work).
    • -
    • width: 1 - Pixel width of the border of each slice.
    • -
    -
  • label:
      -
    • show: 'auto' - Enable/Disable the labels. This can be set to true, false, or 'auto'. When set to 'auto', it will be set to false if the legend is enabled and true if not.
    • -
    • radius: 1 - Sets the radius at which to place the labels. If value is between 0 and 1 (inclusive) then it will use that as a percentage of the available space (size of the container), otherwise it will use the value as a direct pixel length.
    • -
    • threshold: 0 - Hides the labels of any pie slice that is smaller than the specified percentage (ranging from 0 to 1) i.e. a value of '0.03' will hide all slices 3% or less of the total.
    • -
    • formatter: [function] - This function specifies how the positioned labels should be formatted, and is applied after the legend's labelFormatter function. The labels can also still be styled using the class "pieLabel" (i.e. ".pieLabel" or "#graph1 .pieLabel").
    • -
    • radius: 1 - Sets the radius at which to place the labels. If value is between 0 and 1 (inclusive) then it will use that as a percentage of the available space (size of the container), otherwise it will use the value as a direct pixel length.
    • -
    • background:
        -
      • color: null - Backgound color of the positioned labels. If null, the plugin will automatically use the color of the slice.
      • -
      • opacity: 0 - Opacity of the background for the positioned labels. Acceptable values range from 0 to 1, where 0 is completely transparent and 1 is completely opaque.
      • -
      -
    -
  • combine:
      -
    • threshold: 0 - Combines all slices that are smaller than the specified percentage (ranging from 0 to 1) i.e. a value of '0.03' will combine all slices 3% or less into one slice).
    • -
    • color: null - Backgound color of the positioned labels. If null, the plugin will automatically use the color of the first slice to be combined.
    • -
    • label: 'Other' - Label text for the combined slice.
    • -
    -
  • highlight:
      -
    • opacity: 0.5 - Opacity of the highlight overlay on top of the current pie slice. Currently this just uses a white overlay, but support for changing the color of the overlay will also be added at a later date. -
    -
- -

Changes/Features

-
    -
  • v1.0 - November 20th, 2009 - Brian Medendorp
  • -
  • The pie plug-in is now part of the Flot repository! This should make it a lot easier to deal with.
  • -
  • Added a new option (innerRadius) to add a "donut hole" to the center of the pie, based on comtributions from Anthony Aragues. I was a little reluctant to add this feature because it doesn't work very well with the shadow created for the tilted pie, but figured it was worthwhile for non-tilted pies. Also, excanvas apparently doesn't support compositing, so it will fall back to using the stroke color to fill in the center (but I recommend setting the stroke color to the background color anyway).
  • -
  • Changed the lineJoin for the border of the pie slices to use the 'round' option. This should make the center of the pie look better, particularly when there are numerous thin slices.
  • -
  • Included a bug fix submitted by btburnett3 to display a slightly smaller slice in the event that the slice is 100% and being rendered with Internet Explorer. I haven't experienced this bug myself, but it doesn't seem to hurt anything so I've included it.
  • -
  • The tilt value is now used when calculating the maximum radius of the pie in relation to the height of the container. This should prevent the pie from being smaller than it needed to in some cases, as well as reducing the amount of extra white space generated above and below the pie.
  • -
  • Hover and Click functionality are now availabe!
      -
    • Thanks to btburnett3 for the original hover functionality and Anthony Aragues for the modification that makes it compatable with excanvas, this was a huge help!
    • -
    • Added a new option (highlight opacity) to modify the highlight created when mousing over a slice. Currently this just uses a white overlay, but an option to change the hightlight color will be added when the appropriate functionality becomes available. -
    • I had a major setback that required me to practically rebuild the hover/click events from scratch one piece at a time (I discovered that it only worked with a single pie on a page at a time), but the end result ended up being virtually identical to the original, so I'm not quite sure what exactly made it work.
    • -
    • Warning: There are some minor issues with using this functionality in conjuction with some of the other more advanced features (tilt and donut). When using a donut hole, the inner portion still triggers the events even though that portion of the pie is no longer visible. When tilted, the interactive portions still use the original, untilted version of the pie when determining mouse position (this is because the isPointInPath function apparently doesn't work with transformations), however hover and click both work this way, so the appropriate slice is still highlighted when clicking, and it isn't as noticable of a problem.
    • -
  • -
  • Included a bug fix submitted by Xavi Ivars to fix array issues when other javascript libraries are included in addition to jQuery
  • -
    -
  • v0.4 - July 1st, 2009 - Brian Medendorp
  • -
  • Each series will now be shown in the legend, even if it's value is zero. The series will not get a positioned label because it will overlap with the other labels present and often makes them unreadable.
  • -
  • Data can now be passed in using the standard Flot method using an array of datapoints, the pie plugin will simply use the first y-value that it finds for each series in this case. The plugin uses this datastructure internally, but you can still use the old method of passing in a single numerical value for each series (the plugin will convert it as necessary). This should make it easier to transition from other types of graphs (such as a stacked bar graph) to a pie.
  • -
  • The pie can now be tilted at an angle with a new "tilt" option. Acceptable values range from 0-1, where 1 has no change (fully vertical) and 0 is completely flat (fully horizontal -- in which case nothing actually gets drawn). If the plugin determines that it will fit within the canvas, a drop shadow will be drawn under the tilted pie (this also requires a tilt value of 0.8 or less).
  • -
    -
  • v0.3.2 - June 25th, 2009 - Brian Medendorp
  • -
  • Fixed a bug that was causing the pie to be shifted too far left or right when the legend is showing in some cases.
  • -
    -
  • v0.3.1 - June 24th, 2009 - Brian Medendorp
  • -
  • Fixed a bug that was causing nothing to be drawn and generating a javascript error if any of the data values were set to zero.
  • -
    -
  • v0.3 - June 23rd, 2009 - Brian Medendorp
  • -
  • The legend now works without any modifications! Because of changes made to flot and the plugin system (thanks Ole Laursen!) I was able to simplify a number of things and am now able to use the legend without the direct access hack that was required in the previous version.
  • -
    -
  • v0.2 - June 22nd, 2009 - Brian Medendorp
  • -
  • The legend now works but only if you make the necessary changes to jquery.flot.js. Because of this, I changed the default values for pie.radius and pie.label.show to new 'auto' settings that change the default behavior of the size and labels depending on whether the legend functionality is available or not.
  • -
    -
  • v0.1 - June 18th, 2009 - Brian Medendorp
  • -
  • Rewrote the entire pie code into a flot plugin (since that is now an option), so it should be much easier to use and the code is cleaned up a bit. However, the (standard flot) legend is no longer available because the only way to prevent the grid lines from being displayed also prevents the legend from being displayed. Hopefully this can be fixed at a later date.
  • -
  • Restructured and combined some of the options. It should be much easier to deal with now.
  • -
  • Added the ability to change the starting point of the pie (still defaults to the top).
  • -
  • Modified the default options to show the labels to compensate for the lack of a legend.
  • -
  • Modified this page to use a random dataset. Note: you may need to refresh the page to see the effects of some of the examples.
  • -
    -
  • May 21st, 2009 - Brian Medendorp
  • -
  • Merged original pie modifications by Sergey Nosenko into the latest SVN version (as of May 15th, 2009) so that it will work with ie8.
  • -
  • Pie graph will now be centered in the canvas unless moved because of the legend or manually via the options. Additionally it prevents the pie from being moved beyond the edge of the canvas.
  • -
  • Modified the code related to the labelFormatter option to apply flot's legend labelFormatter first. This is so that the labels will be consistent, but still provide extra formatting for the positioned labels (such as adding the percentage value).
  • -
  • Positioned labels now have their backgrounds applied as a seperate element (much like the legend background) so that the opacity value can be set independently from the label itself (foreground). Additionally, the background color defaults to that of the matching slice.
  • -
  • As long as the labelOffset and radiusLimit are not set to hard values, the pie will be shrunk if the labels will extend outside the edge of the canvas
  • -
  • Added new options "radiusLimitFactor" and "radiusLimit" which limits how large the (visual) radius of the pie is in relation to the full radius (as calculated from the canvas dimensions) or a hard-pixel value (respectively). This allows for pushing the labels "outside" the pie.
  • -
  • Added a new option "labelHidePercent" that does not show the positioned labels of slices smaller than the specified percentage. This is to help prevent a bunch of overlapping labels from small slices.
  • -
  • Added a new option "sliceCombinePercent" that combines all slices smaller than the specified percentage into one larger slice. This is to help make the pie more attractive when there are a number of tiny slices. The options "sliceCombineColor" and "sliceCombineLabel" have also been added to change the color and name of the new slice if desired.
  • -
  • Tested in Firefox (3.0.10, 3.5b4), Internet Explorer (6.0.2900, 7.0.5730, 8.0.6001), Chrome (1.0.154), Opera (9.64), and Safari (3.1.1, 4 beta 5528.16). -
- - - - - diff --git a/web/flot/examples/realtime.html b/web/flot/examples/realtime.html deleted file mode 100644 index 3b427e1..0000000 --- a/web/flot/examples/realtime.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

You can update a chart periodically to get a real-time effect - by using a timer to insert the new data in the plot and redraw it.

- -

Time between updates: milliseconds

- - - - - diff --git a/web/flot/examples/resize.html b/web/flot/examples/resize.html deleted file mode 100644 index d1e18c3..0000000 --- a/web/flot/examples/resize.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - Flot Examples - - - - - - - - -

Flot Examples

- -
- -

- -

Sometimes it makes more sense to just let the plot take up the - available space. In that case, we need to redraw the plot each - time the placeholder changes its size. If you include the resize - plugin, this is handled automatically.

- -

Try resizing the window.

- - - - - - diff --git a/web/flot/examples/selection.html b/web/flot/examples/selection.html deleted file mode 100644 index 1646f5a..0000000 --- a/web/flot/examples/selection.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

1000 kg. CO2 emissions per year per capita for various countries (source: Wikipedia).

- -

Flot supports selections through the selection plugin. - You can enable rectangular selection - or one-dimensional selection if the user should only be able to - select on one axis. Try left-click and drag on the plot above - where selection on the x axis is enabled.

- -

You selected:

- -

The plot command returns a plot object you can use to control - the selection. Click the buttons below.

- -

-

- -

Selections are really useful for zooming. Just replot the - chart with min and max values for the axes set to the values - in the "plotselected" event triggered. Enable the checkbox - below and select a region again.

- -

- - - - - diff --git a/web/flot/examples/setting-options.html b/web/flot/examples/setting-options.html deleted file mode 100644 index 8d1967e..0000000 --- a/web/flot/examples/setting-options.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

There are plenty of options you can set to control the precise - looks of your plot. You can control the ticks on the axes, the - legend, the graph type, etc. The idea is that Flot goes to great - lengths to provide sensible defaults so that you don't have to - customize much for a good result.

- - - - - diff --git a/web/flot/examples/stacking.html b/web/flot/examples/stacking.html deleted file mode 100644 index b7de391..0000000 --- a/web/flot/examples/stacking.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

With the stack plugin, you can have Flot stack the - series. This is useful if you wish to display both a total and the - constituents it is made of. The only requirement is that you provide - the input sorted on x.

- -

- - -

- -

- - - -

- - - - - diff --git a/web/flot/examples/symbols.html b/web/flot/examples/symbols.html deleted file mode 100644 index e71b1aa..0000000 --- a/web/flot/examples/symbols.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

Various point types. Circles are built-in. For other - point types, you can define a little callback function to draw the - symbol; some common ones are available in the symbol plugin.

- - - - - diff --git a/web/flot/examples/thresholding.html b/web/flot/examples/thresholding.html deleted file mode 100644 index f10144a..0000000 --- a/web/flot/examples/thresholding.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

With the threshold plugin, you can apply a specific color to - the part of a data series below a threshold. This is can be useful - for highlighting negative values, e.g. when displaying net results - or what's in stock.

- -

- - - -

- - - - - diff --git a/web/flot/examples/time.html b/web/flot/examples/time.html deleted file mode 100644 index da62347..0000000 --- a/web/flot/examples/time.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

Monthly mean atmospheric CO2 in PPM at Mauna Loa, Hawaii (source: NOAA/ESRL).

- -

If you tell Flot that an axis represents time, the data will - be interpreted as timestamps and the ticks adjusted and - formatted accordingly.

- -

Zoom to: - -

- -

The timestamps must be specified as Javascript timestamps, as - milliseconds since January 1, 1970 00:00. This is like Unix - timestamps, but in milliseconds instead of seconds (remember to - multiply with 1000!).

- -

As an extra caveat, the timestamps are interpreted according to - UTC to avoid having the graph shift with each visitor's local - time zone. So you might have to add your local time zone offset - to the timestamps or simply pretend that the data was produced - in UTC instead of your local time zone.

- - - - - diff --git a/web/flot/examples/tracking.html b/web/flot/examples/tracking.html deleted file mode 100644 index c116159..0000000 --- a/web/flot/examples/tracking.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

You can add crosshairs that'll track the mouse position, either - on both axes or as here on only one.

- -

If you combine it with listening on hover events, you can use - it to track the intersection on the curves by interpolating - the data points (look at the legend).

- -

- - - - - diff --git a/web/flot/examples/turning-series.html b/web/flot/examples/turning-series.html deleted file mode 100644 index bc6fd9f..0000000 --- a/web/flot/examples/turning-series.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Flot Examples - - - - - - -

Flot Examples

- -
- -

Here is an example with real data: military budgets for - various countries in constant (2005) million US dollars (source: SIPRI).

- -

Since all data is available client-side, it's pretty easy to - make the plot interactive. Try turning countries on/off with the - checkboxes below.

- -

Show:

- - - - - diff --git a/web/flot/examples/visitors.html b/web/flot/examples/visitors.html deleted file mode 100644 index 8a9d4d7..0000000 --- a/web/flot/examples/visitors.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
- -

Visitors per day to the Flot homepage. Weekends are colored. Try zooming. - The plot below shows an overview.

- -
- - - - - diff --git a/web/flot/examples/zooming.html b/web/flot/examples/zooming.html deleted file mode 100644 index 9a4ef22..0000000 --- a/web/flot/examples/zooming.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Flot Examples - - - - - - - -

Flot Examples

- -
-
-
- -
-
- -

-
- -

The selection support makes it easy to - construct flexible zooming schemes. With a few lines of code, the - small overview plot to the right has been connected to the large - plot. Try selecting a rectangle on either of them.

- - - - - diff --git a/web/flot/jquery.flot.fillbetween.js b/web/flot/jquery.flot.fillbetween.js deleted file mode 100644 index 69700e7..0000000 --- a/web/flot/jquery.flot.fillbetween.js +++ /dev/null @@ -1,183 +0,0 @@ -/* -Flot plugin for computing bottoms for filled line and bar charts. - -The case: you've got two series that you want to fill the area -between. In Flot terms, you need to use one as the fill bottom of the -other. You can specify the bottom of each data point as the third -coordinate manually, or you can use this plugin to compute it for you. - -In order to name the other series, you need to give it an id, like this - - var dataset = [ - { data: [ ... ], id: "foo" } , // use default bottom - { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom - ]; - - $.plot($("#placeholder"), dataset, { line: { show: true, fill: true }}); - -As a convenience, if the id given is a number that doesn't appear as -an id in the series, it is interpreted as the index in the array -instead (so fillBetween: 0 can also mean the first series). - -Internally, the plugin modifies the datapoints in each series. For -line series, extra data points might be inserted through -interpolation. Note that at points where the bottom line is not -defined (due to a null point or start/end of line), the current line -will show a gap too. The algorithm comes from the jquery.flot.stack.js -plugin, possibly some code could be shared. -*/ - -(function ($) { - var options = { - series: { fillBetween: null } // or number - }; - - function init(plot) { - function findBottomSeries(s, allseries) { - var i; - for (i = 0; i < allseries.length; ++i) { - if (allseries[i].id == s.fillBetween) - return allseries[i]; - } - - if (typeof s.fillBetween == "number") { - i = s.fillBetween; - - if (i < 0 || i >= allseries.length) - return null; - - return allseries[i]; - } - - return null; - } - - function computeFillBottoms(plot, s, datapoints) { - if (s.fillBetween == null) - return; - - var other = findBottomSeries(s, plot.getData()); - if (!other) - return; - - var ps = datapoints.pointsize, - points = datapoints.points, - otherps = other.datapoints.pointsize, - otherpoints = other.datapoints.points, - newpoints = [], - px, py, intery, qx, qy, bottom, - withlines = s.lines.show, - withbottom = ps > 2 && datapoints.format[2].y, - withsteps = withlines && s.lines.steps, - fromgap = true, - i = 0, j = 0, l; - - while (true) { - if (i >= points.length) - break; - - l = newpoints.length; - - if (points[i] == null) { - // copy gaps - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - i += ps; - } - else if (j >= otherpoints.length) { - // for lines, we can't use the rest of the points - if (!withlines) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - } - i += ps; - } - else if (otherpoints[j] == null) { - // oops, got a gap - for (m = 0; m < ps; ++m) - newpoints.push(null); - fromgap = true; - j += otherps; - } - else { - // cases where we actually got two points - px = points[i]; - py = points[i + 1]; - qx = otherpoints[j]; - qy = otherpoints[j + 1]; - bottom = 0; - - if (px == qx) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - //newpoints[l + 1] += qy; - bottom = qy; - - i += ps; - j += otherps; - } - else if (px > qx) { - // we got past point below, might need to - // insert interpolated extra point - if (withlines && i > 0 && points[i - ps] != null) { - intery = py + (points[i - ps + 1] - py) * (qx - px) / (points[i - ps] - px); - newpoints.push(qx); - newpoints.push(intery) - for (m = 2; m < ps; ++m) - newpoints.push(points[i + m]); - bottom = qy; - } - - j += otherps; - } - else { // px < qx - if (fromgap && withlines) { - // if we come from a gap, we just skip this point - i += ps; - continue; - } - - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - // we might be able to interpolate a point below, - // this can give us a better y - if (withlines && j > 0 && otherpoints[j - otherps] != null) - bottom = qy + (otherpoints[j - otherps + 1] - qy) * (px - qx) / (otherpoints[j - otherps] - qx); - - //newpoints[l + 1] += bottom; - - i += ps; - } - - fromgap = false; - - if (l != newpoints.length && withbottom) - newpoints[l + 2] = bottom; - } - - // maintain the line steps invariant - if (withsteps && l != newpoints.length && l > 0 - && newpoints[l] != null - && newpoints[l] != newpoints[l - ps] - && newpoints[l + 1] != newpoints[l - ps + 1]) { - for (m = 0; m < ps; ++m) - newpoints[l + ps + m] = newpoints[l + m]; - newpoints[l + 1] = newpoints[l - ps + 1]; - } - } - - datapoints.points = newpoints; - } - - plot.hooks.processDatapoints.push(computeFillBottoms); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'fillbetween', - version: '1.0' - }); -})(jQuery); diff --git a/web/flot/jquery.flot.pie.js b/web/flot/jquery.flot.pie.js deleted file mode 100644 index ef67c4b..0000000 --- a/web/flot/jquery.flot.pie.js +++ /dev/null @@ -1,751 +0,0 @@ -/* -Flot plugin for rendering pie charts. The plugin assumes the data is -coming is as a single data value for each series, and each of those -values is a positive value or zero (negative numbers don't make -any sense and will cause strange effects). The data values do -NOT need to be passed in as percentage values because it -internally calculates the total and percentages. - -* Created by Brian Medendorp, June 2009 -* Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars - -* Changes: - 2009-10-22: lineJoin set to round - 2009-10-23: IE full circle fix, donut - 2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera - 2009-11-17: Added IE hover capability submitted by Anthony Aragues - 2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well) - - -Available options are: -series: { - pie: { - show: true/false - radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' - innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect - startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result - tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) - offset: { - top: integer value to move the pie up or down - left: integer value to move the pie left or right, or 'auto' - }, - stroke: { - color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') - width: integer pixel width of the stroke - }, - label: { - show: true/false, or 'auto' - formatter: a user-defined function that modifies the text/style of the label text - radius: 0-1 for percentage of fullsize, or a specified pixel length - background: { - color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') - opacity: 0-1 - }, - threshold: 0-1 for the percentage value at which to hide labels (if they're too small) - }, - combine: { - threshold: 0-1 for the percentage value at which to combine slices (if they're too small) - color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined - label: any text value of what the combined slice should be labeled - } - highlight: { - opacity: 0-1 - } - } -} - -More detail and specific examples can be found in the included HTML file. - -*/ - -(function ($) -{ - function init(plot) // this is the "body" of the plugin - { - var canvas = null; - var target = null; - var maxRadius = null; - var centerLeft = null; - var centerTop = null; - var total = 0; - var redraw = true; - var redrawAttempts = 10; - var shrink = 0.95; - var legendWidth = 0; - var processed = false; - var raw = false; - - // interactive variables - var highlights = []; - - // add hook to determine if pie plugin in enabled, and then perform necessary operations - plot.hooks.processOptions.push(checkPieEnabled); - plot.hooks.bindEvents.push(bindEvents); - - // check to see if the pie plugin is enabled - function checkPieEnabled(plot, options) - { - if (options.series.pie.show) - { - //disable grid - options.grid.show = false; - - // set labels.show - if (options.series.pie.label.show=='auto') - if (options.legend.show) - options.series.pie.label.show = false; - else - options.series.pie.label.show = true; - - // set radius - if (options.series.pie.radius=='auto') - if (options.series.pie.label.show) - options.series.pie.radius = 3/4; - else - options.series.pie.radius = 1; - - // ensure sane tilt - if (options.series.pie.tilt>1) - options.series.pie.tilt=1; - if (options.series.pie.tilt<0) - options.series.pie.tilt=0; - - // add processData hook to do transformations on the data - plot.hooks.processDatapoints.push(processDatapoints); - plot.hooks.drawOverlay.push(drawOverlay); - - // add draw hook - plot.hooks.draw.push(draw); - } - } - - // bind hoverable events - function bindEvents(plot, eventHolder) - { - var options = plot.getOptions(); - - if (options.series.pie.show && options.grid.hoverable) - eventHolder.unbind('mousemove').mousemove(onMouseMove); - - if (options.series.pie.show && options.grid.clickable) - eventHolder.unbind('click').click(onClick); - } - - - // debugging function that prints out an object - function alertObject(obj) - { - var msg = ''; - function traverse(obj, depth) - { - if (!depth) - depth = 0; - for (var i = 0; i < obj.length; ++i) - { - for (var j=0; jcanvas.width-maxRadius) - centerLeft = canvas.width-maxRadius; - } - - function fixData(data) - { - for (var i = 0; i < data.length; ++i) - { - if (typeof(data[i].data)=='number') - data[i].data = [[1,data[i].data]]; - else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined') - { - if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined') - data[i].label = data[i].data.label; // fix weirdness coming from flot - data[i].data = [[1,0]]; - - } - } - return data; - } - - function combine(data) - { - data = fixData(data); - calcTotal(data); - var combined = 0; - var numCombined = 0; - var color = options.series.pie.combine.color; - - var newdata = []; - for (var i = 0; i < data.length; ++i) - { - // make sure its a number - data[i].data[0][1] = parseFloat(data[i].data[0][1]); - if (!data[i].data[0][1]) - data[i].data[0][1] = 0; - - if (data[i].data[0][1]/total<=options.series.pie.combine.threshold) - { - combined += data[i].data[0][1]; - numCombined++; - if (!color) - color = data[i].color; - } - else - { - newdata.push({ - data: [[1,data[i].data[0][1]]], - color: data[i].color, - label: data[i].label, - angle: (data[i].data[0][1]*(Math.PI*2))/total, - percent: (data[i].data[0][1]/total*100) - }); - } - } - if (numCombined>0) - newdata.push({ - data: [[1,combined]], - color: color, - label: options.series.pie.combine.label, - angle: (combined*(Math.PI*2))/total, - percent: (combined/total*100) - }); - return newdata; - } - - function draw(plot, newCtx) - { - if (!target) return; // if no series were passed - ctx = newCtx; - - setupPie(); - var slices = plot.getData(); - - var attempts = 0; - while (redraw && attempts0) - maxRadius *= shrink; - attempts += 1; - clear(); - if (options.series.pie.tilt<=0.8) - drawShadow(); - drawPie(); - } - if (attempts >= redrawAttempts) { - clear(); - target.prepend('
Could not draw pie with labels contained inside canvas
'); - } - - if ( plot.setSeries && plot.insertLegend ) - { - plot.setSeries(slices); - plot.insertLegend(); - } - - // we're actually done at this point, just defining internal functions at this point - - function clear() - { - ctx.clearRect(0,0,canvas.width,canvas.height); - target.children().filter('.pieLabel, .pieLabelBackground').remove(); - } - - function drawShadow() - { - var shadowLeft = 5; - var shadowTop = 15; - var edge = 10; - var alpha = 0.02; - - // set radius - if (options.series.pie.radius>1) - var radius = options.series.pie.radius; - else - var radius = maxRadius * options.series.pie.radius; - - if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge) - return; // shadow would be outside canvas, so don't draw it - - ctx.save(); - ctx.translate(shadowLeft,shadowTop); - ctx.globalAlpha = alpha; - ctx.fillStyle = '#000'; - - // center and rotate to starting position - ctx.translate(centerLeft,centerTop); - ctx.scale(1, options.series.pie.tilt); - - //radius -= edge; - for (var i=1; i<=edge; i++) - { - ctx.beginPath(); - ctx.arc(0,0,radius,0,Math.PI*2,false); - ctx.fill(); - radius -= i; - } - - ctx.restore(); - } - - function drawPie() - { - startAngle = Math.PI*options.series.pie.startAngle; - - // set radius - if (options.series.pie.radius>1) - var radius = options.series.pie.radius; - else - var radius = maxRadius * options.series.pie.radius; - - // center and rotate to starting position - ctx.save(); - ctx.translate(centerLeft,centerTop); - ctx.scale(1, options.series.pie.tilt); - //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera - - // draw slices - ctx.save(); - var currentAngle = startAngle; - for (var i = 0; i < slices.length; ++i) - { - slices[i].startAngle = currentAngle; - drawSlice(slices[i].angle, slices[i].color, true); - } - ctx.restore(); - - // draw slice outlines - ctx.save(); - ctx.lineWidth = options.series.pie.stroke.width; - currentAngle = startAngle; - for (var i = 0; i < slices.length; ++i) - drawSlice(slices[i].angle, options.series.pie.stroke.color, false); - ctx.restore(); - - // draw donut hole - drawDonutHole(ctx); - - // draw labels - if (options.series.pie.label.show) - drawLabels(); - - // restore to original state - ctx.restore(); - - function drawSlice(angle, color, fill) - { - if (angle <= 0 || isNaN(angle)) - return; - - if (fill) - ctx.fillStyle = color; - else - { - ctx.strokeStyle = color; - ctx.lineJoin = 'round'; - } - - ctx.beginPath(); - if (Math.abs(angle - Math.PI*2) > 0.000000001) - ctx.moveTo(0,0); // Center of the pie - else if ($.browser.msie) - angle -= 0.0001; - //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera - ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false); - ctx.closePath(); - //ctx.rotate(angle); // This doesn't work properly in Opera - currentAngle += angle; - - if (fill) - ctx.fill(); - else - ctx.stroke(); - } - - function drawLabels() - { - var currentAngle = startAngle; - - // set radius - if (options.series.pie.label.radius>1) - var radius = options.series.pie.label.radius; - else - var radius = maxRadius * options.series.pie.label.radius; - - for (var i = 0; i < slices.length; ++i) - { - if (slices[i].percent >= options.series.pie.label.threshold*100) - drawLabel(slices[i], currentAngle, i); - currentAngle += slices[i].angle; - } - - function drawLabel(slice, startAngle, index) - { - if (slice.data[0][1]==0) - return; - - // format label text - var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; - if (lf) - text = lf(slice.label, slice); - else - text = slice.label; - if (plf) - text = plf(text, slice); - - var halfAngle = ((startAngle+slice.angle) + startAngle)/2; - var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); - var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; - - var html = '' + text + ""; - target.append(html); - var label = target.children('#pieLabel'+index); - var labelTop = (y - label.height()/2); - var labelLeft = (x - label.width()/2); - label.css('top', labelTop); - label.css('left', labelLeft); - - // check to make sure that the label is not outside the canvas - if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0) - redraw = true; - - if (options.series.pie.label.background.opacity != 0) { - // put in the transparent background separately to avoid blended labels and label boxes - var c = options.series.pie.label.background.color; - if (c == null) { - c = slice.color; - } - var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;'; - $('
').insertBefore(label).css('opacity', options.series.pie.label.background.opacity); - } - } // end individual label function - } // end drawLabels function - } // end drawPie function - } // end draw function - - // Placed here because it needs to be accessed from multiple locations - function drawDonutHole(layer) - { - // draw donut hole - if(options.series.pie.innerRadius > 0) - { - // subtract the center - layer.save(); - innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; - layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color - layer.beginPath(); - layer.fillStyle = options.series.pie.stroke.color; - layer.arc(0,0,innerRadius,0,Math.PI*2,false); - layer.fill(); - layer.closePath(); - layer.restore(); - - // add inner stroke - layer.save(); - layer.beginPath(); - layer.strokeStyle = options.series.pie.stroke.color; - layer.arc(0,0,innerRadius,0,Math.PI*2,false); - layer.stroke(); - layer.closePath(); - layer.restore(); - // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. - } - } - - //-- Additional Interactive related functions -- - - function isPointInPoly(poly, pt) - { - for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) - ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) - && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) - && (c = !c); - return c; - } - - function findNearbySlice(mouseX, mouseY) - { - var slices = plot.getData(), - options = plot.getOptions(), - radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; - - for (var i = 0; i < slices.length; ++i) - { - var s = slices[i]; - - if(s.pie.show) - { - ctx.save(); - ctx.beginPath(); - ctx.moveTo(0,0); // Center of the pie - //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. - ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false); - ctx.closePath(); - x = mouseX-centerLeft; - y = mouseY-centerTop; - if(ctx.isPointInPath) - { - if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop)) - { - //alert('found slice!'); - ctx.restore(); - return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; - } - } - else - { - // excanvas for IE doesn;t support isPointInPath, this is a workaround. - p1X = (radius * Math.cos(s.startAngle)); - p1Y = (radius * Math.sin(s.startAngle)); - p2X = (radius * Math.cos(s.startAngle+(s.angle/4))); - p2Y = (radius * Math.sin(s.startAngle+(s.angle/4))); - p3X = (radius * Math.cos(s.startAngle+(s.angle/2))); - p3Y = (radius * Math.sin(s.startAngle+(s.angle/2))); - p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5))); - p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5))); - p5X = (radius * Math.cos(s.startAngle+s.angle)); - p5Y = (radius * Math.sin(s.startAngle+s.angle)); - arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]]; - arrPoint = [x,y]; - // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? - if(isPointInPoly(arrPoly, arrPoint)) - { - ctx.restore(); - return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; - } - } - ctx.restore(); - } - } - - return null; - } - - function onMouseMove(e) - { - triggerClickHoverEvent('plothover', e); - } - - function onClick(e) - { - triggerClickHoverEvent('plotclick', e); - } - - // trigger click or hover event (they send the same parameters so we share their code) - function triggerClickHoverEvent(eventname, e) - { - var offset = plot.offset(), - canvasX = parseInt(e.pageX - offset.left), - canvasY = parseInt(e.pageY - offset.top), - item = findNearbySlice(canvasX, canvasY); - - if (options.grid.autoHighlight) - { - // clear auto-highlights - for (var i = 0; i < highlights.length; ++i) - { - var h = highlights[i]; - if (h.auto == eventname && !(item && h.series == item.series)) - unhighlight(h.series); - } - } - - // highlight the slice - if (item) - highlight(item.series, eventname); - - // trigger any hover bind events - var pos = { pageX: e.pageX, pageY: e.pageY }; - target.trigger(eventname, [ pos, item ]); - } - - function highlight(s, auto) - { - if (typeof s == "number") - s = series[s]; - - var i = indexOfHighlight(s); - if (i == -1) - { - highlights.push({ series: s, auto: auto }); - plot.triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s) - { - if (s == null) - { - highlights = []; - plot.triggerRedrawOverlay(); - } - - if (typeof s == "number") - s = series[s]; - - var i = indexOfHighlight(s); - if (i != -1) - { - highlights.splice(i, 1); - plot.triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s) - { - for (var i = 0; i < highlights.length; ++i) - { - var h = highlights[i]; - if (h.series == s) - return i; - } - return -1; - } - - function drawOverlay(plot, octx) - { - //alert(options.series.pie.radius); - var options = plot.getOptions(); - //alert(options.series.pie.radius); - - var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; - - octx.save(); - octx.translate(centerLeft, centerTop); - octx.scale(1, options.series.pie.tilt); - - for (i = 0; i < highlights.length; ++i) - drawHighlight(highlights[i].series); - - drawDonutHole(octx); - - octx.restore(); - - function drawHighlight(series) - { - if (series.angle <= 0 || isNaN(series.angle)) - return; - - //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); - octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor - - octx.beginPath(); - if (Math.abs(series.angle - Math.PI*2) > 0.000000001) - octx.moveTo(0,0); // Center of the pie - octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false); - octx.closePath(); - octx.fill(); - } - - } - - } // end init (plugin body) - - // define pie specific options and their default values - var options = { - series: { - pie: { - show: false, - radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) - innerRadius:0, /* for donut */ - startAngle: 3/2, - tilt: 1, - offset: { - top: 0, - left: 'auto' - }, - stroke: { - color: '#FFF', - width: 1 - }, - label: { - show: 'auto', - formatter: function(label, slice){ - return '
'+label+'
'+Math.round(slice.percent)+'%
'; - }, // formatter function - radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) - background: { - color: null, - opacity: 0 - }, - threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow) - }, - combine: { - threshold: -1, // percentage at which to combine little slices into one larger slice - color: null, // color to give the new slice (auto-generated if null) - label: 'Other' // label to give the new slice - }, - highlight: { - //color: '#FFF', // will add this functionality once parseColor is available - opacity: 0.5 - } - } - } - }; - - $.plot.plugins.push({ - init: init, - options: options, - name: "pie", - version: "1.0" - }); -})(jQuery); diff --git a/web/flot/jquery.flot.resize.js b/web/flot/jquery.flot.resize.js deleted file mode 100644 index 69dfb24..0000000 --- a/web/flot/jquery.flot.resize.js +++ /dev/null @@ -1,60 +0,0 @@ -/* -Flot plugin for automatically redrawing plots when the placeholder -size changes, e.g. on window resizes. - -It works by listening for changes on the placeholder div (through the -jQuery resize event plugin) - if the size changes, it will redraw the -plot. - -There are no options. If you need to disable the plugin for some -plots, you can just fix the size of their placeholders. -*/ - - -/* Inline dependency: - * jQuery resize event - v1.1 - 3/14/2010 - * http://benalman.com/projects/jquery-resize-plugin/ - * - * Copyright (c) 2010 "Cowboy" Ben Alman - * Dual licensed under the MIT and GPL licenses. - * http://benalman.com/about/license/ - */ -(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); - - -(function ($) { - var options = { }; // no options - - function init(plot) { - function onResize() { - var placeholder = plot.getPlaceholder(); - - // somebody might have hidden us and we can't plot - // when we don't have the dimensions - if (placeholder.width() == 0 || placeholder.height() == 0) - return; - - plot.resize(); - plot.setupGrid(); - plot.draw(); - } - - function bindEvents(plot, eventHolder) { - plot.getPlaceholder().resize(onResize); - } - - function shutdown(plot, eventHolder) { - plot.getPlaceholder().unbind("resize", onResize); - } - - plot.hooks.bindEvents.push(bindEvents); - plot.hooks.shutdown.push(shutdown); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'resize', - version: '1.0' - }); -})(jQuery); diff --git a/web/flot/jquery.js b/web/flot/jquery.js deleted file mode 100644 index 78fcfa4..0000000 --- a/web/flot/jquery.js +++ /dev/null @@ -1,8316 +0,0 @@ -/*! - * jQuery JavaScript Library v1.5.1 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Wed Feb 23 13:55:29 2011 -0500 - */ -(function( window, undefined ) { - -// Use the correct document accordingly with window argument (sandbox) -var document = window.document; -var jQuery = (function() { - -// Define a local copy of jQuery -var jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // A central reference to the root jQuery(document) - rootjQuery, - - // A simple way to check for HTML strings or ID strings - // (both of which we optimize for) - quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, - - // Check if a string has a non-whitespace character in it - rnotwhite = /\S/, - - // Used for trimming whitespace - trimLeft = /^\s+/, - trimRight = /\s+$/, - - // Check for digits - rdigit = /\d/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, - rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, - - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // Has the ready events already been bound? - readyBound = false, - - // The deferred used on DOM ready - readyList, - - // Promise methods - promiseMethods = "then done fail isResolved isRejected promise".split( " " ), - - // The ready event handler - DOMContentLoaded, - - // Save a reference to some core methods - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - push = Array.prototype.push, - slice = Array.prototype.slice, - trim = String.prototype.trim, - indexOf = Array.prototype.indexOf, - - // [[Class]] -> type pairs - class2type = {}; - -jQuery.fn = jQuery.prototype = { - constructor: jQuery, - init: function( selector, context, rootjQuery ) { - var match, elem, ret, doc; - - // Handle $(""), $(null), or $(undefined) - if ( !selector ) { - return this; - } - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - } - - // The body element only exists once, optimize finding it - if ( selector === "body" && !context && document.body ) { - this.context = document; - this[0] = document.body; - this.selector = "body"; - this.length = 1; - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - doc = (context ? context.ownerDocument || context : document); - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - ret = rsingleTag.exec( selector ); - - if ( ret ) { - if ( jQuery.isPlainObject( context ) ) { - selector = [ document.createElement( ret[1] ) ]; - jQuery.fn.attr.call( selector, context, true ); - - } else { - selector = [ doc.createElement( ret[1] ) ]; - } - - } else { - ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; - } - - return jQuery.merge( this, selector ); - - // HANDLE: $("#id") - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return (context || rootjQuery).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if (selector.selector !== undefined) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.5.1", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - toArray: function() { - return slice.call( this, 0 ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = this.constructor(); - - if ( jQuery.isArray( elems ) ) { - push.apply( ret, elems ); - - } else { - jQuery.merge( ret, elems ); - } - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) { - ret.selector = this.selector + (this.selector ? " " : "") + selector; - } else if ( name ) { - ret.selector = this.selector + "." + name + "(" + selector + ")"; - } - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Attach the listeners - jQuery.bindReady(); - - // Add the callback - readyList.done( fn ); - - return this; - }, - - eq: function( i ) { - return i === -1 ? - this.slice( i ) : - this.slice( i, +i + 1 ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ), - "slice", slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - // A third-party is pushing the ready event forwards - if ( wait === true ) { - jQuery.readyWait--; - } - - // Make sure that the DOM is not already loaded - if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready, 1 ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).unbind( "ready" ); - } - } - }, - - bindReady: function() { - if ( readyBound ) { - return; - } - - readyBound = true; - - // Catch cases where $(document).ready() is called after the - // browser event has already occurred. - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - return setTimeout( jQuery.ready, 1 ); - } - - // Mozilla, Opera and webkit nightlies currently support this event - if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); - - // If IE event model is used - } else if ( document.attachEvent ) { - // ensure firing before onload, - // maybe late but safe also for iframes - document.attachEvent("onreadystatechange", DOMContentLoaded); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", jQuery.ready ); - - // If IE and not a frame - // continually check to see if the document is ready - var toplevel = false; - - try { - toplevel = window.frameElement == null; - } catch(e) {} - - if ( document.documentElement.doScroll && toplevel ) { - doScrollCheck(); - } - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type(obj) === "array"; - }, - - // A crude way of determining if an object is a window - isWindow: function( obj ) { - return obj && typeof obj === "object" && "setInterval" in obj; - }, - - isNaN: function( obj ) { - return obj == null || !rdigit.test( obj ) || isNaN( obj ); - }, - - type: function( obj ) { - return obj == null ? - String( obj ) : - class2type[ toString.call(obj) ] || "object"; - }, - - isPlainObject: function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for ( key in obj ) {} - - return key === undefined || hasOwn.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - for ( var name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw msg; - }, - - parseJSON: function( data ) { - if ( typeof data !== "string" || !data ) { - return null; - } - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test(data.replace(rvalidescape, "@") - .replace(rvalidtokens, "]") - .replace(rvalidbraces, "")) ) { - - // Try to use the native JSON parser first - return window.JSON && window.JSON.parse ? - window.JSON.parse( data ) : - (new Function("return " + data))(); - - } else { - jQuery.error( "Invalid JSON: " + data ); - } - }, - - // Cross-browser xml parsing - // (xml & tmp used internally) - parseXML: function( data , xml , tmp ) { - - if ( window.DOMParser ) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString( data , "text/xml" ); - } else { // IE - xml = new ActiveXObject( "Microsoft.XMLDOM" ); - xml.async = "false"; - xml.loadXML( data ); - } - - tmp = xml.documentElement; - - if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { - jQuery.error( "Invalid XML: " + data ); - } - - return xml; - }, - - noop: function() {}, - - // Evalulates a script in a global context - globalEval: function( data ) { - if ( data && rnotwhite.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement, - script = document.createElement( "script" ); - - if ( jQuery.support.scriptEval() ) { - script.appendChild( document.createTextNode( data ) ); - } else { - script.text = data; - } - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, - length = object.length, - isObj = length === undefined || jQuery.isFunction(object); - - if ( args ) { - if ( isObj ) { - for ( name in object ) { - if ( callback.apply( object[ name ], args ) === false ) { - break; - } - } - } else { - for ( ; i < length; ) { - if ( callback.apply( object[ i++ ], args ) === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isObj ) { - for ( name in object ) { - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { - break; - } - } - } else { - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} - } - } - - return object; - }, - - // Use native String.trim function wherever possible - trim: trim ? - function( text ) { - return text == null ? - "" : - trim.call( text ); - } : - - // Otherwise use our own trimming functionality - function( text ) { - return text == null ? - "" : - text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); - }, - - // results is for internal usage only - makeArray: function( array, results ) { - var ret = results || []; - - if ( array != null ) { - // The window, strings (and functions) also have 'length' - // The extra typeof function check is to prevent crashes - // in Safari 2 (See: #3039) - // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - var type = jQuery.type(array); - - if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { - push.call( ret, array ); - } else { - jQuery.merge( ret, array ); - } - } - - return ret; - }, - - inArray: function( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; - }, - - merge: function( first, second ) { - var i = first.length, - j = 0; - - if ( typeof second.length === "number" ) { - for ( var l = second.length; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var ret = [], retVal; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for ( var i = 0, length = elems.length; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var ret = [], value; - - // Go through the array, translating each of the items to their - // new value (or values). - for ( var i = 0, length = elems.length; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - // Flatten any nested arrays - return ret.concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - proxy: function( fn, proxy, thisObject ) { - if ( arguments.length === 2 ) { - if ( typeof proxy === "string" ) { - thisObject = fn; - fn = thisObject[ proxy ]; - proxy = undefined; - - } else if ( proxy && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; - } - } - - if ( !proxy && fn ) { - proxy = function() { - return fn.apply( thisObject || this, arguments ); - }; - } - - // Set the guid of unique handler to the same of original handler, so it can be removed - if ( fn ) { - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - } - - // So proxy can be declared as an argument - return proxy; - }, - - // Mutifunctional method to get and set values to a collection - // The value/s can be optionally by executed if its a function - access: function( elems, key, value, exec, fn, pass ) { - var length = elems.length; - - // Setting many attributes - if ( typeof key === "object" ) { - for ( var k in key ) { - jQuery.access( elems, k, key[k], exec, fn, value ); - } - return elems; - } - - // Setting one attribute - if ( value !== undefined ) { - // Optionally, function values get executed if exec is true - exec = !pass && exec && jQuery.isFunction(value); - - for ( var i = 0; i < length; i++ ) { - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); - } - - return elems; - } - - // Getting an attribute - return length ? fn( elems[0], key ) : undefined; - }, - - now: function() { - return (new Date()).getTime(); - }, - - // Create a simple deferred (one callbacks list) - _Deferred: function() { - var // callbacks list - callbacks = [], - // stored [ context , args ] - fired, - // to avoid firing when already doing so - firing, - // flag to know if the deferred has been cancelled - cancelled, - // the deferred itself - deferred = { - - // done( f1, f2, ...) - done: function() { - if ( !cancelled ) { - var args = arguments, - i, - length, - elem, - type, - _fired; - if ( fired ) { - _fired = fired; - fired = 0; - } - for ( i = 0, length = args.length; i < length; i++ ) { - elem = args[ i ]; - type = jQuery.type( elem ); - if ( type === "array" ) { - deferred.done.apply( deferred, elem ); - } else if ( type === "function" ) { - callbacks.push( elem ); - } - } - if ( _fired ) { - deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); - } - } - return this; - }, - - // resolve with given context and args - resolveWith: function( context, args ) { - if ( !cancelled && !fired && !firing ) { - firing = 1; - try { - while( callbacks[ 0 ] ) { - callbacks.shift().apply( context, args ); - } - } - // We have to add a catch block for - // IE prior to 8 or else the finally - // block will never get executed - catch (e) { - throw e; - } - finally { - fired = [ context, args ]; - firing = 0; - } - } - return this; - }, - - // resolve with this as context and given arguments - resolve: function() { - deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments ); - return this; - }, - - // Has this deferred been resolved? - isResolved: function() { - return !!( firing || fired ); - }, - - // Cancel - cancel: function() { - cancelled = 1; - callbacks = []; - return this; - } - }; - - return deferred; - }, - - // Full fledged deferred (two callbacks list) - Deferred: function( func ) { - var deferred = jQuery._Deferred(), - failDeferred = jQuery._Deferred(), - promise; - // Add errorDeferred methods, then and promise - jQuery.extend( deferred, { - then: function( doneCallbacks, failCallbacks ) { - deferred.done( doneCallbacks ).fail( failCallbacks ); - return this; - }, - fail: failDeferred.done, - rejectWith: failDeferred.resolveWith, - reject: failDeferred.resolve, - isRejected: failDeferred.isResolved, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - if ( obj == null ) { - if ( promise ) { - return promise; - } - promise = obj = {}; - } - var i = promiseMethods.length; - while( i-- ) { - obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; - } - return obj; - } - } ); - // Make sure only one callback list will be used - deferred.done( failDeferred.cancel ).fail( deferred.cancel ); - // Unexpose cancel - delete deferred.cancel; - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - return deferred; - }, - - // Deferred helper - when: function( object ) { - var lastIndex = arguments.length, - deferred = lastIndex <= 1 && object && jQuery.isFunction( object.promise ) ? - object : - jQuery.Deferred(), - promise = deferred.promise(); - - if ( lastIndex > 1 ) { - var array = slice.call( arguments, 0 ), - count = lastIndex, - iCallback = function( index ) { - return function( value ) { - array[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; - if ( !( --count ) ) { - deferred.resolveWith( promise, array ); - } - }; - }; - while( ( lastIndex-- ) ) { - object = array[ lastIndex ]; - if ( object && jQuery.isFunction( object.promise ) ) { - object.promise().then( iCallback(lastIndex), deferred.reject ); - } else { - --count; - } - } - if ( !count ) { - deferred.resolveWith( promise, array ); - } - } else if ( deferred !== object ) { - deferred.resolve( object ); - } - return promise; - }, - - // Use of jQuery.browser is frowned upon. - // More details: http://docs.jquery.com/Utilities/jQuery.browser - uaMatch: function( ua ) { - ua = ua.toLowerCase(); - - var match = rwebkit.exec( ua ) || - ropera.exec( ua ) || - rmsie.exec( ua ) || - ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || - []; - - return { browser: match[1] || "", version: match[2] || "0" }; - }, - - sub: function() { - function jQuerySubclass( selector, context ) { - return new jQuerySubclass.fn.init( selector, context ); - } - jQuery.extend( true, jQuerySubclass, this ); - jQuerySubclass.superclass = this; - jQuerySubclass.fn = jQuerySubclass.prototype = this(); - jQuerySubclass.fn.constructor = jQuerySubclass; - jQuerySubclass.subclass = this.subclass; - jQuerySubclass.fn.init = function init( selector, context ) { - if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) { - context = jQuerySubclass(context); - } - - return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); - }; - jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; - var rootjQuerySubclass = jQuerySubclass(document); - return jQuerySubclass; - }, - - browser: {} -}); - -// Create readyList deferred -readyList = jQuery._Deferred(); - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -browserMatch = jQuery.uaMatch( userAgent ); -if ( browserMatch.browser ) { - jQuery.browser[ browserMatch.browser ] = true; - jQuery.browser.version = browserMatch.version; -} - -// Deprecated, use jQuery.browser.webkit instead -if ( jQuery.browser.webkit ) { - jQuery.browser.safari = true; -} - -if ( indexOf ) { - jQuery.inArray = function( elem, array ) { - return indexOf.call( array, elem ); - }; -} - -// IE doesn't match non-breaking spaces with \s -if ( rnotwhite.test( "\xA0" ) ) { - trimLeft = /^[\s\xA0]+/; - trimRight = /[\s\xA0]+$/; -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); - -// Cleanup functions for the document ready method -if ( document.addEventListener ) { - DOMContentLoaded = function() { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - jQuery.ready(); - }; - -} else if ( document.attachEvent ) { - DOMContentLoaded = function() { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( document.readyState === "complete" ) { - document.detachEvent( "onreadystatechange", DOMContentLoaded ); - jQuery.ready(); - } - }; -} - -// The DOM ready check for Internet Explorer -function doScrollCheck() { - if ( jQuery.isReady ) { - return; - } - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch(e) { - setTimeout( doScrollCheck, 1 ); - return; - } - - // and execute any waiting functions - jQuery.ready(); -} - -// Expose jQuery to the global object -return jQuery; - -})(); - - -(function() { - - jQuery.support = {}; - - var div = document.createElement("div"); - - div.style.display = "none"; - div.innerHTML = "
a"; - - var all = div.getElementsByTagName("*"), - a = div.getElementsByTagName("a")[0], - select = document.createElement("select"), - opt = select.appendChild( document.createElement("option") ), - input = div.getElementsByTagName("input")[0]; - - // Can't get basic test support - if ( !all || !all.length || !a ) { - return; - } - - jQuery.support = { - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: div.firstChild.nodeType === 3, - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, - - // Get the style information from getAttribute - // (IE uses .cssText insted) - style: /red/.test( a.getAttribute("style") ), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: a.getAttribute("href") === "/a", - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55$/.test( a.style.opacity ), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Make sure that if no value is specified for a checkbox - // that it defaults to "on". - // (WebKit defaults to "" instead) - checkOn: input.value === "on", - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, - - // Will be defined later - deleteExpando: true, - optDisabled: false, - checkClone: false, - noCloneEvent: true, - noCloneChecked: true, - boxModel: null, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableHiddenOffsets: true - }; - - input.checked = true; - jQuery.support.noCloneChecked = input.cloneNode( true ).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as diabled) - select.disabled = true; - jQuery.support.optDisabled = !opt.disabled; - - var _scriptEval = null; - jQuery.support.scriptEval = function() { - if ( _scriptEval === null ) { - var root = document.documentElement, - script = document.createElement("script"), - id = "script" + jQuery.now(); - - try { - script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); - } catch(e) {} - - root.insertBefore( script, root.firstChild ); - - // Make sure that the execution of code works by injecting a script - // tag with appendChild/createTextNode - // (IE doesn't support this, fails, and uses .text instead) - if ( window[ id ] ) { - _scriptEval = true; - delete window[ id ]; - } else { - _scriptEval = false; - } - - root.removeChild( script ); - // release memory in IE - root = script = id = null; - } - - return _scriptEval; - }; - - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer - try { - delete div.test; - - } catch(e) { - jQuery.support.deleteExpando = false; - } - - if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent("onclick", function click() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - jQuery.support.noCloneEvent = false; - div.detachEvent("onclick", click); - }); - div.cloneNode(true).fireEvent("onclick"); - } - - div = document.createElement("div"); - div.innerHTML = ""; - - var fragment = document.createDocumentFragment(); - fragment.appendChild( div.firstChild ); - - // WebKit doesn't clone checked state correctly in fragments - jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; - - // Figure out if the W3C box model works as expected - // document.body must exist before we can do this - jQuery(function() { - var div = document.createElement("div"), - body = document.getElementsByTagName("body")[0]; - - // Frameset documents with no body should not run this code - if ( !body ) { - return; - } - - div.style.width = div.style.paddingLeft = "1px"; - body.appendChild( div ); - jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; - - if ( "zoom" in div.style ) { - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - // (IE < 8 does this) - div.style.display = "inline"; - div.style.zoom = 1; - jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; - - // Check if elements with layout shrink-wrap their children - // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "
"; - jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; - } - - div.innerHTML = "
t
"; - var tds = div.getElementsByTagName("td"); - - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) - jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; - - tds[0].style.display = ""; - tds[1].style.display = "none"; - - // Check if empty table cells still have offsetWidth/Height - // (IE < 8 fail this test) - jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; - div.innerHTML = ""; - - body.removeChild( div ).style.display = "none"; - div = tds = null; - }); - - // Technique from Juriy Zaytsev - // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ - var eventSupported = function( eventName ) { - var el = document.createElement("div"); - eventName = "on" + eventName; - - // We only care about the case where non-standard event systems - // are used, namely in IE. Short-circuiting here helps us to - // avoid an eval call (in setAttribute) which can cause CSP - // to go haywire. See: https://developer.mozilla.org/en/Security/CSP - if ( !el.attachEvent ) { - return true; - } - - var isSupported = (eventName in el); - if ( !isSupported ) { - el.setAttribute(eventName, "return;"); - isSupported = typeof el[eventName] === "function"; - } - el = null; - - return isSupported; - }; - - jQuery.support.submitBubbles = eventSupported("submit"); - jQuery.support.changeBubbles = eventSupported("change"); - - // release memory in IE - div = all = a = null; -})(); - - - -var rbrace = /^(?:\{.*\}|\[.*\])$/; - -jQuery.extend({ - cache: {}, - - // Please use with caution - uuid: 0, - - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ jQuery.expando ] = id = ++jQuery.uuid; - } else { - id = jQuery.expando; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery - // metadata on plain JS objects when the object is serialized using - // JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); - } else { - cache[ id ] = jQuery.extend(cache[ id ], name); - } - } - - thisCache = cache[ id ]; - - // Internal jQuery data is stored in a separate object inside the object's data - // cache in order to avoid key collisions between internal data and user-defined - // data - if ( pvt ) { - if ( !thisCache[ internalKey ] ) { - thisCache[ internalKey ] = {}; - } - - thisCache = thisCache[ internalKey ]; - } - - if ( data !== undefined ) { - thisCache[ name ] = data; - } - - // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should - // not attempt to inspect the internal events object using jQuery.data, as this - // internal data object is undocumented and subject to change. - if ( name === "events" && !thisCache[name] ) { - return thisCache[ internalKey ] && thisCache[ internalKey ].events; - } - - return getByName ? thisCache[ name ] : thisCache; - }, - - removeData: function( elem, name, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var internalKey = jQuery.expando, isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - - // See jQuery.data for more information - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; - - if ( thisCache ) { - delete thisCache[ name ]; - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !isEmptyDataObject(thisCache) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( pvt ) { - delete cache[ id ][ internalKey ]; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject(cache[ id ]) ) { - return; - } - } - - var internalCache = cache[ id ][ internalKey ]; - - // Browsers that fail expando deletion also refuse to delete expandos on - // the window, but it will allow it on all other JS objects; other browsers - // don't care - if ( jQuery.support.deleteExpando || cache != window ) { - delete cache[ id ]; - } else { - cache[ id ] = null; - } - - // We destroyed the entire user cache at once because it's faster than - // iterating through each key, but we need to continue to persist internal - // data if it existed - if ( internalCache ) { - cache[ id ] = {}; - // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery - // metadata on plain JS objects when the object is serialized using - // JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - - cache[ id ][ internalKey ] = internalCache; - - // Otherwise, we need to eliminate the expando on the node to avoid - // false lookups in the cache for entries that no longer exist - } else if ( isNode ) { - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( jQuery.support.deleteExpando ) { - delete elem[ jQuery.expando ]; - } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); - } else { - elem[ jQuery.expando ] = null; - } - } - }, - - // For internal use only. - _data: function( elem, name, data ) { - return jQuery.data( elem, name, data, true ); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - if ( elem.nodeName ) { - var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; - - if ( match ) { - return !(match === true || elem.getAttribute("classid") !== match); - } - } - - return true; - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var data = null; - - if ( typeof key === "undefined" ) { - if ( this.length ) { - data = jQuery.data( this[0] ); - - if ( this[0].nodeType === 1 ) { - var attr = this[0].attributes, name; - for ( var i = 0, l = attr.length; i < l; i++ ) { - name = attr[i].name; - - if ( name.indexOf( "data-" ) === 0 ) { - name = name.substr( 5 ); - dataAttr( this[0], name, data[ name ] ); - } - } - } - } - - return data; - - } else if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value === undefined ) { - data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - // Try to fetch any internally stored data first - if ( data === undefined && this.length ) { - data = jQuery.data( this[0], key ); - data = dataAttr( this[0], key, data ); - } - - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; - - } else { - return this.each(function() { - var $this = jQuery( this ), - args = [ parts[0], value ]; - - $this.triggerHandler( "setData" + parts[1] + "!", args ); - jQuery.data( this, key, value ); - $this.triggerHandler( "changeData" + parts[1] + "!", args ); - }); - } - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - data = elem.getAttribute( "data-" + key ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - !jQuery.isNaN( data ) ? parseFloat( data ) : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON -// property to be considered empty objects; this property always exists in -// order to make sure JSON.stringify does not expose internal metadata -function isEmptyDataObject( obj ) { - for ( var name in obj ) { - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} - - - - -jQuery.extend({ - queue: function( elem, type, data ) { - if ( !elem ) { - return; - } - - type = (type || "fx") + "queue"; - var q = jQuery._data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( !data ) { - return q || []; - } - - if ( !q || jQuery.isArray(data) ) { - q = jQuery._data( elem, type, jQuery.makeArray(data) ); - - } else { - q.push( data ); - } - - return q; - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - fn = queue.shift(); - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - } - - if ( fn ) { - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift("inprogress"); - } - - fn.call(elem, function() { - jQuery.dequeue(elem, type); - }); - } - - if ( !queue.length ) { - jQuery.removeData( elem, type + "queue", true ); - } - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - } - - if ( data === undefined ) { - return jQuery.queue( this[0], type ); - } - return this.each(function( i ) { - var queue = jQuery.queue( this, type, data ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; - type = type || "fx"; - - return this.queue( type, function() { - var elem = this; - setTimeout(function() { - jQuery.dequeue( elem, type ); - }, time ); - }); - }, - - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - } -}); - - - - -var rclass = /[\n\t\r]/g, - rspaces = /\s+/, - rreturn = /\r/g, - rspecialurl = /^(?:href|src|style)$/, - rtype = /^(?:button|input)$/i, - rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea)?$/i, - rradiocheck = /^(?:radio|checkbox)$/i; - -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, name, value, true, jQuery.attr ); - }, - - removeAttr: function( name, fn ) { - return this.each(function(){ - jQuery.attr( this, name, "" ); - if ( this.nodeType === 1 ) { - this.removeAttribute( name ); - } - }); - }, - - addClass: function( value ) { - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - self.addClass( value.call(this, i, self.attr("class")) ); - }); - } - - if ( value && typeof value === "string" ) { - var classNames = (value || "").split( rspaces ); - - for ( var i = 0, l = this.length; i < l; i++ ) { - var elem = this[i]; - - if ( elem.nodeType === 1 ) { - if ( !elem.className ) { - elem.className = value; - - } else { - var className = " " + elem.className + " ", - setClass = elem.className; - - for ( var c = 0, cl = classNames.length; c < cl; c++ ) { - if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { - setClass += " " + classNames[c]; - } - } - elem.className = jQuery.trim( setClass ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - self.removeClass( value.call(this, i, self.attr("class")) ); - }); - } - - if ( (value && typeof value === "string") || value === undefined ) { - var classNames = (value || "").split( rspaces ); - - for ( var i = 0, l = this.length; i < l; i++ ) { - var elem = this[i]; - - if ( elem.nodeType === 1 && elem.className ) { - if ( value ) { - var className = (" " + elem.className + " ").replace(rclass, " "); - for ( var c = 0, cl = classNames.length; c < cl; c++ ) { - className = className.replace(" " + classNames[c] + " ", " "); - } - elem.className = jQuery.trim( className ); - - } else { - elem.className = ""; - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isBool = typeof stateVal === "boolean"; - - if ( jQuery.isFunction( value ) ) { - return this.each(function(i) { - var self = jQuery(this); - self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, - i = 0, - self = jQuery( this ), - state = stateVal, - classNames = value.split( rspaces ); - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space seperated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); - } - - } else if ( type === "undefined" || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery._data( this, "__className__", this.className ); - } - - // toggle whole className - this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " "; - for ( var i = 0, l = this.length; i < l; i++ ) { - if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - if ( !arguments.length ) { - var elem = this[0]; - - if ( elem ) { - if ( jQuery.nodeName( elem, "option" ) ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { - - // Get the specific value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - // Fixes Bug #2551 -- select.val() broken in IE after form.reset() - if ( one && !values.length && options.length ) { - return jQuery( options[ index ] ).val(); - } - - return values; - } - - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { - return elem.getAttribute("value") === null ? "on" : elem.value; - } - - // Everything else, we just grab the value - return (elem.value || "").replace(rreturn, ""); - - } - - return undefined; - } - - var isFunction = jQuery.isFunction(value); - - return this.each(function(i) { - var self = jQuery(this), val = value; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call(this, i, self.val()); - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray(val) ) { - val = jQuery.map(val, function (value) { - return value == null ? "" : value + ""; - }); - } - - if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { - this.checked = jQuery.inArray( self.val(), val ) >= 0; - - } else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(val); - - jQuery( "option", this ).each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - this.selectedIndex = -1; - } - - } else { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, - - attr: function( elem, name, value, pass ) { - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) { - return undefined; - } - - if ( pass && name in jQuery.attrFn ) { - return jQuery(elem)[name](value); - } - - var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), - // Whether we are setting (or getting) - set = value !== undefined; - - // Try to normalize/fix the name - name = notxml && jQuery.props[ name ] || name; - - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); - - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } - - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } - - } else { - elem[ name ] = value; - } - } - - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; - } - - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); - - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - - return elem[ name ]; - } - - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } - - return elem.style.cssText; - } - - if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); - } - - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; - } - - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; - } - // Handle everything which isn't a DOM element node - if ( set ) { - elem[ name ] = value; - } - return elem[ name ]; - } -}); - - - - -var rnamespaces = /\.(.*)$/, - rformElems = /^(?:textarea|input|select)$/i, - rperiod = /\./g, - rspace = / /g, - rescape = /[^\w\s.|`]/g, - fcleanup = function( nm ) { - return nm.replace(rescape, "\\$&"); - }; - -/* - * A number of helper functions used for managing events. - * Many of the ideas behind this code originated from - * Dean Edwards' addEvent library. - */ -jQuery.event = { - - // Bind an event to an element - // Original by Dean Edwards - add: function( elem, types, handler, data ) { - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // TODO :: Use a try/catch until it's safe to pull this out (likely 1.6) - // Minor release fix for bug #8018 - try { - // For whatever reason, IE has trouble passing the window object - // around, causing it to be cloned in the process - if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { - elem = window; - } - } - catch ( e ) {} - - if ( handler === false ) { - handler = returnFalse; - } else if ( !handler ) { - // Fixes bug #7229. Fix recommended by jdalton - return; - } - - var handleObjIn, handleObj; - - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - } - - // Make sure that the function being executed has a unique ID - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure - var elemData = jQuery._data( elem ); - - // If no elemData is found then we must be trying to bind to one of the - // banned noData elements - if ( !elemData ) { - return; - } - - var events = elemData.events, - eventHandle = elemData.handle; - - if ( !events ) { - elemData.events = events = {}; - } - - if ( !eventHandle ) { - elemData.handle = eventHandle = function() { - // Handle the second event of a trigger and when - // an event is called after a page has unloaded - return typeof jQuery !== "undefined" && !jQuery.event.triggered ? - jQuery.event.handle.apply( eventHandle.elem, arguments ) : - undefined; - }; - } - - // Add elem as a property of the handle function - // This is to prevent a memory leak with non-native events in IE. - eventHandle.elem = elem; - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = types.split(" "); - - var type, i = 0, namespaces; - - while ( (type = types[ i++ ]) ) { - handleObj = handleObjIn ? - jQuery.extend({}, handleObjIn) : - { handler: handler, data: data }; - - // Namespaced event handlers - if ( type.indexOf(".") > -1 ) { - namespaces = type.split("."); - type = namespaces.shift(); - handleObj.namespace = namespaces.slice(0).sort().join("."); - - } else { - namespaces = []; - handleObj.namespace = ""; - } - - handleObj.type = type; - if ( !handleObj.guid ) { - handleObj.guid = handler.guid; - } - - // Get the current list of functions bound to this event - var handlers = events[ type ], - special = jQuery.event.special[ type ] || {}; - - // Init the event handler queue - if ( !handlers ) { - handlers = events[ type ] = []; - - // Check for a special event handler - // Only use addEventListener/attachEvent if the special - // events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add the function to the element's handler list - handlers.push( handleObj ); - - // Keep track of which events have been used, for global triggering - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - global: {}, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, pos ) { - // don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - if ( handler === false ) { - handler = returnFalse; - } - - var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ), - events = elemData && elemData.events; - - if ( !elemData || !events ) { - return; - } - - // types is actually an event object here - if ( types && types.type ) { - handler = types.handler; - types = types.type; - } - - // Unbind all events for the element - if ( !types || typeof types === "string" && types.charAt(0) === "." ) { - types = types || ""; - - for ( type in events ) { - jQuery.event.remove( elem, type + types ); - } - - return; - } - - // Handle multiple events separated by a space - // jQuery(...).unbind("mouseover mouseout", fn); - types = types.split(" "); - - while ( (type = types[ i++ ]) ) { - origType = type; - handleObj = null; - all = type.indexOf(".") < 0; - namespaces = []; - - if ( !all ) { - // Namespaced event handlers - namespaces = type.split("."); - type = namespaces.shift(); - - namespace = new RegExp("(^|\\.)" + - jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - eventType = events[ type ]; - - if ( !eventType ) { - continue; - } - - if ( !handler ) { - for ( j = 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( all || namespace.test( handleObj.namespace ) ) { - jQuery.event.remove( elem, origType, handleObj.handler, j ); - eventType.splice( j--, 1 ); - } - } - - continue; - } - - special = jQuery.event.special[ type ] || {}; - - for ( j = pos || 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( handler.guid === handleObj.guid ) { - // remove the given handler for the given type - if ( all || namespace.test( handleObj.namespace ) ) { - if ( pos == null ) { - eventType.splice( j--, 1 ); - } - - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - - if ( pos != null ) { - break; - } - } - } - - // remove generic event handler if no more handlers exist - if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - ret = null; - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - var handle = elemData.handle; - if ( handle ) { - handle.elem = null; - } - - delete elemData.events; - delete elemData.handle; - - if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem, undefined, true ); - } - } - }, - - // bubbling is internal - trigger: function( event, data, elem /*, bubbling */ ) { - // Event object or event type - var type = event.type || event, - bubbling = arguments[3]; - - if ( !bubbling ) { - event = typeof event === "object" ? - // jQuery.Event object - event[ jQuery.expando ] ? event : - // Object literal - jQuery.extend( jQuery.Event(type), event ) : - // Just the event type (string) - jQuery.Event(type); - - if ( type.indexOf("!") >= 0 ) { - event.type = type = type.slice(0, -1); - event.exclusive = true; - } - - // Handle a global trigger - if ( !elem ) { - // Don't bubble custom events when global (to avoid too much overhead) - event.stopPropagation(); - - // Only trigger if we've ever bound an event for it - if ( jQuery.event.global[ type ] ) { - // XXX This code smells terrible. event.js should not be directly - // inspecting the data cache - jQuery.each( jQuery.cache, function() { - // internalKey variable is just used to make it easier to find - // and potentially change this stuff later; currently it just - // points to jQuery.expando - var internalKey = jQuery.expando, - internalCache = this[ internalKey ]; - if ( internalCache && internalCache.events && internalCache.events[ type ] ) { - jQuery.event.trigger( event, data, internalCache.handle.elem ); - } - }); - } - } - - // Handle triggering a single element - - // don't do events on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { - return undefined; - } - - // Clean up in case it is reused - event.result = undefined; - event.target = elem; - - // Clone the incoming data, if any - data = jQuery.makeArray( data ); - data.unshift( event ); - } - - event.currentTarget = elem; - - // Trigger the event, it is assumed that "handle" is a function - var handle = jQuery._data( elem, "handle" ); - - if ( handle ) { - handle.apply( elem, data ); - } - - var parent = elem.parentNode || elem.ownerDocument; - - // Trigger an inline bound script - try { - if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { - if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { - event.result = false; - event.preventDefault(); - } - } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (inlineError) {} - - if ( !event.isPropagationStopped() && parent ) { - jQuery.event.trigger( event, data, parent, true ); - - } else if ( !event.isDefaultPrevented() ) { - var old, - target = event.target, - targetType = type.replace( rnamespaces, "" ), - isClick = jQuery.nodeName( target, "a" ) && targetType === "click", - special = jQuery.event.special[ targetType ] || {}; - - if ( (!special._default || special._default.call( elem, event ) === false) && - !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { - - try { - if ( target[ targetType ] ) { - // Make sure that we don't accidentally re-trigger the onFOO events - old = target[ "on" + targetType ]; - - if ( old ) { - target[ "on" + targetType ] = null; - } - - jQuery.event.triggered = true; - target[ targetType ](); - } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (triggerError) {} - - if ( old ) { - target[ "on" + targetType ] = old; - } - - jQuery.event.triggered = false; - } - } - }, - - handle: function( event ) { - var all, handlers, namespaces, namespace_re, events, - namespace_sort = [], - args = jQuery.makeArray( arguments ); - - event = args[0] = jQuery.event.fix( event || window.event ); - event.currentTarget = this; - - // Namespaced event handlers - all = event.type.indexOf(".") < 0 && !event.exclusive; - - if ( !all ) { - namespaces = event.type.split("."); - event.type = namespaces.shift(); - namespace_sort = namespaces.slice(0).sort(); - namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.namespace = event.namespace || namespace_sort.join("."); - - events = jQuery._data(this, "events"); - - handlers = (events || {})[ event.type ]; - - if ( events && handlers ) { - // Clone the handlers to prevent manipulation - handlers = handlers.slice(0); - - for ( var j = 0, l = handlers.length; j < l; j++ ) { - var handleObj = handlers[ j ]; - - // Filter the functions by class - if ( all || namespace_re.test( handleObj.namespace ) ) { - // Pass in a reference to the handler function itself - // So that we can later remove it - event.handler = handleObj.handler; - event.data = handleObj.data; - event.handleObj = handleObj; - - var ret = handleObj.handler.apply( this, args ); - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - - if ( event.isImmediatePropagationStopped() ) { - break; - } - } - } - } - - return event.result; - }, - - props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // store a copy of the original event object - // and "clone" to set read-only properties - var originalEvent = event; - event = jQuery.Event( originalEvent ); - - for ( var i = this.props.length, prop; i; ) { - prop = this.props[ --i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Fix target property, if necessary - if ( !event.target ) { - // Fixes #1925 where srcElement might not be defined either - event.target = event.srcElement || document; - } - - // check if target is a textnode (safari) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) { - event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; - } - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, - body = document.body; - - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add which for key events - if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { - event.which = event.charCode != null ? event.charCode : event.keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) { - event.metaKey = event.ctrlKey; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button !== undefined ) { - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); - } - - return event; - }, - - // Deprecated, use jQuery.guid instead - guid: 1E8, - - // Deprecated, use jQuery.proxy instead - proxy: jQuery.proxy, - - special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady, - teardown: jQuery.noop - }, - - live: { - add: function( handleObj ) { - jQuery.event.add( this, - liveConvert( handleObj.origType, handleObj.selector ), - jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); - }, - - remove: function( handleObj ) { - jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); - } - }, - - beforeunload: { - setup: function( data, namespaces, eventHandle ) { - // We only want to do this special case on windows - if ( jQuery.isWindow( this ) ) { - this.onbeforeunload = eventHandle; - } - }, - - teardown: function( namespaces, eventHandle ) { - if ( this.onbeforeunload === eventHandle ) { - this.onbeforeunload = null; - } - } - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - if ( elem.detachEvent ) { - elem.detachEvent( "on" + type, handle ); - } - }; - -jQuery.Event = function( src ) { - // Allow instantiation without the 'new' keyword - if ( !this.preventDefault ) { - return new jQuery.Event( src ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // timeStamp is buggy for some events on Firefox(#3843) - // So we won't rely on the native value - this.timeStamp = jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -function returnFalse() { - return false; -} -function returnTrue() { - return true; -} - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - preventDefault: function() { - this.isDefaultPrevented = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - - // if preventDefault exists run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // otherwise set the returnValue property of the original event to false (IE) - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - this.isPropagationStopped = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - // if stopPropagation exists run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - // otherwise set the cancelBubble property of the original event to true (IE) - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse -}; - -// Checks if an event happened on an element within another element -// Used in jQuery.event.special.mouseenter and mouseleave handlers -var withinElement = function( event ) { - // Check if mouse(over|out) are still within the same parent element - var parent = event.relatedTarget; - - // Firefox sometimes assigns relatedTarget a XUL element - // which we cannot access the parentNode property of - try { - - // Chrome does something similar, the parentNode property - // can be accessed but is null. - if ( parent !== document && !parent.parentNode ) { - return; - } - // Traverse up the tree - while ( parent && parent !== this ) { - parent = parent.parentNode; - } - - if ( parent !== this ) { - // set the correct event type - event.type = event.data; - - // handle event if we actually just moused on to a non sub-element - jQuery.event.handle.apply( this, arguments ); - } - - // assuming we've left the element since we most likely mousedover a xul element - } catch(e) { } -}, - -// In case of event delegation, we only need to rename the event.type, -// liveHandler will take care of the rest. -delegate = function( event ) { - event.type = event.data; - jQuery.event.handle.apply( this, arguments ); -}; - -// Create mouseenter and mouseleave events -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - setup: function( data ) { - jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); - }, - teardown: function( data ) { - jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); - } - }; -}); - -// submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function( data, namespaces ) { - if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) { - jQuery.event.add(this, "click.specialSubmit", function( e ) { - var elem = e.target, - type = elem.type; - - if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { - trigger( "submit", this, arguments ); - } - }); - - jQuery.event.add(this, "keypress.specialSubmit", function( e ) { - var elem = e.target, - type = elem.type; - - if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { - trigger( "submit", this, arguments ); - } - }); - - } else { - return false; - } - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialSubmit" ); - } - }; - -} - -// change delegation, happens here so we have bind. -if ( !jQuery.support.changeBubbles ) { - - var changeFilters, - - getVal = function( elem ) { - var type = elem.type, val = elem.value; - - if ( type === "radio" || type === "checkbox" ) { - val = elem.checked; - - } else if ( type === "select-multiple" ) { - val = elem.selectedIndex > -1 ? - jQuery.map( elem.options, function( elem ) { - return elem.selected; - }).join("-") : - ""; - - } else if ( elem.nodeName.toLowerCase() === "select" ) { - val = elem.selectedIndex; - } - - return val; - }, - - testChange = function testChange( e ) { - var elem = e.target, data, val; - - if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { - return; - } - - data = jQuery._data( elem, "_change_data" ); - val = getVal(elem); - - // the current data will be also retrieved by beforeactivate - if ( e.type !== "focusout" || elem.type !== "radio" ) { - jQuery._data( elem, "_change_data", val ); - } - - if ( data === undefined || val === data ) { - return; - } - - if ( data != null || val ) { - e.type = "change"; - e.liveFired = undefined; - jQuery.event.trigger( e, arguments[1], elem ); - } - }; - - jQuery.event.special.change = { - filters: { - focusout: testChange, - - beforedeactivate: testChange, - - click: function( e ) { - var elem = e.target, type = elem.type; - - if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { - testChange.call( this, e ); - } - }, - - // Change has to be called before submit - // Keydown will be called before keypress, which is used in submit-event delegation - keydown: function( e ) { - var elem = e.target, type = elem.type; - - if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || - (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || - type === "select-multiple" ) { - testChange.call( this, e ); - } - }, - - // Beforeactivate happens also before the previous element is blurred - // with this event you can't trigger a change event, but you can store - // information - beforeactivate: function( e ) { - var elem = e.target; - jQuery._data( elem, "_change_data", getVal(elem) ); - } - }, - - setup: function( data, namespaces ) { - if ( this.type === "file" ) { - return false; - } - - for ( var type in changeFilters ) { - jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); - } - - return rformElems.test( this.nodeName ); - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialChange" ); - - return rformElems.test( this.nodeName ); - } - }; - - changeFilters = jQuery.event.special.change.filters; - - // Handle when the input is .focus()'d - changeFilters.focus = changeFilters.beforeactivate; -} - -function trigger( type, elem, args ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - // Don't pass args or remember liveFired; they apply to the donor event. - var event = jQuery.extend( {}, args[ 0 ] ); - event.type = type; - event.originalEvent = {}; - event.liveFired = undefined; - jQuery.event.handle.call( elem, event ); - if ( event.isDefaultPrevented() ) { - args[ 0 ].preventDefault(); - } -} - -// Create "bubbling" focus and blur events -if ( document.addEventListener ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - jQuery.event.special[ fix ] = { - setup: function() { - this.addEventListener( orig, handler, true ); - }, - teardown: function() { - this.removeEventListener( orig, handler, true ); - } - }; - - function handler( e ) { - e = jQuery.event.fix( e ); - e.type = fix; - return jQuery.event.handle.call( this, e ); - } - }); -} - -jQuery.each(["bind", "one"], function( i, name ) { - jQuery.fn[ name ] = function( type, data, fn ) { - // Handle object literals - if ( typeof type === "object" ) { - for ( var key in type ) { - this[ name ](key, data, type[key], fn); - } - return this; - } - - if ( jQuery.isFunction( data ) || data === false ) { - fn = data; - data = undefined; - } - - var handler = name === "one" ? jQuery.proxy( fn, function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }) : fn; - - if ( type === "unload" && name !== "one" ) { - this.one( type, data, fn ); - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.add( this[i], type, handler, data ); - } - } - - return this; - }; -}); - -jQuery.fn.extend({ - unbind: function( type, fn ) { - // Handle object literals - if ( typeof type === "object" && !type.preventDefault ) { - for ( var key in type ) { - this.unbind(key, type[key]); - } - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.remove( this[i], type, fn ); - } - } - - return this; - }, - - delegate: function( selector, types, data, fn ) { - return this.live( types, data, fn, selector ); - }, - - undelegate: function( selector, types, fn ) { - if ( arguments.length === 0 ) { - return this.unbind( "live" ); - - } else { - return this.die( types, null, fn, selector ); - } - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - - triggerHandler: function( type, data ) { - if ( this[0] ) { - var event = jQuery.Event( type ); - event.preventDefault(); - event.stopPropagation(); - jQuery.event.trigger( event, data, this[0] ); - return event.result; - } - }, - - toggle: function( fn ) { - // Save reference to arguments for access in closure - var args = arguments, - i = 1; - - // link all the functions, so any of them can unbind this click handler - while ( i < args.length ) { - jQuery.proxy( fn, args[ i++ ] ); - } - - return this.click( jQuery.proxy( fn, function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - })); - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -}); - -var liveMap = { - focus: "focusin", - blur: "focusout", - mouseenter: "mouseover", - mouseleave: "mouseout" -}; - -jQuery.each(["live", "die"], function( i, name ) { - jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { - var type, i = 0, match, namespaces, preType, - selector = origSelector || this.selector, - context = origSelector ? this : jQuery( this.context ); - - if ( typeof types === "object" && !types.preventDefault ) { - for ( var key in types ) { - context[ name ]( key, data, types[key], selector ); - } - - return this; - } - - if ( jQuery.isFunction( data ) ) { - fn = data; - data = undefined; - } - - types = (types || "").split(" "); - - while ( (type = types[ i++ ]) != null ) { - match = rnamespaces.exec( type ); - namespaces = ""; - - if ( match ) { - namespaces = match[0]; - type = type.replace( rnamespaces, "" ); - } - - if ( type === "hover" ) { - types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); - continue; - } - - preType = type; - - if ( type === "focus" || type === "blur" ) { - types.push( liveMap[ type ] + namespaces ); - type = type + namespaces; - - } else { - type = (liveMap[ type ] || type) + namespaces; - } - - if ( name === "live" ) { - // bind live handler - for ( var j = 0, l = context.length; j < l; j++ ) { - jQuery.event.add( context[j], "live." + liveConvert( type, selector ), - { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); - } - - } else { - // unbind live handler - context.unbind( "live." + liveConvert( type, selector ), fn ); - } - } - - return this; - }; -}); - -function liveHandler( event ) { - var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, - elems = [], - selectors = [], - events = jQuery._data( this, "events" ); - - // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) - if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { - return; - } - - if ( event.namespace ) { - namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.liveFired = this; - - var live = events.live.slice(0); - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { - selectors.push( handleObj.selector ); - - } else { - live.splice( j--, 1 ); - } - } - - match = jQuery( event.target ).closest( selectors, event.currentTarget ); - - for ( i = 0, l = match.length; i < l; i++ ) { - close = match[i]; - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { - elem = close.elem; - related = null; - - // Those two events require additional checking - if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { - event.type = handleObj.preType; - related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; - } - - if ( !related || related !== elem ) { - elems.push({ elem: elem, handleObj: handleObj, level: close.level }); - } - } - } - } - - for ( i = 0, l = elems.length; i < l; i++ ) { - match = elems[i]; - - if ( maxLevel && match.level > maxLevel ) { - break; - } - - event.currentTarget = match.elem; - event.data = match.handleObj.data; - event.handleObj = match.handleObj; - - ret = match.handleObj.origHandler.apply( match.elem, arguments ); - - if ( ret === false || event.isPropagationStopped() ) { - maxLevel = match.level; - - if ( ret === false ) { - stop = false; - } - if ( event.isImmediatePropagationStopped() ) { - break; - } - } - } - - return stop; -} - -function liveConvert( type, selector ) { - return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); -} - -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - if ( fn == null ) { - fn = data; - data = null; - } - - return arguments.length > 0 ? - this.bind( name, data, fn ) : - this.trigger( name ); - }; - - if ( jQuery.attrFn ) { - jQuery.attrFn[ name ] = true; - } -}); - - -/*! - * Sizzle CSS Selector Engine - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true, - rBackslash = /\\/g, - rNonWord = /\W/; - -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function() { - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function( selector, context, results, seed ) { - results = results || []; - context = context || document; - - var origContext = context; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var m, set, checkSet, extra, ret, cur, pop, i, - prune = true, - contextXML = Sizzle.isXML( context ), - parts = [], - soFar = selector; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec( "" ); - m = chunker.exec( soFar ); - - if ( m ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - } while ( m ); - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); - } - - set = posProcess( selector, set ); - } - } - - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - - ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? - Sizzle.filter( ret.expr, ret.set )[0] : - ret.set[0]; - } - - if ( context ) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - - set = ret.expr ? - Sizzle.filter( ret.expr, ret.set ) : - ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray( set ); - - } else { - prune = false; - } - - while ( parts.length ) { - cur = parts.pop(); - pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - - } else if ( context && context.nodeType === 1 ) { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - - } else { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function( results ) { - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[ i - 1 ] ) { - results.splice( i--, 1 ); - } - } - } - } - - return results; -}; - -Sizzle.matches = function( expr, set ) { - return Sizzle( expr, null, null, set ); -}; - -Sizzle.matchesSelector = function( node, expr ) { - return Sizzle( expr, null, null, [node] ).length > 0; -}; - -Sizzle.find = function( expr, context, isXML ) { - var set; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var match, - type = Expr.order[i]; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice( 1, 1 ); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace( rBackslash, "" ); - set = Expr.find[ type ]( match, context, isXML ); - - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( "*" ) : - []; - } - - return { set: set, expr: expr }; -}; - -Sizzle.filter = function( expr, set, inplace, not ) { - var match, anyFound, - old = expr, - result = [], - curLoop = set, - isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var found, item, - filter = Expr.filter[ type ], - left = match[1]; - - anyFound = false; - - match.splice(1,1); - - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - - if ( curLoop === result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - - } else { - curLoop[i] = false; - } - - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -Sizzle.error = function( msg ) { - throw "Syntax error, unrecognized expression: " + msg; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - - leftMatch: {}, - - attrMap: { - "class": "className", - "for": "htmlFor" - }, - - attrHandle: { - href: function( elem ) { - return elem.getAttribute( "href" ); - }, - type: function( elem ) { - return elem.getAttribute( "type" ); - } - }, - - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !rNonWord.test( part ), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag ) { - part = part.toLowerCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - - ">": function( checkSet, part ) { - var elem, - isPartStr = typeof part === "string", - i = 0, - l = checkSet.length; - - if ( isPartStr && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - - } else { - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - - "": function(checkSet, part, isXML){ - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); - }, - - "~": function( checkSet, part, isXML ) { - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); - } - }, - - find: { - ID: function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }, - - NAME: function( match, context ) { - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], - results = context.getElementsByName( match[1] ); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - - TAG: function( match, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( match[1] ); - } - } - }, - preFilter: { - CLASS: function( match, curLoop, inplace, result, not, isXML ) { - match = " " + match[1].replace( rBackslash, "" ) + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - - ID: function( match ) { - return match[1].replace( rBackslash, "" ); - }, - - TAG: function( match, curLoop ) { - return match[1].replace( rBackslash, "" ).toLowerCase(); - }, - - CHILD: function( match ) { - if ( match[1] === "nth" ) { - if ( !match[2] ) { - Sizzle.error( match[0] ); - } - - match[2] = match[2].replace(/^\+|\s*/g, ''); - - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - else if ( match[2] ) { - Sizzle.error( match[0] ); - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - - ATTR: function( match, curLoop, inplace, result, not, isXML ) { - var name = match[1] = match[1].replace( rBackslash, "" ); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - // Handle if an un-quoted value was used - match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - - PSEUDO: function( match, curLoop, inplace, result, not ) { - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - - if ( !inplace ) { - result.push.apply( result, ret ); - } - - return false; - } - - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - - POS: function( match ) { - match.unshift( true ); - - return match; - } - }, - - filters: { - enabled: function( elem ) { - return elem.disabled === false && elem.type !== "hidden"; - }, - - disabled: function( elem ) { - return elem.disabled === true; - }, - - checked: function( elem ) { - return elem.checked === true; - }, - - selected: function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - parent: function( elem ) { - return !!elem.firstChild; - }, - - empty: function( elem ) { - return !elem.firstChild; - }, - - has: function( elem, i, match ) { - return !!Sizzle( match[3], elem ).length; - }, - - header: function( elem ) { - return (/h\d/i).test( elem.nodeName ); - }, - - text: function( elem ) { - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return "text" === elem.getAttribute( 'type' ); - }, - radio: function( elem ) { - return "radio" === elem.type; - }, - - checkbox: function( elem ) { - return "checkbox" === elem.type; - }, - - file: function( elem ) { - return "file" === elem.type; - }, - password: function( elem ) { - return "password" === elem.type; - }, - - submit: function( elem ) { - return "submit" === elem.type; - }, - - image: function( elem ) { - return "image" === elem.type; - }, - - reset: function( elem ) { - return "reset" === elem.type; - }, - - button: function( elem ) { - return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; - }, - - input: function( elem ) { - return (/input|select|textarea|button/i).test( elem.nodeName ); - } - }, - setFilters: { - first: function( elem, i ) { - return i === 0; - }, - - last: function( elem, i, match, array ) { - return i === array.length - 1; - }, - - even: function( elem, i ) { - return i % 2 === 0; - }, - - odd: function( elem, i ) { - return i % 2 === 1; - }, - - lt: function( elem, i, match ) { - return i < match[3] - 0; - }, - - gt: function( elem, i, match ) { - return i > match[3] - 0; - }, - - nth: function( elem, i, match ) { - return match[3] - 0 === i; - }, - - eq: function( elem, i, match ) { - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function( elem, match, i, array ) { - var name = match[1], - filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; - - } else if ( name === "not" ) { - var not = match[3]; - - for ( var j = 0, l = not.length; j < l; j++ ) { - if ( not[j] === elem ) { - return false; - } - } - - return true; - - } else { - Sizzle.error( name ); - } - }, - - CHILD: function( elem, match ) { - var type = match[1], - node = elem; - - switch ( type ) { - case "only": - case "first": - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - if ( type === "first" ) { - return true; - } - - node = elem; - - case "last": - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - return true; - - case "nth": - var first = match[2], - last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - - if ( first === 0 ) { - return diff === 0; - - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } - } - }, - - ID: function( elem, match ) { - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - - TAG: function( elem, match ) { - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; - }, - - CLASS: function( elem, match ) { - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - - ATTR: function( elem, match ) { - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - - POS: function( elem, match, i, array ) { - var name = match[2], - filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS, - fescape = function(all, num){ - return "\\" + (num - 0 + 1); - }; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); -} - -var makeArray = function( array, results ) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - -// Provide a fallback method if it does not work -} catch( e ) { - makeArray = function( array, results ) { - var i = 0, - ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - - } else { - if ( typeof array.length === "number" ) { - for ( var l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - - } else { - for ( ; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder, siblingCheck; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - return a.compareDocumentPosition ? -1 : 1; - } - - return a.compareDocumentPosition(b) & 4 ? -1 : 1; - }; - -} else { - sortOrder = function( a, b ) { - var al, bl, - ap = [], - bp = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - - // If the nodes are siblings (or identical) we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - - // If no parents were found then the nodes are disconnected - } else if ( !aup ) { - return -1; - - } else if ( !bup ) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while ( cur ) { - ap.unshift( cur ); - cur = cur.parentNode; - } - - cur = bup; - - while ( cur ) { - bp.unshift( cur ); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for ( var i = 0; i < al && i < bl; i++ ) { - if ( ap[i] !== bp[i] ) { - return siblingCheck( ap[i], bp[i] ); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck( a, bp[i], -1 ) : - siblingCheck( ap[i], b, 1 ); - }; - - siblingCheck = function( a, b, ret ) { - if ( a === b ) { - return ret; - } - - var cur = a.nextSibling; - - while ( cur ) { - if ( cur === b ) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; - }; -} - -// Utility function for retreiving the text value of an array of DOM nodes -Sizzle.getText = function( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += Sizzle.getText( elem.childNodes ); - } - } - - return ret; -}; - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(), - root = document.documentElement; - - form.innerHTML = ""; - - // Inject it into the root element, check its status, and remove it quickly - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - - return m ? - m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? - [m] : - undefined : - []; - } - }; - - Expr.filter.ID = function( elem, match ) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - - // release memory in IE - root = form = null; -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function( match, context ) { - var results = context.getElementsByTagName( match[1] ); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = ""; - - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - - Expr.attrHandle.href = function( elem ) { - return elem.getAttribute( "href", 2 ); - }; - } - - // release memory in IE - div = null; -})(); - -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, - div = document.createElement("div"), - id = "__sizzle__"; - - div.innerHTML = "

"; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function( query, context, extra, seed ) { - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && !Sizzle.isXML(context) ) { - // See if we find a selector to speed up - var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); - - if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { - // Speed-up: Sizzle("TAG") - if ( match[1] ) { - return makeArray( context.getElementsByTagName( query ), extra ); - - // Speed-up: Sizzle(".CLASS") - } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { - return makeArray( context.getElementsByClassName( match[2] ), extra ); - } - } - - if ( context.nodeType === 9 ) { - // Speed-up: Sizzle("body") - // The body element only exists once, optimize finding it - if ( query === "body" && context.body ) { - return makeArray( [ context.body ], extra ); - - // Speed-up: Sizzle("#ID") - } else if ( match && match[3] ) { - var elem = context.getElementById( match[3] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id === match[3] ) { - return makeArray( [ elem ], extra ); - } - - } else { - return makeArray( [], extra ); - } - } - - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(qsaError) {} - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var oldContext = context, - old = context.getAttribute( "id" ), - nid = old || id, - hasParent = context.parentNode, - relativeHierarchySelector = /^\s*[+~]/.test( query ); - - if ( !old ) { - context.setAttribute( "id", nid ); - } else { - nid = nid.replace( /'/g, "\\$&" ); - } - if ( relativeHierarchySelector && hasParent ) { - context = context.parentNode; - } - - try { - if ( !relativeHierarchySelector || hasParent ) { - return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); - } - - } catch(pseudoError) { - } finally { - if ( !old ) { - oldContext.removeAttribute( "id" ); - } - } - } - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - // release memory in IE - div = null; - })(); -} - -(function(){ - var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, - pseudoWorks = false; - - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( document.documentElement, "[test!='']:sizzle" ); - - } catch( pseudoError ) { - pseudoWorks = true; - } - - if ( matches ) { - Sizzle.matchesSelector = function( node, expr ) { - // Make sure that attribute selectors are quoted - expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); - - if ( !Sizzle.isXML( node ) ) { - try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { - return matches.call( node, expr ); - } - } catch(e) {} - } - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } -})(); - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "
"; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function( match, context, isXML ) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - // release memory in IE - div = null; -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -if ( document.documentElement.contains ) { - Sizzle.contains = function( a, b ) { - return a !== b && (a.contains ? a.contains(b) : true); - }; - -} else if ( document.documentElement.compareDocumentPosition ) { - Sizzle.contains = function( a, b ) { - return !!(a.compareDocumentPosition(b) & 16); - }; - -} else { - Sizzle.contains = function() { - return false; - }; -} - -Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -var posProcess = function( selector, context ) { - var match, - tmpSet = [], - later = "", - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.filters; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})(); - - -var runtil = /Until$/, - rparentsprev = /^(?:parents|prevUntil|prevAll)/, - // Note: This RegExp should be improved, or likely pulled from Sizzle - rmultiselector = /,/, - isSimple = /^.[^:#\[\.,]*$/, - slice = Array.prototype.slice, - POS = jQuery.expr.match.POS, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend({ - find: function( selector ) { - var ret = this.pushStack( "", "find", selector ), - length = 0; - - for ( var i = 0, l = this.length; i < l; i++ ) { - length = ret.length; - jQuery.find( selector, this[i], ret ); - - if ( i > 0 ) { - // Make sure that the results are unique - for ( var n = length; n < ret.length; n++ ) { - for ( var r = 0; r < length; r++ ) { - if ( ret[r] === ret[n] ) { - ret.splice(n--, 1); - break; - } - } - } - } - } - - return ret; - }, - - has: function( target ) { - var targets = jQuery( target ); - return this.filter(function() { - for ( var i = 0, l = targets.length; i < l; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector, false), "not", selector); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector, true), "filter", selector ); - }, - - is: function( selector ) { - return !!selector && jQuery.filter( selector, this ).length > 0; - }, - - closest: function( selectors, context ) { - var ret = [], i, l, cur = this[0]; - - if ( jQuery.isArray( selectors ) ) { - var match, selector, - matches = {}, - level = 1; - - if ( cur && selectors.length ) { - for ( i = 0, l = selectors.length; i < l; i++ ) { - selector = selectors[i]; - - if ( !matches[selector] ) { - matches[selector] = jQuery.expr.match.POS.test( selector ) ? - jQuery( selector, context || this.context ) : - selector; - } - } - - while ( cur && cur.ownerDocument && cur !== context ) { - for ( selector in matches ) { - match = matches[selector]; - - if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { - ret.push({ selector: selector, elem: cur, level: level }); - } - } - - cur = cur.parentNode; - level++; - } - } - - return ret; - } - - var pos = POS.test( selectors ) ? - jQuery( selectors, context || this.context ) : null; - - for ( i = 0, l = this.length; i < l; i++ ) { - cur = this[i]; - - while ( cur ) { - if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { - ret.push( cur ); - break; - - } else { - cur = cur.parentNode; - if ( !cur || !cur.ownerDocument || cur === context ) { - break; - } - } - } - } - - ret = ret.length > 1 ? jQuery.unique(ret) : ret; - - return this.pushStack( ret, "closest", selectors ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - if ( !elem || typeof elem === "string" ) { - return jQuery.inArray( this[0], - // If it receives a string, the selector is used - // If it receives nothing, the siblings are used - elem ? jQuery( elem ) : this.parent().children() ); - } - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context ) : - jQuery.makeArray( selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? - all : - jQuery.unique( all ) ); - }, - - andSelf: function() { - return this.add( this.prevObject ); - } -}); - -// A painfully simple check to see if an element is disconnected -// from a document (should be improved, where feasible). -function isDisconnected( node ) { - return !node || !node.parentNode || node.parentNode.nodeType === 11; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return jQuery.nth( elem, 2, "nextSibling" ); - }, - prev: function( elem ) { - return jQuery.nth( elem, 2, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( elem.parentNode.firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.makeArray( elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ), - // The variable 'args' was introduced in - // https://github.com/jquery/jquery/commit/52a0238 - // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. - // http://code.google.com/p/v8/issues/detail?id=1050 - args = slice.call(arguments); - - if ( !runtil.test( name ) ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; - - if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - - return this.pushStack( ret, name, args.join(",") ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : - jQuery.find.matches(expr, elems); - }, - - dir: function( elem, dir, until ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - nth: function( cur, result, dir, elem ) { - result = result || 1; - var num = 0; - - for ( ; cur; cur = cur[dir] ) { - if ( cur.nodeType === 1 && ++num === result ) { - break; - } - } - - return cur; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; - }); - - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem, i ) { - return (elem === qualifier) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } - } - - return jQuery.grep(elements, function( elem, i ) { - return (jQuery.inArray( elem, qualifier ) >= 0) === keep; - }); -} - - - - -var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rtagName = /<([\w:]+)/, - rtbody = /", "" ], - legend: [ 1, "
", "
" ], - thead: [ 1, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - col: [ 2, "", "
" ], - area: [ 1, "", "" ], - _default: [ 0, "", "" ] - }; - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// IE can't serialize and - - - - - - - -
- -
-
-
- - -
- -
- - -
- - - - -
- -
- -
- - -
-
- - -
-
-
-
-
-
-
-
-
- - - - - -
-
-
- - -
- - -
-
- - -
- -
-
- - -
-
-
-
- - - - - diff --git a/web/regstatus.php b/web/regstatus.php deleted file mode 100644 index ff3c36b..0000000 --- a/web/regstatus.php +++ /dev/null @@ -1,224 +0,0 @@ - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License at -for more details. -*/ - -if(isset($_POST)){ - ob_start(); //Redirect output to internal buffer - require_once 'monitormate3cfg.php'; - ob_end_clean(); - date_default_timezone_set($timezone); - - if($_POST["token"] == $token) { - - if(!(date('i',time())%$reg_interval)){ - - - if(isset($_POST["datetime"])){ - $date_time = date('Y-m-d G:i',$_POST["datetime"]); //Date from remote host - }else{ - $date_time = date('Y-m-d G:i',time()); //Date from localhost - } - - $flexnet_available = 0; - $fmmx_total = array( - "total_daily_kwh" => 0, - "total_daily_ah" => 0, - ); - - $summary = array( - "date" => date('Y-m-d ', strtotime($date_time)), - "kwh_in" => 0, - "kwh_out" => 0, - "ah_in" => 0, - "ah_out" => 0, - "max_temp" => 0, - "min_temp" => 0, - "min_soc" => 0, - "max_soc" => 0, - "max_pv_voltage" => 0, - ); - - $devices_array = json_decode($_POST["devices"],True); - - foreach($devices_array as $i){ - switch ($i["device_id"]) { - case '4': //Flexnet device id - $flexnet_available = $i; - register_flexnet($i, $date_time); - break; - case '3'://FM/MX device id - $fmmx_total["total_daily_kwh"] = $fmmx_total["total_daily_kwh"] + $i["daily_kwh"]; - $fmmx_total["total_daily_ah"] = $fmmx_total["total_daily_ah"] + $i["daily_ah"]; - register_fmmx($i, $date_time); - break; - case '2'://FX device id - register_fxinv($i, $date_time); - - break; - - default: - break; - } - } - - - - - if ($flexnet_available != 0){ - $summary["kwh_in"] = $flexnet_available["today_net_input_kwh"]; - $summary["kwh_out"] = $flexnet_available["today_net_output_kwh"]; - $summary["ah_in"] = $flexnet_available["today_net_input_ah"]; - $summary["ah_out"] = $flexnet_available["today_net_output_ah"]; - $summary["min_soc"] = $flexnet_available['today_min_soc']; - }else{ - $summary["kwh_in"] = $fmmx_total["total_daily_kwh"]; - $summary["ah_in"] = $fmmx_total["total_daily_ah"]; - } - - register_summary($summary); - $file = "matelog"; - $data = $_POST["devices"]; - file_put_contents($file, $data); - - }else { - $file = "matelog"; - $data = $_POST["devices"]; - file_put_contents($file, $data); - } -} -} - - - - function register_flexnet($device_array,$date_time){ - - - $query = "INSERT INTO monitormate3_flexnet (date, address , device_id , shunt_a_amps , shunt_b_amps , shunt_c_amps , accumulated_ah_shunt_a , - accumulated_kwh_shunt_a , accumulated_ah_shunt_b , accumulated_kwh_shunt_b , accumulated_ah_shunt_c , accumulated_kwh_shunt_c , days_since_full , - today_min_soc , today_net_input_ah , today_net_output_ah , today_net_input_kwh , today_net_output_kwh , charge_factor_corrected_net_batt_ah , - charge_factor_corrected_net_batt_kwh , charge_params_met , relay_mode , relay_status , battery_volt , soc , shunt_enabled_a , shunt_enabled_b , - shunt_enabled_c , battery_temp) VALUES ('".$date_time."','".$device_array['address']."','".$device_array['device_id']."','".$device_array['shunt_a_amps']."','".$device_array['shunt_b_amps']."','". - $device_array['shunt_c_amps']."','".$device_array['accumulated_ah_shunt_a']."','".$device_array['accumulated_kwh_shunt_a']."','".$device_array['accumulated_ah_shunt_b']."','". - $device_array['accumulated_kwh_shunt_b']."','".$device_array['accumulated_ah_shunt_c']."','".$device_array['accumulated_kwh_shunt_c']."','".$device_array['days_since_full']."','". - $device_array['today_min_soc']."','".$device_array['today_net_input_ah']."','".$device_array['today_net_output_ah']."','".$device_array['today_net_input_kwh']."','" - .$device_array['today_net_output_kwh']."','".$device_array['charge_factor_corrected_net_batt_ah']."','".$device_array['charge_factor_corrected_net_batt_kwh']. - "','".$device_array['charge_params_met']."','".$device_array['relay_mode']."','".$device_array['relay_status']."','".$device_array['battery_volt']."','".$device_array['soc']. - "','".$device_array['shunt_enabled_a']."','".$device_array['shunt_enabled_b']."','".$device_array['shunt_enabled_c']."','".$device_array['battery_temp']."')"; - - - $connection = db_connection(); - mysql_query($query,$connection); -} - - - -function register_fmmx($device_array,$date_time){ - $query = "INSERT INTO monitormate3_fmmx (date, address , device_id , charge_current , pv_current , pv_voltage , - daily_kwh , aux_mode , error_modes , charge_mode , battery_volts , daily_ah ) - VALUES ('".$date_time."','".$device_array['address']."','".$device_array['device_id']."','".$device_array['charge_current']."','" - .$device_array['pv_current']."','".$device_array['pv_voltage']."','".$device_array['daily_kwh']."','".$device_array['aux_mode']."','" - .implode(",", $device_array['error_modes'])."','".$device_array['charge_mode']."','".$device_array['battery_volts']."','".$device_array['daily_ah']."')"; - - $connection = db_connection(); - mysql_query($query,$connection); - -} - - - - -function register_fxinv($device_array,$date_time){ - $query = "INSERT INTO monitormate3_fxinv (date, address,device_id, inverter_current, charge_current,buy_current,ac_input_voltage,ac_output_voltage, - sell_current, operational_mode, error_modes,ac_mode,battery_volt, misc, warning_modes ) - VALUES ('".$date_time."','".$device_array['address']."','".$device_array['device_id']."','".$device_array['inverter_current']."','" - .$device_array['charge_current']."','".$device_array['buy_current']."','".$device_array['ac_input_voltage']."','".$device_array['ac_output_voltage']."','".$device_array['sell_current']."','" - .$device_array['operational_mode']."','".implode(",", $device_array['error_modes'])."','".$device_array['ac_mode']."','".$device_array['battery_volt'] - ."','".$device_array['misc']."','".implode(",", $device_array['warning_modes'])."')"; - $connection = db_connection(); - mysql_query($query,$connection); - -} - -function register_summary($summary){ - - $connection = db_connection(); - - $max_tempq = mysql_query("select max(battery_temp) from monitormate3_flexnet where date(date) ='".$summary['date']."'",$connection); - $max_temp = mysql_fetch_row($max_tempq); - if($max_temp != NULL){ - $summary['max_temp'] = $max_temp[0]; - } - - - - $min_tempq = mysql_query("select min(battery_temp) from monitormate3_flexnet where date(date) ='".$summary['date']."'",$connection); - $min_temp = mysql_fetch_row($min_tempq); - if($min_temp != NULL){ - $summary['min_temp'] = $min_temp[0]; - } - - $max_pv_voltageq = mysql_query("select max(pv_voltage) from monitormate3_fmmx where date(date) ='".$summary['date']."'",$connection); - $max_pv_voltage = mysql_fetch_row($max_pv_voltageq); - if($max_pv_voltage != NULL){ - $summary['max_pv_voltage'] = $max_pv_voltage[0]; - } - - $max_socq = mysql_query("select max(soc) from monitormate3_flexnet where date(date) ='".$summary['date']."'",$connection); - $max_soc = mysql_fetch_row($max_socq); - if($max_soc != NULL){ - $summary['max_soc'] = $max_soc[0]; - } - - $not_first_record = mysql_query("select date from monitormate3_summary where date(date) ='".$summary['date']."'",$connection); - $not_first_record = mysql_fetch_row($not_first_record); - - - - $query = "INSERT INTO monitormate3_summary(date, kwh_in, kwh_out, ah_in, ah_out, max_temp, min_temp, max_soc, min_soc, max_pv_voltage) VALUES ('" - .$summary['date']."','".$summary['kwh_in']."','".$summary['kwh_out']."','".$summary['ah_in']."','" - .$summary['ah_out']."','".$summary['max_temp']."','".$summary['min_temp']."','".$summary['max_soc']."','" - .$summary['min_soc']."','".$summary['max_pv_voltage']."')"; - - - $update_query = "UPDATE monitormate3_summary SET kwh_in=".$summary['kwh_in'].", kwh_out=".$summary['kwh_out'].", - ah_in=".$summary['ah_in'].", ah_out=".$summary['ah_out'].", max_temp=".$summary['max_temp'].", min_temp=".$summary['min_temp']. - ", max_soc=".$summary['max_soc'].", min_soc=".$summary['min_soc'].", max_pv_voltage=".$summary['max_pv_voltage']." where date(date)='".$summary['date']."'"; - - $file22 = "matelog22"; - file_put_contents($file22, $update_query); - - if($not_first_record){ - mysql_query($update_query,$connection); - }else{ - mysql_query($query,$connection); - } - } - - - - - -function db_connection(){ - global $dbpass; - global $dbuser; - global $dbname; - global $dbhost; - $connection = mysql_connect($dbhost, $dbuser, $dbpass); - mysql_select_db($dbname, $connection); - return $connection; -} - - -?> -