From f994797896e767e489b88d3e8644cd95f62d848b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?William=20Beltr=C3=A1n?=
Date: Fri, 4 May 2012 12:23:03 -0500
Subject: [PATCH 001/195] [FIX]: Bug #942894 This fix the format.js file to
make openerp show a count of ids instead the list of them in 2om
relationship.
lp bug: https://launchpad.net/bugs/942894 fixed
bzr revid: wbeltran@infostudio.com.ec-20120504172303-r4pynkj4vofeptk5
---
addons/web/static/src/js/formats.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index 640ba4d5a93..834a73fac2a 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -134,6 +134,10 @@ instance.web.format_value = function (value, descriptor, value_if_empty) {
case 'many2one':
// name_get value format
return value[1];
+ case 'one2many':
+ // this is to show count of ids related in this o2m relation instead the list of them for example will show this:(5) instead of this 1,2,5,8,76
+ count_ids='('+value.length.toString()+')'
+ return count_ids;
case 'datetime':
if (typeof(value) == "string")
value = instance.web.auto_str_to_date(value);
From 1e041011fd76d6318f2e6ed1e90fcb3dbec3ada7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?William=20Beltr=C3=A1n?=
Date: Fri, 4 May 2012 14:30:27 -0500
Subject: [PATCH 002/195] Changes accordig to quality code.
bzr revid: wbeltran@infostudio.com.ec-20120504193027-0u94nwfbbitoa2zc
---
addons/web/static/src/js/formats.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index 834a73fac2a..5bb5e461fce 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -136,7 +136,7 @@ instance.web.format_value = function (value, descriptor, value_if_empty) {
return value[1];
case 'one2many':
// this is to show count of ids related in this o2m relation instead the list of them for example will show this:(5) instead of this 1,2,5,8,76
- count_ids='('+value.length.toString()+')'
+ count_ids = '('+value.length.toString()+')'
return count_ids;
case 'datetime':
if (typeof(value) == "string")
From 16a2d354fc333d8fd3c0c638c5c2d74ffee8c580 Mon Sep 17 00:00:00 2001
From: Antony Lesuisse
Date: Sat, 5 May 2012 18:23:23 +0200
Subject: [PATCH 003/195] [IMP] module loading add a second namespaced argument
module(instance,module)
bzr revid: al@openerp.com-20120505162323-ggqwrd4t1c2wcxcd
---
addons/web/static/src/js/boot.js | 2 +-
addons/web/static/src/js/coresetup.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 29d953aadd8..f83dbc97095 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -37,7 +37,7 @@
};
openerp.instances[new_instance._session_id] = new_instance;
for(var i=0; i < modules.length; i++) {
- openerp[modules[i]](new_instance);
+ openerp[modules[i]](new_instance,new_instance[modules[i]]);
}
return new_instance;
}
diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js
index e633ea9cfec..8f5f150f125 100644
--- a/addons/web/static/src/js/coresetup.js
+++ b/addons/web/static/src/js/coresetup.js
@@ -280,7 +280,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
instance[mod] = {};
// init module mod
if(instance._openerp[mod] != undefined) {
- instance._openerp[mod](instance);
+ instance._openerp[mod](instance,instance[mod]);
this.module_loaded[mod] = true;
}
}
From dd6de9b2006cfe5bc7be28d70addc321691d2746 Mon Sep 17 00:00:00 2001
From: Fabien Pinckaers
Date: Mon, 7 May 2012 10:19:08 +0200
Subject: [PATCH 004/195] [IMP] replacing by flotr2, initial commit
bzr revid: fp@tinyerp.com-20120507081908-j0sfm5f84f0t32yg
---
addons/web_graph/__openerp__.py | 24 +-
addons/web_graph/doc/TODO.txt | 2 +
.../static/lib/dhtmlxGraph/License_GPL.html | 73 -
.../lib/dhtmlxGraph/codebase/dhtmlxchart.css | 15 -
.../lib/dhtmlxGraph/codebase/dhtmlxchart.js | 2711 -------
.../codebase/dhtmlxchart_debug.css | 6 -
.../dhtmlxGraph/codebase/dhtmlxchart_debug.js | 4736 ------------
.../codebase/thirdparty/excanvas/AUTHORS | 10 -
.../codebase/thirdparty/excanvas/COPYING | 202 -
.../codebase/thirdparty/excanvas/README | 22 -
.../lib/dhtmlxGraph/dhtmlxchart_full.zip | Bin 22694 -> 0 bytes
.../static/lib/dhtmlxGraph/readme.txt | 7 -
.../01_initialization/01_load_xml.html | 70 -
.../01_initialization/02_load_json.html | 63 -
.../01_initialization/03_load_csv.html | 65 -
.../01_initialization/04_load_jsarray.html | 68 -
.../samples/01_initialization/05_series.html | 119 -
.../samples/02_color/01_default.html | 66 -
.../samples/02_color/02_custom.html | 67 -
.../samples/02_color/03_custom_logic.html | 77 -
.../samples/02_color/04_gradient.html | 64 -
.../samples/03_group/01_basic.html | 96 -
.../samples/03_group/02_scales.html | 126 -
.../samples/04_pie_chart/01_init.html | 67 -
.../samples/04_pie_chart/02_text.html | 52 -
.../samples/04_pie_chart/03_3d_chart.html | 48 -
.../samples/04_pie_chart/04_legend.html | 81 -
.../samples/05_line_chart/01_init.html | 55 -
.../samples/05_line_chart/02_style.html | 153 -
.../samples/05_line_chart/03_scale.html | 101 -
.../samples/05_line_chart/04_spline.html | 57 -
.../samples/05_line_chart/05_series.html | 94 -
.../samples/06_bar_chart/01_init.html | 74 -
.../samples/06_bar_chart/02_text.html | 80 -
.../samples/06_bar_chart/03_scales.html | 99 -
.../samples/06_bar_chart/04_styles.html | 207 -
.../06_bar_chart/05_stacked_chart.html | 87 -
.../samples/06_bar_chart/06_series.html | 81 -
.../06_bar_chart/07_horizonal_bars.html | 120 -
.../08_horizonal_stacked_bars.html | 75 -
.../samples/07_area_chart/01_init.html | 55 -
.../samples/07_area_chart/02_scale.html | 101 -
.../samples/07_area_chart/03_series.html | 90 -
.../07_area_chart/03_stacked_area.html | 88 -
.../samples/08_dynamic/01_add.html | 83 -
.../samples/08_dynamic/02_events.html | 96 -
.../samples/08_dynamic/03_sorting.html | 73 -
.../samples/08_dynamic/04_filtering.html | 81 -
.../samples/09_integration/01_dhtmlxgrid.html | 70 -
.../09_integration/02_dhtmlxgrid_group.html | 79 -
.../samples/09_integration/03_windows.html | 77 -
.../lib/dhtmlxGraph/samples/common/config.php | 13 -
.../lib/dhtmlxGraph/samples/common/data.php | 16 -
.../lib/dhtmlxGraph/samples/common/sales.xml | 46 -
.../lib/dhtmlxGraph/samples/common/stat.xml | 1 -
.../static/lib/dhtmlxGraph/samples/readme.txt | 7 -
.../lib/dhtmlxGraph/sources/dhtmlxchart.js | 3498 ---------
addons/web_graph/static/lib/flotr2/LICENSE | 19 +
addons/web_graph/static/lib/flotr2/Makefile | 35 +
addons/web_graph/static/lib/flotr2/README.md | 89 +
.../web_graph/static/lib/flotr2/dev/notes.txt | 86 +
.../static/lib/flotr2/flotr2.examples.min.js | 2 +
.../lib/flotr2/flotr2.examples.types.js | 1425 ++++
.../static/lib/flotr2/flotr2.ie.min.js | 33 +
addons/web_graph/static/lib/flotr2/flotr2.js | 6865 +++++++++++++++++
.../web_graph/static/lib/flotr2/flotr2.min.js | 27 +
addons/web_graph/static/lib/flotr2/js/Axis.js | 303 +
.../web_graph/static/lib/flotr2/js/Color.js | 163 +
addons/web_graph/static/lib/flotr2/js/DOM.js | 88 +
addons/web_graph/static/lib/flotr2/js/Date.js | 207 +
.../static/lib/flotr2/js/DefaultOptions.js | 98 +
.../static/lib/flotr2/js/EventAdapter.js | 52 +
.../web_graph/static/lib/flotr2/js/Flotr.js | 250 +
.../web_graph/static/lib/flotr2/js/Graph.js | 745 ++
.../web_graph/static/lib/flotr2/js/Series.js | 74 +
addons/web_graph/static/lib/flotr2/js/Text.js | 88 +
.../static/lib/flotr2/js/plugins/crosshair.js | 84 +
.../static/lib/flotr2/js/plugins/download.js | 51 +
.../static/lib/flotr2/js/plugins/grid.js | 208 +
.../static/lib/flotr2/js/plugins/handles.js | 199 +
.../static/lib/flotr2/js/plugins/hit.js | 337 +
.../static/lib/flotr2/js/plugins/labels.js | 227 +
.../static/lib/flotr2/js/plugins/legend.js | 179 +
.../static/lib/flotr2/js/plugins/selection.js | 278 +
.../lib/flotr2/js/plugins/spreadsheet.js | 296 +
.../static/lib/flotr2/js/plugins/titles.js | 177 +
.../static/lib/flotr2/js/types/bars.js | 274 +
.../static/lib/flotr2/js/types/bubbles.js | 119 +
.../static/lib/flotr2/js/types/candles.js | 127 +
.../static/lib/flotr2/js/types/gantt.js | 229 +
.../static/lib/flotr2/js/types/lines.js | 275 +
.../static/lib/flotr2/js/types/markers.js | 140 +
.../static/lib/flotr2/js/types/pie.js | 210 +
.../static/lib/flotr2/js/types/points.js | 66 +
.../static/lib/flotr2/js/types/radar.js | 60 +
.../static/lib/flotr2/js/types/timeline.js | 90 +
.../web_graph/static/lib/flotr2/lib/base64.js | 113 +
.../static/lib/flotr2/lib/bean-min.js | 10 +
.../web_graph/static/lib/flotr2/lib/bean.js | 503 ++
.../static/lib/flotr2/lib/canvas2image.js | 198 +
.../static/lib/flotr2/lib/canvastext.js | 429 +
.../excanvas => flotr2/lib}/excanvas.js | 801 +-
.../static/lib/flotr2/lib/imagediff.js | 343 +
.../static/lib/flotr2/lib/jasmine/MIT.LICENSE | 20 +
.../lib/flotr2/lib/jasmine/jasmine-html.js | 190 +
.../static/lib/flotr2/lib/jasmine/jasmine.css | 166 +
.../static/lib/flotr2/lib/jasmine/jasmine.js | 2476 ++++++
.../flotr2/lib/jasmine/jasmine_favicon.png | Bin 0 -> 905 bytes
.../static/lib/flotr2/lib/prototype.js | 4320 +++++++++++
.../static/lib/flotr2/lib/underscore-min.js | 27 +
.../static/lib/flotr2/lib/underscore.js | 839 ++
.../static/lib/flotr2/lib/yepnope.js | 1 +
.../static/lib/flotr2/make/basic.json | 25 +
.../static/lib/flotr2/make/build.json | 97 +
.../static/lib/flotr2/make/examples.json | 38 +
.../static/lib/flotr2/make/flotr2.json | 36 +
.../web_graph/static/lib/flotr2/make/ie.json | 10 +
.../web_graph/static/lib/flotr2/make/lib.json | 11 +
.../web_graph/static/lib/flotr2/spec/Chart.js | 120 +
.../web_graph/static/lib/flotr2/spec/Color.js | 92 +
.../web_graph/static/lib/flotr2/spec/Flotr.js | 76 +
.../web_graph/static/lib/flotr2/spec/Graph.js | 79 +
.../static/lib/flotr2/spec/SpecRunner.html | 101 +
.../lib/flotr2/spec/helpers/stableFlotr.js | 1 +
.../lib/flotr2/spec/helpers/testFlotr.js | 1 +
.../lib/flotr2/spec/images/butterfly.jpg | Bin 0 -> 19050 bytes
.../lib/flotr2/spec/images/checkmark.png | Bin 0 -> 903 bytes
.../static/lib/flotr2/spec/images/xmark.png | Bin 0 -> 939 bytes
.../lib/flotr2/spec/img/test-background.png | Bin 0 -> 25218 bytes
.../static/lib/flotr2/spec/jasmine.yml | 48 +
.../lib/flotr2/spec/js/flotr2.stable.js | 6865 +++++++++++++++++
.../lib/flotr2/spec/js/test-background.js | 68 +
.../lib/flotr2/spec/js/test-boundaries.js | 31 +
addons/web_graph/static/src/css/flotr2.css | 212 +
addons/web_graph/static/src/css/graph.css | 46 +
addons/web_graph/static/src/js/graph.js | 354 +-
addons/web_graph/static/src/js/index.html | 358 +
addons/web_graph/static/src/xml/web_graph.xml | 51 +-
138 files changed, 32959 insertions(+), 15166 deletions(-)
create mode 100644 addons/web_graph/doc/TODO.txt
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/License_GPL.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart.css
delete mode 100755 addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart.js
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.css
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.js
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/AUTHORS
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/COPYING
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/README
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/dhtmlxchart_full.zip
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/readme.txt
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/01_load_xml.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/02_load_json.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/03_load_csv.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/04_load_jsarray.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/05_series.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/01_default.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/02_custom.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/03_custom_logic.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/04_gradient.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/03_group/01_basic.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/03_group/02_scales.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/01_init.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/02_text.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/03_3d_chart.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/04_legend.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/01_init.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/02_style.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/03_scale.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/04_spline.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/05_series.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/01_init.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/02_text.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/03_scales.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/04_styles.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/05_stacked_chart.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/06_series.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/07_horizonal_bars.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/08_horizonal_stacked_bars.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/01_init.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/02_scale.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/03_series.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/03_stacked_area.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/01_add.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/02_events.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/03_sorting.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/04_filtering.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/01_dhtmlxgrid.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/02_dhtmlxgrid_group.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/03_windows.html
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/common/config.php
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/common/data.php
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/common/sales.xml
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/common/stat.xml
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/samples/readme.txt
delete mode 100644 addons/web_graph/static/lib/dhtmlxGraph/sources/dhtmlxchart.js
create mode 100644 addons/web_graph/static/lib/flotr2/LICENSE
create mode 100644 addons/web_graph/static/lib/flotr2/Makefile
create mode 100644 addons/web_graph/static/lib/flotr2/README.md
create mode 100644 addons/web_graph/static/lib/flotr2/dev/notes.txt
create mode 100644 addons/web_graph/static/lib/flotr2/flotr2.examples.min.js
create mode 100644 addons/web_graph/static/lib/flotr2/flotr2.examples.types.js
create mode 100644 addons/web_graph/static/lib/flotr2/flotr2.ie.min.js
create mode 100644 addons/web_graph/static/lib/flotr2/flotr2.js
create mode 100644 addons/web_graph/static/lib/flotr2/flotr2.min.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/Axis.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/Color.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/DOM.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/Date.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/DefaultOptions.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/EventAdapter.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/Flotr.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/Graph.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/Series.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/Text.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/download.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/grid.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/handles.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/hit.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/labels.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/legend.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/selection.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/plugins/titles.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/bars.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/bubbles.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/candles.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/gantt.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/lines.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/markers.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/pie.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/points.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/radar.js
create mode 100644 addons/web_graph/static/lib/flotr2/js/types/timeline.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/base64.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/bean-min.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/bean.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/canvas2image.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/canvastext.js
rename addons/web_graph/static/lib/{dhtmlxGraph/codebase/thirdparty/excanvas => flotr2/lib}/excanvas.js (52%)
create mode 100644 addons/web_graph/static/lib/flotr2/lib/imagediff.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE
create mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css
create mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png
create mode 100644 addons/web_graph/static/lib/flotr2/lib/prototype.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/underscore-min.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/underscore.js
create mode 100644 addons/web_graph/static/lib/flotr2/lib/yepnope.js
create mode 100644 addons/web_graph/static/lib/flotr2/make/basic.json
create mode 100644 addons/web_graph/static/lib/flotr2/make/build.json
create mode 100644 addons/web_graph/static/lib/flotr2/make/examples.json
create mode 100644 addons/web_graph/static/lib/flotr2/make/flotr2.json
create mode 100644 addons/web_graph/static/lib/flotr2/make/ie.json
create mode 100644 addons/web_graph/static/lib/flotr2/make/lib.json
create mode 100644 addons/web_graph/static/lib/flotr2/spec/Chart.js
create mode 100644 addons/web_graph/static/lib/flotr2/spec/Color.js
create mode 100644 addons/web_graph/static/lib/flotr2/spec/Flotr.js
create mode 100644 addons/web_graph/static/lib/flotr2/spec/Graph.js
create mode 100644 addons/web_graph/static/lib/flotr2/spec/SpecRunner.html
create mode 100644 addons/web_graph/static/lib/flotr2/spec/helpers/stableFlotr.js
create mode 100644 addons/web_graph/static/lib/flotr2/spec/helpers/testFlotr.js
create mode 100644 addons/web_graph/static/lib/flotr2/spec/images/butterfly.jpg
create mode 100644 addons/web_graph/static/lib/flotr2/spec/images/checkmark.png
create mode 100644 addons/web_graph/static/lib/flotr2/spec/images/xmark.png
create mode 100644 addons/web_graph/static/lib/flotr2/spec/img/test-background.png
create mode 100644 addons/web_graph/static/lib/flotr2/spec/jasmine.yml
create mode 100644 addons/web_graph/static/lib/flotr2/spec/js/flotr2.stable.js
create mode 100644 addons/web_graph/static/lib/flotr2/spec/js/test-background.js
create mode 100644 addons/web_graph/static/lib/flotr2/spec/js/test-boundaries.js
create mode 100644 addons/web_graph/static/src/css/flotr2.css
create mode 100644 addons/web_graph/static/src/css/graph.css
create mode 100644 addons/web_graph/static/src/js/index.html
diff --git a/addons/web_graph/__openerp__.py b/addons/web_graph/__openerp__.py
index 6c62ffe4ede..72a60ef240f 100644
--- a/addons/web_graph/__openerp__.py
+++ b/addons/web_graph/__openerp__.py
@@ -1,14 +1,24 @@
{
- "name": "web Graph",
+ "name": "Graph Views",
"category" : "Hidden",
- "description":'Openerp web graph view',
- "version": "2.0",
+ "description":"""Graph Views for Web Client
+
+* Parse a view but allows changing dynamically the presentation
+* Graph Types: pie, lines, areas, bars, radar
+* Stacked / Not Stacked for areas and bars
+* Legends: top, inside (top/left), hidden
+* Features: download as PNG or CSV, browse data grid, switch orientation
+* Unlimited "Group By" levels, multi level analysis
+""",
+ "version": "3.0",
"depends": ['web'],
"js": [
- "static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/excanvas.js",
- "static/lib/dhtmlxGraph/codebase/dhtmlxchart.js",
- "static/src/js/graph.js"],
- "css": ["static/lib/dhtmlxGraph/codebase/dhtmlxchart.css"],
+ "static/lib/flotr2/flotr2.js",
+ "static/src/js/graph.js"
+ ],
+ "css": [
+ "static/src/css/*.css",
+ ],
'qweb' : [
"static/src/xml/*.xml",
],
diff --git a/addons/web_graph/doc/TODO.txt b/addons/web_graph/doc/TODO.txt
new file mode 100644
index 00000000000..b7969527a1f
--- /dev/null
+++ b/addons/web_graph/doc/TODO.txt
@@ -0,0 +1,2 @@
+* Keep the minimum files for flotr2.js
+* Support clicking on a graph area to filter (or switch view?)
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/License_GPL.html b/addons/web_graph/static/lib/dhtmlxGraph/License_GPL.html
deleted file mode 100644
index afe2ef33f0b..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/License_GPL.html
+++ /dev/null
@@ -1,73 +0,0 @@
-GNU GENERAL PUBLIC LICENSE
-Version 2, June 1991
-
-Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
-
-Everyone is permitted to copy and distribute verbatim copies
-of this license document, but changing it is not allowed.
-
-
-TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
-
-1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
-
-2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
-
-
-a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
-
-b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
-
-c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
-These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
-
-3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
-
-a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
-
-b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
-
-c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
-The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
-
-If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
-
-4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
-
-5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
-
-6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
-
-7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
-
-This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
-
-8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
-
-9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
-
-10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
-
-NO WARRANTY
-
-11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
-
-
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart.css b/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart.css
deleted file mode 100644
index b7c3880d162..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart.css
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
-Copyright DHTMLX LTD. http://www.dhtmlx.com
-You allowed to use this component or parts of it under GPL terms
-To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com
-*/
-.dhx_tooltip{display:none;position:absolute;font-family:Tahoma;font-size:8pt;z-index:10000;background-color:white;padding:2px 2px 2px 2px;border:1px solid #A4BED4;}
-.dhx_chart{position:relative;font-family:Verdana;font-size:13px;color:#000;overflow:hidden;}
-.dhx_canvas_text{position:absolute;text-align:center;}
-.dhx_map_img{width:100%;height:100%;position:absolute;top:0;left:0;border:0;filter:alpha(opacity=0);}
-.dhx_axis_item_y{position:absolute;height:10px;line-height:10px;text-align:right;}
-.dhx_axis_title_y{text-align:center;font-family:Verdana;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-o-transform:rotate(-90deg);padding-left:3px;}
-.dhx_axis_item_x{text-align:left;margin-top:20px;margin-left:-14px;font-size:8pt;-webkit-transform:rotate(-60deg);-moz-transform:rotate(-60deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-o-transform:rotate(-60deg);padding-left:3px;}
-.dhx_axis_title_x{text-align:center;margin-top:50px;}
-.dhx_chart_legend{position:absolute;}
-.dhx_chart_legend_item{height:18px;line-height:18px;padding:2px;}
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart.js b/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart.js
deleted file mode 100755
index 73d936e26d0..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart.js
+++ /dev/null
@@ -1,2711 +0,0 @@
-/*
-Copyright DHTMLX LTD. http://www.dhtmlx.com
-You allowed to use this component or parts of it under GPL terms
-To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com
-*/
-window.dhtmlx || (dhtmlx = {});
-dhtmlx.version = "3.0";
-dhtmlx.codebase = "./";
-dhtmlx.extend = function (a, b) {
- for (var c in b) a[c] = b[c];
- b.k && a.k();
- return a
-};
-dhtmlx.proto_extend = function () {
- for (var a = arguments, b = a[0], c = [], d = a.length - 1; d > 0; d--) {
- if (typeof a[d] == "function") a[d] = a[d].prototype;
- for (var e in a[d]) if (e == "_init") c.push(a[d][e]);
- else b[e] || (b[e] = a[d][e])
- }
- a[0].k && c.push(a[0].k);
- b.k = function () {
- for (var g = 0; g < c.length; g++) c[g].apply(this, arguments)
- };
- b.base = a[1];
- var f = function (g) {
- this.k(g);
- this.B && this.B(g, this.defaults)
- };
- f.prototype = b;
- b = a = null;
- return f
-};
-dhtmlx.bind = function (a, b) {
- return function () {
- return a.apply(b, arguments)
- }
-};
-dhtmlx.require = function (a) {
- if (!dhtmlx.ha[a]) {
- dhtmlx.exec(dhtmlx.ajax().sync().get(dhtmlx.codebase + a).responseText);
- dhtmlx.ha[a] = true
- }
-};
-dhtmlx.ha = {};
-dhtmlx.exec = function (a) {
- window.execScript ? window.execScript(a) : window.eval(a)
-};
-dhtmlx.methodPush = function (a, b) {
- return function () {
- var c = false;
- return c = a[b].apply(a, arguments)
- }
-};
-dhtmlx.isNotDefined = function (a) {
- return typeof a == "undefined"
-};
-dhtmlx.delay = function (a, b, c, d) {
- setTimeout(function () {
- var e = a.apply(b, c);
- a = b = c = null;
- return e
- }, d || 1)
-};
-dhtmlx.uid = function () {
- if (!this.S) this.S = (new Date).valueOf();
- this.S++;
- return this.S
-};
-dhtmlx.toNode = function (a) {
- if (typeof a == "string") return document.getElementById(a);
- return a
-};
-dhtmlx.toArray = function (a) {
- return dhtmlx.extend(a || [], dhtmlx.PowerArray)
-};
-dhtmlx.toFunctor = function (a) {
- return typeof a == "string" ? eval(a) : a
-};
-dhtmlx.j = {};
-dhtmlx.event = function (a, b, c, d) {
- a = dhtmlx.toNode(a);
- var e = dhtmlx.uid();
- dhtmlx.j[e] = [a, b, c];
- if (d) c = dhtmlx.bind(c, d);
- if (a.addEventListener) a.addEventListener(b, c, false);
- else a.attachEvent && a.attachEvent("on" + b, c);
- return e
-};
-dhtmlx.eventRemove = function (a) {
- if (a) {
- var b = dhtmlx.j[a];
- if (b[0].removeEventListener) b[0].removeEventListener(b[1], b[2], false);
- else b[0].detachEvent && b[0].detachEvent("on" + b[1], b[2]);
- delete this.j[a]
- }
-};
-dhtmlx.EventSystem = {
- k: function () {
- this.j = {};
- this.A = {};
- this.s = {}
- },
- block: function () {
- this.j.U = true
- },
- unblock: function () {
- this.j.U = false
- },
- mapEvent: function (a) {
- dhtmlx.extend(this.s, a)
- },
- callEvent: function (a, b) {
- if (this.j.U) return true;
- a = a.toLowerCase();
- var c = this.j[a.toLowerCase()],
- d = true;
- if (c) for (var e = 0; e < c.length; e++) if (c[e].apply(this, b || []) === false) d = false;
- if (this.s[a] && !this.s[a].callEvent(a, b)) d = false;
- return d
- },
- attachEvent: function (a, b, c) {
- a = a.toLowerCase();
- c = c || dhtmlx.uid();
- b = dhtmlx.toFunctor(b);
- var d = this.j[a] || dhtmlx.toArray();
- d.push(b);
- this.j[a] = d;
- this.A[c] = {
- f: b,
- t: a
- };
- return c
- },
- detachEvent: function (a) {
- var b = this.A[a].t,
- c = this.A[a].f;
- b = this.j[b];
- b.remove(c);
- delete this.A[a]
- }
-};
-dhtmlx.PowerArray = {
- removeAt: function (a, b) {
- if (a >= 0) this.splice(a, b || 1)
- },
- remove: function (a) {
- this.removeAt(this.find(a))
- },
- insertAt: function (a, b) {
- if (!b && b !== 0) this.push(a);
- else {
- var c = this.splice(b, this.length - b);
- this[b] = a;
- this.push.apply(this, c)
- }
- },
- find: function (a) {
- for (i = 0; i < this.length; i++) if (a == this[i]) return i;
- return -1
- },
- each: function (a, b) {
- for (var c = 0; c < this.length; c++) a.call(b || this, this[c])
- },
- map: function (a, b) {
- for (var c = 0; c < this.length; c++) this[c] = a.call(b || this, this[c]);
- return this
- }
-};
-dhtmlx.env = {};
-if (navigator.userAgent.indexOf("Opera") != -1) dhtmlx.La = true;
-else {
- dhtmlx.r = !! document.all;
- dhtmlx.Ka = !document.all;
- dhtmlx.Ma = navigator.userAgent.indexOf("KHTML") != -1;
- if (navigator.appVersion.indexOf("MSIE 8.0") != -1 && document.compatMode != "BackCompat") dhtmlx.r = 8
-}
-dhtmlx.env = {};
-(function () {
- dhtmlx.env.transform = false;
- dhtmlx.env.transition = false;
- var a = {};
- a.names = ["transform", "transition"];
- a.transform = ["transform", "WebkitTransform", "MozTransform", "oTransform"];
- a.transition = ["transition", "WebkitTransition", "MozTransition", "oTransition"];
- for (var b = document.createElement("DIV"), c = 0; c < a.names.length; c++) for (; p = a[a.names[c]].pop();) if (typeof b.style[p] != "undefined") dhtmlx.env[a.names[c]] = true
-})();
-dhtmlx.env.transform_prefix = function () {
- var a;
- if (dhtmlx.La) a = "-o-";
- else {
- a = "";
- if (dhtmlx.Ka) a = "-moz-";
- if (dhtmlx.Ma) a = "-webkit-"
- }
- return a
-}();
-dhtmlx.env.svg = function () {
- return document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1")
-}();
-dhtmlx.zIndex = {
- drag: 1E4
-};
-dhtmlx.html = {
- create: function (a, b, c) {
- b = b || {};
- var d = document.createElement(a);
- for (var e in b) d.setAttribute(e, b[e]);
- if (b.style) d.style.cssText = b.style;
- if (b["class"]) d.className = b["class"];
- if (c) d.innerHTML = c;
- return d
- },
- getValue: function (a) {
- a = dhtmlx.toNode(a);
- if (!a) return "";
- return dhtmlx.isNotDefined(a.value) ? a.innerHTML : a.value
- },
- remove: function (a) {
- if (a instanceof Array) for (var b = 0; b < a.length; b++) this.remove(a[b]);
- else a && a.parentNode && a.parentNode.removeChild(a)
- },
- insertBefore: function (a, b, c) {
- if (a) b ? b.parentNode.insertBefore(a, b) : c.appendChild(a)
- },
- locate: function (a, b) {
- a = a || event;
- for (var c = a.target || a.srcElement; c;) {
- if (c.getAttribute) {
- var d = c.getAttribute(b);
- if (d) return d
- }
- c = c.parentNode
- }
- return null
- },
- offset: function (a) {
- if (a.getBoundingClientRect) {
- var b = a.getBoundingClientRect(),
- c = document.body,
- d = document.documentElement,
- e = window.pageYOffset || d.scrollTop || c.scrollTop,
- f = window.pageXOffset || d.scrollLeft || c.scrollLeft,
- g = d.clientTop || c.clientTop || 0,
- i = d.clientLeft || c.clientLeft || 0,
- j = b.top + e - g,
- k = b.left + f - i;
- return {
- y: Math.round(j),
- x: Math.round(k)
- }
- } else {
- for (k = j = 0; a;) {
- j += parseInt(a.offsetTop, 10);
- k += parseInt(a.offsetLeft, 10);
- a = a.offsetParent
- }
- return {
- y: j,
- x: k
- }
- }
- },
- pos: function (a) {
- a = a || event;
- if (a.pageX || a.pageY) return {
- x: a.pageX,
- y: a.pageY
- };
- var b = dhtmlx.r && document.compatMode != "BackCompat" ? document.documentElement : document.body;
- return {
- x: a.clientX + b.scrollLeft - b.clientLeft,
- y: a.clientY + b.scrollTop - b.clientTop
- }
- },
- preventEvent: function (a) {
- a && a.preventDefault && a.preventDefault();
- dhtmlx.html.stopEvent(a)
- },
- stopEvent: function (a) {
- (a || event).cancelBubble = true;
- return false
- },
- addCss: function (a, b) {
- a.className += " " + b
- },
- removeCss: function (a, b) {
- a.className = a.className.replace(RegExp(b, "g"), "")
- }
-};
-(function () {
- var a = document.getElementsByTagName("SCRIPT");
- if (a.length) {
- a = (a[a.length - 1].getAttribute("src") || "").split("/");
- a.splice(a.length - 1, 1);
- dhtmlx.codebase = a.slice(0, a.length).join("/") + "/"
- }
-})();
-dhtmlx.ui = {};
-dhtmlx.Destruction = {
- k: function () {
- dhtmlx.destructors.push(this)
- },
- destructor: function () {
- this.destructor = function () {};
- this.ib = this.v = null;
- this.fa && document.body.appendChild(this.fa);
- this.fa = null;
- if (this.g) {
- this.g.innerHTML = "";
- this.g.v = null
- }
- this.data = this.g = this.L = null;
- this.j = this.A = {}
- }
-};
-dhtmlx.destructors = [];
-dhtmlx.event(window, "unload", function () {
- for (var a = 0; a < dhtmlx.destructors.length; a++) dhtmlx.destructors[a].destructor();
- dhtmlx.destructors = [];
- for (var b in dhtmlx.j) {
- a = dhtmlx.j[b];
- if (a[0].removeEventListener) a[0].removeEventListener(a[1], a[2], false);
- else a[0].detachEvent && a[0].detachEvent("on" + a[1], a[2]);
- delete dhtmlx.j[b]
- }
-});
-dhtmlx.math = {};
-dhtmlx.math.fb = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
-dhtmlx.math.toHex = function (a, b) {
- a = parseInt(a, 10);
- for (str = ""; a > 0;) {
- str = this.fb[a % 16] + str;
- a = Math.floor(a / 16)
- }
- for (; str.length < b;) str = "0" + str;
- return str
-};
-dhtmlx.ui.Map = function (a) {
- this.name = "Map";
- this.q = "map_" + dhtmlx.uid();
- this.Pa = a;
- this.s = []
-};
-dhtmlx.ui.Map.prototype = {
- addRect: function (a, b, c) {
- this.X(a, "RECT", b, c)
- },
- addPoly: function (a, b) {
- this.X(a, "POLY", b)
- },
- X: function (a, b, c, d) {
- var e = "";
- if (arguments.length == 4) e = "userdata='" + d + "'";
- this.s.push(" ")
- },
- addSector: function (a, b, c, d, e, f, g) {
- var i = [];
- i.push(d);
- i.push(Math.floor(e * g));
- for (var j = b; j < c; j += Math.PI / 18) {
- i.push(Math.floor(d + f * Math.cos(j)));
- i.push(Math.floor((e + f * Math.sin(j)) * g))
- }
- i.push(Math.floor(d + f * Math.cos(c)));
- i.push(Math.floor((e + f * Math.sin(c)) * g));
- i.push(d);
- i.push(Math.floor(e * g));
- return this.addPoly(a, i)
- },
- render: function (a) {
- var b = dhtmlx.html.create("DIV");
- b.style.cssText = "position:absolute; width:100%; height:100%; top:0px; left:0px;";
- a.appendChild(b);
- var c = dhtmlx.r ? "" : "src='data:image/gif;base64,R0lGODlhEgASAIAAAP///////yH5BAUUAAEALAAAAAASABIAAAIPjI+py+0Po5y02ouz3pwXADs='";
- b.innerHTML = "" + this.s.join("\n") + " ";
- a.v = b;
- this.s = []
- }
-};
-dhtmlx.chart = {};
-dhtmlx.chart.area = {
- pvt_render_area: function (a, b, c, d, e, f) {
- var g = this.C(a, b, c, d, e),
- i = Math.floor(g.cellWidth / 2);
- if (b.length) {
- a.globalAlpha = this.e.alpha.call(this, b[0]);
- a.fillStyle = this.e.color.call(this, b[0]);
- var j = this.p(b[0], c, d, g),
- k = this.e.offset ? c.x + g.cellWidth * 0.5 : c.x;
- a.beginPath();
- a.moveTo(k, d.y);
- a.lineTo(k, j);
- f.addRect(b[0].id, [k - i, j - i, k + i, j + i]);
- this.e.yAxis || this.renderTextAt(false, !this.e.offset ? false : true, k, j - this.e.labelOffset, this.e.label(b[0]));
- for (var h = 1; h < b.length; h++) {
- var m = k + Math.floor(g.cellWidth * h) - 0.5,
- l = this.p(b[h], c, d, g);
- a.lineTo(m, l);
- f.addRect(b[h].id, [m - i, l - i, m + i, l + i]);
- this.e.yAxis || this.renderTextAt(false, !this.e.offset && h == b.length - 1 ? "left" : "center", m, l - this.e.labelOffset, this.e.label(b[h]))
- }
- a.lineTo(k + Math.floor(g.cellWidth * [b.length - 1]), d.y);
- a.lineTo(k, d.y);
- a.fill()
- }
- }
-};
-dhtmlx.chart.stackedArea = {
- pvt_render_stackedArea: function (a, b, c, d, e, f) {
- var g = this.C(a, b, c, d, e),
- i = Math.floor(g.cellWidth / 2),
- j = [];
- if (b.length) {
- a.globalAlpha = this.e.alpha.call(this, b[0]);
- a.fillStyle = this.e.color.call(this, b[0]);
- var k = e ? b[0].$startY : d.y,
- h = this.e.offset ? c.x + g.cellWidth * 0.5 : c.x,
- m = this.p(b[0], c, d, g) - (e ? d.y - k : 0);
- j[0] = m;
- a.beginPath();
- a.moveTo(h, k);
- a.lineTo(h, m);
- f.addRect(b[0].id, [h - i, m - i, h + i, m + i]);
- this.e.yAxis || this.renderTextAt(false, true, h, m - this.e.labelOffset, this.e.label(b[0]));
- for (var l = 1; l < b.length; l++) {
- var n = h + Math.floor(g.cellWidth * l) - 0.5,
- o = this.p(b[l], c, d, g) - (e ? d.y - b[l].$startY : 0);
- j[l] = o;
- a.lineTo(n, o);
- f.addRect(b[l].id, [n - i, o - i, n + i, o + i]);
- this.e.yAxis || this.renderTextAt(false, true, n, o - this.e.labelOffset, this.e.label(b[l]))
- }
- a.lineTo(h + Math.floor(g.cellWidth * [b.length - 1]), k);
- if (e) for (l = b.length - 1; l >= 0; l--) {
- n = h + Math.floor(g.cellWidth * l) - 0.5;
- var s = b[l].$startY;
- a.lineTo(n, s)
- } else a.lineTo(h + Math.floor(g.cellWidth * (length - 1)) - 0.5, k);
- a.lineTo(h, k);
- a.fill();
- for (l = 0; l < b.length; l++) b[l].$startY = j[l]
- }
- }
-};
-dhtmlx.chart.spline = {
- pvt_render_spline: function (a, b, c, d, e) {
- var f = this.C(a, b, c, d, e);
- Math.floor(f.cellWidth / 2);
- var g = [];
- if (b.length) {
- var i = this.e.offset ? c.x + f.cellWidth * 0.5 : c.x;
- for (e = 0; e < b.length; e++) {
- var j = !e ? i : Math.floor(f.cellWidth * e) - 0.5 + i,
- k = this.p(b[e], c, d, f);
- g.push({
- x: j,
- y: k
- })
- }
- var h = this.Ha(g);
- for (e = 0; e < g.length - 1; e++) {
- var m = g[e].x;
- c = g[e].y;
- for (var l = g[e + 1].x, n = g[e + 1].y, o = m; o < l; o++) this.i(a, o, this.P(o, m, e, h.a, h.b, h.c, h.d), o + 1, this.P(o + 1, m, e, h.a, h.b, h.c, h.d), this.e.line.color(b[e]), this.e.line.width);
- this.i(a, l - 1, this.P(o, m, e, h.a, h.b, h.c, h.d), l, n, this.e.line.color(b[e]), this.e.line.width);
- this.M(a, m, c, b[e], this.e.label(b[e]))
- }
- this.M(a, l, n, b[e], this.e.label(b[e]))
- }
- },
- Ha: function (a) {
- var b, c, d, e, f, g, i, j, k;
- b = [];
- m = [];
- k = a.length;
- for (var h = 0; h < k - 1; h++) {
- b[h] = a[h + 1].x - a[h].x;
- m[h] = (a[h + 1].y - a[h].y) / b[h]
- }
- c = [];
- d = [];
- c[0] = 0;
- c[1] = 2 * (b[0] + b[1]);
- d[0] = 0;
- d[1] = 6 * (m[1] - m[0]);
- for (h = 2; h < k - 1; h++) {
- c[h] = 2 * (b[h - 1] + b[h]) - b[h - 1] * b[h - 1] / c[h - 1];
- d[h] = 6 * (m[h] - m[h - 1]) - b[h - 1] * d[h - 1] / c[h - 1]
- }
- e = [];
- e[k - 1] = e[0] = 0;
- for (h = k - 2; h >= 1; h--) e[h] = (d[h] - b[h] * e[h + 1]) / c[h];
- f = [];
- g = [];
- i = [];
- j = [];
- for (h = 0; h < k - 1; h++) {
- f[h] = a[h].y;
- g[h] = -b[h] * e[h + 1] / 6 - b[h] * e[h] / 3 + (a[h + 1].y - a[h].y) / b[h];
- i[h] = e[h] / 2;
- j[h] = (e[h + 1] - e[h]) / (6 * b[h])
- }
- return {
- a: f,
- b: g,
- c: i,
- d: j
- }
- },
- P: function (a, b, c, d, e, f, g) {
- return d[c] + (a - b) * (e[c] + (a - b) * (f[c] + (a - b) * g[c]))
- }
-};
-dhtmlx.chart.barH = {
- pvt_render_barH: function (a, b, c, d, e, f) {
- var g, i, j, k, h = d.x - c.x,
- m = !! this.e.yAxis,
- l = this.O("h");
- g = l.max;
- i = l.min;
- var n = Math.floor((d.y - c.y) / b.length);
- e || this.$(a, b, c, d, i, g, n);
- if (m) {
- g = parseFloat(this.e.xAxis.end);
- i = parseFloat(this.e.xAxis.start)
- }
- var o = this.z(i, g);
- k = o[0];
- j = o[1];
- var s = k ? h / k : 10;
- if (!m) {
- var t = 10;
- s = k ? (h - t) / k : 10
- }
- var q = parseInt(this.e.width, 10);
- if (q * this.h.length + 4 > n) q = n / this.h.length - 4;
- var v = Math.floor((n - q * this.h.length) / 2),
- p = typeof this.e.radius != "undefined" ? parseInt(this.e.radius, 10) : Math.round(q / 5),
- u = false,
- r = this.e.gradient;
- if (r && typeof r != "function") {
- u = r;
- r = false
- } else if (r) {
- r = a.createLinearGradient(c.x, c.y, d.x, c.y);
- this.e.gradient(r)
- }
- m || this.i(a, c.x - 0.5, c.y, c.x - 0.5, d.y, "#000000", 1);
- for (d = 0; d < b.length; d++) {
- var w = parseFloat(this.e.value(b[d]));
- if (w > g) w = g;
- w -= i;
- w *= j;
- var x = c.x,
- y = c.y + v + d * n + (q + 1) * e;
- if (w < 0 || this.e.yAxis && w === 0) this.renderTextAt("middle", true, x + 10, y + q / 2 + v, this.e.label(b[d]));
- else {
- m || (w += t / s);
- var B = r || this.e.color.call(this, b[d]);
- if (this.e.border) {
- a.beginPath();
- a.fillStyle = B;
- this.o(a, x, y, q, p, s, w, 0);
- a.lineTo(x, 0);
- a.fill();
- a.fillStyle = "#000000";
- a.globalAlpha = 0.37;
- a.beginPath();
- this.o(a, x, y, q, p, s, w, 0);
- a.fill()
- }
- a.globalAlpha = this.e.alpha.call(this, b[d]);
- a.fillStyle = r || this.e.color.call(this, b[d]);
- a.beginPath();
- var z = this.o(a, x, y, q, p, s, w, this.e.border ? 1 : 0);
- if (r && !u) a.lineTo(c.x + h, y + (this.e.border ? 1 : 0));
- a.fill();
- a.globalAlpha = 1;
- if (u != false) {
- var A = this.G(a, c.x, y + q, c.x + s * w + 2, y, u, B, "x");
- a.fillStyle = A.gradient;
- a.beginPath();
- z = this.o(a, x, y + A.offset, q - A.offset * 2, p, s, w, A.offset);
- a.fill();
- a.globalAlpha = 1
- }
- this.renderTextAt("middle", false, z[0] + 3, parseInt(y + (z[1] - y) / 2, 10), this.e.label(b[d]));
- f.addRect(b[d].id, [x, y, z[0], z[1]], e)
- }
- }
- },
- o: function (a, b, c, d, e, f, g, i) {
- var j = 0;
- if (e > f * g) {
- var k = (e - f * g) / e;
- j = -Math.asin(k) + Math.PI / 2
- }
- a.moveTo(b, c + i);
- var h = b + f * g - e - (e ? 0 : i);
- e < f * g && a.lineTo(h, c + i);
- f = c + e;
- e && a.arc(h, f, e - i, -Math.PI / 2 + j, 0, false);
- var m = c + d - e - (e ? 0 : i),
- l = h + e - (e ? i : 0);
- a.lineTo(l, m);
- var n = h;
- e && a.arc(n, m, e - i, 0, Math.PI / 2 - j, false);
- var o = c + d - i;
- a.lineTo(b, o);
- a.lineTo(b, c + i);
- return [l, o]
- },
- $: function (a, b, c, d, e, f, g) {
- this.xa(a, b, c, d, e, f);
- this.ya(a, b, c, d, g)
- },
- ya: function (a, b, c, d, e) {
- if (this.e.yAxis) {
- var f = c.x - 0.5,
- g = d.y + 0.5,
- i = c.y;
- this.i(a, f, g, f, i, this.e.yAxis.color, 1);
- for (a = 0; a < b.length; a++) this.renderTextAt("middle", 0, 0, i + e / 2 + a * e, this.e.yAxis.template(b[a]), "dhx_axis_item_y", c.x - 5);
- this.oa(c, d)
- }
- },
- xa: function (a, b, c, d, e, f) {
- var g, i = {},
- j = this.e.xAxis;
- if (j) {
- b = d.y + 0.5;
- var k = c.x - 0.5,
- h = d.x - 0.5;
- this.i(a, k, b, h, b, j.color, 1);
- if (j.step) g = parseFloat(j.step);
- if (typeof j.step == "undefined" || typeof j.start == "undefined" || typeof j.end == "undefined") {
- i = this.V(e, f);
- e = i.start;
- f = i.end;
- g = i.step;
- this.e.xAxis.end = f;
- this.e.xAxis.start = e;
- this.e.xAxis.step = g
- }
- if (g !== 0) {
- for (var m = (h - k) * g / (f - e), l = 0, n = e; n <= f; n += g) {
- if (i.fixNum) n = parseFloat((new Number(n)).toFixed(i.fixNum));
- var o = Math.floor(k + l * m) + 0.5;
- n != e && j.lines && this.i(a, o, b, o, c.y, this.e.xAxis.color, 0.2);
- this.renderTextAt(false, true, o, b + 2, j.template(n.toString()), "dhx_axis_item_x");
- l++
- }
- this.renderTextAt(true, false, k, d.y + this.e.padding.bottom - 3, this.e.xAxis.title, "dhx_axis_title_x", d.x - c.x);
- j.lines && this.i(a, k, c.y - 0.5, h, c.y - 0.5, this.e.xAxis.color, 0.2)
- }
- }
- }
-};
-dhtmlx.chart.stackedBarH = {
- pvt_render_stackedBarH: function (a, b, c, d, e, f) {
- var g, i, j, k, h = d.x - c.x,
- m = !! this.e.yAxis;
- i = this.Q(b);
- g = i.max;
- i = i.min;
- var l = Math.floor((d.y - c.y) / b.length);
- e || this.$(a, b, c, d, i, g, l);
- if (m) {
- g = parseFloat(this.e.xAxis.end);
- i = parseFloat(this.e.xAxis.start)
- }
- j = this.z(i, g);
- k = j[0];
- j = j[1];
- var n = k ? h / k : 10;
- if (!m) {
- var o = 10;
- n = k ? (h - o) / k : 10
- }
- k = parseInt(this.e.width, 10);
- if (k + 4 > l) k = l - 4;
- var s = Math.floor((l - k) / 2),
- t = 0,
- q = false,
- v = this.e.gradient;
- q = false;
- if (v = this.e.gradient) q = true;
- m || this.i(a, c.x - 0.5, c.y, c.x - 0.5, d.y, "#000000", 1);
- for (d = 0; d < b.length; d++) {
- if (!e) b[d].$startX = c.x;
- var p = parseFloat(this.e.value(b[d]));
- if (p > g) p = g;
- p -= i;
- p *= j;
- var u = c.x,
- r = c.y + s + d * l;
- if (e) u = b[d].$startX || u;
- if (p < 0 || this.e.yAxis && p === 0) this.renderTextAt("middle", true, u + 10, r + k / 2, this.e.label(b[d]));
- else {
- m || (p += o / n);
- var w = this.e.color.call(this, b[d]);
- if (this.e.border) {
- a.beginPath();
- a.fillStyle = w;
- this.o(a, u, r, k, t, n, p, 0);
- a.lineTo(u, 0);
- a.fill();
- a.fillStyle = "#000000";
- a.globalAlpha = 0.37;
- a.beginPath();
- this.o(a, u, r, k, t, n, p, 0);
- a.fill()
- }
- a.globalAlpha = 1;
- a.globalAlpha = this.e.alpha.call(this, b[d]);
- a.fillStyle = this.e.color.call(this, b[d]);
- a.beginPath();
- var x = this.o(a, u, r, k, t, n, p, this.e.border ? 1 : 0);
- if (v && !q) a.lineTo(c.x + h, r + (this.e.border ? 1 : 0));
- a.fill();
- if (q != false) {
- w = this.G(a, u, r + k, u, r, q, w, "x");
- a.fillStyle = w.gradient;
- a.beginPath();
- x = this.o(a, u, r, k, t, n, p, 0);
- a.fill();
- a.globalAlpha = 1
- }
- this.renderTextAt("middle", true, b[d].$startX + (x[0] - b[d].$startX) / 2 - 1, r + (x[1] - r) / 2, this.e.label(b[d]));
- f.addRect(b[d].id, [b[d].$startX, r, x[0], x[1]], e);
- b[d].$startX = x[0]
- }
- }
- }
-};
-dhtmlx.chart.stackedBar = {
- pvt_render_stackedBar: function (a, b, c, d, e, f) {
- var g, i, j, k = d.y - c.y;
- j = !! this.e.yAxis;
- var h = !! this.e.xAxis;
- i = this.Q(b);
- g = i.max;
- i = i.min;
- var m = Math.floor((d.x - c.x) / b.length);
- e || this.N(a, b, c, d, i, g, m);
- if (j) {
- g = parseFloat(this.e.yAxis.end);
- i = parseFloat(this.e.yAxis.start)
- }
- g = this.z(i, g);
- j = g[0];
- g = g[1];
- j = j ? k / j : 10;
- var l = parseInt(this.e.width, 10);
- if (l + 4 > m) l = m - 4;
- var n = Math.floor((m - l) / 2),
- o = this.e.gradient ? this.e.gradient : false;
- h || this.i(a, c.x, d.y + 0.5, d.x, d.y + 0.5, "#000000", 1);
- for (h = 0; h < b.length; h++) {
- var s = parseFloat(this.e.value(b[h]));
- if (s) {
- e || (s -= i);
- s *= g;
- var t = c.x + n + h * m,
- q = d.y;
- if (e) q = b[h].$startY || q;
- if (!(q < c.y + 1)) if (s < 0 || this.e.yAxis && s === 0) this.renderTextAt(true, true, t + Math.floor(l / 2), q, this.e.label(b[h]));
- else {
- var v = this.e.color.call(this, b[h]);
- if (this.e.border) {
- a.beginPath();
- a.fillStyle = v;
- this.I(a, t - 1, q, l + 2, j, s, 0, c.y);
- a.lineTo(t, q);
- a.fill();
- a.fillStyle = "#000000";
- a.globalAlpha = 0.37;
- a.beginPath();
- this.I(a, t - 1, q, l + 2, j, s, 0, c.y);
- a.fill()
- }
- a.globalAlpha = this.e.alpha.call(this, b[h]);
- a.fillStyle = this.e.color.call(this, b[h]);
- a.beginPath();
- var p = this.I(a, t, q, l, j, s, this.e.border ? 1 : 0, c.y);
- a.fill();
- a.globalAlpha = 1;
- if (o) {
- v = this.G(a, t, q, t + l, p[1], o, v, "y");
- a.fillStyle = v.gradient;
- a.beginPath();
- p = this.I(a, t + v.offset, q, l - v.offset * 2, j, s, this.e.border ? 1 : 0, c.y);
- a.fill();
- a.globalAlpha = 1
- }
- this.renderTextAt(false, true, t + Math.floor(l / 2), p[1] + (q - p[1]) / 2 - 7, this.e.label(b[h]));
- f.addRect(b[h].id, [t, p[1], p[0], b[h].$startY || q], e);
- b[h].$startY = this.e.border ? p[1] + 1 : p[1]
- }
- }
- }
- },
- I: function (a, b, c, d, e, f, g, i) {
- a.moveTo(b, c);
- f = c - e * f + g;
- if (f < i) f = i;
- a.lineTo(b, f);
- e = b + d;
- f = f;
- a.lineTo(e, f);
- var j = b + d;
- a.lineTo(j, c);
- a.lineTo(b, c);
- return [j, f - 2 * g]
- }
-};
-dhtmlx.chart.line = {
- pvt_render_line: function (a, b, c, d, e, f) {
- e = this.C(a, b, c, d, e);
- var g = Math.floor(e.cellWidth / 2);
- if (b.length) for (var i = this.p(b[0], c, d, e), j = this.e.offset ? c.x + e.cellWidth * 0.5 : c.x, k = j, h = 1; h <= b.length; h++) {
- var m = Math.floor(e.cellWidth * h) - 0.5 + k;
- if (b.length != h) {
- var l = this.p(b[h], c, d, e);
- this.i(a, j, i, m, l, this.e.line.color(b[h - 1]), this.e.line.width)
- }
- this.M(a, j, i, b[h - 1], !! this.e.offset);
- f.addRect(b[h - 1].id, [j - g, i - g, j + g, i + g]);
- i = l;
- j = m
- }
- },
- M: function (a, b, c, d, e) {
- var f = parseInt(this.e.item.radius, 10);
- a.lineWidth = parseInt(this.e.item.borderWidth, 10);
- a.fillStyle = this.e.item.color(d);
- a.strokeStyle = this.e.item.borderColor(d);
- a.beginPath();
- a.arc(b, c, f, 0, Math.PI * 2, true);
- a.fill();
- a.stroke();
- e && this.renderTextAt(false, true, b, c - f - this.e.labelOffset, this.e.label(d))
- },
- p: function (a, b, c, d) {
- var e = d.minValue,
- f = d.maxValue,
- g = d.unit,
- i = d.valueFactor;
- a = this.e.value(a);
- i = (parseFloat(a) - e) * i;
- this.e.yAxis || (i += d.startValue / g);
- d = c.y - Math.floor(g * i);
- if (i < 0) d = c.y;
- if (a > f) d = b.y;
- if (a < e) d = c.y;
- return d
- },
- C: function (a, b, c, d, e) {
- var f = {};
- f.totalHeight = d.y - c.y;
- f.cellWidth = Math.round((d.x - c.x) / (!this.e.offset ? b.length - 1 : b.length));
- var g = !! this.e.yAxis,
- i = this.e.view.indexOf("stacked") != -1 ? this.Q(b) : this.O();
- f.maxValue = i.max;
- f.minValue = i.min;
- e || this.N(a, b, c, d, f.minValue, f.maxValue, f.cellWidth);
- if (g) {
- f.maxValue = parseFloat(this.e.yAxis.end);
- f.minValue = parseFloat(this.e.yAxis.start)
- }
- b = this.z(f.minValue, f.maxValue);
- a = b[0];
- f.valueFactor = b[1];
- f.unit = a ? f.totalHeight / a : 10;
- f.startValue = 0;
- if (!g) {
- f.startValue = f.unit > 10 ? f.unit : 10;
- f.unit = a ? (f.totalHeight - f.startValue) / a : 10
- }
- return f
- }
-};
-dhtmlx.chart.bar = {
- pvt_render_bar: function (a, b, c, d, e, f) {
- var g, i, j, k, h = d.y - c.y,
- m = !! this.e.yAxis,
- l = !! this.e.xAxis;
- i = this.O();
- g = i.max;
- i = i.min;
- var n = Math.floor((d.x - c.x) / b.length);
- !e && !(this.e.origin != "auto" && !m) && this.N(a, b, c, d, i, g, n);
- if (m) {
- g = parseFloat(this.e.yAxis.end);
- i = parseFloat(this.e.yAxis.start)
- }
- j = this.z(i, g);
- k = j[0];
- j = j[1];
- var o = k ? h / k : k;
- if (!m && !(this.e.origin != "auto" && l)) {
- var s = 10;
- o = k ? (h - s) / k : s
- }!e && this.e.origin != "auto" && !m && this.e.origin > i && this.da(a, b, c, d, n, d.y - o * (this.e.origin - i));
- h = parseInt(this.e.width, 10);
- if (this.h && h * this.h.length + 4 > n) h = n / this.h.length - 4;
- k = Math.floor((n - h * this.h.length) / 2);
- var t = typeof this.e.radius != "undefined" ? parseInt(this.e.radius, 10) : Math.round(h / 5),
- q = false,
- v = this.e.gradient;
- if (v && typeof v != "function") {
- q = v;
- v = false
- } else if (v) {
- v = a.createLinearGradient(0, d.y, 0, c.y);
- this.e.gradient(v)
- }
- l || this.i(a, c.x, d.y + 0.5, d.x, d.y + 0.5, "#000000", 1);
- for (var p = 0; p < b.length; p++) {
- var u = parseFloat(this.e.value(b[p]));
- if (u > g) u = g;
- u -= i;
- u *= j;
- var r = c.x + k + p * n + (h + 1) * e,
- w = d.y;
- if (u < 0 || this.e.yAxis && u === 0 && !(this.e.origin != "auto" && this.e.origin > i)) this.renderTextAt(true, true, r + Math.floor(h / 2), w, this.e.label(b[p]));
- else {
- if (!m && !(this.e.origin != "auto" && l)) u += s / o;
- var x = v || this.e.color.call(this, b[p]);
- this.e.border && this.va(a, r, w, h, i, t, o, u, x);
- a.globalAlpha = this.e.alpha.call(this, b[p]);
- var y = this.ua(a, c, r, w, h, i, t, o, u, x, v, q);
- a.globalAlpha = 1;
- q && this.wa(a, r, w, h, i, t, o, u, x, q);
- y[0] != r ? this.renderTextAt(false, true, r + Math.floor(h / 2), y[1], this.e.label(b[p])) : this.renderTextAt(true, true, r + Math.floor(h / 2), y[3], this.e.label(b[p]));
- f.addRect(b[p].id, [r, y[3], y[2], y[1]], e)
- }
- }
- },
- K: function (a, b, c, d, e, f, g) {
- var i = this.e.xAxis,
- j = c;
- if (i && this.e.origin != "auto" && this.e.origin > g) {
- c -= (this.e.origin - g) * e;
- j = c;
- d -= this.e.origin - g;
- if (d < 0) {
- d *= -1;
- a.translate(b + f, c);
- a.rotate(Math.PI);
- c = b = 0
- }
- c -= 0.5
- }
- return {
- value: d,
- x0: b,
- y0: c,
- start: j
- }
- },
- ua: function (a, b, c, d, e, f, g, i, j, k, h, m) {
- a.save();
- a.fillStyle = k;
- var l = this.K(a, c, d, j, i, e, f);
- e = this.H(a, l.x0, l.y0, e, g, i, l.value, this.e.border ? 1 : 0);
- if (h && !m) a.lineTo(l.x0 + (this.e.border ? 1 : 0), b.y);
- a.fill();
- a.restore();
- a = l.x0;
- b = l.x0 != c ? c + e[0] : e[0];
- d = l.x0 != c ? l.start - e[1] : d;
- c = l.x0 != c ? l.start : e[1];
- return [a, d, b, c]
- },
- va: function (a, b, c, d, e, f, g, i, j) {
- a.save();
- b = this.K(a, b, c, i, g, d, e);
- a.fillStyle = j;
- this.H(a, b.x0, b.y0, d, f, g, b.value, 0);
- a.lineTo(b.x0, 0);
- a.fill();
- a.fillStyle = "#000000";
- a.globalAlpha = 0.37;
- this.H(a, b.x0, b.y0, d, f, g, b.value, 0);
- a.fill();
- a.restore()
- },
- wa: function (a, b, c, d, e, f, g, i, j, k) {
- a.save();
- b = this.K(a, b, c, i, g, d, e);
- j = this.G(a, b.x0, b.y0, b.x0 + d, b.y0 - g * b.value + 2, k, j, "y");
- a.fillStyle = j.gradient;
- this.H(a, b.x0 + j.offset, b.y0, d - j.offset * 2, f, g, b.value, j.offset);
- a.fill();
- a.restore()
- },
- H: function (a, b, c, d, e, f, g, i) {
- a.beginPath();
- var j = 0;
- if (e > f * g) {
- var k = (e - f * g) / e;
- j = -Math.acos(k) + Math.PI / 2
- }
- a.moveTo(b + i, c);
- var h = c - Math.floor(f * g) + e + (e ? 0 : i);
- e < f * g && a.lineTo(b + i, h);
- f = b + e;
- e && a.arc(f, h, e - i, -Math.PI + j, -Math.PI / 2, false);
- g = b + d - e - (e ? 0 : i);
- f = h - e + (e ? i : 0);
- a.lineTo(g, f);
- h = h;
- e && a.arc(g, h, e - i, -Math.PI / 2, 0 - j, false);
- d = b + d - i;
- a.lineTo(d, c);
- a.lineTo(b + i, c);
- return [d, f]
- }
-};
-dhtmlx.chart.pie = {
- pvt_render_pie: function (a, b, c, d, e, f) {
- this.na(a, b, c, d, 1, f)
- },
- na: function (a, b, c, d, e, f) {
- var g = 0,
- i = this.Ga(c, d);
- c = this.e.radius ? this.e.radius : i.radius;
- this.max(this.e.value);
- for (var j = [], k = [], h = 0, m = 0; m < b.length; m++) g += parseFloat(this.e.value(b[m]));
- for (m = 0; m < b.length; m++) {
- k[m] = parseFloat(this.e.value(b[m]));
- j[m] = Math.PI * 2 * (g ? (k[m] + h) / g : 1 / b.length);
- h += k[m]
- }
- d = this.e.x ? this.e.x : i.x;
- var l = this.e.y ? this.e.y : i.y;
- e == 1 && this.e.shadow && this.sa(a, d, l, c);
- l /= e;
- var n = -Math.PI / 2;
- a.scale(1, e);
- for (m = 0; m < b.length; m++) if (k[m]) {
- a.lineWidth = 2;
- a.beginPath();
- a.moveTo(d, l);
- alpha1 = -Math.PI / 2 + j[m] - 1.0E-4;
- a.arc(d, l, c, n, alpha1, false);
- a.lineTo(d, l);
- var o = this.e.color.call(this, b[m]);
- a.fillStyle = o;
- a.strokeStyle = this.e.lineColor(b[m]);
- a.stroke();
- a.fill();
- this.e.pieInnerText && this.ba(d, l, 5 * c / 6, n, alpha1, e, this.e.pieInnerText(b[m], g), true);
- this.e.label && this.ba(d, l, c + this.e.labelOffset, n, alpha1, e, this.e.label(b[m]));
- if (e != 1) {
- this.W(a, d, l, n, alpha1, c, true);
- a.fillStyle = "#000000";
- a.globalAlpha = 0.2;
- this.W(a, d, l, n, alpha1, c, false);
- a.globalAlpha = 1;
- a.fillStyle = o
- }
- f.addSector(b[m].id, n, alpha1, d, l, c, e);
- n = alpha1
- }
- if (this.e.gradient) {
- b = e != 1 ? d + c / 3 : d;
- f = e != 1 ? l + c / 3 : l;
- this.bb(a, d, l, c, b, f)
- }
- a.scale(1, 1 / e)
- },
- Ga: function (a, b) {
- var c = b.x - a.x,
- d = b.y - a.y;
- b = a.x + c / 2;
- a = a.y + d / 2;
- var e = Math.min(c / 2, d / 2);
- return {
- x: b,
- y: a,
- radius: e
- }
- },
- W: function (a, b, c, d, e, f, g) {
- a.lineWidth = 1;
- if (d <= 0 && e >= 0 || d >= 0 && e <= Math.PI || d <= Math.PI && e >= Math.PI) {
- if (d <= 0 && e >= 0) {
- d = 0;
- g = false;
- this.ca(a, b, c, f, d, e)
- }
- if (d <= Math.PI && e >= Math.PI) {
- e = Math.PI;
- g = false;
- this.ca(a, b, c, f, d, e)
- }
- var i = (this.e.height || Math.floor(f / 4)) / this.e.cant;
- a.beginPath();
- a.arc(b, c, f, d, e, false);
- a.lineTo(b + f * Math.cos(e), c + f * Math.sin(e) + i);
- a.arc(b, c + i, f, e, d, true);
- a.lineTo(b + f * Math.cos(d), c + f * Math.sin(d));
- a.fill();
- g && a.stroke()
- }
- },
- ca: function (a, b, c, d, e, f) {
- a.beginPath();
- a.arc(b, c, d, e, f, false);
- a.stroke()
- },
- sa: function (a, b, c, d) {
- for (var e = ["#676767", "#7b7b7b", "#a0a0a0", "#bcbcbc", "#d1d1d1", "#d6d6d6"], f = e.length - 1; f > -1; f--) {
- a.beginPath();
- a.fillStyle = e[f];
- a.arc(b + 2, c + 2, d + f, 0, Math.PI * 2, true);
- a.fill()
- }
- },
- Fa: function (a) {
- a.addColorStop(0, "#ffffff");
- a.addColorStop(0.7, "#7a7a7a");
- a.addColorStop(1, "#000000");
- return a
- },
- bb: function (a, b, c, d, e, f) {
- a.globalAlpha = 0.3;
- a.beginPath();
- var g;
- if (typeof this.e.gradient != "function") {
- g = a.createRadialGradient(e, f, d / 4, b, c, d);
- g = this.Fa(g)
- } else g = this.e.gradient(g);
- a.fillStyle = g;
- a.arc(b, c, d, 0, Math.PI * 2, true);
- a.fill();
- a.globalAlpha = 1
- },
- ba: function (a, b, c, d, e, f, g, i) {
- var j = this.renderText(0, 0, g, 0, 1);
- if (j) {
- var k = j.scrollWidth;
- j.style.width = k + "px";
- if (k > a) k = a;
- var h = 8;
- if (i) h = k / 1.8;
- var m = d + (e - d) / 2;
- c -= (h - 8) / 2;
- var l = -h,
- n = -8,
- o = "left";
- if (m >= Math.PI / 2 && m < Math.PI) {
- l = -k - l + 1;
- o = "right"
- }
- if (m <= 3 * Math.PI / 2 && m >= Math.PI) {
- l = -k - l + 1;
- o = "right"
- }
- d = (b + Math.floor(c * Math.sin(m))) * f + n;
- h = a + Math.floor((c + h / 2) * Math.cos(m)) + l;
- var s = e < Math.PI / 2 + 0.01,
- t = m < Math.PI / 2;
- if (t && s) h = Math.max(h, a + 3);
- else if (!t && !s) h = Math.min(h, a - k);
- if (!i && f < 1 && d > b * f) d += this.e.height || Math.floor(c / 4);
- j.style.top = d + "px";
- j.style.left = h + "px";
- j.style.width = k + "px";
- j.style.textAlign = o;
- j.style.whiteSpace = "nowrap"
- }
- }
-};
-dhtmlx.chart.pie3D = {
- pvt_render_pie3D: function (a, b, c, d, e, f) {
- this.na(a, b, c, d, this.e.cant, f)
- }
-};
-dhtmlx.Template = {
- J: {},
- empty: function () {
- return ""
- },
- setter: function (a, b) {
- return dhtmlx.Template.fromHTML(b)
- },
- obj_setter: function (a, b) {
- var c = dhtmlx.Template.setter(a, b),
- d = this;
- return function () {
- return c.apply(d, arguments)
- }
- },
- fromHTML: function (a) {
- if (typeof a == "function") return a;
- if (this.J[a]) return this.J[a];
- a = (a || "").toString();
- a = a.replace(/[\r\n]+/g, "\\n");
- a = a.replace(/\{obj\.([^}?]+)\?([^:]*):([^}]*)\}/g, '"+(obj.$1?"$2":"$3")+"');
- a = a.replace(/\{common\.([^}\(]*)\}/g, '"+common.$1+"');
- a = a.replace(/\{common\.([^\}\(]*)\(\)\}/g, '"+(common.$1?common.$1(obj):"")+"');
- a = a.replace(/\{obj\.([^}]*)\}/g, '"+obj.$1+"');
- a = a.replace(/#([a-z0-9_]+)#/gi, '"+obj.$1+"');
- a = a.replace(/\{obj\}/g, '"+obj+"');
- a = a.replace(/\{-obj/g, "{obj");
- a = a.replace(/\{-common/g, "{common");
- a = 'return "' + a + '";';
- return this.J[a] = Function("obj", "common", a)
- }
-};
-dhtmlx.Type = {
- add: function (a, b) {
- if (!a.types && a.prototype.types) a = a.prototype;
- var c = b.name || "default";
- this.T(b);
- this.T(b, "edit");
- this.T(b, "loading");
- a.types[c] = dhtmlx.extend(dhtmlx.extend({}, a.types[c] || this.ta), b);
- return c
- },
- ta: {
- css: "default",
- template: function () {
- return ""
- },
- template_edit: function () {
- return ""
- },
- template_loading: function () {
- return "..."
- },
- width: 150,
- height: 80,
- margin: 5,
- padding: 0
- },
- T: function (a, b) {
- b = "template" + (b ? "_" + b : "");
- var c = a[b];
- if (c && typeof c == "string") {
- if (c.indexOf("->") != -1) {
- c = c.split("->");
- switch (c[0]) {
- case "html":
- c = dhtmlx.html.getValue(c[1]).replace(/\"/g, '\\"');
- break;
- case "http":
- c = (new dhtmlx.ajax).sync().get(c[1], {
- uid: (new Date).valueOf()
- }).responseText;
- break;
- default:
- break
- }
- }
- a[b] = dhtmlx.Template.fromHTML(c)
- }
- }
-};
-dhtmlx.SingleRender = {
- k: function () {},
- eb: function (a) {
- return this.type.Oa(a, this.type) + this.type.template(a, this.type) + this.type.Na
- },
- render: function () {
- if (!this.callEvent || this.callEvent("onBeforeRender", [this.data])) {
- if (this.data) this.L.innerHTML = this.eb(this.data);
- this.callEvent && this.callEvent("onAfterRender", [])
- }
- }
-};
-dhtmlx.ui.Tooltip = function (a) {
- this.name = "Tooltip";
- this.version = "3.0";
- if (typeof a == "string") a = {
- template: a
- };
- dhtmlx.extend(this, dhtmlx.Settings);
- dhtmlx.extend(this, dhtmlx.SingleRender);
- this.B(a, {
- type: "default",
- dy: 0,
- dx: 20
- });
- this.L = this.g = document.createElement("DIV");
- this.g.className = "dhx_tooltip";
- dhtmlx.html.insertBefore(this.g, document.body.firstChild)
-};
-dhtmlx.ui.Tooltip.prototype = {
- show: function (a, b) {
- if (!this.Z) {
- if (this.data != a) {
- this.data = a;
- this.render(a)
- }
- this.g.style.top = b.y + this.e.dy + "px";
- this.g.style.left = b.x + this.e.dx + "px";
- this.g.style.display = "block"
- }
- },
- hide: function () {
- this.data = null;
- this.g.style.display = "none"
- },
- disable: function () {
- this.Z = true
- },
- enable: function () {
- this.Z = false
- },
- types: {
- "default": dhtmlx.Template.fromHTML("{obj.id}")
- },
- template_item_start: dhtmlx.Template.empty,
- template_item_end: dhtmlx.Template.empty
-};
-dhtmlx.AutoTooltip = {
- tooltip_setter: function (a, b) {
- var c = new dhtmlx.ui.Tooltip(b);
- this.attachEvent("onMouseMove", function (d, e) {
- c.show(this.get(d), dhtmlx.html.pos(e))
- });
- this.attachEvent("onMouseOut", function () {
- c.hide()
- });
- this.attachEvent("onMouseMoving", function () {
- c.hide()
- });
- return c
- }
-};
-dhtmlx.DataStore = function () {
- this.name = "DataStore";
- dhtmlx.extend(this, dhtmlx.EventSystem);
- this.setDriver("xml");
- this.pull = {};
- this.order = dhtmlx.toArray();
- this.gb = false
-};
-dhtmlx.DataStore.prototype = {
- setDriver: function (a) {
- this.driver = dhtmlx.DataDriver[a]
- },
- qa: function (a) {
- if (a.item) {
- if (!(a.item instanceof Array)) a.item = [a.item];
- for (var b = 0; b < a.item.length; b++) {
- var c = a.item[b],
- d = this.id(c);
- a.item[b] = d;
- this.pull[d] = c;
- c.parent = a.id;
- c.level = a.level + 1;
- this.qa(c)
- }
- }
- },
- Wa: function (a) {
- for (var b = this.driver.getInfo(a), c = this.driver.getRecords(a), d = (b.u || 0) * 1, e = 0, f = 0; f < c.length; f++) {
- var g = this.driver.getDetails(c[f]),
- i = this.id(g);
- if (!this.pull[i]) {
- this.order[e + d] = i;
- e++
- }
- this.pull[i] = g;
- if (this.gb) {
- g.level = 1;
- this.qa(g)
- }
- }
- for (f = 0; f < b.w; f++) if (!this.order[f]) {
- i = dhtmlx.uid();
- g = {
- id: i,
- $template: "loading"
- };
- this.pull[i] = g;
- this.order[f] = i
- }
- this.callEvent("onStoreLoad", [this.driver, a]);
- this.refresh()
- },
- id: function (a) {
- return a.id || (a.id = dhtmlx.uid())
- },
- get: function (a) {
- return this.pull[a]
- },
- set: function (a, b) {
- this.pull[a] = b;
- this.refresh()
- },
- refresh: function (a) {
- a ? this.callEvent("onStoreUpdated", [a, this.pull[a], "update"]) : this.callEvent("onStoreUpdated", [null, null, null])
- },
- getRange: function (a, b) {
- if (arguments.length) {
- a = this.indexById(a);
- b = this.indexById(b);
- if (a > b) {
- var c = b;
- b = a;
- a = c
- }
- } else {
- a = this.min || 0;
- b = Math.min(this.max || Infinity, this.dataCount() - 1)
- }
- return this.getIndexRange(a, b)
- },
- getIndexRange: function (a, b) {
- b = Math.min(b, this.dataCount() - 1);
- var c = dhtmlx.toArray();
- for (a = a; a <= b; a++) c.push(this.get(this.order[a]));
- return c
- },
- dataCount: function () {
- return this.order.length
- },
- exists: function (a) {
- return !!this.pull[a]
- },
- move: function (a, b) {
- if (!(a < 0 || b < 0)) {
- var c = this.idByIndex(a),
- d = this.get(c);
- this.order.removeAt(a);
- this.order.insertAt(c, Math.min(this.order.length, b));
- this.callEvent("onStoreUpdated", [c, d, "move"])
- }
- },
- add: function (a, b) {
- var c = this.id(a),
- d = this.dataCount();
- if (dhtmlx.isNotDefined(b) || b < 0) b = d;
- if (b > d) b = Math.min(this.order.length, b);
- if (this.callEvent("onbeforeAdd", [c, b])) {
- if (this.exists(c)) return null;
- this.pull[c] = a;
- this.order.insertAt(c, b);
- if (this.m) {
- var e = this.m.length;
- if (!b && this.order.length) e = 0;
- this.m.insertAt(c, e)
- }
- this.callEvent("onafterAdd", [c, b]);
- this.callEvent("onStoreUpdated", [c, a, "add"]);
- return c
- }
- },
- remove: function (a) {
- if (a instanceof
- Array) for (var b = 0; b < a.length; b++) this.remove(a[b]);
- else if (this.callEvent("onbeforedelete", [a])) {
- if (!this.exists(a)) return null;
- b = this.get(a);
- this.order.remove(a);
- this.m && this.m.remove(a);
- delete this.pull[a];
- this.callEvent("onafterdelete", [a]);
- this.callEvent("onStoreUpdated", [a, b, "delete"])
- }
- },
- clearAll: function () {
- this.pull = {};
- this.order = dhtmlx.toArray();
- this.m = null;
- this.callEvent("onClearAll", []);
- this.refresh()
- },
- idByIndex: function (a) {
- return this.order[a]
- },
- indexById: function (a) {
- return a = this.order.find(a)
- },
- next: function (a, b) {
- return this.order[this.indexById(a) + (b || 1)]
- },
- first: function () {
- return this.order[0]
- },
- last: function () {
- return this.order[this.order.length - 1]
- },
- previous: function (a, b) {
- return this.order[this.indexById(a) - (b || 1)]
- },
- sort: function (a, b, c) {
- var d = a;
- if (typeof a == "function") d = {
- as: a,
- dir: b
- };
- else if (typeof a == "string") d = {
- by: a,
- dir: b,
- as: c
- };
- var e = [d.by, d.dir, d.as];
- if (this.callEvent("onbeforesort", e)) {
- if (this.order.length) {
- var f = dhtmlx.sort.create(d),
- g = this.getRange(this.first(), this.last());
- g.sort(f);
- this.order = g.map(function (i) {
- return this.id(i)
- }, this)
- }
- this.refresh();
- this.callEvent("onaftersort", e)
- }
- },
- filter: function (a, b) {
- if (this.m) {
- this.order = this.m;
- delete this.m
- }
- if (a) {
- var c = a;
- if (typeof a == "string") {
- a = dhtmlx.Template.setter(0, a);
- c = function (e, f) {
- return a(e).toLowerCase().indexOf(f) != -1
- }
- }
- b = (b || "").toString().toLowerCase();
- var d = dhtmlx.toArray();
- this.order.each(function (e) {
- c(this.get(e), b) && d.push(e)
- }, this);
- this.m = this.order;
- this.order = d
- }
- this.refresh()
- },
- each: function (a, b) {
- for (var c = 0; c < this.order.length; c++) a.call(b || this, this.get(this.order[c]))
- },
- provideApi: function (a, b) {
- b && this.mapEvent({
- onbeforesort: a,
- onaftersort: a,
- onbeforeadd: a,
- onafteradd: a,
- onbeforedelete: a,
- onafterdelete: a
- });
- for (var c = ["sort", "add", "remove", "exists", "idByIndex", "indexById", "get", "set", "refresh", "dataCount", "filter", "next", "previous", "clearAll", "first", "last"], d = 0; d < c.length; d++) a[c[d]] = dhtmlx.methodPush(this, c[d])
- }
-};
-dhtmlx.sort = {
- create: function (a) {
- return dhtmlx.sort.dir(a.dir, dhtmlx.sort.by(a.by, a.as))
- },
- as: {
- "int": function (a, b) {
- a *= 1;
- b *= 1;
- return a > b ? 1 : a < b ? -1 : 0
- },
- string_strict: function (a, b) {
- a = a.toString();
- b = b.toString();
- return a > b ? 1 : a < b ? -1 : 0
- },
- string: function (a, b) {
- a = a.toString().toLowerCase();
- b = b.toString().toLowerCase();
- return a > b ? 1 : a < b ? -1 : 0
- }
- },
- by: function (a, b) {
- if (typeof b != "function") b = dhtmlx.sort.as[b || "string"];
- a = dhtmlx.Template.setter(0, a);
- return function (c, d) {
- return b(a(c), a(d))
- }
- },
- dir: function (a, b) {
- if (a == "asc") return b;
- return function (c, d) {
- return b(c, d) * -1
- }
- }
-};
-dhtmlx.Group = {
- k: function () {
- this.data.attachEvent("onStoreLoad", dhtmlx.bind(function () {
- this.e.group && this.group(this.e.group, false)
- }, this));
- this.attachEvent("onBeforeRender", dhtmlx.bind(function (a) {
- if (this.e.sort) {
- a.block();
- a.sort(this.e.sort);
- a.unblock()
- }
- }, this));
- this.attachEvent("onBeforeSort", dhtmlx.bind(function () {
- this.e.sort = null
- }, this))
- },
- Ja: function (a, b) {
- a.attachEvent("onClearAll", dhtmlx.bind(function () {
- this.ungroup(false)
- }, b))
- },
- sum: function (a, b) {
- a = dhtmlx.Template.setter(0, a);
- b = b || this.data;
- var c = 0;
- b.each(function (d) {
- c += a(d) * 1
- });
- return c
- },
- min: function (a, b) {
- a = dhtmlx.Template.setter(0, a);
- b = b || this.data;
- var c = Infinity;
- b.each(function (d) {
- if (a(d) * 1 < c) c = a(d) * 1
- });
- return c * 1
- },
- max: function (a, b) {
- a = dhtmlx.Template.setter(0, a);
- b = b || this.data;
- var c = -Infinity;
- b.each(function (d) {
- if (a(d) * 1 > c) c = a(d) * 1
- });
- return c
- },
- cb: function (a) {
- var b = function (j, k) {
- j = dhtmlx.Template.setter(0, j);
- return j(k[0])
- },
- c = dhtmlx.Template.setter(0, a.by);
- a.map[c] || (a.map[c] = [c, b]);
- var d = {},
- e = [];
- this.data.each(function (j) {
- var k = c(j);
- if (!d[k]) {
- e.push({
- id: k
- });
- d[k] = dhtmlx.toArray()
- }
- d[k].push(j)
- });
- for (var f in a.map) {
- var g = a.map[f][1] || b;
- if (typeof g != "function") g = this[g];
- for (var i = 0; i < e.length; i++) e[i][f] = g.call(this, a.map[f][0], d[e[i].id])
- }
- this.ja = this.data;
- this.data = new dhtmlx.DataStore;
- this.data.provideApi(this, true);
- this.Ja(this.data, this);
- this.parse(e, "json")
- },
- group: function (a, b) {
- this.ungroup(false);
- this.cb(a);
- b !== false && this.render()
- },
- ungroup: function (a) {
- if (this.ja) {
- this.data = this.ja;
- this.data.provideApi(this, true)
- }
- a !== false && this.render()
- },
- group_setter: function (a, b) {
- return b
- },
- sort_setter: function (a, b) {
- if (typeof b != "object") b = {
- by: b
- };
- this.n(b, {
- as: "string",
- dir: "asc"
- });
- return b
- }
-};
-dhtmlx.KeyEvents = {
- k: function () {
- dhtmlx.event(this.g, "keypress", this.Ta, this)
- },
- Ta: function (a) {
- a = a || event;
- var b = a.which || a.keyCode;
- this.callEvent(this.hb ? "onEditKeyPress" : "onKeyPress", [b, a.ctrlKey, a.shiftKey, a])
- }
-};
-dhtmlx.MouseEvents = {
- k: function () {
- if (this.on_click) {
- dhtmlx.event(this.g, "click", this.Qa, this);
- dhtmlx.event(this.g, "contextmenu", this.Ra, this)
- }
- this.on_dblclick && dhtmlx.event(this.g, "dblclick", this.Sa, this);
- if (this.on_mouse_move) {
- dhtmlx.event(this.g, "mousemove", this.la, this);
- dhtmlx.event(this.g, dhtmlx.r ? "mouseleave" : "mouseout", this.la, this)
- }
- },
- Qa: function (a) {
- return this.R(a, this.on_click, "ItemClick")
- },
- Sa: function (a) {
- return this.R(a, this.on_dblclick, "ItemDblClick")
- },
- Ra: function (a) {
- var b = dhtmlx.html.locate(a, this.q);
- if (b && !this.callEvent("onBeforeContextMenu", [b, a])) return dhtmlx.html.preventEvent(a)
- },
- la: function (a) {
- if (dhtmlx.r) a = document.createEventObject(event);
- this.ia && window.clearTimeout(this.ia);
- this.callEvent("onMouseMoving", [a]);
- this.ia = window.setTimeout(dhtmlx.bind(function () {
- a.type == "mousemove" ? this.Ua(a) : this.Va(a)
- }, this), 500)
- },
- Ua: function (a) {
- this.R(a, this.on_mouse_move, "MouseMove") || this.callEvent("onMouseOut", [a || event])
- },
- Va: function (a) {
- this.callEvent("onMouseOut", [a || event])
- },
- R: function (a, b, c) {
- a = a || event;
- for (var d = a.target || a.srcElement, e = "", f = null, g = false; d && d.parentNode;) {
- if (!g && d.getAttribute) if (f = d.getAttribute(this.q)) {
- d.getAttribute("userdata") && this.callEvent("onLocateData", [f, d]);
- if (!this.callEvent("on" + c, [f, a, d])) return;
- g = true
- }
- if (e = d.className) {
- e = e.split(" ");
- e = e[0] || e[1];
- if (b[e]) return b[e].call(this, a, f, d)
- }
- d = d.parentNode
- }
- return g
- }
-};
-dhtmlx.Settings = {
- k: function () {
- this.e = this.config = {}
- },
- define: function (a, b) {
- if (typeof a == "object") return this.ma(a);
- return this.Y(a, b)
- },
- Y: function (a, b) {
- var c = this[a + "_setter"];
- return this.e[a] = c ? c.call(this, a, b) : b
- },
- ma: function (a) {
- if (a) for (var b in a) this.Y(b, a[b])
- },
- B: function (a, b) {
- var c = dhtmlx.extend({}, b);
- typeof a == "object" && !a.tagName && dhtmlx.extend(c, a);
- this.ma(c)
- },
- n: function (a, b) {
- for (var c in b) switch (typeof a[c]) {
- case "object":
- a[c] = this.n(a[c] || {}, b[c]);
- break;
- case "undefined":
- a[c] = b[c];
- break;
- default:
- break
- }
- return a
- },
- Xa: function (a, b, c) {
- if (typeof a == "object" && !a.tagName) a = a.container;
- this.g = dhtmlx.toNode(a);
- if (!this.g && c) this.g = c(a);
- this.g.className += " " + b;
- this.g.onselectstart = function () {
- return false
- };
- this.L = this.g
- },
- ab: function (a) {
- if (typeof a == "object") return this.type_setter("type", a);
- this.type = dhtmlx.extend({}, this.types[a]);
- this.customize()
- },
- customize: function (a) {
- a && dhtmlx.extend(this.type, a);
- this.type.Oa = dhtmlx.Template.fromHTML(this.template_item_start(this.type));
- this.type.Na = this.template_item_end(this.type);
- this.render()
- },
- type_setter: function (a, b) {
- this.ab(typeof b == "object" ? dhtmlx.Type.add(this, b) : b);
- return b
- },
- template_setter: function (a, b) {
- return this.type_setter("type", {
- template: b
- })
- },
- css_setter: function (a, b) {
- this.g.className += " " + b;
- return b
- }
-};
-dhtmlx.compat = function (a, b) {
- dhtmlx.compat[a] && dhtmlx.compat[a](b)
-};
-(function () {
- if (!window.dhtmlxError) {
- var a = function () {};
- window.dhtmlxError = {
- catchError: a,
- throwError: a
- };
- window.convertStringToBoolean = function (c) {
- return !!c
- };
- window.dhtmlxEventable = function (c) {
- dhtmlx.extend(c, dhtmlx.EventSystem)
- };
- var b = {
- getXMLTopNode: function () {},
- doXPath: function (c) {
- return dhtmlx.DataDriver.xml.xpath(this.xml, c)
- },
- xmlDoc: {
- responseXML: true
- }
- };
- dhtmlx.compat.dataProcessor = function (c) {
- var d = "_sendData",
- e = "_in_progress",
- f = "_tMode",
- g = "_waitMode";
- c[d] = function (i, j) {
- if (i) {
- if (j) this[e][j] = (new Date).valueOf();
- if (!this.callEvent("onBeforeDataSending", j ? [j, this.getState(j)] : [])) return false;
- var k = this,
- h = this.serverProcessor;
- this[f] != "POST" ? dhtmlx.ajax().get(h + (h.indexOf("?") != -1 ? "&" : "?") + this.serialize(i, j), "", function (m, l) {
- b.xml = dhtmlx.DataDriver.xml.checkResponse(m, l);
- k.afterUpdate(k, null, null, null, b)
- }) : dhtmlx.ajax().post(h, this.serialize(i, j), function (m, l) {
- b.xml = dhtmlx.DataDriver.xml.checkResponse(m, l);
- k.afterUpdate(k, null, null, null, b)
- });
- this[g]++
- }
- }
- }
- }
-})();
-if (!dhtmlx.attaches) dhtmlx.attaches = {};
-dhtmlx.attaches.attachAbstract = function (a, b) {
- var c = document.createElement("DIV");
- c.id = "CustomObject_" + dhtmlx.uid();
- c.style.width = "100%";
- c.style.height = "100%";
- c.cmp = "grid";
- document.body.appendChild(c);
- this.attachObject(c.id);
- b.container = c.id;
- var d = this.vs[this.av];
- d.grid = new window[a](b);
- d.gridId = c.id;
- d.gridObj = c;
- d.grid.setSizes = function () {
- this.resize ? this.resize() : this.render()
- };
- var e = "_viewRestore";
- return this.vs[this[e]()].grid
-};
-dhtmlx.attaches.attachDataView = function (a) {
- return this.attachAbstract("dhtmlXDataView", a)
-};
-dhtmlx.attaches.attachChart = function (a) {
- return this.attachAbstract("dhtmlXChart", a)
-};
-dhtmlx.compat.layout = function () {};
-dhtmlx.ajax = function (a, b, c) {
- if (arguments.length !== 0) {
- var d = new dhtmlx.ajax;
- if (c) d.master = c;
- d.get(a, null, b)
- }
- if (!this.getXHR) return new dhtmlx.ajax;
- return this
-};
-dhtmlx.ajax.prototype = {
- getXHR: function () {
- return dhtmlx.r ? new ActiveXObject("Microsoft.xmlHTTP") : new XMLHttpRequest
- },
- send: function (a, b, c) {
- var d = this.getXHR();
- if (typeof c == "function") c = [c];
- if (typeof b == "object") {
- var e = [];
- for (var f in b) e.push(f + "=" + encodeURIComponent(b[f]));
- b = e.join("&")
- }
- if (b && !this.post) {
- a = a + (a.indexOf("?") != -1 ? "&" : "?") + b;
- b = null
- }
- d.open(this.post ? "POST" : "GET", a, !this.pa);
- this.post && d.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
- if (!this.pa) {
- var g = this;
- d.onreadystatechange = function () {
- if (!d.readyState || d.readyState == 4) {
- if (c && g) for (var i = 0; i < c.length; i++) if (c[i]) c[i].call(g.master || g, d.responseText, d.responseXML, d);
- c = d = g = g.master = null
- }
- }
- }
- d.send(b || null);
- return d
- },
- get: function (a, b, c) {
- this.post = false;
- return this.send(a, b, c)
- },
- post: function (a, b, c) {
- this.post = true;
- return this.send(a, b, c)
- },
- sync: function () {
- this.pa = true;
- return this
- }
-};
-dhtmlx.DataLoader = {
- k: function () {
- this.data = new dhtmlx.DataStore
- },
- load: function (a, b, c) {
- this.callEvent("onXLS", []);
- if (typeof b == "string") {
- this.data.setDriver(b);
- b = c
- }
- if (!this.data.feed) this.data.feed = function (d, e) {
- if (this.F) return this.F = [d, e];
- else this.F = true;
- this.load(a + (a.indexOf("?") == -1 ? "?" : "&") + "posStart=" + d + "&count=" + e, function () {
- var f = this.F;
- this.F = false;
- typeof f == "object" && this.data.feed.apply(this, f)
- })
- };
- dhtmlx.ajax(a, [this.ka, b], this)
- },
- parse: function (a, b) {
- this.callEvent("onXLS", []);
- b && this.data.setDriver(b);
- this.ka(a, null)
- },
- ka: function (a, b) {
- this.data.Wa(this.data.driver.toObject(a, b));
- this.callEvent("onXLE", [])
- }
-};
-dhtmlx.DataDriver = {};
-dhtmlx.DataDriver.json = {
- toObject: function (a) {
- if (typeof a == "string") {
- eval("dhtmlx.temp=" + a);
- return dhtmlx.temp
- }
- return a
- },
- getRecords: function (a) {
- if (a && !(a instanceof Array)) return [a];
- return a
- },
- getDetails: function (a) {
- return a
- },
- getInfo: function (a) {
- return {
- w: a.total_count || 0,
- u: a.pos || 0
- }
- }
-};
-dhtmlx.DataDriver.html = {
- toObject: function (a) {
- if (typeof a == "string") {
- var b = null;
- if (a.indexOf("<") == -1) b = dhtmlx.toNode(a);
- if (!b) {
- b = document.createElement("DIV");
- b.innerHTML = a
- }
- return b.getElementsByTagName(this.tag)
- }
- return a
- },
- getRecords: function (a) {
- if (a.tagName) return a.childNodes;
- return a
- },
- getDetails: function (a) {
- return dhtmlx.DataDriver.xml.tagToObject(a)
- },
- getInfo: function () {
- return {
- w: 0,
- u: 0
- }
- },
- tag: "LI"
-};
-dhtmlx.DataDriver.jsarray = {
- toObject: function (a) {
- if (typeof a == "string") {
- eval("dhtmlx.temp=" + a);
- return dhtmlx.temp
- }
- return a
- },
- getRecords: function (a) {
- return a
- },
- getDetails: function (a) {
- for (var b = {}, c = 0; c < a.length; c++) b["data" + c] = a[c];
- return b
- },
- getInfo: function () {
- return {
- w: 0,
- u: 0
- }
- }
-};
-dhtmlx.DataDriver.csv = {
- toObject: function (a) {
- return a
- },
- getRecords: function (a) {
- return a.split(this.row)
- },
- getDetails: function (a) {
- a = this.stringToArray(a);
- for (var b = {}, c = 0; c < a.length; c++) b["data" + c] = a[c];
- return b
- },
- getInfo: function () {
- return {
- w: 0,
- u: 0
- }
- },
- stringToArray: function (a) {
- a = a.split(this.cell);
- for (var b = 0; b < a.length; b++) a[b] = a[b].replace(/^[ \t\n\r]*(\"|)/g, "").replace(/(\"|)[ \t\n\r]*$/g, "");
- return a
- },
- row: "\n",
- cell: ","
-};
-dhtmlx.DataDriver.xml = {
- toObject: function (a, b) {
- if (b && (b = this.checkResponse(a, b))) return b;
- if (typeof a == "string") return this.fromString(a);
- return a
- },
- getRecords: function (a) {
- return this.xpath(a, this.records)
- },
- records: "/*/item",
- userdata: "/*/userdata",
- getDetails: function (a) {
- return this.tagToObject(a, {})
- },
- getUserData: function (a, b) {
- b = b || {};
- var c = this.xpath(a, this.userdata);
- for (a = 0; a < c.length; a++) {
- var d = this.tagToObject(c[a]);
- b[d.name] = d.value
- }
- return b
- },
- getInfo: function (a) {
- return {
- w: a.documentElement.getAttribute("total_count") || 0,
- u: a.documentElement.getAttribute("pos") || 0
- }
- },
- xpath: function (a, b) {
- if (window.XPathResult) {
- var c = a;
- if (a.nodeName.indexOf("document") == -1) a = a.ownerDocument;
- var d = [];
- a = a.evaluate(b, c, null, XPathResult.ANY_TYPE, null);
- for (b = a.iterateNext(); b;) {
- d.push(b);
- b = a.iterateNext()
- }
- return d
- }
- return a.selectNodes(b)
- },
- tagToObject: function (a, b) {
- b = b || {};
- for (var c = a.attributes, d = 0; d < c.length; d++) b[c[d].name] = c[d].value;
- var e = false,
- f = a.childNodes;
- for (d = 0; d < f.length; d++) if (f[d].nodeType == 1) {
- var g = f[d].tagName;
- if (typeof b[g] != "undefined") {
- b[g] instanceof Array || (b[g] = [b[g]]);
- b[g].push(this.tagToObject(f[d], {}))
- } else b[f[d].tagName] = this.tagToObject(f[d], {});
- e = true
- }
- if (!c.length && !e) return this.nodeValue(a);
- b.value = this.nodeValue(a);
- return b
- },
- nodeValue: function (a) {
- if (a.firstChild) return a.firstChild.data;
- return ""
- },
- fromString: function (a) {
- if (window.DOMParser) return (new DOMParser).parseFromString(a, "text/xml");
- if (window.ActiveXObject) {
- temp = new ActiveXObject("Microsoft.xmlDOM");
- temp.loadXML(a);
- return temp
- }
- },
- checkResponse: function (a, b) {
- if (b && b.firstChild && b.firstChild.tagName != "parsererror") return b;
- if (a = this.from_string(a.responseText.replace(/^[\s]+/, ""))) return a
- }
-};
-dhtmlx.DataDriver.dhtmlxgrid = {
- Ia: "_get_cell_value",
- toObject: function (a) {
- return this.ea = a
- },
- getRecords: function (a) {
- return a.rowsBuffer
- },
- getDetails: function (a) {
- for (var b = {}, c = 0; c < this.ea.getColumnsNum(); c++) b["data" + c] = this.ea[this.Ia](a, c);
- return b
- },
- getInfo: function () {
- return {
- w: 0,
- u: 0
- }
- }
-};
-dhtmlx.Canvas = {
- k: function () {
- this.D = []
- },
- Ya: function (a) {
- this.l = dhtmlx.html.create("canvas", {
- width: a.offsetWidth,
- height: a.offsetHeight
- });
- a.appendChild(this.l);
- if (!this.l.getContext) if (dhtmlx.r) {
- G_vmlCanvasManager.init_(document);
- G_vmlCanvasManager.initElement(this.l)
- }
- return this.l
- },
- getCanvas: function (a) {
- return (this.l || this.Ya(this.g)).getContext(a || "2d")
- },
- $a: function () {
- if (this.l) {
- this.l.setAttribute("width", this.l.parentNode.offsetWidth);
- this.l.setAttribute("height", this.l.parentNode.offsetHeight)
- }
- },
- renderText: function (a, b, c, d, e) {
- if (c) {
- a = dhtmlx.html.create("DIV", {
- "class": "dhx_canvas_text" + (d ? " " + d : ""),
- style: "left:" + a + "px; top:" + b + "px;"
- }, c);
- this.g.appendChild(a);
- this.D.push(a);
- if (e) a.style.width = e + "px";
- return a
- }
- },
- renderTextAt: function (a, b, c, d, e, f, g) {
- if (e = this.renderText.call(this, c, d, e, f, g)) {
- if (a) e.style.top = a == "middle" ? parseInt(d - e.offsetHeight / 2, 10) + "px" : d - e.offsetHeight + "px";
- if (b) e.style.left = b == "left" ? c - e.offsetWidth + "px" : parseInt(c - e.offsetWidth / 2, 10) + "px"
- }
- return e
- },
- clearCanvas: function () {
- for (var a = 0; a < this.D.length; a++) this.g.removeChild(this.D[a]);
- this.D = [];
- if (this.g.v) {
- this.g.v.parentNode.removeChild(this.g.v);
- this.g.v = null
- }
- this.getCanvas().clearRect(0, 0, this.l.offsetWidth, this.l.offsetHeight)
- }
-};
-dhtmlXChart = function (a) {
- this.name = "Chart";
- this.version = "3.0";
- dhtmlx.extend(this, dhtmlx.Settings);
- this.Xa(a, "dhx_chart");
- dhtmlx.extend(this, dhtmlx.DataLoader);
- this.data.provideApi(this, true);
- dhtmlx.extend(this, dhtmlx.EventSystem);
- dhtmlx.extend(this, dhtmlx.MouseEvents);
- dhtmlx.extend(this, dhtmlx.Destruction);
- dhtmlx.extend(this, dhtmlx.Canvas);
- dhtmlx.extend(this, dhtmlx.Group);
- dhtmlx.extend(this, dhtmlx.AutoTooltip);
- for (var b in dhtmlx.chart) dhtmlx.extend(this, dhtmlx.chart[b]);
- this.B(a, {
- color: "RAINBOW",
- alpha: "1",
- label: false,
- value: "{obj.value}",
- padding: {},
- view: "pie",
- lineColor: "#ffffff",
- cant: 0.5,
- width: 15,
- labelWidth: 100,
- line: {},
- item: {},
- shadow: true,
- gradient: false,
- border: true,
- labelOffset: 20,
- origin: "auto"
- });
- this.h = [this.e];
- this.data.attachEvent("onStoreUpdated", dhtmlx.bind(function () {
- this.render()
- }, this));
- this.attachEvent("onLocateData", this.db)
-};
-dhtmlXChart.prototype = {
- q: "dhx_area_id",
- on_click: {},
- on_dblclick: {},
- on_mouse_move: {},
- resize: function () {
- this.$a();
- this.render()
- },
- view_setter: function (a, b) {
- if (typeof this.e.offset == "undefined") this.e.offset = b == "area" || b == "stackedArea" ? false : true;
- return b
- },
- render: function () {
- if (this.callEvent("onBeforeRender", [this.data])) {
- this.clearCanvas();
- this.e.legend && this.za(this.getCanvas(), this.data.getRange(), this.g.offsetWidth, this.g.offsetHeight);
- for (var a = this.Ea(this.g.offsetWidth, this.g.offsetHeight), b = new dhtmlx.ui.Map(this.q), c = this.e, d = 0; d < this.h.length; d++) {
- this.e = this.h[d];
- this["pvt_render_" + this.e.view](this.getCanvas(), this.data.getRange(), a.start, a.end, d, b)
- }
- b.render(this.g);
- this.e = c
- }
- },
- value_setter: dhtmlx.Template.obj_setter,
- alpha_setter: dhtmlx.Template.obj_setter,
- label_setter: dhtmlx.Template.obj_setter,
- lineColor_setter: dhtmlx.Template.obj_setter,
- pieInnerText_setter: dhtmlx.Template.obj_setter,
- gradient_setter: function (a, b) {
- if (typeof b != "function" && b && (b === true || b != "3d")) b = "light";
- return b
- },
- colormap: {
- RAINBOW: function (a) {
- a = Math.floor(this.indexById(a.id) / this.dataCount() * 1536);
- if (a == 1536) a -= 1;
- return this.Za[Math.floor(a / 256)](a % 256)
- }
- },
- color_setter: function (a, b) {
- return this.colormap[b] || dhtmlx.Template.obj_setter(a, b)
- },
- legend_setter: function (a, b) {
- if (typeof b != "object") b = {
- template: b
- };
- this.n(b, {
- width: 150,
- height: 18,
- layout: "y",
- align: "left",
- valign: "bottom",
- template: "",
- marker: {
- type: "square",
- width: 25,
- height: 15
- }
- });
- b.template = dhtmlx.Template.setter(0, b.template);
- return b
- },
- item_setter: function (a, b) {
- if (typeof b != "object") b = {
- color: b,
- borderColor: b
- };
- this.n(b, {
- radius: 4,
- color: "#000000",
- borderColor: "#000000",
- borderWidth: 2
- });
- b.color = dhtmlx.Template.setter(0, b.color);
- b.borderColor = dhtmlx.Template.setter(0, b.borderColor);
- return b
- },
- line_setter: function (a, b) {
- if (typeof b != "object") b = {
- color: b
- };
- this.n(b, {
- width: 3,
- color: "#d4d4d4"
- });
- b.color = dhtmlx.Template.setter(0, b.color);
- return b
- },
- padding_setter: function (a, b) {
- if (typeof b != "object") b = {
- left: b,
- right: b,
- top: b,
- bottom: b
- };
- this.n(b, {
- left: 50,
- right: 20,
- top: 35,
- bottom: 40
- });
- return b
- },
- xAxis_setter: function (a, b) {
- if (!b) return false;
- if (typeof b != "object") b = {
- template: b
- };
- this.n(b, {
- title: "",
- color: "#000000",
- template: "{obj}",
- lines: false
- });
- if (b.template) b.template = dhtmlx.Template.setter(0, b.template);
- return b
- },
- yAxis_setter: function (a, b) {
- this.n(b, {
- title: "",
- color: "#000000",
- template: "{obj}",
- lines: true
- });
- if (b.template) b.template = dhtmlx.Template.setter(0, b.template);
- return b
- },
- N: function (a, b, c, d, e, f, g) {
- e = this.Da(a, b, c, d, e, f);
- this.da(a, b, c, d, g, e);
- return e
- },
- da: function (a, b, c, d, e, f) {
- if (this.e.xAxis) {
- var g = c.x - 0.5;
- f = parseInt(f ? f : d.y, 10) + 0.5;
- var i = d.x,
- j, k = true;
- this.i(a, g, f, i, f, this.e.xAxis.color, 1);
- for (var h = 0; h < b.length; h++) {
- if (this.e.offset === true) j = g + e / 2 + h * e;
- else {
- j = g + h * e;
- k = !! h
- }
- var m = this.e.origin != "auto" && this.e.view == "bar" && parseFloat(this.e.value(b[h])) < this.e.origin;
- this.Ba(j, f, b[h], k, m);
- this.e.view_setter != "bar" && this.Ca(a, j, d.y, c.y)
- }
- this.renderTextAt(true, false, g, d.y + this.e.padding.bottom - 3, this.e.xAxis.title, "dhx_axis_title_x", d.x - c.x);
- this.e.xAxis.lines && this.e.offset && this.i(a, i + 0.5, d.y, i + 0.5, c.y + 0.5, this.e.xAxis.color, 0.2)
- }
- },
- Da: function (a, b, c, d, e, f) {
- var g;
- b = {};
- if (this.e.yAxis) {
- var i = c.x - 0.5,
- j = d.y,
- k = c.y,
- h = d.y;
- this.i(a, i, j, i, k, this.e.yAxis.color, 1);
- if (this.e.yAxis.step) g = parseFloat(this.e.yAxis.step);
- if (typeof this.e.yAxis.step == "undefined" || typeof this.e.yAxis.start == "undefined" || typeof this.e.yAxis.end == "undefined") {
- b = this.V(e, f);
- e = b.start;
- f = b.end;
- g = b.step;
- this.e.yAxis.end = f;
- this.e.yAxis.start = e
- }
- if (g !== 0) {
- k = (j - k) * g / (f - e);
- for (var m = 0, l = e; l <= f; l += g) {
- if (b.fixNum) l = parseFloat((new Number(l)).toFixed(b.fixNum));
- var n = Math.floor(j - m * k) + 0.5;
- !(l == e && this.e.origin == "auto") && this.e.yAxis.lines && this.i(a, i, n, d.x, n, this.e.yAxis.color, 0.2);
- if (l == this.e.origin) h = n;
- this.renderText(0, n - 5, this.e.yAxis.template(l.toString()), "dhx_axis_item_y", c.x - 5);
- m++
- }
- this.oa(c, d);
- return h
- }
- }
- },
- oa: function (a, b) {
- if (a = this.renderTextAt("middle", false, 0, parseInt((b.y - a.y) / 2 + a.y, 10), this.e.yAxis.title, "dhx_axis_title_y")) a.style.left = (dhtmlx.env.transform ? (a.offsetHeight - a.offsetWidth) / 2 : 0) + "px"
- },
- V: function (a, b) {
- if (this.e.origin != "auto" && this.e.origin < a) a = this.e.origin;
- var c, d, e;
- c = (b - a) / 8 || 1;
- var f = Math.floor(this.ga(c)),
- g = Math.pow(10, f),
- i = c / g;
- i = i > 5 ? 10 : 5;
- c = parseInt(i, 10) * g;
- if (c > Math.abs(a)) d = a < 0 ? -c : 0;
- else {
- var j = Math.abs(a),
- k = Math.floor(this.ga(j)),
- h = j / Math.pow(10, k);
- d = Math.ceil(h * 10) / 10 * Math.pow(10, k) - c;
- if (a < 0) d = -d - 2 * c
- }
- for (e = d; e < b;) {
- e += c;
- e = parseFloat((new Number(e)).toFixed(Math.abs(f)))
- }
- return {
- start: d,
- end: e,
- step: c,
- fixNum: Math.abs(f)
- }
- },
- O: function (a) {
- var b, c;
- if ((c = arguments.length && a == "h" ? this.e.xAxis : this.e.yAxis) && typeof c.end != "undefied" && typeof c.start != "undefied" && c.step) {
- b = parseFloat(c.end);
- c = parseFloat(c.start)
- } else {
- b = this.max(this.h[0].value);
- c = this.min(this.h[0].value);
- if (this.h.length > 1) for (var d = 1; d < this.h.length; d++) {
- var e = this.max(this.h[d].value),
- f = this.min(this.h[d].value);
- if (e > b) b = e;
- if (f < c) c = f
- }
- }
- return {
- max: b,
- min: c
- }
- },
- ga: function (a) {
- var b = "log";
- return Math.floor(Math[b](a) / Math.LN10)
- },
- Ba: function (a, b, c, d, e) {
- var to = offset = 11,from = 0,
- val = this.e.xAxis.template(c);
- while(1){
- this.e.xAxis && this.renderTextAt(e, d, a+from, b+from, val.slice(from,to), "dhx_axis_item_x");
- if(to > val.length){break;}
- from += offset;to += offset;
- }
- },
- Ca: function (a, b, c, d) {
- this.e.xAxis && this.e.xAxis.lines && this.i(a, b, c, b, d, this.e.xAxis.color, 0.2)
- },
- i: function (a, b, c, d, e, f, g) {
- a.strokeStyle = f;
- a.lineWidth = g;
- a.beginPath();
- a.moveTo(b, c);
- a.lineTo(d, e);
- a.stroke()
- },
- z: function (a, b) {
- var c = 1;
- if (b != a) {
- a = b - a;
- if (Math.abs(a) < 1) for (; Math.abs(a) < 1;) {
- c *= 10;
- a *= c
- }
- } else a = a;
- return [a, c]
- },
- Za: [function (a) {
- return "#FF" + dhtmlx.math.toHex(a / 2, 2) + "00"
- }, function (a) {
- return "#FF" + dhtmlx.math.toHex(a / 2 + 128, 2) + "00"
- }, function (a) {
- return "#" + dhtmlx.math.toHex(255 - a, 2) + "FF00"
- }, function (a) {
- return "#00FF" + dhtmlx.math.toHex(a, 2)
- }, function (a) {
- return "#00" + dhtmlx.math.toHex(255 - a, 2) + "FF"
- }, function (a) {
- return "#" + dhtmlx.math.toHex(a, 2) + "00FF"
- }],
- addSeries: function (a) {
- var b = this.e;
- this.e = dhtmlx.extend({}, b);
- this.B(a, {});
- this.h.push(this.e);
- this.e = b
- },
- db: function (a, b) {
- this.ra = b.getAttribute("userdata");
- for (a = 0; a < this.h.length; a++) {
- var c = this.h[a].tooltip;
- c && c.disable()
- }(c = this.h[this.ra].tooltip) && c.enable()
- },
- za: function (a, b) {
- var c = 0,
- d = 0,
- e = this.e.legend,
- f, g, i = this.e.legend.layout != "x" ? "width:" + e.width + "px" : "",
- j = dhtmlx.html.create("DIV", {
- "class": "dhx_chart_legend",
- style: "left:" + c + "px; top:" + d + "px;" + i
- }, "");
- this.g.appendChild(j);
- var k = [];
- if (e.values) for (h = 0; h < e.values.length; h++) k.push(this.aa(j, e.values[h].text));
- else for (var h = 0; h < b.length; h++) k.push(this.aa(j, e.template(b[h])));
- g = j.offsetWidth;
- f = j.offsetHeight;
- this.e.legend.width = g;
- this.e.legend.height = f;
- if (g < this.g.offsetWidth) {
- if (e.layout == "x" && e.align == "center") c = (this.g.offsetWidth - g) / 2;
- if (e.align == "right") c = this.g.offsetWidth - g
- }
- if (f < this.g.offsetHeight) if (e.valign == "middle" && e.align != "center" && e.layout != "x") d = (this.g.offsetHeight - f) / 2;
- else if (e.valign == "bottom") d = this.g.offsetHeight - f;
- j.style.left = c + "px";
- j.style.top = d + "px";
- for (h = 0; h < k.length; h++) {
- var m = k[h],
- l = e.values ? e.values[h].color : this.e.color.call(this, b[h]);
- this.Aa(a, m.offsetLeft + c, m.offsetTop + d, l)
- }
- k = null
- },
- aa: function (a, b) {
- var c = "";
- if (this.e.legend.layout == "x") c = "float:left;";
- b = dhtmlx.html.create("DIV", {
- style: c + "padding-left:" + (10 + this.e.legend.marker.width) + "px",
- "class": "dhx_chart_legend_item"
- }, b);
- a.appendChild(b);
- return b
- },
- Aa: function (a, b, c, d) {
- var e = this.e.legend;
- a.strokeStyle = a.fillStyle = d;
- a.lineWidth = e.marker.height;
- a.lineCap = e.marker.type;
- a.beginPath();
- b += a.lineWidth / 2 + 5;
- c += a.lineWidth / 2 + 3;
- a.moveTo(b, c);
- b = b + e.marker.width - e.marker.height + 1;
- a.lineTo(b, c);
- a.stroke()
- },
- Ea: function (a, b) {
- var c, d, e, f;
- c = this.e.padding.left;
- d = this.e.padding.top;
- e = a - this.e.padding.right;
- f = b - this.e.padding.bottom;
- if (this.e.legend) {
- a = this.e.legend;
- b = this.e.legend.width;
- var g = this.e.legend.height;
- if (a.layout == "x") if (a.valign == "center") if (a.align == "right") e -= b;
- else {
- if (a.align == "left") c += b
- } else if (a.valign == "bottom") f -= g;
- else d += g;
- else if (a.align == "right") e -= b;
- else if (a.align == "left") c += b
- }
- return {
- start: {
- x: c,
- y: d
- },
- end: {
- x: e,
- y: f
- }
- }
- },
- Q: function (a) {
- var b, c;
- if (this.e.yAxis && typeof this.e.yAxis.end != "undefied" && typeof this.e.yAxis.start != "undefied" && this.e.yAxis.step) {
- b = parseFloat(this.e.yAxis.end);
- c = parseFloat(this.e.yAxis.start)
- } else {
- for (var d = 0; d < a.length; d++) {
- a[d].$sum = 0;
- a[d].$min = Infinity;
- for (b = 0; b < this.h.length; b++) {
- c = parseFloat(this.h[b].value(a[d]));
- if (!isNaN(c)) {
- a[d].$sum += c;
- if (c < a[d].$min) a[d].$min = c
- }
- }
- }
- b = -Infinity;
- c = Infinity;
- for (d = 0; d < a.length; d++) {
- if (a[d].$sum > b) b = a[d].$sum;
- if (a[d].$min < c) c = a[d].$min
- }
- if (c > 0) c = 0
- }
- return {
- max: b,
- min: c
- }
- },
- G: function (a, b, c, d, e, f, g, i) {
- if (f == "light") {
- a = i == "x" ? a.createLinearGradient(b, c, d, c) : a.createLinearGradient(b, c, b, e);
- a.addColorStop(0, "#FFFFFF");
- a.addColorStop(0.9, g);
- a.addColorStop(1, g);
- g = 2
- } else {
- a.globalAlpha = 0.37;
- g = 0;
- a = i == "x" ? a.createLinearGradient(b, e, b, c) : a.createLinearGradient(b, c, d, c);
- a.addColorStop(0, "#000000");
- a.addColorStop(0.5, "#FFFFFF");
- a.addColorStop(0.6, "#FFFFFF");
- a.addColorStop(1, "#000000")
- }
- return {
- gradient: a,
- offset: g
- }
- }
-};
-dhtmlx.compat("layout");
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.css b/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.css
deleted file mode 100644
index c30fbda2da3..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.css
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
-Copyright DHTMLX LTD. http://www.dhtmlx.com
-You allowed to use this component or parts of it under GPL terms
-To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com
-*/
-/*pure colors*/
/*fonts*/
/*2010 September 28*//* DHX DEPEND FROM FILE 'tooltip.css'*//*style used by tooltip's container*/
.dhx_tooltip{
display:none;
position:absolute;
font-family:Tahoma;
font-size:8pt;
z-index:10000;
background-color:white;
padding:2px 2px 2px 2px;
border:1px solid #A4BED4;
}/* DHX DEPEND FROM FILE 'chart.css'*//*chart container*/
.dhx_chart{
position:relative;
font-family:Verdana;
font-size:13px;
color:#000000;
overflow:hidden;
}
/*labels*/
.dhx_canvas_text{
position:absolute;
text-align:center;
overflow:hidden;
white-space:nowrap;
}
/*map*/
.dhx_map_img{
width : 100%;
height : 100%;
position : absolute;
top : 0px;
left : 0px;
border:0px;
filter:alpha(opacity=0);
}
/*scales*/
.dhx_axis_item_y{
position:absolute;
height:10px;
line-height:10px;
text-align:right;
}
.dhx_axis_title_x{
text-align:center;
}
.dhx_axis_title_y{
text-align:center;
font-family:Verdana;
/*safari*/
-webkit-transform: rotate(-90deg);
/*firefox*/
-moz-transform: rotate(-90deg);
/*IE*/
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
/*opera*/
-o-transform:rotate(-90deg);
padding-left:3px;
}
/*legend block*/
.dhx_chart_legend{
position:absolute;
}
.dhx_chart_legend_item{
height:18px;
line-height:18px;
padding: 2px;
}
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.js b/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.js
deleted file mode 100644
index b536825fe02..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/codebase/dhtmlxchart_debug.js
+++ /dev/null
@@ -1,4736 +0,0 @@
-/*
-Copyright DHTMLX LTD. http://www.dhtmlx.com
-You allowed to use this component or parts of it under GPL terms
-To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com
-*/
-/*
-2010 September 30
-*/
-
-
-
-/* DHX DEPEND FROM FILE 'assert.js'*/
-
-
-if (!window.dhtmlx)
- dhtmlx={};
-
-//check some rule, show message as error if rule is not correct
-dhtmlx.assert = function(test, message){
- if (!test) dhtmlx.error(message);
-};
-dhtmlx.assert_enabled=function(){ return false; };
-
-//register names of event, which can be triggered by the object
-dhtmlx.assert_event = function(obj, evs){
- if (!obj._event_check){
- obj._event_check = {};
- obj._event_check_size = {};
- }
-
- for (var a in evs){
- obj._event_check[a.toLowerCase()]=evs[a];
- var count=-1; for (var t in evs[a]) count++;
- obj._event_check_size[a.toLowerCase()]=count;
- }
-};
-dhtmlx.assert_method_info=function(obj, name, descr, rules){
- var args = [];
- for (var i=0; i < rules.length; i++) {
- args.push(rules[i][0]+" : "+rules[i][1]+"\n "+rules[i][2].describe()+(rules[i][3]?"; optional":""));
- }
- return obj.name+"."+name+"\n"+descr+"\n Arguments:\n - "+args.join("\n - ");
-};
-dhtmlx.assert_method = function(obj, config){
- for (var key in config)
- dhtmlx.assert_method_process(obj, key, config[key].descr, config[key].args, (config[key].min||99), config[key].skip);
-};
-dhtmlx.assert_method_process = function (obj, name, descr, rules, min, skip){
- var old = obj[name];
- if (!skip)
- obj[name] = function(){
- if (arguments.length != rules.length && arguments.length < min)
- dhtmlx.log("warn","Incorrect count of parameters\n"+obj[name].describe()+"\n\nExpecting "+rules.length+" but have only "+arguments.length);
- else
- for (var i=0; i= 0) return true;
- return false;
- };
- dhtmlx.assert_rule_dimension.describe=function(){
- return "{Integer} value must be a positive number";
- };
-
- dhtmlx.assert_rule_number=function(check){
- if (typeof check == "number") return true;
- return false;
- };
- dhtmlx.assert_rule_number.describe=function(){
- return "{Integer} value must be a number";
- };
-
- dhtmlx.assert_rule_function=function(check){
- if (typeof check == "function") return true;
- return false;
- };
- dhtmlx.assert_rule_function.describe=function(){
- return "{Function} value must be a custom function";
- };
-
- dhtmlx.assert_rule_any=function(check){
- return true;
- };
- dhtmlx.assert_rule_any.describe=function(){
- return "Any value";
- };
-
- dhtmlx.assert_rule_mix=function(a,b){
- var t = function(check){
- if (a(check)||b(check)) return true;
- return false;
- };
- t.describe = function(){
- return a.describe();
- };
- return t;
- };
-
-}
-
-
-/* DHX DEPEND FROM FILE 'dhtmlx.js'*/
-
-
-/*DHX:Depend assert.js*/
-
-/*
- Common helpers
-*/
-dhtmlx.version="3.0";
-dhtmlx.codebase="./";
-
-//coding helpers
-
-//copies methods and properties from source to the target
-dhtmlx.extend = function(target, source){
- for (var method in source)
- target[method] = source[method];
-
- //applying asserts
- if (dhtmlx.assert_enabled() && source._assert){
- target._assert();
- target._assert=null;
- }
-
- dhtmlx.assert(target,"Invalid nesting target");
- dhtmlx.assert(source,"Invalid nesting source");
- //if source object has init code - call init against target
- if (source._init)
- target._init();
-
- return target;
-};
-dhtmlx.proto_extend = function(){
- var origins = arguments;
- var compilation = origins[0];
- var construct = [];
-
- for (var i=origins.length-1; i>0; i--) {
- if (typeof origins[i]== "function")
- origins[i]=origins[i].prototype;
- for (var key in origins[i]){
- if (key == "_init")
- construct.push(origins[i][key]);
- else if (!compilation[key])
- compilation[key] = origins[i][key];
- }
- };
-
- if (origins[0]._init)
- construct.push(origins[0]._init);
-
- compilation._init = function(){
- for (var i=0; i handler
- this._handlers = {}; //hash of event handlers, ID => handler
- this._map = {};
- },
- //temporary block event triggering
- block : function(){
- this._events._block = true;
- },
- //re-enable event triggering
- unblock : function(){
- this._events._block = false;
- },
- mapEvent:function(map){
- dhtmlx.extend(this._map, map);
- },
- //trigger event
- callEvent:function(type,params){
- if (this._events._block) return true;
-
- type = type.toLowerCase();
- dhtmlx.assert_event_call(this, type, params);
-
- var event_stack =this._events[type.toLowerCase()]; //all events for provided name
- var return_value = true;
-
- if (dhtmlx.debug) //can slowdown a lot
- dhtmlx.log("info","["+this.name+"] event:"+type,params);
-
- if (event_stack)
- for(var i=0; i=0) this.splice(pos,(len||1));
- },
- //find element in collection and remove it
- remove:function(value){
- this.removeAt(this.find(value));
- },
- //add element to collection at specific position
- insertAt:function(data,pos){
- if (!pos && pos!==0) //add to the end by default
- this.push(data);
- else {
- var b = this.splice(pos,(this.length-pos));
- this[pos] = data;
- this.push.apply(this,b); //reconstruct array without loosing this pointer
- }
- },
- //return index of element, -1 if it doesn't exists
- find:function(data){
- for (i=0; i0){
- str=this._toHex[number%16]+str;
- number=Math.floor(number/16);
- }
- while (str.length ");
- },
- addSector:function(id,alpha0,alpha1,x,y,R,ky){
- var points = [];
- points.push(x);
- points.push(Math.floor(y*ky));
- for(var i = alpha0; i < alpha1; i+=Math.PI/18){
- points.push(Math.floor(x+R*Math.cos(i)));
- points.push(Math.floor((y+R*Math.sin(i))*ky));
- }
- points.push(Math.floor(x+R*Math.cos(alpha1)));
- points.push(Math.floor((y+R*Math.sin(alpha1))*ky));
- points.push(x);
- points.push(Math.floor(y*ky));
-
- return this.addPoly(id,points);
- },
- render:function(obj){
- var d = dhtmlx.html.create("DIV");
- d.style.cssText="position:absolute; width:100%; height:100%; top:0px; left:0px;";
- obj.appendChild(d);
- var src = dhtmlx._isIE?"":"src='data:image/gif;base64,R0lGODlhEgASAIAAAP///////yH5BAUUAAEALAAAAAASABIAAAIPjI+py+0Po5y02ouz3pwXADs='";
- d.innerHTML=""+this._map.join("\n")+" ";
-
- obj._htmlmap = d; //for clearing routine
-
- this._map = [];
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'ext/chart/chart_base.js'*/
-
-
-/*DHX:Depend map.js*/
-dhtmlx.chart = {};
-
-
-/* DHX DEPEND FROM FILE 'ext/chart/chart_area.js'*/
-
-
-/*DHX:Depend ext/chart/chart_base.js*/
-dhtmlx.chart.area = {
- /**
- * renders an area chart
- * @param: ctx - canvas object
- * @param: data - object those need to be displayed
- * @param: width - the width of the container
- * @param: height - the height of the container
- * @param: sIndex - index of drawing chart
- */
- pvt_render_area:function(ctx, data, point0, point1, sIndex, map){
-
- var params = this._calculateParametersOfLineChart(ctx,data,point0,point1,sIndex);
-
- /*the value that defines the map area position*/
- var areaPos = Math.floor(params.cellWidth/2);
-
- /*drawing all items*/
- if (data.length) {
-
- ctx.globalAlpha = this._settings.alpha.call(this,data[0]);
- ctx.fillStyle = this._settings.color.call(this,data[0]);
-
- /*the position of the first item*/
- var y0 = this._getYPointOfLineChart(data[0],point0,point1,params);
- var x0 = (this._settings.offset?point0.x+params.cellWidth*0.5:point0.x);
- ctx.beginPath();
- ctx.moveTo(x0,point1.y);
- ctx.lineTo(x0,y0);
-
- /*creates map area*/
- map.addRect(data[0].id,[x0-areaPos,y0-areaPos,x0+areaPos,y0+areaPos]);
- /*item label*/
- if(!this._settings.yAxis)
- this.renderTextAt(false, (!this._settings.offset?false:true), x0, y0-this._settings.labelOffset, this._settings.label(data[0]));
-
- /*drawing the previous item and the line between to items*/
- for(var i=1; i < data.length;i ++){
- /*horizontal positions of the previous and current items (0.5 - the fix for line width)*/
- var xi = x0+ Math.floor(params.cellWidth*i) - 0.5;
- var yi = this._getYPointOfLineChart(data[i],point0,point1,params);
- ctx.lineTo(xi,yi);
- /*creates map area*/
- map.addRect(data[i].id,[xi-areaPos,yi-areaPos,xi+areaPos,yi+areaPos]);
- /*item label*/
- if(!this._settings.yAxis)
- this.renderTextAt(false, (!this._settings.offset&&i==(data.length-1)?"left":"center"), xi, yi-this._settings.labelOffset, this._settings.label(data[i]));
- }
- ctx.lineTo(x0+Math.floor(params.cellWidth*[data.length-1]),point1.y);
- ctx.lineTo(x0,point1.y);
- ctx.fill();
- }
- }
-};
-dhtmlx.chart.stackedArea ={
- /**
- * renders an area chart
- * @param: ctx - canvas object
- * @param: data - object those need to be displayed
- * @param: width - the width of the container
- * @param: height - the height of the container
- * @param: sIndex - index of drawing chart
- */
- pvt_render_stackedArea:function(ctx, data, point0, point1, sIndex, map){
-
- var params = this._calculateParametersOfLineChart(ctx,data,point0,point1,sIndex);
-
- /*the value that defines the map area position*/
- var areaPos = Math.floor(params.cellWidth/2);
-
- var y1 = [];
-
- /*drawing all items*/
- if (data.length) {
-
- ctx.globalAlpha = this._settings.alpha.call(this,data[0]);
- ctx.fillStyle = this._settings.color.call(this,data[0]);
-
- /*for the 2nd, 3rd, etc. series*/
- var y01 = (sIndex?data[0].$startY:point1.y);
-
- /*the position of the first item*/
- var x0 = (this._settings.offset?point0.x+params.cellWidth*0.5:point0.x);
- var y02 = this._getYPointOfLineChart(data[0],point0,point1,params)-(sIndex?(point1.y-y01):0);
-
- y1[0] = y02;
-
- ctx.beginPath();
- ctx.moveTo(x0,y01);
- ctx.lineTo(x0,y02);
-
- /*creates map area*/
- map.addRect(data[0].id,[x0-areaPos,y02-areaPos,x0+areaPos,y02+areaPos]);
- /*item label*/
- if(!this._settings.yAxis)
- this.renderTextAt(false, true, x0, y02-this._settings.labelOffset, this._settings.label(data[0]));
-
- /*drawing the previous item and the line between to items*/
- for(var i=1; i < data.length;i ++){
- /*horizontal positions of the previous and current items (0.5 - the fix for line width)*/
- var xi = x0+ Math.floor(params.cellWidth*i) - 0.5;
- var yi2 = this._getYPointOfLineChart(data[i],point0,point1,params)-(sIndex?(point1.y-data[i].$startY):0);
-
- y1[i] = yi2;
-
- ctx.lineTo(xi,yi2);
- /*creates map area*/
- map.addRect(data[i].id,[xi-areaPos,yi2-areaPos,xi+areaPos,yi2+areaPos]);
- /*item label*/
- if(!this._settings.yAxis)
- this.renderTextAt(false, true, xi, yi2-this._settings.labelOffset, this._settings.label(data[i]));
- }
- ctx.lineTo(x0+Math.floor(params.cellWidth*[data.length-1]),y01);
- /*drawing lines of the lower part*/
- if(sIndex){
- for(var i=data.length-1; i >=0 ;i--){
- var xi = x0+ Math.floor(params.cellWidth*i) - 0.5;
- var yi1 = data[i].$startY;
- ctx.lineTo(xi,yi1);
- }
- }
- else ctx.lineTo(x0+ Math.floor(params.cellWidth*(length-1)) - 0.5,y01);
- ctx.lineTo(x0,y01);
- ctx.fill();
- for(var i=0; i < data.length;i ++){
- data[i].$startY = y1[i];
- }
- }
- }
-};
-
-
-
-/* DHX DEPEND FROM FILE 'ext/chart/chart_spline.js'*/
-
-
-/*DHX:Depend ext/chart/chart_base.js*/
-dhtmlx.chart.spline = {
- /**
- * renders a spline chart
- * @param: ctx - canvas object
- * @param: data - object those need to be displayed
- * @param: width - the width of the container
- * @param: height - the height of the container
- * @param: sIndex - index of drawing chart
- */
- pvt_render_spline:function(ctx, data, point0, point1, sIndex, map){
-
- var params = this._calculateParametersOfLineChart(ctx,data,point0,point1,sIndex);
-
- /*the value that defines the map area position*/
- var areaPos = Math.floor(params.cellWidth/2);
-
- /*array of all points*/
- var items = [];
-
- /*drawing all items*/
- if (data.length) {
-
- /*getting all points*/
- var x0 = (this._settings.offset?point0.x+params.cellWidth*0.5:point0.x);
- for(var i=0; i < data.length;i ++){
- var x = ((!i)?x0:Math.floor(params.cellWidth*i) - 0.5 + x0);
- var y = this._getYPointOfLineChart(data[i],point0,point1,params);
- items.push({x:x,y:y});
- }
- var sparam = this._getSplineParameters(items);
-
- for(var i =0; i< items.length-1; i++){
- var x1 = items[i].x;
- var y1 = items[i].y;
- var x2 = items[i+1].x;
- var y2 = items[i+1].y;
-
-
- for(var j = x1; j < x2; j++)
- this._drawLine(ctx,j,this._getSplineYPoint(j,x1,i,sparam.a,sparam.b,sparam.c,sparam.d),j+1,this._getSplineYPoint(j+1,x1,i,sparam.a,sparam.b,sparam.c,sparam.d),this._settings.line.color(data[i]),this._settings.line.width);
- this._drawLine(ctx,x2-1,this._getSplineYPoint(j,x1,i,sparam.a,sparam.b,sparam.c,sparam.d),x2,y2,this._settings.line.color(data[i]),this._settings.line.width);
- this._drawItemOfLineChart(ctx,x1,y1,data[i],this._settings.label(data[i]));
- }
- this._drawItemOfLineChart(ctx,x2,y2,data[i],this._settings.label(data[i]));
- }
- },
- /*gets spline parameter*/
- _getSplineParameters:function(points){
- var h,u,v,s,a,b,c,d,n;
- h = []; m = [];
- n = points.length;
-
- for(var i =0; i=1; i--)
- s[i] = (v[i] - h[i]*s[i+1])/u[i];
-
- a = []; b = []; c = []; d = [];
-
- for(var i =0; icellWidth) barWidth = cellWidth/this._series.length-4;
- /*the half of distance between bars*/
- var barOffset = Math.floor((cellWidth - barWidth*this._series.length)/2);
- /*the radius of rounding in the top part of each bar*/
- var radius = (typeof this._settings.radius!="undefined"?parseInt(this._settings.radius,10):Math.round(barWidth/5));
-
- var inner_gradient = false;
- var gradient = this._settings.gradient;
-
- if (gradient&&typeof(gradient) != "function"){
- inner_gradient = gradient;
- gradient = false;
- } else if (gradient){
- gradient = ctx.createLinearGradient(point0.x,point0.y,point1.x,point0.y);
- this._settings.gradient(gradient);
- }
- var scaleY = 0;
- /*draws a black line if the horizontal scale isn't defined*/
- if(!yax){
- this._drawLine(ctx,point0.x-0.5,point0.y,point0.x-0.5,point1.y,"#000000",1); //hardcoded color!
- }
-
-
-
- for(var i=0; i < data.length;i ++){
-
-
- var value = parseFloat(this._settings.value(data[i]));
- if(value>maxValue) value = maxValue;
- value -= minValue;
- value *= valueFactor;
-
- /*start point (bottom left)*/
- var x0 = point0.x;
- var y0 = point0.y+ barOffset + i*cellWidth+(barWidth+1)*sIndex;
-
- if(value<0||(this._settings.yAxis&&value===0)){
- this.renderTextAt(true, true, x0+Math.floor(barWidth/2),y0,this._settings.label(data[i]));
- continue;
- }
-
- /*takes start value into consideration*/
- if(!yax) value += startValue/unit;
- var color = gradient||this._settings.color.call(this,data[i]);
-
- /*drawing the gradient border of a bar*/
- if(this._settings.border){
- ctx.beginPath();
- ctx.fillStyle = color;
- this._setBarHPoints(ctx,x0,y0,barWidth,radius,unit,value,0);
- ctx.lineTo(x0,0);
- ctx.fill();
-
- ctx.fillStyle = "#000000";
- ctx.globalAlpha = 0.37;
- ctx.beginPath();
- this._setBarHPoints(ctx,x0,y0,barWidth,radius,unit,value,0);
- ctx.fill();
- }
-
- /*drawing bar body*/
- ctx.globalAlpha = this._settings.alpha.call(this,data[i]);
- ctx.fillStyle = (gradient||this._settings.color.call(this,data[i]));
- ctx.beginPath();
- var points = this._setBarHPoints(ctx,x0,y0,barWidth,radius,unit,value,(this._settings.border?1:0));
- if (gradient&&!inner_gradient) ctx.lineTo(point0.x+total_width,y0+(this._settings.border?1:0)); //fix gradient sphreading
- ctx.fill();
- ctx.globalAlpha = 1;
-
- if (inner_gradient!=false){
- var gradParam = this._setBarGradient(ctx,point0.x,y0+barWidth,point0.x+unit*value+2,y0,inner_gradient,color,"x");
- ctx.fillStyle = gradParam.gradient;
- ctx.beginPath();
- var points = this._setBarHPoints(ctx,x0,y0+gradParam.offset,barWidth-gradParam.offset*2,radius,unit,value,gradParam.offset);
- ctx.fill();
- ctx.globalAlpha = 1;
- }
-
-
- /*sets a bar label*/
- this.renderTextAt("middle",false,points[0]+3, parseInt(y0+(points[1]-y0)/2,10), this._settings.label(data[i]));
- /*defines a map area for a bar*/
- map.addRect(data[i].id,[x0,y0,points[0],points[1]],sIndex);
- }
- },
- /**
- * sets points for bar and returns the position of the bottom right point
- * @param: ctx - canvas object
- * @param: x0 - the x position of start point
- * @param: y0 - the y position of start point
- * @param: barWidth - bar width
- * @param: radius - the rounding radius of the top
- * @param: unit - the value defines the correspondence between item value and bar height
- * @param: value - item value
- * @param: offset - the offset from expected bar edge (necessary for drawing border)
- */
- _setBarHPoints:function(ctx,x0,y0,barWidth,radius,unit,value,offset){
- /*correction for displaing small values (when rounding radius is bigger than bar height)*/
- var angle_corr = 0;
- if(radius>unit*value){
- var sinA = (radius-unit*value)/radius;
- angle_corr = -Math.asin(sinA)+Math.PI/2;
- }
- /*start*/
- ctx.moveTo(x0,y0+offset);
- /*start of left rounding*/
- var x1 = x0 + unit*value - radius - (radius?0:offset);
- if(radiuscellWidth) barWidth = cellWidth-4;
- /*the half of distance between bars*/
- var barOffset = Math.floor((cellWidth - barWidth)/2);
- /*the radius of rounding in the top part of each bar*/
- var radius = 0;
-
- var inner_gradient = false;
- var gradient = this._settings.gradient;
-
- var inner_gradient = false;
- var gradient = this._settings.gradient;
- if (gradient){
- inner_gradient = true;
- }
- var scaleY = 0;
- /*draws a black line if the horizontal scale isn't defined*/
- if(!yax){
- this._drawLine(ctx,point0.x-0.5,point0.y,point0.x-0.5,point1.y,"#000000",1); //hardcoded color!
- }
-
- for(var i=0; i < data.length;i ++){
-
- if(!sIndex)
- data[i].$startX = point0.x;
-
- var value = parseFloat(this._settings.value(data[i]));
- if(value>maxValue) value = maxValue;
- value -= minValue;
- value *= valueFactor;
-
- /*start point (bottom left)*/
- var x0 = point0.x;
- var y0 = point0.y+ barOffset + i*cellWidth;
-
- /*for the 2nd, 3rd, etc. series*/
- if(sIndex)
- x0 = data[i].$startX || x0;
-
- if(value<0||(this._settings.yAxis&&value===0)){
- this.renderTextAt(true, true, x0+Math.floor(barWidth/2),y0,this._settings.label(data[i]));
- continue;
- }
-
- /*takes start value into consideration*/
- if(!yax) value += startValue/unit;
- var color = this._settings.color.call(this,data[i]);
-
- /*drawing the gradient border of a bar*/
- if(this._settings.border){
- ctx.beginPath();
- ctx.fillStyle = color;
- this._setBarHPoints(ctx,x0,y0,barWidth,radius,unit,value,0);
- ctx.lineTo(x0,0);
- ctx.fill();
-
- ctx.fillStyle = "#000000";
- ctx.globalAlpha = 0.37;
- ctx.beginPath();
- this._setBarHPoints(ctx,x0,y0,barWidth,radius,unit,value,0);
- ctx.fill();
- }
- ctx.globalAlpha = 1;
- /*drawing bar body*/
- ctx.globalAlpha = this._settings.alpha.call(this,data[i]);
- ctx.fillStyle = this._settings.color.call(this,data[i]);
- ctx.beginPath();
- var points = this._setBarHPoints(ctx,x0,y0,barWidth,radius,unit,value,(this._settings.border?1:0));
- if (gradient&&!inner_gradient) ctx.lineTo(point0.x+total_width,y0+(this._settings.border?1:0)); //fix gradient sphreading
- ctx.fill();
-
- if (inner_gradient!=false){
- var gradParam = this._setBarGradient(ctx,x0,y0+barWidth,x0,y0,inner_gradient,color,"x")
- ctx.fillStyle = gradParam.gradient;
- ctx.beginPath();
- var points = this._setBarHPoints(ctx,x0,y0, barWidth,radius,unit,value,0);
- ctx.fill();
- ctx.globalAlpha = 1;
- }
-
- /*sets a bar label*/
- this.renderTextAt("middle",true,data[i].$startX+(points[0]-data[i].$startX)/2-1, y0+(points[1]-y0)/2, this._settings.label(data[i]));
- /*defines a map area for a bar*/
- map.addRect(data[i].id,[data[i].$startX,y0,points[0],points[1]],sIndex);
- /*the start position for the next series*/
- data[i].$startX = points[0];
- }
- }
-}
-
-
-/* DHX DEPEND FROM FILE 'ext/chart/chart_stackedbar.js'*/
-
-
-/*DHX:Depend ext/chart/chart_base.js*/
-dhtmlx.chart.stackedBar = {
- /**
- * renders a bar chart
- * @param: ctx - canvas object
- * @param: data - object those need to be displayed
- * @param: x - the width of the container
- * @param: y - the height of the container
- * @param: sIndex - index of drawing chart
- */
- pvt_render_stackedBar:function(ctx, data, point0, point1, sIndex, map){
- var maxValue,minValue;
- /*necessary if maxValue - minValue < 0*/
- var valueFactor;
- /*maxValue - minValue*/
- var relValue;
-
- var total_height = point1.y-point0.y;
-
- var yax = !!this._settings.yAxis;
- var xax = !!this._settings.xAxis;
-
- var limits = this._getStackedLimits(data);
- maxValue = limits.max;
- minValue = limits.min;
-
- /*an available width for one bar*/
- var cellWidth = Math.floor((point1.x-point0.x)/data.length);
-
- /*draws x and y scales*/
- if(!sIndex)
- this._drawScales(ctx,data,point0, point1,minValue,maxValue,cellWidth);
-
- /*necessary for automatic scale*/
- if(yax){
- maxValue = parseFloat(this._settings.yAxis.end);
- minValue = parseFloat(this._settings.yAxis.start);
- }
-
- /*unit calculation (bar_height = value*unit)*/
- var relativeValues = this._getRelativeValue(minValue,maxValue);
- relValue = relativeValues[0];
- valueFactor = relativeValues[1];
-
- var unit = (relValue?total_height/relValue:10);
-
- /*a real bar width */
- var barWidth = parseInt(this._settings.width,10);
- if(barWidth+4 > cellWidth) barWidth = cellWidth-4;
- /*the half of distance between bars*/
- var barOffset = Math.floor((cellWidth - barWidth)/2);
-
-
- var inner_gradient = (this._settings.gradient?this._settings.gradient:false);
-
- var scaleY = 0;
- /*draws a black line if the horizontal scale isn't defined*/
- if(!xax){
- //scaleY = y-bottomPadding;
- this._drawLine(ctx,point0.x,point1.y+0.5,point1.x,point1.y+0.5,"#000000",1); //hardcoded color!
- }
-
- for(var i=0; i < data.length;i ++){
- var value = parseFloat(this._settings.value(data[i]));
- if(!value) continue;
-
- /*adjusts the first tab to the scale*/
- if(!sIndex)
- value -= minValue;
-
- value *= valueFactor;
-
- /*start point (bottom left)*/
- var x0 = point0.x + barOffset + i*cellWidth;
- var y0 = point1.y;
-
- /*for the 2nd, 3rd, etc. series*/
- if(sIndex)
- y0 = data[i].$startY || y0;
-
- /*the max height limit*/
- if(y0 < (point0.y+1)) continue;
-
- if(value<0||(this._settings.yAxis&&value===0)){
- this.renderTextAt(true, true, x0+Math.floor(barWidth/2),y0,this._settings.label(data[i]));
- continue;
- }
-
- var color = this._settings.color.call(this,data[i]);
-
- /*drawing the gradient border of a bar*/
- if(this._settings.border){
- ctx.beginPath();
- ctx.fillStyle = color;
- this._setStakedBarPoints(ctx,x0-1,y0,barWidth+2,unit,value,0,point0.y);
- ctx.lineTo(x0,y0);
- ctx.fill();
-
- ctx.fillStyle = "#000000";
- ctx.globalAlpha = 0.37;
- ctx.beginPath();
- this._setStakedBarPoints(ctx,x0-1,y0,barWidth+2,unit,value,0,point0.y);
- ctx.fill();
- }
-
- /*drawing bar body*/
- ctx.globalAlpha = this._settings.alpha.call(this,data[i]);
- ctx.fillStyle = this._settings.color.call(this,data[i]);
- ctx.beginPath();
- var points = this._setStakedBarPoints(ctx,x0,y0,barWidth,unit,value,(this._settings.border?1:0),point0.y);
- ctx.fill();
- ctx.globalAlpha = 1;
-
- /*gradient*/
- if (inner_gradient){
- var gradParam = this._setBarGradient(ctx,x0,y0,x0+barWidth,points[1],inner_gradient,color,"y");
- ctx.fillStyle = gradParam.gradient;
- ctx.beginPath();
- var points = this._setStakedBarPoints(ctx,x0+gradParam.offset,y0,barWidth-gradParam.offset*2,unit,value,(this._settings.border?1:0),point0.y);
- ctx.fill();
- ctx.globalAlpha = 1;
- }
-
- /*sets a bar label*/
- this.renderTextAt(false, true, x0+Math.floor(barWidth/2),(points[1]+(y0-points[1])/2)-7,this._settings.label(data[i]));
- /*defines a map area for a bar*/
- map.addRect(data[i].id,[x0,points[1],points[0],(data[i].$startY||y0)],sIndex);
-
- /*the start position for the next series*/
- data[i].$startY = (this._settings.border?(points[1]+1):points[1]);
- }
- },
- /**
- * sets points for bar and returns the position of the bottom right point
- * @param: ctx - canvas object
- * @param: x0 - the x position of start point
- * @param: y0 - the y position of start point
- * @param: barWidth - bar width
- * @param: radius - the rounding radius of the top
- * @param: unit - the value defines the correspondence between item value and bar height
- * @param: value - item value
- * @param: offset - the offset from expected bar edge (necessary for drawing border)
- * @param: minY - the minimum y position for the bars ()
- */
- _setStakedBarPoints:function(ctx,x0,y0,barWidth,unit,value,offset,minY){
- /*start*/
- ctx.moveTo(x0,y0);
- /*start of left rounding*/
- var y1 = y0 - unit*value+offset;
- /*maximum height limit*/
- if(y1 maxValue)
- y = point0.y;
- /*the limit of the minimum value*/
- if(value < minValue)
- y = point1.y;
- return y;
- },
- _calculateParametersOfLineChart: function(ctx,data,point0,point1,sIndex){
- var params = {};
-
- /*maxValue - minValue*/
- var relValue;
-
- /*available height*/
- params.totalHeight = point1.y-point0.y;
-
- /*a space available for a single item*/
- //params.cellWidth = Math.round((point1.x-point0.x)/((!this._settings.offset&&this._settings.yAxis)?(data.length-1):data.length));
- params.cellWidth = Math.round((point1.x-point0.x)/((!this._settings.offset)?(data.length-1):data.length));
-
- /*scales*/
- var yax = !!this._settings.yAxis;
- var xax = !!this._settings.xAxis;
-
- var limits = (this._settings.view.indexOf("stacked")!=-1?this._getStackedLimits(data):this._getLimits());
- params.maxValue = limits.max;
- params.minValue = limits.min;
-
- /*draws x and y scales*/
- if(!sIndex)
- this._drawScales(ctx,data, point0, point1,params.minValue,params.maxValue,params.cellWidth);
-
- /*necessary for automatic scale*/
- if(yax){
- params.maxValue = parseFloat(this._settings.yAxis.end);
- params.minValue = parseFloat(this._settings.yAxis.start);
- }
-
- /*unit calculation (y_position = value*unit)*/
- var relativeValues = this._getRelativeValue(params.minValue,params.maxValue);
- relValue = relativeValues[0];
- params.valueFactor = relativeValues[1];
- params.unit = (relValue?params.totalHeight/relValue:10);
-
- params.startValue = 0;
- if(!yax){
- /*defines start value for better representation of small values*/
- params.startValue = (params.unit>10?params.unit:10);
- params.unit = (relValue?(params.totalHeight - params.startValue)/relValue:10);
- }
- return params;
- }
-};
-
-
-
-/* DHX DEPEND FROM FILE 'ext/chart/chart_bar.js'*/
-
-
-/*DHX:Depend ext/chart/chart_base.js*/
-dhtmlx.chart.bar = {
- /**
- * renders a bar chart
- * @param: ctx - canvas object
- * @param: data - object those need to be displayed
- * @param: x - the width of the container
- * @param: y - the height of the container
- * @param: sIndex - index of drawing chart
- */
- pvt_render_bar:function(ctx, data, point0, point1, sIndex, map){
- var maxValue,minValue;
- /*necessary if maxValue - minValue < 0*/
- var valueFactor;
- /*maxValue - minValue*/
- var relValue;
-
- var total_height = point1.y-point0.y;
-
- var yax = !!this._settings.yAxis;
- var xax = !!this._settings.xAxis;
-
- var limits = this._getLimits();
- maxValue = limits.max;
- minValue = limits.min;
-
- /*an available width for one bar*/
- var cellWidth = Math.floor((point1.x-point0.x)/data.length);
-
- /*draws x and y scales*/
- if(!sIndex&&!(this._settings.origin!="auto"&&!yax)){
- this._drawScales(ctx,data,point0, point1,minValue,maxValue,cellWidth);
- }
-
- /*necessary for automatic scale*/
- if(yax){
- maxValue = parseFloat(this._settings.yAxis.end);
- minValue = parseFloat(this._settings.yAxis.start);
- }
-
- /*unit calculation (bar_height = value*unit)*/
- var relativeValues = this._getRelativeValue(minValue,maxValue);
- relValue = relativeValues[0];
- valueFactor = relativeValues[1];
-
- var unit = (relValue?total_height/relValue:relValue);
- if(!yax&&!(this._settings.origin!="auto"&&xax)){
- /*defines start value for better representation of small values*/
- var startValue = 10;
- unit = (relValue?(total_height-startValue)/relValue:startValue);
- }
- /*if yAxis isn't set, but with custom origin */
- if(!sIndex&&(this._settings.origin!="auto"&&!yax)&&this._settings.origin>minValue){
- this._drawXAxis(ctx,data,point0,point1,cellWidth,point1.y-unit*(this._settings.origin-minValue));
- }
-
- /*a real bar width */
- var barWidth = parseInt(this._settings.width,10);
- if(this._series&&(barWidth*this._series.length+4)>cellWidth) barWidth = cellWidth/this._series.length-4;
- /*the half of distance between bars*/
- var barOffset = Math.floor((cellWidth - barWidth*this._series.length)/2);
- /*the radius of rounding in the top part of each bar*/
- var radius = (typeof this._settings.radius!="undefined"?parseInt(this._settings.radius,10):Math.round(barWidth/5));
-
- var inner_gradient = false;
- var gradient = this._settings.gradient;
-
- if(gradient && typeof(gradient) != "function"){
- inner_gradient = gradient;
- gradient = false;
- } else if (gradient){
- gradient = ctx.createLinearGradient(0,point1.y,0,point0.y);
- this._settings.gradient(gradient);
- }
- var scaleY = 0;
- /*draws a black line if the horizontal scale isn't defined*/
- if(!xax){
- this._drawLine(ctx,point0.x,point1.y+0.5,point1.x,point1.y+0.5,"#000000",1); //hardcoded color!
- }
-
- for(var i=0; i < data.length;i ++){
-
- var value = parseFloat(this._settings.value(data[i]));
- if(value>maxValue) value = maxValue;
- value -= minValue;
- value *= valueFactor;
-
- /*start point (bottom left)*/
- var x0 = point0.x + barOffset + i*cellWidth+(barWidth+1)*sIndex;
- var y0 = point1.y;
-
- if(value<0||(this._settings.yAxis&&value===0&&!(this._settings.origin!="auto"&&this._settings.origin>minValue))){
- this.renderTextAt(true, true, x0+Math.floor(barWidth/2),y0,this._settings.label(data[i]));
- continue;
- }
-
- /*takes start value into consideration*/
- if(!yax&&!(this._settings.origin!="auto"&&xax)) value += startValue/unit;
-
- var color = gradient||this._settings.color.call(this,data[i]);
-
- /*drawing the gradient border of a bar*/
- if(this._settings.border)
- this._drawBarBorder(ctx,x0,y0,barWidth,minValue,radius,unit,value,color);
-
- /*drawing bar body*/
- ctx.globalAlpha = this._settings.alpha.call(this,data[i]);
- var points = this._drawBar(ctx,point0,x0,y0,barWidth,minValue,radius,unit,value,color,gradient,inner_gradient);
- ctx.globalAlpha = 1;
-
- if (inner_gradient){
- this._drawBarGradient(ctx,x0,y0,barWidth,minValue,radius,unit,value,color,inner_gradient);
- }
- /*sets a bar label*/
- if(points[0]!=x0)
- this.renderTextAt(false, true, x0+Math.floor(barWidth/2),points[1],this._settings.label(data[i]));
- else
- this.renderTextAt(true, true, x0+Math.floor(barWidth/2),points[3],this._settings.label(data[i]));
- /*defines a map area for a bar*/
- map.addRect(data[i].id,[x0,points[3],points[2],points[1]],sIndex);
- }
- },
- _correctBarParams:function(ctx,x,y,value,unit,barWidth,minValue){
- var xax = this._settings.xAxis;
- var axisStart = y;
- if(!!xax&&this._settings.origin!="auto" && (this._settings.origin>minValue)){
- y -= (this._settings.origin-minValue)*unit;
- axisStart = y;
- value = value-(this._settings.origin-minValue);
- if(value < 0){
- value *= (-1);
- ctx.translate(x+barWidth,y);
- ctx.rotate(Math.PI);
- x = 0;
- y = 0;
- }
- y -= 0.5;
- }
-
- return {value:value,x0:x,y0:y,start:axisStart}
- },
- _drawBar:function(ctx,point0,x0,y0,barWidth,minValue,radius,unit,value,color,gradient,inner_gradient){
- ctx.save();
- ctx.fillStyle = color;
- var p = this._correctBarParams(ctx,x0,y0,value,unit,barWidth,minValue);
- var points = this._setBarPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,(this._settings.border?1:0));
- if (gradient&&!inner_gradient) ctx.lineTo(p.x0+(this._settings.border?1:0),point0.y); //fix gradient sphreading
- ctx.fill()
- ctx.restore();
- var x1 = p.x0;
- var x2 = (p.x0!=x0?x0+points[0]:points[0]);
- var y1 = (p.x0!=x0?(p.start-points[1]):y0);
- var y2 = (p.x0!=x0?p.start:points[1]);
- return [x1,y1,x2,y2];
- },
- _drawBarBorder:function(ctx,x0,y0,barWidth,minValue,radius,unit,value,color){
- ctx.save();
- var p = this._correctBarParams(ctx,x0,y0,value,unit,barWidth,minValue);
-
- ctx.fillStyle = color;
- this._setBarPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,0);
- ctx.lineTo(p.x0,0);
- ctx.fill()
-
-
- ctx.fillStyle = "#000000";
- ctx.globalAlpha = 0.37;
-
- this._setBarPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,0);
- ctx.fill()
- ctx.restore();
- },
- _drawBarGradient:function(ctx,x0,y0,barWidth,minValue,radius,unit,value,color,inner_gradient){
- ctx.save();
- //y0 -= (dhtmlx._isIE?0:0.5);
- var p = this._correctBarParams(ctx,x0,y0,value,unit,barWidth,minValue);
- var gradParam = this._setBarGradient(ctx,p.x0,p.y0,p.x0+barWidth,p.y0-unit*p.value+2,inner_gradient,color,"y");
- ctx.fillStyle = gradParam.gradient;
- this._setBarPoints(ctx,p.x0+gradParam.offset,p.y0,barWidth-gradParam.offset*2,radius,unit,p.value,gradParam.offset);
- ctx.fill()
- ctx.restore();
- },
- /**
- * sets points for bar and returns the position of the bottom right point
- * @param: ctx - canvas object
- * @param: x0 - the x position of start point
- * @param: y0 - the y position of start point
- * @param: barWidth - bar width
- * @param: radius - the rounding radius of the top
- * @param: unit - the value defines the correspondence between item value and bar height
- * @param: value - item value
- * @param: offset - the offset from expected bar edge (necessary for drawing border)
- */
- _setBarPoints:function(ctx,x0,y0,barWidth,radius,unit,value,offset){
- /*correction for displaing small values (when rounding radius is bigger than bar height)*/
- ctx.beginPath();
- //y0 = 0.5;
- var angle_corr = 0;
- if(radius>unit*value){
- var cosA = (radius-unit*value)/radius;
- angle_corr = -Math.acos(cosA)+Math.PI/2;
- }
- /*start*/
- ctx.moveTo(x0+offset,y0);
- /*start of left rounding*/
- var y1 = y0 - Math.floor(unit*value) + radius + (radius?0:offset);
- if(radius=0)||(a1>=0 && a2<=Math.PI)||(a1<=Math.PI && a2>=Math.PI))) return;
-
- if(a1<=0 && a2>=0){
- a1 = 0;
- line = false;
- this._drawSectorLine(ctx,x0,y0,R,a1,a2);
- }
- if(a1<=Math.PI && a2>=Math.PI){
- a2 = Math.PI;
- line = false;
- this._drawSectorLine(ctx,x0,y0,R,a1,a2);
- }
- /*the height of 3D pie*/
- var offset = (this._settings.height||Math.floor(R/4))/this._settings.cant;
- ctx.beginPath();
- ctx.arc(x0,y0,R,a1,a2,false);
- ctx.lineTo(x0+R*Math.cos(a2),y0+R*Math.sin(a2)+offset);
- ctx.arc(x0,y0+offset,R,a2,a1,true);
- ctx.lineTo(x0+R*Math.cos(a1),y0+R*Math.sin(a1));
- ctx.fill();
- if(line)
- ctx.stroke();
- },
- /**
- * draws a serctor arc
- */
- _drawSectorLine:function(ctx,x0,y0,R,a1,a2){
- ctx.beginPath();
- ctx.arc(x0,y0,R,a1,a2,false);
- ctx.stroke();
- },
- /**
- * adds a shadow to pie
- * @param: ctx - canvas object
- * @param: x - the horizontal position of the pie center
- * @param: y - the vertical position of the pie center
- * @param: R - pie radius
- */
- _addShadow:function(ctx,x,y,R){
- var shadows = ["#676767","#7b7b7b","#a0a0a0","#bcbcbc","#d1d1d1","#d6d6d6"];
- for(var i = shadows.length-1;i>-1;i--){
- ctx.beginPath();
- ctx.fillStyle = shadows[i];
- ctx.arc(x+2,y+2,R+i,0,Math.PI*2,true);
- ctx.fill();
- }
- },
- /**
- * returns a gray gradient
- * @param: gradient - gradient object
- */
- _getGrayGradient:function(gradient){
- gradient.addColorStop(0.0,"#ffffff");
- gradient.addColorStop(0.7,"#7a7a7a");
- gradient.addColorStop(1.0,"#000000");
- return gradient;
- },
- /**
- * adds gray radial gradient
- * @param: ctx - canvas object
- * @param: x - the horizontal position of the pie center
- * @param: y - the vertical position of the pie center
- * @param: radius - pie radius
- * @param: x0 - the horizontal position of a gradient center
- * @param: y0 - the vertical position of a gradient center
- */
- _showRadialGradient:function(ctx,x,y,radius,x0,y0){
- ctx.globalAlpha = 0.3;
- ctx.beginPath();
- var gradient;
- if(typeof this._settings.gradient!= "function"){
- gradient = ctx.createRadialGradient(x0,y0,radius/4,x,y,radius);
- gradient = this._getGrayGradient(gradient);
- }
- else gradient = this._settings.gradient(gradient);
- ctx.fillStyle = gradient;
- ctx.arc(x,y,radius,0,Math.PI*2,true);
- ctx.fill();
- ctx.globalAlpha = 1;
- },
- /**
- * returns the calculates pie parameters: center position and radius
- * @param: ctx - canvas object
- * @param: x0 - the horizontal position of the pie center
- * @param: y0 - the vertical position of the pie center
- * @param: R - pie radius
- * @param: alpha1 - the angle that defines the 1st edge of a sector
- * @param: alpha2 - the angle that defines the 2nd edge of a sector
- * @param: ky - the value that defines an angle of inclination
- * @param: text - label text
- * @param: in_width (boolean) - if label needs being displayed inside a pie
- */
- _drawSectorLabel:function(x0,y0,R,alpha1,alpha2,ky,text,in_width){
- var t = this.renderText(0,0,text,0,1);
- if (!t) return;
-
- //get existing width of text
- var labelWidth = t.scrollWidth;
- t.style.width = labelWidth+"px"; //adjust text label to fit all text
- if (labelWidth>x0) labelWidth = x0; //the text can't be greater than half of view
-
- //calculate expected correction based on default font metrics
- var width = 8;
- if (in_width) width = labelWidth/1.8;
- var alpha = alpha1+(alpha2-alpha1)/2;
-
- //calcualteion position and correction
- R = R-(width-8)/2;
- var corr_x = - width;
- var corr_y = -8;
- var align = "left";
-
- //for items in right upper sector
- if(alpha>=Math.PI/2 && alpha=Math.PI){
- corr_x = -labelWidth-corr_x+1;
- align = "right";
- }
-
- //calculate position of text
- //basically get point at center of pie sector
- var y = (y0+Math.floor(R*Math.sin(alpha)))*ky+corr_y;
- var x = x0+Math.floor((R+width/2)*Math.cos(alpha))+corr_x;
-
- //if pie sector starts in left of right part pie, related text
- //must be placed to the left of to the right of pie as well
- var left_end = (alpha2 < Math.PI/2+0.01)
- var left_start = (alpha < Math.PI/2);
- if (left_start && left_end)
- x = Math.max(x,x0+3); //right part of pie
- else if (!left_start && !left_end)
- x = Math.min(x,x0-labelWidth); //left part of pie
-
- /*correction for the lower sector of the 3D pie*/
- if (!in_width && ky<1 && y > y0*ky){
- y+= (this._settings.height||Math.floor(R/4));
- }
-
- //we need to set position of text manually, based on above calculations
- t.style.top = y+"px";
- t.style.left = x+"px";
- t.style.width = labelWidth+"px";
- t.style.textAlign = align;
- t.style.whiteSpace = "nowrap";
- }
-};
-
-dhtmlx.chart.pie3D = {
- pvt_render_pie3D:function(ctx,data,x,y,sIndex,map){
- this._renderPie(ctx,data,x,y,this._settings.cant,map);
- }
-}
-
-
-/* DHX DEPEND FROM FILE 'template.js'*/
-
-
-/*
- Template - handles html templates
-*/
-
-/*DHX:Depend dhtmlx.js*/
-
-dhtmlx.Template={
- _cache:{
- },
- empty:function(){
- return "";
- },
- setter:function(name, value){
- return dhtmlx.Template.fromHTML(value);
- },
- obj_setter:function(name,value){
- var f = dhtmlx.Template.setter(name,value);
- var obj = this;
- return function(){
- return f.apply(obj, arguments);
- };
- },
- fromHTML:function(str){
- if (typeof str == "function") return str;
- if (this._cache[str])
- return this._cache[str];
-
- //supported idioms
- // {obj} => value
- // {obj.attr} => named attribute or value of sub-tag in case of xml
- // {obj.attr?some:other} conditional output
- // {-obj => sub-template
- str=(str||"").toString();
- str=str.replace(/[\r\n]+/g,"\\n");
- str=str.replace(/\{obj\.([^}?]+)\?([^:]*):([^}]*)\}/g,"\"+(obj.$1?\"$2\":\"$3\")+\"");
- str=str.replace(/\{common\.([^}\(]*)\}/g,"\"+common.$1+\"");
- str=str.replace(/\{common\.([^\}\(]*)\(\)\}/g,"\"+(common.$1?common.$1(obj):\"\")+\"");
- str=str.replace(/\{obj\.([^}]*)\}/g,"\"+obj.$1+\"");
- str=str.replace(/#([a-z0-9_]+)#/gi,"\"+obj.$1+\"");
- str=str.replace(/\{obj\}/g,"\"+obj+\"");
- str=str.replace(/\{-obj/g,"{obj");
- str=str.replace(/\{-common/g,"{common");
- str="return \""+str+"\";";
- return this._cache[str]= Function("obj","common",str);
- }
-};
-
-dhtmlx.Type={
- /*
- adds new template-type
- obj - object to which template will be added
- data - properties of template
- */
- add:function(obj, data){
- //auto switch to prototype, if name of class was provided
- if (!obj.types && obj.prototype.types)
- obj = obj.prototype;
- //if (typeof data == "string")
- // data = { template:data };
-
- if (dhtmlx.assert_enabled())
- this.assert_event(data);
-
- var name = data.name||"default";
-
- //predefined templates - autoprocessing
- this._template(data);
- this._template(data,"edit");
- this._template(data,"loading");
-
- obj.types[name]=dhtmlx.extend(dhtmlx.extend({},(obj.types[name]||this._default)),data);
- return name;
- },
- //default template value - basically empty box with 5px margin
- _default:{
- css:"default",
- template:function(){ return ""; },
- template_edit:function(){ return ""; },
- template_loading:function(){ return "..."; },
- width:150,
- height:80,
- margin:5,
- padding:0
- },
- //template creation helper
- _template:function(obj,name){
- name = "template"+(name?("_"+name):"");
- var data = obj[name];
- //if template is a string - check is it plain string or reference to external content
- if (data && (typeof data == "string")){
- if (data.indexOf("->")!=-1){
- data = data.split("->");
- switch(data[0]){
- case "html": //load from some container on the page
- data = dhtmlx.html.getValue(data[1]).replace(/\"/g,"\\\"");
- break;
- case "http": //load from external file
- data = new dhtmlx.ajax().sync().get(data[1],{uid:(new Date()).valueOf()}).responseText;
- break;
- default:
- //do nothing, will use template as is
- break;
- }
- }
- obj[name] = dhtmlx.Template.fromHTML(data);
- }
- }
-};
-
-
-
-/* DHX DEPEND FROM FILE 'single_render.js'*/
-
-
-/*
- REnders single item.
- Can be used for elements without datastore, or with complex custom rendering logic
-
- @export
- render
-*/
-
-/*DHX:Depend template.js*/
-
-dhtmlx.SingleRender={
- _init:function(){
- },
- //convert item to the HTML text
- _toHTML:function(obj){
- /*
- this one doesn't support per-item-$template
- it has not sense, because we have only single item per object
- */
- return this.type._item_start(obj,this.type)+this.type.template(obj,this.type)+this.type._item_end;
- },
- //render self, by templating data object
- render:function(){
- if (!this.callEvent || this.callEvent("onBeforeRender",[this.data])){
- if (this.data)
- this._dataobj.innerHTML = this._toHTML(this.data);
- if (this.callEvent) this.callEvent("onAfterRender",[]);
- }
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'tooltip.js'*/
-
-
-/*
- UI: Tooltip
-
- @export
- show
- hide
-*/
-
-/*DHX:Depend tooltip.css*/
-/*DHX:Depend template.js*/
-/*DHX:Depend single_render.js*/
-
-dhtmlx.ui.Tooltip=function(container){
- this.name = "Tooltip";
- this.version = "3.0";
-
- if (dhtmlx.assert_enabled()) this._assert();
-
- if (typeof container == "string"){
- container = { template:container };
- }
-
- dhtmlx.extend(this, dhtmlx.Settings);
- dhtmlx.extend(this, dhtmlx.SingleRender);
- this._parseSettings(container,{
- type:"default",
- dy:0,
- dx:20
- });
-
- //create container for future tooltip
- this._dataobj = this._obj = document.createElement("DIV");
- this._obj.className="dhx_tooltip";
- dhtmlx.html.insertBefore(this._obj,document.body.firstChild);
-};
-dhtmlx.ui.Tooltip.prototype = {
- //show tooptip
- //pos - object, pos.x - left, pox.y - top
- show:function(data,pos){
- if (this._disabled) return;
- //render sefl only if new data was provided
- if (this.data!=data){
- this.data=data;
- this.render(data);
- }
- //show at specified position
- this._obj.style.top = pos.y+this._settings.dy+"px";
- this._obj.style.left = pos.x+this._settings.dx+"px";
- this._obj.style.display="block";
- },
- //hide tooltip
- hide:function(){
- this.data=null; //nulify, to be sure that on next show it will be fresh-rendered
- this._obj.style.display="none";
- },
- disable:function(){
- this._disabled = true;
- },
- enable:function(){
- this._disabled = false;
- },
- types:{
- "default":dhtmlx.Template.fromHTML("{obj.id}")
- },
- template_item_start:dhtmlx.Template.empty,
- template_item_end:dhtmlx.Template.empty
-};
-
-
-
-/* DHX DEPEND FROM FILE 'autotooltip.js'*/
-
-
-/*
- Behavior: AutoTooltip - links tooltip to data driven item
-*/
-
-/*DHX:Depend tooltip.js*/
-
-dhtmlx.AutoTooltip = {
- tooltip_setter:function(mode,value){
- var t = new dhtmlx.ui.Tooltip(value);
- this.attachEvent("onMouseMove",function(id,e){ //show tooltip on mousemove
- t.show(this.get(id),dhtmlx.html.pos(e));
- });
- this.attachEvent("onMouseOut",function(id,e){ //hide tooltip on mouseout
- t.hide();
- });
- this.attachEvent("onMouseMoving",function(id,e){ //hide tooltip just after moving start
- t.hide();
- });
- return t;
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'datastore.js'*/
-
-
-/*DHX:Depend dhtmlx.js*/
-
-/*
- DataStore is not a behavior, it standalone object, which represents collection of data.
- Call provideAPI to map data API
-
- @export
- exists
- idByIndex
- indexById
- get
- set
- refresh
- dataCount
- sort
- filter
- next
- previous
- clearAll
- first
- last
-*/
-dhtmlx.DataStore = function(){
- this.name = "DataStore";
-
- dhtmlx.extend(this, dhtmlx.EventSystem);
-
- this.setDriver("xml"); //default data source is an XML
- this.pull = {}; //hash of IDs
- this.order = dhtmlx.toArray(); //order of IDs
- this._tree_store = false;
-};
-
-dhtmlx.DataStore.prototype={
- //defines type of used data driver
- //data driver is an abstraction other different data formats - xml, json, csv, etc.
- setDriver:function(type){
- dhtmlx.assert(dhtmlx.DataDriver[type],"incorrect DataDriver");
- this.driver = dhtmlx.DataDriver[type];
- },
- _tree_parse:function(data){
- if (data.item){ //FIXME
- if (!(data.item instanceof Array))
- data.item=[data.item];
- for (var i=0; i < data.item.length; i++) {
- var item = data.item[i];
- var id = this.id(item);
-
- data.item[i]=id;
- this.pull[id]=item;
- item.parent = data.id;
- item.level = data.level+1;
- this._tree_parse(item);
- };
- }
- },
- //process incoming raw data
- _parse:function(data){
- //get size and position of data
- var info = this.driver.getInfo(data);
- //get array of records
-
- var recs = this.driver.getRecords(data);
- var from = (info._from||0)*1;
- var j=0;
- for (var i=0; ito){ //can be in case of backward shift-selection
- var a=to; to=from; from=a;
- }
- }
- return this.getIndexRange(from,to);
- },
- //converts range of indexes to array of all IDs between them
- getIndexRange:function(from,to){
- to=Math.min(to,this.dataCount()-1);
-
- var ret=dhtmlx.toArray(); //result of method is rich-array
- for (var i=from; i <= to; i++)
- ret.push(this.get(this.order[i]));
- return ret;
- },
- //returns total count of elements
- dataCount:function(){
- return this.order.length;
- },
- //returns truy if item with such ID exists
- exists:function(id){
- return !!(this.pull[id]);
- },
- //nextmethod is not visible on component level, check DataMove.move
- //moves item from source index to the target index
- move:function(sindex,tindex){
- if (sindex<0 || tindex<0){
- dhtmlx.error("DataStore::move","Incorrect indexes");
- return;
- }
-
- var id = this.idByIndex(sindex);
- var obj = this.get(id);
-
- this.order.removeAt(sindex); //remove at old position
- //if (sindex data_size){
- dhtmlx.log("Warning","DataStore:add","Index of out of bounds");
- index = Math.min(this.order.length,index);
- }
-
- if (!this.callEvent("onbeforeAdd",[id,index])) return;
-
- if (this.exists(id)) return dhtmlx.error("Not unique ID");
-
- this.pull[id]=obj;
- this.order.insertAt(id,index);
- if (this._filter_order){ //adding during filtering
- //we can't know the location of new item in full dataset, making suggestion
- //put at end by default
- var original_index = this._filter_order.length;
- //put at start only if adding to the start and some data exists
- if (!index && this.order.length)
- original_index = 0;
-
- this._filter_order.insertAt(id,original_index);
- }
-
- this.callEvent("onafterAdd",[id,index]);
- //repaint signal
- this.callEvent("onStoreUpdated",[id,obj,"add"]);
- return id;
- },
-
- //removes element from datastore
- remove:function(id){
- //id can be an array of IDs - result of getSelect, for example
- if (id instanceof Array){
- for (var i=0; i < id.length; i++)
- this.remove(id[i]);
- return;
- }
- if (!this.callEvent("onbeforedelete",[id])) return;
- if (!this.exists(id)) return dhtmlx.error("Not existing ID",id);
- var obj = this.get(id); //save for later event
- //clear from collections
- this.order.remove(id);
- if (this._filter_order)
- this._filter_order.remove(id);
-
- delete this.pull[id];
- this.callEvent("onafterdelete",[id]);
- //repaint signal
- this.callEvent("onStoreUpdated",[id,obj,"delete"]);
- },
- //deletes all records in datastore
- clearAll:function(){
- //instead of deleting one by one - just reset inner collections
- this.pull = {};
- this.order = dhtmlx.toArray();
- this._filter_order = null;
- this.callEvent("onClearAll",[]);
- this.refresh();
- },
- //converts id to index
- idByIndex:function(index){
- if (index>=this.order.length || index<0)
- dhtmlx.log("Warning","DataStore::idByIndex Incorrect index");
-
- return this.order[index];
- },
- //converts index to id
- indexById:function(id){
- var res = this.order.find(id); //slower than idByIndex
-
- if (res == -1)
- dhtmlx.log("Warning","DataStore::indexById Non-existing ID: "+ id);
-
- return res;
- },
- //returns ID of next element
- next:function(id,step){
- return this.order[this.indexById(id)+(step||1)];
- },
- //returns ID of first element
- first:function(){
- return this.order[0];
- },
- //returns ID of last element
- last:function(){
- return this.order[this.order.length-1];
- },
- //returns ID of previous element
- previous:function(id,step){
- return this.order[this.indexById(id)-(step||1)];
- },
- /*
- sort data in collection
- by - settings of sorting
-
- or
-
- by - sorting function
- dir - "asc" or "desc"
-
- or
-
- by - property
- dir - "asc" or "desc"
- as - type of sortings
-
- Sorting function will accept 2 parameters and must return 1,0,-1, based on desired order
- */
- sort:function(by, dir, as){
- var sort = by;
- if (typeof by == "function")
- sort = {as:by, dir:dir};
- else if (typeof by == "string")
- sort = {by:by, dir:dir, as:as};
-
-
- var parameters = [sort.by, sort.dir, sort.as];
- if (!this.callEvent("onbeforesort",parameters)) return;
-
- if (this.order.length){
- var sorter = dhtmlx.sort.create(sort);
- //get array of IDs
- var neworder = this.getRange(this.first(), this.last());
- neworder.sort(sorter);
- this.order = neworder.map(function(obj){ return this.id(obj); },this);
- }
-
- //repaint self
- this.refresh();
-
- this.callEvent("onaftersort",parameters);
- },
- /*
- Filter datasource
-
- text - property, by which filter
- value - filter mask
-
- or
-
- text - filter method
-
- Filter method will receive data object and must return true or false
- */
- filter:function(text,value){
- //remove previous filtering , if any
- if (this._filter_order){
- this.order = this._filter_order;
- delete this._filter_order;
- }
- //if text not define -just unfilter previous state and exit
- if (text){
- var filter = text;
- if (typeof text == "string"){
- text = dhtmlx.Template.setter(0,text);
- filter = function(obj,value){ //default filter - string start from, case in-sensitive
- return text(obj).toLowerCase().indexOf(value)!=-1;
- };
- }
-
- value = (value||"").toString().toLowerCase();
- var neworder = dhtmlx.toArray();
- this.order.each(function(id){
- if (filter(this.get(id),value))
- neworder.push(id);
- },this);
- //set new order of items, store original
- this._filter_order = this.order;
- this.order = neworder;
- }
- //repaint self
- this.refresh();
- },
- /*
- Iterate through collection
- */
- each:function(method,master){
- for (var i=0; ib?1:(ab?1:(ab?1:(a max) max = property(obj)*1;
- });
- return max;
- },
- _split_data_by:function(stats){
- var any=function(property, data){
- property = dhtmlx.Template.setter(0,property);
- return property(data[0]);
- };
- var key = dhtmlx.Template.setter(0,stats.by);
- if (!stats.map[key])
- stats.map[key] = [key, any];
-
- var groups = {};
- var labels = [];
- this.data.each(function(data){
- var current = key(data);
- if (!groups[current]){
- labels.push({id:current});
- groups[current] = dhtmlx.toArray();
- }
- groups[current].push(data);
- });
- for (var prop in stats.map){
- var functor = (stats.map[prop][1]||any);
- if (typeof functor != "function")
- functor = this[functor];
-
- for (var i=0; i < labels.length; i++) {
- labels[i][prop]=functor.call(this, stats.map[prop][0], groups[labels[i].id]);
- }
- }
-// if (this._settings.sort)
-// labels.sortBy(stats.sort);
-
- this._not_grouped_data = this.data;
- this.data = new dhtmlx.DataStore();
- this.data.provideApi(this,true);
- this._init_group_data_event(this.data, this);
- this.parse(labels,"json");
- },
- group:function(config,mode){
- this.ungroup(false);
- this._split_data_by(config);
- if (mode!==false)
- this.render();
- },
- ungroup:function(mode){
- if (this._not_grouped_data){
- this.data = this._not_grouped_data;
- this.data.provideApi(this, true);
- }
- if (mode!==false)
- this.render();
- },
- group_setter:function(name, config){
- dhtmlx.assert(typeof config == "object", "Incorrect group value");
- dhtmlx.assert(config.by,"group.by is mandatory");
- dhtmlx.assert(config.map,"group.map is mandatory");
- return config;
- },
- //need to be moved to more appropriate object
- sort_setter:function(name, config){
- if (typeof config != "object")
- config = { by:config };
-
- this._mergeSettings(config,{
- as:"string",
- dir:"asc"
- });
- return config;
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'key.js'*/
-
-
-/*
- Behavior:KeyEvents - hears keyboard
-*/
-dhtmlx.KeyEvents = {
- _init:function(){
- //attach handler to the main container
- dhtmlx.event(this._obj,"keypress",this._onKeyPress,this);
- },
- //called on each key press , when focus is inside of related component
- _onKeyPress:function(e){
- e=e||event;
- var code = e.which||e.keyCode; //FIXME better solution is required
- this.callEvent((this._edit_id?"onEditKeyPress":"onKeyPress"),[code,e.ctrlKey,e.shiftKey,e]);
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'mouse.js'*/
-
-
-/*
- Behavior:MouseEvents - provides inner evnets for mouse actions
-*/
-dhtmlx.MouseEvents={
- _init: function(){
- //attach dom events if related collection is defined
- if (this.on_click){
- dhtmlx.event(this._obj,"click",this._onClick,this);
- dhtmlx.event(this._obj,"contextmenu",this._onContext,this);
- }
- if (this.on_dblclick)
- dhtmlx.event(this._obj,"dblclick",this._onDblClick,this);
- if (this.on_mouse_move){
- dhtmlx.event(this._obj,"mousemove",this._onMouse,this);
- dhtmlx.event(this._obj,(dhtmlx._isIE?"mouseleave":"mouseout"),this._onMouse,this);
- }
-
- },
- //inner onclick object handler
- _onClick: function(e) {
- return this._mouseEvent(e,this.on_click,"ItemClick");
- },
- //inner ondblclick object handler
- _onDblClick: function(e) {
- return this._mouseEvent(e,this.on_dblclick,"ItemDblClick");
- },
- //process oncontextmenu events
- _onContext: function(e) {
- var id = dhtmlx.html.locate(e, this._id);
- if (id && !this.callEvent("onBeforeContextMenu", [id,e]))
- return dhtmlx.html.preventEvent(e);
- },
- /*
- event throttler - ignore events which occurs too fast
- during mouse moving there are a lot of event firing - we need no so much
- also, mouseout can fire when moving inside the same html container - we need to ignore such fake calls
- */
- _onMouse:function(e){
- if (dhtmlx._isIE) //make a copy of event, will be used in timed call
- e = document.createEventObject(event);
-
- if (this._mouse_move_timer) //clear old event timer
- window.clearTimeout(this._mouse_move_timer);
-
- //this event just inform about moving operation, we don't care about details
- this.callEvent("onMouseMoving",[e]);
- //set new event timer
- this._mouse_move_timer = window.setTimeout(dhtmlx.bind(function(){
- //called only when we have at least 100ms after previous event
- if (e.type == "mousemove")
- this._onMouseMove(e);
- else
- this._onMouseOut(e);
- },this),500);
- },
- //inner mousemove object handler
- _onMouseMove: function(e) {
- if (!this._mouseEvent(e,this.on_mouse_move,"MouseMove"))
- this.callEvent("onMouseOut",[e||event]);
- },
- //inner mouseout object handler
- _onMouseOut: function(e) {
- this.callEvent("onMouseOut",[e||event]);
- },
- //common logic for click and dbl-click processing
- _mouseEvent:function(e,hash,name){
- e=e||event;
- var trg=e.target||e.srcElement;
- var css = "";
- var id = null;
- var found = false;
- //loop through all parents
- while (trg && trg.parentNode){
- if (!found && trg.getAttribute){ //if element with ID mark is not detected yet
- id = trg.getAttribute(this._id); //check id of current one
- if (id){
- if (trg.getAttribute("userdata"))
- this.callEvent("onLocateData",[id,trg]);
- if (!this.callEvent("on"+name,[id,e,trg])) return; //it will be triggered only for first detected ID, in case of nested elements
- found = true; //set found flag
- }
- }
- css=trg.className;
- if (css){ //check if pre-defined reaction for element's css name exists
- css = css.split(" ");
- css = css[0]||css[1]; //FIXME:bad solution, workaround css classes which are starting from whitespace
- if (hash[css])
- return hash[css].call(this,e,id,trg);
- }
- trg=trg.parentNode;
- }
- return found; //returns true if item was located and event was triggered
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'config.js'*/
-
-
-/*
- Behavior:Settings
-
- @export
- customize
- config
-*/
-
-/*DHX:Depend template.js*/
-/*DHX:Depend dhtmlx.js*/
-
-dhtmlx.Settings={
- _init:function(){
- /*
- property can be accessed as this.config.some
- in same time for inner call it have sense to use _settings
- because it will be minified in final version
- */
- this._settings = this.config= {};
- },
- define:function(property, value){
- if (typeof property == "object")
- return this._parseSeetingColl(property);
- return this._define(property, value);
- },
- _define:function(property,value){
- dhtmlx.assert_settings.call(this,property,value);
-
- //method with name {prop}_setter will be used as property setter
- //setter is optional
- var setter = this[property+"_setter"];
- return this._settings[property]=setter?setter.call(this,property,value):value;
- },
- //process configuration object
- _parseSeetingColl:function(coll){
- if (coll){
- for (var a in coll) //for each setting
- this._define(a,coll[a]); //set value through config
- }
- },
- //helper for object initialization
- _parseSettings:function(obj,initial){
- //initial - set of default values
- var settings = dhtmlx.extend({},initial);
- //code below will copy all properties over default one
- if (typeof obj == "object" && !obj.tagName)
- dhtmlx.extend(settings,obj);
- //call config for each setting
- this._parseSeetingColl(settings);
- },
- _mergeSettings:function(config, defaults){
- for (var key in defaults)
- switch(typeof config[key]){
- case "object":
- config[key] = this._mergeSettings((config[key]||{}), defaults[key]);
- break;
- case "undefined":
- config[key] = defaults[key];
- break;
- default: //do nothing
- break;
- }
- return config;
- },
- //helper for html container init
- _parseContainer:function(obj,name,fallback){
- /*
- parameter can be a config object, in such case real container will be obj.container
- or it can be html object or ID of html object
- */
- if (typeof obj == "object" && !obj.tagName)
- obj=obj.container;
- this._obj = dhtmlx.toNode(obj);
- if (!this._obj && fallback)
- this._obj = fallback(obj);
-
- dhtmlx.assert(this._obj, "Incorrect html container");
-
- this._obj.className+=" "+name;
- this._obj.onselectstart=function(){return false;}; //block selection by default
- this._dataobj = this._obj;//separate reference for rendering modules
- },
- //apply template-type
- _set_type:function(name){
- //parameter can be a hash of settings
- if (typeof name == "object")
- return this.type_setter("type",name);
-
- dhtmlx.assert(this.types, "RenderStack :: Types are not defined");
- dhtmlx.assert(this.types[name],"RenderStack :: Inccorect type name",name);
- //or parameter can be a name of existing template-type
- this.type=dhtmlx.extend({},this.types[name]);
- this.customize(); //init configs
- },
- customize:function(obj){
- //apply new properties
- if (obj) dhtmlx.extend(this.type,obj);
-
- //init tempaltes for item start and item end
- this.type._item_start = dhtmlx.Template.fromHTML(this.template_item_start(this.type));
- this.type._item_end = this.template_item_end(this.type);
-
- //repaint self
- this.render();
- },
- //config.type - creates new template-type, based on configuration object
- type_setter:function(mode,value){
- this._set_type(typeof value == "object"?dhtmlx.Type.add(this,value):value);
- return value;
- },
- //config.template - creates new template-type with defined template string
- template_setter:function(mode,value){
- return this.type_setter("type",{template:value});
- },
- //config.css - css name for top level container
- css_setter:function(mode,value){
- this._obj.className += " "+value;
- return value;
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'compatibility.js'*/
-
-
-/*
- Collection of compatibility hacks
-*/
-
-/*DHX:Depend dhtmlx.js*/
-
-dhtmlx.compat=function(name, obj){
- //check if name hash present, and applies it when necessary
- if (dhtmlx.compat[name])
- dhtmlx.compat[name](obj);
-};
-
-
-(function(){
- if (!window.dhtmlxError){
- //dhtmlxcommon is not included
-
- //create fake error tracker for connectors
- var dummy = function(){};
- window.dhtmlxError={ catchError:dummy, throwError:dummy };
- //helpers instead of ones from dhtmlxcommon
- window.convertStringToBoolean=function(value){
- return !!value;
- };
- window.dhtmlxEventable = function(node){
- dhtmlx.extend(node,dhtmlx.EventSystem);
- };
- //imitate ajax layer of dhtmlxcommon
- var loader = {
- getXMLTopNode:function(name){
-
- },
- doXPath:function(path){
- return dhtmlx.DataDriver.xml.xpath(this.xml,path);
- },
- xmlDoc:{
- responseXML:true
- }
- };
- //wrap ajax methods of dataprocessor
- dhtmlx.compat.dataProcessor=function(obj){
- //FIXME
- //this is pretty ugly solution - we replace whole method , so changes in dataprocessor need to be reflected here
-
- var sendData = "_sendData";
- var in_progress = "_in_progress";
- var tMode = "_tMode";
- var waitMode = "_waitMode";
-
- obj[sendData]=function(a1,rowId){
- if (!a1) return; //nothing to send
- if (rowId)
- this[in_progress][rowId]=(new Date()).valueOf();
-
- if (!this.callEvent("onBeforeDataSending",rowId?[rowId,this.getState(rowId)]:[])) return false;
-
- var a2 = this;
- var a3=this.serverProcessor;
- if (this[tMode]!="POST")
- //use dhtmlx.ajax instead of old ajax layer
- dhtmlx.ajax().get(a3+((a3.indexOf("?")!=-1)?"&":"?")+this.serialize(a1,rowId),"",function(t,x,xml){
- loader.xml = dhtmlx.DataDriver.xml.checkResponse(t,x);
- a2.afterUpdate(a2, null, null, null, loader);
- });
- else
- dhtmlx.ajax().post(a3,this.serialize(a1,rowId),function(t,x,xml){
- loader.xml = dhtmlx.DataDriver.xml.checkResponse(t,x);
- a2.afterUpdate(a2, null, null, null, loader);
- });
-
- this[waitMode]++;
- };
- };
- }
-
-})();
-
-
-/* DHX DEPEND FROM FILE 'compatibility_layout.js'*/
-
-
-/*DHX:Depend dhtmlx.js*/
-/*DHX:Depend compatibility.js*/
-
-if (!dhtmlx.attaches)
- dhtmlx.attaches = {};
-
-dhtmlx.attaches.attachAbstract=function(name, conf){
- var obj = document.createElement("DIV");
- obj.id = "CustomObject_"+dhtmlx.uid();
- obj.style.width = "100%";
- obj.style.height = "100%";
- obj.cmp = "grid";
- document.body.appendChild(obj);
- this.attachObject(obj.id);
-
- conf.container = obj.id;
-
- var that = this.vs[this.av];
- that.grid = new window[name](conf);
-
- that.gridId = obj.id;
- that.gridObj = obj;
-
-
- that.grid.setSizes = function(){
- if (this.resize) this.resize();
- else this.render();
- };
-
- var method_name="_viewRestore";
- return this.vs[this[method_name]()].grid;
-};
-dhtmlx.attaches.attachDataView = function(conf){
- return this.attachAbstract("dhtmlXDataView",conf);
-};
-dhtmlx.attaches.attachChart = function(conf){
- return this.attachAbstract("dhtmlXChart",conf);
-};
-
-dhtmlx.compat.layout = function(){};
-
-
-
-/* DHX DEPEND FROM FILE 'load.js'*/
-
-
-/*
- ajax operations
-
- can be used for direct loading as
- dhtmlx.ajax(ulr, callback)
- or
- dhtmlx.ajax().get(url)
- dhtmlx.ajax().post(url)
-
-*/
-
-/*DHX:Depend datastore.js*/
-/*DHX:Depend dhtmlx.js*/
-
-dhtmlx.ajax = function(url,call,master){
- //if parameters was provided - made fast call
- if (arguments.length!==0){
- var http_request = new dhtmlx.ajax();
- if (master) http_request.master=master;
- http_request.get(url,null,call);
- }
- if (!this.getXHR) return new dhtmlx.ajax(); //allow to create new instance without direct new declaration
-
- return this;
-};
-dhtmlx.ajax.prototype={
- //creates xmlHTTP object
- getXHR:function(){
- if (dhtmlx._isIE)
- return new ActiveXObject("Microsoft.xmlHTTP");
- else
- return new XMLHttpRequest();
- },
- /*
- send data to the server
- params - hash of properties which will be added to the url
- call - callback, can be an array of functions
- */
- send:function(url,params,call){
- var x=this.getXHR();
- if (typeof call == "function")
- call = [call];
- //add extra params to the url
- if (typeof params == "object"){
- var t=[];
- for (var a in params)
- t.push(a+"="+encodeURIComponent(params[a]));// utf-8 escaping
- params=t.join("&");
- }
- if (params && !this.post){
- url=url+(url.indexOf("?")!=-1 ? "&" : "?")+params;
- params=null;
- }
-
- x.open(this.post?"POST":"GET",url,!this._sync);
- if (this.post)
- x.setRequestHeader('Content-type','application/x-www-form-urlencoded');
-
- //async mode, define loading callback
- if (!this._sync){
- var self=this;
- x.onreadystatechange= function(){
- if (!x.readyState || x.readyState == 4){
- dhtmlx.log_full_time("data_loading"); //log rendering time
- if (call && self)
- for (var i=0; i < call.length; i++) //there can be multiple callbacks
- if (call[i])
- call[i].call((self.master||self),x.responseText,x.responseXML,x);
- self.master=null;
- call=x=self=null; //anti-leak
- }
- };
- }
-
- x.send(params||null);
- return x; //return XHR, which can be used in case of sync. mode
- },
- //GET request
- get:function(url,params,call){
- this.post=false;
- return this.send(url,params,call);
- },
- //POST request
- post:function(url,params,call){
- this.post=true;
- return this.send(url,params,call);
- },
- sync:function(){
- this._sync = true;
- return this;
- }
-};
-
-
-/*
- Behavior:DataLoader - load data in the component
-
- @export
- load
- parse
-*/
-dhtmlx.DataLoader={
- _init:function(){
- //prepare data store
- this.data = new dhtmlx.DataStore();
- },
- //loads data from external URL
- load:function(url,call){
- this.callEvent("onXLS",[]);
- if (typeof call == "string"){ //second parameter can be a loading type or callback
- this.data.setDriver(call);
- call = arguments[2];
- }
- //prepare data feed for dyn. loading
- if (!this.data.feed)
- this.data.feed = function(from,count){
- //allow only single request at same time
- if (this._load_count)
- return this._load_count=[from,count]; //save last ignored request
- else
- this._load_count=true;
-
- this.load(url+((url.indexOf("?")==-1)?"?":"&")+"posStart="+from+"&count="+count,function(){
- //after loading check if we have some ignored requests
- var temp = this._load_count;
- this._load_count = false;
- if (typeof temp =="object")
- this.data.feed.apply(this, temp); //load last ignored request
- });
- };
- //load data by async ajax call
- dhtmlx.ajax(url,[this._onLoad,call],this);
- },
- //loads data from object
- parse:function(data,type){
- this.callEvent("onXLS",[]);
- if (type)
- this.data.setDriver(type);
- this._onLoad(data,null);
- },
- //default after loading callback
- _onLoad:function(text,xml,loader){
- this.data._parse(this.data.driver.toObject(text,xml));
- this.callEvent("onXLE",[]);
- }
-};
-
-/*
- Abstraction layer for different data types
-*/
-
-dhtmlx.DataDriver={};
-dhtmlx.DataDriver.json={
- //convert json string to json object if necessary
- toObject:function(data){
- if (typeof data == "string"){
- eval ("dhtmlx.temp="+data);
- return dhtmlx.temp;
- }
- return data;
- },
- //get array of records
- getRecords:function(data){
- if (data && !(data instanceof Array))
- return [data];
- return data;
- },
- //get hash of properties for single record
- getDetails:function(data){
- return data;
- },
- //get count of data and position at which new data need to be inserted
- getInfo:function(data){
- return {
- _size:(data.total_count||0),
- _from:(data.pos||0)
- };
- }
-};
-
-dhtmlx.DataDriver.html={
- /*
- incoming data can be
- - collection of nodes
- - ID of parent container
- - HTML text
- */
- toObject:function(data){
- if (typeof data == "string"){
- var t=null;
- if (data.indexOf("<")==-1) //if no tags inside - probably its an ID
- t = dhtmlx.toNode(data);
- if (!t){
- t=document.createElement("DIV");
- t.innerHTML = data;
- }
-
- return t.getElementsByTagName(this.tag);
- }
- return data;
- },
- //get array of records
- getRecords:function(data){
- if (data.tagName)
- return data.childNodes;
- return data;
- },
- //get hash of properties for single record
- getDetails:function(data){
- return dhtmlx.DataDriver.xml.tagToObject(data);
- },
- //dyn loading is not supported by HTML data source
- getInfo:function(data){
- return {
- _size:0,
- _from:0
- };
- },
- tag: "LI"
-};
-
-dhtmlx.DataDriver.jsarray={
- //eval jsarray string to jsarray object if necessary
- toObject:function(data){
- if (typeof data == "string"){
- eval ("dhtmlx.temp="+data);
- return dhtmlx.temp;
- }
- return data;
- },
- //get array of records
- getRecords:function(data){
- return data;
- },
- //get hash of properties for single record, in case of array they will have names as "data{index}"
- getDetails:function(data){
- var result = {};
- for (var i=0; i < data.length; i++)
- result["data"+i]=data[i];
-
- return result;
- },
- //dyn loading is not supported by js-array data source
- getInfo:function(data){
- return {
- _size:0,
- _from:0
- };
- }
-};
-
-dhtmlx.DataDriver.csv={
- //incoming data always a string
- toObject:function(data){
- return data;
- },
- //get array of records
- getRecords:function(data){
- return data.split(this.row);
- },
- //get hash of properties for single record, data named as "data{index}"
- getDetails:function(data){
- data = this.stringToArray(data);
- var result = {};
- for (var i=0; i < data.length; i++)
- result["data"+i]=data[i];
-
- return result;
- },
- //dyn loading is not supported by csv data source
- getInfo:function(data){
- return {
- _size:0,
- _from:0
- };
- },
- //split string in array, takes string surrounding quotes in account
- stringToArray:function(data){
- data = data.split(this.cell);
- for (var i=0; i < data.length; i++)
- data[i] = data[i].replace(/^[ \t\n\r]*(\"|)/g,"").replace(/(\"|)[ \t\n\r]*$/g,"");
- return data;
- },
- row:"\n", //default row separator
- cell:"," //default cell separator
-};
-
-dhtmlx.DataDriver.xml={
- //convert xml string to xml object if necessary
- toObject:function(text,xml){
- if (xml && (xml=this.checkResponse(text,xml))) //checkResponse - fix incorrect content type and extra whitespaces errors
- return xml;
- if (typeof text == "string"){
- return this.fromString(text);
- }
- return text;
- },
- //get array of records
- getRecords:function(data){
- return this.xpath(data,this.records);
- },
- records:"/*/item",
- userdata:"/*/userdata",
- //get hash of properties for single record
- getDetails:function(data){
- return this.tagToObject(data,{});
- },
- getUserData:function(data,col){
- col = col || {};
- var ud = this.xpath(data,this.userdata);
- for (var i=0; i < ud.length; i++) {
- var udx = this.tagToObject(ud[i]);
- col[udx.name] = udx.value;
- }
- return col;
- },
- //get count of data and position at which new data_loading need to be inserted
- getInfo:function(data){
- return {
- _size:(data.documentElement.getAttribute("total_count")||0),
- _from:(data.documentElement.getAttribute("pos")||0)
- };
- },
- //xpath helper
- xpath:function(xml,path){
- if (window.XPathResult){ //FF, KHTML, Opera
- var node=xml;
- if(xml.nodeName.indexOf("document")==-1)
- xml=xml.ownerDocument;
- var res = [];
- var col = xml.evaluate(path, node, null, XPathResult.ANY_TYPE, null);
- var temp = col.iterateNext();
- while (temp){
- res.push(temp);
- temp = col.iterateNext();
- }
- return res;
- } //IE
- return xml.selectNodes(path);
- },
- //convert xml tag to js object, all subtags and attributes are mapped to the properties of result object
- tagToObject:function(tag,z){
- z=z||{};
- //map attributes
- var a=tag.attributes;
- for (var i=0; i5?10:5);
- step = parseInt(stepVal,10)*calculStep;
-
- if(step>Math.abs(nmin))
- start = (nmin<0?-step:0);
- else{
- var absNmin = Math.abs(nmin);
- var powerStart = Math.floor(this._log10(absNmin));
- var nminVal = absNmin/Math.pow(10,powerStart);
- start = Math.ceil(nminVal*10)/10*Math.pow(10,powerStart)-step;
- if(nmin<0) start =-start-2*step;
- }
- var end = start;
- while(end1)
- for(var i=1; i < this._series.length;i++){
- var maxI = this.max(this._series[i].value);
- var minI = this.min(this._series[i].value);
- if (maxI > maxValue) maxValue = maxI;
- if (minI < minValue) minValue = minI;
- }
- }
- return {max:maxValue,min:minValue};
- },
- _log10:function(n){
- var method_name="log";
- return Math.floor((Math[method_name](n)/Math.LN10));
- },
- _drawXAxisLabel:function(x,y,obj,center,top){
- if (!this._settings.xAxis) return;
- var elem = this.renderTextAt(top, center, x,y,this._settings.xAxis.template(obj));
- },
- _drawXAxisLine:function(ctx,x,y1,y2){
- if (!this._settings.xAxis||!this._settings.xAxis.lines) return;
- this._drawLine(ctx,x,y1,x,y2,this._settings.xAxis.color,0.2);
- },
- _drawLine:function(ctx,x1,y1,x2,y2,color,width){
- ctx.strokeStyle = color;
- ctx.lineWidth = width;
- ctx.beginPath();
- ctx.moveTo(x1,y1);
- ctx.lineTo(x2,y2);
- ctx.stroke();
- },
- _getRelativeValue:function(minValue,maxValue){
- var relValue;
- var valueFactor = 1;
- if(maxValue != minValue){
- relValue = maxValue - minValue;
- if(Math.abs(relValue) < 1){
- while(Math.abs(relValue)<1){
- valueFactor *= 10;
- relValue *= valueFactor;
- }
- }
- }
- else relValue = minValue;
- return [relValue,valueFactor];
- },
- _rainbow : [
- function(pos){ return "#FF"+dhtmlx.math.toHex(pos/2,2)+"00";},
- function(pos){ return "#FF"+dhtmlx.math.toHex(pos/2+128,2)+"00";},
- function(pos){ return "#"+dhtmlx.math.toHex(255-pos,2)+"FF00";},
- function(pos){ return "#00FF"+dhtmlx.math.toHex(pos,2);},
- function(pos){ return "#00"+dhtmlx.math.toHex(255-pos,2)+"FF";},
- function(pos){ return "#"+dhtmlx.math.toHex(pos,2)+"00FF";}
- ],
- /**
- * adds series to the chart (value and color properties)
- * @param: obj - obj with configuration properties
- */
- addSeries:function(obj){
- var temp = this._settings; this._settings = dhtmlx.extend({},temp);
- this._parseSettings(obj,{});
- this._series.push(this._settings);
- this._settings = temp;
- },
- /*switch global settings to serit in question*/
- _switchSerie:function(id, tag){
- this._active_serie = tag.getAttribute("userdata");
- for (var i=0; i < this._series.length; i++) {
- var tip = this._series[i].tooltip;
- if (tip)
- tip.disable();
- }
- var tip = this._series[this._active_serie].tooltip;
- if (tip)
- tip.enable();
- },
- /**
- * renders legend block
- * @param: ctx - canvas object
- * @param: data - object those need to be displayed
- * @param: width - the width of the container
- * @param: height - the height of the container
- */
- _drawLegend:function(ctx,data,width,height){
- /*position of the legend block*/
- var x=0,y=0;
- /*legend config*/
- var legend = this._settings.legend;
- /*the legend sizes*/
- var legendHeight,legendWidth;
-
- var style = (this._settings.legend.layout!="x"?"width:"+legend.width+"px":"");
- /*creation of legend container*/
- var legendContainer = dhtmlx.html.create("DIV",{
- "class":"dhx_chart_legend",
- "style":"left:"+x+"px; top:"+y+"px;"+style
- },"");
- this._obj.appendChild(legendContainer);
- /*rendering legend text items*/
- var legendItems = [];
- if(!legend.values)
- for(var i = 0; i < data.length; i++){
- legendItems.push(this._drawLegendText(legendContainer,legend.template(data[i])));
- }
- else
- for(var i = 0; i < legend.values.length; i++){
- legendItems.push(this._drawLegendText(legendContainer,legend.values[i].text));
- }
- legendWidth = legendContainer.offsetWidth;
- legendHeight = legendContainer.offsetHeight;
- this._settings.legend.width = legendWidth;
- this._settings.legend.height = legendHeight;
- /*setting legend position*/
- if(legendWidth maxValue) maxValue = data[i].$sum ;
- if (data[i].$min < minValue) minValue = data[i].$min ;
- }
- if(minValue>0) minValue =0;
- }
- return {max:maxValue,min:minValue};
- },
- /*adds colors to the gradient object*/
- _setBarGradient:function(ctx,x1,y1,x2,y2,type,color,axis){
- var gradient,offset;
- if(type == "light"){
- if(axis == "x")
- gradient = ctx.createLinearGradient(x1,y1,x2,y1);
- else
- gradient = ctx.createLinearGradient(x1,y1,x1,y2);
- gradient.addColorStop(0,"#FFFFFF");
- gradient.addColorStop(0.9,color);
- gradient.addColorStop(1,color);
- offset = 2;
- }
- else{
- ctx.globalAlpha = 0.37;
- offset = 0;
- if(axis == "x")
- gradient = ctx.createLinearGradient(x1,y2,x1,y1);
- else
- gradient = ctx.createLinearGradient(x1,y1,x2,y1);
- gradient.addColorStop(0,"#000000");
- gradient.addColorStop(0.5,"#FFFFFF");
- gradient.addColorStop(0.6,"#FFFFFF");
- gradient.addColorStop(1,"#000000");
- }
- return {gradient:gradient,offset:offset};
- }
-};
-
-dhtmlx.compat("layout");
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/AUTHORS b/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/AUTHORS
deleted file mode 100644
index 90decb3077e..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/AUTHORS
+++ /dev/null
@@ -1,10 +0,0 @@
-ExplorerCanvas
-
-Google Open Source:
-
-
-
-Developers:
- Emil A Eklund
- Erik Arvidsson
- Glen Murphy
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/COPYING b/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/COPYING
deleted file mode 100644
index d6456956733..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/COPYING
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/README b/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/README
deleted file mode 100644
index eb7c42f40be..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/README
+++ /dev/null
@@ -1,22 +0,0 @@
-ExplorerCanvas
-Copyright 2006 Google Inc.
-
--------------------------------------------------------------------------------
-DESCRIPTION
-
-Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based
-drawing operations. ExplorerCanvas brings the same functionality to Internet
-Explorer; web developers only need to include a single script tag in their
-existing canvas webpages to enable this support.
-
-
--------------------------------------------------------------------------------
-INSTALLATION
-
-Include the ExplorerCanvas tag in the same directory as your HTML files, and
-add the following code to your page, preferably in the tag.
-
-
-
-If you run into trouble, please look at the included example code to see how
-to best implement this
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/dhtmlxchart_full.zip b/addons/web_graph/static/lib/dhtmlxGraph/dhtmlxchart_full.zip
deleted file mode 100644
index 4baf4a63cef626ab43d6fab131b2d716304cd0ce..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 22694
zcmV)3K+C^SO9KQH00ICA027fsJ*sKs!Yu*-0AvIJ02BZK0Ay%%ZESdBXkl`6UuJb|
zY%XJSb9GZuYuhjoeh=h72+E)>I7!z&bTWml%h(1j^dXGy$y}_nEh_7Tbg7+&{`bmG
z8oVVn7~6NJ@B8k%^YnZ?`c7``by=~P|61JNJ&L=hb6=
ztppfD4JyGTRsjW9I*6EbWYMx9FC2Idf(oHotgJ#Wem>j@Mqh{VXt6yNQ)C5k6|;S&
zJ6Ix%2Tuitpvi&}s5I-Yu81@^U~vW^bOFndN0uOfMF4~0>rn3bbUcds+l+|}YqwE4
zI0I{GBTVWj^zagvfy|0AEh1i0K&{PME})_swxOV3P+mJuUlJV~YURZ&Ug;9@r_xjG
zG0Dl0mrbQ3iUXyzE#+s|iou;O$wxEfMf{9>T8WcySGParSLti7F|VR;o4qKH23SA;
zE9wt=1$L@8jx7~#kFuJrdkM5#BKbA0o15gIT*dZ2zEWeWd8vT#^QtPnr|<
zU_(K^mYx_GPm?b*h2>eA)b#Rkc4ve0RB9#XkH2duaq(U~x!1X;Km|{3A?Uo%p7s#Y
zkekbhP9J0Mwle9h@0Mn;b(z5utvX`vVNpMb&AZ3f|C`Jb3|6951JWN374Yn)Kno!ukOA3JZ2k8=r@Mhhk4a|l)=dgQzfPa`
zqrsiygO}-il||vWXuW!K{r=sj*1PLhPHS8g^M0pue}C@;<6;&s9Y38Nd`=fF7RTv5
z4_ZaqTI9S{jHA2-WAilONzqEP)|_QU-bzQUsAw&cfM=~=F5k5Zp3U-uYqoQA6JkO&vVR4HGQ4!(15$wXXP3AFYId&uk^I?;`#fnzWVxIp)L-x3H
za33W>T2k=%Xvzoru(n(^@s4M>8#SIfUBj(b2i$`lsNr;~PQEO76422i@ewPN5zl%U
zrI~rhvXkz3+4
zx7>sy$~a~L01nH(L`hT_mha3L`Pjsf8$PsL9s{dAcm)38jg}*8L=4HSR$$XY(svOR
z(ykkx`icNIv~A1335O1w&*PPeYD6lktu;Uq5@0vIp^M8$HO$a@!0Eu;&~nAo#nF+x
zn;{Vo0UxnNEC7va+NtLPJ880r<5DW4btzvj+LCTLrzWP9rNY|NQLg2i@!Lg|aSfV~
z(xDn2vl}+F9xA=!zEVtwP1w@3oP3q|a07!dH#E{_7C_Iz4sw1C3RLN_fJuvN!Po1$
z4>aA?wrL=wYybX0|0d6beb5mDJu12GgIv8*
z^sLE5wLz5smKLx0C`x$Hz*~VRRUQ-=&{^dHVM+nfW-Ml*YuB2l
z0nXMIdQ&NjtCRMT&(gasp)#p?9<2yq0&x!=X(pC5(cCuxD|$oX-JA>?D2wR<*RbB~
zQHYnTft5Vwg$(Kf9nGnU1y?KBaYi4eeNFM@Y?P;X8dOg?Jgtcrr~Z!l&hOG)kwvn9
zh9GB6W^69D@9P|Fs;u0QfQuqNq$r!y(KVqRDZr)mAO>LT3>54gs`*Rsv8GfqpO#!`
z4q_M<%K)5jRnZip8?U1qhv;Q(0@DMIYpX!47R;XS6GQ
zbv}l6K0stbNloCJKuCc0Avr{w{Y!wX|UX}!S@J-exg
zoFZdM4q})oN3uC2>Mb=U3lRXuoP^DIPH&8Axj~G2zA=O;HRR^Be{pXb71vtN`}C{c
zOzTDkN1}0~@6mu%c9o#Yc6zNNRjM2KxQde*hipuxNv84Y?H`6kq&g}pb&W&-?i^B8
zH3n)3g;Y8R_h;2PndjuiD&{xy;h~<2S~YL=^)lvJ0ODYmT)xaRSihtj$te^0OsTgP
zqeeSz8&Y+lVQ9m@%Hdi1wXJu*m)-Y*iEU$D<%V_sgoxyKA>A%_D*i>Yx#S(g2~c3<
zKLbM-88;2>z~KI=lV)M(`eWznk6(Jd&PdMr;ezFne+3Niu?h0(IX%PDF!=fwZGk=n
z84LTp*UxHyj=%Ilif=2>c@&%lD5?xwdxQ->w^8|eR^UK2Feo5H^%ey;Iq^ijiwT$%
zDbB#VTbxa}R;
zE(Q>${4|KUnXKz25@fZa#IVj;y=7l@?u=&`3V;!L!4e{Uw
z=!@lBnHXa-JZBj&8L@7*)clv@D8@)x2FeQ#`)mtRig(EA?K^1
zLjJg^7$X09d=%-oTSp%7Fi>ss;_;C=dFCtaaD7+o?qd~Isf4t>fHr$E=MmLKB~3`H
zG>l8r!rF07($NUCY>V*-@gfCp0oBpVI1(j-UzDlPo?#PxRi)lu8mx$>?&X6biEye)
z_c;ss=MMr8NaUQ{&(b))PUnx0e%&A=efp=(>F@ZcsOMvggcKDYaZ|9gMwdj@Xt`8x
zb;$&bsYvHGZ--{iP0u)tgC6`m)C|mdyozl^~c1-qCCzWHhqi%zR9p2A7M*T8bGqr0SK%r
zRH}ufXKfu@!!$9gORD{xNg5>rhz&n$k%3f|pneqdisvFiV+PhN^Q)sZIU&tdHG^@%
z!s@Q<->T&6Aw9WJhXj!-@9yMjn=GuDW)-N~4Uey;Smj)~uq>j>)8SAHy%0K8pWJ0F
z(P9OYdx!G&PD5crtHX@7cC1*@PH%?I<1178PXak$oU*_mI}i53QJwB!qzbbP!tC}a
zR>5_%7Tg9lRp`d_raV&8vIv)OnJYK3+R~5U4(7JvV
zGXwvw9av-28F^3!bTL(g>jbW+a6Nm^*T;Cc<$&qggK
z0kKHm@Fm^M6zb3*;sLS}c#C?&&bkm;upzb0yJ3Ipy%^fi17SnNq*0v0eAel`u-2kx
zfH5V}Q_r;opspcSDE|eI>)QMvIiJ(5qXsM)r~yMtcWZXDotrXP65!KlQf{Z{@L=
zEKaHue#I9`pII=5C9|-?CqR~MJW-@7$6}m9FlOMv6_K`q^WgTRm5&*a11%3*KJY)t
zvFS@cXJM~lbK5vO?I4gdLy~jFn+{4LOE?*kP@){6EkzOoJTR6|nxOe{Xw}68Br@*g
z)OR8G5OXfyc6vWSlDD-iut&$D>8CklG}bQACNC%DKm|;~t=8XwplvKalhB?{>^QT
zIlhWk4M-6B-TBgOVRk5FpXTn*k127}9v{I`1Z{9^F??}NeX!MlO@cQv@c32!eaZ|(WY?p~(PSKX6z
z@%8Ea{?p(U>Kl-!1({Q@&sG$m9!iTvEfi*~_nfKcT!rNSO)w^(c3|<@X*3H#Z++Wn
ziDU$*7%#t|*~7pSe02`b^vMqmaKMr~P{P{&YX{(uSyt%LU355T5%ghxSA3BY{em4k
zjY0}og~ylmhy@A0rZ}MkiCX+Glw;>amC#k9$rDlWx%JIZUy7c)^nZl>*sOyHW@aJQqO^26L&4VF4x8AC&ORo#1a@kx!I*hGmIWBmyTtaI=NZ$U3jwCg9mbG52x-me)ZQ(d
zZ2p@{$7>8o*QV~V6;Aj2c8)O
zcK;4ejDZvDT2A{2zNt4FT3f9AU`%E^?Q4u$2G`gY
z+Vv-TT1A7pK8U@l+v0X@g_g8t75WXO+2J&O@2bsCi$@>
zKLOHHxDp^g{j1~y(C;8WZrIQG`|M{bTss$sgt2DBZhQuXO^hESeUo8A19PggMrR0A
zlInH)w@@?7-7P7Ird~~J!*(UQ-Aw`OD9{6x%?=kZM~F@97hO9HUuw)~+w5J9f>AS4
zN=LD2_%aRMw#U(qNQT78m(QO5^7%oA$U}Y}^>=T7%mv>-#iq3ibE(23%ME3A_^|9x
z$f`eGTWc9}#MCzoKfS8A4r^85br=fq?f264<-Fh-K{aRr7nbJNq40zl*r__9qnskbS&St+zL6w=|z6Wu;_l>P%
zu(ZDl`aOW20fz;nH!PB)MOL#A`rcV<@`i0^;dLQHAS)qL*F(jc;m@@Ca8S!0x{y6g
zL1H3tz=%rO%~*(~%`oII-5GY8s&^%OS5^n-uJMCJH^3hs9ex*Xy&*k07Oqu%QUZm&
z90_pb#j!bqdwV9r5MnW=Cprc`ZYI{Fv#~{pZ_CE9ymfqW19)QvZ;bGSZz^X7`ph*)
z{5Yg*fB=`s$epKc*!v6flHP=d7lE;Xmq^KoYi6{~nzHiiW37cb<(#UQ
z)M~Sg1<@jJyr(|jL`cpd2Y{0P%H_86+#(D4Z2-y2;1uF)^$sLEI#Ro3hx$jz4-1;P
znfjDV1b3VaI*CW_;;`TyV8XIr6f6_91q2Q0xwu!^Pq`T^UeA=|(OJWMkpbYE@)=WF
zTyfuR)-he@>EAc1gZ;ax((F4cpqSRezx(w(?8Sa#m)OKSy%KU`Q9
zD=+G6SQ<@22elzK
zX=>7A4^%cuZrkPK@d?m)fJBx(`;lF5E3Pq%f*|IGd^144V(@=TDxU(}_=-^q`OrHe97?x#
z1>T;JZu#Cs=Ec4p)NmmTj&0G3`nqAoSQ@Jmv<9DGE(+kgq=Mg`=+ILEJ*we&SYv7}
zfwW8qZentf4%g(zy=jAx){-^CyR_C--5FYIvSK?6qT1UC|5sK>rIW@_2v#;eMd(56
z^c#gbf>^FEmNs0va``6B(_ftZiE?(~e`IqI20P|Ws0E+RV?40-@Q*Txfd{S(2EIfv
z@?~X+i1NJjQA~VA4!XExf1do%hc%f{J*hy|oZ!bwie_~bQ}RM4O4wgO#vez3Gpxt(T$mp09>u>(YJAG}po-ByOrZdrMiK%je}uCk
zd5gq;?q-rjNA7lFMfqkYU>dFg`A~c)TfHuwQAnSrkdt=IQJbTwbfIG8ZgL866Pt!Y
z!y@oj%ik6=E74gZ{`LZTJ)W6~W#I?T(UJ%C?9yJea67f*W*C
zEJ(qiM5fUoK?$4k*eoBk{{K@I
z;VRYqC%y_oHIy|(RMimie^x`p|E(HgvQa-wWc@HH>xX1#{g7h)kjVPs-`#y_bKAzU
z=y&}J3CR-)5tK+d&dDVWs~kH{e2(qp+D>9y=2HP6xJcYgkQNz<|NZqg8vs&r&bf7~
zUR)6|`_|Lb-P6-O75?FBH}7x-+``De3P-{pWX`}xu#b?KT)-CkEL8k;fr|fZ9wZbJ
zfe|4UoNbi%-dB^}INsE!GoQt9?-3hP=V$kz$Y(&21Kw!QkRqQcihM~F`J5>73@Gvu
zi!7I+-3}EmQs=Wfsq>{xonPUE&UaJivsySknJ}*T68>AHxlQnWF*NfMx^NXNf(g)Z
zHUY8xk5cA^R!S-Jy=MDwlImYcr?0}&o|V(-*FdL#&eQ4hJLq)e#tmI=XmV(-c14YT
z2Aa7*iGSHfiErs~z&Ljd@Nrji47_%Zfw3=STiG$N-hB+L
z?PH+t90QkOACG}P{50t_IJ-Kz#3O)Dtetp%1bBYzkHWFwc~?j_u8#=MPxI*eEEjGu
zrXRT2JWvFA*ZM9Xa~6FTZ=SPoZ#&u^2-*fN0Oq=y0oL~0*%69VNMMHBa#9-{jp2NP
zmKkk-4(aw;T@>MoQT_Y~aeLFpHyBwFiD!2r@r8}VM>t!b%dVHr8?Ec@=xOkki2IsR
z>I<0e1?LW?IZgI@jgs9#WMIxgJF
zZn=@gBPA`lkL6oeTk&nX{*vNFhXuWd)HfWybxE6+C`|y_AF#SkSf;O2-jgF%)~Q|D
z{W%Ebez>T@lUnzemvDNGVTYAnQ1V@V@ru%1K4NLk7aX|CJ2@ysy>*1c%R0ab{C`=4
z-&!AWVmN|xZ47|*YR6T1{-8syS6x8haks~CLDNy5nW6bleL$ArC(6fz?~)4rVm`e}
zi&?m;rzWF|!%_T3MgffQ4X^|5Xa^B+?eLij54{ZyKwb6Tothe5d|f=-s$JVf0*79b
z1w9NzD-NJ!Z8;e(=p7E7z|SMpd~Iv=&c`x7F$=5Kp&j}4)IS>L6i%wcTG(Y$hUz~7
z8V27*t?RaO#?g?r&hSygr$kcxecXdUEP+R4j>d
z(9&ZFtxGCH*j00?c1BsHrMI?11i5=jt$Fcfm|mObxk9}1>)bONpfamHUDH~o{~H2{1x>*WlIE9
zWjEC2Tn4Uo8Iv)&i^(_uq;Q~CV>%xKemMxeXt|hrkTCWIWxT%sb@tcrAa{wPrXq^E
zVoB7Fu8w>~@Wa|oSxl9om@3;GD7;rTz+BSfp+t1eZNdcEj{5Y-ZMr^$-8$I980`sX
z#P{Eahl7$^^g+oj`c>`*eNenX7vAJ=-=gO((Fb^mzFqvrvI=SJ+Py^o46MU-?h^eP
z3E?f5=nay?HOep0ul>#F?=XH$#W&hB!~D$@8ME9WvYQF-w|w09m%#BYQDnDNBD*>9
zepj^_UY`%>`aJlec)(?m!&@SUcX+tB6+Cl5=I`VN=QcS`nz&!`Q&vsHZ#o1gVhf*@3t*={@_4$L+YTA1?MLJ`8bk5n=_+)Ku-5~$#
zj+Z|+vIpWmA#!FbK6AkgDoN^L4sG>O6$C5l*27#1Jt7BE>85*x=0U{t1z$zhddw8f
z;Ut8xkeg`0Ck`7MXa;ttQUjws*6IeUMk@~1jaCv6x+jIOWwe#6JncdH-M}Lii(QIs|Ng8Hy{P89r32$B8nZ*F=sBhp>UZTHi&cV^*qfM
zXaE4o5-il-i|O|uOwN)Ts`j<;4-lm)cEqP%J%#0Obz$am2`x-eoMr@rL$hI<;LENV
zv*s!`GlQA%oF)#;p2N$wo~0OPDaKjqQf0Bsk}a^OJbz1Of4^pmler15cLT^h27GBG
zF{X9=s&(nR-anFF)K2l0OHn*?X%0YVAhk;+k>L^5RrqM{)EV7nnc|8y$X|AfHu6%c
zduoy3%eyUFB()*#UKUd)Hiv|D)Y95EjS?(4K>12+Yn1e0m)aU7wM$F6M5%uCQKn)k
ztMtM_of>kLO8U5Y2ev{fS?sQ#Y4Ubed5rAA#R5Kg8k{Sqap~>aZ
ztB!0!Wqt5eAaD{&c(8L%ZVr(d$
zF;G1H+74=Gte%#X6uh~DEG((Kqud5v>|
z>}k1F2y1*QLNhL+g*zJ2D7}jz5dRYu65yy1#bkQV|-T)7_*CXePJ3rGnIJ^YCVd26FRdz7pSx>da(Pd!4!_#Iy|vG}lka^0L~X&SaQ9*hRB1kP`#83rxTijVzV(aE6$A4ZY0N^(-uX>O14?e=%e*Ku9_d2TA
zK#wKxf48
zjVbNlvMe
z$;s*A;rNlpETgb~RUg%wZOBdj20X+=<456xBfEfwi%Lp#Lx5Wg%Oqw$kA2@i>eVJd
z-$r(pYEnZ1fI`@G$e`k^u?EK@im*hy@(7_zfJb
z$pHO;40V1edoVma>^(wDo{!_Bp5Lv7+tFnL=u$5eYz~-rS|k;P^BWG_IA=9=Srny=
zhW!apH9F&9xJcj5q6Cii$@F?2&Ad$+rT*Yqo>qVG?4H!Rh3N6bHI=^;P1o?|pRJos
z0HMXYRWLE>d4MU?#Uh=zO3lgVDm(7Yr(-m_QAOi3)A;hDw6J3uM3?2Plp|omxxLlU
zeL!8im=ki29$XX>MCt!H6~*1Y?6=MkAQW4rPq&M{3e!78q_;g%7|G%5FPR;*x24Ahtk>A1?`2Dnjf)%e;g>1*)HqX3oPra7+
z?E|zvQYqAxOvmHtgsb~hwTg2BRB%VBPg1PJX1}jaiG6UFQ){2L7Zx69
z7dm{dV8+9W15Zk$>2hn^D059E7Nw(OgtLsz?TKk8j`FdS#?^-+YtaS`fR;GL5F<8ZjeB18r#!jWfG8*WD28Zj)2w-iyeGURyT67g6gb$+DL2OrTWZ
z%aMXgy}
z@o<8@R^k{~Qnq8O_~P2PVA4Zy{`3h!IR?t+s;%l+%&-o65n(wRar}&6UBS(A*lm?L
zt=PC;N2GdL!WM-#8=Qu4E22&u11oP~YDVcBI%jfb2H}i|TV-A)2%Zqsf5l@*2mP8!
z(a$Qjq`!`eMyxEy9opQ!K(8^s?@cFcf|k4N1?Sp=An@4wofC%nvgxH|7>duv*vis&
zs~0BUY3@l64k_JQ$C?50J7oY*B}>#|UKGM%J=Fd-bpqhj%o
z;<+Q8Y)KaTtaKQ(ZuEk;wqgf5WRHj44ch6`2%R25@oi1MB+-$%oG`cL`OLCDSuUn(
z>)Z&u?4lhllxZMBADlTCFt4V|EPXZol3KPuN!bnXJOGYBA3AmN0~6w^nt-s^z#qqlfx66P0_af(j&cIuP
zt>7^8l_QJMNc)MQi@&hoV}$1`7HBdSYE?g+ovB(W`t0aZd}x*Coaiwm_#72&b0aYE
z#+E?QOuEkr;W_`>&IZ_SI%yPI#?x<<`Eg8ugHkvT0QoB2s7SQ(p@P$czT1+vGA)ip
zyKQtZ^CcaTIT!c^-k1#mMtr^oe?|pIAeMI(o(TlO7n9x;GAKtYCgDw*pe;>ILtguM
zd3)RNkD5UmHi90SPcc4jdTLnxd?nA)MKl~`KveWPzPh1-k@lG_HX|H%WQ_;{&eIwU
zd59*2Hg=wHuWzjT!q)7|B$&bXVemLs?qPggZRYJs_h7C>9)c%V->2i;bprjT
z!y|hWP5Nnxf1aGxShUp3UWtgDR({R|x=kEg=#L&7$v#T*fjdjYY`iGYjE55dZ%EU^
z8+9*udkZ@R{UORbUpz3MPM0tuA4vC&J^8eopk*2Zb)AM};tJe^ZY?gW+%;%wupz&C
zhfzF&;oy7s#F*cvQ^f*MBuZ_^OR9k)YqW1l$t1(Ez8YqWtOVK)4qR}{`IJ!PP&GoG
zt@u&H_iYzP!!9fi&I%(J-S5P=_LlLm6gkn#!BRB|_T1I7`;Jbs+INB=%BwtV#Y3_>
zC^Fejr>)*v3uU=haoljkK94*GOpa>%kGp;zymB4ZsLkneOa$wbF84RKnG9YYH0W!2
zny|1{qP;~Grp{_@tW?N3q@6^u$Erd{ebnZatX74oUxEiCWL>PT-)BXlz=L)0IyPZ)
z53ePAf7fftj!9=D<%s2E)(`AykKAdG4ID=$aI7Ph>g(7_u>F_Go@#HIO}kI+d!Xky
z@T3TVcrr?(`IFJ8K&s!H3>#bQg46UXt~7Ls=RPw)T-p>t%I)9fcM*p74d;<0DR^hxe6Nw&Cikdj3LnCU*E;L?qW8b
z7wxsPW16VFP3GJUZ=$Rf0S7m4#Tx_iMZO<$#%q-`fI`VeuDH*;$1xh=f)w~0Wu0A^
z3VZ1RIxuL;!5;FSLtqOtD?8!93-$FL;xkly>%q!Y<((4FKTtvKJ{7I@{Pvy&v3)qP
zXT~kZ(4Anw;Ap#?y;QjHxox+p!H{pgQIR*aaH!P{j&gSOcw^jrCWYnF62fTP9YrIE
zP^C$?PFF8#M*z?0`ghbq#vVx+H=8&_C~~h9N<3Y5x9oXdLG%b{CKtI`Lvo}j-90=M
zXOO9yW`#0LEe?X&TFt;coh5Hp~-a3N4F+(dvy2
z(6B8mh@cQBBFUiBRKPwxOsWcRe9H^fxyK+zXI|j(kpLfj#=wWhoA|BJ0(`^h;BQ8M
zWdh+#(dJ8R1AD!1NTVtFf@AYPZC8Sydb=q|(#
z9*wed+~k5x@E&m&GRfa`k~)e+##Iy}1Q9(t+|Y?oWXS+;MOmV6HRJZ)?kX{KgviLi
z%FpxZa#rR;7~wcC%CeYh+nIB|C~9Lst+JizyJ{zhYK|bNa;cds;V*41vTd5-^pmm;
z%3AsHC!S7rD#VF6sGCM(9u
zv*ow|`@2Al+%f9)Z0{*F9_Nk{fRdyZBD5VfZGmI}oYKG3Ah)OZ_p)XYj;Yy4iJugE
z0D)i}t^WN!)bG`a$EBSBqCGD0w4!53JO6TfpDuM(m(?r0QQQQ{ju8OnC1u=bMwho$
z^%)8a$8-W-&Y}Hdvs>L^Y4xK7KFKTZLHGQslT25h_lFFLBsri#sF$G_4w;l`}o$sK!ilj}n7DqRB^l>rUK>JhR`
zo8Vov%_pRCKMm!fu!-4{!#`eorO)@(P!f!ii*K`2<2KFnBw)CQw1E>
z{~c`=TD(yT25LeB@5Le=KcxWyT)e$sQ8fo#`dK{E6}*Y=@Uq1oZ_!f$-+{2vlEcG;
zZHfy|dBtAQiXl98v>?m5i?sZGvTNPK1(}*iKPuePW2s+hdsf+pr&uJ34iCkmBq0(#Ye3Eyn|9>GZXrs9u?21!dEJZv=J
z`}e4Lg=jCDfl72hu5Nt&GL;c?b$hN51D?Zwl(cZ4a&K(3xJPoJ5+BFiEk2F|^u-tS
zh;j^+a(|bYckR2Fy&xa-G1l>fjFx^vH?Asx2YWN;yHt7g^ZYW!mX`#%ct*nTdVyFz
zEP-3EmSBbmvk0i7Ha2P=;G34v^1F@i7E+~5weR5rReTN#Q-hB@$z6A=$NqG~mToOXDa_>wEpR|x%rV`CMMdU+#g
zl&vcl<TGf+*ghzFY>Im@IHs^)TS+g(FN`fCQbCS@#ypY%v`V|Dwk+I*@lQ
zT~sikdYtx-e~q@Q8*;9qB9W=fx2t+BWNtyB$jaZ&Q?=H7lu_kTtf^Krp=XwUFTRG6
zCyB5nEh^RMHSHpqx2=iMoLm4$
z*JoCv2NC3g-UA}RySR>q9BQM)*HlK<5a_wk4Lv)eb13bcUv|PW1!9&3Y@5~MF$bQdP=}5(Z(>}auzVGhL7H5>k@aG~npekk
zEx?zx)vLhOhryHHZ@C?vO*80eP}Z6M>7KMjfKscWjYx7DPcj|pABh;#%+G~whqEe9
zKAyzDo<~U@mk1@XQ|C89r6}(yofynx1RWde13knTk&v;zb;cLE(U(peh+}Ak#6yg37%@uxi!K-D5Hx_MV(1$g
zoVT#hSwR~U_9?`lS>IL1m`>GKH@Myfny`DrmM}brGL$aDmaY07YH;hCAte0)^@Q74
zT6bxy9#!*R6{CkO?eeOh4jh8zUiDHLr&2+Cqi7A>pnHl~J1Hz7+wux#?0$k*GWS}7
z9(ed@ViA!3oR;yS#Xj_CPPI$}
z(I7FrHPnyXinCGGk1j{L$5YtIRbBRsPl1;A^H1*pe8em9Oi`4W1=-#Vbzo_xen}(T
zO&&>3CW|^#R~|kH90DZ*Q*pAYUteF>VWi{waz5fwbv;|lHL_J?0cNHXygdfO0T5g=
zKy`<3FGnDCk0}cseYdxEC=9>j=R}8xeczR7C$>x*v!HG*{$bXyFK~N%+Yh?VTOKP2
zfWjtJx?w+5C3rG78=M)Sfa2R*jPNZBp8d3_pQ5K!cjVVbY$5=aAZAl^=Z(nluh5i{7D{cwF{(JcvBL2jQ2bE
z8igy~!IWz#N`bJJ^KeBxD=d!@8(Hy|jQG0AVQ!4n75O=bg7ilW=|+FT6}^yd
z_HG(9wqEr`YHnjZF(qU6Evv*F1(FeZ4Yj0E`i)_uY_?&d6yc+6CBQaw{
zFZY<7(m6@8FI(p0-op_AHk0zxYk%wTL|B!Hl!NcjBftGWJM6$|FMsiAcOI=*`|TY{
z+4NKx|Cray|Mz_V)8f0cmcNF0z$2H
znO0k{iAd++;HZj9x63gTwl_*oxpA6vW_qi`#k`ZF
z6BJK+fx2j+D1Ga42aZs9$)JSbX2Y=dJ5+&lRd}%yqX4~aJ&SOQ$u5AK%fHJl^QBwl0(=N&RKfrxTRW?<3I9degeTgOjc
zf4F$};kTcde4!bS`B~u{%DhICKrp3kypyCFmvKG2CcK8Esvr|t$UwVx%wJKwoS`=H
zectaJIF2sS*L}Z}Cw6Xcs~8=g<6i>Ge#d%WWkW5tgj+_;K>_1xHQfof=0~f!a(Q{6
z<_9RR)Il3KvOkn-1~G6N7E{a)(a}YU!g|=Q*V)9e0+TVLVGvfnCV}{8zrOkn$?&<<
z#&TXA$@)sqEwmSSsBv(@uYOHEj@ZJd+lufmaRxMlFNYy3u`s}?+SjB=G{AvpBW(Ao
zT;cZE8owJ->vs@(G>&M5Wj4&j3emf)6G76C+1EM&6cq3Dx8UH*ESP5<|i4
zE=G*Ns7Q7=u^}sUmI#<@w?sN4o($5a@ojY&byqY`KQD)KJbwnmc^9B*u|7#x+!g%+
zG&BJ8&lg|DBOdOnXcG15wcc=XQB^0#)+~xKk?uJ!Qb#o3T&4sVq>L!6d=MVD@B7xk
z$Q0FS7gdqIjmk91Mrz)Xy|&IznrNjqspt
zBS@}_6G`KWV=UVwRZ96d5^|ED>P>cYcC3R=Dxf_DD@E
z55WdQF^wq`oOc1eep!9S7^qFiQks0ci
z_vXoq*FXOHrze(^TVB%($X+u$3PV3%!;%8v4H~dN22Qfv5l&H-?1toGnprck|_lgAHc@&W1ujIM6|%2ugKH}+c%5Oty-g#(}&;t!14Sa{M|^G?$+E>J%?lB_ICRmiuNz>ysvy7V<`e!k7NpF
zFq=OFtUJ;2)*gV@us>m%*F%`3P&{2Mren)kr{|&R%q!@N81>6OF9A<^a`2o!)RjNn
zY*3P+N^XCHX7bpaBdczH)v)=bje+ks9zESD5aayc1#&2BKWBD{d@LWLryJJ&fgHPr
zLK!fpRpYMwRyKR|xHtda=!+}
zS;`2&$vJ`8z^o4*s=VJd?Dt#&x#}wEipYEpVpnPKE4RvGxEK+v=i$Wc10>WoOpIkr
zIBmp%u!iQp5B%#bqf@ukf2)%ug#6F-@%2_dRZi4rkz+c%o%i_I4@`Y28mro3a@;(;
zrbSnR-{g3OZj#%UH{ssdUay7j50SORG}ea(WJ)n;R@}o
zsocJJsj>;a%li1}iZQ@Jbrgnq*R-K*J734I3t-60JR!fRld^?IK*Lpo`kl(rfbR*x)oob^)_0N3w@1q7r7N$Kv+r4i|pmhKdgknV=1o29$EK|(-!NoiR?
zKv&pl`6#LPM0zu-HcRGPjv+h}CuL9FfbqQv^P-T1mrF@j$rQV#_S
zl~Y#ou0m|2g+~W+g|4L?0bssbPkt1@^6N^;IX1nO?!w
zobBaKlr2OKPNQ8eFEQLinOrru%OiRR8iyJR#Zd}OA1c=TILM`F(iaJ>X{I!i!A0BN
zi`j@^yQ&(^SD4?f1PX^Ts)OU{gw(~&U0?_Dz0kS>N(QTqcL{$z@6k^^7Ywpm7n~mG
z+HRswtY;9596#c-v~e`v3S)oO>qrSke41o^b#~==PUt&+`?^?gNpyvQi@r*P5r!w$8i=Wnp?Fh@p$9wwBBP7l(YF`<=l=%!YKZszF%qcdrCul
zmdbZ|8frQ$gWIjQVan?|gQFbE6vf;4>2tP_7X>uby6lFT);gB@vw4E|gN1ii-9*Nhs8n#wOqK3C3}K#IMerwKLM
z?l!_kxHnz+*G28XC0Qqa-s$T1h71Y-#bjU34$#@J@P*44MEZWSyscV(kmv#O-L0;J
zxeC)KdyD9*Rd5>q=Ci%U*Fx+_Apj-pcF3$12AambQBRuX%5||HNnl<>&6>ft6czT0
z(~{uxI-7x=iEVhcP?yYK(iH-vSibi_{rTp_{v#>lM{((PU-lho{~TA^LTI0Dp8TD1UV?+%Y29&D)RWqUFZb==HF)!O@KJ3SN8{JbkYvRR
zGx!Yy&QH2<*x|IW%UVI&Cp4nDQtkL6GsDQ`Rr0|*b6VXadn?Si*hBR9_H+C!;nF
z{QD`yacRv$A3rH_8cNgd@`~Tdxjm{k`I<*yJ%oA{CJ%MVj>Y)raLgHt1OXb%v6!1<
zc@EEdv@yE`SlSaFBhi_gcukGWZ5m3J$7r*blA=DEJS00W2-N{1zQ=F?Vq?||n$5Q?
zYPzXTX@ttcobu4Vwam#AuHp4;23N|7^j}}%WY$^{DQ+nJSt6TH5Z0=6DIT`_8IMZ3Fg;_i}vKq^NlW146(rL
zZ{UUpCqsE*cY*TN$6We#=}VtukR@~t14i3e#IYNE6VA%8@)Fk8c%z>9{y}GIOA)^v
zSw|}DVaKjlGMn=z^Mc5oYP#7&(wG7ECFD?crFRbRdKS?T1wv}0Yco^7kyM~qAmPA1
zlGiXpE6UGvl{8SOKp^H8P2sz{_xEJe=0_*z9#BbA*O8;$Jy3;YZ{UL#MK}A$z=F7W
z_I2X)#goAA&`bnmK;S5`82$wFmpq)&QZQ6$nq0o5VNFMUs00U4K(uPnR^D#A9Fb~K
zguAYjnoSpv_aSzVpFtha=R|dApZ9w+hM+!g#AHK%|N6`2c#e>|Nx$Fu!upfx=8@#n
z^>qvJsmbF#nam8L;QMpqO9OF30qO(G6me^6{TMHur+c(}wx|6cXnEBF>Hz7xX&Zaq
zBWwbawbK5$dsrYbby!A#EI3Ys0r}gyI^?*
z6lQWIhK>$Q!P${9!P`Q9!=^e-etZoKyA#&{Me{S7=IX32+5QaoFpJIQX$wbTY0ZAM$lmp6sbD~l^FLRQD$}u2U8rH{bTywW1cWSC!eiLh
zW`czxn3T!%v{11$r<#lNY9hGBXj)#!Z&<=M7n-N~iXtI^%nLSY1@IB+87rA;r*$nU
zQVn6Zl4`-n+@fC#HU?T$D(4B|3#oDQ7f6*Do>s9DV(s4`72>aa2DPoBE{wb2U#3|+
zrtsEQ$M|f{5-UfY#TXE|Is4SeY17E@&A5Bpp(gB=D)jscGIZRWE;BVZh^W#1%
zFUAA
zB6AcZF=~e(PZY}C%RYj2`}jzLa+
z05vPh(Y?bf%5muy`$Hvv5p9!d!u*J7$m!Es8G%Q6bQ3ck`{ni_YGF5OqvgGcM%P8GVgB?i!n)w_~)?
zhg}1V)2wuz@t_|F)1*gYMuJTd=lllXe$cmsk%}{ff4uST4Nu?(MzS^kdSvR6IW_5Y
zIjkIFhDn!|Z(vbvzksrqR9vcw$5p_3Q=nz`je)FRN9;`}eMs`_(zx@tr^@t-JmcZq
z@z=LOg?fMd*!fU*
z=Gq>&hiXUXy%l9{4NYXL*8LUK#8>0BmF|Gbd)czD*dYco#i$Gg3x^(Y5r5W)Ht&9@
zO6OW44%R>(wBf;J+thXw-bN}xQK*f;1Vs0}Y7g1>jZ$aE!lb~9+@zo+GHj4Q2MZsd
zM@|ta=!gYl-f|Ce>i=AGV2g39&c7vk+DKzxRfaO_ta8t~N`!l4sl6U}{wnx&BOS_Y
zpndy`np27CYqXZWYS+BxO#lN(R&>Q#bU&42U%b$I&k9e*-oFXk$MwR{zuUE_*H*Uc
zYjf@nobR1xj)I@5j>gR=dyqSSgyWHZmz+*l0oT@92C9H}Eo{WUvyoW#tF9LFH}m$Og_*SW(7f%M
z^*fo!Ru+Ab_LB$8v1$`$fV^fUEiRZL;;wnv_xr6xv;?ByU~cKg(KCnF>#lAh@hAXhgD9Mj`_8OL#pe`&%Qfte0@JbQ@DsO~Oo
zf@@yA)SsWS1QZM+#P8L&1RRM{Rr6RE>_0Oul4wkN?(e3b*J8XBpz)>ElVBvK@+ixT
zv+ZCG@U%q5opZ&6oym0w4!ky0ugs%4`9)TjzX^Rh8u)Vv
z9n)T_W@xiO4N>8?Vg40HD(HnQZ4N|w*%LIv>n+L@l9ok7D^R&F;g1dkd+fnkFtzKW
zNE^*kE}0+4jvmBl!nhbGKcgl*=bgzqlc%18x-wub;$>54TQ9pob9E{n#Om{@u2Q=&
zYjOX)+7+p@%A9tI!bHehsw*vB!u|mC&bgAg1e))2L&2vFWD~j7&)rB6
zox0lwUG3v&**qOhzC=GyB*p5*X(U;xIa{xcv5&a48c7G(R;$a&I%q%M;K5+joGxV
z2XU#z8V5H;>NHbD*B`j3ZQYNp?>_CWO}ZV6JOyOIPZ3?u&veV1R6yEWCQ0UpUGy>o
zA0Fwps*~q%deKUjLS^k=I!?)~)P*+H+xPra_3Byeqyq@4zWz9Q7h?C_XYQ+tZp-Hg
z3Fi3msEKE~zFCLlnNcMTBphNx(&zL@6NX#wE8ADeXAjC6=N`@9M=!{v|FK|%a6e9V
z;!BabyX#^E78U;aZ5Zq7qBk`efBw@4@Z?1quFB)_lSj3S*h*-X9$3Kzd#bVFt3^@6
z{$;En+=dU<9kj2P)neLJstmVfi>8{Xks`_SzhZE%`?K^;Z+nqMz9y*+DP~Jx*D2dQbpjfcc5rXP1lT!dz-tWG&$wqZUbor=mdm;c0)O=_|CND+m88Pj
zCH@@VtfI+yzuhRHrN+kGQ)esN_NdE3hPJ*Bu0<%al`F9o|wEzky$KE0%
zZ-_`ed!rDjrOSXLZKEkP*j=LUJmkQWcoVuz%MJxn{H$D-NB34{Kc`n)6d5W_mRoeh
zdv5Y%Y9$q6VZQD4wg6LN$)b?l6~h}>2BvKwrCRg5tkM(f@b0`_lbXRB%i!V$6kY)b
zLQjoJB8+o#bT()VJpo@OYFPjDyrSLrN7*8}(TZl0+x+
z{St`gyDyY`40#L(sz7GP23_&@vV7VqQYySkJ;J@OU;h&D+5_o3Fln+$$2?#+phTUl
z3#YxF&TD)KCi+672^}e4c3GHN`d*yB2dVzZXtR9PGN;c(b!x)+&PU$RWx&GXSZx1;
z$+oO9^UO*E8R+68bXjTev-1dIG>$Wv!1{~grsHN86Op|Uo1TY9VzKNMu@Z8^#X6rL
zor|?+g!Y|Gi~~-Do-T-az%8`G`LHUlyQGHse9IA0IqS)=bw@wD6?-W1<7)0GE8Dv#
z!dFe#$#z~~@kC4OSVEvFe2}$_)&nO^O&><`3vul&DL}_S@XAEE-Y&q|rRW!3?3s%&
zrFCFhwz@#-G0(?aA##^y>8iHz_VTqQqjiJf2+f?lcn3pwrxd4_)k%&xL>4S1jC|gb
zk;aSb>n?P6cF2v;8-+b%tD$7pYFwz2=N%2IEC3URq?V-q12?(_RUMK4KZR~=Tp^G>qu`L>jLZ%|6P5<`GrL+1>EnX#mo8Kw4Pn)5VH1M
zG7|xr)YqM>slPMKU5oPU^JcC>`0Bu(st^u=)$Aq61=uFO7zHnK9S_6&+`()SYsu{r
z>oGRNN8eSo-FXH09t1j-jEzoTB5au{diSyrRigY!sjm
zST`TISmg0FL~M$^vd}5bxRvEuIA@wz?m>7QES-|)@XY{yvZoa&J8A1T#K$JawM@s<
z%h&)WPi)x7&i-gxkuu0|2vJMMX~8@#9Hn<}>Z7psKKr;)DOMD!qtq)^@Rdcbl!S%A
zwKTI>j=&^rp;``RLE-owXXNKfg`;r;{;o!}Ow&}2cPfnj8ilPXbC6c;i$i{Px!%Wu
zwYlts>2O9|R?G(?Tx6Yh>B@$_`KsLx$p~iu`6yXU+iO%{0v=tk>F`2M|1u|8|0<8_
z$177`XGNzw49O+6xqV#5p=IdHm%R{DGfzvg{5@vPEgg&UrdWmZ5dKXs5W|hkMrk>Lcz~^HQ
z$qX&?<#?e_S%=qXWV|i>8LzZ@s~JH$+&CFlmWZ-qwyr7}J)K4LP7onPAb%}lu+|B)
zg84&oXnc1t2)Rm7*87_KE{uioyUf{Z=RI;`|-CsnW(p{Qc@Db!r5S$>|~WH7f@1p(+J-{rXz{i%kV@Zug+oE=D4^
zZ^1H8XeVyDpN1NQOl3(Ap+5V$$G=yD(zaXJSuo7?lAolYSn$+-Ag%DTWO_oLhR<0}
zgXZ%S=eFT}1AKAW)4^|@*~~+HI>TOi1x#P&LsIrb@m1|2J-dV_ua|ww{@{qzoniX{
zMT|~d&~1_hiP>3x-H!stOA(<0@0xPnteLL6cg-pMKW^Nd3|;z!r&860lRWBvyCwkv>|KAG#jrjV1UjL(D`X9{j|BDxPaMw^qMf=YY^1lrG*Rl2gv;7YMHx|AC
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/readme.txt b/addons/web_graph/static/lib/dhtmlxGraph/readme.txt
deleted file mode 100644
index 046235b626b..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/readme.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-dhtmlxChart v.2.6 Standard edition build 100928
-
-This component is allowed to use under GPL or you need to obtain Commercial or Enterise License
-to use it in not GPL project. PLease contact sales@dhtmlx.com for details
-
-
-(c) DHTMLX Ltd.
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/01_load_xml.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/01_load_xml.html
deleted file mode 100644
index e720ba8ee3e..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/01_load_xml.html
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
- Data Loading: XML
-
-
-
-
-
-
-
-
- Chart data can be loaded from different sources: xml,json,JS array,csv This samples demonstrates loading from xml file.
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/02_load_json.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/02_load_json.html
deleted file mode 100644
index e2ce20ae764..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/02_load_json.html
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
- Data Loading: JSON
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/03_load_csv.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/03_load_csv.html
deleted file mode 100644
index 64c39cfaef1..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/03_load_csv.html
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
- Data Loading: CSV
-
-
-
-
-
-
-
-When data are loaded from CSV string, you should use "data" and field index to define field in the template, for example, "#data0#","#data1#",etc. Charts in this sample represents values in the first column. Therefore, we have set value:"#data0#" in a chart contructor.
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/04_load_jsarray.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/04_load_jsarray.html
deleted file mode 100644
index 023cb0a503a..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/04_load_jsarray.html
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
- Data Loading: JS array
-
-
-
-
-
-
-
-
- When data are loaded from JS array, you should use "data" and field index to define field in the template, for example, "#data0#","#data1#",etc. Charts in this sample represents values in the first column. Therefore, we have set value:"#data0#" in a chart contructor.
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/05_series.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/05_series.html
deleted file mode 100644
index 0600c05741d..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/01_initialization/05_series.html
+++ /dev/null
@@ -1,119 +0,0 @@
-
-
-
-
- PieChart: Initiazation
-
-
-
-
- BarChart: Scales
-
-
-
- Automatic vertical scale
- Custom vertical scale
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/01_default.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/01_default.html
deleted file mode 100644
index 39b2f5e3865..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/01_default.html
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
- Colors: default
-
-
-
-
-
-
-
- Chart colors are defined by color property. If it is not set, chart is colored using "rainbow" algorithm.
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/02_custom.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/02_custom.html
deleted file mode 100644
index 8157a8f82b2..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/02_custom.html
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
- Colors: custom colors
-
-
-
-
-
-
-
- Colors can be defined with template. In this sample colors and defined in data by "color" field.
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/03_custom_logic.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/03_custom_logic.html
deleted file mode 100644
index e88c37b3283..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/03_custom_logic.html
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
- Colors: custom logic
-
-
-
-
-
-
-
- Colors can be defined using function. In this sample color of bars depends on sales value, and colors of pie sectors are alternative.
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/04_gradient.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/04_gradient.html
deleted file mode 100644
index 973d9cdc008..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/02_color/04_gradient.html
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
- Colors: color gradient
-
-
-
-
-
-
-
-
-
- The color of Bar Chart can be defined by gradient property.
- In this case you should set a function that takes a gradient object as an argument. And using addColorStop you may define colors for chart.
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/03_group/01_basic.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/03_group/01_basic.html
deleted file mode 100644
index 401dbdae24f..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/03_group/01_basic.html
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
- Grouping and Sorting
-
-
-
-
-
-
-
-
- It's possible to group chart data by a certain property. You may use group method or group property in a chart constructor.
-
-
-
-
-
-
- company
- year (total sales)
- year (max sales)
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/03_group/02_scales.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/03_group/02_scales.html
deleted file mode 100644
index 75746062b29..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/03_group/02_scales.html
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
- Scales and grouping
-
-
-
-
-
-
-
-
- To update configuration properties after grouping you need to cal define method with new settings.
-
-
-
-
- company
- year (total sales)
- year (max sales)
-
-
-
-
- asc
- desc
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/01_init.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/01_init.html
deleted file mode 100644
index 63c82ecf42c..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/01_init.html
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
- Initialization
-
-
-
-
-
-
-
- Pie chart with Automatic radius and center position
- Pie chart with Custom radius and center position
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/02_text.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/02_text.html
deleted file mode 100644
index 7ba118f19e2..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/02_text.html
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
- Labels
-
-
-
-
-
- There are two properties to define sectors labels:
-
label - defines text outside sectors
- pieInnerText - set text inside sectors
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/03_3d_chart.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/03_3d_chart.html
deleted file mode 100644
index 957f7ed7954..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/03_3d_chart.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
- 3D view
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/04_legend.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/04_legend.html
deleted file mode 100644
index 2d5c45c0151..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/04_pie_chart/04_legend.html
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
- Details Block
-
-
-
-
-
- Pie Chart provides property "details" that displayes informationa about all sectors in a separate block.
-
-
-
- PieChart with default details block
- PieChart with custom details block
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/01_init.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/01_init.html
deleted file mode 100644
index 626a1e216fe..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/01_init.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
- Initialization
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/02_style.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/02_style.html
deleted file mode 100644
index 39dfe7eb41f..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/02_style.html
+++ /dev/null
@@ -1,153 +0,0 @@
-
-
-
-
- Style definition
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/03_scale.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/03_scale.html
deleted file mode 100644
index 161cbf7ee53..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/03_scale.html
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
- Scales
-
-
-
-
-
-
-
- Automatic vertical scale
- Custom vertical scale
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/04_spline.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/04_spline.html
deleted file mode 100644
index 4f266ee8983..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/04_spline.html
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
- Initialization
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/05_series.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/05_series.html
deleted file mode 100644
index 532cd385519..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/05_line_chart/05_series.html
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
- Scales
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/01_init.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/01_init.html
deleted file mode 100644
index e31ae20dd39..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/01_init.html
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
- PieChart: Initiazation
-
-
-
-
-
- BarChart: Initiazation
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/02_text.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/02_text.html
deleted file mode 100644
index ee378b194f1..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/02_text.html
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
- PieChart: Initiazation
-
-
-
-
- BarChart: Initiazation
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/03_scales.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/03_scales.html
deleted file mode 100644
index edc1f6ca45d..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/03_scales.html
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
- PieChart: Initiazation
-
-
-
-
- BarChart: Scales
-
-
-
- Custom vertical scale and origin
- Automatic vertical scale
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/04_styles.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/04_styles.html
deleted file mode 100644
index 0877a95de16..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/04_styles.html
+++ /dev/null
@@ -1,207 +0,0 @@
-
-
-
-
- Chart
-
-
-
-
- Chart
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/05_stacked_chart.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/05_stacked_chart.html
deleted file mode 100644
index d165d0bd488..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/05_stacked_chart.html
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
- StackedBar Chart
-
-
-
-
- StackedBar Chart
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/06_series.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/06_series.html
deleted file mode 100644
index 3c5fb695692..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/06_series.html
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
- Series
-
-
-
-
- Series
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/07_horizonal_bars.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/07_horizonal_bars.html
deleted file mode 100644
index 6c8046c5855..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/07_horizonal_bars.html
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
- Horizontal bar Chart
-
-
-
-
- Horizontal bar Chart
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/08_horizonal_stacked_bars.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/08_horizonal_stacked_bars.html
deleted file mode 100644
index fe4d8509376..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/06_bar_chart/08_horizonal_stacked_bars.html
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
- Horizontal Stacked Bars
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/01_init.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/01_init.html
deleted file mode 100644
index 5195e7476e1..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/01_init.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
- Initialization
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/02_scale.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/02_scale.html
deleted file mode 100644
index e3d65bd6d55..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/02_scale.html
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
- Scales
-
-
-
-
-
-
-
-
- Custom vertical scale
- Automatic vertical scale
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/03_series.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/03_series.html
deleted file mode 100644
index c071386c348..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/03_series.html
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
- Scales
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/03_stacked_area.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/03_stacked_area.html
deleted file mode 100644
index 55fccb93e84..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/07_area_chart/03_stacked_area.html
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
-
-
- Scales
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/01_add.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/01_add.html
deleted file mode 100644
index 7a3b45f8cee..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/01_add.html
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
- Adding
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/02_events.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/02_events.html
deleted file mode 100644
index 495d42bf795..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/02_events.html
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
- Events
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/03_sorting.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/03_sorting.html
deleted file mode 100644
index cb8b254fe10..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/03_sorting.html
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
- Sorting
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/04_filtering.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/04_filtering.html
deleted file mode 100644
index 230aa820800..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/08_dynamic/04_filtering.html
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
- Filtering
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/01_dhtmlxgrid.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/01_dhtmlxgrid.html
deleted file mode 100644
index 698da15d9fd..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/01_dhtmlxgrid.html
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
- Integration with grid
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/02_dhtmlxgrid_group.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/02_dhtmlxgrid_group.html
deleted file mode 100644
index d911e209922..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/02_dhtmlxgrid_group.html
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
-
- Integration with Grid and Grouping
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/03_windows.html b/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/03_windows.html
deleted file mode 100644
index 822e621d9ca..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/09_integration/03_windows.html
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
- Integration with dhtmlxWindows
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/common/config.php b/addons/web_graph/static/lib/dhtmlxGraph/samples/common/config.php
deleted file mode 100644
index 3695ac3905a..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/common/config.php
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/common/data.php b/addons/web_graph/static/lib/dhtmlxGraph/samples/common/data.php
deleted file mode 100644
index ac8f5116a9d..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/common/data.php
+++ /dev/null
@@ -1,16 +0,0 @@
-";
-
-for ($i=1; $i <= 100; $i++) {
- $sales = rand(100,1000);
- $year = rand(1996,2009);
- $company = "Company ".rand(1,3);
- $str.=" ";
-}
-
-$str .= "";
-
-file_put_contents("stat.xml",$str);
-
-?>
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/common/sales.xml b/addons/web_graph/static/lib/dhtmlxGraph/samples/common/sales.xml
deleted file mode 100644
index 03c153cb1e5..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/common/sales.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
- -
-
2.9
- 2000
-
- -
-
3.5
- 2001
-
- -
-
3.1
- 2002
-
- -
-
4.2
- 2003
-
- -
-
4.5
- 2004
-
- -
-
7.4
- 2005
-
- -
-
9.6
- 2006
-
- -
-
9
- 2007
-
- -
-
7.3
- 2008
-
- -
-
5.8
- 2009
-
- -
-
7.7
- 2010
-
-
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/common/stat.xml b/addons/web_graph/static/lib/dhtmlxGraph/samples/common/stat.xml
deleted file mode 100644
index 824ff6f6829..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/common/stat.xml
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/samples/readme.txt b/addons/web_graph/static/lib/dhtmlxGraph/samples/readme.txt
deleted file mode 100644
index c1f4b60567c..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/samples/readme.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-Next samples
- 08_integration\01_dhtmlxgrid.html
- 08_integration\02_dhtmlxgrid_group.html
- 08_integration\03_layout.html
-requires external components ( dhtmlxgrid and dhtmlxwindow )
-
-Its recommended to run samples through any kind of webserver.
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/sources/dhtmlxchart.js b/addons/web_graph/static/lib/dhtmlxGraph/sources/dhtmlxchart.js
deleted file mode 100644
index cbb0f8346bd..00000000000
--- a/addons/web_graph/static/lib/dhtmlxGraph/sources/dhtmlxchart.js
+++ /dev/null
@@ -1,3498 +0,0 @@
-/*
-2010 July 02
-*/
-
-
-
-/* DHX DEPEND FROM FILE 'assert.js'*/
-
-
-if (!window.dhtmlx)
- dhtmlx={};
-
-//check some rule, show message as error if rule is not correct
-dhtmlx.assert = function(test, message){
- if (!test) dhtmlx.error(message);
-};
-dhtmlx.assert_enabled=function(){ return false; };
-
-//register names of event, which can be triggered by the object
-dhtmlx.assert_event = function(obj, evs){
- if (!obj._event_check){
- obj._event_check = {};
- obj._event_check_size = {};
- }
-
- for (var a in evs){
- obj._event_check[a.toLowerCase()]=evs[a];
- var count=-1; for (var t in evs[a]) count++;
- obj._event_check_size[a.toLowerCase()]=count;
- }
-};
-dhtmlx.assert_method_info=function(obj, name, descr, rules){
- var args = [];
- for (var i=0; i < rules.length; i++) {
- args.push(rules[i][0]+" : "+rules[i][1]+"\n "+rules[i][2].describe()+(rules[i][3]?"; optional":""));
- }
- return obj.name+"."+name+"\n"+descr+"\n Arguments:\n - "+args.join("\n - ");
-};
-dhtmlx.assert_method = function(obj, config){
- for (var key in config)
- dhtmlx.assert_method_process(obj, key, config[key].descr, config[key].args, (config[key].min||99), config[key].skip);
-};
-dhtmlx.assert_method_process = function (obj, name, descr, rules, min, skip){
- var old = obj[name];
- if (!skip)
- obj[name] = function(){
- if (arguments.length != rules.length && arguments.length < min)
- dhtmlx.log("warn","Incorrect count of parameters\n"+obj[name].describe()+"\n\nExpecting "+rules.length+" but have only "+arguments.length);
- else
- for (var i=0; i= 0) return true;
- return false;
- };
- dhtmlx.assert_rule_dimension.describe=function(){
- return "{Integer} value must be a positive number";
- };
-
- dhtmlx.assert_rule_number=function(check){
- if (typeof check == "number") return true;
- return false;
- };
- dhtmlx.assert_rule_number.describe=function(){
- return "{Integer} value must be a number";
- };
-
- dhtmlx.assert_rule_function=function(check){
- if (typeof check == "function") return true;
- return false;
- };
- dhtmlx.assert_rule_function.describe=function(){
- return "{Function} value must be a custom function";
- };
-
- dhtmlx.assert_rule_any=function(check){
- return true;
- };
- dhtmlx.assert_rule_any.describe=function(){
- return "Any value";
- };
-
- dhtmlx.assert_rule_mix=function(a,b){
- var t = function(check){
- if (a(check)||b(check)) return true;
- return false;
- };
- t.describe = function(){
- return a.describe();
- };
- return t;
- };
-
-}
-
-
-/* DHX DEPEND FROM FILE 'dhtmlx.js'*/
-
-
-/*DHX:Depend assert.js*/
-
-/*
- Common helpers
-*/
-dhtmlx.version="3.0";
-dhtmlx.codebase="./";
-
-//coding helpers
-
-//copies methods and properties from source to the target
-dhtmlx.extend = function(target, source){
- for (var method in source)
- target[method] = source[method];
-
- //applying asserts
- if (dhtmlx.assert_enabled() && source._assert){
- target._assert();
- target._assert=null;
- }
-
- //if source object has init code - call init against target
- if (source._init)
- target._init();
-
- return target;
-};
-//creates function with specified "this" pointer
-dhtmlx.bind=function(functor, object){
- return function(){ return functor.apply(object,arguments); };
-};
-
-//loads module from external js file
-dhtmlx.require=function(module){
- if (!dhtmlx._modules[module]){
- dhtmlx.assert(dhtmlx.ajax,"load module is required");
-
- //load and exec the required module
- dhtmlx.exec( dhtmlx.ajax().sync().get(dhtmlx.codebase+module).responseText );
- dhtmlx._modules[module]=true;
- }
-};
-dhtmlx._modules = {}; //hash of already loaded modules
-
-//evaluate javascript code in the global scoope
-dhtmlx.exec=function(code){
- if (window.execScript) //special handling for IE
- window.execScript(code);
- else window.eval(code);
-};
-
-/*
- creates method in the target object which will transfer call to the source object
- if event parameter was provided , each call of method will generate onBefore and onAfter events
-*/
-dhtmlx.methodPush=function(object,method,event){
- return function(){
- var res = false;
- //if (!event || this.callEvent("onBefore"+event,arguments)){ //not used anymore, probably can be removed
- res=object[method].apply(object,arguments);
- // if (event) this.callEvent("onAfter"+event,arguments);
- //}
- return res; //result of wrapped method
- };
-};
-//check === undefined
-dhtmlx.isNotDefined=function(a){
- return typeof a == "undefined";
-};
-//delay call to after-render time
-dhtmlx.delay=function(method, obj, params){
- setTimeout(function(){
- var ret = method.apply(obj,params);
- method = obj = params = null;
- return ret;
- },1);
-};
-
-//common helpers
-
-//generates unique ID (unique per window, nog GUID)
-dhtmlx.uid = function(){
- if (!this._seed) this._seed=(new Date).valueOf(); //init seed with timestemp
- this._seed++;
- return this._seed;
-};
-//resolve ID as html object
-dhtmlx.toNode = function(node){
- if (typeof node == "string") return document.getElementById(node);
- return node;
-};
-//adds extra methods for the array
-dhtmlx.toArray = function(array){
- return dhtmlx.extend((array||[]),dhtmlx.PowerArray);
-};
-//resolve function name
-dhtmlx.toFunctor=function(str){
- return (typeof(str)=="string") ? eval(str) : str;
-};
-
-//dom helpers
-
-//hash of attached events
-dhtmlx._events = {};
-//attach event to the DOM element
-dhtmlx.event=function(node,event,handler,master){
- node = dhtmlx.toNode(node);
-
- var id = dhtmlx.uid();
- dhtmlx._events[id]=[node,event,handler]; //store event info, for detaching
-
- if (master)
- handler=dhtmlx.bind(handler,master);
-
- //use IE's of FF's way of event's attaching
- if (node.addEventListener)
- node.addEventListener(event, handler, false);
- else if (node.attachEvent)
- node.attachEvent("on"+event, handler);
-
- return id; //return id of newly created event, can be used in eventRemove
-};
-
-//remove previously attached event
-dhtmlx.eventRemove=function(id){
-
- if (!id) return;
- dhtmlx.assert(this._events[id],"Removing non-existing event");
-
- var ev = dhtmlx._events[id];
- //browser specific event removing
- if (ev[0].removeEventListener)
- ev[0].removeEventListener(ev[1],ev[2],false);
- else if (ev[0].detachEvent)
- ev[0].detachEvent("on"+ev[1],ev[2]);
-
- delete this._events[id]; //delete all traces
-};
-
-
-//debugger helpers
-//anything starting from error or log will be removed during code compression
-
-//add message in the log
-dhtmlx.log = function(type,message,details){
- if (window.console && console.log){
- type=type.toLowerCase();
- if (window.console[type])
- window.console[type](message);
- else
- window.console.log(type +": "+message);
- if (details)
- window.console.log(details);
- }
-};
-//register rendering time from call point
-dhtmlx.log_full_time = function(name){
- dhtmlx._start_time_log = new Date();
- dhtmlx.log("Info","Timing start ["+name+"]");
- window.setTimeout(function(){
- var time = new Date();
- dhtmlx.log("Info","Timing end ["+name+"]:"+(time.valueOf()-dhtmlx._start_time_log.valueOf())/1000+"s");
- },1);
-};
-//register execution time from call point
-dhtmlx.log_time = function(name){
- var fname = "_start_time_log"+name;
- if (!dhtmlx[fname]){
- dhtmlx[fname] = new Date();
- dhtmlx.log("Info","Timing start ["+name+"]");
- } else {
- var time = new Date();
- dhtmlx.log("Info","Timing end ["+name+"]:"+(time.valueOf()-dhtmlx[fname].valueOf())/1000+"s");
- dhtmlx[fname] = null;
- }
-};
-//log message with type=error
-dhtmlx.error = function(message,details){
- dhtmlx.log("Error",message,details);
-};
-//event system
-dhtmlx.EventSystem={
- _init:function(){
- this._events = {}; //hash of event handlers, name => handler
- this._handlers = {}; //hash of event handlers, ID => handler
- this._map = {};
- },
- //temporary block event triggering
- block : function(){
- this._events._block = true;
- },
- //re-enable event triggering
- unblock : function(){
- this._events._block = false;
- },
- mapEvent:function(map){
- dhtmlx.extend(this._map, map);
- },
- //trigger event
- callEvent:function(type,params){
- if (this._events._block) return true;
-
- type = type.toLowerCase();
- dhtmlx.assert_event_call(this, type, params);
-
- var event_stack =this._events[type.toLowerCase()]; //all events for provided name
- var return_value = true;
-
- if (dhtmlx.debug) //can slowdown a lot
- dhtmlx.log("info","["+this.name+"] event:"+type,params);
-
- if (event_stack)
- for(var i=0; i=0) this.splice(pos,(len||1));
- },
- //find element in collection and remove it
- remove:function(value){
- this.removeAt(this.find(value));
- },
- //add element to collection at specific position
- insertAt:function(data,pos){
- if (!pos && pos!==0) //add to the end by default
- this.push(data);
- else {
- var b = this.splice(pos,(this.length-pos));
- this[pos] = data;
- this.push.apply(this,b); //reconstruct array without loosing this pointer
- }
- },
- //return index of element, -1 if it doesn't exists
- find:function(data){
- for (i=0; i0){
- str=this._toHex[number%16]+str;
- number=Math.floor(number/16);
- }
- while (str.length ");
- },
- addSector:function(id,alpha0,alpha1,x,y,R,ky){
- var points = [];
- points.push(x);
- points.push(Math.floor(y*ky));
- for(var i = alpha0; i < alpha1; i+=Math.PI/18){
- points.push(Math.floor(x+R*Math.cos(i)));
- points.push(Math.floor((y+R*Math.sin(i))*ky));
- }
- points.push(Math.floor(x+R*Math.cos(alpha1)));
- points.push(Math.floor((y+R*Math.sin(alpha1))*ky));
- points.push(x);
- points.push(Math.floor(y*ky));
-
- return this.addPoly(id,points);
- },
- render:function(obj){
- var d = dhtmlx.html.create("DIV");
- d.style.cssText="position:absolute; width:100%; height:100%; top:0px; left:0px;";
- obj.appendChild(d);
- var src = dhtmlx._isIE?"":"src='data:image/gif;base64,R0lGODlhEgASAIAAAP///////yH5BAUUAAEALAAAAAASABIAAAIPjI+py+0Po5y02ouz3pwXADs='";
- d.innerHTML=""+this._map.join("\n")+" ";
-
- obj._htmlmap = d; //for clearing routine
-
- this._map = [];
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'chart_graph.js'*/
-
-
-/*DHX:Depend map.js*/
-
-if (!dhtmlx.chart) dhtmlx.chart = {};
-
-dhtmlx.chart.line = {
-
- /**
- * renders a graphic
- * @param: ctx - canvas object
- * @param: data - object those need to be displayed
- * @param: width - the width of the container
- * @param: height - the height of the container
- */
- pvt_render_line:function(ctx, data, width, height){
- var maxValue,minValue;
- /*necessary if maxValue - minValue < 0*/
- var valueFactor;
- /*maxValue - minValue*/
- var relValue;
-
-
- /*necessary to add events and tooltips*/
- var map = new dhtmlx.ui.Map(this._id);
-
- /*scales*/
- var yax = !!this._settings.yAxis;
- var xax = !!this._settings.xAxis;
-
- if(yax&&(typeof this._settings.yAxis.end!="undefied")&&(typeof this._settings.yAxis.start!="undefied")&&this._settings.yAxis.step){
- maxValue = parseFloat(this._settings.yAxis.end);
- minValue = parseFloat(this._settings.yAxis.start);
- }
- else{
- maxValue = this.max(this._settings.value);
- minValue = this.min(this._settings.value);
- }
-
- /*a vertical scale*/
- this._drawYAxis(ctx,data,width,height,minValue,maxValue);
-
- /*necessary for automatic scale*/
- if(yax){
- maxValue = parseFloat(this._settings.yAxis.end);
- minValue = parseFloat(this._settings.yAxis.start);
- }
-
- var topPadding = parseInt(this._settings.padding.top,10);
- var leftPadding = parseInt(this._settings.padding.left,10);
- var bottomPadding = parseInt(this._settings.padding.bottom,10);
- var rightPadding = parseInt(this._settings.padding.right,10);
-
- /*available height*/
- var total_height = height-topPadding-bottomPadding;
-
-
- /*unit calculation (y_position = value*unit)*/
- var relativeValues = this._getRelativeValue(minValue,maxValue);
- relValue = relativeValues[0];
- valueFactor = relativeValues[1];
- var unit = total_height/relValue;
-
- if(!yax){
- /*defines start value for better representation of small values*/
- var startValue = (unit>10?unit:10);
- unit = (total_height-startValue)/relValue;
- }
-
- /*a space available for a single item*/
- var cellWidth = Math.round((width - leftPadding - rightPadding)/data.length);
-
- /*the value that defines the map area position*/
- var areaPos = Math.floor(cellWidth/2);
-
- /*drawing all items*/
- if (data.length) {
- /*gets the vertical coordinate of an item*/
- var _graph_point = function(data){
- /*the real value of an object*/
- var value = this._settings.value(data);
- /*a relative value*/
- var v = (parseFloat(value) - minValue)*valueFactor;
- if(!yax)
- v += startValue/unit;
- /*a vertical coordinate*/
- var y = height - Math.floor(unit*v) - bottomPadding;
- /*the limit of the minimum value is the minimum visible value*/
- if(v<0)
- y = height - topPadding;
- /*the limit of the maximum value*/
- if(value > maxValue)
- y = topPadding;
- /*the limit of the minimum value*/
- if(value < minValue)
- y = height - bottomPadding;
- return y;
- };
- /*the vertical position of the first item*/
- var y1 = _graph_point.call(this,data[0]);
- /*drawing the previous item and the line between to items*/
- for(var i=1; i <= data.length;i ++){
-
-
- /*horizontal positions of the previous and current items (0.5 - the fix for line width)*/
- var x1 = Math.floor(cellWidth*(i-0.5)) - 0.5 + leftPadding;
- var x2 = Math.floor(cellWidth*(i+0.5)) - 0.5 + leftPadding;
-
- /*sets label on the horizontal scale*/
- this._drawXAxisLabel(x1,data[i-1]);
- /*draws a vertical line for the horizontal scale*/
- this._drawXAxisLine(ctx,x1,height-bottomPadding,topPadding);
-
- /*a line between items*/
- if (data.length!=i){
- var y2 = _graph_point.call(this,data[i]);
- this._drawLine(ctx,x1,y1,x2,y2,this._settings.line.color(data[i-1]),this._settings.line.width);
- }
-
- /*draws prevous item*/
- this._drawGraphicItem(ctx,x1,y1,data[i-1]);
-
- /*creates map area*/
- map.addRect(data[i-1].id,[x1-areaPos,y1-areaPos,x1+areaPos,y1+areaPos]);
-
- y1=y2;
- }
- _graph_point = null;
- }
- /*horizontal scale*/
- this._drawXAxis(ctx,data,width,height);
- map.render(this._obj);
- },
- /**
- * draws an item and its label
- * @param: ctx - canvas object
- * @param: x0 - the x position of a circle
- * @param: y0 - the y position of a circle
- * @param: obj - data object
- */
- _drawGraphicItem:function(ctx,x0,y0,obj){
- var R = parseInt(this._settings.item.radius,10);
- ctx.lineWidth = parseInt(this._settings.item.borderWidth,10);
- ctx.fillStyle = this._settings.item.color(obj);
- ctx.strokeStyle = this._settings.item.borderColor(obj);
- ctx.beginPath();
- ctx.arc(x0,y0,R,0,Math.PI*2,true);
- ctx.fill();
- ctx.stroke();
- /*item label*/
- this.renderTextAt(false, true, x0,y0-R-this._settings.labelOffset,this._settings.label(obj));
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'chart_bar.js'*/
-
-
-/*DHX:Depend map.js*/
-
-if (!dhtmlx.chart) dhtmlx.chart = {};
-
-dhtmlx.chart.bar = {
- /**
- * renders a bar chart
- * @param: ctx - canvas object
- * @param: data - object those need to be displayed
- * @param: x - the width of the container
- * @param: y - the height of the container
- */
- pvt_render_bar:function(ctx, data, x, y){
- var maxValue,minValue;
- /*necessary if maxValue - minValue < 0*/
- var valueFactor;
- /*maxValue - minValue*/
- var relValue;
-
- var map = new dhtmlx.ui.Map(this._id);
-
- var yax = !!this._settings.yAxis;
- var xax = !!this._settings.xAxis;
-
- if(yax&&(typeof this._settings.yAxis.end!="undefied")&&(typeof this._settings.yAxis.start!="undefied")&&this._settings.yAxis.step){
- maxValue = parseFloat(this._settings.yAxis.end);
- minValue = parseFloat(this._settings.yAxis.start);
- }
- else{
- maxValue = this.max(this._settings.value);
- minValue = this.min(this._settings.value);
- }
-
- /*a vertical scale*/
- this._drawYAxis(ctx,data,x,y,minValue,maxValue);
-
- /*necessary for automatic scale*/
- if(yax){
- maxValue = parseFloat(this._settings.yAxis.end);
- minValue = parseFloat(this._settings.yAxis.start);
- }
-
- var yPadding = parseInt(this._settings.padding.top,10);
- var xPadding = parseInt(this._settings.padding.left,10);
- var bottomPadding = parseInt(this._settings.padding.bottom,10);
- var total_height = y-yPadding-bottomPadding;
-
- /*unit calculation (bar_height = value*unit)*/
- var relativeValues = this._getRelativeValue(minValue,maxValue);
- relValue = relativeValues[0];
- valueFactor = relativeValues[1];
-
- var unit = total_height/relValue;
- if(!yax){
- /*defines start value for better representation of small values*/
- var startValue = (unit>10?unit:10);
- unit = (total_height-startValue)/relValue;
- }
-
- /*an available width for one bar*/
- var cellWidth = Math.floor((x-xPadding-parseInt(this._settings.padding.right,10))/data.length);
- /*a real bar width */
- var barWidth = parseInt(this._settings.width,10);
- if(barWidth>cellWidth) barWidth = cellWidth-2;
- /*the half of distance between bars*/
- var barOffset = Math.floor((cellWidth - barWidth)/2);
- /*the radius of rounding in the top part of each bar*/
- var radius = Math.round(barWidth/5);
-
- var inner_gradient = false;
- var gradient = this._settings.gradient;
-
- if (gradient === true){
- inner_gradient = true;
- gradient = false;
- } else if (gradient){
- gradient = ctx.createLinearGradient(0,bottomPadding,0,y-yPadding);
- this._settings.gradient(gradient);
- }
- var scaleY = 0;
- /*draws a black line if the horizontal scale isn't defined*/
- if(bottomPadding&&!xax){
- scaleY = y-bottomPadding;
- this._drawLine(ctx,0,scaleY+0.5,x,scaleY+0.5,"#000000",1); //hardcoded color!
- }
-
- for(var i=0; i < data.length;i ++){
-
-
- var value = parseFloat(this._settings.value(data[i]));
- if(value>maxValue) value = maxValue;
- value -= minValue;
- value *= valueFactor;
-
- /*start point (bottom left)*/
- var x0 = xPadding + barOffset + i*cellWidth;
- var y0 = y-bottomPadding;
-
-
- /*sets a label in the horizontal scale*/
- this._drawXAxisLabel(x0+Math.floor(barWidth/2),data[i]);
-
- if(value<0||(this._settings.yAxis&&value===0)){
- this.renderTextAt(true, true, x0+Math.floor(barWidth/2),y0,this._settings.label(data[i]));
- continue;
- }
-
- /*takes start value into consideration*/
- if(!yax) value += startValue/unit;
- var color = gradient||this._settings.color.call(this,data[i]);
-
- /*drawing the gradient border of a bar*/
- if(this._settings.border){
- ctx.beginPath();
- ctx.fillStyle = color;
- this._setBarPoints(ctx,x0-1,y0,barWidth+2,radius,unit,value,0);
- ctx.lineTo(x0,0);
- ctx.fill();
-
- ctx.fillStyle = "#000000";
- ctx.globalAlpha = 0.37;
- ctx.beginPath();
- this._setBarPoints(ctx,x0-1,y0,barWidth+2,radius,unit,value,0);
- ctx.fill();
- }
-
- /*drawing bar body*/
- ctx.globalAlpha = this._settings.alpha.call(this,data[i]);
- ctx.fillStyle = (gradient||this._settings.color.call(this,data[i]));
- ctx.beginPath();
- var points = this._setBarPoints(ctx,x0,y0,barWidth,radius,unit,value,(this._settings.border?1:0));
- if (gradient) ctx.lineTo(x0,0); //fix gradient sphreading
- ctx.fill();
- ctx.globalAlpha = 1;
-
- if (inner_gradient){
- var i_gradient = ctx.createLinearGradient(0,y0-unit*value+2,0,y0);
- i_gradient.addColorStop(0,color);
- i_gradient.addColorStop(0.1,color);
- i_gradient.addColorStop(1,"#FFFFFF");
- ctx.fillStyle = i_gradient;
- ctx.beginPath();
- var points = this._setBarPoints(ctx,x0+2,y0,barWidth-4,radius,unit,value,1);
- ctx.fill();
- }
-
-
- /*sets a bar label*/
- this.renderTextAt(true, true, x0+Math.floor(barWidth/2),points[1],this._settings.label(data[i]));
- /*defines a map area for a bar*/
- map.addRect(data[i].id,[x0,points[1],points[0],y0]);
- }
-
- this._drawXAxis(ctx,data,x,y);
- map.render(this._obj);
- },
- /**
- * sets points for bar and returns the position of the bottom right point
- * @param: ctx - canvas object
- * @param: x0 - the x position of start point
- * @param: y0 - the y position of start point
- * @param: barWidth - bar width
- * @param: radius - the rounding radius of the top
- * @param: unit - the value defines the correspondence between item value and bar height
- * @param: value - item value
- * @param: offset - the offset from expected bar edge (necessary for drawing border)
- */
- _setBarPoints:function(ctx,x0,y0,barWidth,radius,unit,value,offset){
- /*correction for displaing small values (when rounding radius is bigger than bar height)*/
- var angle_corr = 0;
- if(radius>unit*value){
- var cosA = (radius-unit*value)/radius;
- angle_corr = -Math.acos(cosA)+Math.PI/2;
- }
- /*start*/
- ctx.moveTo(x0,y0);
- /*start of left rounding*/
- var y1 = y0 - Math.floor(unit*value) + radius;
- if(radiusdetails.total_height))
- y0 += y-details.total_height-ctx.lineWidth/2;
- /*considers item index*/
- y0 += i*details.height;
-
- ctx.moveTo(x0,y0);
- var x1 = x0+details.marker.width-details.marker.height +1;
- ctx.lineTo(x1,y0);
- ctx.stroke();
-
- /*item text*/
- this.renderText(x1+details.marker.width/2+5,y0-details.marker.height/2,details.template(obj));
- },
- /**
- * returns calculated pie parameters: center position and radius
- * @param: x - the width of a container
- * @param: y - the height of a container
- */
- _getPieParameters:function(x,y){
- var offset = 0;
- if(this._settings.legend)
- offset = this._settings.legend.width*(this._settings.legend.align=="right"?-1:1);
- x = (x + offset)/2;
- y = y/2;
- var radius = Math.min(x,y)-this._settings.padding.top;
- return {"x":x,"y":y,"radius":radius};
- },
- /**
- * creates lower part of sector in 3Dpie
- * @param: ctx - canvas object
- * @param: x0 - the horizontal position of the pie center
- * @param: y0 - the vertical position of the pie center
- * @param: a0 - the angle that defines the first edge of a sector
- * @param: a1 - the angle that defines the second edge of a sector
- * @param: R - pie radius
- * @param: line (boolean) - if the sector needs a border
- */
- _createLowerSector:function(ctx,x0,y0,a1,a2,R,line){
- ctx.lineWidth = 1;
- /*checks if the lower sector needs being displayed*/
- if(!((a1<=0 && a2>=0)||(a1>=0 && a2<=Math.PI)||(a1<=Math.PI && a2>=Math.PI))) return;
-
- if(a1<=0 && a2>=0){
- a1 = 0;
- line = false;
- this._drawSectorLine(ctx,x0,y0,R,a1,a2);
- }
- if(a1<=Math.PI && a2>=Math.PI){
- a2 = Math.PI;
- line = false;
- this._drawSectorLine(ctx,x0,y0,R,a1,a2);
- }
- /*the height of 3D pie*/
- var offset = (this._settings.height||Math.floor(R/4))/this._settings.cant;
- ctx.beginPath();
- ctx.arc(x0,y0,R,a1,a2,false);
- ctx.lineTo(x0+R*Math.cos(a2),y0+R*Math.sin(a2)+offset);
- ctx.arc(x0,y0+offset,R,a2,a1,true);
- ctx.lineTo(x0+R*Math.cos(a1),y0+R*Math.sin(a1));
- ctx.fill();
- if(line)
- ctx.stroke();
- },
- /**
- * draws a serctor arc
- */
- _drawSectorLine:function(ctx,x0,y0,R,a1,a2){
- ctx.beginPath();
- ctx.arc(x0,y0,R,a1,a2,false);
- ctx.stroke();
- },
- /**
- * adds a shadow to pie
- * @param: ctx - canvas object
- * @param: x - the horizontal position of the pie center
- * @param: y - the vertical position of the pie center
- * @param: R - pie radius
- */
- _addShadow:function(ctx,x,y,R){
- var shadows = ["#676767","#7b7b7b","#a0a0a0","#bcbcbc","#d1d1d1","#d6d6d6"];
- for(var i = shadows.length-1;i>-1;i--){
- ctx.beginPath();
- ctx.fillStyle = shadows[i];
- ctx.arc(x+2,y+2,R+i,0,Math.PI*2,true);
- ctx.fill();
- }
- },
- /**
- * returns a gray gradient
- * @param: gradient - gradient object
- */
- _getGrayGradient:function(gradient){
- gradient.addColorStop(0.0,"#ffffff");
- gradient.addColorStop(0.7,"#7a7a7a");
- gradient.addColorStop(1.0,"#000000");
- return gradient;
- },
- /**
- * adds gray radial gradient
- * @param: ctx - canvas object
- * @param: x - the horizontal position of the pie center
- * @param: y - the vertical position of the pie center
- * @param: radius - pie radius
- * @param: x0 - the horizontal position of a gradient center
- * @param: y0 - the vertical position of a gradient center
- */
- _showRadialGradient:function(ctx,x,y,radius,x0,y0){
- ctx.globalAlpha = 0.3;
- ctx.beginPath();
- var gradient;
- if(typeof this._settings.gradient!= "function"){
- gradient = ctx.createRadialGradient(x0,y0,radius/4,x,y,radius);
- gradient = this._getGrayGradient(gradient);
- }
- else gradient = this._settings.gradient(gradient);
- ctx.fillStyle = gradient;
- ctx.arc(x,y,radius,0,Math.PI*2,true);
- ctx.fill();
- ctx.globalAlpha = 1;
- },
- /**
- * returns the calculates pie parameters: center position and radius
- * @param: ctx - canvas object
- * @param: x0 - the horizontal position of the pie center
- * @param: y0 - the vertical position of the pie center
- * @param: R - pie radius
- * @param: alpha1 - the angle that defines the 1st edge of a sector
- * @param: alpha2 - the angle that defines the 2nd edge of a sector
- * @param: ky - the value that defines an angle of inclination
- * @param: text - label text
- * @param: in_width (boolean) - if label needs being displayed inside a pie
- */
- _drawSectorLabel:function(x0,y0,R,alpha1,alpha2,ky,text,in_width){
- var t = this.renderText(0,0,text,0,1);
- if (!t) return;
-
- //get existing width of text
- var labelWidth = t.scrollWidth;
- t.style.width = labelWidth+"px"; //adjust text label to fit all text
- if (labelWidth>x0) labelWidth = x0; //the text can't be greater than half of view
-
- //calculate expected correction based on default font metrics
- var width = 8;
- if (in_width) width = labelWidth/1.8;
- var alpha = alpha1+(alpha2-alpha1)/2;
-
- //calcualteion position and correction
- R = R-(width-8)/2;
- var corr_x = - width;
- var corr_y = -8;
- var align = "left";
-
- //for items in right upper sector
- if(alpha>=Math.PI/2 && alpha=Math.PI){
- corr_x = -labelWidth-corr_x+1;
- align = "right";
- }
-
- //calculate position of text
- //basically get point at center of pie sector
- var y = (y0+Math.floor(R*Math.sin(alpha)))*ky+corr_y;
- var x = x0+Math.floor((R+width/2)*Math.cos(alpha))+corr_x;
-
- //if pie sector starts in left of right part pie, related text
- //must be placed to the left of to the right of pie as well
- var left_end = (alpha2 < Math.PI/2+0.01)
- var left_start = (alpha < Math.PI/2);
- if (left_start && left_end)
- x = Math.max(x,x0+3); //right part of pie
- else if (!left_start && !left_end)
- x = Math.min(x,x0-labelWidth); //left part of pie
-
-
- /*correction for the lower sector of the 3D pie*/
- if (!in_width && ky<1 && y > y0*ky){
- y+= (this._settings.height||Math.floor(R/4));
- }
-
- //we need to set position of text manually, based on above calculations
- t.style.top = y+"px";
- t.style.left = x+"px";
- t.style.width = labelWidth+"px";
- t.style.textAlign = align;
- t.style.whiteSpace = "nowrap";
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'template.js'*/
-
-
-/*
- Template - handles html templates
-*/
-
-/*DHX:Depend dhtmlx.js*/
-
-dhtmlx.Template={
- _cache:{
- },
- empty:function(){
- return "";
- },
- setter:function(name, value){
- return dhtmlx.Template.fromHTML(value);
- },
- obj_setter:function(name,value){
- var f = dhtmlx.Template.setter(name,value);
- var obj = this;
- return function(){
- return f.apply(obj, arguments);
- };
- },
- fromHTML:function(str){
- if (typeof str == "function") return str;
- if (this._cache[str])
- return this._cache[str];
-
- //supported idioms
- // {obj} => value
- // {obj.attr} => named attribute or value of sub-tag in case of xml
- // {obj.attr?some:other} conditional output
- // {-obj => sub-template
- str=(str||"").toString();
- str=str.replace(/[\r\n]+/g,"\\n");
- str=str.replace(/\{obj\.([^}?]+)\?([^:]*):([^}]*)\}/g,"\"+(obj.$1?\"$2\":\"$3\")+\"");
- str=str.replace(/\{common\.([^}\(]*)\}/g,"\"+common.$1+\"");
- str=str.replace(/\{common\.([^\}\(]*)\(\)\}/g,"\"+(common.$1?common.$1(obj):\"\")+\"");
- str=str.replace(/\{obj\.([^}]*)\}/g,"\"+obj.$1+\"");
- str=str.replace(/#([a-z0-9_]+)#/gi,"\"+obj.$1+\"");
- str=str.replace(/\{obj\}/g,"\"+obj+\"");
- str=str.replace(/\{-obj/g,"{obj");
- str=str.replace(/\{-common/g,"{common");
- str="return \""+str+"\";";
- return this._cache[str]= Function("obj","common",str);
- }
-};
-
-dhtmlx.Type={
- /*
- adds new template-type
- obj - object to which template will be added
- data - properties of template
- */
- add:function(obj, data){
- //auto switch to prototype, if name of class was provided
- if (!obj.types && obj.prototype.types)
- obj = obj.prototype;
- //if (typeof data == "string")
- // data = { template:data };
-
- if (dhtmlx.assert_enabled())
- this.assert_event(data);
-
- var name = data.name||"default";
-
- //predefined templates - autoprocessing
- this._template(data);
- this._template(data,"edit");
- this._template(data,"loading");
-
- obj.types[name]=dhtmlx.extend(dhtmlx.extend({},(obj.types[name]||this._default)),data);
- return name;
- },
- //default template value - basically empty box with 5px margin
- _default:{
- css:"default",
- template:function(){ return ""; },
- template_edit:function(){ return ""; },
- template_loading:function(){ return "..."; },
- width:150,
- height:80,
- margin:5,
- padding:0
- },
- //template creation helper
- _template:function(obj,name){
- name = "template"+(name?("_"+name):"");
- var data = obj[name];
- //if template is a string - check is it plain string or reference to external content
- if (data && (typeof data == "string")){
- if (data.indexOf("->")!=-1){
- data = data.split("->");
- switch(data[0]){
- case "html": //load from some container on the page
- data = dhtmlx.html.getValue(data[1]).replace(/\"/g,"\\\"");
- break;
- case "http": //load from external file
- data = new dhtmlx.ajax().sync().get(data[1],{uid:(new Date()).valueOf()}).responseText;
- break;
- default:
- //do nothing, will use template as is
- break;
- }
- }
- obj[name] = dhtmlx.Template.fromHTML(data);
- }
- }
-};
-
-
-
-/* DHX DEPEND FROM FILE 'single_render.js'*/
-
-
-/*
- REnders single item.
- Can be used for elements without datastore, or with complex custom rendering logic
-
- @export
- render
-*/
-
-/*DHX:Depend template.js*/
-
-dhtmlx.SingleRender={
- _init:function(){
- },
- //convert item to the HTML text
- _toHTML:function(obj){
- /*
- this one doesn't support per-item-$template
- it has not sense, because we have only single item per object
- */
- return this.type._item_start(obj,this.type)+this.type.template(obj,this.type)+this.type._item_end;
- },
- //render self, by templating data object
- render:function(){
- if (!this.callEvent || this.callEvent("onBeforeRender",[this.data])){
- if (this.data)
- this._dataobj.innerHTML = this._toHTML(this.data);
- if (this.callEvent) this.callEvent("onAfterRender",[]);
- }
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'tooltip.js'*/
-
-
-/*
- UI: Tooltip
-
- @export
- show
- hide
-*/
-
-/*DHX:Depend tooltip.css*/
-/*DHX:Depend template.js*/
-/*DHX:Depend single_render.js*/
-
-dhtmlx.ui.Tooltip=function(container){
- this.name = "Tooltip";
- this.version = "3.0";
-
- if (dhtmlx.assert_enabled()) this._assert();
-
- if (typeof container == "string"){
- container = { template:container };
- }
-
- dhtmlx.extend(this, dhtmlx.Settings);
- dhtmlx.extend(this, dhtmlx.SingleRender);
- this._parseSettings(container,{
- type:"default",
- dy:0,
- dx:20
- });
-
- //create container for future tooltip
- this._dataobj = this._obj = document.createElement("DIV");
- this._obj.className="dhx_tooltip";
- dhtmlx.html.insertBefore(this._obj,document.body.firstChild);
-};
-dhtmlx.ui.Tooltip.prototype = {
- //show tooptip
- //pos - object, pos.x - left, pox.y - top
- show:function(data,pos){
- //render sefl only if new data was provided
- if (this.data!=data){
- this.data=data;
- this.render(data);
- }
- //show at specified position
- this._obj.style.top = pos.y+this._settings.dy+"px";
- this._obj.style.left = pos.x+this._settings.dx+"px";
- this._obj.style.display="block";
- },
- //hide tooltip
- hide:function(){
- this.data=null; //nulify, to be sure that on next show it will be fresh-rendered
- this._obj.style.display="none";
- },
- types:{
- "default":dhtmlx.Template.fromHTML("{obj.id}")
- },
- template_item_start:dhtmlx.Template.empty,
- template_item_end:dhtmlx.Template.empty
-};
-
-
-
-/* DHX DEPEND FROM FILE 'autotooltip.js'*/
-
-
-/*
- Behavior: AutoTooltip - links tooltip to data driven item
-*/
-
-/*DHX:Depend tooltip.js*/
-
-dhtmlx.AutoTooltip = {
- tooltip_setter:function(mode,value){
- var t = new dhtmlx.ui.Tooltip(value);
- this.attachEvent("onMouseMove",function(id,e){ //show tooltip on mousemove
- t.show(this.get(id),dhtmlx.html.pos(e));
- });
- this.attachEvent("onMouseOut",function(id,e){ //hide tooltip on mouseout
- t.hide();
- });
- this.attachEvent("onMouseMoving",function(id,e){ //hide tooltip just after moving start
- t.hide();
- });
- return t;
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'datastore.js'*/
-
-
-/*DHX:Depend dhtmlx.js*/
-
-/*
- DataStore is not a behavior, it standalone object, which represents collection of data.
- Call provideAPI to map data API
-
- @export
- exists
- idByIndex
- indexById
- get
- set
- refresh
- dataCount
- sort
- filter
- next
- previous
- clearAll
- first
- last
-*/
-dhtmlx.DataStore = function(){
- this.name = "DataStore";
-
- dhtmlx.extend(this, dhtmlx.EventSystem);
-
- this.setDriver("xml"); //default data source is an XML
- this.pull = {}; //hash of IDs
- this.order = dhtmlx.toArray(); //order of IDs
-};
-
-dhtmlx.DataStore.prototype={
- //defines type of used data driver
- //data driver is an abstraction other different data formats - xml, json, csv, etc.
- setDriver:function(type){
- dhtmlx.assert(dhtmlx.DataDriver[type],"incorrect DataDriver");
- this.driver = dhtmlx.DataDriver[type];
- },
- //process incoming raw data
- _parse:function(data){
- //get size and position of data
- var info = this.driver.getInfo(data);
- //get array of records
- var recs = this.driver.getRecords(data);
-
- var from = (info._from||0)*1;
- var j=0;
- for (var i=0; ito){ //can be in case of backward shift-selection
- var a=to; to=from; from=a;
- }
- }
- return this.getIndexRange(from,to);
- },
- //converts range of indexes to array of all IDs between them
- getIndexRange:function(from,to){
- to=Math.min(to,this.dataCount()-1);
-
- var ret=dhtmlx.toArray(); //result of method is rich-array
- for (var i=from; i <= to; i++)
- ret.push(this.get(this.order[i]));
- return ret;
- },
- //returns total count of elements
- dataCount:function(){
- return this.order.length;
- },
- //returns truy if item with such ID exists
- exists:function(id){
- return !!(this.pull[id]);
- },
- //nextmethod is not visible on component level, check DataMove.move
- //moves item from source index to the target index
- move:function(sindex,tindex){
- if (sindex<0 || tindex<0 || tindex >= this.order.length){
- dhtmlx.error("DataStore::move","Incorrect indexes");
- return;
- }
-
-
- var id = this.idByIndex(sindex);
- var obj = this.get(id);
-
- this.order.removeAt(sindex); //remove at old position
- //if (sindex>tindex) tindex--; //correct shift, caused by element removing
- this.order.insertAt(id,tindex); //insert at new position
-
- //repaint signal
- this.callEvent("onStoreUpdated",[id,obj,"move"]);
- },
- //adds item to the store
- add:function(obj,index){
- //generate id for the item
- var id = this.id(obj);
-
- //by default item is added to the end of the list
- var data_size = this.dataCount();
- if (dhtmlx.isNotDefined(index))
- index = data_size;
- //check to prevent too big indexes
- if (index > data_size){
- dhtmlx.log("Warning","DataStore:add","Index of out of bounds");
- index = Math.min(this.order.length,index);
- }
-
- if (!this.callEvent("onbeforeAdd",[id,index])) return;
-
- if (this.exists(id)) return dhtmlx.error("Not unique ID");
-
- this.pull[id]=obj;
- this.order.insertAt(id,index);
- if (this._filter_order){ //adding during filtering
- //we can't know the location of new item in full dataset, making suggestion
- //put at end by default
- var original_index = this._filter_order.length;
- //put at start only if adding to the start and some data exists
- if (!index && this.order.length)
- original_index = 0;
-
- this._filter_order.insertAt(id,original_index);
- }
-
- this.callEvent("onafterAdd",[id,index]);
- //repaint signal
- this.callEvent("onStoreUpdated",[id,obj,"add"]);
- return id;
- },
-
- //removes element from datastore
- remove:function(id){
- //id can be an array of IDs - result of getSelect, for example
- if (id instanceof Array){
- for (var i=0; i < id.length; i++)
- this.remove(id[i]);
- return;
- }
- if (!this.callEvent("onbeforedelete",[id])) return;
- if (!this.exists(id)) return dhtmlx.error("Not existing ID",id);
- var obj = this.get(id); //save for later event
- //clear from collections
- this.order.remove(id);
- if (this._filter_order)
- this._filter_order.remove(id);
-
- delete this.pull[id];
- this.callEvent("onafterdelete",[id]);
- //repaint signal
- this.callEvent("onStoreUpdated",[id,obj,"delete"]);
- },
- //deletes all records in datastore
- clearAll:function(){
- //instead of deleting one by one - just reset inner collections
- this.pull = {};
- this.order = dhtmlx.toArray();
- this._filter_order = null;
- this.callEvent("onClearAll",[]);
- this.refresh();
- },
- //converts id to index
- idByIndex:function(index){
- if (index>=this.order.length || index<0)
- dhtmlx.log("Warning","DataStore::idByIndex Incorrect index");
-
- return this.order[index];
- },
- //converts index to id
- indexById:function(id){
- var res = this.order.find(id); //slower than idByIndex
-
- if (!res && res!==0)
- dhtmlx.log("Warning","DataStore::indexById Non-existing ID: "+ id);
-
- return res;
- },
- //returns ID of next element
- next:function(id,step){
- return this.order[this.indexById(id)+(step||1)];
- },
- //returns ID of first element
- first:function(){
- return this.order[0];
- },
- //returns ID of last element
- last:function(){
- return this.order[this.order.length-1];
- },
- //returns ID of previous element
- previous:function(id,step){
- return this.order[this.indexById(id)-(step||1)];
- },
- /*
- sort data in collection
- by - settings of sorting
-
- or
-
- by - sorting function
- dir - "asc" or "desc"
-
- or
-
- by - property
- dir - "asc" or "desc"
- as - type of sortings
-
- Sorting function will accept 2 parameters and must return 1,0,-1, based on desired order
- */
- sort:function(by, dir, as){
-
- var sort = by;
- if (typeof by == "function")
- sort = {as:by, dir:dir};
- else if (typeof by == "string")
- sort = {by:by, dir:dir, as:as};
-
-
- var parameters = [sort.by, sort.dir, sort.as];
- if (!this.callEvent("onbeforesort",parameters)) return;
-
- var sorter = dhtmlx.sort.create(sort);
- //get array of IDs
- var neworder = this.getRange();
- neworder.sort(sorter);
- this.order = neworder.map(function(obj){ return this.id(obj); },this);
-
- //repaint self
- this.refresh();
-
- this.callEvent("onaftersort",parameters);
- },
- /*
- Filter datasource
-
- text - property, by which filter
- value - filter mask
-
- or
-
- text - filter method
-
- Filter method will receive data object and must return true or false
- */
- filter:function(text,value){
- //remove previous filtering , if any
- if (this._filter_order){
- this.order = this._filter_order;
- delete this._filter_order;
- }
- //if text not define -just unfilter previous state and exit
- if (text){
- var filter = text;
- if (typeof text == "string"){
- text = dhtmlx.Template.setter(0,text);
- filter = function(obj,value){ //default filter - string start from, case in-sensitive
- return text(obj).toLowerCase().indexOf(value)!=-1;
- };
- }
-
- value = (value||"").toString().toLowerCase();
- var neworder = dhtmlx.toArray();
- this.order.each(function(id){
- if (filter(this.get(id),value))
- neworder.push(id);
- },this);
- //set new order of items, store original
- this._filter_order = this.order;
- this.order = neworder;
- }
- //repaint self
- this.refresh();
- },
- /*
- Iterate through collection
- */
- each:function(method,master){
- for (var i=0; ib?1:(ab?1:(ab?1:(a max) max = property(obj)*1;
- });
- return max;
- },
- _split_data_by:function(stats){
- var any=function(property, data){
- property = dhtmlx.Template.setter(0,property);
- return property(data[0]);
- };
- var key = dhtmlx.Template.setter(0,stats.by);
- if (!stats.map[key])
- stats.map[key] = [key, any];
-
- var groups = {};
- var labels = [];
- this.data.each(function(data){
- var current = key(data);
- if (!groups[current]){
- labels.push({id:current});
- groups[current] = dhtmlx.toArray();
- }
- groups[current].push(data);
- });
- for (var prop in stats.map){
- var functor = (stats.map[prop][1]||any);
- if (typeof functor != "function")
- functor = this[functor];
-
- for (var i=0; i < labels.length; i++) {
- labels[i][prop]=functor.call(this, stats.map[prop][0], groups[labels[i].id]);
- }
- }
-// if (this._settings.sort)
-// labels.sortBy(stats.sort);
-
- this._not_grouped_data = this.data;
- this.data = new dhtmlx.DataStore();
- this.data.provideApi(this,true);
- this._init_group_data_event(this.data, this);
- this.parse(labels,"json");
- },
- group:function(config,mode){
- this.ungroup(false);
- this._split_data_by(config);
- if (mode!==false)
- this.render();
- },
- ungroup:function(mode){
- if (this._not_grouped_data){
- this.data = this._not_grouped_data;
- this.data.provideApi(this, true);
- }
- if (mode!==false)
- this.render();
- },
- group_setter:function(name, config){
- dhtmlx.assert(typeof config == "object", "Incorrect group value");
- dhtmlx.assert(config.by,"group.by is mandatory");
- dhtmlx.assert(config.map,"group.map is mandatory");
- return config;
- },
- //need to be moved to more appropriate object
- sort_setter:function(name, config){
- if (typeof config != "object")
- config = { by:config };
-
- this._mergeSettings(config,{
- as:"string",
- dir:"asc"
- });
- return config;
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'key.js'*/
-
-
-/*
- Behavior:KeyEvents - hears keyboard
-*/
-dhtmlx.KeyEvents = {
- _init:function(){
- //attach handler to the main container
- dhtmlx.event(this._obj,"keypress",this._onKeyPress,this);
- },
- //called on each key press , when focus is inside of related component
- _onKeyPress:function(e){
- e=e||event;
- var code = e.keyCode; //FIXME better solution is required
- this.callEvent((this._edit_id?"onEditKeyPress":"onKeyPress"),[code,e.ctrlKey,e.shiftKey,e]);
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'mouse.js'*/
-
-
-/*
- Behavior:MouseEvents - provides inner evnets for mouse actions
-*/
-dhtmlx.MouseEvents={
- _init: function(){
- //attach dom events if related collection is defined
- if (this._click){
- dhtmlx.event(this._obj,"click",this._onClick,this);
- dhtmlx.event(this._obj,"contextmenu",this._onContext,this);
- }
- if (this._dblclick)
- dhtmlx.event(this._obj,"dblclick",this._onDblClick,this);
- if (this._mouse_move){
- dhtmlx.event(this._obj,"mousemove",this._onMouse,this);
- dhtmlx.event(this._obj,(dhtmlx._isIE?"mouseleave":"mouseout"),this._onMouse,this);
- }
-
- },
- //inner onclick object handler
- _onClick: function(e) {
- return this._mouseEvent(e,this._click,"ItemClick");
- },
- //inner ondblclick object handler
- _onDblClick: function(e) {
- return this._mouseEvent(e,this._dblclick,"ItemDblClick");
- },
- //process oncontextmenu events
- _onContext: function(e) {
- var id = dhtmlx.html.locate(e, this._id);
- if (id && !this.callEvent("onBeforeContextMenu", [id,e]))
- return dhtmlx.html.preventEvent(e);
- },
- /*
- event throttler - ignore events which occurs too fast
- during mouse moving there are a lot of event firing - we need no so much
- also, mouseout can fire when moving inside the same html container - we need to ignore such fake calls
- */
- _onMouse:function(e){
- if (dhtmlx._isIE) //make a copy of event, will be used in timed call
- e = document.createEventObject(event);
-
- if (this._mouse_move_timer) //clear old event timer
- window.clearTimeout(this._mouse_move_timer);
-
- //this event just inform about moving operation, we don't care about details
- this.callEvent("onMouseMoving",[e]);
- //set new event timer
- this._mouse_move_timer = window.setTimeout(dhtmlx.bind(function(){
- //called only when we have at least 100ms after previous event
- if (e.type == "mousemove")
- this._onMouseMove(e);
- else
- this._onMouseOut(e);
- },this),500);
- },
- //inner mousemove object handler
- _onMouseMove: function(e) {
- if (!this._mouseEvent(e,this._mouse_move,"MouseMove"))
- this.callEvent("onMouseOut",[e||event]);
- },
- //inner mouseout object handler
- _onMouseOut: function(e) {
- this.callEvent("onMouseOut",[e||event]);
- },
- //common logic for click and dbl-click processing
- _mouseEvent:function(e,hash,name){
- e=e||event;
- var trg=e.target||e.srcElement;
- var css = "";
- var id = null;
- var found = false;
- //loop through all parents
- while (trg && trg.parentNode){
- if (!found){ //if element with ID mark is not detected yet
- id = trg.getAttribute(this._id); //check id of current one
- if (id){
- if (!this.callEvent("on"+name,[id,e,trg])) return; //it will be triggered only for first detected ID, in case of nested elements
- found = true; //set found flag
- }
- }
- css=trg.className;
- if (css){ //check if pre-defined reaction for element's css name exists
- css = css.split(" ");
- css = css[0]||css[1]; //FIXME:bad solution, workaround css classes which are starting from whitespace
- if (hash[css])
- return hash[css].call(this,e,id,trg);
- }
- trg=trg.parentNode;
- }
- return found; //returns true if item was located and event was triggered
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'config.js'*/
-
-
-/*
- Behavior:Settings
-
- @export
- customize
- config
-*/
-
-/*DHX:Depend template.js*/
-/*DHX:Depend dhtmlx.js*/
-
-dhtmlx.Settings={
- _init:function(){
- /*
- property can be accessed as this.config.some
- in same time for inner call it have sense to use _settings
- because it will be minified in final version
- */
- this._settings = this.config=[];
- this._settings.sort = null; //mask default method
- },
- define:function(property, value){
- if (typeof property == "object")
- return this._parseSeetingColl(property);
- return this._define(property, value);
- },
- _define:function(property,value){
- dhtmlx.assert_settings.call(this,property,value);
-
- //method with name {prop}_setter will be used as property setter
- //setter is optional
- var setter = this[property+"_setter"];
- return this._settings[property]=setter?setter.call(this,property,value):value;
- },
- //process configuration object
- _parseSeetingColl:function(coll){
- if (coll){
- for (var a in coll) //for each setting
- this._define(a,coll[a]); //set value through config
- }
- },
- //helper for object initialization
- _parseSettings:function(obj,initial){
- //initial - set of default values
- var settings = dhtmlx.extend({},initial);
- //code below will copy all properties over default one
- if (typeof obj == "object" && !obj.tagName)
- dhtmlx.extend(settings,obj);
- //call config for each setting
- this._parseSeetingColl(settings);
- },
- _mergeSettings:function(config, defaults){
- for (var key in defaults)
- switch(typeof config[key]){
- case "object":
- config[key] = this._mergeSettings((config[key]||{}), defaults[key]);
- break;
- case "undefined":
- config[key] = defaults[key];
- break;
- default: //do nothing
- break;
- }
- return config;
- },
- //helper for html container init
- _parseContainer:function(obj,name,fallback){
- /*
- parameter can be a config object, in such case real container will be obj.container
- or it can be html object or ID of html object
- */
- if (typeof obj == "object" && !obj.tagName)
- obj=obj.container;
- this._obj = dhtmlx.toNode(obj);
- if (!this._obj && fallback)
- this._obj = fallback(obj);
-
- dhtmlx.assert(this._obj, "Incorrect html container");
-
- this._obj.className+=" "+name;
- this._obj.onselectstart=function(){return false;}; //block selection by default
- this._dataobj = this._obj;//separate reference for rendering modules
- },
- //apply template-type
- _set_type:function(name){
- //parameter can be a hash of settings
- if (typeof name == "object")
- return this.type_setter("type",name);
-
- dhtmlx.assert(this.types, "RenderStack :: Types are not defined");
- dhtmlx.assert(this.types[name],"RenderStack :: Inccorect type name",name);
- //or parameter can be a name of existing template-type
- this.type=dhtmlx.extend({},this.types[name]);
- this.customize(); //init configs
- },
- customize:function(obj){
- //apply new properties
- if (obj) dhtmlx.extend(this.type,obj);
-
- //init tempaltes for item start and item end
- this.type._item_start = dhtmlx.Template.fromHTML(this.template_item_start(this.type));
- this.type._item_end = this.template_item_end(this.type);
-
- //repaint self
- this.render();
- },
- //config.type - creates new template-type, based on configuration object
- type_setter:function(mode,value){
- this._set_type(typeof value == "object"?dhtmlx.Type.add(this,value):value);
- return value;
- },
- //config.template - creates new template-type with defined template string
- template_setter:function(mode,value){
- return this.type_setter("type",{template:value});
- },
- //config.css - css name for top level container
- css_setter:function(mode,value){
- this._obj.className += " "+value;
- return value;
- }
-};
-
-
-/* DHX DEPEND FROM FILE 'compatibility.js'*/
-
-
-/*
- Collection of compatibility hacks
-*/
-
-/*DHX:Depend dhtmlx.js*/
-
-dhtmlx.compat=function(name, obj){
- //check if name hach present, and applies it when necessary
- if (dhtmlx.compat[name])
- dhtmlx.compat[name](obj);
-};
-
-
-(function(){
- if (!window.dhtmlxError){
- //dhtmlxcommon is not included
-
- //create fake error tracker for connectors
- var dummy = function(){};
- window.dhtmlxError={ catchError:dummy, throwError:dummy };
- //helpers instead of ones from dhtmlxcommon
- window.convertStringToBoolean=function(value){
- return !!value;
- };
- window.dhtmlxEventable = function(node){
- dhtmlx.extend(node,dhtmlx.EventSystem);
- };
- //imitate ajax layer of dhtmlxcommon
- var loader = {
- getXMLTopNode:function(name){
-
- },
- doXPath:function(path){
- return dhtmlx.DataDriver.xml.xpath(this.xml,path);
- },
- xmlDoc:{
- responseXML:true
- }
- };
- //wrap ajax methods of dataprocessor
- dhtmlx.compat.dataProcessor=function(obj){
- //FIXME
- //this is pretty ugly solution - we replace whole method , so changes in dataprocessor need to be reflected here
-
- var sendData = "_sendData";
- var in_progress = "_in_progress";
- var tMode = "_tMode";
- var waitMode = "_waitMode";
-
- obj[sendData]=function(a1,rowId){
- if (!a1) return; //nothing to send
- if (rowId)
- this[in_progress][rowId]=(new Date()).valueOf();
-
- if (!this.callEvent("onBeforeDataSending",rowId?[rowId,this.getState(rowId)]:[])) return false;
-
- var a2 = this;
- var a3=this.serverProcessor;
- if (this[tMode]!="POST")
- //use dhtmlx.ajax instead of old ajax layer
- dhtmlx.ajax().get(a3+((a3.indexOf("?")!=-1)?"&":"?")+a1,"",function(t,x,xml){
- loader.xml = dhtmlx.DataDriver.xml.checkResponse(t,x);
- a2.afterUpdate(a2, null, null, null, loader);
- });
- else
- dhtmlx.ajax().post(a3,a1,function(t,x,xml){
- loader.xml = dhtmlx.DataDriver.xml.checkResponse(t,x);
- a2.afterUpdate(a2, null, null, null, loader);
- });
-
- this[waitMode]++;
- };
- };
- }
-
-})();
-
-
-/* DHX DEPEND FROM FILE 'compatibility_layout.js'*/
-
-
-/*DHX:Depend dhtmlx.js*/
-/*DHX:Depend compatibility.js*/
-
-if (!dhtmlx.attaches)
- dhtmlx.attaches = {};
-
-dhtmlx.attaches.attachAbstract=function(name, conf){
- var obj = document.createElement("DIV");
- obj.id = "CustomObject_"+dhtmlx.uid();
- obj.style.width = "100%";
- obj.style.height = "100%";
- obj.cmp = "grid";
- document.body.appendChild(obj);
- this.attachObject(obj.id);
-
- conf.container = obj.id;
- this.grid = new window[name](conf);
-
- this.gridId = obj.id;
- this.gridObj = obj;
- return this.grid;
-};
-dhtmlx.attaches.attachDataView = function(conf){
- return this.attachAbstract("dhtmlXDataView",conf);
-};
-dhtmlx.attaches.attachChart = function(conf){
- return this.attachAbstract("dhtmlXChart",conf);
-};
-
-dhtmlx.compat.layout = function(){
-if (window.dhtmlXChart)
- dhtmlXChart.prototype.setSizes = function(){
- this.resize();
- };
-if (window.dhtmlXDataView)
- dhtmlXDataView.prototype.setSizes = function(){
- this.render();
- };
-};
-
-
-/* DHX DEPEND FROM FILE 'load.js'*/
-
-
-/*
- ajax operations
-
- can be used for direct loading as
- dhtmlx.ajax(ulr, callback)
- or
- dhtmlx.ajax().get(url)
- dhtmlx.ajax().post(url)
-
-*/
-
-/*DHX:Depend datastore.js*/
-/*DHX:Depend dhtmlx.js*/
-
-dhtmlx.ajax = function(url,call,master){
- //if parameters was provided - made fast call
- if (arguments.length!==0){
- var http_request = new dhtmlx.ajax();
- if (master) http_request.master=master;
- http_request.get(url,null,call);
- }
- if (!this.getXHR) return new dhtmlx.ajax(); //allow to create new instance without direct new declaration
-
- return this;
-};
-dhtmlx.ajax.prototype={
- //creates xmlHTTP object
- getXHR:function(){
- if (dhtmlx._isIE)
- return new ActiveXObject("Microsoft.xmlHTTP");
- else
- return new XMLHttpRequest();
- },
- /*
- send data to the server
- params - hash of properties which will be added to the url
- call - callback, can be an array of functions
- */
- send:function(url,params,call){
- var x=this.getXHR();
- if (typeof call == "function")
- call = [call];
- //add extra params to the url
- if (typeof params == "object"){
- var t=[];
- for (var a in params)
- t.push(a+"="+encodeURIComponent(params[a]));// utf-8 escaping
- params=t.join("&");
- }
- if (params && !this.post){
- url=url+(url.indexOf("?")!=-1 ? "&" : "?")+params;
- params=null;
- }
-
- x.open(this.post?"POST":"GET",url,!this._sync);
- if (this.post)
- x.setRequestHeader('Content-type','application/x-www-form-urlencoded');
-
- //async mode, define loading callback
- if (!this._sync){
- var self=this;
- x.onreadystatechange= function(){
- if (!x.readyState || x.readyState == 4){
- dhtmlx.log_full_time("data_loading"); //log rendering time
- if (call && self)
- for (var i=0; i < call.length; i++) //there can be multiple callbacks
- if (call[i])
- call[i].call((self.master||self),x.responseText,x.responseXML,x);
- self.master=null;
- call=x=self=null; //anti-leak
- }
- };
- }
-
- x.send(params||null);
- return x; //return XHR, which can be used in case of sync. mode
- },
- //GET request
- get:function(url,params,call){
- this.post=false;
- return this.send(url,params,call);
- },
- //POST request
- post:function(url,params,call){
- this.post=true;
- return this.send(url,params,call);
- },
- sync:function(){
- this._sync = true;
- return this;
- }
-};
-
-
-/*
- Behavior:DataLoader - load data in the component
-
- @export
- load
- parse
-*/
-dhtmlx.DataLoader={
- _init:function(){
- //prepare data store
- this.data = new dhtmlx.DataStore();
- },
- //loads data from external URL
- load:function(url,call){
- this.callEvent("onXLS",[]);
- if (typeof call == "string"){ //second parameter can be a loading type or callback
- this.data.setDriver(call);
- call = arguments[2];
- }
- //prepare data feed for dyn. loading
- if (!this.data.feed)
- this.data.feed = function(from,count){
- //allow only single request at same time
- if (this._load_count)
- return this._load_count=[from,count]; //save last ignored request
- else
- this._load_count=true;
-
- this.load(url+((url.indexOf("?")==-1)?"?":"&")+"posStart="+from+"&count="+count,function(){
- //after loading check if we have some ignored requests
- var temp = this._load_count;
- this._load_count = false;
- if (typeof temp =="object")
- this.data.feed.apply(this, temp); //load last ignored request
- });
- };
- //load data by async ajax call
- dhtmlx.ajax(url,[this._onLoad,call],this);
- },
- //loads data from object
- parse:function(data,type){
- this.callEvent("onXLS",[]);
- if (type)
- this.data.setDriver(type);
- this._onLoad(data,null);
- },
- //default after loading callback
- _onLoad:function(text,xml,loader){
- this.data._parse(this.data.driver.toObject(text,xml));
- this.callEvent("onXLE",[]);
- }
-};
-
-/*
- Abstraction layer for different data types
-*/
-
-dhtmlx.DataDriver={};
-dhtmlx.DataDriver.json={
- //convert json string to json object if necessary
- toObject:function(data){
- if (typeof data == "string"){
- eval ("dhtmlx.temp="+data);
- return dhtmlx.temp;
- }
- return data;
- },
- //get array of records
- getRecords:function(data){
- if (data && !(data instanceof Array))
- return [data];
- return data;
- },
- //get hash of properties for single record
- getDetails:function(data){
- return data;
- },
- //get count of data and position at which new data need to be inserted
- getInfo:function(data){
- return {
- _size:(data.total_count||0),
- _from:(data.pos||0)
- };
- }
-};
-
-dhtmlx.DataDriver.html={
- /*
- incoming data can be
- - collection of nodes
- - ID of parent container
- - HTML text
- */
- toObject:function(data){
- if (typeof data == "string"){
- var t=null;
- if (data.indexOf("<")==-1) //if no tags inside - probably its an ID
- t = dhtmlx.toNode(data);
- if (!t){
- t=document.createElement("DIV");
- t.innerHTML = data;
- }
-
- return t.getElementsByTagName(this.tag);
- }
- return data;
- },
- //get array of records
- getRecords:function(data){
- if (data.tagName)
- return data.childNodes;
- return data;
- },
- //get hash of properties for single record
- getDetails:function(data){
- return dhtmlx.DataDriver.xml.tagToObject(data);
- },
- //dyn loading is not supported by HTML data source
- getInfo:function(data){
- return {
- _size:0,
- _from:0
- };
- },
- tag: "LI"
-};
-
-dhtmlx.DataDriver.jsarray={
- //eval jsarray string to jsarray object if necessary
- toObject:function(data){
- if (typeof data == "string"){
- eval ("dhtmlx.temp="+data);
- return dhtmlx.temp;
- }
- return data;
- },
- //get array of records
- getRecords:function(data){
- return data;
- },
- //get hash of properties for single record, in case of array they will have names as "data{index}"
- getDetails:function(data){
- var result = {};
- for (var i=0; i < data.length; i++)
- result["data"+i]=data[i];
-
- return result;
- },
- //dyn loading is not supported by js-array data source
- getInfo:function(data){
- return {
- _size:0,
- _from:0
- };
- }
-};
-
-dhtmlx.DataDriver.csv={
- //incoming data always a string
- toObject:function(data){
- return data;
- },
- //get array of records
- getRecords:function(data){
- return data.split(this.row);
- },
- //get hash of properties for single record, data named as "data{index}"
- getDetails:function(data){
- data = this.stringToArray(data);
- var result = {};
- for (var i=0; i < data.length; i++)
- result["data"+i]=data[i];
-
- return result;
- },
- //dyn loading is not supported by csv data source
- getInfo:function(data){
- return {
- _size:0,
- _from:0
- };
- },
- //split string in array, takes string surrounding quotes in account
- stringToArray:function(data){
- data = data.split(this.cell);
- for (var i=0; i < data.length; i++)
- data[i] = data[i].replace(/^[ \t\n\r]*(\"|)/g,"").replace(/(\"|)[ \t\n\r]*$/g,"");
- return data;
- },
- row:"\n", //default row separator
- cell:"," //default cell separator
-};
-
-dhtmlx.DataDriver.xml={
- //convert xml string to xml object if necessary
- toObject:function(text,xml){
- if (xml && (xml=this.checkResponse(text,xml))) //checkResponse - fix incorrect content type and extra whitespaces errors
- return xml;
- if (typeof text == "string"){
- return this.fromString(text);
- }
- return text;
- },
- //get array of records
- getRecords:function(data){
- return this.xpath(data,this.records);
- },
- records:"/*/item",
- userdata:"/*/userdata",
- //get hash of properties for single record
- getDetails:function(data){
- return this.tagToObject(data,{});
- },
- getUserData:function(data,col){
- col = col || {};
- var ud = this.xpath(data,this.userdata);
- for (var i=0; i < ud.length; i++) {
- var udx = this.tagToObject(ud[i]);
- col[udx.name] = udx.value;
- }
- return col;
- },
- //get count of data and position at which new data_loading need to be inserted
- getInfo:function(data){
- return {
- _size:(data.documentElement.getAttribute("total_count")||0),
- _from:(data.documentElement.getAttribute("pos")||0)
- };
- },
- //xpath helper
- xpath:function(xml,path){
- if (window.XPathResult){ //FF, KHTML, Opera
- var node=xml;
- if(xml.nodeName.indexOf("document")==-1)
- xml=xml.ownerDocument;
- var res = [];
- var col = xml.evaluate(path, node, null, XPathResult.ANY_TYPE, null);
- var temp = col.iterateNext();
- while (temp){
- res.push(temp);
- temp = col.iterateNext();
- }
- return res;
- } //IE
- return xml.selectNodes(path);
- },
- //convert xml tag to js object, all subtags and attributes are mapped to the properties of result object
- tagToObject:function(tag,z){
- z=z||{};
- //map attributes
- var a=tag.attributes;
- for (var i=0; i5?10:5);
- step = parseInt(stepVal,10)*calculStep;
-
- if(step>Math.abs(nmin)) start = (nmin<0?-step:0);
- else{
-
- var absNmin = Math.abs(nmin);
- var powerStart = Math.floor(this.log10(absNmin));
- var nminVal = absNmin/Math.pow(10,powerStart);
- start = Math.ceil(nminVal*10)/10*Math.pow(10,powerStart)-step;
- if(nmin<0) start =-start-2*step;
- }
- var end = start;
- while(end<=nmax){
- end += step;
- end = parseFloat((new Number(end)).toFixed(Math.abs(power)));
- }
- return {start:start,end:end,step:step,fixNum:Math.abs(power)};
- },
- log10:function(n){
- return Math.floor((Math.log(n)/Math.LN10));
- },
- _drawXAxisLabel:function(x,obj){
- if (!this._settings.xAxis) return;
- var elem = this.renderTextAt(false, true, x,
- this._obj.offsetHeight-parseInt(this._settings.padding.bottom,10),
- this._settings.xAxis.template(obj));
- },
- _drawXAxisLine:function(ctx,x,y1,y2){
- if (!this._settings.xAxis||!this._settings.xAxis.lines) return;
- this._drawLine(ctx,x,y1,x,y2,this._settings.xAxis.color,0.2);
- },
- _drawLine:function(ctx,x1,y1,x2,y2,color,width){
- ctx.strokeStyle = color;
- ctx.lineWidth = width;
- ctx.beginPath();
- ctx.moveTo(x1,y1);
- ctx.lineTo(x2,y2);
- ctx.stroke();
- },
- _getRelativeValue:function(minValue,maxValue){
- var relValue;
- var valueFactor = 1;
- if(maxValue != minValue){
- relValue = maxValue - minValue;
- if(Math.abs(relValue) < 1){
- while(Math.abs(relValue)<1){
- valueFactor *= 10;
- relValue *= valueFactor;
- }
- }
- }
- else relValue = minValue;
- return [relValue,valueFactor];
- },
- _rainbow : [
- function(pos){ return "#FF"+dhtmlx.math.toHex(pos/2,2)+"00";},
- function(pos){ return "#FF"+dhtmlx.math.toHex(pos/2+128,2)+"00";},
- function(pos){ return "#"+dhtmlx.math.toHex(255-pos,2)+"FF00";},
- function(pos){ return "#00FF"+dhtmlx.math.toHex(pos,2);},
- function(pos){ return "#00"+dhtmlx.math.toHex(255-pos,2)+"FF";},
- function(pos){ return "#"+dhtmlx.math.toHex(pos,2)+"00FF";}
- ]
-};
-
-dhtmlx.compat("layout");
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/flotr2/LICENSE b/addons/web_graph/static/lib/flotr2/LICENSE
new file mode 100644
index 00000000000..eeb8ce52b3b
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Carl Sutherland
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/addons/web_graph/static/lib/flotr2/Makefile b/addons/web_graph/static/lib/flotr2/Makefile
new file mode 100644
index 00000000000..3fd8e8f3a64
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/Makefile
@@ -0,0 +1,35 @@
+all: test flotr2
+
+test:
+ cd spec; jasmine-headless-webkit -j jasmine.yml -c
+
+libraries:
+ smoosh make/lib.json
+ cat ./build/bean.js > build/lib.js
+ cat ./build/underscore.js >> build/lib.js
+ cat ./build/bean.min.js > build/lib.min.js
+ echo ";" >> build/lib.min.js
+ cat ./build/underscore.min.js >> build/lib.min.js
+ echo ";" >> build/lib.min.js
+
+ie:
+ smoosh make/ie.json
+
+flotr2: libraries ie
+ smoosh make/flotr2.json
+ cat build/lib.js build/flotr2.js > flotr2.js
+ cat build/lib.min.js > flotr2.min.js
+ cat build/flotr2.min.js >> flotr2.min.js
+ echo ';' >> flotr2.min.js
+ cp build/ie.min.js flotr2.ie.min.js
+
+flotr2-basic: libraries ie
+ smoosh make/basic.json
+ cat build/lib.min.js > flotr2-basic.min.js
+ cat build/flotr2-basic.min.js >> flotr2-basic.min.js
+
+flotr-examples:
+ smoosh make/examples.json
+ cp build/examples.min.js flotr2.examples.min.js
+ cp build/examples-types.js flotr2.examples.types.js
+
diff --git a/addons/web_graph/static/lib/flotr2/README.md b/addons/web_graph/static/lib/flotr2/README.md
new file mode 100644
index 00000000000..46f180c0fc6
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/README.md
@@ -0,0 +1,89 @@
+Flotr2
+======
+
+The Canvas graphing library.
+
+![Google Groups](http://groups.google.com/intl/en/images/logos/groups_logo_sm.gif)
+
+http://groups.google.com/group/flotr2/
+
+Please fork http://jsfiddle.net/cesutherland/ZFBj5/ with your question or bug reproduction case.
+
+
+API
+---
+
+The API consists of a primary draw method which accepts a configuration object, helper methods, and several microlibs.
+
+### Example
+
+```javascript
+ var
+ // Container div:
+ container = document.getElementById("flotr-example-graph"),
+ // First data series:
+ d1 = [[0, 3], [4, 8], [8, 5], [9, 13]],
+ // Second data series:
+ d2 = [],
+ // A couple flotr configuration options:
+ options = {
+ xaxis: {
+ minorTickFreq: 4
+ },
+ grid: {
+ minorVerticalLines: true
+ }
+ },
+ i, graph;
+
+ // Generated second data set:
+ for (i = 0; i < 14; i += 0.5) {
+ d2.push([i, Math.sin(i)]);
+ }
+
+ // Draw the graph:
+ graph = Flotr.draw(
+ container, // Container element
+ [ d1, d2 ], // Array of data series
+ options // Configuration options
+ );
+```
+
+### Microlibs
+
+* [underscore.js](http://documentcloud.github.com/underscore/)
+* [bean.js](https://github.com/fat/bean)
+
+Extending
+---------
+
+Flotr may be extended by adding new plugins and graph types.
+
+### Graph Types
+
+Graph types define how a particular chart is rendered. Examples include line, bar, pie.
+
+Existing graph types are found in `js/types/`.
+
+### Plugins
+
+Plugins extend the core of flotr with new functionality. They can add interactions, new decorations, etc. Examples
+include titles, labels and selection.
+
+The plugins included are found in `js/plugins/`.
+
+Development
+-----------
+
+This project uses [smoosh](https://github.com/fat/smoosh) to build and [jasmine](http://pivotal.github.com/jasmine/)
+with [js-imagediff](https://github.com/HumbleSoftware/js-imagediff) to test. Tests may be executed by
+[jasmine-headless-webkit](http://johnbintz.github.com/jasmine-headless-webkit/) with
+`cd spec; jasmine-headless-webkit -j jasmine.yml -c` or by a browser by navigating to
+`flotr2/spec/SpecRunner.html`.
+
+Shoutouts
+---------
+
+Thanks to Bas Wenneker, Fabien Ménager and others for all the work on the original Flotr.
+Thanks to Jochen Berger and Jordan Santell for their contributions to Flotr2.
+
diff --git a/addons/web_graph/static/lib/flotr2/dev/notes.txt b/addons/web_graph/static/lib/flotr2/dev/notes.txt
new file mode 100644
index 00000000000..982474e579f
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/dev/notes.txt
@@ -0,0 +1,86 @@
+Flotr 2 Architecture Notes
+
+
+Global:
+======
+
+Flotr.js -
+ versioning information
+ browser detection
+ extension (plugins, graph types)
+ draw
+ clone / merge
+ tick size
+ tick formatter
+ engineering notation
+ magnitude
+ rad, pixel, floor
+ drawText
+ measureText
+ getBestTextAlign
+ align map
+ compatibility
+
+
+Graph Architecture:
+===================
+
+Axis -
+ all series
+ orientation
+ ticks (major, minor)
+ scale (d2p, p2d, logarithmic)
+ notion of stacks
+
+Series -
+ per 'data'
+ notion of range (x, y, min, max)
+
+Graph -
+ DOM constructon
+ event attachment
+ options initialization
+ data range calculations
+ canvas spacing calculations
+ event normalization
+ draw methods
+ DOM cleanup
+ event cleanup
+
+
+Utilities:
+==========
+
+Color
+ build colors
+ parse textual color data
+ convert colors
+ clone colors
+
+Text
+ calculate text size
+ canvas size
+ html size
+
+Date
+ formatting
+ constants
+
+
+Spacing Calculation
+===================
+
+Flotr
+ calculate data
+ calculate margins
+
+Chart
+ calculate Data Ranges - Explicit or auto data minimum, maximums
+ calculate Data Range Extensions - By chart type, extend data range with needs of chart type (ie. stacked bars, stacked lines)
+ add Chart Padding - By chart type
+
+Text
+ use explicit margins
+ calculate label margins
+ calculate title margins
+
diff --git a/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js b/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js
new file mode 100644
index 00000000000..7bbf25592a3
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js
@@ -0,0 +1,2 @@
+
+(function(){var a=Flotr.EventAdapter,b=Flotr._,c="click",d="example",e="mouseenter",f="mouseleave",g=".",h="flotr-examples",i="flotr-examples-container",j="flotr-examples-reset",k="flotr-examples-thumbs",l="flotr-examples-thumb",m="flotr-examples-collapsed",n="flotr-examples-highlight",o="flotr-examples-large",p="flotr-examples-medium",q="flotr-examples-small",r="flotr-examples-mobile",s='
',t='";Examples=function(a){if(b.isUndefined(Flotr.ExampleList))throw"Flotr.ExampleList not defined.";this.options=a,this.list=Flotr.ExampleList,this.current=null,this.single=!1,this._initNodes(),this._example=new Flotr.Examples.Example({node:this._exampleNode}),this._initExamples()},Examples.prototype={examples:function(){function f(b){var c=$(b.currentTarget),e=c.data("example"),f=b.data.orientation;f^c.hasClass(n)&&(c.toggleClass(n).css(a),d._example.executeCallback(e,c))}var a={cursor:"pointer"},b=this._thumbsNode,c=this.list.get(),d=this,e=["basic","basic-axis","basic-bars","basic-bars-horizontal","basic-bar-stacked","basic-stacked-horizontal","basic-pie","basic-radar","basic-bubble","basic-candle","basic-legend","mouse-tracking","mouse-zoom","mouse-drag","basic-time","negative-values","click-example","download-image","download-data","advanced-titles","color-gradients","basic-timeline","advanced-markers"];(function h(){var a=e.shift(),f=c[a];if(f.type==="profile"||f.type==="test")return;var g=$(s);g.data("example",f),b.append(g),d._example.executeCallback(f,g),g.click(function(){d._loadExample(f)}),e.length&&setTimeout(h,20)})(),b.delegate(g+l,"mouseenter",{orientation:!0},f),b.delegate(g+l,"mouseleave",{orientation:!1},f)},_loadExample:function(a){a&&(window.location.hash="!"+(this.single?"single/":"")+a.key,u||(this._thumbsNode.css({position:"absolute",height:"0px",overflow:"hidden",width:"0px"}),this._resetNode.css({top:"16px"})),this._examplesNode.addClass(m),this._exampleNode.show(),this._example.setExample(a),this._resize(),$(document).scrollTop(0))},_reset:function(){window.location.hash="",u||this._thumbsNode.css({position:"",height:"",overflow:"",width:""}),this._examplesNode.removeClass(m),this._thumbsNode.height(""),this._exampleNode.hide()},_initNodes:function(){var a=$(this.options.node),b=this,c=$(t);b._resetNode=c.find(g+j),b._exampleNode=c.find(g+i),b._thumbsNode=c.find(g+k),b._examplesNode=c,b._resetNode.click(function(){b._reset()}),a.append(c),this._initResizer()},_initResizer:function(){function e(){var b=c.height()-(a.options.thumbPadding||0),e=c.width(),f;e>1760?(f=o,a._thumbsNode.height(b)):e>1140?(f=p,a._thumbsNode.height(b)):(f=q,a._thumbsNode.height("")),d!==f&&(d&&a._examplesNode.removeClass(d),a._examplesNode.addClass(f),d=f)}var a=this,b=a._examplesNode,c=$(window),d;$(window).resize(e),e(),this._resize=e},_initExamples:function(){var a=window.location.hash,b,c;a?(a=a.substring(2),c=a.split("/"),c.length==1?(b=this.list.get(a),this.examples()):c[0]=="single"&&(this.single=!0,b=this.list.get(c[1])),this._loadExample(b)):this.examples()}};var u=function(){var a=!!(navigator.userAgent.match(/Android/i)||navigator.userAgent.match(/webOS/i)||navigator.userAgent.match(/iPhone/i)||navigator.userAgent.match(/iPod/i)),b=!!$.browser.mozilla;return!a||b}();Flotr.Examples=Examples})(),function(){var a=Flotr._,b=".",c="flotr-example",d="flotr-example-label",e="flotr-example-title",f="flotr-example-description",g="flotr-example-editor",h="flotr-example-graph",i='",j=function(a){this.options=a,this.example=null,this._initNodes()};j.prototype={setExample:function(a){var b=this.getSource(a),c=this._editorNode;this.example=a,Math.seedrandom(a.key),this._exampleNode.css({display:"block"}),this._titleNode.html(a.name||""),this._markupNode.html(a.description||""),this._editor?this._editor.setExample(b):this._editor=new Flotr.Examples.Editor(c,{example:b,teardown:function(){Flotr.EventAdapter.stopObserving($(c).find(".render")[0]),$(c).find("canvas").each(function(a,b){Flotr.EventAdapter.stopObserving(b)})}})},getSource:function(a){var b=a.callback.toString();return navigator.userAgent.search(/firefox/i)!==-1&&(b=js_beautify(b)),b},executeCallback:function(b,c){a.isElement(c)||(c=c[0]);var d=b.args?[c].concat(b.args):[c];return Math.seedrandom(b.key),b.callback.apply(this,d)},_initNodes:function(){var a=this.options.node,c=$(i);this._titleNode=c.find(b+e),this._markupNode=c.find(b+f),this._editorNode=c.find(b+g),this._exampleNode=c,a.append(c)}},Flotr.Examples.Example=j}(),function(){function Editor(a,b){function o(){i.hide(),f&&f.call(),m.render({example:d,render:h})}function p(a,b,c){var d=!1,e='Error: ',f,g;e+=''+a+" ",typeof c!="undefined"&&(e+='',e+='Line '+c+" ",console.log(b),b&&(e+=" of ",b==window.location?(e+='script ',!d):e+=''+b+" "),e+=". "),i.show(),i.html(e)}var c=b.type||"javascript",d=b.example||"",e=b.noRun||!1,f=b.teardown||!1,g=$(T_CONTROLS),h=$(T_RENDER),i=$(T_ERRORS),j=$(T_SOURCE),k=$(T_EDITOR),l="editor-render-"+COUNT,m,h,n;m=new TYPES[c]({onerror:p});if(!m)throw"Invalid type: API not found for type `"+c+"`.";h.attr("id",l),i.hide(),k.append(h).append(g).append(j).addClass(c).addClass(e?"no-run":""),a=$(a),a.append(k),j.append(i),d=m.example({example:d,render:h}),n=CodeMirror(j[0],{value:d,readOnly:e,lineNumbers:!0,mode:m.codeMirrorType}),e||(g.delegate(".run","click",function(){d=n.getValue(),o()}),o()),window.onerror=function(a,b,c){return p(a,b,c),console.log(a),ONERROR&&$.isFunction(ONERROR)?ONERROR(a,b,c):!1},COUNT++,this.setExample=function(a){d=m.example({example:a,render:h}),n.setValue(d),n.refresh(),o()}}var ONERROR=window.onerror,COUNT=0,TYPES={},T_CONTROLS='Run
',T_EDITOR='
',T_SOURCE='
',T_ERRORS='
',T_RENDER='
',T_IFRAME="";TYPES.javascript=function(b){this.onerror=b.onerror},TYPES.javascript.prototype={codeMirrorType:"javascript",example:function(a){var b=a.example,c=a.render,d=$(c).attr("id");return"("+b+')(document.getElementById("'+d+'"));'},render:function(o){eval(o.example)}},TYPES.html=function(b){this.onerror=b.onerror},TYPES.html.prototype={codeMirrorType:"htmlmixed",example:function(a){return $.trim(a.example)},render:function(a){var b=a.example,c=a.render,d=$(T_IFRAME),e=this,f,g;c.html(d),f=d[0].contentWindow,g=f.document,g.open(),f.onerror=d.onerror=function(){e.onerror.apply(null,arguments)},g.write(b),g.close()}},typeof Flotr.Examples=="undefined"&&(Flotr.Examples={}),Flotr.Examples.Editor=Editor}(),function(){var a=Flotr.DOM,b=Flotr.EventAdapter,c=Flotr._,d="click",e="example-profile",f="examples",g=function(a){if(c.isUndefined(Flotr.ExampleList))throw"Flotr.ExampleList not defined.";this.editMode="off",this.list=Flotr.ExampleList,this.current=null,this.single=!1,this.init()};g.prototype=c.extend({},Flotr.Examples.prototype,{examples:function(){var e=document.getElementById(f),g=a.node(""),h;c.each(this.list.getType("profile"),function(e){h=a.node(""+e.name+" "),a.insert(g,h),b.observe(h,d,c.bind(function(){this.example(e)},this))},this),a.insert(e,g)},example:function(a){this._renderSource(a),this.profileStart(a),setTimeout(c.bind(function(){this._renderGraph(a),this.profileEnd()},this),50)},profileStart:function(a){var b=document.getElementById(e);this._startTime=new Date,b.innerHTML='Profile started for "'+a.name+'"...
'},profileEnd:function(a){var b=document.getElementById(e);profileTime=new Date-this._startTime,this._startTime=null,b.innerHTML+="Profile complete: "+profileTime+"ms
"}}),Flotr.Profile=g}()
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js b/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js
new file mode 100644
index 00000000000..f22bc9596f9
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js
@@ -0,0 +1,1425 @@
+(function () {
+
+var ExampleList = function () {
+
+ // Map of examples.
+ this.examples = {};
+
+};
+
+ExampleList.prototype = {
+
+ add : function (example) {
+ this.examples[example.key] = example;
+ },
+
+ get : function (key) {
+ return key ? (this.examples[key] || null) : this.examples;
+ },
+
+ getType : function (type) {
+ return Flotr._.select(this.examples, function (example) {
+ return (example.type === type);
+ });
+ }
+}
+
+Flotr.ExampleList = new ExampleList();
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic',
+ name : 'Basic',
+ callback : basic
+});
+
+function basic (container) {
+
+ var
+ d1 = [[0, 3], [4, 8], [8, 5], [9, 13]], // First data series
+ d2 = [], // Second data series
+ i, graph;
+
+ // Generate first data set
+ for (i = 0; i < 14; i += 0.5) {
+ d2.push([i, Math.sin(i)]);
+ }
+
+ // Draw Graph
+ graph = Flotr.draw(container, [ d1, d2 ], {
+ xaxis: {
+ minorTickFreq: 4
+ },
+ grid: {
+ minorVerticalLines: true
+ }
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-stacked',
+ name : 'Basic Stacked',
+ callback : basic_stacked,
+ type : 'test'
+});
+
+function basic_stacked (container) {
+
+ var
+ d1 = [[0, 3], [4, 8], [8, 2], [9, 3]], // First data series
+ d2 = [[0, 2], [4, 3], [8, 8], [9, 4]], // Second data series
+ i, graph;
+
+ // Draw Graph
+ graph = Flotr.draw(container, [ d1, d2 ], {
+ lines: {
+ show : true,
+ stacked: true
+ },
+ xaxis: {
+ minorTickFreq: 4
+ },
+ grid: {
+ minorVerticalLines: true
+ }
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-stepped',
+ name : 'Basic Stepped',
+ callback : basic_stepped,
+ type : 'test'
+});
+
+function basic_stepped (container) {
+
+ var
+ d1 = [[0, 3], [4, 8], [8, 5], [9, 13]], // First data series
+ d2 = [], // Second data series
+ i, graph;
+
+ // Generate first data set
+ for (i = 0; i < 14; i += 0.5) {
+ d2.push([i, Math.sin(i)]);
+ }
+
+ // Draw Graph
+ graph = Flotr.draw(container, [ d1, d2 ], {
+ lines: {
+ steps : true,
+ show : true
+ },
+ xaxis: {
+ minorTickFreq: 4
+ },
+ yaxis: {
+ autoscale: true
+ },
+ grid: {
+ minorVerticalLines: true
+ },
+ mouse : {
+ track : true,
+ relative : true
+ }
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-axis',
+ name : 'Basic Axis',
+ callback : basic_axis
+});
+
+function basic_axis (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ d4 = [],
+ d5 = [], // Data
+ ticks = [[ 0, "Lower"], 10, 20, 30, [40, "Upper"]], // Ticks for the Y-Axis
+ graph;
+
+ for(var i = 0; i <= 10; i += 0.1){
+ d1.push([i, 4 + Math.pow(i,1.5)]);
+ d2.push([i, Math.pow(i,3)]);
+ d3.push([i, i*5+3*Math.sin(i*4)]);
+ d4.push([i, i]);
+ if( i.toFixed(1)%1 == 0 ){
+ d5.push([i, 2*i]);
+ }
+ }
+
+ d3[30][1] = null;
+ d3[31][1] = null;
+
+ function ticksFn (n) { return '('+n+')'; }
+
+ graph = Flotr.draw(container, [
+ { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } },
+ { data : d2, label : 'y = x^3'},
+ { data : d3, label : 'y = 5x + 3sin(4x)'},
+ { data : d4, label : 'y = x'},
+ { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } }
+ ], {
+ xaxis : {
+ noTicks : 7, // Display 7 ticks.
+ tickFormatter : ticksFn, // Displays tick values between brackets.
+ min : 1, // Part of the series is not displayed.
+ max : 7.5 // Part of the series is not displayed.
+ },
+ yaxis : {
+ ticks : ticks, // Set Y-Axis ticks
+ max : 40 // Maximum value along Y-Axis
+ },
+ grid : {
+ verticalLines : false,
+ backgroundColor : {
+ colors : [[0,'#fff'], [1,'#ccc']],
+ start : 'top',
+ end : 'bottom'
+ }
+ },
+ legend : {
+ position : 'nw'
+ },
+ title : 'Basic Axis example',
+ subtitle : 'This is a subtitle'
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-bars',
+ name : 'Basic Bars',
+ callback : basic_bars
+});
+
+Flotr.ExampleList.add({
+ key : 'basic-bars-horizontal',
+ name : 'Horizontal Bars',
+ args : [true],
+ callback : basic_bars,
+ tolerance : 5
+});
+
+function basic_bars (container, horizontal) {
+
+ var
+ horizontal = (horizontal ? true : false), // Show horizontal bars
+ d1 = [], // First data series
+ d2 = [], // Second data series
+ point, // Data point variable declaration
+ i;
+
+ for (i = 0; i < 4; i++) {
+
+ if (horizontal) {
+ point = [Math.ceil(Math.random()*10), i];
+ } else {
+ point = [i, Math.ceil(Math.random()*10)];
+ }
+
+ d1.push(point);
+
+ if (horizontal) {
+ point = [Math.ceil(Math.random()*10), i+0.5];
+ } else {
+ point = [i+0.5, Math.ceil(Math.random()*10)];
+ }
+
+ d2.push(point);
+ };
+
+ // Draw the graph
+ Flotr.draw(
+ container,
+ [d1, d2],
+ {
+ bars : {
+ show : true,
+ horizontal : horizontal,
+ shadowSize : 0,
+ barWidth : 0.5
+ },
+ mouse : {
+ track : true,
+ relative : true
+ },
+ yaxis : {
+ min : 0,
+ autoscaleMargin : 1
+ }
+ }
+ );
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-bar-stacked',
+ name : 'Stacked Bars',
+ callback : bars_stacked
+});
+
+Flotr.ExampleList.add({
+ key : 'basic-stacked-horizontal',
+ name : 'Stacked Horizontal Bars',
+ args : [true],
+ callback : bars_stacked,
+ tolerance : 5
+});
+
+function bars_stacked (container, horizontal) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ graph, i;
+
+ for (i = -10; i < 10; i++) {
+ if (horizontal) {
+ d1.push([Math.random(), i]);
+ d2.push([Math.random(), i]);
+ d3.push([Math.random(), i]);
+ } else {
+ d1.push([i, Math.random()]);
+ d2.push([i, Math.random()]);
+ d3.push([i, Math.random()]);
+ }
+ }
+
+ graph = Flotr.draw(container,[
+ { data : d1, label : 'Serie 1' },
+ { data : d2, label : 'Serie 2' },
+ { data : d3, label : 'Serie 3' }
+ ], {
+ legend : {
+ backgroundColor : '#D2E8FF' // Light blue
+ },
+ bars : {
+ show : true,
+ stacked : true,
+ horizontal : horizontal,
+ barWidth : 0.6,
+ lineWidth : 1,
+ shadowSize : 0
+ },
+ grid : {
+ verticalLines : horizontal,
+ horizontalLines : !horizontal
+ }
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-pie',
+ name : 'Basic Pie',
+ callback : basic_pie
+});
+
+function basic_pie (container) {
+
+ var
+ d1 = [[0, 4]],
+ d2 = [[0, 3]],
+ d3 = [[0, 1.03]],
+ d4 = [[0, 3.5]],
+ graph;
+
+ graph = Flotr.draw(container, [
+ { data : d1, label : 'Comedy' },
+ { data : d2, label : 'Action' },
+ { data : d3, label : 'Romance',
+ pie : {
+ explode : 50
+ }
+ },
+ { data : d4, label : 'Drama' }
+ ], {
+ HtmlText : false,
+ grid : {
+ verticalLines : false,
+ horizontalLines : false
+ },
+ xaxis : { showLabels : false },
+ yaxis : { showLabels : false },
+ pie : {
+ show : true,
+ explode : 6
+ },
+ mouse : { track : true },
+ legend : {
+ position : 'se',
+ backgroundColor : '#D2E8FF'
+ }
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-radar',
+ name : 'Basic Radar',
+ callback : basic_radar
+});
+
+function basic_radar (container) {
+
+ // Fill series s1 and s2.
+ var
+ s1 = { label : 'Actual', data : [[0, 3], [1, 8], [2, 5], [3, 5], [4, 3], [5, 9]] },
+ s2 = { label : 'Target', data : [[0, 8], [1, 7], [2, 8], [3, 2], [4, 4], [5, 7]] },
+ graph, ticks;
+
+ // Radar Labels
+ ticks = [
+ [0, "Statutory"],
+ [1, "External"],
+ [2, "Videos"],
+ [3, "Yippy"],
+ [4, "Management"],
+ [5, "oops"]
+ ];
+
+ // Draw the graph.
+ graph = Flotr.draw(container, [ s1, s2 ], {
+ radar : { show : true},
+ grid : { circular : true, minorHorizontalLines : true},
+ yaxis : { min : 0, max : 10, minorTickFreq : 2},
+ xaxis : { ticks : ticks}
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-bubble',
+ name : 'Basic Bubble',
+ callback : basic_bubble
+});
+
+function basic_bubble (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ point, graph, i;
+
+ for (i = 0; i < 10; i++ ){
+ point = [i, Math.ceil(Math.random()*10), Math.ceil(Math.random()*10)];
+ d1.push(point);
+
+ point = [i, Math.ceil(Math.random()*10), Math.ceil(Math.random()*10)];
+ d2.push(point);
+ }
+
+ // Draw the graph
+ graph = Flotr.draw(container, [d1, d2], {
+ bubbles : { show : true, baseRadius : 5 },
+ xaxis : { min : -4, max : 14 },
+ yaxis : { min : -4, max : 14 }
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-candle',
+ name : 'Basic Candle',
+ callback : basic_candle
+});
+
+function basic_candle (container) {
+
+ var
+ d1 = [],
+ price = 3.206,
+ graph,
+ i, a, b, c;
+
+ for (i = 0; i < 50; i++) {
+ a = Math.random();
+ b = Math.random();
+ c = (Math.random() * (a + b)) - b;
+ d1.push([i, price, price + a, price - b, price + c]);
+ price = price + c;
+ }
+
+ // Graph
+ graph = Flotr.draw(container, [ d1 ], {
+ candles : { show : true, candleWidth : 0.6 },
+ xaxis : { noTicks : 10 }
+ });
+}
+
+})();
+
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-legend',
+ name : 'Basic Legend',
+ callback : basic_legend
+});
+
+function basic_legend (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ data,
+ graph, i;
+
+ // Data Generation
+ for (i = 0; i < 15; i += 0.5) {
+ d1.push([i, i + Math.sin(i+Math.PI)]);
+ d2.push([i, i]);
+ d3.push([i, 15-Math.cos(i)]);
+ }
+
+ data = [
+ { data : d1, label :'x + sin(x+π)' },
+ { data : d2, label :'x' },
+ { data : d3, label :'15 - cos(x)' }
+ ];
+
+
+ // This function prepend each label with 'y = '
+ function labelFn (label) {
+ return 'y = ' + label;
+ }
+
+ // Draw graph
+ graph = Flotr.draw(container, data, {
+ legend : {
+ position : 'se', // Position the legend 'south-east'.
+ labelFormatter : labelFn, // Format the labels.
+ backgroundColor : '#D2E8FF' // A light blue background color.
+ },
+ HtmlText : false
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'mouse-tracking',
+ name : 'Mouse Tracking',
+ callback : mouse_tracking
+});
+
+function mouse_tracking (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ graph, i;
+
+ for (i = 0; i < 20; i += 0.5) {
+ d1.push([i, 2*i]);
+ d2.push([i, i*1.5+1.5*Math.sin(i)]);
+ d3.push([i, 3*Math.cos(i)+10]);
+ }
+
+ graph = Flotr.draw(
+ container,
+ [
+ {
+ data : d1,
+ mouse : { track : false } // Disable mouse tracking for d1
+ },
+ d2,
+ d3
+ ],
+ {
+ mouse : {
+ track : true, // Enable mouse tracking
+ lineColor : 'purple',
+ relative : true,
+ position : 'ne',
+ sensibility : 1,
+ trackDecimals : 2,
+ trackFormatter : function (o) { return 'x = ' + o.x +', y = ' + o.y; }
+ },
+ crosshair : {
+ mode : 'xy'
+ }
+ }
+ );
+
+};
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'mouse-zoom',
+ name : 'Mouse Zoom',
+ callback : mouse_zoom,
+ description : "
Select an area of the graph to zoom. Click to reset the chart.
"
+});
+
+function mouse_zoom (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ options,
+ graph,
+ i;
+
+ for (i = 0; i < 40; i += 0.5) {
+ d1.push([i, Math.sin(i)+3*Math.cos(i)]);
+ d2.push([i, Math.pow(1.1, i)]);
+ d3.push([i, 40 - i+Math.random()*10]);
+ }
+
+ options = {
+ selection : { mode : 'x', fps : 30 },
+ title : 'Mouse Zoom'
+ };
+
+ // Draw graph with default options, overwriting with passed options
+ function drawGraph (opts) {
+
+ // Clone the options, so the 'options' variable always keeps intact.
+ var o = Flotr._.extend(Flotr._.clone(options), opts || {});
+
+ // Return a new graph.
+ return Flotr.draw(
+ container,
+ [ d1, d2, d3 ],
+ o
+ );
+ }
+
+ // Actually draw the graph.
+ graph = drawGraph();
+
+ // Hook into the 'flotr:select' event.
+ Flotr.EventAdapter.observe(container, 'flotr:select', function (area) {
+
+ // Draw graph with new area
+ f = drawGraph({
+ xaxis: {min:area.x1, max:area.x2},
+ yaxis: {min:area.y1, max:area.y2}
+ });
+
+ });
+
+ // When graph is clicked, draw the graph with default area.
+ Flotr.EventAdapter.observe(container, 'flotr:click', function () { drawGraph(); });
+};
+
+})();
+
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'mouse-drag',
+ name : 'Mouse Drag',
+ callback : mouse_drag
+});
+
+function mouse_drag (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ options,
+ graph,
+ start,
+ i;
+
+ for (i = -40; i < 40; i += 0.5) {
+ d1.push([i, Math.sin(i)+3*Math.cos(i)]);
+ d2.push([i, Math.pow(1.1, i)]);
+ d3.push([i, 40 - i+Math.random()*10]);
+ }
+
+ options = {
+ xaxis: {min: 0, max: 20},
+ title : 'Mouse Drag'
+ };
+
+ // Draw graph with default options, overwriting with passed options
+ function drawGraph (opts) {
+
+ // Clone the options, so the 'options' variable always keeps intact.
+ var o = Flotr._.extend(Flotr._.clone(options), opts || {});
+
+ // Return a new graph.
+ return Flotr.draw(
+ container,
+ [ d1, d2, d3 ],
+ o
+ );
+ }
+
+ graph = drawGraph();
+
+ function initializeDrag (e) {
+ start = graph.getEventPosition(e);
+ Flotr.EventAdapter.observe(document, 'mousemove', move);
+ Flotr.EventAdapter.observe(document, 'mouseup', stopDrag);
+ }
+
+ function move (e) {
+ var
+ end = graph.getEventPosition(e),
+ xaxis = graph.axes.x,
+ offset = start.x - end.x;
+
+ graph = drawGraph({
+ xaxis : {
+ min : xaxis.min + offset,
+ max : xaxis.max + offset
+ }
+ });
+ // @todo: refector initEvents in order not to remove other observed events
+ Flotr.EventAdapter.observe(graph.overlay, 'mousedown', initializeDrag);
+ }
+
+ function stopDrag () {
+ Flotr.EventAdapter.stopObserving(document, 'mousemove', move);
+ }
+
+ Flotr.EventAdapter.observe(graph.overlay, 'mousedown', initializeDrag);
+
+};
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-time',
+ name : 'Basic Time',
+ callback : basic_time,
+ description : "
Select an area of the graph to zoom. Click to reset the chart.
"
+});
+
+function basic_time (container) {
+
+ var
+ d1 = [],
+ start = new Date("2009/01/01 01:00").getTime(),
+ options,
+ graph,
+ i, x, o;
+
+ for (i = 0; i < 100; i++) {
+ x = start+(i*1000*3600*24*36.5);
+ d1.push([x, i+Math.random()*30+Math.sin(i/20+Math.random()*2)*20+Math.sin(i/10+Math.random())*10]);
+ }
+
+ options = {
+ xaxis : {
+ mode : 'time',
+ labelsAngle : 45
+ },
+ selection : {
+ mode : 'x'
+ },
+ HtmlText : false,
+ title : 'Time'
+ };
+
+ // Draw graph with default options, overwriting with passed options
+ function drawGraph (opts) {
+
+ // Clone the options, so the 'options' variable always keeps intact.
+ o = Flotr._.extend(Flotr._.clone(options), opts || {});
+
+ // Return a new graph.
+ return Flotr.draw(
+ container,
+ [ d1 ],
+ o
+ );
+ }
+
+ graph = drawGraph();
+
+ Flotr.EventAdapter.observe(container, 'flotr:select', function(area){
+ // Draw selected area
+ graph = drawGraph({
+ xaxis : { min : area.x1, max : area.x2, mode : 'time', labelsAngle : 45 },
+ yaxis : { min : area.y1, max : area.y2 }
+ });
+ });
+
+ // When graph is clicked, draw the graph with default area.
+ Flotr.EventAdapter.observe(container, 'flotr:click', function () { graph = drawGraph(); });
+};
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'negative-values',
+ name : 'Negative Values',
+ callback : negative_values
+});
+
+function negative_values (container) {
+
+ var
+ d0 = [], // Line through y = 0
+ d1 = [], // Random data presented as a scatter plot.
+ d2 = [], // A regression line for the scatter.
+ sx = 0,
+ sy = 0,
+ sxy = 0,
+ sxsq = 0,
+ xmean,
+ ymean,
+ alpha,
+ beta,
+ n, x, y;
+
+ for (n = 0; n < 20; n++){
+
+ x = n;
+ y = x + Math.random()*8 - 15;
+
+ d0.push([x, 0]);
+ d1.push([x, y]);
+
+ // Computations used for regression line
+ sx += x;
+ sy += y;
+ sxy += x*y;
+ sxsq += Math.pow(x,2);
+ }
+
+ xmean = sx/n;
+ ymean = sy/n;
+ beta = ((n*sxy) - (sx*sy))/((n*sxsq)-(Math.pow(sx,2)));
+ alpha = ymean - (beta * xmean);
+
+ // Compute the regression line.
+ for (n = 0; n < 20; n++){
+ d2.push([n, alpha + beta*n])
+ }
+
+ // Draw the graph
+ graph = Flotr.draw(
+ container, [
+ { data : d0, shadowSize : 0, color : '#545454' }, // Horizontal
+ { data : d1, label : 'y = x + (Math.random() * 8) - 15', points : { show : true } }, // Scatter
+ { data : d2, label : 'y = ' + alpha.toFixed(2) + ' + ' + beta.toFixed(2) + '*x' } // Regression
+ ],
+ {
+ legend : { position : 'se', backgroundColor : '#D2E8FF' },
+ title : 'Negative Values'
+ }
+ );
+};
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'click-example',
+ name : 'Click Example',
+ callback : click_example
+});
+
+function click_example (container) {
+
+ var
+ d1 = [[0,0]], // Point at origin
+ options,
+ graph;
+
+ options = {
+ xaxis: {min: 0, max: 15},
+ yaxis: {min: 0, max: 15},
+ lines: {show: true},
+ points: {show: true},
+ mouse: {track:true},
+ title: 'Click Example'
+ };
+
+ graph = Flotr.draw(container, [d1], options);
+
+ // Add a point to the series and redraw the graph
+ Flotr.EventAdapter.observe(container, 'flotr:click', function(position){
+
+ // Add a point to the series at the location of the click
+ d1.push([position.x, position.y]);
+
+ // Sort the series.
+ d1 = d1.sort(function (a, b) { return a[0] - b[0]; });
+
+ // Redraw the graph, with the new series.
+ graph = Flotr.draw(container, [d1], options);
+ });
+};
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'download-image',
+ name : 'Download Image',
+ callback : download_image,
+ description : '' +
+ '
'
+});
+
+function download_image (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ d4 = [],
+ d5 = [],
+ graph,
+ i;
+
+ for (i = 0; i <= 10; i += 0.1) {
+ d1.push([i, 4 + Math.pow(i,1.5)]);
+ d2.push([i, Math.pow(i,3)]);
+ d3.push([i, i*5+3*Math.sin(i*4)]);
+ d4.push([i, i]);
+ if( i.toFixed(1)%1 == 0 ){
+ d5.push([i, 2*i]);
+ }
+ }
+
+ // Draw the graph
+ graph = Flotr.draw(
+ container,[
+ {data:d1, label:'y = 4 + x^(1.5)', lines:{fill:true}},
+ {data:d2, label:'y = x^3', yaxis:2},
+ {data:d3, label:'y = 5x + 3sin(4x)'},
+ {data:d4, label:'y = x'},
+ {data:d5, label:'y = 2x', lines: {show: true}, points: {show: true}}
+ ],{
+ title: 'Download Image Example',
+ subtitle: 'You can save me as an image',
+ xaxis:{
+ noTicks: 7, // Display 7 ticks.
+ tickFormatter: function(n){ return '('+n+')'; }, // => displays tick values between brackets.
+ min: 1, // => part of the series is not displayed.
+ max: 7.5, // => part of the series is not displayed.
+ labelsAngle: 45,
+ title: 'x Axis'
+ },
+ yaxis:{
+ ticks: [[0, "Lower"], 10, 20, 30, [40, "Upper"]],
+ max: 40,
+ title: 'y = f(x)'
+ },
+ y2axis:{color:'#FF0000', max: 500, title: 'y = x^3'},
+ grid:{
+ verticalLines: false,
+ backgroundColor: 'white'
+ },
+ HtmlText: false,
+ legend: {
+ position: 'nw'
+ }
+ });
+
+ this.CurrentExample = function (operation) {
+
+ var
+ format = $('#image-download input:radio[name=format]:checked').val();
+ if (Flotr.isIE && Flotr.isIE < 9) {
+ alert(
+ "Your browser doesn't allow you to get a bitmap image from the plot, " +
+ "you can only get a VML image that you can use in Microsoft Office.
"
+ );
+ }
+
+ if (operation == 'to-image') {
+ graph.download.saveImage(format, null, null, true)
+ } else if (operation == 'download') {
+ graph.download.saveImage(format);
+ } else if (operation == 'reset') {
+ graph.download.restoreCanvas();
+ }
+ };
+
+ return graph;
+};
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'download-data',
+ name : 'Download Data',
+ callback : download_data
+});
+
+function download_data (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ d4 = [],
+ d5 = [],
+ graph,
+ i,x;
+
+ for (i = 0; i <= 100; i += 1) {
+ x = i / 10;
+ d1.push([x, 4 + Math.pow(x,1.5)]);
+ d2.push([x, Math.pow(x,3)]);
+ d3.push([x, i*5+3*Math.sin(x*4)]);
+ d4.push([x, x]);
+ if(x%1 === 0 ){
+ d5.push([x, 2*x]);
+ }
+ }
+
+ // Draw the graph.
+ graph = Flotr.draw(
+ container, [
+ { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } },
+ { data : d2, label : 'y = x^3' },
+ { data : d3, label : 'y = 5x + 3sin(4x)' },
+ { data : d4, label : 'y = x' },
+ { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } }
+ ],{
+ xaxis : {
+ noTicks : 7,
+ tickFormatter : function (n) { return '('+n+')'; },
+ min: 1, // Part of the series is not displayed.
+ max: 7.5
+ },
+ yaxis : {
+ ticks : [[ 0, "Lower"], 10, 20, 30, [40, "Upper"]],
+ max : 40
+ },
+ grid : {
+ verticalLines : false,
+ backgroundColor : 'white'
+ },
+ legend : {
+ position : 'nw'
+ },
+ spreadsheet : {
+ show : true,
+ tickFormatter : function (e) { return e+''; }
+ }
+ });
+};
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'advanced-titles',
+ name : 'Advanced Titles',
+ callback : advanced_titles
+});
+
+function advanced_titles (container) {
+
+ var
+ d1 = [],
+ d2 = [],
+ d3 = [],
+ d4 = [],
+ d5 = [],
+ graph,
+ i;
+
+ for (i = 0; i <= 10; i += 0.1) {
+ d1.push([i, 4 + Math.pow(i,1.5)]);
+ d2.push([i, Math.pow(i,3)]);
+ d3.push([i, i*5+3*Math.sin(i*4)]);
+ d4.push([i, i]);
+ if (i.toFixed(1)%1 == 0) {
+ d5.push([i, 2*i]);
+ }
+ }
+
+ // Draw the graph.
+ graph = Flotr.draw(
+ container,[
+ { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } },
+ { data : d2, label : 'y = x^3', yaxis : 2 },
+ { data : d3, label : 'y = 5x + 3sin(4x)' },
+ { data : d4, label : 'y = x' },
+ { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } }
+ ], {
+ title : 'Advanced Titles Example',
+ subtitle : 'You can save me as an image',
+ xaxis : {
+ noTicks : 7,
+ tickFormatter : function (n) { return '('+n+')'; },
+ min : 1,
+ max : 7.5,
+ labelsAngle : 45,
+ title : 'x Axis'
+ },
+ yaxis : {
+ ticks : [[0, "Lower"], 10, 20, 30, [40, "Upper"]],
+ max : 40,
+ title : 'y = f(x)'
+ },
+ y2axis : { color : '#FF0000', max : 500, title : 'y = x^3' },
+ grid : {
+ verticalLines : false,
+ backgroundColor : 'white'
+ },
+ HtmlText : false,
+ legend : {
+ position : 'nw'
+ }
+ });
+};
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'color-gradients',
+ name : 'Color Gradients',
+ callback : color_gradients
+});
+
+function color_gradients (container) {
+
+ var
+ bars = {
+ data: [],
+ bars: {
+ show: true,
+ barWidth: 0.8,
+ lineWidth: 0,
+ fillColor: {
+ colors: ['#CB4B4B', '#fff'],
+ start: 'top',
+ end: 'bottom'
+ },
+ fillOpacity: 0.8
+ }
+ }, markers = {
+ data: [],
+ markers: {
+ show: true,
+ position: 'ct'
+ }
+ }, lines = {
+ data: [],
+ lines: {
+ show: true,
+ fillColor: ['#00A8F0', '#fff'],
+ fill: true,
+ fillOpacity: 1
+ }
+ },
+ point,
+ graph,
+ i;
+
+ for (i = 0; i < 8; i++) {
+ point = [i, Math.ceil(Math.random() * 10)];
+ bars.data.push(point);
+ markers.data.push(point);
+ }
+
+ for (i = -1; i < 9; i += 0.01){
+ lines.data.push([i, i*i/8+2]);
+ }
+
+ graph = Flotr.draw(
+ container,
+ [lines, bars, markers], {
+ yaxis: {
+ min: 0,
+ max: 11
+ },
+ xaxis: {
+ min: -0.5,
+ max: 7.5
+ },
+ grid: {
+ verticalLines: false,
+ backgroundColor: ['#fff', '#ccc']
+ }
+ }
+ );
+};
+
+})();
+
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'profile-bars',
+ name : 'Profile Bars',
+ type : 'profile',
+ callback : profile_bars
+});
+
+/*
+Flotr.ExampleList.add({
+ key : 'basic-bars-horizontal',
+ name : 'Horizontal Bars',
+ args : [true],
+ callback : basic_bars
+});
+*/
+
+function profile_bars (container, horizontal) {
+
+ var
+ horizontal = (horizontal ? true : false), // Show horizontal bars
+ d1 = [], // First data series
+ d2 = [], // Second data series
+ point, // Data point variable declaration
+ i;
+
+ for (i = 0; i < 5000; i++) {
+
+ if (horizontal) {
+ point = [Math.ceil(Math.random()*10), i];
+ } else {
+ point = [i, Math.ceil(Math.random()*10)];
+ }
+
+ d1.push(point);
+
+ if (horizontal) {
+ point = [Math.ceil(Math.random()*10), i+0.5];
+ } else {
+ point = [i+0.5, Math.ceil(Math.random()*10)];
+ }
+
+ d2.push(point);
+ };
+
+ // Draw the graph
+ Flotr.draw(
+ container,
+ [d1, d2],
+ {
+ bars : {
+ show : true,
+ horizontal : horizontal,
+ barWidth : 0.5
+ },
+ mouse : {
+ track : true,
+ relative : true
+ },
+ yaxis : {
+ min : 0,
+ autoscaleMargin : 1
+ }
+ }
+ );
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'basic-timeline',
+ name : 'Basic Timeline',
+ callback : basic_timeline
+});
+
+function basic_timeline (container) {
+
+ var
+ d1 = [[1, 4, 5]],
+ d2 = [[3.2, 3, 4]],
+ d3 = [[1.9, 2, 2], [5, 2, 3.3]],
+ d4 = [[1.55, 1, 9]],
+ d5 = [[5, 0, 2.3]],
+ data = [],
+ timeline = { show : true, barWidth : .5 },
+ markers = [],
+ labels = ['Obama', 'Bush', 'Clinton', 'Palin', 'McCain'],
+ i, graph, point;
+
+ // Timeline
+ Flotr._.each([d1, d2, d3, d4, d5], function (d) {
+ data.push({
+ data : d,
+ timeline : Flotr._.clone(timeline)
+ });
+ });
+
+ // Markers
+ Flotr._.each([d1, d2, d3, d4, d5], function (d) {
+ point = d[0];
+ markers.push([point[0], point[1]]);
+ });
+ data.push({
+ data: markers,
+ markers: {
+ show: true,
+ position: 'rm',
+ fontSize: 11,
+ labelFormatter : function (o) { return labels[o.index]; }
+ }
+ });
+
+ // Draw Graph
+ graph = Flotr.draw(container, data, {
+ xaxis: {
+ noTicks: 3,
+ tickFormatter: function (x) {
+ var
+ x = parseInt(x),
+ months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ return months[(x-1)%12];
+ }
+ },
+ yaxis: {
+ showLabels : false
+ },
+ grid: {
+ horizontalLines : false
+ }
+ });
+}
+
+})();
+
+(function () {
+
+Flotr.ExampleList.add({
+ key : 'advanced-markers',
+ name : 'Advanced Markers',
+ callback : advanced_markers,
+ timeout : 150
+});
+
+function advanced_markers (container) {
+
+ var
+ xmark = new Image(),
+ checkmark = new Image(),
+ bars = {
+ data: [],
+ bars: {
+ show: true,
+ barWidth: 0.6,
+ lineWidth: 0,
+ fillOpacity: 0.8
+ }
+ }, markers = {
+ data: [],
+ markers: {
+ show: true,
+ position: 'ct',
+ labelFormatter: function (o) {
+ return (o.y >= 5) ? checkmark : xmark;
+ }
+ }
+ },
+ flotr = Flotr,
+ point,
+ graph,
+ i;
+
+
+ for (i = 0; i < 8; i++) {
+ point = [i, Math.ceil(Math.random() * 10)];
+ bars.data.push(point);
+ markers.data.push(point);
+ }
+
+ var runner = function () {
+ if (!xmark.complete || !checkmark.complete) {
+ setTimeout(runner, 50);
+ return;
+ }
+
+ graph = flotr.draw(
+ container,
+ [bars, markers], {
+ yaxis: {
+ min: 0,
+ max: 11
+ },
+ xaxis: {
+ min: -0.5,
+ max: 7.5
+ },
+ grid: {
+ verticalLines: false
+ }
+ }
+ );
+ }
+
+ xmark.onload = runner;
+ xmark.src = 'images/xmark.png';
+ checkmark.src = 'images/checkmark.png';
+};
+
+})();
+
diff --git a/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js b/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js
new file mode 100644
index 00000000000..0c64520fcb5
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js
@@ -0,0 +1,33 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// Known Issues:
+//
+// * Patterns only support repeat.
+// * Radial gradient are not implemented. The VML version of these look very
+// different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+// width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+// Quirks mode will draw the canvas using border-box. Either change your
+// doctype to HTML5
+// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+// or use Box Sizing Behavior from WebFX
+// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Optimize. There is always room for speed improvements.
+// Only add this code if we do not already have a canvas implementation
+
+document.createElement("canvas").getContext||function(){function j(){return this.context_||(this.context_=new N(this))}function l(a,b,c){var d=k.call(arguments,2);return function(){return a.apply(b,d.concat(k.call(arguments)))}}function m(a){return String(a).replace(/&/g,"&").replace(/"/g,""")}function n(a,b,c){a.namespaces[b]||a.namespaces.add(b,c,"#default#VML")}function o(a){n(a,"g_vml_","urn:schemas-microsoft-com:vml"),n(a,"g_o_","urn:schemas-microsoft-com:office:office");if(!a.styleSheets.ex_canvas_){var b=a.createStyleSheet();b.owningElement.id="ex_canvas_",b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}function q(a){var b=a.srcElement;switch(a.propertyName){case"width":b.getContext().clearRect(),b.style.width=b.attributes.width.nodeValue+"px",b.firstChild&&(b.firstChild.style.width=b.clientWidth+"px");break;case"height":b.getContext().clearRect(),b.style.height=b.attributes.height.nodeValue+"px",b.firstChild&&(b.firstChild.style.height=b.clientHeight+"px")}}function r(a){var b=a.srcElement;b.firstChild&&(b.firstChild.style.width=b.clientWidth+"px",b.firstChild.style.height=b.clientHeight+"px")}function v(){return[[1,0,0],[0,1,0],[0,0,1]]}function w(a,b){var c=v();for(var d=0;d<3;d++)for(var e=0;e<3;e++){var f=0;for(var g=0;g<3;g++)f+=a[d][g]*b[g][e];c[d][e]=f}return c}function x(a,b){b.fillStyle=a.fillStyle,b.lineCap=a.lineCap,b.lineJoin=a.lineJoin,b.lineWidth=a.lineWidth,b.miterLimit=a.miterLimit,b.shadowBlur=a.shadowBlur,b.shadowColor=a.shadowColor,b.shadowOffsetX=a.shadowOffsetX,b.shadowOffsetY=a.shadowOffsetY,b.strokeStyle=a.strokeStyle,b.globalAlpha=a.globalAlpha,b.font=a.font,b.textAlign=a.textAlign,b.textBaseline=a.textBaseline,b.arcScaleX_=a.arcScaleX_,b.arcScaleY_=a.arcScaleY_,b.lineScale_=a.lineScale_}function z(a){var b=a.indexOf("(",3),c=a.indexOf(")",b+1),d=a.substring(b+1,c).split(",");if(d.length!=4||a.charAt(3)!="a")d[3]=1;return d}function A(a){return parseFloat(a)/100}function B(a,b,c){return Math.min(c,Math.max(b,a))}function C(a){var b,c,d,e,f,g;e=parseFloat(a[0])/360%360,e<0&&e++,f=B(A(a[1]),0,1),g=B(A(a[2]),0,1);if(f==0)b=c=d=g;else{var h=g<.5?g*(1+f):g+f-g*f,i=2*g-h;b=D(i,h,e+1/3),c=D(i,h,e),d=D(i,h,e-1/3)}return"#"+s[Math.floor(b*255)]+s[Math.floor(c*255)]+s[Math.floor(d*255)]}function D(a,b,c){return c<0&&c++,c>1&&c--,6*c<1?a+(b-a)*6*c:2*c<1?b:3*c<2?a+(b-a)*(2/3-c)*6:a}function F(a){if(a in E)return E[a];var b,c=1;a=String(a);if(a.charAt(0)=="#")b=a;else if(/^rgb/.test(a)){var d=z(a),b="#",e;for(var f=0;f<3;f++)d[f].indexOf("%")!=-1?e=Math.floor(A(d[f])*255):e=+d[f],b+=s[B(e,0,255)];c=+d[3]}else if(/^hsl/.test(a)){var d=z(a);b=C(d),c=d[3]}else b=y[a]||a;return E[a]={color:b,alpha:c}}function I(a){if(H[a])return H[a];var b=document.createElement("div"),c=b.style;try{c.font=a}catch(d){}return H[a]={style:c.fontStyle||G.style,variant:c.fontVariant||G.variant,weight:c.fontWeight||G.weight,size:c.fontSize||G.size,family:c.fontFamily||G.family}}function J(a,b){var c={};for(var d in a)c[d]=a[d];var e=parseFloat(b.currentStyle.fontSize),f=parseFloat(a.size);return typeof a.size=="number"?c.size=a.size:a.size.indexOf("px")!=-1?c.size=f:a.size.indexOf("em")!=-1?c.size=e*f:a.size.indexOf("%")!=-1?c.size=e/100*f:a.size.indexOf("pt")!=-1?c.size=f/.75:c.size=e,c}function K(a){return a.style+" "+a.variant+" "+a.weight+" "+a.size+"px "+a.family}function M(a){return L[a]||"square"}function N(a){this.m_=v(),this.mStack_=[],this.aStack_=[],this.currentPath_=[],this.strokeStyle="#000",this.fillStyle="#000",this.lineWidth=1,this.lineJoin="miter",this.lineCap="butt",this.miterLimit=g*1,this.globalAlpha=1,this.font="10px sans-serif",this.textAlign="left",this.textBaseline="alphabetic",this.canvas=a;var b="width:"+a.clientWidth+"px;height:"+a.clientHeight+"px;overflow:hidden;position:absolute",c=a.ownerDocument.createElement("div");c.style.cssText=b,a.appendChild(c);var d=c.cloneNode(!1);d.style.backgroundColor="red",d.style.filter="alpha(opacity=0)",a.appendChild(d),this.element_=c,this.arcScaleX_=1,this.arcScaleY_=1,this.lineScale_=1}function P(a,b,c,d){a.currentPath_.push({type:"bezierCurveTo",cp1x:b.x,cp1y:b.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y}),a.currentX_=d.x,a.currentY_=d.y}function Q(a,b){var c=F(a.strokeStyle),d=c.color,e=c.alpha*a.globalAlpha,f=a.lineScale_*a.lineWidth;f<1&&(e*=f),b.push("
')}function R(b,c,d,e){var f=b.fillStyle,h=b.arcScaleX_,i=b.arcScaleY_,j=e.x-d.x,k=e.y-d.y;if(f instanceof V){var l=0,m={x:0,y:0},n=0,o=1;if(f.type_=="gradient"){var p=f.x0_/h,q=f.y0_/i,r=f.x1_/h,s=f.y1_/i,t=S(b,p,q),u=S(b,r,s),v=u.x-t.x,w=u.y-t.y;l=Math.atan2(v,w)*180/Math.PI,l<0&&(l+=360),l<1e-6&&(l=0)}else{var t=S(b,f.x0_,f.y0_);m={x:(t.x-d.x)/j,y:(t.y-d.y)/k},j/=h*g,k/=i*g;var x=a.max(j,k);n=2*f.r0_/x,o=2*f.r1_/x-n}var y=f.colors_;y.sort(function(a,b){return a.offset-b.offset});var z=y.length,A=y[0].color,B=y[z-1].color,C=y[0].alpha*b.globalAlpha,D=y[z-1].alpha*b.globalAlpha,E=[];for(var G=0;G
')}else if(f instanceof W){if(j&&k){var I=-d.x,J=-d.y;c.push("
')}}else{var K=F(b.fillStyle),L=K.color,M=K.alpha*b.globalAlpha;c.push('
')}}function S(a,b,c){var d=a.m_;return{x:g*(b*d[0][0]+c*d[1][0]+d[2][0])-h,y:g*(b*d[0][1]+c*d[1][1]+d[2][1])-h}}function T(a){return isFinite(a[0][0])&&isFinite(a[0][1])&&isFinite(a[1][0])&&isFinite(a[1][1])&&isFinite(a[2][0])&&isFinite(a[2][1])}function U(a,b,c){if(!T(b))return;a.m_=b;if(c){var d=b[0][0]*b[1][1]-b[0][1]*b[1][0];a.lineScale_=f(e(d))}}function V(a){this.type_=a,this.x0_=0,this.y0_=0,this.r0_=0,this.x1_=0,this.y1_=0,this.r1_=0,this.colors_=[]}function W(a,b){Y(a);switch(b){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=b;break;default:X("SYNTAX_ERR")}this.src_=a.src,this.width_=a.width,this.height_=a.height}function X(a){throw new Z(a)}function Y(a){(!a||a.nodeType!=1||a.tagName!="IMG")&&X("TYPE_MISMATCH_ERR"),a.readyState!="complete"&&X("INVALID_STATE_ERR")}function Z(a){this.code=this[a],this.message=a+": DOM Exception "+this.code}var a=Math,b=a.round,c=a.sin,d=a.cos,e=a.abs,f=a.sqrt,g=10,h=g/2,i=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1],k=Array.prototype.slice;o(document);var p={init:function(a){var b=a||document;b.createElement("canvas"),b.attachEvent("onreadystatechange",l(this.init_,this,b))},init_:function(a){var b=a.getElementsByTagName("canvas");for(var c=0;c
',' ",""),this.element_.insertAdjacentHTML("BeforeEnd",u.join(""))},O.stroke=function(a){var c=[],d=!1,e=10,f=10;c.push("j.x)j.x=l.x;if(i.y==null||l.yj.y)j.y=l.y}}c.push(' ">'),a?R(this,c,i,j):Q(this,c),c.push(" "),this.element_.insertAdjacentHTML("beforeEnd",c.join(""))},O.fill=function(){this.stroke(!0)},O.closePath=function(){this.currentPath_.push({type:"close"})},O.save=function(){var a={};x(this,a),this.aStack_.push(a),this.mStack_.push(this.m_),this.m_=w(v(),this.m_)},O.restore=function(){this.aStack_.length&&(x(this.aStack_.pop(),this),this.m_=this.mStack_.pop())},O.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];U(this,w(c,this.m_),!1)},O.rotate=function(a){var b=d(a),e=c(a),f=[[b,e,0],[-e,b,0],[0,0,1]];U(this,w(f,this.m_),!1)},O.scale=function(a,b){this.arcScaleX_*=a,this.arcScaleY_*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];U(this,w(c,this.m_),!0)},O.transform=function(a,b,c,d,e,f){var g=[[a,b,0],[c,d,0],[e,f,1]];U(this,w(g,this.m_),!0)},O.setTransform=function(a,b,c,d,e,f){var g=[[a,b,0],[c,d,0],[e,f,1]];U(this,g,!0)},O.drawText_=function(a,c,d,e,f){var h=this.m_,i=1e3,j=0,k=i,l={x:0,y:0},n=[],o=J(I(this.font),this.element_),p=K(o),q=this.element_.currentStyle,r=this.textAlign.toLowerCase();switch(r){case"left":case"center":case"right":break;case"end":r=q.direction=="ltr"?"right":"left";break;case"start":r=q.direction=="rtl"?"right":"left";break;default:r="left"}switch(this.textBaseline){case"hanging":case"top":l.y=o.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":l.y=-o.size/2.25}switch(r){case"right":j=i,k=.05;break;case"center":j=k=i/2}var s=S(this,c+l.x,d+l.y);n.push(''),f?Q(this,n):R(this,n,{x:-j,y:0},{x:k,y:o.size});var t=h[0][0].toFixed(3)+","+h[1][0].toFixed(3)+","+h[0][1].toFixed(3)+","+h[1][1].toFixed(3)+",0,0",u=b(s.x/g)+","+b(s.y/g);n.push(' ',' ',' '),this.element_.insertAdjacentHTML("beforeEnd",n.join(""))},O.fillText=function(a,b,c,d){this.drawText_(a,b,c,d,!1)},O.strokeText=function(a,b,c,d){this.drawText_(a,b,c,d,!0)},O.measureText=function(a){if(!this.textMeasureEl_){var b=' ';this.element_.insertAdjacentHTML("beforeEnd",b),this.textMeasureEl_=this.element_.lastChild}var c=this.element_.ownerDocument;return this.textMeasureEl_.innerHTML="",this.textMeasureEl_.style.font=this.font,this.textMeasureEl_.appendChild(c.createTextNode(a)),{width:this.textMeasureEl_.offsetWidth}},O.clip=function(){},O.arcTo=function(){},O.createPattern=function(a,b){return new W(a,b)},V.prototype.addColorStop=function(a,b){b=F(b),this.colors_.push({offset:a,color:b.color,alpha:b.alpha})};var $=Z.prototype=new Error;$.INDEX_SIZE_ERR=1,$.DOMSTRING_SIZE_ERR=2,$.HIERARCHY_REQUEST_ERR=3,$.WRONG_DOCUMENT_ERR=4,$.INVALID_CHARACTER_ERR=5,$.NO_DATA_ALLOWED_ERR=6,$.NO_MODIFICATION_ALLOWED_ERR=7,$.NOT_FOUND_ERR=8,$.NOT_SUPPORTED_ERR=9,$.INUSE_ATTRIBUTE_ERR=10,$.INVALID_STATE_ERR=11,$.SYNTAX_ERR=12,$.INVALID_MODIFICATION_ERR=13,$.NAMESPACE_ERR=14,$.INVALID_ACCESS_ERR=15,$.VALIDATION_ERR=16,$.TYPE_MISMATCH_ERR=17,G_vmlCanvasManager=p,CanvasRenderingContext2D=N,CanvasGradient=V,CanvasPattern=W,DOMException=Z}(),function(){function c(b){var c,d,e,f,g,h;e=b.length,d=0,c="";while(d>2),c+=a.charAt((f&3)<<4),c+="==";break}g=b.charCodeAt(d++);if(d==e){c+=a.charAt(f>>2),c+=a.charAt((f&3)<<4|(g&240)>>4),c+=a.charAt((g&15)<<2),c+="=";break}h=b.charCodeAt(d++),c+=a.charAt(f>>2),c+=a.charAt((f&3)<<4|(g&240)>>4),c+=a.charAt((g&15)<<2|(h&192)>>6),c+=a.charAt(h&63)}return c}function d(a){var c,d,e,f,g,h,i;h=a.length,g=0,i="";while(g>4);do{e=a.charCodeAt(g++)&255;if(e==61)return i;e=b[e]}while(g>2);do{f=a.charCodeAt(g++)&255;if(f==61)return i;f=b[f]}while(g":{width:24,points:[[4,18],[20,9],[4,0]]},"?":{width:18,points:[[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]]},"@":{width:27,points:[[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]]},A:{width:18,points:[[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]]},B:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]]},C:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]]},D:{width:21,points:[[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]]},E:{width:19,points:[[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]]},F:{width:18,points:[[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]]},G:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]]},H:{width:22,points:[[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]]},I:{width:8,points:[[4,21],[4,0]]},J:{width:16,points:[[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]]},K:{width:21,points:[[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]]},L:{width:17,points:[[4,21],[4,0],null,[4,0],[16,0]]},M:{width:24,points:[[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]]},N:{width:22,points:[[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]]},O:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]]},P:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]]},Q:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]]},R:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]]},S:{width:20,points:[[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]]},T:{width:16,points:[[8,21],[8,0],null,[1,21],[15,21]]},U:{width:22,points:[[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]]},V:{width:18,points:[[1,21],[9,0],null,[17,21],[9,0]]},W:{width:24,points:[[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]]},X:{width:20,points:[[3,21],[17,0],null,[17,21],[3,0]]},Y:{width:18,points:[[1,21],[9,11],[9,0],null,[17,21],[9,11]]},Z:{width:20,points:[[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]]},"[":{width:14,points:[[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]]},"\\":{width:14,points:[[0,21],[14,-3]]},"]":{width:14,points:[[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]]},"^":{width:14,points:[[3,10],[8,18],[13,10]]},_:{width:16,points:[[0,-2],[16,-2]]},"`":{width:10,points:[[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]]},a:{width:19,points:[[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},b:{width:19,points:[[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},c:{width:18,points:[[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},d:{width:19,points:[[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},e:{width:18,points:[[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},f:{width:12,points:[[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]]},g:{width:19,points:[[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},h:{width:19,points:[[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},i:{width:8,points:[[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]]},j:{width:10,points:[[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]]},k:{width:17,points:[[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]]},l:{width:8,points:[[4,21],[4,0]]},m:{width:30,points:[[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]]},n:{width:19,points:[[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},o:{width:19,points:[[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]]},p:{width:19,points:[[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},q:{width:19,points:[[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},r:{width:13,points:[[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]]},s:{width:17,points:[[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]]},t:{width:12,points:[[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]]},u:{width:19,points:[[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]]},v:{width:16,points:[[2,14],[8,0],null,[14,14],[8,0]]},w:{width:22,points:[[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]]},x:{width:17,points:[[3,14],[14,0],null,[14,14],[3,0]]},y:{width:16,points:[[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]]},z:{width:17,points:[[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]]},"{":{width:14,points:[[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]]},"|":{width:8,points:[[4,25],[4,-7]]},"}":{width:14,points:[[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]]},"~":{width:24,points:[[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]]},"�":{diacritic:"`",letter:"a"},"�":{diacritic:"�",letter:"a"},"�":{diacritic:"^",letter:"a"},"�":{diacritic:"�",letter:"a"},"�":{diacritic:"~",letter:"a"},"�":{diacritic:"`",letter:"e"},"�":{diacritic:"�",letter:"e"},"�":{diacritic:"^",letter:"e"},"�":{diacritic:"�",letter:"e"},"�":{diacritic:"`",letter:"i"},"�":{diacritic:"�",letter:"i"},"�":{diacritic:"^",letter:"i"},"�":{diacritic:"�",letter:"i"},"�":{diacritic:"`",letter:"o"},"�":{diacritic:"�",letter:"o"},"�":{diacritic:"^",letter:"o"},"�":{diacritic:"�",letter:"o"},"�":{diacritic:"~",letter:"o"},"�":{diacritic:"`",letter:"u"},"�":{diacritic:"�",letter:"u"},"�":{diacritic:"^",letter:"u"},"�":{diacritic:"�",letter:"u"},"�":{diacritic:"�",letter:"y"},"�":{diacritic:"�",letter:"y"},"�":{diacritic:"�",letter:"c"},"�":{diacritic:"~",letter:"n"},"�":{diacritic:"`",letter:"A"},"�":{diacritic:"�",letter:"A"},"�":{diacritic:"^",letter:"A"},"�":{diacritic:"�",letter:"A"},"�":{diacritic:"~",letter:"A"},"�":{diacritic:"`",letter:"E"},"�":{diacritic:"�",letter:"E"},"�":{diacritic:"^",letter:"E"},"�":{diacritic:"�",letter:"E"},"�":{diacritic:"`",letter:"I"},"�":{diacritic:"�",letter:"I"},"�":{diacritic:"^",letter:"I"},"�":{diacritic:"�",letter:"I"},"�":{diacritic:"`",letter:"O"},"�":{diacritic:"�",letter:"O"},"�":{diacritic:"^",letter:"O"},"�":{diacritic:"�",letter:"O"},"�":{diacritic:"~",letter:"O"},"�":{diacritic:"`",letter:"U"},"�":{diacritic:"�",letter:"U"},"�":{diacritic:"^",letter:"U"},"�":{diacritic:"�",letter:"U"},"�":{diacritic:"�",letter:"Y"},"�":{diacritic:"�",letter:"C"},"�":{diacritic:"~",letter:"N"}},specialchars:{pi:{width:19,points:[[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]]}},diacritics:{"�":{entity:"cedil",points:[[6,-4],[4,-6],[2,-7],[1,-7]]},"�":{entity:"acute",points:[[8,19],[13,22]]},"`":{entity:"grave",points:[[7,22],[12,19]]},"^":{entity:"circ",points:[[5.5,19],[9.5,23],[12.5,19]]},"�":{entity:"trema",points:[[5,21],[6,20],[7,21],[6,22],[5,21],null,[12,21],[13,20],[14,21],[13,22],[12,21]]},"~":{entity:"tilde",points:[[4,18],[7,22],[10,18],[13,22]]}},style:{size:8,font:null,color:"#000000",weight:1,textAlign:"left",textBaseline:"bottom",adjustAlign:!1,angle:0,tracking:1,boundingBoxColor:"#ff0000",originPointColor:"#000000"},debug:!1,_bufferLexemes:{},extend:function(a,b){for(var c in b){if(c in a)continue;a[c]=b[c]}return a},letter:function(a){return CanvasText.letters[a]},parseLexemes:function(a){if(CanvasText._bufferLexemes[a])return CanvasText._bufferLexemes[a];var b,c,d=a.match(/&[A-Za-z]{2,5};|\s|./g),e=[],f=[];for(b=0;b-1;--d)c=f[d],e=c.diacritic?CanvasText.letter(c.letter).width:c.width,g+=e*(b.tracking||CanvasText.style.tracking)*(b.size||CanvasText.style.size)/25;return g},getDimensions:function(a,b){b=b||CanvasText.style;var c=CanvasText.measure(a,b),d=b.size||CanvasText.style.size,e=b.angle||CanvasText.style.angle;return b.angle==0?{width:c,height:d}:{width:Math.abs(Math.cos(e)*c)+Math.abs(Math.sin(e)*d),height:Math.abs(Math.sin(e)*c)+Math.abs(Math.cos(e)*d)}},drawPoints:function(a,b,c,d,e,f){var g,h,i=!0,j=0;f=f||{x:0,y:0},a.beginPath();for(g=0;g 0) {
+ // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3')
+ typeSpec = typeSpec.split(' ')
+ for (i = typeSpec.length; i--;)
+ remove(element, typeSpec[i], fn)
+ return element
+ }
+ type = isString && typeSpec.replace(nameRegex, '')
+ if (type && customEvents[type])
+ type = customEvents[type].type
+ if (!typeSpec || isString) {
+ // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3)
+ if (namespaces = isString && typeSpec.replace(namespaceRegex, ''))
+ namespaces = namespaces.split('.')
+ rm(element, type, fn, namespaces)
+ } else if (typeof typeSpec === 'function') {
+ // remove(el, fn)
+ rm(element, null, typeSpec)
+ } else {
+ // remove(el, { t1: fn1, t2, fn2 })
+ for (k in typeSpec) {
+ if (typeSpec.hasOwnProperty(k))
+ remove(element, k, typeSpec[k])
+ }
+ }
+ return element
+ }
+
+ , add = function (element, events, fn, delfn, $) {
+ var type, types, i, args
+ , originalFn = fn
+ , isDel = fn && typeof fn === 'string'
+
+ if (events && !fn && typeof events === 'object') {
+ for (type in events) {
+ if (events.hasOwnProperty(type))
+ add.apply(this, [ element, type, events[type] ])
+ }
+ } else {
+ args = arguments.length > 3 ? slice.call(arguments, 3) : []
+ types = (isDel ? fn : events).split(' ')
+ isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1))
+ // special case for one()
+ this === ONE && (fn = once(remove, element, events, fn, originalFn))
+ for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args)
+ }
+ return element
+ }
+
+ , one = function () {
+ return add.apply(ONE, arguments)
+ }
+
+ , fireListener = W3C_MODEL ? function (isNative, type, element) {
+ var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
+ evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
+ element.dispatchEvent(evt)
+ } : function (isNative, type, element) {
+ element = targetElement(element, isNative)
+ // if not-native then we're using onpropertychange so we just increment a custom property
+ isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
+ }
+
+ , fire = function (element, type, args) {
+ var i, j, l, names, handlers
+ , types = type.split(' ')
+
+ for (i = types.length; i--;) {
+ type = types[i].replace(nameRegex, '')
+ if (names = types[i].replace(namespaceRegex, ''))
+ names = names.split('.')
+ if (!names && !args && element[eventSupport]) {
+ fireListener(nativeEvents[type], type, element)
+ } else {
+ // non-native event, either because of a namespace, arguments or a non DOM element
+ // iterate over all listeners and manually 'fire'
+ handlers = registry.get(element, type)
+ args = [false].concat(args)
+ for (j = 0, l = handlers.length; j < l; j++) {
+ if (handlers[j].inNamespaces(names))
+ handlers[j].handler.apply(element, args)
+ }
+ }
+ }
+ return element
+ }
+
+ , clone = function (element, from, type) {
+ var i = 0
+ , handlers = registry.get(from, type)
+ , l = handlers.length
+
+ for (;i < l; i++)
+ handlers[i].original && add(element, handlers[i].type, handlers[i].original)
+ return element
+ }
+
+ , bean = {
+ add: add
+ , one: one
+ , remove: remove
+ , clone: clone
+ , fire: fire
+ , noConflict: function () {
+ context[name] = old
+ return this
+ }
+ }
+
+ if (win[attachEvent]) {
+ // for IE, clean up on unload to avoid leaks
+ var cleanup = function () {
+ var i, entries = registry.entries()
+ for (i in entries) {
+ if (entries[i].type && entries[i].type !== 'unload')
+ remove(entries[i].element, entries[i].type)
+ }
+ win[detachEvent]('onunload', cleanup)
+ win.CollectGarbage && win.CollectGarbage()
+ }
+ win[attachEvent]('onunload', cleanup)
+ }
+
+ return bean
+});
+// Underscore.js 1.1.7
+// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the MIT license.
+// Portions of Underscore are inspired or borrowed from Prototype,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore
+
+(function() {
+
+ // Baseline setup
+ // --------------
+
+ // Establish the root object, `window` in the browser, or `global` on the server.
+ var root = this;
+
+ // Save the previous value of the `_` variable.
+ var previousUnderscore = root._;
+
+ // Establish the object that gets returned to break out of a loop iteration.
+ var breaker = {};
+
+ // Save bytes in the minified (but not gzipped) version:
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var slice = ArrayProto.slice,
+ unshift = ArrayProto.unshift,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
+
+ // All **ECMAScript 5** native function implementations that we hope to use
+ // are declared here.
+ var
+ nativeForEach = ArrayProto.forEach,
+ nativeMap = ArrayProto.map,
+ nativeReduce = ArrayProto.reduce,
+ nativeReduceRight = ArrayProto.reduceRight,
+ nativeFilter = ArrayProto.filter,
+ nativeEvery = ArrayProto.every,
+ nativeSome = ArrayProto.some,
+ nativeIndexOf = ArrayProto.indexOf,
+ nativeLastIndexOf = ArrayProto.lastIndexOf,
+ nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys,
+ nativeBind = FuncProto.bind;
+
+ // Create a safe reference to the Underscore object for use below.
+ var _ = function(obj) { return new wrapper(obj); };
+
+ // Export the Underscore object for **CommonJS**, with backwards-compatibility
+ // for the old `require()` API. If we're not in CommonJS, add `_` to the
+ // global object.
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = _;
+ _._ = _;
+ } else {
+ // Exported as a string, for Closure Compiler "advanced" mode.
+ root['_'] = _;
+ }
+
+ // Current version.
+ _.VERSION = '1.1.7';
+
+ // Collection Functions
+ // --------------------
+
+ // The cornerstone, an `each` implementation, aka `forEach`.
+ // Handles objects with the built-in `forEach`, arrays, and raw objects.
+ // Delegates to **ECMAScript 5**'s native `forEach` if available.
+ var each = _.each = _.forEach = function(obj, iterator, context) {
+ if (obj == null) return;
+ if (nativeForEach && obj.forEach === nativeForEach) {
+ obj.forEach(iterator, context);
+ } else if (obj.length === +obj.length) {
+ for (var i = 0, l = obj.length; i < l; i++) {
+ if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
+ }
+ } else {
+ for (var key in obj) {
+ if (hasOwnProperty.call(obj, key)) {
+ if (iterator.call(context, obj[key], key, obj) === breaker) return;
+ }
+ }
+ }
+ };
+
+ // Return the results of applying the iterator to each element.
+ // Delegates to **ECMAScript 5**'s native `map` if available.
+ _.map = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+ each(obj, function(value, index, list) {
+ results[results.length] = iterator.call(context, value, index, list);
+ });
+ return results;
+ };
+
+ // **Reduce** builds up a single result from a list of values, aka `inject`,
+ // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
+ _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
+ var initial = memo !== void 0;
+ if (obj == null) obj = [];
+ if (nativeReduce && obj.reduce === nativeReduce) {
+ if (context) iterator = _.bind(iterator, context);
+ return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+ }
+ each(obj, function(value, index, list) {
+ if (!initial) {
+ memo = value;
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, value, index, list);
+ }
+ });
+ if (!initial) throw new TypeError("Reduce of empty array with no initial value");
+ return memo;
+ };
+
+ // The right-associative version of reduce, also known as `foldr`.
+ // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
+ _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+ if (obj == null) obj = [];
+ if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
+ if (context) iterator = _.bind(iterator, context);
+ return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+ }
+ var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
+ return _.reduce(reversed, iterator, memo, context);
+ };
+
+ // Return the first value which passes a truth test. Aliased as `detect`.
+ _.find = _.detect = function(obj, iterator, context) {
+ var result;
+ any(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) {
+ result = value;
+ return true;
+ }
+ });
+ return result;
+ };
+
+ // Return all the elements that pass a truth test.
+ // Delegates to **ECMAScript 5**'s native `filter` if available.
+ // Aliased as `select`.
+ _.filter = _.select = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
+ each(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) results[results.length] = value;
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ each(obj, function(value, index, list) {
+ if (!iterator.call(context, value, index, list)) results[results.length] = value;
+ });
+ return results;
+ };
+
+ // Determine whether all of the elements match a truth test.
+ // Delegates to **ECMAScript 5**'s native `every` if available.
+ // Aliased as `all`.
+ _.every = _.all = function(obj, iterator, context) {
+ var result = true;
+ if (obj == null) return result;
+ if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
+ each(obj, function(value, index, list) {
+ if (!(result = result && iterator.call(context, value, index, list))) return breaker;
+ });
+ return result;
+ };
+
+ // Determine if at least one element in the object matches a truth test.
+ // Delegates to **ECMAScript 5**'s native `some` if available.
+ // Aliased as `any`.
+ var any = _.some = _.any = function(obj, iterator, context) {
+ iterator = iterator || _.identity;
+ var result = false;
+ if (obj == null) return result;
+ if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
+ each(obj, function(value, index, list) {
+ if (result |= iterator.call(context, value, index, list)) return breaker;
+ });
+ return !!result;
+ };
+
+ // Determine if a given value is included in the array or object using `===`.
+ // Aliased as `contains`.
+ _.include = _.contains = function(obj, target) {
+ var found = false;
+ if (obj == null) return found;
+ if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
+ any(obj, function(value) {
+ if (found = value === target) return true;
+ });
+ return found;
+ };
+
+ // Invoke a method (with arguments) on every item in a collection.
+ _.invoke = function(obj, method) {
+ var args = slice.call(arguments, 2);
+ return _.map(obj, function(value) {
+ return (method.call ? method || value : value[method]).apply(value, args);
+ });
+ };
+
+ // Convenience version of a common use case of `map`: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, function(value){ return value[key]; });
+ };
+
+ // Return the maximum element or (element-based computation).
+ _.max = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
+ var result = {computed : -Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed >= result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
+ var result = {computed : Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed < result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Sort the object's values by a criterion produced by an iterator.
+ _.sortBy = function(obj, iterator, context) {
+ return _.pluck(_.map(obj, function(value, index, list) {
+ return {
+ value : value,
+ criteria : iterator.call(context, value, index, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }), 'value');
+ };
+
+ // Groups the object's values by a criterion produced by an iterator
+ _.groupBy = function(obj, iterator) {
+ var result = {};
+ each(obj, function(value, index) {
+ var key = iterator(value, index);
+ (result[key] || (result[key] = [])).push(value);
+ });
+ return result;
+ };
+
+ // Use a comparator function to figure out at what index an object should
+ // be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iterator) {
+ iterator || (iterator = _.identity);
+ var low = 0, high = array.length;
+ while (low < high) {
+ var mid = (low + high) >> 1;
+ iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
+ }
+ return low;
+ };
+
+ // Safely convert anything iterable into a real, live array.
+ _.toArray = function(iterable) {
+ if (!iterable) return [];
+ if (iterable.toArray) return iterable.toArray();
+ if (_.isArray(iterable)) return slice.call(iterable);
+ if (_.isArguments(iterable)) return slice.call(iterable);
+ return _.values(iterable);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ return _.toArray(obj).length;
+ };
+
+ // Array Functions
+ // ---------------
+
+ // Get the first element of an array. Passing **n** will return the first N
+ // values in the array. Aliased as `head`. The **guard** check allows it to work
+ // with `_.map`.
+ _.first = _.head = function(array, n, guard) {
+ return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
+ };
+
+ // Returns everything but the first entry of the array. Aliased as `tail`.
+ // Especially useful on the arguments object. Passing an **index** will return
+ // the rest of the values in the array from that index onward. The **guard**
+ // check allows it to work with `_.map`.
+ _.rest = _.tail = function(array, index, guard) {
+ return slice.call(array, (index == null) || guard ? 1 : index);
+ };
+
+ // Get the last element of an array.
+ _.last = function(array) {
+ return array[array.length - 1];
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.filter(array, function(value){ return !!value; });
+ };
+
+ // Return a completely flattened version of an array.
+ _.flatten = function(array) {
+ return _.reduce(array, function(memo, value) {
+ if (_.isArray(value)) return memo.concat(_.flatten(value));
+ memo[memo.length] = value;
+ return memo;
+ }, []);
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = function(array) {
+ return _.difference(array, slice.call(arguments, 1));
+ };
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ // Aliased as `unique`.
+ _.uniq = _.unique = function(array, isSorted) {
+ return _.reduce(array, function(memo, el, i) {
+ if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el;
+ return memo;
+ }, []);
+ };
+
+ // Produce an array that contains the union: each distinct element from all of
+ // the passed-in arrays.
+ _.union = function() {
+ return _.uniq(_.flatten(arguments));
+ };
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays. (Aliased as "intersect" for back-compat.)
+ _.intersection = _.intersect = function(array) {
+ var rest = slice.call(arguments, 1);
+ return _.filter(_.uniq(array), function(item) {
+ return _.every(rest, function(other) {
+ return _.indexOf(other, item) >= 0;
+ });
+ });
+ };
+
+ // Take the difference between one array and another.
+ // Only the elements present in just the first array will remain.
+ _.difference = function(array, other) {
+ return _.filter(array, function(value){ return !_.include(other, value); });
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = function() {
+ var args = slice.call(arguments);
+ var length = _.max(_.pluck(args, 'length'));
+ var results = new Array(length);
+ for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
+ return results;
+ };
+
+ // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
+ // we need this function. Return the position of the first occurrence of an
+ // item in an array, or -1 if the item is not included in the array.
+ // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+ // If the array is large and already in sort order, pass `true`
+ // for **isSorted** to use binary search.
+ _.indexOf = function(array, item, isSorted) {
+ if (array == null) return -1;
+ var i, l;
+ if (isSorted) {
+ i = _.sortedIndex(array, item);
+ return array[i] === item ? i : -1;
+ }
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
+ for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
+ return -1;
+ };
+
+
+ // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
+ _.lastIndexOf = function(array, item) {
+ if (array == null) return -1;
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
+ var i = array.length;
+ while (i--) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python `range()` function. See
+ // [the Python documentation](http://docs.python.org/library/functions.html#range).
+ _.range = function(start, stop, step) {
+ if (arguments.length <= 1) {
+ stop = start || 0;
+ start = 0;
+ }
+ step = arguments[2] || 1;
+
+ var len = Math.max(Math.ceil((stop - start) / step), 0);
+ var idx = 0;
+ var range = new Array(len);
+
+ while(idx < len) {
+ range[idx++] = start;
+ start += step;
+ }
+
+ return range;
+ };
+
+ // Function (ahem) Functions
+ // ------------------
+
+ // Create a function bound to a given object (assigning `this`, and arguments,
+ // optionally). Binding with arguments is also known as `curry`.
+ // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
+ // We check for `func.bind` first, to fail fast when `func` is undefined.
+ _.bind = function(func, obj) {
+ if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+ var args = slice.call(arguments, 2);
+ return function() {
+ return func.apply(obj, args.concat(slice.call(arguments)));
+ };
+ };
+
+ // Bind all of an object's methods to that object. Useful for ensuring that
+ // all callbacks defined on an object belong to it.
+ _.bindAll = function(obj) {
+ var funcs = slice.call(arguments, 1);
+ if (funcs.length == 0) funcs = _.functions(obj);
+ each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+ return obj;
+ };
+
+ // Memoize an expensive function by storing its results.
+ _.memoize = function(func, hasher) {
+ var memo = {};
+ hasher || (hasher = _.identity);
+ return function() {
+ var key = hasher.apply(this, arguments);
+ return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+ };
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = function(func, wait) {
+ var args = slice.call(arguments, 2);
+ return setTimeout(function(){ return func.apply(func, args); }, wait);
+ };
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = function(func) {
+ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+ };
+
+ // Internal function used to implement `_.throttle` and `_.debounce`.
+ var limit = function(func, wait, debounce) {
+ var timeout;
+ return function() {
+ var context = this, args = arguments;
+ var throttler = function() {
+ timeout = null;
+ func.apply(context, args);
+ };
+ if (debounce) clearTimeout(timeout);
+ if (debounce || !timeout) timeout = setTimeout(throttler, wait);
+ };
+ };
+
+ // Returns a function, that, when invoked, will only be triggered at most once
+ // during a given window of time.
+ _.throttle = function(func, wait) {
+ return limit(func, wait, false);
+ };
+
+ // Returns a function, that, as long as it continues to be invoked, will not
+ // be triggered. The function will be called after it stops being called for
+ // N milliseconds.
+ _.debounce = function(func, wait) {
+ return limit(func, wait, true);
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = function(func) {
+ var ran = false, memo;
+ return function() {
+ if (ran) return memo;
+ ran = true;
+ return memo = func.apply(this, arguments);
+ };
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return function() {
+ var args = [func].concat(slice.call(arguments));
+ return wrapper.apply(this, args);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var funcs = slice.call(arguments);
+ return function() {
+ var args = slice.call(arguments);
+ for (var i = funcs.length - 1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+ return args[0];
+ };
+ };
+
+ // Returns a function that will only be executed after being called N times.
+ _.after = function(times, func) {
+ return function() {
+ if (--times < 1) { return func.apply(this, arguments); }
+ };
+ };
+
+
+ // Object Functions
+ // ----------------
+
+ // Retrieve the names of an object's properties.
+ // Delegates to **ECMAScript 5**'s native `Object.keys`
+ _.keys = nativeKeys || function(obj) {
+ if (obj !== Object(obj)) throw new TypeError('Invalid object');
+ var keys = [];
+ for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ return _.map(obj, _.identity);
+ };
+
+ // Return a sorted list of the function names available on the object.
+ // Aliased as `methods`
+ _.functions = _.methods = function(obj) {
+ var names = [];
+ for (var key in obj) {
+ if (_.isFunction(obj[key])) names.push(key);
+ }
+ return names.sort();
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ for (var prop in source) {
+ if (source[prop] !== void 0) obj[prop] = source[prop];
+ }
+ });
+ return obj;
+ };
+
+ // Fill in a given object with default properties.
+ _.defaults = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ for (var prop in source) {
+ if (obj[prop] == null) obj[prop] = source[prop];
+ }
+ });
+ return obj;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in
+ // order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ // Check object identity.
+ if (a === b) return true;
+ // Different types?
+ var atype = typeof(a), btype = typeof(b);
+ if (atype != btype) return false;
+ // Basic equality test (watch out for coercions).
+ if (a == b) return true;
+ // One is falsy and the other truthy.
+ if ((!a && b) || (a && !b)) return false;
+ // Unwrap any wrapped objects.
+ if (a._chain) a = a._wrapped;
+ if (b._chain) b = b._wrapped;
+ // One of them implements an isEqual()?
+ if (a.isEqual) return a.isEqual(b);
+ if (b.isEqual) return b.isEqual(a);
+ // Check dates' integer values.
+ if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
+ // Both are NaN?
+ if (_.isNaN(a) && _.isNaN(b)) return false;
+ // Compare regular expressions.
+ if (_.isRegExp(a) && _.isRegExp(b))
+ return a.source === b.source &&
+ a.global === b.global &&
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline;
+ // If a is not an object by this point, we can't handle it.
+ if (atype !== 'object') return false;
+ // Check for different array lengths before comparing contents.
+ if (a.length && (a.length !== b.length)) return false;
+ // Nothing else worked, deep compare the contents.
+ var aKeys = _.keys(a), bKeys = _.keys(b);
+ // Different object sizes?
+ if (aKeys.length != bKeys.length) return false;
+ // Recursive comparison of contents.
+ for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
+ return true;
+ };
+
+ // Is a given array or object empty?
+ _.isEmpty = function(obj) {
+ if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+ for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
+ return true;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType == 1);
+ };
+
+ // Is a given value an array?
+ // Delegates to ECMA5's native Array.isArray
+ _.isArray = nativeIsArray || function(obj) {
+ return toString.call(obj) === '[object Array]';
+ };
+
+ // Is a given variable an object?
+ _.isObject = function(obj) {
+ return obj === Object(obj);
+ };
+
+ // Is a given variable an arguments object?
+ _.isArguments = function(obj) {
+ return !!(obj && hasOwnProperty.call(obj, 'callee'));
+ };
+
+ // Is a given value a function?
+ _.isFunction = function(obj) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ };
+
+ // Is a given value a string?
+ _.isString = function(obj) {
+ return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
+ };
+
+ // Is a given value a number?
+ _.isNumber = function(obj) {
+ return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
+ };
+
+ // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
+ // that does not equal itself.
+ _.isNaN = function(obj) {
+ return obj !== obj;
+ };
+
+ // Is a given value a boolean?
+ _.isBoolean = function(obj) {
+ return obj === true || obj === false;
+ };
+
+ // Is a given value a date?
+ _.isDate = function(obj) {
+ return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
+ };
+
+ // Is the given value a regular expression?
+ _.isRegExp = function(obj) {
+ return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ // Utility Functions
+ // -----------------
+
+ // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iterators.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Run a function **n** times.
+ _.times = function (n, iterator, context) {
+ for (var i = 0; i < n; i++) iterator.call(context, i);
+ };
+
+ // Add your own custom functions to the Underscore object, ensuring that
+ // they're correctly added to the OOP wrapper as well.
+ _.mixin = function(obj) {
+ each(_.functions(obj), function(name){
+ addToWrapper(name, _[name] = obj[name]);
+ });
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = idCounter++;
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ evaluate : /<%([\s\S]+?)%>/g,
+ interpolate : /<%=([\s\S]+?)%>/g
+ };
+
+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ _.template = function(str, data) {
+ var c = _.templateSettings;
+ var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
+ 'with(obj||{}){__p.push(\'' +
+ str.replace(/\\/g, '\\\\')
+ .replace(/'/g, "\\'")
+ .replace(c.interpolate, function(match, code) {
+ return "'," + code.replace(/\\'/g, "'") + ",'";
+ })
+ .replace(c.evaluate || null, function(match, code) {
+ return "');" + code.replace(/\\'/g, "'")
+ .replace(/[\r\n\t]/g, ' ') + "__p.push('";
+ })
+ .replace(/\r/g, '\\r')
+ .replace(/\n/g, '\\n')
+ .replace(/\t/g, '\\t')
+ + "');}return __p.join('');";
+ var func = new Function('obj', tmpl);
+ return data ? func(data) : func;
+ };
+
+ // The OOP Wrapper
+ // ---------------
+
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+ var wrapper = function(obj) { this._wrapped = obj; };
+
+ // Expose `wrapper.prototype` as `_.prototype`
+ _.prototype = wrapper.prototype;
+
+ // Helper function to continue chaining intermediate results.
+ var result = function(obj, chain) {
+ return chain ? _(obj).chain() : obj;
+ };
+
+ // A method to easily add functions to the OOP wrapper.
+ var addToWrapper = function(name, func) {
+ wrapper.prototype[name] = function() {
+ var args = slice.call(arguments);
+ unshift.call(args, this._wrapped);
+ return result(func.apply(_, args), this._chain);
+ };
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.mixin(_);
+
+ // Add all mutator Array functions to the wrapper.
+ each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ var method = ArrayProto[name];
+ wrapper.prototype[name] = function() {
+ method.apply(this._wrapped, arguments);
+ return result(this._wrapped, this._chain);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ each(['concat', 'join', 'slice'], function(name) {
+ var method = ArrayProto[name];
+ wrapper.prototype[name] = function() {
+ return result(method.apply(this._wrapped, arguments), this._chain);
+ };
+ });
+
+ // Start chaining a wrapped Underscore object.
+ wrapper.prototype.chain = function() {
+ this._chain = true;
+ return this;
+ };
+
+ // Extracts the result from a wrapped and chained object.
+ wrapper.prototype.value = function() {
+ return this._wrapped;
+ };
+
+})();
+/**
+ * Flotr2 (c) 2012 Carl Sutherland
+ * MIT License
+ * Special thanks to:
+ * Flotr: http://code.google.com/p/flotr/ (fork)
+ * Flot: https://github.com/flot/flot (original fork)
+ */
+(function () {
+
+var
+ global = this,
+ previousFlotr = this.Flotr,
+ Flotr;
+
+Flotr = {
+ _: _,
+ bean: bean,
+ isIphone: /iphone/i.test(navigator.userAgent),
+ isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false),
+
+ /**
+ * An object of the registered graph types. Use Flotr.addType(type, object)
+ * to add your own type.
+ */
+ graphTypes: {},
+
+ /**
+ * The list of the registered plugins
+ */
+ plugins: {},
+
+ /**
+ * Can be used to add your own chart type.
+ * @param {String} name - Type of chart, like 'pies', 'bars' etc.
+ * @param {String} graphType - The object containing the basic drawing functions (draw, etc)
+ */
+ addType: function(name, graphType){
+ Flotr.graphTypes[name] = graphType;
+ Flotr.defaultOptions[name] = graphType.options || {};
+ Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name;
+ },
+
+ /**
+ * Can be used to add a plugin
+ * @param {String} name - The name of the plugin
+ * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...)
+ */
+ addPlugin: function(name, plugin){
+ Flotr.plugins[name] = plugin;
+ Flotr.defaultOptions[name] = plugin.options || {};
+ },
+
+ /**
+ * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha.
+ * You could also draw graphs by directly calling Flotr.Graph(element, data, options).
+ * @param {Element} el - element to insert the graph into
+ * @param {Object} data - an array or object of dataseries
+ * @param {Object} options - an object containing options
+ * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph
+ * @return {Object} returns a new graph object and of course draws the graph.
+ */
+ draw: function(el, data, options, GraphKlass){
+ GraphKlass = GraphKlass || Flotr.Graph;
+ return new GraphKlass(el, data, options);
+ },
+
+ /**
+ * Recursively merges two objects.
+ * @param {Object} src - source object (likely the object with the least properties)
+ * @param {Object} dest - destination object (optional, object with the most properties)
+ * @return {Object} recursively merged Object
+ * @TODO See if we can't remove this.
+ */
+ merge: function(src, dest){
+ var i, v, result = dest || {};
+
+ for (i in src) {
+ v = src[i];
+ if (v && typeof(v) === 'object') {
+ if (v.constructor === Array) {
+ result[i] = this._.clone(v);
+ } else if (v.constructor !== RegExp && !this._.isElement(v)) {
+ result[i] = Flotr.merge(v, (dest ? dest[i] : undefined));
+ } else {
+ result[i] = v;
+ }
+ } else {
+ result[i] = v;
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Recursively clones an object.
+ * @param {Object} object - The object to clone
+ * @return {Object} the clone
+ * @TODO See if we can't remove this.
+ */
+ clone: function(object){
+ return Flotr.merge(object, {});
+ },
+
+ /**
+ * Function calculates the ticksize and returns it.
+ * @param {Integer} noTicks - number of ticks
+ * @param {Integer} min - lower bound integer value for the current axis
+ * @param {Integer} max - upper bound integer value for the current axis
+ * @param {Integer} decimals - number of decimals for the ticks
+ * @return {Integer} returns the ticksize in pixels
+ */
+ getTickSize: function(noTicks, min, max, decimals){
+ var delta = (max - min) / noTicks,
+ magn = Flotr.getMagnitude(delta),
+ tickSize = 10,
+ norm = delta / magn; // Norm is between 1.0 and 10.0.
+
+ if(norm < 1.5) tickSize = 1;
+ else if(norm < 2.25) tickSize = 2;
+ else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5);
+ else if(norm < 7.5) tickSize = 5;
+
+ return tickSize * magn;
+ },
+
+ /**
+ * Default tick formatter.
+ * @param {String, Integer} val - tick value integer
+ * @param {Object} axisOpts - the axis' options
+ * @return {String} formatted tick string
+ */
+ defaultTickFormatter: function(val, axisOpts){
+ return val+'';
+ },
+
+ /**
+ * Formats the mouse tracker values.
+ * @param {Object} obj - Track value Object {x:..,y:..}
+ * @return {String} Formatted track string
+ */
+ defaultTrackFormatter: function(obj){
+ return '('+obj.x+', '+obj.y+')';
+ },
+
+ /**
+ * Utility function to convert file size values in bytes to kB, MB, ...
+ * @param value {Number} - The value to convert
+ * @param precision {Number} - The number of digits after the comma (default: 2)
+ * @param base {Number} - The base (default: 1000)
+ */
+ engineeringNotation: function(value, precision, base){
+ var sizes = ['Y','Z','E','P','T','G','M','k',''],
+ fractionSizes = ['y','z','a','f','p','n','µ','m',''],
+ total = sizes.length;
+
+ base = base || 1000;
+ precision = Math.pow(10, precision || 2);
+
+ if (value === 0) return 0;
+
+ if (value > 1) {
+ while (total-- && (value >= base)) value /= base;
+ }
+ else {
+ sizes = fractionSizes;
+ total = sizes.length;
+ while (total-- && (value < 1)) value *= base;
+ }
+
+ return (Math.round(value * precision) / precision) + sizes[total];
+ },
+
+ /**
+ * Returns the magnitude of the input value.
+ * @param {Integer, Float} x - integer or float value
+ * @return {Integer, Float} returns the magnitude of the input value
+ */
+ getMagnitude: function(x){
+ return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
+ },
+ toPixel: function(val){
+ return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val);
+ },
+ toRad: function(angle){
+ return -angle * (Math.PI/180);
+ },
+ floorInBase: function(n, base) {
+ return base * Math.floor(n / base);
+ },
+ drawText: function(ctx, text, x, y, style) {
+ if (!ctx.fillText) {
+ ctx.drawText(text, x, y, style);
+ return;
+ }
+
+ style = this._.extend({
+ size: Flotr.defaultOptions.fontSize,
+ color: '#000000',
+ textAlign: 'left',
+ textBaseline: 'bottom',
+ weight: 1,
+ angle: 0
+ }, style);
+
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.rotate(style.angle);
+ ctx.fillStyle = style.color;
+ ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
+ ctx.textAlign = style.textAlign;
+ ctx.textBaseline = style.textBaseline;
+ ctx.fillText(text, 0, 0);
+ ctx.restore();
+ },
+ getBestTextAlign: function(angle, style) {
+ style = style || {textAlign: 'center', textBaseline: 'middle'};
+ angle += Flotr.getTextAngleFromAlign(style);
+
+ if (Math.abs(Math.cos(angle)) > 10e-3)
+ style.textAlign = (Math.cos(angle) > 0 ? 'right' : 'left');
+
+ if (Math.abs(Math.sin(angle)) > 10e-3)
+ style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom');
+
+ return style;
+ },
+ alignTable: {
+ 'right middle' : 0,
+ 'right top' : Math.PI/4,
+ 'center top' : Math.PI/2,
+ 'left top' : 3*(Math.PI/4),
+ 'left middle' : Math.PI,
+ 'left bottom' : -3*(Math.PI/4),
+ 'center bottom': -Math.PI/2,
+ 'right bottom' : -Math.PI/4,
+ 'center middle': 0
+ },
+ getTextAngleFromAlign: function(style) {
+ return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0;
+ },
+ noConflict : function () {
+ global.Flotr = previousFlotr;
+ return this;
+ }
+};
+
+global.Flotr = Flotr;
+
+})();
+
+/**
+ * Flotr Defaults
+ */
+Flotr.defaultOptions = {
+ colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.
+ ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping
+ title: null, // => The graph's title
+ subtitle: null, // => The graph's subtitle
+ shadowSize: 4, // => size of the 'fake' shadow
+ defaultType: null, // => default series type
+ HtmlText: true, // => wether to draw the text using HTML or on the canvas
+ fontColor: '#545454', // => default font color
+ fontSize: 7.5, // => canvas' text font size
+ resolution: 1, // => resolution of the graph, to have printer-friendly graphs !
+ parseFloat: true, // => whether to preprocess data for floats (ie. if input is string)
+ xaxis: {
+ ticks: null, // => format: either [1, 3] or [[1, 'a'], 3]
+ minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3]
+ showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise
+ showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
+ labelsAngle: 0, // => labels' angle, in degrees
+ title: null, // => axis title
+ titleAngle: 0, // => axis title's angle, in degrees
+ noTicks: 5, // => number of ticks for automagically generated ticks
+ minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks
+ tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
+ tickDecimals: null, // => no. of decimals, null means auto
+ min: null, // => min. value to show, null means set automatically
+ max: null, // => max. value to show, null means set automatically
+ autoscale: false, // => Turns autoscaling on with true
+ autoscaleMargin: 0, // => margin in % to add if auto-setting min/max
+ color: null, // => color of the ticks
+ mode: 'normal', // => can be 'time' or 'normal'
+ timeFormat: null,
+ timeMode:'UTC', // => For UTC time ('local' for local time).
+ timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year)
+ scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic'
+ base: Math.E,
+ titleAlign: 'center',
+ margin: true // => Turn off margins with false
+ },
+ x2axis: {},
+ yaxis: {
+ ticks: null, // => format: either [1, 3] or [[1, 'a'], 3]
+ minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3]
+ showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise
+ showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
+ labelsAngle: 0, // => labels' angle, in degrees
+ title: null, // => axis title
+ titleAngle: 90, // => axis title's angle, in degrees
+ noTicks: 5, // => number of ticks for automagically generated ticks
+ minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks
+ tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
+ tickDecimals: null, // => no. of decimals, null means auto
+ min: null, // => min. value to show, null means set automatically
+ max: null, // => max. value to show, null means set automatically
+ autoscale: false, // => Turns autoscaling on with true
+ autoscaleMargin: 0, // => margin in % to add if auto-setting min/max
+ color: null, // => The color of the ticks
+ scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic'
+ base: Math.E,
+ titleAlign: 'center',
+ margin: true // => Turn off margins with false
+ },
+ y2axis: {
+ titleAngle: 270
+ },
+ grid: {
+ color: '#545454', // => primary color used for outline and labels
+ backgroundColor: null, // => null for transparent, else color
+ backgroundImage: null, // => background image. String or object with src, left and top
+ watermarkAlpha: 0.4, // =>
+ tickColor: '#DDDDDD', // => color used for the ticks
+ labelMargin: 3, // => margin in pixels
+ verticalLines: true, // => whether to show gridlines in vertical direction
+ minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir.
+ horizontalLines: true, // => whether to show gridlines in horizontal direction
+ minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir.
+ outlineWidth: 1, // => width of the grid outline/border in pixels
+ outline : 'nsew', // => walls of the outline to display
+ circular: false // => if set to true, the grid will be circular, must be used when radars are drawn
+ },
+ mouse: {
+ track: false, // => true to track the mouse, no tracking otherwise
+ trackAll: false,
+ position: 'se', // => position of the value box (default south-east)
+ relative: false, // => next to the mouse cursor
+ trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box
+ margin: 5, // => margin in pixels of the valuebox
+ lineColor: '#FF3F19', // => line color of points that are drawn when mouse comes near a value of a series
+ trackDecimals: 1, // => decimals for the track values
+ sensibility: 2, // => the lower this number, the more precise you have to aim to show a value
+ trackY: true, // => whether or not to track the mouse in the y axis
+ radius: 3, // => radius of the track point
+ fillColor: null, // => color to fill our select bar with only applies to bar and similar graphs (only bars for now)
+ fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ }
+};
+
+/**
+ * Flotr Color
+ */
+
+(function () {
+
+var
+ _ = Flotr._;
+
+// Constructor
+function Color (r, g, b, a) {
+ this.rgba = ['r','g','b','a'];
+ var x = 4;
+ while(-1<--x){
+ this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
+ }
+ this.normalize();
+}
+
+// Constants
+var COLOR_NAMES = {
+ 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]
+};
+
+Color.prototype = {
+ scale: function(rf, gf, bf, af){
+ var x = 4;
+ while (-1 < --x) {
+ if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x];
+ }
+ return this.normalize();
+ },
+ alpha: function(alpha) {
+ if (!_.isUndefined(alpha) && !_.isNull(alpha)) {
+ this.a = alpha;
+ }
+ return this.normalize();
+ },
+ clone: function(){
+ return new Color(this.r, this.b, this.g, this.a);
+ },
+ limit: function(val,minVal,maxVal){
+ return Math.max(Math.min(val, maxVal), minVal);
+ },
+ normalize: function(){
+ var limit = this.limit;
+ this.r = limit(parseInt(this.r, 10), 0, 255);
+ this.g = limit(parseInt(this.g, 10), 0, 255);
+ this.b = limit(parseInt(this.b, 10), 0, 255);
+ this.a = limit(this.a, 0, 1);
+ return this;
+ },
+ distance: function(color){
+ if (!color) return;
+ color = new Color.parse(color);
+ var dist = 0, x = 3;
+ while(-1<--x){
+ dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]);
+ }
+ return dist;
+ },
+ toString: function(){
+ return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')';
+ },
+ contrast: function () {
+ var
+ test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255;
+ return (test < 0.5 ? '#000000' : '#ffffff');
+ }
+};
+
+_.extend(Color, {
+ /**
+ * Parses a color string and returns a corresponding Color.
+ * The different tests are in order of probability to improve speed.
+ * @param {String, Color} str - string thats representing a color
+ * @return {Color} returns a Color object or false
+ */
+ parse: function(color){
+ if (color instanceof Color) return color;
+
+ var result;
+
+ // #a0b1c2
+ if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)))
+ return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
+
+ // rgb(num,num,num)
+ if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)))
+ return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
+
+ // #fff
+ if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)))
+ return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));
+
+ // rgba(num,num,num,num)
+ if((result = /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(color)))
+ return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
+
+ // rgb(num%,num%,num%)
+ if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)))
+ return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
+
+ // rgba(num%,num%,num%,num)
+ if((result = /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(color)))
+ return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
+
+ // Otherwise, we're most likely dealing with a named color.
+ var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase();
+ if(name == 'transparent'){
+ return new Color(255, 255, 255, 0);
+ }
+ return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0);
+ },
+
+ /**
+ * Process color and options into color style.
+ */
+ processColor: function(color, options) {
+
+ var opacity = options.opacity;
+ if (!color) return 'rgba(0, 0, 0, 0)';
+ if (color instanceof Color) return color.alpha(opacity).toString();
+ if (_.isString(color)) return Color.parse(color).alpha(opacity).toString();
+
+ var grad = color.colors ? color : {colors: color};
+
+ if (!options.ctx) {
+ if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)';
+ return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString();
+ }
+ grad = _.extend({start: 'top', end: 'bottom'}, grad);
+
+ if (/top/i.test(grad.start)) options.x1 = 0;
+ if (/left/i.test(grad.start)) options.y1 = 0;
+ if (/bottom/i.test(grad.end)) options.x2 = 0;
+ if (/right/i.test(grad.end)) options.y2 = 0;
+
+ var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2);
+ for (i = 0; i < grad.colors.length; i++) {
+ c = grad.colors[i];
+ if (_.isArray(c)) {
+ stop = c[0];
+ c = c[1];
+ }
+ else stop = i / (grad.colors.length-1);
+ gradient.addColorStop(stop, Color.parse(c).alpha(opacity));
+ }
+ return gradient;
+ }
+});
+
+Flotr.Color = Color;
+
+})();
+
+/**
+ * Flotr Date
+ */
+Flotr.Date = {
+
+ set : function (date, name, mode, value) {
+ mode = mode || 'UTC';
+ name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name;
+ date[name](value);
+ },
+
+ get : function (date, name, mode) {
+ mode = mode || 'UTC';
+ name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name;
+ return date[name]();
+ },
+
+ format: function(d, format, mode) {
+ if (!d) return;
+
+ // We should maybe use an "official" date format spec, like PHP date() or ColdFusion
+ // http://fr.php.net/manual/en/function.date.php
+ // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html
+ var
+ get = this.get,
+ tokens = {
+ h: get(d, 'Hours', mode).toString(),
+ H: leftPad(get(d, 'Hours', mode)),
+ M: leftPad(get(d, 'Minutes', mode)),
+ S: leftPad(get(d, 'Seconds', mode)),
+ s: get(d, 'Milliseconds', mode),
+ d: get(d, 'Date', mode).toString(),
+ m: (get(d, 'Month') + 1).toString(),
+ y: get(d, 'FullYear').toString(),
+ b: Flotr.Date.monthNames[get(d, 'Month', mode)]
+ };
+
+ function leftPad(n){
+ n += '';
+ return n.length == 1 ? "0" + n : n;
+ }
+
+ var r = [], c,
+ escape = false;
+
+ for (var i = 0; i < format.length; ++i) {
+ c = format.charAt(i);
+
+ if (escape) {
+ r.push(tokens[c] || c);
+ escape = false;
+ }
+ else if (c == "%")
+ escape = true;
+ else
+ r.push(c);
+ }
+ return r.join('');
+ },
+ getFormat: function(time, span) {
+ var tu = Flotr.Date.timeUnits;
+ if (time < tu.second) return "%h:%M:%S.%s";
+ else if (time < tu.minute) return "%h:%M:%S";
+ else if (time < tu.day) return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M";
+ else if (time < tu.month) return "%b %d";
+ else if (time < tu.year) return (span < tu.year) ? "%b" : "%b %y";
+ else return "%y";
+ },
+ formatter: function (v, axis) {
+ var
+ options = axis.options,
+ scale = Flotr.Date.timeUnits[options.timeUnit],
+ d = new Date(v * scale);
+
+ // first check global format
+ if (axis.options.timeFormat)
+ return Flotr.Date.format(d, options.timeFormat, options.timeMode);
+
+ var span = (axis.max - axis.min) * scale,
+ t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit];
+
+ return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode);
+ },
+ generator: function(axis) {
+
+ var
+ set = this.set,
+ get = this.get,
+ timeUnits = this.timeUnits,
+ spec = this.spec,
+ options = axis.options,
+ mode = options.timeMode,
+ scale = timeUnits[options.timeUnit],
+ min = axis.min * scale,
+ max = axis.max * scale,
+ delta = (max - min) / options.noTicks,
+ ticks = [],
+ tickSize = axis.tickSize,
+ tickUnit,
+ formatter, i;
+
+ // Use custom formatter or time tick formatter
+ formatter = (options.tickFormatter === Flotr.defaultTickFormatter ?
+ this.formatter : options.tickFormatter
+ );
+
+ for (i = 0; i < spec.length - 1; ++i) {
+ var d = spec[i][0] * timeUnits[spec[i][1]];
+ if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize)
+ break;
+ }
+ tickSize = spec[i][0];
+ tickUnit = spec[i][1];
+
+ // special-case the possibility of several years
+ if (tickUnit == "year") {
+ tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0);
+
+ // Fix for 0.5 year case
+ if (tickSize == 0.5) {
+ tickUnit = "month";
+ tickSize = 6;
+ }
+ }
+
+ axis.tickUnit = tickUnit;
+ axis.tickSize = tickSize;
+
+ var
+ d = new Date(min);
+
+ var step = tickSize * timeUnits[tickUnit];
+
+ function setTick (name) {
+ set(d, name, mode, Flotr.floorInBase(
+ get(d, name, mode), tickSize
+ ));
+ }
+
+ switch (tickUnit) {
+ case "millisecond": setTick('Milliseconds'); break;
+ case "second": setTick('Seconds'); break;
+ case "minute": setTick('Minutes'); break;
+ case "hour": setTick('Hours'); break;
+ case "month": setTick('Month'); break;
+ case "year": setTick('FullYear'); break;
+ }
+
+ // reset smaller components
+ if (step >= timeUnits.second) set(d, 'Milliseconds', mode, 0);
+ if (step >= timeUnits.minute) set(d, 'Seconds', mode, 0);
+ if (step >= timeUnits.hour) set(d, 'Minutes', mode, 0);
+ if (step >= timeUnits.day) set(d, 'Hours', mode, 0);
+ if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1);
+ if (step >= timeUnits.year) set(d, 'Month', mode, 0);
+
+ var carry = 0, v = NaN, prev;
+ do {
+ prev = v;
+ v = d.getTime();
+ ticks.push({ v: v / scale, label: formatter(v / scale, axis) });
+ if (tickUnit == "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 */
+ set(d, 'Date', mode, 1);
+ var start = d.getTime();
+ set(d, 'Month', mode, get(d, 'Month', mode) + 1)
+ var end = d.getTime();
+ d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize);
+ carry = get(d, 'Hours', mode)
+ set(d, 'Hours', mode, 0);
+ }
+ else
+ set(d, 'Month', mode, get(d, 'Month', mode) + tickSize);
+ }
+ else if (tickUnit == "year") {
+ set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize);
+ }
+ else
+ d.setTime(v + step);
+
+ } while (v < max && v != prev);
+
+ return ticks;
+ },
+ timeUnits: {
+ millisecond: 1,
+ second: 1000,
+ minute: 1000 * 60,
+ hour: 1000 * 60 * 60,
+ day: 1000 * 60 * 60 * 24,
+ month: 1000 * 60 * 60 * 24 * 30,
+ year: 1000 * 60 * 60 * 24 * 365.2425
+ },
+ // the allowed tick sizes, after 1 year we use an integer algorithm
+ spec: [
+ [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"],
+ [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"]
+ ],
+ monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+};
+
+(function () {
+
+var _ = Flotr._;
+
+Flotr.DOM = {
+ addClass: function(element, name){
+ var classList = (element.className ? element.className : '');
+ if (_.include(classList.split(/\s+/g), name)) return;
+ element.className = (classList ? classList + ' ' : '') + name;
+ },
+ /**
+ * Create an element.
+ */
+ create: function(tag){
+ return document.createElement(tag);
+ },
+ node: function(html) {
+ var div = Flotr.DOM.create('div'), n;
+ div.innerHTML = html;
+ n = div.children[0];
+ div.innerHTML = '';
+ return n;
+ },
+ /**
+ * Remove all children.
+ */
+ empty: function(element){
+ element.innerHTML = '';
+ /*
+ if (!element) return;
+ _.each(element.childNodes, function (e) {
+ Flotr.DOM.empty(e);
+ element.removeChild(e);
+ });
+ */
+ },
+ hide: function(element){
+ Flotr.DOM.setStyles(element, {display:'none'});
+ },
+ /**
+ * Insert a child.
+ * @param {Element} element
+ * @param {Element|String} Element or string to be appended.
+ */
+ insert: function(element, child){
+ if(_.isString(child))
+ element.innerHTML += child;
+ else if (_.isElement(child))
+ element.appendChild(child);
+ },
+ // @TODO find xbrowser implementation
+ opacity: function(element, opacity) {
+ element.style.opacity = opacity;
+ },
+ position: function(element, p){
+ if (!element.offsetParent)
+ return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)};
+
+ p = this.position(element.offsetParent);
+ p.left += element.offsetLeft;
+ p.top += element.offsetTop;
+ return p;
+ },
+ removeClass: function(element, name) {
+ var classList = (element.className ? element.className : '');
+ element.className = _.filter(classList.split(/\s+/g), function (c) {
+ if (c != name) return true; }
+ ).join(' ');
+ },
+ setStyles: function(element, o) {
+ _.each(o, function (value, key) {
+ element.style[key] = value;
+ });
+ },
+ show: function(element){
+ Flotr.DOM.setStyles(element, {display:''});
+ },
+ /**
+ * Return element size.
+ */
+ size: function(element){
+ return {
+ height : element.offsetHeight,
+ width : element.offsetWidth };
+ }
+};
+
+})();
+
+/**
+ * Flotr Event Adapter
+ */
+(function () {
+var
+ F = Flotr,
+ bean = F.bean;
+F.EventAdapter = {
+ observe: function(object, name, callback) {
+ bean.add(object, name, callback);
+ return this;
+ },
+ fire: function(object, name, args) {
+ bean.fire(object, name, args);
+ if (typeof(Prototype) != 'undefined')
+ Event.fire(object, name, args);
+ // @TODO Someone who uses mootools, add mootools adapter for existing applciations.
+ return this;
+ },
+ stopObserving: function(object, name, callback) {
+ bean.remove(object, name, callback);
+ return this;
+ },
+ eventPointer: function(e) {
+ if (!F._.isUndefined(e.touches) && e.touches.length > 0) {
+ return {
+ x : e.touches[0].pageX,
+ y : e.touches[0].pageY
+ };
+ } else if (!F._.isUndefined(e.changedTouches) && e.changedTouches.length > 0) {
+ return {
+ x : e.changedTouches[0].pageX,
+ y : e.changedTouches[0].pageY
+ };
+ } else if (e.pageX || e.pageY) {
+ return {
+ x : e.pageX,
+ y : e.pageY
+ };
+ } else if (e.clientX || e.clientY) {
+ var
+ d = document,
+ b = d.body,
+ de = d.documentElement;
+ return {
+ x: e.clientX + b.scrollLeft + de.scrollLeft,
+ y: e.clientY + b.scrollTop + de.scrollTop
+ };
+ }
+ }
+};
+})();
+
+/**
+ * Text Utilities
+ */
+(function () {
+
+var
+ F = Flotr,
+ D = F.DOM,
+ _ = F._,
+
+Text = function (o) {
+ this.o = o;
+};
+
+Text.prototype = {
+
+ dimensions : function (text, canvasStyle, htmlStyle, className) {
+
+ if (!text) return { width : 0, height : 0 };
+
+ return (this.o.html) ?
+ this.html(text, this.o.element, htmlStyle, className) :
+ this.canvas(text, canvasStyle);
+ },
+
+ canvas : function (text, style) {
+
+ if (!this.o.textEnabled) return;
+ style = style || {};
+
+ var
+ metrics = this.measureText(text, style),
+ width = metrics.width,
+ height = style.size || F.defaultOptions.fontSize,
+ angle = style.angle || 0,
+ cosAngle = Math.cos(angle),
+ sinAngle = Math.sin(angle),
+ widthPadding = 2,
+ heightPadding = 6,
+ bounds;
+
+ bounds = {
+ width: Math.abs(cosAngle * width) + Math.abs(sinAngle * height) + widthPadding,
+ height: Math.abs(sinAngle * width) + Math.abs(cosAngle * height) + heightPadding
+ };
+
+ return bounds;
+ },
+
+ html : function (text, element, style, className) {
+
+ var div = D.create('div');
+
+ D.setStyles(div, { 'position' : 'absolute', 'top' : '-10000px' });
+ D.insert(div, '' + text + '
');
+ D.insert(this.o.element, div);
+
+ return D.size(div);
+ },
+
+ measureText : function (text, style) {
+
+ var
+ context = this.o.ctx,
+ metrics;
+
+ if (!context.fillText || (F.isIphone && context.measure)) {
+ return { width : context.measure(text, style)};
+ }
+
+ style = _.extend({
+ size: F.defaultOptions.fontSize,
+ weight: 1,
+ angle: 0
+ }, style);
+
+ context.save();
+ context.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
+ metrics = context.measureText(text);
+ context.restore();
+
+ return metrics;
+ }
+};
+
+Flotr.Text = Text;
+
+})();
+
+/**
+ * Flotr Graph class that plots a graph on creation.
+ */
+(function () {
+
+var
+ D = Flotr.DOM,
+ E = Flotr.EventAdapter,
+ _ = Flotr._,
+ flotr = Flotr;
+/**
+ * Flotr Graph constructor.
+ * @param {Element} el - element to insert the graph into
+ * @param {Object} data - an array or object of dataseries
+ * @param {Object} options - an object containing options
+ */
+Graph = function(el, data, options){
+// Let's see if we can get away with out this [JS]
+// try {
+ this._setEl(el);
+ this._initMembers();
+ this._initPlugins();
+
+ E.fire(this.el, 'flotr:beforeinit', [this]);
+
+ this.data = data;
+ this.series = flotr.Series.getSeries(data);
+ this._initOptions(options);
+ this._initGraphTypes();
+ this._initCanvas();
+ this._text = new flotr.Text({
+ element : this.el,
+ ctx : this.ctx,
+ html : this.options.HtmlText,
+ textEnabled : this.textEnabled
+ });
+ E.fire(this.el, 'flotr:afterconstruct', [this]);
+ this._initEvents();
+
+ this.findDataRanges();
+ this.calculateSpacing();
+
+ this.draw(_.bind(function() {
+ E.fire(this.el, 'flotr:afterinit', [this]);
+ }, this));
+/*
+ try {
+ } catch (e) {
+ try {
+ console.error(e);
+ } catch (e2) {}
+ }*/
+};
+
+function observe (object, name, callback) {
+ E.observe.apply(this, arguments);
+ this._handles.push(arguments);
+ return this;
+}
+
+Graph.prototype = {
+
+ destroy: function () {
+ E.fire(this.el, 'flotr:destroy');
+ _.each(this._handles, function (handle) {
+ E.stopObserving.apply(this, handle);
+ });
+ this._handles = [];
+ this.el.graph = null;
+ },
+
+ observe : observe,
+
+ /**
+ * @deprecated
+ */
+ _observe : observe,
+
+ processColor: function(color, options){
+ var o = { x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx };
+ _.extend(o, options);
+ return flotr.Color.processColor(color, o);
+ },
+ /**
+ * Function determines the min and max values for the xaxis and yaxis.
+ *
+ * TODO logarithmic range validation (consideration of 0)
+ */
+ findDataRanges: function(){
+ var a = this.axes,
+ xaxis, yaxis, range;
+
+ _.each(this.series, function (series) {
+ range = series.getRange();
+ if (range) {
+ xaxis = series.xaxis;
+ yaxis = series.yaxis;
+ xaxis.datamin = Math.min(range.xmin, xaxis.datamin);
+ xaxis.datamax = Math.max(range.xmax, xaxis.datamax);
+ yaxis.datamin = Math.min(range.ymin, yaxis.datamin);
+ yaxis.datamax = Math.max(range.ymax, yaxis.datamax);
+ xaxis.used = (xaxis.used || range.xused);
+ yaxis.used = (yaxis.used || range.yused);
+ }
+ }, this);
+
+ // Check for empty data, no data case (none used)
+ if (!a.x.used && !a.x2.used) a.x.used = true;
+ if (!a.y.used && !a.y2.used) a.y.used = true;
+
+ _.each(a, function (axis) {
+ axis.calculateRange();
+ });
+
+ var
+ types = _.keys(flotr.graphTypes),
+ drawn = false;
+
+ _.each(this.series, function (series) {
+ if (series.hide) return;
+ _.each(types, function (type) {
+ if (series[type] && series[type].show) {
+ this.extendRange(type, series);
+ drawn = true;
+ }
+ }, this);
+ if (!drawn) {
+ this.extendRange(this.options.defaultType, series);
+ }
+ }, this);
+ },
+
+ extendRange : function (type, series) {
+ if (this[type].extendRange) this[type].extendRange(series, series.data, series[type], this[type]);
+ if (this[type].extendYRange) this[type].extendYRange(series.yaxis, series.data, series[type], this[type]);
+ if (this[type].extendXRange) this[type].extendXRange(series.xaxis, series.data, series[type], this[type]);
+ },
+
+ /**
+ * Calculates axis label sizes.
+ */
+ calculateSpacing: function(){
+
+ var a = this.axes,
+ options = this.options,
+ series = this.series,
+ margin = options.grid.labelMargin,
+ T = this._text,
+ x = a.x,
+ x2 = a.x2,
+ y = a.y,
+ y2 = a.y2,
+ maxOutset = options.grid.outlineWidth,
+ i, j, l, dim;
+
+ // TODO post refactor, fix this
+ _.each(a, function (axis) {
+ axis.calculateTicks();
+ axis.calculateTextDimensions(T, options);
+ });
+
+ // Title height
+ dim = T.dimensions(
+ options.title,
+ {size: options.fontSize*1.5},
+ 'font-size:1em;font-weight:bold;',
+ 'flotr-title'
+ );
+ this.titleHeight = dim.height;
+
+ // Subtitle height
+ dim = T.dimensions(
+ options.subtitle,
+ {size: options.fontSize},
+ 'font-size:smaller;',
+ 'flotr-subtitle'
+ );
+ this.subtitleHeight = dim.height;
+
+ for(j = 0; j < options.length; ++j){
+ if (series[j].points.show){
+ maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2);
+ }
+ }
+
+ var p = this.plotOffset;
+ if (x.options.margin === false) {
+ p.bottom = 0;
+ p.top = 0;
+ } else {
+ p.bottom += (options.grid.circular ? 0 : (x.used && x.options.showLabels ? (x.maxLabel.height + margin) : 0)) +
+ (x.used && x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset;
+
+ p.top += (options.grid.circular ? 0 : (x2.used && x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) +
+ (x2.used && x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset;
+ }
+ if (y.options.margin === false) {
+ p.left = 0;
+ p.right = 0;
+ } else {
+ p.left += (options.grid.circular ? 0 : (y.used && y.options.showLabels ? (y.maxLabel.width + margin) : 0)) +
+ (y.used && y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset;
+
+ p.right += (options.grid.circular ? 0 : (y2.used && y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) +
+ (y2.used && y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset;
+ }
+
+ p.top = Math.floor(p.top); // In order the outline not to be blured
+
+ this.plotWidth = this.canvasWidth - p.left - p.right;
+ this.plotHeight = this.canvasHeight - p.bottom - p.top;
+
+ // TODO post refactor, fix this
+ x.length = x2.length = this.plotWidth;
+ y.length = y2.length = this.plotHeight;
+ y.offset = y2.offset = this.plotHeight;
+ x.setScale();
+ x2.setScale();
+ y.setScale();
+ y2.setScale();
+ },
+ /**
+ * Draws grid, labels, series and outline.
+ */
+ draw: function(after) {
+
+ var
+ context = this.ctx,
+ i;
+
+ E.fire(this.el, 'flotr:beforedraw', [this.series, this]);
+
+ if (this.series.length) {
+
+ context.save();
+ context.translate(this.plotOffset.left, this.plotOffset.top);
+
+ for (i = 0; i < this.series.length; i++) {
+ if (!this.series[i].hide) this.drawSeries(this.series[i]);
+ }
+
+ context.restore();
+ this.clip();
+ }
+
+ E.fire(this.el, 'flotr:afterdraw', [this.series, this]);
+ if (after) after();
+ },
+ /**
+ * Actually draws the graph.
+ * @param {Object} series - series to draw
+ */
+ drawSeries: function(series){
+
+ function drawChart (series, typeKey) {
+ var options = this.getOptions(series, typeKey);
+ this[typeKey].draw(options);
+ }
+
+ var drawn = false;
+ series = series || this.series;
+
+ _.each(flotr.graphTypes, function (type, typeKey) {
+ if (series[typeKey] && series[typeKey].show && this[typeKey]) {
+ drawn = true;
+ drawChart.call(this, series, typeKey);
+ }
+ }, this);
+
+ if (!drawn) drawChart.call(this, series, this.options.defaultType);
+ },
+
+ getOptions : function (series, typeKey) {
+ var
+ type = series[typeKey],
+ graphType = this[typeKey],
+ options = {
+ context : this.ctx,
+ width : this.plotWidth,
+ height : this.plotHeight,
+ fontSize : this.options.fontSize,
+ fontColor : this.options.fontColor,
+ textEnabled : this.textEnabled,
+ htmlText : this.options.HtmlText,
+ text : this._text, // TODO Is this necessary?
+ element : this.el,
+ data : series.data,
+ color : series.color,
+ shadowSize : series.shadowSize,
+ xScale : _.bind(series.xaxis.d2p, series.xaxis),
+ yScale : _.bind(series.yaxis.d2p, series.yaxis)
+ };
+
+ options = flotr.merge(type, options);
+
+ // Fill
+ options.fillStyle = this.processColor(
+ type.fillColor || series.color,
+ {opacity: type.fillOpacity}
+ );
+
+ return options;
+ },
+ /**
+ * Calculates the coordinates from a mouse event object.
+ * @param {Event} event - Mouse Event object.
+ * @return {Object} Object with coordinates of the mouse.
+ */
+ getEventPosition: function (e){
+
+ var
+ d = document,
+ b = d.body,
+ de = d.documentElement,
+ axes = this.axes,
+ plotOffset = this.plotOffset,
+ lastMousePos = this.lastMousePos,
+ pointer = E.eventPointer(e),
+ dx = pointer.x - lastMousePos.pageX,
+ dy = pointer.y - lastMousePos.pageY,
+ r, rx, ry;
+
+ if ('ontouchstart' in this.el) {
+ r = D.position(this.overlay);
+ rx = pointer.x - r.left - plotOffset.left;
+ ry = pointer.y - r.top - plotOffset.top;
+ } else {
+ r = this.overlay.getBoundingClientRect();
+ rx = e.clientX - r.left - plotOffset.left - b.scrollLeft - de.scrollLeft;
+ ry = e.clientY - r.top - plotOffset.top - b.scrollTop - de.scrollTop;
+ }
+
+ return {
+ x: axes.x.p2d(rx),
+ x2: axes.x2.p2d(rx),
+ y: axes.y.p2d(ry),
+ y2: axes.y2.p2d(ry),
+ relX: rx,
+ relY: ry,
+ dX: dx,
+ dY: dy,
+ absX: pointer.x,
+ absY: pointer.y,
+ pageX: pointer.x,
+ pageY: pointer.y
+ };
+ },
+ /**
+ * Observes the 'click' event and fires the 'flotr:click' event.
+ * @param {Event} event - 'click' Event object.
+ */
+ clickHandler: function(event){
+ if(this.ignoreClick){
+ this.ignoreClick = false;
+ return this.ignoreClick;
+ }
+ E.fire(this.el, 'flotr:click', [this.getEventPosition(event), this]);
+ },
+ /**
+ * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event.
+ * @param {Event} event - 'mousemove' Event object.
+ */
+ mouseMoveHandler: function(event){
+ if (this.mouseDownMoveHandler) return;
+ var pos = this.getEventPosition(event);
+ E.fire(this.el, 'flotr:mousemove', [event, pos, this]);
+ this.lastMousePos = pos;
+ },
+ /**
+ * Observes the 'mousedown' event.
+ * @param {Event} event - 'mousedown' Event object.
+ */
+ mouseDownHandler: function (event){
+
+ /*
+ // @TODO Context menu?
+ if(event.isRightClick()) {
+ event.stop();
+
+ var overlay = this.overlay;
+ overlay.hide();
+
+ function cancelContextMenu () {
+ overlay.show();
+ E.stopObserving(document, 'mousemove', cancelContextMenu);
+ }
+ E.observe(document, 'mousemove', cancelContextMenu);
+ return;
+ }
+ */
+
+ if (this.mouseUpHandler) return;
+ this.mouseUpHandler = _.bind(function (e) {
+ E.stopObserving(document, 'mouseup', this.mouseUpHandler);
+ E.stopObserving(document, 'mousemove', this.mouseDownMoveHandler);
+ this.mouseDownMoveHandler = null;
+ this.mouseUpHandler = null;
+ // @TODO why?
+ //e.stop();
+ E.fire(this.el, 'flotr:mouseup', [e, this]);
+ }, this);
+ this.mouseDownMoveHandler = _.bind(function (e) {
+ var pos = this.getEventPosition(e);
+ E.fire(this.el, 'flotr:mousemove', [event, pos, this]);
+ this.lastMousePos = pos;
+ }, this);
+ E.observe(document, 'mouseup', this.mouseUpHandler);
+ E.observe(document, 'mousemove', this.mouseDownMoveHandler);
+ E.fire(this.el, 'flotr:mousedown', [event, this]);
+ this.ignoreClick = false;
+ },
+ drawTooltip: function(content, x, y, options) {
+ var mt = this.getMouseTrack(),
+ style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;',
+ p = options.position,
+ m = options.margin,
+ plotOffset = this.plotOffset;
+
+ if(x !== null && y !== null){
+ if (!options.relative) { // absolute to the canvas
+ if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;';
+ else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;';
+ if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;';
+ else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;';
+ }
+ else { // relative to the mouse
+ if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;';
+ else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;';
+ if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;';
+ else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;';
+ }
+
+ mt.style.cssText = style;
+ D.empty(mt);
+ D.insert(mt, content);
+ D.show(mt);
+ }
+ else {
+ D.hide(mt);
+ }
+ },
+
+ clip: function () {
+
+ var
+ ctx = this.ctx,
+ o = this.plotOffset,
+ w = this.canvasWidth,
+ h = this.canvasHeight;
+
+ if (flotr.isIE && flotr.isIE < 9) {
+ // Clipping for excanvas :-(
+ ctx.save();
+ ctx.fillStyle = this.processColor(this.options.ieBackgroundColor);
+ ctx.fillRect(0, 0, w, o.top);
+ ctx.fillRect(0, 0, o.left, h);
+ ctx.fillRect(0, h - o.bottom, w, o.bottom);
+ ctx.fillRect(w - o.right, 0, o.right,h);
+ ctx.restore();
+ } else {
+ ctx.clearRect(0, 0, w, o.top);
+ ctx.clearRect(0, 0, o.left, h);
+ ctx.clearRect(0, h - o.bottom, w, o.bottom);
+ ctx.clearRect(w - o.right, 0, o.right,h);
+ }
+ },
+
+ _initMembers: function() {
+ this._handles = [];
+ this.lastMousePos = {pageX: null, pageY: null };
+ this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0};
+ this.ignoreClick = true;
+ this.prevHit = null;
+ },
+
+ _initGraphTypes: function() {
+ _.each(flotr.graphTypes, function(handler, graphType){
+ this[graphType] = flotr.clone(handler);
+ }, this);
+ },
+
+ _initEvents: function () {
+
+ var
+ el = this.el,
+ touchendHandler, movement, touchend;
+
+ if ('ontouchstart' in el) {
+
+ touchendHandler = _.bind(function (e) {
+ touchend = true;
+ E.stopObserving(document, 'touchend', touchendHandler);
+ E.fire(el, 'flotr:mouseup', [event, this]);
+ this.multitouches = null;
+
+ if (!movement) {
+ this.clickHandler(e);
+ }
+ }, this);
+
+ this.observe(this.overlay, 'touchstart', _.bind(function (e) {
+ movement = false;
+ touchend = false;
+ this.ignoreClick = false;
+
+ if (e.touches && e.touches.length > 1) {
+ this.multitouches = e.touches;
+ }
+
+ E.fire(el, 'flotr:mousedown', [event, this]);
+ this.observe(document, 'touchend', touchendHandler);
+ }, this));
+
+ this.observe(this.overlay, 'touchmove', _.bind(function (e) {
+
+ var pos = this.getEventPosition(e);
+
+ e.preventDefault();
+
+ movement = true;
+
+ if (this.multitouches || (e.touches && e.touches.length > 1)) {
+ this.multitouches = e.touches;
+ } else {
+ if (!touchend) {
+ E.fire(el, 'flotr:mousemove', [event, pos, this]);
+ }
+ }
+ this.lastMousePos = pos;
+ }, this));
+
+ } else {
+ this.
+ observe(this.overlay, 'mousedown', _.bind(this.mouseDownHandler, this)).
+ observe(el, 'mousemove', _.bind(this.mouseMoveHandler, this)).
+ observe(this.overlay, 'click', _.bind(this.clickHandler, this)).
+ observe(el, 'mouseout', function () {
+ E.fire(el, 'flotr:mouseout');
+ });
+ }
+ },
+
+ /**
+ * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use
+ * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements
+ * are created, the elements are inserted into the container element.
+ */
+ _initCanvas: function(){
+ var el = this.el,
+ o = this.options,
+ children = el.children,
+ removedChildren = [],
+ child, i,
+ size, style;
+
+ // Empty the el
+ for (i = children.length; i--;) {
+ child = children[i];
+ if (!this.canvas && child.className === 'flotr-canvas') {
+ this.canvas = child;
+ } else if (!this.overlay && child.className === 'flotr-overlay') {
+ this.overlay = child;
+ } else {
+ removedChildren.push(child);
+ }
+ }
+ for (i = removedChildren.length; i--;) {
+ el.removeChild(removedChildren[i]);
+ }
+
+ D.setStyles(el, {position: 'relative'}); // For positioning labels and overlay.
+ size = {};
+ size.width = el.clientWidth;
+ size.height = el.clientHeight;
+
+ if(size.width <= 0 || size.height <= 0 || o.resolution <= 0){
+ throw 'Invalid dimensions for plot, width = ' + size.width + ', height = ' + size.height + ', resolution = ' + o.resolution;
+ }
+
+ // Main canvas for drawing graph types
+ this.canvas = getCanvas(this.canvas, 'canvas');
+ // Overlay canvas for interactive features
+ this.overlay = getCanvas(this.overlay, 'overlay');
+ this.ctx = getContext(this.canvas);
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ this.octx = getContext(this.overlay);
+ this.octx.clearRect(0, 0, this.overlay.width, this.overlay.height);
+ this.canvasHeight = size.height;
+ this.canvasWidth = size.width;
+ this.textEnabled = !!this.ctx.drawText || !!this.ctx.fillText; // Enable text functions
+
+ function getCanvas(canvas, name){
+ if(!canvas){
+ canvas = D.create('canvas');
+ if (typeof FlashCanvas != "undefined" && typeof canvas.getContext === 'function') {
+ FlashCanvas.initElement(canvas);
+ }
+ canvas.className = 'flotr-'+name;
+ canvas.style.cssText = 'position:absolute;left:0px;top:0px;';
+ D.insert(el, canvas);
+ }
+ _.each(size, function(size, attribute){
+ D.show(canvas);
+ if (name == 'canvas' && canvas.getAttribute(attribute) === size) {
+ return;
+ }
+ canvas.setAttribute(attribute, size * o.resolution);
+ canvas.style[attribute] = size + 'px';
+ });
+ canvas.context_ = null; // Reset the ExCanvas context
+ return canvas;
+ }
+
+ function getContext(canvas){
+ if(window.G_vmlCanvasManager) window.G_vmlCanvasManager.initElement(canvas); // For ExCanvas
+ var context = canvas.getContext('2d');
+ if(!window.G_vmlCanvasManager) context.scale(o.resolution, o.resolution);
+ return context;
+ }
+ },
+
+ _initPlugins: function(){
+ // TODO Should be moved to flotr and mixed in.
+ _.each(flotr.plugins, function(plugin, name){
+ _.each(plugin.callbacks, function(fn, c){
+ this.observe(this.el, c, _.bind(fn, this));
+ }, this);
+ this[name] = flotr.clone(plugin);
+ _.each(this[name], function(fn, p){
+ if (_.isFunction(fn))
+ this[name][p] = _.bind(fn, this);
+ }, this);
+ }, this);
+ },
+
+ /**
+ * Sets options and initializes some variables and color specific values, used by the constructor.
+ * @param {Object} opts - options object
+ */
+ _initOptions: function(opts){
+ var options = flotr.clone(flotr.defaultOptions);
+ options.x2axis = _.extend(_.clone(options.xaxis), options.x2axis);
+ options.y2axis = _.extend(_.clone(options.yaxis), options.y2axis);
+ this.options = flotr.merge(opts || {}, options);
+
+ if (this.options.grid.minorVerticalLines === null &&
+ this.options.xaxis.scaling === 'logarithmic') {
+ this.options.grid.minorVerticalLines = true;
+ }
+ if (this.options.grid.minorHorizontalLines === null &&
+ this.options.yaxis.scaling === 'logarithmic') {
+ this.options.grid.minorHorizontalLines = true;
+ }
+
+ E.fire(this.el, 'flotr:afterinitoptions', [this]);
+
+ this.axes = flotr.Axis.getAxes(this.options);
+
+ // Initialize some variables used throughout this function.
+ var assignedColors = [],
+ colors = [],
+ ln = this.series.length,
+ neededColors = this.series.length,
+ oc = this.options.colors,
+ usedColors = [],
+ variation = 0,
+ c, i, j, s;
+
+ // Collect user-defined colors from series.
+ for(i = neededColors - 1; i > -1; --i){
+ c = this.series[i].color;
+ if(c){
+ --neededColors;
+ if(_.isNumber(c)) assignedColors.push(c);
+ else usedColors.push(flotr.Color.parse(c));
+ }
+ }
+
+ // Calculate the number of colors that need to be generated.
+ for(i = assignedColors.length - 1; i > -1; --i)
+ neededColors = Math.max(neededColors, assignedColors[i] + 1);
+
+ // Generate needed number of colors.
+ for(i = 0; colors.length < neededColors;){
+ c = (oc.length == i) ? new flotr.Color(100, 100, 100) : flotr.Color.parse(oc[i]);
+
+ // Make sure each serie gets a different color.
+ var sign = variation % 2 == 1 ? -1 : 1,
+ factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
+ c.scale(factor, factor, factor);
+
+ /**
+ * @todo if we're getting too close to something else, we should probably skip this one
+ */
+ colors.push(c);
+
+ if(++i >= oc.length){
+ i = 0;
+ ++variation;
+ }
+ }
+
+ // Fill the options with the generated colors.
+ for(i = 0, j = 0; i < ln; ++i){
+ s = this.series[i];
+
+ // Assign the color.
+ if (!s.color){
+ s.color = colors[j++].toString();
+ }else if(_.isNumber(s.color)){
+ s.color = colors[s.color].toString();
+ }
+
+ // Every series needs an axis
+ if (!s.xaxis) s.xaxis = this.axes.x;
+ if (s.xaxis == 1) s.xaxis = this.axes.x;
+ else if (s.xaxis == 2) s.xaxis = this.axes.x2;
+
+ if (!s.yaxis) s.yaxis = this.axes.y;
+ if (s.yaxis == 1) s.yaxis = this.axes.y;
+ else if (s.yaxis == 2) s.yaxis = this.axes.y2;
+
+ // Apply missing options to the series.
+ for (var t in flotr.graphTypes){
+ s[t] = _.extend(_.clone(this.options[t]), s[t]);
+ }
+ s.mouse = _.extend(_.clone(this.options.mouse), s.mouse);
+
+ if (_.isUndefined(s.shadowSize)) s.shadowSize = this.options.shadowSize;
+ }
+ },
+
+ _setEl: function(el) {
+ if (!el) throw 'The target container doesn\'t exist';
+ else if (el.graph instanceof Graph) el.graph.destroy();
+ else if (!el.clientWidth) throw 'The target container must be visible';
+
+ el.graph = this;
+ this.el = el;
+ }
+};
+
+Flotr.Graph = Graph;
+
+})();
+
+/**
+ * Flotr Axis Library
+ */
+
+(function () {
+
+var
+ _ = Flotr._,
+ LOGARITHMIC = 'logarithmic';
+
+function Axis (o) {
+
+ this.orientation = 1;
+ this.offset = 0;
+ this.datamin = Number.MAX_VALUE;
+ this.datamax = -Number.MAX_VALUE;
+
+ _.extend(this, o);
+
+ this._setTranslations();
+}
+
+
+// Prototype
+Axis.prototype = {
+
+ setScale : function () {
+ var length = this.length;
+ if (this.options.scaling == LOGARITHMIC) {
+ this.scale = length / (log(this.max, this.options.base) - log(this.min, this.options.base));
+ } else {
+ this.scale = length / (this.max - this.min);
+ }
+ },
+
+ calculateTicks : function () {
+ var options = this.options;
+
+ this.ticks = [];
+ this.minorTicks = [];
+
+ // User Ticks
+ if(options.ticks){
+ this._cleanUserTicks(options.ticks, this.ticks);
+ this._cleanUserTicks(options.minorTicks || [], this.minorTicks);
+ }
+ else {
+ if (options.mode == 'time') {
+ this._calculateTimeTicks();
+ } else if (options.scaling === 'logarithmic') {
+ this._calculateLogTicks();
+ } else {
+ this._calculateTicks();
+ }
+ }
+ },
+
+ /**
+ * Calculates the range of an axis to apply autoscaling.
+ */
+ calculateRange: function () {
+
+ if (!this.used) return;
+
+ var axis = this,
+ o = axis.options,
+ min = o.min !== null ? o.min : axis.datamin,
+ max = o.max !== null ? o.max : axis.datamax,
+ margin = o.autoscaleMargin;
+
+ if (o.scaling == 'logarithmic') {
+ if (min <= 0) min = axis.datamin;
+
+ // Let it widen later on
+ if (max <= 0) max = min;
+ }
+
+ if (max == min) {
+ var widen = max ? 0.01 : 1.00;
+ if (o.min === null) min -= widen;
+ if (o.max === null) max += widen;
+ }
+
+ if (o.scaling === 'logarithmic') {
+ if (min < 0) min = max / o.base; // Could be the result of widening
+
+ var maxexp = Math.log(max);
+ if (o.base != Math.E) maxexp /= Math.log(o.base);
+ maxexp = Math.ceil(maxexp);
+
+ var minexp = Math.log(min);
+ if (o.base != Math.E) minexp /= Math.log(o.base);
+ minexp = Math.ceil(minexp);
+
+ axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals);
+
+ // Try to determine a suitable amount of miniticks based on the length of a decade
+ if (o.minorTickFreq === null) {
+ if (maxexp - minexp > 10)
+ o.minorTickFreq = 0;
+ else if (maxexp - minexp > 5)
+ o.minorTickFreq = 2;
+ else
+ o.minorTickFreq = 5;
+ }
+ } else {
+ axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals);
+ }
+
+ axis.min = min;
+ axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled
+
+ // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it
+ if(o.min === null && o.autoscale){
+ axis.min -= axis.tickSize * margin;
+ // Make sure we don't go below zero if all values are positive.
+ if(axis.min < 0 && axis.datamin >= 0) axis.min = 0;
+ axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize);
+ }
+
+ if(o.max === null && o.autoscale){
+ axis.max += axis.tickSize * margin;
+ if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0;
+ axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize);
+ }
+
+ if (axis.min == axis.max) axis.max = axis.min + 1;
+ },
+
+ calculateTextDimensions : function (T, options) {
+
+ var maxLabel = '',
+ length,
+ i;
+
+ if (this.options.showLabels) {
+ for (i = 0; i < this.ticks.length; ++i) {
+ length = this.ticks[i].label.length;
+ if (length > maxLabel.length){
+ maxLabel = this.ticks[i].label;
+ }
+ }
+ }
+
+ this.maxLabel = T.dimensions(
+ maxLabel,
+ {size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)},
+ 'font-size:smaller;',
+ 'flotr-grid-label'
+ );
+
+ this.titleSize = T.dimensions(
+ this.options.title,
+ {size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)},
+ 'font-weight:bold;',
+ 'flotr-axis-title'
+ );
+ },
+
+ _cleanUserTicks : function (ticks, axisTicks) {
+
+ var axis = this, options = this.options,
+ v, i, label, tick;
+
+ if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max});
+
+ for(i = 0; i < ticks.length; ++i){
+ tick = ticks[i];
+ if(typeof(tick) === 'object'){
+ v = tick[0];
+ label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max});
+ } else {
+ v = tick;
+ label = options.tickFormatter(v, {min : this.min, max : this.max});
+ }
+ axisTicks[i] = { v: v, label: label };
+ }
+ },
+
+ _calculateTimeTicks : function () {
+ this.ticks = Flotr.Date.generator(this);
+ },
+
+ _calculateLogTicks : function () {
+
+ var axis = this,
+ o = axis.options,
+ v,
+ decadeStart;
+
+ var max = Math.log(axis.max);
+ if (o.base != Math.E) max /= Math.log(o.base);
+ max = Math.ceil(max);
+
+ var min = Math.log(axis.min);
+ if (o.base != Math.E) min /= Math.log(o.base);
+ min = Math.ceil(min);
+
+ for (i = min; i < max; i += axis.tickSize) {
+ decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
+ // Next decade begins here:
+ var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize));
+ var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq;
+
+ axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
+ for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize)
+ axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})});
+ }
+
+ // Always show the value at the would-be start of next decade (end of this decade)
+ decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
+ axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
+ },
+
+ _calculateTicks : function () {
+
+ var axis = this,
+ o = axis.options,
+ tickSize = axis.tickSize,
+ min = axis.min,
+ max = axis.max,
+ start = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size.
+ decimals,
+ minorTickSize,
+ v, v2,
+ i, j;
+
+ if (o.minorTickFreq)
+ minorTickSize = tickSize / o.minorTickFreq;
+
+ // Then store all possible ticks.
+ for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){
+
+ // Round (this is always needed to fix numerical instability).
+ decimals = o.tickDecimals;
+ if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10);
+ if (decimals < 0) decimals = 0;
+
+ v = v.toFixed(decimals);
+ axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
+
+ if (o.minorTickFreq) {
+ for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) {
+ v = v2 + j * minorTickSize;
+ axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
+ }
+ }
+ }
+
+ },
+
+ _setTranslations : function (logarithmic) {
+ this.d2p = (logarithmic ? d2pLog : d2p);
+ this.p2d = (logarithmic ? p2dLog : p2d);
+ }
+};
+
+
+// Static Methods
+_.extend(Axis, {
+ getAxes : function (options) {
+ return {
+ x: new Axis({options: options.xaxis, n: 1, length: this.plotWidth}),
+ x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}),
+ y: new Axis({options: options.yaxis, n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}),
+ y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1})
+ };
+ }
+});
+
+
+// Helper Methods
+
+function d2p (dataValue) {
+ return this.offset + this.orientation * (dataValue - this.min) * this.scale;
+}
+
+function p2d (pointValue) {
+ return (this.offset + this.orientation * pointValue) / this.scale + this.min;
+}
+
+function d2pLog (dataValue) {
+ return this.offset + this.orientation * (log(dataValue, this.options.base) - log(this.min, this.options.base)) * this.scale;
+}
+
+function p2dLog (pointValue) {
+ return exp((this.offset + this.orientation * pointValue) / this.scale + log(this.min, this.options.base), this.options.base);
+}
+
+function log (value, base) {
+ value = Math.log(Math.max(value, Number.MIN_VALUE));
+ if (base !== Math.E)
+ value /= Math.log(base);
+ return value;
+}
+
+function exp (value, base) {
+ return (base === Math.E) ? Math.exp(value) : Math.pow(base, value);
+}
+
+Flotr.Axis = Axis;
+
+})();
+
+/**
+ * Flotr Series Library
+ */
+
+(function () {
+
+var
+ _ = Flotr._;
+
+function Series (o) {
+ _.extend(this, o);
+}
+
+Series.prototype = {
+
+ getRange: function () {
+
+ var
+ data = this.data,
+ length = data.length,
+ xmin = Number.MAX_VALUE,
+ ymin = Number.MAX_VALUE,
+ xmax = -Number.MAX_VALUE,
+ ymax = -Number.MAX_VALUE,
+ xused = false,
+ yused = false,
+ x, y, i;
+
+ if (length < 0 || this.hide) return false;
+
+ for (i = 0; i < length; i++) {
+ x = data[i][0];
+ y = data[i][1];
+ if (x < xmin) { xmin = x; xused = true; }
+ if (x > xmax) { xmax = x; xused = true; }
+ if (y < ymin) { ymin = y; yused = true; }
+ if (y > ymax) { ymax = y; yused = true; }
+ }
+
+ return {
+ xmin : xmin,
+ xmax : xmax,
+ ymin : ymin,
+ ymax : ymax,
+ xused : xused,
+ yused : yused
+ };
+ }
+};
+
+_.extend(Series, {
+ /**
+ * Collects dataseries from input and parses the series into the right format. It returns an Array
+ * of Objects each having at least the 'data' key set.
+ * @param {Array, Object} data - Object or array of dataseries
+ * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)})
+ */
+ getSeries: function(data){
+ return _.map(data, function(s){
+ var series;
+ if (s.data) {
+ series = new Series();
+ _.extend(series, s);
+ } else {
+ series = new Series({data:s});
+ }
+ return series;
+ });
+ }
+});
+
+Flotr.Series = Series;
+
+})();
+
+/** Lines **/
+Flotr.addType('lines', {
+ options: {
+ show: false, // => setting to true will show lines, false will hide
+ lineWidth: 2, // => line width in pixels
+ fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillBorder: false, // => draw a border around the fill
+ fillColor: null, // => fill color
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ steps: false, // => draw steps
+ stacked: false // => setting to true will show stacked lines, false will show normal lines
+ },
+
+ stack : {
+ values : []
+ },
+
+ /**
+ * Draws lines series in the canvas element.
+ * @param {Object} options
+ */
+ draw : function (options) {
+
+ var
+ context = options.context,
+ lineWidth = options.lineWidth,
+ shadowSize = options.shadowSize,
+ offset;
+
+ context.save();
+ context.lineJoin = 'round';
+
+ if (shadowSize) {
+
+ context.lineWidth = shadowSize / 2;
+ offset = lineWidth / 2 + context.lineWidth / 2;
+
+ // @TODO do this instead with a linear gradient
+ context.strokeStyle = "rgba(0,0,0,0.1)";
+ this.plot(options, offset + shadowSize / 2, false);
+
+ context.strokeStyle = "rgba(0,0,0,0.2)";
+ this.plot(options, offset, false);
+ }
+
+ context.lineWidth = lineWidth;
+ context.strokeStyle = options.color;
+
+ this.plot(options, 0, true);
+
+ context.restore();
+ },
+
+ plot : function (options, shadowOffset, incStack) {
+
+ var
+ context = options.context,
+ width = options.width,
+ height = options.height,
+ xScale = options.xScale,
+ yScale = options.yScale,
+ data = options.data,
+ stack = options.stacked ? this.stack : false,
+ length = data.length - 1,
+ prevx = null,
+ prevy = null,
+ zero = yScale(0),
+ x1, x2, y1, y2, stack1, stack2, i;
+
+ if (length < 1) return;
+
+ context.beginPath();
+
+ for (i = 0; i < length; ++i) {
+
+ // To allow empty values
+ if (data[i][1] === null || data[i+1][1] === null) continue;
+
+ // Zero is infinity for log scales
+ // TODO handle zero for logarithmic
+ // if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue;
+ // if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue;
+
+ x1 = xScale(data[i][0]);
+ x2 = xScale(data[i+1][0]);
+
+ if (stack) {
+
+ stack1 = stack.values[data[i][0]] || 0;
+ stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0;
+
+ y1 = yScale(data[i][1] + stack1);
+ y2 = yScale(data[i+1][1] + stack2);
+
+ if(incStack){
+ stack.values[data[i][0]] = data[i][1]+stack1;
+
+ if(i == length-1)
+ stack.values[data[i+1][0]] = data[i+1][1]+stack2;
+ }
+ }
+ else{
+ y1 = yScale(data[i][1]);
+ y2 = yScale(data[i+1][1]);
+ }
+
+ if (
+ (y1 > height && y2 > height) ||
+ (y1 < 0 && y2 < 0) ||
+ (x1 < 0 && x2 < 0) ||
+ (x1 > width && x2 > width)
+ ) continue;
+
+ if((prevx != x1) || (prevy != y1 + shadowOffset))
+ context.moveTo(x1, y1 + shadowOffset);
+
+ prevx = x2;
+ prevy = y2 + shadowOffset;
+ if (options.steps) {
+ context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset);
+ context.lineTo(prevx + shadowOffset / 2, prevy);
+ } else {
+ context.lineTo(prevx, prevy);
+ }
+ }
+
+ if (!options.fill || options.fill && !options.fillBorder) context.stroke();
+
+ // TODO stacked lines
+ if(!shadowOffset && options.fill){
+ x1 = xScale(data[0][0]);
+ context.fillStyle = options.fillStyle;
+ context.lineTo(x2, zero);
+ context.lineTo(x1, zero);
+ context.lineTo(x1, yScale(data[0][1]));
+ context.fill();
+ if (options.fillBorder) {
+ context.stroke();
+ }
+ }
+
+ context.closePath();
+ },
+
+ // Perform any pre-render precalculations (this should be run on data first)
+ // - Pie chart total for calculating measures
+ // - Stacks for lines and bars
+ // precalculate : function () {
+ // }
+ //
+ //
+ // Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max)
+ // getBounds : function () {
+ // }
+ // getMin : function () {
+ // }
+ // getMax : function () {
+ // }
+ //
+ //
+ // Padding around rendered elements
+ // getPadding : function () {
+ // }
+
+ extendYRange : function (axis, data, options, lines) {
+
+ var o = axis.options;
+
+ // If stacked and auto-min
+ if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) {
+
+ var
+ newmax = axis.max,
+ newmin = axis.min,
+ positiveSums = lines.positiveSums || {},
+ negativeSums = lines.negativeSums || {},
+ x, j;
+
+ for (j = 0; j < data.length; j++) {
+
+ x = data[j][0] + '';
+
+ // Positive
+ if (data[j][1] > 0) {
+ positiveSums[x] = (positiveSums[x] || 0) + data[j][1];
+ newmax = Math.max(newmax, positiveSums[x]);
+ }
+
+ // Negative
+ else {
+ negativeSums[x] = (negativeSums[x] || 0) + data[j][1];
+ newmin = Math.min(newmin, negativeSums[x]);
+ }
+ }
+
+ lines.negativeSums = negativeSums;
+ lines.positiveSums = positiveSums;
+
+ axis.max = newmax;
+ axis.min = newmin;
+ }
+
+ if (options.steps) {
+
+ this.hit = function (options) {
+ var
+ data = options.data,
+ args = options.args,
+ yScale = options.yScale,
+ mouse = args[0],
+ length = data.length,
+ n = args[1],
+ x = mouse.x,
+ relY = mouse.relY,
+ i;
+
+ for (i = 0; i < length - 1; i++) {
+ if (x >= data[i][0] && x <= data[i+1][0]) {
+ if (Math.abs(yScale(data[i][1]) - relY) < 8) {
+ n.x = data[i][0];
+ n.y = data[i][1];
+ n.index = i;
+ n.seriesIndex = options.index;
+ }
+ break;
+ }
+ }
+ };
+
+ this.drawHit = function (options) {
+ var
+ context = options.context,
+ args = options.args,
+ data = options.data,
+ xScale = options.xScale,
+ index = args.index,
+ x = xScale(args.x),
+ y = options.yScale(args.y),
+ x2;
+
+ if (data.length - 1 > index) {
+ x2 = options.xScale(data[index + 1][0]);
+ context.save();
+ context.strokeStyle = options.color;
+ context.lineWidth = options.lineWidth;
+ context.beginPath();
+ context.moveTo(x, y);
+ context.lineTo(x2, y);
+ context.stroke();
+ context.closePath();
+ context.restore();
+ }
+ };
+
+ this.clearHit = function (options) {
+ var
+ context = options.context,
+ args = options.args,
+ data = options.data,
+ xScale = options.xScale,
+ width = options.lineWidth,
+ index = args.index,
+ x = xScale(args.x),
+ y = options.yScale(args.y),
+ x2;
+
+ if (data.length - 1 > index) {
+ x2 = options.xScale(data[index + 1][0]);
+ context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width);
+ }
+ };
+ }
+ }
+
+});
+
+/** Bars **/
+Flotr.addType('bars', {
+
+ options: {
+ show: false, // => setting to true will show bars, false will hide
+ lineWidth: 2, // => in pixels
+ barWidth: 1, // => in units of the x axis
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillColor: null, // => fill color
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ horizontal: false, // => horizontal bars (x and y inverted)
+ stacked: false, // => stacked bar charts
+ centered: true, // => center the bars to their x axis value
+ topPadding: 0.1 // => top padding in percent
+ },
+
+ stack : {
+ positive : [],
+ negative : [],
+ _positive : [], // Shadow
+ _negative : [] // Shadow
+ },
+
+ draw : function (options) {
+ var
+ context = options.context;
+
+ context.save();
+ context.lineJoin = 'miter';
+ // @TODO linewidth not interpreted the right way.
+ context.lineWidth = options.lineWidth;
+ context.strokeStyle = options.color;
+ if (options.fill) context.fillStyle = options.fillStyle;
+
+ this.plot(options);
+
+ context.restore();
+ },
+
+ plot : function (options) {
+
+ var
+ data = options.data,
+ context = options.context,
+ shadowSize = options.shadowSize,
+ i, geometry, left, top, width, height;
+
+ if (data.length < 1) return;
+
+ this.translate(context, options.horizontal);
+
+ for (i = 0; i < data.length; i++) {
+
+ geometry = this.getBarGeometry(data[i][0], data[i][1], options);
+ if (geometry === null) continue;
+
+ left = geometry.left;
+ top = geometry.top;
+ width = geometry.width;
+ height = geometry.height;
+
+ if (options.fill) context.fillRect(left, top, width, height);
+ if (shadowSize) {
+ context.save();
+ context.fillStyle = 'rgba(0,0,0,0.05)';
+ context.fillRect(left + shadowSize, top + shadowSize, width, height);
+ context.restore();
+ }
+ if (options.lineWidth) {
+ context.strokeRect(left, top, width, height);
+ }
+ }
+ },
+
+ translate : function (context, horizontal) {
+ if (horizontal) {
+ context.rotate(-Math.PI / 2);
+ context.scale(-1, 1);
+ }
+ },
+
+ getBarGeometry : function (x, y, options) {
+
+ var
+ horizontal = options.horizontal,
+ barWidth = options.barWidth,
+ centered = options.centered,
+ stack = options.stacked ? this.stack : false,
+ lineWidth = options.lineWidth,
+ bisection = centered ? barWidth / 2 : 0,
+ xScale = horizontal ? options.yScale : options.xScale,
+ yScale = horizontal ? options.xScale : options.yScale,
+ xValue = horizontal ? y : x,
+ yValue = horizontal ? x : y,
+ stackOffset = 0,
+ stackValue, left, right, top, bottom;
+
+ // Stacked bars
+ if (stack) {
+ stackValue = yValue > 0 ? stack.positive : stack.negative;
+ stackOffset = stackValue[xValue] || stackOffset;
+ stackValue[xValue] = stackOffset + yValue;
+ }
+
+ left = xScale(xValue - bisection);
+ right = xScale(xValue + barWidth - bisection);
+ top = yScale(yValue + stackOffset);
+ bottom = yScale(stackOffset);
+
+ // TODO for test passing... probably looks better without this
+ if (bottom < 0) bottom = 0;
+
+ // TODO Skipping...
+ // if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue;
+
+ return (x === null || y === null) ? null : {
+ x : xValue,
+ y : yValue,
+ xScale : xScale,
+ yScale : yScale,
+ top : top,
+ left : Math.min(left, right) - lineWidth / 2,
+ width : Math.abs(right - left) - lineWidth,
+ height : bottom - top
+ };
+ },
+
+ hit : function (options) {
+ var
+ data = options.data,
+ args = options.args,
+ mouse = args[0],
+ n = args[1],
+ x = mouse.x,
+ y = mouse.y,
+ hitGeometry = this.getBarGeometry(x, y, options),
+ width = hitGeometry.width / 2,
+ left = hitGeometry.left,
+ geometry, i;
+
+ for (i = data.length; i--;) {
+ geometry = this.getBarGeometry(data[i][0], data[i][1], options);
+ if (geometry.y > hitGeometry.y && Math.abs(left - geometry.left) < width) {
+ n.x = data[i][0];
+ n.y = data[i][1];
+ n.index = i;
+ n.seriesIndex = options.index;
+ }
+ }
+ },
+
+ drawHit : function (options) {
+ // TODO hits for stacked bars; implement using calculateStack option?
+ var
+ context = options.context,
+ args = options.args,
+ geometry = this.getBarGeometry(args.x, args.y, options),
+ left = geometry.left,
+ top = geometry.top,
+ width = geometry.width,
+ height = geometry.height;
+
+ context.save();
+ context.strokeStyle = options.color;
+ context.lineWidth = options.lineWidth;
+ this.translate(context, options.horizontal);
+
+ // Draw highlight
+ context.beginPath();
+ context.moveTo(left, top + height);
+ context.lineTo(left, top);
+ context.lineTo(left + width, top);
+ context.lineTo(left + width, top + height);
+ if (options.fill) {
+ context.fillStyle = options.fillStyle;
+ context.fill();
+ }
+ context.stroke();
+ context.closePath();
+
+ context.restore();
+ },
+
+ clearHit: function (options) {
+ var
+ context = options.context,
+ args = options.args,
+ geometry = this.getBarGeometry(args.x, args.y, options),
+ left = geometry.left,
+ width = geometry.width,
+ top = geometry.top,
+ height = geometry.height,
+ lineWidth = 2 * options.lineWidth;
+
+ context.save();
+ this.translate(context, options.horizontal);
+ context.clearRect(
+ left - lineWidth,
+ Math.min(top, top + height) - lineWidth,
+ width + 2 * lineWidth,
+ Math.abs(height) + 2 * lineWidth
+ );
+ context.restore();
+ },
+
+ extendXRange : function (axis, data, options, bars) {
+ this._extendRange(axis, data, options, bars);
+ },
+
+ extendYRange : function (axis, data, options, bars) {
+ this._extendRange(axis, data, options, bars);
+ },
+ _extendRange: function (axis, data, options, bars) {
+
+ var
+ max = axis.options.max;
+
+ if (_.isNumber(max) || _.isString(max)) return;
+
+ var
+ newmin = axis.min,
+ newmax = axis.max,
+ horizontal = options.horizontal,
+ orientation = axis.orientation,
+ positiveSums = this.positiveSums || {},
+ negativeSums = this.negativeSums || {},
+ value, datum, index, j;
+
+ // Sides of bars
+ if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) {
+ if (options.centered) {
+ newmax = Math.max(axis.datamax + options.barWidth, newmax);
+ newmin = Math.min(axis.datamin - options.barWidth, newmin);
+ }
+ }
+
+ if (options.stacked &&
+ ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){
+
+ for (j = data.length; j--;) {
+ value = data[j][(orientation == 1 ? 1 : 0)]+'';
+ datum = data[j][(orientation == 1 ? 0 : 1)];
+
+ // Positive
+ if (datum > 0) {
+ positiveSums[value] = (positiveSums[value] || 0) + datum;
+ newmax = Math.max(newmax, positiveSums[value]);
+ }
+
+ // Negative
+ else {
+ negativeSums[value] = (negativeSums[value] || 0) + datum;
+ newmin = Math.min(newmin, negativeSums[value]);
+ }
+ }
+ }
+
+ // End of bars
+ if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) {
+ if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) {
+ newmax += options.topPadding * (newmax - newmin);
+ }
+ }
+
+ this.stackMin = newmin;
+ this.stackMax = newmax;
+ this.negativeSums = negativeSums;
+ this.positiveSums = positiveSums;
+
+ axis.max = newmax;
+ axis.min = newmin;
+ }
+
+});
+
+/** Bubbles **/
+Flotr.addType('bubbles', {
+ options: {
+ show: false, // => setting to true will show radar chart, false will hide
+ lineWidth: 2, // => line width in pixels
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ baseRadius: 2 // => ratio of the radar, against the plot size
+ },
+ draw : function (options) {
+ var
+ context = options.context,
+ shadowSize = options.shadowSize;
+
+ context.save();
+ context.lineWidth = options.lineWidth;
+
+ // Shadows
+ context.fillStyle = 'rgba(0,0,0,0.05)';
+ context.strokeStyle = 'rgba(0,0,0,0.05)';
+ this.plot(options, shadowSize / 2);
+ context.strokeStyle = 'rgba(0,0,0,0.1)';
+ this.plot(options, shadowSize / 4);
+
+ // Chart
+ context.strokeStyle = options.color;
+ context.fillStyle = options.fillStyle;
+ this.plot(options);
+
+ context.restore();
+ },
+ plot : function (options, offset) {
+
+ var
+ data = options.data,
+ context = options.context,
+ geometry,
+ i, x, y, z;
+
+ offset = offset || 0;
+
+ for (i = 0; i < data.length; ++i){
+
+ geometry = this.getGeometry(data[i], options);
+
+ context.beginPath();
+ context.arc(geometry.x + offset, geometry.y + offset, geometry.z, 0, 2 * Math.PI, true);
+ context.stroke();
+ if (options.fill) context.fill();
+ context.closePath();
+ }
+ },
+ getGeometry : function (point, options) {
+ return {
+ x : options.xScale(point[0]),
+ y : options.yScale(point[1]),
+ z : point[2] * options.baseRadius
+ };
+ },
+ hit : function (options) {
+ var
+ data = options.data,
+ args = options.args,
+ mouse = args[0],
+ n = args[1],
+ x = mouse.x,
+ y = mouse.y,
+ geometry,
+ dx, dy;
+
+ for (i = data.length; i--;) {
+ geometry = this.getGeometry(data[i], options);
+
+ dx = geometry.x - options.xScale(x);
+ dy = geometry.y - options.yScale(y);
+
+ if (Math.sqrt(dx * dx + dy * dy) < geometry.z) {
+ n.x = data[i][0];
+ n.y = data[i][1];
+ n.index = i;
+ n.seriesIndex = options.index;
+ }
+ }
+ },
+ drawHit : function (options) {
+
+ var
+ context = options.context,
+ geometry = this.getGeometry(options.data[options.args.index], options);
+
+ context.save();
+ context.lineWidth = options.lineWidth;
+ context.fillStyle = options.fillStyle;
+ context.strokeStyle = options.color;
+ context.beginPath();
+ context.arc(geometry.x, geometry.y, geometry.z, 0, 2 * Math.PI, true);
+ context.fill();
+ context.stroke();
+ context.closePath();
+ context.restore();
+ },
+ clearHit : function (options) {
+
+ var
+ context = options.context,
+ geometry = this.getGeometry(options.data[options.args.index], options),
+ offset = geometry.z + options.lineWidth;
+
+ context.save();
+ context.clearRect(
+ geometry.x - offset,
+ geometry.y - offset,
+ 2 * offset,
+ 2 * offset
+ );
+ context.restore();
+ }
+ // TODO Add a hit calculation method (like pie)
+});
+
+/** Candles **/
+Flotr.addType('candles', {
+ options: {
+ show: false, // => setting to true will show candle sticks, false will hide
+ lineWidth: 1, // => in pixels
+ wickLineWidth: 1, // => in pixels
+ candleWidth: 0.6, // => in units of the x axis
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ upFillColor: '#00A8F0',// => up sticks fill color
+ downFillColor: '#CB4B4B',// => down sticks fill color
+ fillOpacity: 0.5, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ // TODO Test this barcharts option.
+ barcharts: false // => draw as barcharts (not standard bars but financial barcharts)
+ },
+
+ draw : function (options) {
+
+ var
+ context = options.context;
+
+ context.save();
+ context.lineJoin = 'miter';
+ context.lineCap = 'butt';
+ // @TODO linewidth not interpreted the right way.
+ context.lineWidth = options.wickLineWidth || options.lineWidth;
+
+ this.plot(options);
+
+ context.restore();
+ },
+
+ plot : function (options) {
+
+ var
+ data = options.data,
+ context = options.context,
+ xScale = options.xScale,
+ yScale = options.yScale,
+ width = options.candleWidth / 2,
+ shadowSize = options.shadowSize,
+ lineWidth = options.lineWidth,
+ wickLineWidth = options.wickLineWidth,
+ pixelOffset = (wickLineWidth % 2) / 2,
+ color,
+ datum, x, y,
+ open, high, low, close,
+ left, right, bottom, top, bottom2, top2,
+ i;
+
+ if (data.length < 1) return;
+
+ for (i = 0; i < data.length; i++) {
+ datum = data[i];
+ x = datum[0];
+ open = datum[1];
+ high = datum[2];
+ low = datum[3];
+ close = datum[4];
+ left = xScale(x - width);
+ right = xScale(x + width);
+ bottom = yScale(low);
+ top = yScale(high);
+ bottom2 = yScale(Math.min(open, close));
+ top2 = yScale(Math.max(open, close));
+
+ /*
+ // TODO skipping
+ if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
+ continue;
+ */
+
+ color = options[open > close ? 'downFillColor' : 'upFillColor'];
+
+ // Fill the candle.
+ // TODO Test the barcharts option
+ if (options.fill && !options.barcharts) {
+ context.fillStyle = 'rgba(0,0,0,0.05)';
+ context.fillRect(left + shadowSize, top2 + shadowSize, right - left, bottom2 - top2);
+ context.save();
+ context.globalAlpha = options.fillOpacity;
+ context.fillStyle = color;
+ context.fillRect(left, top2 + lineWidth, right - left, bottom2 - top2);
+ context.restore();
+ }
+
+ // Draw candle outline/border, high, low.
+ if (lineWidth || wickLineWidth) {
+
+ x = Math.floor((left + right) / 2) + pixelOffset;
+
+ context.strokeStyle = color;
+ context.beginPath();
+
+ // TODO Again with the bartcharts
+ if (options.barcharts) {
+
+ context.moveTo(x, Math.floor(top + width));
+ context.lineTo(x, Math.floor(bottom + width));
+
+ y = Math.floor(open + width) + 0.5;
+ context.moveTo(Math.floor(left) + pixelOffset, y);
+ context.lineTo(x, y);
+
+ y = Math.floor(close + width) + 0.5;
+ context.moveTo(Math.floor(right) + pixelOffset, y);
+ context.lineTo(x, y);
+ } else {
+ context.strokeRect(left, top2 + lineWidth, right - left, bottom2 - top2);
+
+ context.moveTo(x, Math.floor(top2 + lineWidth));
+ context.lineTo(x, Math.floor(top + lineWidth));
+ context.moveTo(x, Math.floor(bottom2 + lineWidth));
+ context.lineTo(x, Math.floor(bottom + lineWidth));
+ }
+
+ context.closePath();
+ context.stroke();
+ }
+ }
+ },
+ extendXRange: function (axis, data, options) {
+ if (axis.options.max === null) {
+ axis.max = Math.max(axis.datamax + 0.5, axis.max);
+ axis.min = Math.min(axis.datamin - 0.5, axis.min);
+ }
+ }
+});
+
+/** Gantt
+ * Base on data in form [s,y,d] where:
+ * y - executor or simply y value
+ * s - task start value
+ * d - task duration
+ * **/
+Flotr.addType('gantt', {
+ options: {
+ show: false, // => setting to true will show gantt, false will hide
+ lineWidth: 2, // => in pixels
+ barWidth: 1, // => in units of the x axis
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillColor: null, // => fill color
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ centered: true // => center the bars to their x axis value
+ },
+ /**
+ * Draws gantt series in the canvas element.
+ * @param {Object} series - Series with options.gantt.show = true.
+ */
+ draw: function(series) {
+ var ctx = this.ctx,
+ bw = series.gantt.barWidth,
+ lw = Math.min(series.gantt.lineWidth, bw);
+
+ ctx.save();
+ ctx.translate(this.plotOffset.left, this.plotOffset.top);
+ ctx.lineJoin = 'miter';
+
+ /**
+ * @todo linewidth not interpreted the right way.
+ */
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+
+ ctx.save();
+ this.gantt.plotShadows(series, bw, 0, series.gantt.fill);
+ ctx.restore();
+
+ if(series.gantt.fill){
+ var color = series.gantt.fillColor || series.color;
+ ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity});
+ }
+
+ this.gantt.plot(series, bw, 0, series.gantt.fill);
+ ctx.restore();
+ },
+ plot: function(series, barWidth, offset, fill){
+ var data = series.data;
+ if(data.length < 1) return;
+
+ var xa = series.xaxis,
+ ya = series.yaxis,
+ ctx = this.ctx, i;
+
+ for(i = 0; i < data.length; i++){
+ var y = data[i][0],
+ s = data[i][1],
+ d = data[i][2],
+ drawLeft = true, drawTop = true, drawRight = true;
+
+ if (s === null || d === null) continue;
+
+ var left = s,
+ right = s + d,
+ bottom = y - (series.gantt.centered ? barWidth/2 : 0),
+ top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0);
+
+ if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
+ continue;
+
+ if(left < xa.min){
+ left = xa.min;
+ drawLeft = false;
+ }
+
+ if(right > xa.max){
+ right = xa.max;
+ if (xa.lastSerie != series)
+ drawTop = false;
+ }
+
+ if(bottom < ya.min)
+ bottom = ya.min;
+
+ if(top > ya.max){
+ top = ya.max;
+ if (ya.lastSerie != series)
+ drawTop = false;
+ }
+
+ /**
+ * Fill the bar.
+ */
+ if(fill){
+ ctx.beginPath();
+ ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset);
+ ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset);
+ ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset);
+ ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset);
+ ctx.fill();
+ ctx.closePath();
+ }
+
+ /**
+ * Draw bar outline/border.
+ */
+ if(series.gantt.lineWidth && (drawLeft || drawRight || drawTop)){
+ ctx.beginPath();
+ ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset);
+
+ ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset);
+ ctx[drawTop ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset);
+ ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset);
+
+ ctx.stroke();
+ ctx.closePath();
+ }
+ }
+ },
+ plotShadows: function(series, barWidth, offset){
+ var data = series.data;
+ if(data.length < 1) return;
+
+ var i, y, s, d,
+ xa = series.xaxis,
+ ya = series.yaxis,
+ ctx = this.ctx,
+ sw = this.options.shadowSize;
+
+ for(i = 0; i < data.length; i++){
+ y = data[i][0];
+ s = data[i][1];
+ d = data[i][2];
+
+ if (s === null || d === null) continue;
+
+ var left = s,
+ right = s + d,
+ bottom = y - (series.gantt.centered ? barWidth/2 : 0),
+ top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0);
+
+ if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
+ continue;
+
+ if(left < xa.min) left = xa.min;
+ if(right > xa.max) right = xa.max;
+ if(bottom < ya.min) bottom = ya.min;
+ if(top > ya.max) top = ya.max;
+
+ var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw);
+ var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw );
+
+ ctx.fillStyle = 'rgba(0,0,0,0.05)';
+ ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height);
+ }
+ },
+ extendXRange: function(axis) {
+ if(axis.options.max === null){
+ var newmin = axis.min,
+ newmax = axis.max,
+ i, j, x, s, g,
+ stackedSumsPos = {},
+ stackedSumsNeg = {},
+ lastSerie = null;
+
+ for(i = 0; i < this.series.length; ++i){
+ s = this.series[i];
+ g = s.gantt;
+
+ if(g.show && s.xaxis == axis) {
+ for (j = 0; j < s.data.length; j++) {
+ if (g.show) {
+ y = s.data[j][0]+'';
+ stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]);
+ lastSerie = s;
+ }
+ }
+ for (j in stackedSumsPos) {
+ newmax = Math.max(stackedSumsPos[j], newmax);
+ }
+ }
+ }
+ axis.lastSerie = lastSerie;
+ axis.max = newmax;
+ axis.min = newmin;
+ }
+ },
+ extendYRange: function(axis){
+ if(axis.options.max === null){
+ var newmax = Number.MIN_VALUE,
+ newmin = Number.MAX_VALUE,
+ i, j, s, g,
+ stackedSumsPos = {},
+ stackedSumsNeg = {},
+ lastSerie = null;
+
+ for(i = 0; i < this.series.length; ++i){
+ s = this.series[i];
+ g = s.gantt;
+
+ if (g.show && !s.hide && s.yaxis == axis) {
+ var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE;
+ for(j=0; j < s.data.length; j++){
+ datamax = Math.max(datamax,s.data[j][0]);
+ datamin = Math.min(datamin,s.data[j][0]);
+ }
+
+ if (g.centered) {
+ newmax = Math.max(datamax + 0.5, newmax);
+ newmin = Math.min(datamin - 0.5, newmin);
+ }
+ else {
+ newmax = Math.max(datamax + 1, newmax);
+ newmin = Math.min(datamin, newmin);
+ }
+ // For normal horizontal bars
+ if (g.barWidth + datamax > newmax){
+ newmax = axis.max + g.barWidth;
+ }
+ }
+ }
+ axis.lastSerie = lastSerie;
+ axis.max = newmax;
+ axis.min = newmin;
+ axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals);
+ }
+ }
+});
+
+/** Markers **/
+/**
+ * Formats the marker labels.
+ * @param {Object} obj - Marker value Object {x:..,y:..}
+ * @return {String} Formatted marker string
+ */
+(function () {
+
+Flotr.defaultMarkerFormatter = function(obj){
+ return (Math.round(obj.y*100)/100)+'';
+};
+
+Flotr.addType('markers', {
+ options: {
+ show: false, // => setting to true will show markers, false will hide
+ lineWidth: 1, // => line width of the rectangle around the marker
+ color: '#000000', // => text color
+ fill: false, // => fill or not the marekers' rectangles
+ fillColor: "#FFFFFF", // => fill color
+ fillOpacity: 0.4, // => fill opacity
+ stroke: false, // => draw the rectangle around the markers
+ position: 'ct', // => the markers position (vertical align: b, m, t, horizontal align: l, c, r)
+ verticalMargin: 0, // => the margin between the point and the text.
+ labelFormatter: Flotr.defaultMarkerFormatter,
+ fontSize: Flotr.defaultOptions.fontSize,
+ stacked: false, // => true if markers should be stacked
+ stackingType: 'b', // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details)
+ horizontal: false // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly)
+ },
+
+ // TODO test stacked markers.
+ stack : {
+ positive : [],
+ negative : [],
+ values : []
+ },
+
+ draw : function (options) {
+
+ var
+ data = options.data,
+ context = options.context,
+ stack = options.stacked ? options.stack : false,
+ stackType = options.stackingType,
+ stackOffsetNeg,
+ stackOffsetPos,
+ stackOffset,
+ i, x, y, label;
+
+ context.save();
+ context.lineJoin = 'round';
+ context.lineWidth = options.lineWidth;
+ context.strokeStyle = 'rgba(0,0,0,0.5)';
+ context.fillStyle = options.fillStyle;
+
+ function stackPos (a, b) {
+ stackOffsetPos = stack.negative[a] || 0;
+ stackOffsetNeg = stack.positive[a] || 0;
+ if (b > 0) {
+ stack.positive[a] = stackOffsetPos + b;
+ return stackOffsetPos + b;
+ } else {
+ stack.negative[a] = stackOffsetNeg + b;
+ return stackOffsetNeg + b;
+ }
+ }
+
+ for (i = 0; i < data.length; ++i) {
+
+ x = data[i][0];
+ y = data[i][1];
+
+ if (stack) {
+ if (stackType == 'b') {
+ if (options.horizontal) y = stackPos(y, x);
+ else x = stackPos(x, y);
+ } else if (stackType == 'a') {
+ stackOffset = stack.values[x] || 0;
+ stack.values[x] = stackOffset + y;
+ y = stackOffset + y;
+ }
+ }
+
+ label = options.labelFormatter({x: x, y: y, index: i, data : data});
+ this.plot(options.xScale(x), options.yScale(y), label, options);
+ }
+ context.restore();
+ },
+ plot: function(x, y, label, options) {
+ var context = options.context;
+ if (isImage(label) && !label.complete) {
+ throw 'Marker image not loaded.';
+ } else {
+ this._plot(x, y, label, options);
+ }
+ },
+
+ _plot: function(x, y, label, options) {
+ var context = options.context,
+ margin = 2,
+ left = x,
+ top = y,
+ dim;
+
+ if (isImage(label))
+ dim = {height : label.height, width: label.width};
+ else
+ dim = options.text.canvas(label);
+
+ dim.width = Math.floor(dim.width+margin*2);
+ dim.height = Math.floor(dim.height+margin*2);
+
+ if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin;
+ else if (options.position.indexOf('l') != -1) left -= dim.width;
+
+ if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin;
+ else if (options.position.indexOf('t') != -1) top -= dim.height + options.verticalMargin;
+ else top += options.verticalMargin;
+
+ left = Math.floor(left)+0.5;
+ top = Math.floor(top)+0.5;
+
+ if(options.fill)
+ context.fillRect(left, top, dim.width, dim.height);
+
+ if(options.stroke)
+ context.strokeRect(left, top, dim.width, dim.height);
+
+ if (isImage(label))
+ context.drawImage(label, left+margin, top+margin);
+ else
+ Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color});
+ }
+});
+
+function isImage (i) {
+ return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image);
+}
+
+})();
+
+/** Pie **/
+/**
+ * Formats the pies labels.
+ * @param {Object} slice - Slice object
+ * @return {String} Formatted pie label string
+ */
+(function () {
+
+var
+ _ = Flotr._;
+
+Flotr.defaultPieLabelFormatter = function (total, value) {
+ return (100 * value / total).toFixed(2)+'%';
+};
+
+Flotr.addType('pie', {
+ options: {
+ show: false, // => setting to true will show bars, false will hide
+ lineWidth: 1, // => in pixels
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillColor: null, // => fill color
+ fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ explode: 6, // => the number of pixels the splices will be far from the center
+ sizeRatio: 0.6, // => the size ratio of the pie relative to the plot
+ startAngle: Math.PI/4, // => the first slice start angle
+ labelFormatter: Flotr.defaultPieLabelFormatter,
+ pie3D: false, // => whether to draw the pie in 3 dimenstions or not (ineffective)
+ pie3DviewAngle: (Math.PI/2 * 0.8),
+ pie3DspliceThickness: 20
+ },
+
+ draw : function (options) {
+
+ // TODO 3D charts what?
+
+ var
+ data = options.data,
+ context = options.context,
+ canvas = context.canvas,
+ lineWidth = options.lineWidth,
+ shadowSize = options.shadowSize,
+ sizeRatio = options.sizeRatio,
+ height = options.height,
+ width = options.width,
+ explode = options.explode,
+ color = options.color,
+ fill = options.fill,
+ fillStyle = options.fillStyle,
+ radius = Math.min(canvas.width, canvas.height) * sizeRatio / 2,
+ value = data[0][1],
+ html = [],
+ vScale = 1,//Math.cos(series.pie.viewAngle);
+ measure = Math.PI * 2 * value / this.total,
+ startAngle = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable)
+ endAngle = startAngle + measure,
+ bisection = startAngle + measure / 2,
+ label = options.labelFormatter(this.total, value),
+ //plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale;
+ explodeCoeff = explode + radius + 4,
+ distX = Math.cos(bisection) * explodeCoeff,
+ distY = Math.sin(bisection) * explodeCoeff,
+ textAlign = distX < 0 ? 'right' : 'left',
+ textBaseline = distY > 0 ? 'top' : 'bottom',
+ style,
+ x, y,
+ distX, distY;
+
+ context.save();
+ context.translate(width / 2, height / 2);
+ context.scale(1, vScale);
+
+ x = Math.cos(bisection) * explode;
+ y = Math.sin(bisection) * explode;
+
+ // Shadows
+ if (shadowSize > 0) {
+ this.plotSlice(x + shadowSize, y + shadowSize, radius, startAngle, endAngle, context);
+ if (fill) {
+ context.fillStyle = 'rgba(0,0,0,0.1)';
+ context.fill();
+ }
+ }
+
+ this.plotSlice(x, y, radius, startAngle, endAngle, context);
+ if (fill) {
+ context.fillStyle = fillStyle;
+ context.fill();
+ }
+ context.lineWidth = lineWidth;
+ context.strokeStyle = color;
+ context.stroke();
+
+ style = {
+ size : options.fontSize * 1.2,
+ color : options.fontColor,
+ weight : 1.5
+ };
+
+ if (label) {
+ if (options.htmlText || !options.textEnabled) {
+ divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;';
+ divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;';
+ html.push('', label, '
');
+ }
+ else {
+ style.textAlign = textAlign;
+ style.textBaseline = textBaseline;
+ Flotr.drawText(context, label, distX, distY, style);
+ }
+ }
+
+ if (options.htmlText || !options.textEnabled) {
+ var div = Flotr.DOM.node('
');
+ Flotr.DOM.insert(div, html.join(''));
+ Flotr.DOM.insert(options.element, div);
+ }
+
+ context.restore();
+
+ // New start angle
+ this.startAngle = endAngle;
+ this.slices = this.slices || [];
+ this.slices.push({
+ radius : Math.min(canvas.width, canvas.height) * sizeRatio / 2,
+ x : x,
+ y : y,
+ explode : explode,
+ start : startAngle,
+ end : endAngle
+ });
+ },
+ plotSlice : function (x, y, radius, startAngle, endAngle, context) {
+ context.beginPath();
+ context.moveTo(x, y);
+ context.arc(x, y, radius, startAngle, endAngle, false);
+ context.lineTo(x, y);
+ context.closePath();
+ },
+ hit : function (options) {
+
+ var
+ data = options.data[0],
+ args = options.args,
+ index = options.index,
+ mouse = args[0],
+ n = args[1],
+ slice = this.slices[index],
+ x = mouse.relX - options.width / 2,
+ y = mouse.relY - options.height / 2,
+ r = Math.sqrt(x * x + y * y),
+ theta = Math.atan(y / x),
+ circle = Math.PI * 2,
+ explode = slice.explode || options.explode,
+ start = slice.start % circle,
+ end = slice.end % circle;
+
+ if (x < 0) {
+ theta += Math.PI;
+ } else if (x > 0 && y < 0) {
+ theta += circle;
+ }
+
+ if (r < slice.radius + explode && r > explode) {
+ if ((start > end && (theta < end || theta > start)) ||
+ (theta > start && theta < end)) {
+
+ // TODO Decouple this from hit plugin (chart shouldn't know what n means)
+ n.x = data[0];
+ n.y = data[1];
+ n.sAngle = start;
+ n.eAngle = end;
+ n.index = 0;
+ n.seriesIndex = index;
+ n.fraction = data[1] / this.total;
+ }
+ }
+ },
+ drawHit: function (options) {
+ var
+ context = options.context,
+ slice = this.slices[options.args.seriesIndex];
+
+ context.save();
+ context.translate(options.width / 2, options.height / 2);
+ this.plotSlice(slice.x, slice.y, slice.radius, slice.start, slice.end, context);
+ context.stroke();
+ context.restore();
+ },
+ clearHit : function (options) {
+ var
+ context = options.context,
+ slice = this.slices[options.args.seriesIndex],
+ padding = 2 * options.lineWidth,
+ radius = slice.radius + padding;
+
+ context.save();
+ context.translate(options.width / 2, options.height / 2);
+ context.clearRect(
+ slice.x - radius,
+ slice.y - radius,
+ 2 * radius + padding,
+ 2 * radius + padding
+ );
+ context.restore();
+ },
+ extendYRange : function (axis, data) {
+ this.total = (this.total || 0) + data[0][1];
+ }
+});
+})();
+
+/** Points **/
+Flotr.addType('points', {
+ options: {
+ show: false, // => setting to true will show points, false will hide
+ radius: 3, // => point radius (pixels)
+ lineWidth: 2, // => line width in pixels
+ fill: true, // => true to fill the points with a color, false for (transparent) no fill
+ fillColor: '#FFFFFF', // => fill color
+ fillOpacity: 0.4 // => opacity of color inside the points
+ },
+
+ draw : function (options) {
+ var
+ context = options.context,
+ lineWidth = options.lineWidth,
+ shadowSize = options.shadowSize;
+
+ context.save();
+
+ if (shadowSize > 0) {
+ context.lineWidth = shadowSize / 2;
+
+ context.strokeStyle = 'rgba(0,0,0,0.1)';
+ this.plot(options, shadowSize / 2 + context.lineWidth / 2);
+
+ context.strokeStyle = 'rgba(0,0,0,0.2)';
+ this.plot(options, context.lineWidth / 2);
+ }
+
+ context.lineWidth = options.lineWidth;
+ context.strokeStyle = options.color;
+ context.fillStyle = options.fillColor || options.color;
+
+ this.plot(options);
+ context.restore();
+ },
+
+ plot : function (options, offset) {
+ var
+ data = options.data,
+ context = options.context,
+ xScale = options.xScale,
+ yScale = options.yScale,
+ i, x, y;
+
+ for (i = data.length - 1; i > -1; --i) {
+ y = data[i][1];
+ if (y === null) continue;
+
+ x = xScale(data[i][0]);
+ y = yScale(y);
+
+ if (x < 0 || x > options.width || y < 0 || y > options.height) continue;
+
+ context.beginPath();
+ if (offset) {
+ context.arc(x, y + offset, options.radius, 0, Math.PI, false);
+ } else {
+ context.arc(x, y, options.radius, 0, 2 * Math.PI, true);
+ if (options.fill) context.fill();
+ }
+ context.stroke();
+ context.closePath();
+ }
+ }
+});
+
+/** Radar **/
+Flotr.addType('radar', {
+ options: {
+ show: false, // => setting to true will show radar chart, false will hide
+ lineWidth: 2, // => line width in pixels
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ radiusRatio: 0.90 // => ratio of the radar, against the plot size
+ },
+ draw : function (options) {
+ var
+ context = options.context,
+ shadowSize = options.shadowSize;
+
+ context.save();
+ context.translate(options.width / 2, options.height / 2);
+ context.lineWidth = options.lineWidth;
+
+ // Shadow
+ context.fillStyle = 'rgba(0,0,0,0.05)';
+ context.strokeStyle = 'rgba(0,0,0,0.05)';
+ this.plot(options, shadowSize / 2);
+ context.strokeStyle = 'rgba(0,0,0,0.1)';
+ this.plot(options, shadowSize / 4);
+
+ // Chart
+ context.strokeStyle = options.color;
+ context.fillStyle = options.fillStyle;
+ this.plot(options);
+
+ context.restore();
+ },
+ plot : function (options, offset) {
+ var
+ data = options.data,
+ context = options.context,
+ radius = Math.min(options.height, options.width) * options.radiusRatio / 2,
+ step = 2 * Math.PI / data.length,
+ angle = -Math.PI / 2,
+ i, ratio;
+
+ offset = offset || 0;
+
+ context.beginPath();
+ for (i = 0; i < data.length; ++i) {
+ ratio = data[i][1] / this.max;
+
+ context[i === 0 ? 'moveTo' : 'lineTo'](
+ Math.cos(i * step + angle) * radius * ratio + offset,
+ Math.sin(i * step + angle) * radius * ratio + offset
+ );
+ }
+ context.closePath();
+ if (options.fill) context.fill();
+ context.stroke();
+ },
+ extendYRange : function (axis, data) {
+ this.max = Math.max(axis.max, this.max || -Number.MAX_VALUE);
+ }
+});
+
+Flotr.addType('timeline', {
+ options: {
+ show: false,
+ lineWidth: 1,
+ barWidth: 0.2,
+ fill: true,
+ fillColor: null,
+ fillOpacity: 0.4,
+ centered: true
+ },
+
+ draw : function (options) {
+
+ var
+ context = options.context;
+
+ context.save();
+ context.lineJoin = 'miter';
+ context.lineWidth = options.lineWidth;
+ context.strokeStyle = options.color;
+ context.fillStyle = options.fillStyle;
+
+ this.plot(options);
+
+ context.restore();
+ },
+
+ plot : function (options) {
+
+ var
+ data = options.data,
+ context = options.context,
+ xScale = options.xScale,
+ yScale = options.yScale,
+ barWidth = options.barWidth,
+ lineWidth = options.lineWidth,
+ i;
+
+ Flotr._.each(data, function (timeline) {
+
+ var
+ x = timeline[0],
+ y = timeline[1],
+ w = timeline[2],
+ h = barWidth,
+
+ xt = Math.ceil(xScale(x)),
+ wt = Math.ceil(xScale(x + w)) - xt,
+ yt = Math.round(yScale(y)),
+ ht = Math.round(yScale(y - h)) - yt,
+
+ x0 = xt - lineWidth / 2,
+ y0 = Math.round(yt - ht / 2) - lineWidth / 2;
+
+ context.strokeRect(x0, y0, wt, ht);
+ context.fillRect(x0, y0, wt, ht);
+
+ });
+ },
+
+ extendRange : function (series) {
+
+ var
+ data = series.data,
+ xa = series.xaxis,
+ ya = series.yaxis,
+ w = series.timeline.barWidth;
+
+ if (xa.options.min === null)
+ xa.min = xa.datamin - w / 2;
+
+ if (xa.options.max === null) {
+
+ var
+ max = xa.max;
+
+ Flotr._.each(data, function (timeline) {
+ max = Math.max(max, timeline[0] + timeline[2]);
+ }, this);
+
+ xa.max = max + w / 2;
+ }
+
+ if (ya.options.min === null)
+ ya.min = ya.datamin - w;
+ if (ya.options.min === null)
+ ya.max = ya.datamax + w;
+ }
+
+});
+
+(function () {
+
+var D = Flotr.DOM;
+
+Flotr.addPlugin('crosshair', {
+ options: {
+ mode: null, // => one of null, 'x', 'y' or 'xy'
+ color: '#FF0000', // => crosshair color
+ hideCursor: true // => hide the cursor when the crosshair is shown
+ },
+ callbacks: {
+ 'flotr:mousemove': function(e, pos) {
+ if (this.options.crosshair.mode) {
+ this.crosshair.clearCrosshair();
+ this.crosshair.drawCrosshair(pos);
+ }
+ }
+ },
+ /**
+ * Draws the selection box.
+ */
+ drawCrosshair: function(pos) {
+ var octx = this.octx,
+ options = this.options.crosshair,
+ plotOffset = this.plotOffset,
+ x = plotOffset.left + pos.relX + 0.5,
+ y = plotOffset.top + pos.relY + 0.5;
+
+ if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) {
+ this.el.style.cursor = null;
+ D.removeClass(this.el, 'flotr-crosshair');
+ return;
+ }
+
+ if (options.hideCursor) {
+ this.el.style.cursor = 'none';
+ D.addClass(this.el, 'flotr-crosshair');
+ }
+
+ octx.save();
+ octx.strokeStyle = options.color;
+ octx.lineWidth = 1;
+ octx.beginPath();
+
+ if (options.mode.indexOf('x') != -1) {
+ octx.moveTo(x, plotOffset.top);
+ octx.lineTo(x, plotOffset.top + this.plotHeight);
+ }
+
+ if (options.mode.indexOf('y') != -1) {
+ octx.moveTo(plotOffset.left, y);
+ octx.lineTo(plotOffset.left + this.plotWidth, y);
+ }
+
+ octx.stroke();
+ octx.restore();
+ },
+ /**
+ * Removes the selection box from the overlay canvas.
+ */
+ clearCrosshair: function() {
+
+ var
+ plotOffset = this.plotOffset,
+ position = this.lastMousePos,
+ context = this.octx;
+
+ if (position) {
+ context.clearRect(
+ position.relX + plotOffset.left,
+ plotOffset.top,
+ 1,
+ this.plotHeight + 1
+ );
+ context.clearRect(
+ plotOffset.left,
+ position.relY + plotOffset.top,
+ this.plotWidth + 1,
+ 1
+ );
+ }
+ }
+});
+})();
+
+(function() {
+
+var
+ D = Flotr.DOM,
+ _ = Flotr._;
+
+function getImage (type, canvas, width, height) {
+
+ // TODO add scaling for w / h
+ var
+ mime = 'image/'+type,
+ data = canvas.toDataURL(mime),
+ image = new Image();
+ image.src = data;
+ return image;
+}
+
+Flotr.addPlugin('download', {
+
+ saveImage: function (type, width, height, replaceCanvas) {
+ var image = null;
+ if (Flotr.isIE && Flotr.isIE < 9) {
+ image = ''+this.canvas.firstChild.innerHTML+'';
+ return window.open().document.write(image);
+ }
+
+ if (type !== 'jpeg' && type !== 'png') return;
+
+ image = getImage(type, this.canvas, width, height);
+
+ if (_.isElement(image) && replaceCanvas) {
+ this.download.restoreCanvas();
+ D.hide(this.canvas);
+ D.hide(this.overlay);
+ D.setStyles({position: 'absolute'});
+ D.insert(this.el, image);
+ this.saveImageElement = image;
+ } else {
+ return window.open(image.src);
+ }
+ },
+
+ restoreCanvas: function() {
+ D.show(this.canvas);
+ D.show(this.overlay);
+ if (this.saveImageElement) this.el.removeChild(this.saveImageElement);
+ this.saveImageElement = null;
+ }
+});
+
+})();
+
+(function () {
+
+var E = Flotr.EventAdapter,
+ _ = Flotr._;
+
+Flotr.addPlugin('graphGrid', {
+
+ callbacks: {
+ 'flotr:beforedraw' : function () {
+ this.graphGrid.drawGrid();
+ },
+ 'flotr:afterdraw' : function () {
+ this.graphGrid.drawOutline();
+ }
+ },
+
+ drawGrid: function(){
+
+ var
+ ctx = this.ctx,
+ options = this.options,
+ grid = options.grid,
+ verticalLines = grid.verticalLines,
+ horizontalLines = grid.horizontalLines,
+ minorVerticalLines = grid.minorVerticalLines,
+ minorHorizontalLines = grid.minorHorizontalLines,
+ plotHeight = this.plotHeight,
+ plotWidth = this.plotWidth,
+ a, v, i, j;
+
+ if(verticalLines || minorVerticalLines ||
+ horizontalLines || minorHorizontalLines){
+ E.fire(this.el, 'flotr:beforegrid', [this.axes.x, this.axes.y, options, this]);
+ }
+ ctx.save();
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = grid.tickColor;
+
+ function circularHorizontalTicks (ticks) {
+ for(i = 0; i < ticks.length; ++i){
+ var ratio = ticks[i].v / a.max;
+ for(j = 0; j <= sides; ++j){
+ ctx[j === 0 ? 'moveTo' : 'lineTo'](
+ Math.cos(j*coeff+angle)*radius*ratio,
+ Math.sin(j*coeff+angle)*radius*ratio
+ );
+ }
+ }
+ }
+ function drawGridLines (ticks, callback) {
+ _.each(_.pluck(ticks, 'v'), function(v){
+ // Don't show lines on upper and lower bounds.
+ if ((v <= a.min || v >= a.max) ||
+ (v == a.min || v == a.max) && grid.outlineWidth)
+ return;
+ callback(Math.floor(a.d2p(v)) + ctx.lineWidth/2);
+ });
+ }
+ function drawVerticalLines (x) {
+ ctx.moveTo(x, 0);
+ ctx.lineTo(x, plotHeight);
+ }
+ function drawHorizontalLines (y) {
+ ctx.moveTo(0, y);
+ ctx.lineTo(plotWidth, y);
+ }
+
+ if (grid.circular) {
+ ctx.translate(this.plotOffset.left+plotWidth/2, this.plotOffset.top+plotHeight/2);
+ var radius = Math.min(plotHeight, plotWidth)*options.radar.radiusRatio/2,
+ sides = this.axes.x.ticks.length,
+ coeff = 2*(Math.PI/sides),
+ angle = -Math.PI/2;
+
+ // Draw grid lines in vertical direction.
+ ctx.beginPath();
+
+ a = this.axes.y;
+
+ if(horizontalLines){
+ circularHorizontalTicks(a.ticks);
+ }
+ if(minorHorizontalLines){
+ circularHorizontalTicks(a.minorTicks);
+ }
+
+ if(verticalLines){
+ _.times(sides, function(i){
+ ctx.moveTo(0, 0);
+ ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
+ });
+ }
+ ctx.stroke();
+ }
+ else {
+ ctx.translate(this.plotOffset.left, this.plotOffset.top);
+
+ // Draw grid background, if present in options.
+ if(grid.backgroundColor){
+ ctx.fillStyle = this.processColor(grid.backgroundColor, {x1: 0, y1: 0, x2: plotWidth, y2: plotHeight});
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
+ }
+
+ ctx.beginPath();
+
+ a = this.axes.x;
+ if (verticalLines) drawGridLines(a.ticks, drawVerticalLines);
+ if (minorVerticalLines) drawGridLines(a.minorTicks, drawVerticalLines);
+
+ a = this.axes.y;
+ if (horizontalLines) drawGridLines(a.ticks, drawHorizontalLines);
+ if (minorHorizontalLines) drawGridLines(a.minorTicks, drawHorizontalLines);
+
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ if(verticalLines || minorVerticalLines ||
+ horizontalLines || minorHorizontalLines){
+ E.fire(this.el, 'flotr:aftergrid', [this.axes.x, this.axes.y, options, this]);
+ }
+ },
+
+ drawOutline: function(){
+ var
+ that = this,
+ options = that.options,
+ grid = options.grid,
+ outline = grid.outline,
+ ctx = that.ctx,
+ backgroundImage = grid.backgroundImage,
+ plotOffset = that.plotOffset,
+ leftOffset = plotOffset.left,
+ topOffset = plotOffset.top,
+ plotWidth = that.plotWidth,
+ plotHeight = that.plotHeight,
+ v, img, src, left, top, globalAlpha;
+
+ if (!grid.outlineWidth) return;
+
+ ctx.save();
+
+ if (grid.circular) {
+ ctx.translate(leftOffset + plotWidth / 2, topOffset + plotHeight / 2);
+ var radius = Math.min(plotHeight, plotWidth) * options.radar.radiusRatio / 2,
+ sides = this.axes.x.ticks.length,
+ coeff = 2*(Math.PI/sides),
+ angle = -Math.PI/2;
+
+ // Draw axis/grid border.
+ ctx.beginPath();
+ ctx.lineWidth = grid.outlineWidth;
+ ctx.strokeStyle = grid.color;
+ ctx.lineJoin = 'round';
+
+ for(i = 0; i <= sides; ++i){
+ ctx[i === 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
+ }
+ //ctx.arc(0, 0, radius, 0, Math.PI*2, true);
+
+ ctx.stroke();
+ }
+ else {
+ ctx.translate(leftOffset, topOffset);
+
+ // Draw axis/grid border.
+ var lw = grid.outlineWidth,
+ orig = 0.5-lw+((lw+1)%2/2),
+ lineTo = 'lineTo',
+ moveTo = 'moveTo';
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = grid.color;
+ ctx.lineJoin = 'miter';
+ ctx.beginPath();
+ ctx.moveTo(orig, orig);
+ plotWidth = plotWidth - (lw / 2) % 1;
+ plotHeight = plotHeight + lw / 2;
+ ctx[outline.indexOf('n') !== -1 ? lineTo : moveTo](plotWidth, orig);
+ ctx[outline.indexOf('e') !== -1 ? lineTo : moveTo](plotWidth, plotHeight);
+ ctx[outline.indexOf('s') !== -1 ? lineTo : moveTo](orig, plotHeight);
+ ctx[outline.indexOf('w') !== -1 ? lineTo : moveTo](orig, orig);
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+ ctx.restore();
+
+ if (backgroundImage) {
+
+ src = backgroundImage.src || backgroundImage;
+ left = (parseInt(backgroundImage.left, 10) || 0) + plotOffset.left;
+ top = (parseInt(backgroundImage.top, 10) || 0) + plotOffset.top;
+ img = new Image();
+
+ img.onload = function() {
+ ctx.save();
+ if (backgroundImage.alpha) ctx.globalAlpha = backgroundImage.alpha;
+ ctx.globalCompositeOperation = 'destination-over';
+ ctx.drawImage(img, 0, 0, img.width, img.height, left, top, plotWidth, plotHeight);
+ ctx.restore();
+ };
+
+ img.src = src;
+ }
+ }
+});
+
+})();
+
+(function () {
+
+var
+ D = Flotr.DOM,
+ _ = Flotr._,
+ flotr = Flotr,
+ S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;';
+
+Flotr.addPlugin('hit', {
+ callbacks: {
+ 'flotr:mousemove': function(e, pos) {
+ this.hit.track(pos);
+ },
+ 'flotr:click': function(pos) {
+ this.hit.track(pos);
+ },
+ 'flotr:mouseout': function() {
+ this.hit.clearHit();
+ }
+ },
+ track : function (pos) {
+ if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) {
+ this.hit.hit(pos);
+ }
+ },
+ /**
+ * Try a method on a graph type. If the method exists, execute it.
+ * @param {Object} series
+ * @param {String} method Method name.
+ * @param {Array} args Arguments applied to method.
+ * @return executed successfully or failed.
+ */
+ executeOnType: function(s, method, args){
+ var
+ success = false,
+ options;
+
+ if (!_.isArray(s)) s = [s];
+
+ function e(s, index) {
+ _.each(_.keys(flotr.graphTypes), function (type) {
+ if (s[type] && s[type].show && this[type][method]) {
+ options = this.getOptions(s, type);
+
+ options.fill = !!s.mouse.fillColor;
+ options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
+ options.color = s.mouse.lineColor;
+ options.context = this.octx;
+ options.index = index;
+
+ if (args) options.args = args;
+ this[type][method].call(this[type], options);
+ success = true;
+ }
+ }, this);
+ }
+ _.each(s, e, this);
+
+ return success;
+ },
+ /**
+ * Updates the mouse tracking point on the overlay.
+ */
+ drawHit: function(n){
+ var octx = this.octx,
+ s = n.series;
+
+ if (s.mouse.lineColor) {
+ octx.save();
+ octx.lineWidth = (s.points ? s.points.lineWidth : 1);
+ octx.strokeStyle = s.mouse.lineColor;
+ octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
+ octx.translate(this.plotOffset.left, this.plotOffset.top);
+
+ if (!this.hit.executeOnType(s, 'drawHit', n)) {
+ var xa = n.xaxis,
+ ya = n.yaxis;
+
+ octx.beginPath();
+ // TODO fix this (points) should move to general testable graph mixin
+ octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true);
+ octx.fill();
+ octx.stroke();
+ octx.closePath();
+ }
+ octx.restore();
+ }
+ this.prevHit = n;
+ },
+ /**
+ * Removes the mouse tracking point from the overlay.
+ */
+ clearHit: function(){
+ var prev = this.prevHit,
+ octx = this.octx,
+ plotOffset = this.plotOffset;
+ octx.save();
+ octx.translate(plotOffset.left, plotOffset.top);
+ if (prev) {
+ if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) {
+ // TODO fix this (points) should move to general testable graph mixin
+ var
+ s = prev.series,
+ lw = (s.points ? s.points.lineWidth : 1);
+ offset = (s.points.radius || s.mouse.radius) + lw;
+ octx.clearRect(
+ prev.xaxis.d2p(prev.x) - offset,
+ prev.yaxis.d2p(prev.y) - offset,
+ offset*2,
+ offset*2
+ );
+ }
+ D.hide(this.mouseTrack);
+ this.prevHit = null;
+ }
+ octx.restore();
+ },
+ /**
+ * Retrieves the nearest data point from the mouse cursor. If it's within
+ * a certain range, draw a point on the overlay canvas and display the x and y
+ * value of the data.
+ * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor.
+ */
+ hit: function(mouse){
+
+ var
+ options = this.options,
+ prevHit = this.prevHit,
+ closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis;
+
+ if (this.series.length === 0) return;
+
+ // Nearest data element.
+ // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse,
+ // xaxis, yaxis, series, index, seriesIndex
+ n = {
+ relX : mouse.relX,
+ relY : mouse.relY,
+ absX : mouse.absX,
+ absY : mouse.absY
+ };
+
+ if (options.mouse.trackY &&
+ !options.mouse.trackAll &&
+ this.hit.executeOnType(this.series, 'hit', [mouse, n]))
+ {
+
+ if (!_.isUndefined(n.seriesIndex)) {
+ series = this.series[n.seriesIndex];
+ n.series = series;
+ n.mouse = series.mouse;
+ n.xaxis = series.xaxis;
+ n.yaxis = series.yaxis;
+ }
+ } else {
+
+ closest = this.hit.closest(mouse);
+
+ if (closest) {
+
+ closest = options.mouse.trackY ? closest.point : closest.x;
+ seriesIndex = closest.seriesIndex;
+ series = this.series[seriesIndex];
+ xaxis = series.xaxis;
+ yaxis = series.yaxis;
+ sensibility = 2 * series.mouse.sensibility;
+
+ if
+ (options.mouse.trackAll ||
+ (closest.distanceX < sensibility / xaxis.scale &&
+ (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale)))
+ {
+ n.series = series;
+ n.xaxis = series.xaxis;
+ n.yaxis = series.yaxis;
+ n.mouse = series.mouse;
+ n.x = closest.x;
+ n.y = closest.y;
+ n.dist = closest.distance;
+ n.index = closest.dataIndex;
+ n.seriesIndex = seriesIndex;
+ }
+ }
+ }
+
+ if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) {
+ this.hit.clearHit();
+ if (n.series && n.mouse && n.mouse.track) {
+ this.hit.drawMouseTrack(n);
+ this.hit.drawHit(n);
+ Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]);
+ }
+ }
+ },
+
+ closest : function (mouse) {
+
+ var
+ series = this.series,
+ options = this.options,
+ mouseX = mouse.x,
+ mouseY = mouse.y,
+ compare = Number.MAX_VALUE,
+ compareX = Number.MAX_VALUE,
+ closest = {},
+ closestX = {},
+ check = false,
+ serie, data,
+ distance, distanceX, distanceY,
+ x, y, i, j;
+
+ function setClosest (o) {
+ o.distance = distance;
+ o.distanceX = distanceX;
+ o.distanceY = distanceY;
+ o.seriesIndex = i;
+ o.dataIndex = j;
+ o.x = x;
+ o.y = y;
+ }
+
+ for (i = 0; i < series.length; i++) {
+
+ serie = series[i];
+ data = serie.data;
+
+ if (data.length) check = true;
+
+ for (j = data.length; j--;) {
+
+ x = data[j][0];
+ y = data[j][1];
+
+ if (x === null || y === null) continue;
+
+ // don't check if the point isn't visible in the current range
+ if (x < serie.xaxis.min || x > serie.xaxis.max) continue;
+
+ distanceX = Math.abs(x - mouseX);
+ distanceY = Math.abs(y - mouseY);
+
+ // Skip square root for speed
+ distance = distanceX * distanceX + distanceY * distanceY;
+
+ if (distance < compare) {
+ compare = distance;
+ setClosest(closest);
+ }
+
+ if (distanceX < compareX) {
+ compareX = distanceX;
+ setClosest(closestX);
+ }
+ }
+ }
+
+ return check ? {
+ point : closest,
+ x : closestX
+ } : false;
+ },
+
+ drawMouseTrack : function (n) {
+
+ var
+ pos = '',
+ s = n.series,
+ p = n.mouse.position,
+ m = n.mouse.margin,
+ elStyle = S_MOUSETRACK,
+ mouseTrack = this.mouseTrack,
+ plotOffset = this.plotOffset,
+ left = plotOffset.left,
+ right = plotOffset.right,
+ bottom = plotOffset.bottom,
+ top = plotOffset.top,
+ decimals = n.mouse.trackDecimals,
+ options = this.options;
+
+ // Create
+ if (!mouseTrack) {
+ mouseTrack = D.node('
');
+ this.mouseTrack = mouseTrack;
+ D.insert(this.el, mouseTrack);
+ }
+
+ if (!n.mouse.relative) { // absolute to the canvas
+
+ if (p.charAt(0) == 'n') pos += 'top:' + (m + top) + 'px;bottom:auto;';
+ else if (p.charAt(0) == 's') pos += 'bottom:' + (m + bottom) + 'px;top:auto;';
+ if (p.charAt(1) == 'e') pos += 'right:' + (m + right) + 'px;left:auto;';
+ else if (p.charAt(1) == 'w') pos += 'left:' + (m + left) + 'px;right:auto;';
+
+ // Bars
+ } else if (s.bars.show) {
+ pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y/2) + this.canvasHeight) + 'px;top:auto;';
+ pos += 'left:' + (m + left + n.xaxis.d2p(n.x - options.bars.barWidth/2)) + 'px;right:auto;';
+
+ // Pie
+ } else if (s.pie.show) {
+ var center = {
+ x: (this.plotWidth)/2,
+ y: (this.plotHeight)/2
+ },
+ radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2,
+ bisection = n.sAngle one of null, 'x', 'y' or 'xy'
+ color: '#B6D9FF', // => selection box color
+ fps: 20 // => frames-per-second
+ },
+
+ callbacks: {
+ 'flotr:mouseup' : function (event) {
+
+ var
+ options = this.options.selection,
+ selection = this.selection,
+ pointer = this.getEventPosition(event);
+
+ if (!options || !options.mode) return;
+ if (selection.interval) clearInterval(selection.interval);
+
+ if (this.multitouches) {
+ selection.updateSelection();
+ } else
+ if (!options.pinchOnly) {
+ selection.setSelectionPos(selection.selection.second, pointer);
+ }
+ selection.clearSelection();
+
+ if(selection.selecting && selection.selectionIsSane()){
+ selection.drawSelection();
+ selection.fireSelectEvent();
+ this.ignoreClick = true;
+ }
+ },
+ 'flotr:mousedown' : function (event) {
+
+ var
+ options = this.options.selection,
+ selection = this.selection,
+ pointer = this.getEventPosition(event);
+
+ if (!options || !options.mode) return;
+ if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return;
+ if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer);
+ if (selection.interval) clearInterval(selection.interval);
+
+ this.lastMousePos.pageX = null;
+ selection.selecting = false;
+ selection.interval = setInterval(
+ _.bind(selection.updateSelection, this),
+ 1000 / options.fps
+ );
+ },
+ 'flotr:destroy' : function (event) {
+ clearInterval(this.selection.interval);
+ }
+ },
+
+ // TODO This isn't used. Maybe it belongs in the draw area and fire select event methods?
+ getArea: function() {
+
+ var s = this.selection.selection,
+ first = s.first,
+ second = s.second;
+
+ return {
+ x1: Math.min(first.x, second.x),
+ x2: Math.max(first.x, second.x),
+ y1: Math.min(first.y, second.y),
+ y2: Math.max(first.y, second.y)
+ };
+ },
+
+ selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}},
+ prevSelection: null,
+ interval: null,
+
+ /**
+ * Fires the 'flotr:select' event when the user made a selection.
+ */
+ fireSelectEvent: function(name){
+ var a = this.axes,
+ s = this.selection.selection,
+ x1, x2, y1, y2;
+
+ name = name || 'select';
+
+ x1 = a.x.p2d(s.first.x);
+ x2 = a.x.p2d(s.second.x);
+ y1 = a.y.p2d(s.first.y);
+ y2 = a.y.p2d(s.second.y);
+
+ E.fire(this.el, 'flotr:'+name, [{
+ x1:Math.min(x1, x2),
+ y1:Math.min(y1, y2),
+ x2:Math.max(x1, x2),
+ y2:Math.max(y1, y2),
+ xfirst:x1, xsecond:x2, yfirst:y1, ysecond:y2
+ }, this]);
+ },
+
+ /**
+ * Allows the user the manually select an area.
+ * @param {Object} area - Object with coordinates to select.
+ */
+ setSelection: function(area, preventEvent){
+ var options = this.options,
+ xa = this.axes.x,
+ ya = this.axes.y,
+ vertScale = ya.scale,
+ hozScale = xa.scale,
+ selX = options.selection.mode.indexOf('x') != -1,
+ selY = options.selection.mode.indexOf('y') != -1,
+ s = this.selection.selection;
+
+ this.selection.clearSelection();
+
+ s.first.y = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this);
+ s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this);
+ s.first.x = boundX((selY && !selX) ? 0 : area.x1, this);
+ s.second.x = boundX((selY && !selX) ? this.plotWidth : area.x2, this);
+
+ this.selection.drawSelection();
+ if (!preventEvent)
+ this.selection.fireSelectEvent();
+ },
+
+ /**
+ * Calculates the position of the selection.
+ * @param {Object} pos - Position object.
+ * @param {Event} event - Event object.
+ */
+ setSelectionPos: function(pos, pointer) {
+ var mode = this.options.selection.mode,
+ selection = this.selection.selection;
+
+ if(mode.indexOf('x') == -1) {
+ pos.x = (pos == selection.first) ? 0 : this.plotWidth;
+ }else{
+ pos.x = boundX(pointer.relX, this);
+ }
+
+ if (mode.indexOf('y') == -1) {
+ pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1;
+ }else{
+ pos.y = boundY(pointer.relY, this);
+ }
+ },
+ /**
+ * Draws the selection box.
+ */
+ drawSelection: function() {
+
+ this.selection.fireSelectEvent('selecting');
+
+ var s = this.selection.selection,
+ octx = this.octx,
+ options = this.options,
+ plotOffset = this.plotOffset,
+ prevSelection = this.selection.prevSelection;
+
+ if (prevSelection &&
+ s.first.x == prevSelection.first.x &&
+ s.first.y == prevSelection.first.y &&
+ s.second.x == prevSelection.second.x &&
+ s.second.y == prevSelection.second.y) {
+ return;
+ }
+
+ octx.save();
+ octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8});
+ octx.lineWidth = 1;
+ octx.lineJoin = 'miter';
+ octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4});
+
+ this.selection.prevSelection = {
+ first: { x: s.first.x, y: s.first.y },
+ second: { x: s.second.x, y: s.second.y }
+ };
+
+ var x = Math.min(s.first.x, s.second.x),
+ y = Math.min(s.first.y, s.second.y),
+ w = Math.abs(s.second.x - s.first.x),
+ h = Math.abs(s.second.y - s.first.y);
+
+ octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
+ octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
+ octx.restore();
+ },
+
+ /**
+ * Updates (draws) the selection box.
+ */
+ updateSelection: function(){
+ if (!this.lastMousePos.pageX) return;
+
+ this.selection.selecting = true;
+
+ if (this.multitouches) {
+ this.selection.setSelectionPos(this.selection.selection.first, this.getEventPosition(this.multitouches[0]));
+ this.selection.setSelectionPos(this.selection.selection.second, this.getEventPosition(this.multitouches[1]));
+ } else
+ if (this.options.selection.pinchOnly) {
+ return;
+ } else {
+ this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos);
+ }
+
+ this.selection.clearSelection();
+
+ if(this.selection.selectionIsSane()) {
+ this.selection.drawSelection();
+ }
+ },
+
+ /**
+ * Removes the selection box from the overlay canvas.
+ */
+ clearSelection: function() {
+ if (!this.selection.prevSelection) return;
+
+ var prevSelection = this.selection.prevSelection,
+ lw = 1,
+ plotOffset = this.plotOffset,
+ x = Math.min(prevSelection.first.x, prevSelection.second.x),
+ y = Math.min(prevSelection.first.y, prevSelection.second.y),
+ w = Math.abs(prevSelection.second.x - prevSelection.first.x),
+ h = Math.abs(prevSelection.second.y - prevSelection.first.y);
+
+ this.octx.clearRect(x + plotOffset.left - lw + 0.5,
+ y + plotOffset.top - lw,
+ w + 2 * lw + 0.5,
+ h + 2 * lw + 0.5);
+
+ this.selection.prevSelection = null;
+ },
+ /**
+ * Determines whether or not the selection is sane and should be drawn.
+ * @return {Boolean} - True when sane, false otherwise.
+ */
+ selectionIsSane: function(){
+ var s = this.selection.selection;
+ return Math.abs(s.second.x - s.first.x) >= 5 ||
+ Math.abs(s.second.y - s.first.y) >= 5;
+ }
+
+});
+
+})();
+
+(function () {
+
+var D = Flotr.DOM;
+
+Flotr.addPlugin('labels', {
+
+ callbacks : {
+ 'flotr:afterdraw' : function () {
+ this.labels.draw();
+ }
+ },
+
+ draw: function(){
+ // Construct fixed width label boxes, which can be styled easily.
+ var
+ axis, tick, left, top, xBoxWidth,
+ radius, sides, coeff, angle,
+ div, i, html = '',
+ noLabels = 0,
+ options = this.options,
+ ctx = this.ctx,
+ a = this.axes,
+ style = { size: options.fontSize };
+
+ for (i = 0; i < a.x.ticks.length; ++i){
+ if (a.x.ticks[i].label) { ++noLabels; }
+ }
+ xBoxWidth = this.plotWidth / noLabels;
+
+ if (options.grid.circular) {
+ ctx.save();
+ ctx.translate(this.plotOffset.left + this.plotWidth / 2,
+ this.plotOffset.top + this.plotHeight / 2);
+
+ radius = this.plotHeight * options.radar.radiusRatio / 2 + options.fontSize;
+ sides = this.axes.x.ticks.length;
+ coeff = 2 * (Math.PI / sides);
+ angle = -Math.PI / 2;
+
+ drawLabelCircular(this, a.x, false);
+ drawLabelCircular(this, a.x, true);
+ drawLabelCircular(this, a.y, false);
+ drawLabelCircular(this, a.y, true);
+ ctx.restore();
+ }
+
+ if (!options.HtmlText && this.textEnabled) {
+ drawLabelNoHtmlText(this, a.x, 'center', 'top');
+ drawLabelNoHtmlText(this, a.x2, 'center', 'bottom');
+ drawLabelNoHtmlText(this, a.y, 'right', 'middle');
+ drawLabelNoHtmlText(this, a.y2, 'left', 'middle');
+
+ } else if ((
+ a.x.options.showLabels ||
+ a.x2.options.showLabels ||
+ a.y.options.showLabels ||
+ a.y2.options.showLabels) &&
+ !options.grid.circular
+ ) {
+
+ html = '';
+
+ drawLabelHtml(this, a.x);
+ drawLabelHtml(this, a.x2);
+ drawLabelHtml(this, a.y);
+ drawLabelHtml(this, a.y2);
+
+ ctx.stroke();
+ ctx.restore();
+ div = D.create('div');
+ D.setStyles(div, {
+ fontSize: 'smaller',
+ color: options.grid.color
+ });
+ div.className = 'flotr-labels';
+ D.insert(this.el, div);
+ D.insert(div, html);
+ }
+
+ function drawLabelCircular (graph, axis, minorTicks) {
+ var
+ ticks = minorTicks ? axis.minorTicks : axis.ticks,
+ isX = axis.orientation === 1,
+ isFirst = axis.n === 1,
+ style, offset;
+
+ style = {
+ color : axis.options.color || options.grid.color,
+ angle : Flotr.toRad(axis.options.labelsAngle),
+ textBaseline : 'middle'
+ };
+
+ for (i = 0; i < ticks.length &&
+ (minorTicks ? axis.options.showMinorLabels : axis.options.showLabels); ++i){
+ tick = ticks[i];
+ tick.label += '';
+ if (!tick.label || !tick.label.length) { continue; }
+
+ x = Math.cos(i * coeff + angle) * radius;
+ y = Math.sin(i * coeff + angle) * radius;
+
+ style.textAlign = isX ? (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left')) : 'left';
+
+ Flotr.drawText(
+ ctx, tick.label,
+ isX ? x : 3,
+ isX ? y : -(axis.ticks[i].v / axis.max) * (radius - options.fontSize),
+ style
+ );
+ }
+ }
+
+ function drawLabelNoHtmlText (graph, axis, textAlign, textBaseline) {
+ var
+ isX = axis.orientation === 1,
+ isFirst = axis.n === 1,
+ style, offset;
+
+ style = {
+ color : axis.options.color || options.grid.color,
+ textAlign : textAlign,
+ textBaseline : textBaseline,
+ angle : Flotr.toRad(axis.options.labelsAngle)
+ };
+ style = Flotr.getBestTextAlign(style.angle, style);
+
+ for (i = 0; i < axis.ticks.length && continueShowingLabels(axis); ++i) {
+
+ tick = axis.ticks[i];
+ if (!tick.label || !tick.label.length) { continue; }
+
+ offset = axis.d2p(tick.v);
+ if (offset < 0 ||
+ offset > (isX ? graph.plotWidth : graph.plotHeight)) { continue; }
+
+ Flotr.drawText(
+ ctx, tick.label,
+ leftOffset(graph, isX, isFirst, offset),
+ topOffset(graph, isX, isFirst, offset),
+ style
+ );
+
+ // Only draw on axis y2
+ if (!isX && !isFirst) {
+ ctx.save();
+ ctx.strokeStyle = style.color;
+ ctx.beginPath();
+ ctx.moveTo(graph.plotOffset.left + graph.plotWidth - 8, graph.plotOffset.top + axis.d2p(tick.v));
+ ctx.lineTo(graph.plotOffset.left + graph.plotWidth, graph.plotOffset.top + axis.d2p(tick.v));
+ ctx.stroke();
+ ctx.restore();
+ }
+ }
+
+ function continueShowingLabels (axis) {
+ return axis.options.showLabels && axis.used;
+ }
+ function leftOffset (graph, isX, isFirst, offset) {
+ return graph.plotOffset.left +
+ (isX ? offset :
+ (isFirst ?
+ -options.grid.labelMargin :
+ options.grid.labelMargin + graph.plotWidth));
+ }
+ function topOffset (graph, isX, isFirst, offset) {
+ return graph.plotOffset.top +
+ (isX ? options.grid.labelMargin : offset) +
+ ((isX && isFirst) ? graph.plotHeight : 0);
+ }
+ }
+
+ function drawLabelHtml (graph, axis) {
+ var
+ isX = axis.orientation === 1,
+ isFirst = axis.n === 1,
+ name = '',
+ left, style, top,
+ offset = graph.plotOffset;
+
+ if (!isX && !isFirst) {
+ ctx.save();
+ ctx.strokeStyle = axis.options.color || options.grid.color;
+ ctx.beginPath();
+ }
+
+ if (axis.options.showLabels && (isFirst ? true : axis.used)) {
+ for (i = 0; i < axis.ticks.length; ++i) {
+ tick = axis.ticks[i];
+ if (!tick.label || !tick.label.length ||
+ ((isX ? offset.left : offset.top) + axis.d2p(tick.v) < 0) ||
+ ((isX ? offset.left : offset.top) + axis.d2p(tick.v) > (isX ? graph.canvasWidth : graph.canvasHeight))) {
+ continue;
+ }
+ top = offset.top +
+ (isX ?
+ ((isFirst ? 1 : -1 ) * (graph.plotHeight + options.grid.labelMargin)) :
+ axis.d2p(tick.v) - axis.maxLabel.height / 2);
+ left = isX ? (offset.left + axis.d2p(tick.v) - xBoxWidth / 2) : 0;
+
+ name = '';
+ if (i === 0) {
+ name = ' first';
+ } else if (i === axis.ticks.length - 1) {
+ name = ' last';
+ }
+ name += isX ? ' flotr-grid-label-x' : ' flotr-grid-label-y';
+
+ html += [
+ '' + tick.label + '
'
+ ].join(' ');
+
+ if (!isX && !isFirst) {
+ ctx.moveTo(offset.left + graph.plotWidth - 8, offset.top + axis.d2p(tick.v));
+ ctx.lineTo(offset.left + graph.plotWidth, offset.top + axis.d2p(tick.v));
+ }
+ }
+ }
+ }
+ }
+
+});
+})();
+
+(function () {
+
+var
+ D = Flotr.DOM,
+ _ = Flotr._;
+
+Flotr.addPlugin('legend', {
+ options: {
+ show: true, // => setting to true will show the legend, hide otherwise
+ noColumns: 1, // => number of colums in legend table // @todo: doesn't work for HtmlText = false
+ labelFormatter: function(v){return v;}, // => fn: string -> string
+ labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes
+ labelBoxWidth: 14,
+ labelBoxHeight: 10,
+ labelBoxMargin: 5,
+ labelBoxOpacity: 0.4,
+ container: null, // => container (as jQuery object) to put legend in, null means default on top of graph
+ position: 'nw', // => 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, set to 1 for a solid background
+ },
+ callbacks: {
+ 'flotr:afterinit': function() {
+ this.legend.insertLegend();
+ }
+ },
+ /**
+ * Adds a legend div to the canvas container or draws it on the canvas.
+ */
+ insertLegend: function(){
+
+ if(!this.options.legend.show)
+ return;
+
+ var series = this.series,
+ plotOffset = this.plotOffset,
+ options = this.options,
+ legend = options.legend,
+ fragments = [],
+ rowStarted = false,
+ ctx = this.ctx,
+ itemCount = _.filter(series, function(s) {return (s.label && !s.hide);}).length,
+ p = legend.position,
+ m = legend.margin,
+ i, label, color;
+
+ if (itemCount) {
+ if (!options.HtmlText && this.textEnabled && !legend.container) {
+ var style = {
+ size: options.fontSize*1.1,
+ color: options.grid.color
+ };
+
+ var lbw = legend.labelBoxWidth,
+ lbh = legend.labelBoxHeight,
+ lbm = legend.labelBoxMargin,
+ offsetX = plotOffset.left + m,
+ offsetY = plotOffset.top + m;
+
+ // We calculate the labels' max width
+ var labelMaxWidth = 0;
+ for(i = series.length - 1; i > -1; --i){
+ if(!series[i].label || series[i].hide) continue;
+ label = legend.labelFormatter(series[i].label);
+ labelMaxWidth = Math.max(labelMaxWidth, this._text.measureText(label, style).width);
+ }
+
+ var legendWidth = Math.round(lbw + lbm*3 + labelMaxWidth),
+ legendHeight = Math.round(itemCount*(lbm+lbh) + lbm);
+
+ if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight);
+ if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth);
+
+ // Legend box
+ color = this.processColor(legend.backgroundColor || 'rgb(240,240,240)', {opacity: legend.backgroundOpacity || 0.1});
+
+ ctx.fillStyle = color;
+ ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight);
+ ctx.strokeStyle = legend.labelBoxBorderColor;
+ ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight);
+
+ // Legend labels
+ var x = offsetX + lbm;
+ var y = offsetY + lbm;
+ for(i = 0; i < series.length; i++){
+ if(!series[i].label || series[i].hide) continue;
+ label = legend.labelFormatter(series[i].label);
+
+ ctx.fillStyle = series[i].color;
+ ctx.fillRect(x, y, lbw-1, lbh-1);
+
+ ctx.strokeStyle = legend.labelBoxBorderColor;
+ ctx.lineWidth = 1;
+ ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2);
+
+ // Legend text
+ Flotr.drawText(ctx, label, x + lbw + lbm, y + lbh, style);
+
+ y += lbh + lbm;
+ }
+ }
+ else {
+ for(i = 0; i < series.length; ++i){
+ if(!series[i].label || series[i].hide) continue;
+
+ if(i % legend.noColumns === 0){
+ fragments.push(rowStarted ? '' : ' ');
+ rowStarted = true;
+ }
+
+ // @TODO remove requirement on bars
+ var s = series[i],
+ boxWidth = legend.labelBoxWidth,
+ boxHeight = legend.labelBoxHeight,
+ opacityValue = (s.bars ? s.bars.fillOpacity : legend.labelBoxOpacity),
+ opacity = 'opacity:' + opacityValue + ';filter:alpha(opacity=' + opacityValue*100 + ');';
+
+ label = legend.labelFormatter(s.label);
+ color = 'background-color:' + ((s.bars && s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';';
+
+ fragments.push(
+ '',
+ '',
+ '
', // Border
+ '
', // Background
+ '
',
+ '
',
+ ' ',
+ '', label, ' '
+ );
+ }
+ if(rowStarted) fragments.push(' ');
+
+ if(fragments.length > 0){
+ var table = '' + fragments.join('') + '
';
+ if(legend.container){
+ D.insert(legend.container, table);
+ }
+ else {
+ var styles = {position: 'absolute', 'z-index': 2};
+
+ if(p.charAt(0) == 'n') { styles.top = (m + plotOffset.top) + 'px'; styles.bottom = 'auto'; }
+ else if(p.charAt(0) == 's') { styles.bottom = (m + plotOffset.bottom) + 'px'; styles.top = 'auto'; }
+ if(p.charAt(1) == 'e') { styles.right = (m + plotOffset.right) + 'px'; styles.left = 'auto'; }
+ else if(p.charAt(1) == 'w') { styles.left = (m + plotOffset.left) + 'px'; styles.right = 'auto'; }
+
+ var div = D.create('div'), size;
+ div.className = 'flotr-legend';
+ D.setStyles(div, styles);
+ D.insert(div, table);
+ D.insert(this.el, div);
+
+ if(!legend.backgroundOpacity)
+ return;
+
+ var c = legend.backgroundColor || options.grid.backgroundColor || '#ffffff';
+
+ _.extend(styles, D.size(div), {
+ 'backgroundColor': c,
+ 'z-index': 1
+ });
+ styles.width += 'px';
+ styles.height += 'px';
+
+ // Put in the transparent background separately to avoid blended labels and
+ div = D.create('div');
+ div.className = 'flotr-legend-bg';
+ D.setStyles(div, styles);
+ D.opacity(div, legend.backgroundOpacity);
+ D.insert(div, ' ');
+ D.insert(this.el, div);
+ }
+ }
+ }
+ }
+ }
+});
+})();
+
+/** Spreadsheet **/
+(function() {
+
+function getRowLabel(value){
+ if (this.options.spreadsheet.tickFormatter){
+ //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out?
+ return this.options.spreadsheet.tickFormatter(value);
+ }
+ else {
+ var t = _.find(this.axes.x.ticks, function(t){return t.v == value;});
+ if (t) {
+ return t.label;
+ }
+ return value;
+ }
+}
+
+var
+ D = Flotr.DOM,
+ _ = Flotr._;
+
+Flotr.addPlugin('spreadsheet', {
+ options: {
+ show: false, // => show the data grid using two tabs
+ tabGraphLabel: 'Graph',
+ tabDataLabel: 'Data',
+ toolbarDownload: 'Download CSV', // @todo: add better language support
+ toolbarSelectAll: 'Select all',
+ csvFileSeparator: ',',
+ decimalSeparator: '.',
+ tickFormatter: null,
+ initialTab: 'graph'
+ },
+ /**
+ * Builds the tabs in the DOM
+ */
+ callbacks: {
+ 'flotr:afterconstruct': function(){
+ // @TODO necessary?
+ //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove');
+
+ if (!this.options.spreadsheet.show) return;
+
+ var ss = this.spreadsheet,
+ container = D.node('
'),
+ graph = D.node(''+this.options.spreadsheet.tabGraphLabel+'
'),
+ data = D.node(''+this.options.spreadsheet.tabDataLabel+'
'),
+ offset;
+
+ ss.tabsContainer = container;
+ ss.tabs = { graph : graph, data : data };
+
+ D.insert(container, graph);
+ D.insert(container, data);
+ D.insert(this.el, container);
+
+ offset = D.size(data).height + 2;
+ this.plotOffset.bottom += offset;
+
+ D.setStyles(container, {top: this.canvasHeight-offset+'px'});
+
+ this.
+ observe(graph, 'click', function(){ss.showTab('graph');}).
+ observe(data, 'click', function(){ss.showTab('data');});
+ if (this.options.spreadsheet.initialTab !== 'graph'){
+ ss.showTab(this.options.spreadsheet.initialTab);
+ }
+ }
+ },
+ /**
+ * Builds a matrix of the data to make the correspondance between the x values and the y values :
+ * X value => Y values from the axes
+ * @return {Array} The data grid
+ */
+ loadDataGrid: function(){
+ if (this.seriesData) return this.seriesData;
+
+ var s = this.series,
+ rows = {};
+
+ /* The data grid is a 2 dimensions array. There is a row for each X value.
+ * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)
+ **/
+ _.each(s, function(serie, i){
+ _.each(serie.data, function (v) {
+ var x = v[0],
+ y = v[1],
+ r = rows[x];
+ if (r) {
+ r[i+1] = y;
+ } else {
+ var newRow = [];
+ newRow[0] = x;
+ newRow[i+1] = y;
+ rows[x] = newRow;
+ }
+ });
+ });
+
+ // The data grid is sorted by x value
+ this.seriesData = _.sortBy(rows, function(row, x){
+ return parseInt(x, 10);
+ });
+ return this.seriesData;
+ },
+ /**
+ * Constructs the data table for the spreadsheet
+ * @todo make a spreadsheet manager (Flotr.Spreadsheet)
+ * @return {Element} The resulting table element
+ */
+ constructDataGrid: function(){
+ // If the data grid has already been built, nothing to do here
+ if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid;
+
+ var s = this.series,
+ datagrid = this.spreadsheet.loadDataGrid(),
+ colgroup = [' '],
+ buttonDownload, buttonSelect, t;
+
+ // First row : series' labels
+ var html = [''];
+ html.push(' ');
+ _.each(s, function(serie,i){
+ html.push(''+(serie.label || String.fromCharCode(65+i))+' ');
+ colgroup.push(' ');
+ });
+ html.push(' ');
+ // Data rows
+ _.each(datagrid, function(row){
+ html.push('');
+ _.times(s.length+1, function(i){
+ var tag = 'td',
+ value = row[i],
+ // TODO: do we really want to handle problems with floating point
+ // precision here?
+ content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : '');
+ if (i === 0) {
+ tag = 'th';
+ var label = getRowLabel.call(this, content);
+ if (label) content = label;
+ }
+
+ html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+''+tag+'>');
+ }, this);
+ html.push(' ');
+ }, this);
+ colgroup.push('');
+ t = D.node(html.join(''));
+
+ /**
+ * @TODO disabled this
+ if (!Flotr.isIE || Flotr.isIE == 9) {
+ function handleMouseout(){
+ t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover');
+ }
+ function handleMouseover(e){
+ var td = e.element(),
+ siblings = td.previousSiblings();
+ t.select('th[scope=col]')[siblings.length-1].addClassName('hover');
+ t.select('colgroup col')[siblings.length].addClassName('hover');
+ }
+ _.each(t.select('td'), function(td) {
+ Flotr.EventAdapter.
+ observe(td, 'mouseover', handleMouseover).
+ observe(td, 'mouseout', handleMouseout);
+ });
+ }
+ */
+
+ buttonDownload = D.node(
+ '' +
+ this.options.spreadsheet.toolbarDownload +
+ ' ');
+
+ buttonSelect = D.node(
+ '' +
+ this.options.spreadsheet.toolbarSelectAll+
+ ' ');
+
+ this.
+ observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)).
+ observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this));
+
+ var toolbar = D.node('
');
+ D.insert(toolbar, buttonDownload);
+ D.insert(toolbar, buttonSelect);
+
+ var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2,
+ container = D.node('
');
+
+ D.insert(container, toolbar);
+ D.insert(container, t);
+ D.insert(this.el, container);
+ this.spreadsheet.datagrid = t;
+ this.spreadsheet.container = container;
+
+ return t;
+ },
+ /**
+ * Shows the specified tab, by its name
+ * @todo make a tab manager (Flotr.Tabs)
+ * @param {String} tabName - The tab name
+ */
+ showTab: function(tabName){
+ if (this.spreadsheet.activeTab === tabName){
+ return;
+ }
+ switch(tabName) {
+ case 'graph':
+ D.hide(this.spreadsheet.container);
+ D.removeClass(this.spreadsheet.tabs.data, 'selected');
+ D.addClass(this.spreadsheet.tabs.graph, 'selected');
+ break;
+ case 'data':
+ if (!this.spreadsheet.datagrid)
+ this.spreadsheet.constructDataGrid();
+ D.show(this.spreadsheet.container);
+ D.addClass(this.spreadsheet.tabs.data, 'selected');
+ D.removeClass(this.spreadsheet.tabs.graph, 'selected');
+ break;
+ default:
+ throw 'Illegal tab name: ' + tabName;
+ }
+ this.spreadsheet.activeTab = tabName;
+ },
+ /**
+ * Selects the data table in the DOM for copy/paste
+ */
+ selectAllData: function(){
+ if (this.spreadsheet.tabs) {
+ var selection, range, doc, win, node = this.spreadsheet.constructDataGrid();
+
+ this.spreadsheet.showTab('data');
+
+ // deferred to be able to select the table
+ setTimeout(function () {
+ if ((doc = node.ownerDocument) && (win = doc.defaultView) &&
+ win.getSelection && doc.createRange &&
+ (selection = window.getSelection()) &&
+ selection.removeAllRanges) {
+ range = doc.createRange();
+ range.selectNode(node);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ else if (document.body && document.body.createTextRange &&
+ (range = document.body.createTextRange())) {
+ range.moveToElementText(node);
+ range.select();
+ }
+ }, 0);
+ return true;
+ }
+ else return false;
+ },
+ /**
+ * Converts the data into CSV in order to download a file
+ */
+ downloadCSV: function(){
+ var csv = '',
+ series = this.series,
+ options = this.options,
+ dg = this.spreadsheet.loadDataGrid(),
+ separator = encodeURIComponent(options.spreadsheet.csvFileSeparator);
+
+ if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) {
+ throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")";
+ }
+
+ // The first row
+ _.each(series, function(serie, i){
+ csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"';
+ });
+
+ csv += "%0D%0A"; // \r\n
+
+ // For each row
+ csv += _.reduce(dg, function(memo, row){
+ var rowLabel = getRowLabel.call(this, row[0]) || '';
+ rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"';
+ var numbers = row.slice(1).join(separator);
+ if (options.spreadsheet.decimalSeparator !== '.') {
+ numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator);
+ }
+ return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n
+ }, '', this);
+
+ if (Flotr.isIE && Flotr.isIE < 9) {
+ csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r');
+ window.open().document.write(csv);
+ }
+ else window.open('data:text/csv,'+csv);
+ }
+});
+})();
+
+(function () {
+
+var D = Flotr.DOM;
+
+Flotr.addPlugin('titles', {
+ callbacks: {
+ 'flotr:afterdraw': function() {
+ this.titles.drawTitles();
+ }
+ },
+ /**
+ * Draws the title and the subtitle
+ */
+ drawTitles : function () {
+ var html,
+ options = this.options,
+ margin = options.grid.labelMargin,
+ ctx = this.ctx,
+ a = this.axes;
+
+ if (!options.HtmlText && this.textEnabled) {
+ var style = {
+ size: options.fontSize,
+ color: options.grid.color,
+ textAlign: 'center'
+ };
+
+ // Add subtitle
+ if (options.subtitle){
+ Flotr.drawText(
+ ctx, options.subtitle,
+ this.plotOffset.left + this.plotWidth/2,
+ this.titleHeight + this.subtitleHeight - 2,
+ style
+ );
+ }
+
+ style.weight = 1.5;
+ style.size *= 1.5;
+
+ // Add title
+ if (options.title){
+ Flotr.drawText(
+ ctx, options.title,
+ this.plotOffset.left + this.plotWidth/2,
+ this.titleHeight - 2,
+ style
+ );
+ }
+
+ style.weight = 1.8;
+ style.size *= 0.8;
+
+ // Add x axis title
+ if (a.x.options.title && a.x.used){
+ style.textAlign = a.x.options.titleAlign || 'center';
+ style.textBaseline = 'top';
+ style.angle = Flotr.toRad(a.x.options.titleAngle);
+ style = Flotr.getBestTextAlign(style.angle, style);
+ Flotr.drawText(
+ ctx, a.x.options.title,
+ this.plotOffset.left + this.plotWidth/2,
+ this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin,
+ style
+ );
+ }
+
+ // Add x2 axis title
+ if (a.x2.options.title && a.x2.used){
+ style.textAlign = a.x2.options.titleAlign || 'center';
+ style.textBaseline = 'bottom';
+ style.angle = Flotr.toRad(a.x2.options.titleAngle);
+ style = Flotr.getBestTextAlign(style.angle, style);
+ Flotr.drawText(
+ ctx, a.x2.options.title,
+ this.plotOffset.left + this.plotWidth/2,
+ this.plotOffset.top - a.x2.maxLabel.height - 2 * margin,
+ style
+ );
+ }
+
+ // Add y axis title
+ if (a.y.options.title && a.y.used){
+ style.textAlign = a.y.options.titleAlign || 'right';
+ style.textBaseline = 'middle';
+ style.angle = Flotr.toRad(a.y.options.titleAngle);
+ style = Flotr.getBestTextAlign(style.angle, style);
+ Flotr.drawText(
+ ctx, a.y.options.title,
+ this.plotOffset.left - a.y.maxLabel.width - 2 * margin,
+ this.plotOffset.top + this.plotHeight / 2,
+ style
+ );
+ }
+
+ // Add y2 axis title
+ if (a.y2.options.title && a.y2.used){
+ style.textAlign = a.y2.options.titleAlign || 'left';
+ style.textBaseline = 'middle';
+ style.angle = Flotr.toRad(a.y2.options.titleAngle);
+ style = Flotr.getBestTextAlign(style.angle, style);
+ Flotr.drawText(
+ ctx, a.y2.options.title,
+ this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin,
+ this.plotOffset.top + this.plotHeight / 2,
+ style
+ );
+ }
+ }
+ else {
+ html = [];
+
+ // Add title
+ if (options.title)
+ html.push(
+ '', options.title, '
'
+ );
+
+ // Add subtitle
+ if (options.subtitle)
+ html.push(
+ '', options.subtitle, '
'
+ );
+
+ html.push('');
+
+ html.push('');
+
+ // Add x axis title
+ if (a.x.options.title && a.x.used)
+ html.push(
+ '
', a.x.options.title, '
'
+ );
+
+ // Add x2 axis title
+ if (a.x2.options.title && a.x2.used)
+ html.push(
+ '
', a.x2.options.title, '
'
+ );
+
+ // Add y axis title
+ if (a.y.options.title && a.y.used)
+ html.push(
+ '
', a.y.options.title, '
'
+ );
+
+ // Add y2 axis title
+ if (a.y2.options.title && a.y2.used)
+ html.push(
+ '
', a.y2.options.title, '
'
+ );
+
+ html = html.join('');
+
+ var div = D.create('div');
+ D.setStyles({
+ color: options.grid.color
+ });
+ div.className = 'flotr-titles';
+ D.insert(this.el, div);
+ D.insert(div, html);
+ }
+ }
+});
+})();
diff --git a/addons/web_graph/static/lib/flotr2/flotr2.min.js b/addons/web_graph/static/lib/flotr2/flotr2.min.js
new file mode 100644
index 00000000000..0dce3034464
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/flotr2.min.js
@@ -0,0 +1,27 @@
+/*!
+ * bean.js - copyright Jacob Thornton 2011
+ * https://github.com/fat/bean
+ * MIT License
+ * special thanks to:
+ * dean edwards: http://dean.edwards.name/
+ * dperini: https://github.com/dperini/nwevents
+ * the entire mootools team: github.com/mootools/mootools-core
+ *//*global module:true, define:true*/
+!function(a,b,c){typeof module!="undefined"?module.exports=c(a,b):typeof define=="function"&&typeof define.amd=="object"?define(c):b[a]=c(a,b)}("bean",this,function(a,b){var c=window,d=b[a],e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=Array.prototype.slice,q=/click|mouse|menu|drag|drop/i,r=/^touch|^gesture/i,s={one:1},t=function(a,b,c){for(c=0;c
0){b=b.split(" ");for(j=b.length;j--;)G(a,b[j],c);return a}h=l&&b.replace(g,""),h&&u[h]&&(h=u[h].type);if(!b||l){if(i=l&&b.replace(f,""))i=i.split(".");k(a,h,c,i)}else if(typeof b=="function")k(a,null,b);else for(d in b)b.hasOwnProperty(d)&&G(a,d,b[d]);return a},H=function(a,b,c,d,e){var f,g,h,i,j=c,k=c&&typeof c=="string";if(b&&!c&&typeof b=="object")for(f in b)b.hasOwnProperty(f)&&H.apply(this,[a,f,b[f]]);else{i=arguments.length>3?p.call(arguments,3):[],g=(k?c:b).split(" "),k&&(c=F(b,j=d,e))&&(i=p.call(i,1)),this===s&&(c=C(G,a,b,c,j));for(h=g.length;h--;)E(a,g[h],c,j,i)}return a},I=function(){return H.apply(s,arguments)},J=n?function(a,b,d){var e=l.createEvent(a?"HTMLEvents":"UIEvents");e[a?"initEvent":"initUIEvent"](b,!0,!0,c,1),d.dispatchEvent(e)}:function(a,b,c){c=w(c,a),a?c.fireEvent("on"+b,l.createEventObject()):c["_on"+b]++},K=function(a,b,c){var d,e,h,i,j,k=b.split(" ");for(d=k.length;d--;){b=k[d].replace(g,"");if(i=k[d].replace(f,""))i=i.split(".");if(!i&&!c&&a[o])J(t[b],b,a);else{j=y.get(a,b),c=[!1].concat(c);for(e=0,h=j.length;e=d.computed&&(d={value:a,computed:g})}),d.value},w.min=function(a,b,c){if(!b&&w.isArray(a))return Math.min.apply(Math,a);var d={computed:Infinity};return x(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;gd?1:0}),"value")},w.groupBy=function(a,b){var c={};return x(a,function(a,d){var e=b(a,d);(c[e]||(c[e]=[])).push(a)}),c},w.sortedIndex=function(a,b,c){c||(c=w.identity);var d=0,e=a.length;while(d>1;c(a[f])=0})})},w.difference=function(a,b){return w.filter(a,function(a){return!w.include(b,a)})},w.zip=function(){var a=g.call(arguments),b=w.max(w.pluck(a,"length")),c=new Array(b);for(var d=0;d=0;c--)b=[a[c].apply(this,b)];return b[0]}},w.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}},w.keys=u||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[];for(var c in a)j.call(a,c)&&(b[b.length]=c);return b},w.values=function(a){return w.map(a,w.identity)},w.functions=w.methods=function(a){var b=[];for(var c in a)w.isFunction(a[c])&&b.push(c);return b.sort()},w.extend=function(a){return x(g.call(arguments,1),function(b){for(var c in b)b[c]!==void 0&&(a[c]=b[c])}),a},w.defaults=function(a){return x(g.call(arguments,1),function(b){for(var c in b)a[c]==null&&(a[c]=b[c])}),a},w.clone=function(a){return w.isArray(a)?a.slice():w.extend({},a)},w.tap=function(a,b){return b(a),a},w.isEqual=function(a,b){if(a===b)return!0;var c=typeof a,d=typeof b;if(c!=d)return!1;if(a==b)return!0;if(!a&&b||a&&!b)return!1;a._chain&&(a=a._wrapped),b._chain&&(b=b._wrapped);if(a.isEqual)return a.isEqual(b);if(b.isEqual)return b.isEqual(a);if(w.isDate(a)&&w.isDate(b))return a.getTime()===b.getTime();if(w.isNaN(a)&&w.isNaN(b))return!1;if(w.isRegExp(a)&&w.isRegExp(b))return a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===b.multiline;if(c!=="object")return!1;if(a.length&&a.length!==b.length)return!1;var e=w.keys(a),f=w.keys(b);if(e.length!=f.length)return!1;for(var g in a)if(!(g in b)||!w.isEqual(a[g],b[g]))return!1;return!0},w.isEmpty=function(a){if(w.isArray(a)||w.isString(a))return a.length===0;for(var b in a)if(j.call(a,b))return!1;return!0},w.isElement=function(a){return!!a&&a.nodeType==1},w.isArray=t||function(a){return i.call(a)==="[object Array]"},w.isObject=function(a){return a===Object(a)},w.isArguments=function(a){return!!a&&!!j.call(a,"callee")},w.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)},w.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)},w.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)},w.isNaN=function(a){return a!==a},w.isBoolean=function(a){return a===!0||a===!1},w.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)},w.isRegExp=function(a){return!(!(a&&a.test&&a.exec)||!a.ignoreCase&&a.ignoreCase!==!1)},w.isNull=function(a){return a===null},w.isUndefined=function(a){return a===void 0},w.noConflict=function(){return a._=b,this},w.identity=function(a){return a},w.times=function(a,b,c){for(var d=0;d/g,interpolate:/<%=([\s\S]+?)%>/g},w.template=function(a,b){var c=w.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(c.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(c.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj",d);return b?e(b):e};var B=function(a){this._wrapped=a};w.prototype=B.prototype;var C=function(a,b){return b?w(a).chain():a},D=function(a,b){B.prototype[a]=function(){var a=g.call(arguments);return h.call(a,this._wrapped),C(b.apply(w,a),this._chain)}};w.mixin(w),x(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=d[a];B.prototype[a]=function(){return b.apply(this._wrapped,arguments),C(this._wrapped,this._chain)}}),x(["concat","join","slice"],function(a){var b=d[a];B.prototype[a]=function(){return C(b.apply(this._wrapped,arguments),this._chain)}}),B.prototype.chain=function(){return this._chain=!0,this},B.prototype.value=function(){return this._wrapped}})();
+/**
+ * Flotr2 (c) 2012 Carl Sutherland
+ * MIT License
+ * Special thanks to:
+ * Flotr: http://code.google.com/p/flotr/ (fork)
+ * Flot: https://github.com/flot/flot (original fork)
+ */
+(function(){var a=this,b=this.Flotr,c;c={_:_,bean:bean,isIphone:/iphone/i.test(navigator.userAgent),isIE:navigator.appVersion.indexOf("MSIE")!=-1?parseFloat(navigator.appVersion.split("MSIE")[1]):!1,graphTypes:{},plugins:{},addType:function(a,b){c.graphTypes[a]=b,c.defaultOptions[a]=b.options||{},c.defaultOptions.defaultType=c.defaultOptions.defaultType||a},addPlugin:function(a,b){c.plugins[a]=b,c.defaultOptions[a]=b.options||{}},draw:function(a,b,d,e){return e=e||c.Graph,new e(a,b,d)},merge:function(a,b){var d,e,f=b||{};for(d in a)e=a[d],e&&typeof e=="object"?e.constructor===Array?f[d]=this._.clone(e):e.constructor!==RegExp&&!this._.isElement(e)?f[d]=c.merge(e,b?b[d]:undefined):f[d]=e:f[d]=e;return f},clone:function(a){return c.merge(a,{})},getTickSize:function(a,b,d,e){var f=(d-b)/a,g=c.getMagnitude(f),h=10,i=f/g;return i<1.5?h=1:i<2.25?h=2:i<3?h=e===0?2:2.5:i<7.5&&(h=5),h*g},defaultTickFormatter:function(a,b){return a+""},defaultTrackFormatter:function(a){return"("+a.x+", "+a.y+")"},engineeringNotation:function(a,b,c){var d=["Y","Z","E","P","T","G","M","k",""],e=["y","z","a","f","p","n","µ","m",""],f=d.length;c=c||1e3,b=Math.pow(10,b||2);if(a===0)return 0;if(a>1)while(f--&&a>=c)a/=c;else{d=e,f=d.length;while(f--&&a<1)a*=c}return Math.round(a*b)/b+d[f]},getMagnitude:function(a){return Math.pow(10,Math.floor(Math.log(a)/Math.LN10))},toPixel:function(a){return Math.floor(a)+.5},toRad:function(a){return-a*(Math.PI/180)},floorInBase:function(a,b){return b*Math.floor(a/b)},drawText:function(a,b,d,e,f){if(!a.fillText){a.drawText(b,d,e,f);return}f=this._.extend({size:c.defaultOptions.fontSize,color:"#000000",textAlign:"left",textBaseline:"bottom",weight:1,angle:0},f),a.save(),a.translate(d,e),a.rotate(f.angle),a.fillStyle=f.color,a.font=(f.weight>1?"bold ":"")+f.size*1.3+"px sans-serif",a.textAlign=f.textAlign,a.textBaseline=f.textBaseline,a.fillText(b,0,0),a.restore()},getBestTextAlign:function(a,b){return b=b||{textAlign:"center",textBaseline:"middle"},a+=c.getTextAngleFromAlign(b),Math.abs(Math.cos(a))>.01&&(b.textAlign=Math.cos(a)>0?"right":"left"),Math.abs(Math.sin(a))>.01&&(b.textBaseline=Math.sin(a)>0?"top":"bottom"),b},alignTable:{"right middle":0,"right top":Math.PI/4,"center top":Math.PI/2,"left top":3*(Math.PI/4),"left middle":Math.PI,"left bottom":-3*(Math.PI/4),"center bottom":-Math.PI/2,"right bottom":-Math.PI/4,"center middle":0},getTextAngleFromAlign:function(a){return c.alignTable[a.textAlign+" "+a.textBaseline]||0},noConflict:function(){return a.Flotr=b,this}},a.Flotr=c})(),Flotr.defaultOptions={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],ieBackgroundColor:"#FFFFFF",title:null,subtitle:null,shadowSize:4,defaultType:null,HtmlText:!0,fontColor:"#545454",fontSize:7.5,resolution:1,parseFloat:!0,xaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:0,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,mode:"normal",timeFormat:null,timeMode:"UTC",timeUnit:"millisecond",scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},x2axis:{},yaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:90,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},y2axis:{titleAngle:270},grid:{color:"#545454",backgroundColor:null,backgroundImage:null,watermarkAlpha:.4,tickColor:"#DDDDDD",labelMargin:3,verticalLines:!0,minorVerticalLines:null,horizontalLines:!0,minorHorizontalLines:null,outlineWidth:1,outline:"nsew",circular:!1},mouse:{track:!1,trackAll:!1,position:"se",relative:!1,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,trackY:!0,radius:3,fillColor:null,fillOpacity:.4}},function(){function b(a,b,c,d){this.rgba=["r","g","b","a"];var e=4;while(-1<--e)this[this.rgba[e]]=arguments[e]||(e==3?1:0);this.normalize()}var a=Flotr._,c={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]};b.prototype={scale:function(b,c,d,e){var f=4;while(-1<--f)a.isUndefined(arguments[f])||(this[this.rgba[f]]*=arguments[f]);return this.normalize()},alpha:function(b){return!a.isUndefined(b)&&!a.isNull(b)&&(this.a=b),this.normalize()},clone:function(){return new b(this.r,this.b,this.g,this.a)},limit:function(a,b,c){return Math.max(Math.min(a,c),b)},normalize:function(){var a=this.limit;return this.r=a(parseInt(this.r,10),0,255),this.g=a(parseInt(this.g,10),0,255),this.b=a(parseInt(this.b,10),0,255),this.a=a(this.a,0,1),this},distance:function(a){if(!a)return;a=new b.parse(a);var c=0,d=3;while(-1<--d)c+=Math.abs(this[this.rgba[d]]-a[this.rgba[d]]);return c},toString:function(){return this.a>=1?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"},contrast:function(){var a=1-(.299*this.r+.587*this.g+.114*this.b)/255;return a<.5?"#000000":"#ffffff"}},a.extend(b,{parse:function(a){if(a instanceof b)return a;var d;if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))return new b(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16));if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10));if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))return new b(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16));if(d=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]));if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55);if(d=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]));var e=(a+"").replace(/^\s*([\S\s]*?)\s*$/,"$1").toLowerCase();return e=="transparent"?new b(255,255,255,0):(d=c[e])?new b(d[0],d[1],d[2]):new b(0,0,0,0)},processColor:function(c,d){var e=d.opacity;if(!c)return"rgba(0, 0, 0, 0)";if(c instanceof b)return c.alpha(e).toString();if(a.isString(c))return b.parse(c).alpha(e).toString();var f=c.colors?c:{colors:c};if(!d.ctx)return a.isArray(f.colors)?b.parse(a.isArray(f.colors[0])?f.colors[0][1]:f.colors[0]).alpha(e).toString():"rgba(0, 0, 0, 0)";f=a.extend({start:"top",end:"bottom"},f),/top/i.test(f.start)&&(d.x1=0),/left/i.test(f.start)&&(d.y1=0),/bottom/i.test(f.end)&&(d.x2=0),/right/i.test(f.end)&&(d.y2=0);var g,h,i,j=d.ctx.createLinearGradient(d.x1,d.y1,d.x2,d.y2);for(g=0;g=m)break}m=e[p][0],n=e[p][1],n=="year"&&(m=Flotr.getTickSize(f.noTicks*d.year,i,j,0),m==.5&&(n="month",m=6)),a.tickUnit=n,a.tickSize=m;var q=new Date(i),r=m*d[n];switch(n){case"millisecond":s("Milliseconds");break;case"second":s("Seconds");break;case"minute":s("Minutes");break;case"hour":s("Hours");break;case"month":s("Month");break;case"year":s("FullYear")}r>=d.second&&b(q,"Milliseconds",g,0),r>=d.minute&&b(q,"Seconds",g,0),r>=d.hour&&b(q,"Minutes",g,0),r>=d.day&&b(q,"Hours",g,0),r>=d.day*4&&b(q,"Date",g,1),r>=d.year&&b(q,"Month",g,0);var t=0,u=NaN,v;do{v=u,u=q.getTime(),l.push({v:u/h,label:o(u/h,a)});if(n=="month")if(m<1){b(q,"Date",g,1);var w=q.getTime();b(q,"Month",g,c(q,"Month",g)+1);var x=q.getTime();q.setTime(u+t*d.hour+(x-w)*m),t=c(q,"Hours",g),b(q,"Hours",g,0)}else b(q,"Month",g,c(q,"Month",g)+m);else n=="year"?b(q,"FullYear",g,c(q,"FullYear",g)+m):q.setTime(u+r)}while(u0)return{x:b.touches[0].pageX,y:b.touches[0].pageY};if(!a._.isUndefined(b.changedTouches)&&b.changedTouches.length>0)return{x:b.changedTouches[0].pageX,y:b.changedTouches[0].pageY};if(b.pageX||b.pageY)return{x:b.pageX,y:b.pageY};if(b.clientX||b.clientY){var c=document,d=c.body,e=c.documentElement;return{x:b.clientX+d.scrollLeft+e.scrollLeft,y:b.clientY+d.scrollTop+e.scrollTop}}}}}(),function(){var a=Flotr,b=a.DOM,c=a._,d=function(a){this.o=a};d.prototype={dimensions:function(a,b,c,d){return a?this.o.html?this.html(a,this.o.element,c,d):this.canvas(a,b):{width:0,height:0}},canvas:function(b,c){if(!this.o.textEnabled)return;c=c||{};var d=this.measureText(b,c),e=d.width,f=c.size||a.defaultOptions.fontSize,g=c.angle||0,h=Math.cos(g),i=Math.sin(g),j=2,k=6,l;return l={width:Math.abs(h*e)+Math.abs(i*f)+j,height:Math.abs(i*e)+Math.abs(h*f)+k},l},html:function(a,c,d,e){var f=b.create("div");return b.setStyles(f,{position:"absolute",top:"-10000px"}),b.insert(f,''+a+"
"),b.insert(this.o.element,f),b.size(f)},measureText:function(b,d){var e=this.o.ctx,f;return!e.fillText||a.isIphone&&e.measure?{width:e.measure(b,d)}:(d=c.extend({size:a.defaultOptions.fontSize,weight:1,angle:0},d),e.save(),e.font=(d.weight>1?"bold ":"")+d.size*1.3+"px sans-serif",f=e.measureText(b),e.restore(),f)}},Flotr.Text=d}(),function(){function e(a,c,d){return b.observe.apply(this,arguments),this._handles.push(arguments),this}var a=Flotr.DOM,b=Flotr.EventAdapter,c=Flotr._,d=Flotr;Graph=function(a,e,f){this._setEl(a),this._initMembers(),this._initPlugins(),b.fire(this.el,"flotr:beforeinit",[this]),this.data=e,this.series=d.Series.getSeries(e),this._initOptions(f),this._initGraphTypes(),this._initCanvas(),this._text=new d.Text({element:this.el,ctx:this.ctx,html:this.options.HtmlText,textEnabled:this.textEnabled}),b.fire(this.el,"flotr:afterconstruct",[this]),this._initEvents(),this.findDataRanges(),this.calculateSpacing(),this.draw(c.bind(function(){b.fire(this.el,"flotr:afterinit",[this])},this))},Graph.prototype={destroy:function(){b.fire(this.el,"flotr:destroy"),c.each(this._handles,function(a){b.stopObserving.apply(this,a)}),this._handles=[],this.el.graph=null},observe:e,_observe:e,processColor:function(a,b){var e={x1:0,y1:0,x2:this.plotWidth,y2:this.plotHeight,opacity:1,ctx:this.ctx};return c.extend(e,b),d.Color.processColor(a,e)},findDataRanges:function(){var a=this.axes,b,e,f;c.each(this.series,function(a){f=a.getRange(),f&&(b=a.xaxis,e=a.yaxis,b.datamin=Math.min(f.xmin,b.datamin),b.datamax=Math.max(f.xmax,b.datamax),e.datamin=Math.min(f.ymin,e.datamin),e.datamax=Math.max(f.ymax,e.datamax),b.used=b.used||f.xused,e.used=e.used||f.yused)},this),!a.x.used&&!a.x2.used&&(a.x.used=!0),!a.y.used&&!a.y2.used&&(a.y.used=!0),c.each(a,function(a){a.calculateRange()});var g=c.keys(d.graphTypes),h=!1;c.each(this.series,function(a){if(a.hide)return;c.each(g,function(b){a[b]&&a[b].show&&(this.extendRange(b,a),h=!0)},this),h||this.extendRange(this.options.defaultType,a)},this)},extendRange:function(a,b){this[a].extendRange&&this[a].extendRange(b,b.data,b[a],this[a]),this[a].extendYRange&&this[a].extendYRange(b.yaxis,b.data,b[a],this[a]),this[a].extendXRange&&this[a].extendXRange(b.xaxis,b.data,b[a],this[a])},calculateSpacing:function(){var a=this.axes,b=this.options,d=this.series,e=b.grid.labelMargin,f=this._text,g=a.x,h=a.x2,i=a.y,j=a.y2,k=b.grid.outlineWidth,l,m,n,o;c.each(a,function(a){a.calculateTicks(),a.calculateTextDimensions(f,b)}),o=f.dimensions(b.title,{size:b.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title"),this.titleHeight=o.height,o=f.dimensions(b.subtitle,{size:b.fontSize},"font-size:smaller;","flotr-subtitle"),this.subtitleHeight=o.height;for(m=0;m1&&(this.multitouches=c.touches),b.fire(a,"flotr:mousedown",[event,this]),this.observe(document,"touchend",d)},this)),this.observe(this.overlay,"touchmove",c.bind(function(c){var d=this.getEventPosition(c);c.preventDefault(),e=!0,this.multitouches||c.touches&&c.touches.length>1?this.multitouches=c.touches:f||b.fire(a,"flotr:mousemove",[event,d,this]),this.lastMousePos=d},this))):this.observe(this.overlay,"mousedown",c.bind(this.mouseDownHandler,this)).observe(a,"mousemove",c.bind(this.mouseMoveHandler,this)).observe(this.overlay,"click",c.bind(this.clickHandler,this)).observe(a,"mouseout",function(){b.fire(a,"flotr:mouseout")})},_initCanvas:function(){function k(e,f){return e||(e=a.create("canvas"),typeof FlashCanvas!="undefined"&&typeof e.getContext=="function"&&FlashCanvas.initElement(e),e.className="flotr-"+f,e.style.cssText="position:absolute;left:0px;top:0px;",a.insert(b,e)),c.each(i,function(b,c){a.show(e);if(f=="canvas"&&e.getAttribute(c)===b)return;e.setAttribute(c,b*d.resolution),e.style[c]=b+"px"}),e.context_=null,e}function l(a){window.G_vmlCanvasManager&&window.G_vmlCanvasManager.initElement(a);var b=a.getContext("2d");return window.G_vmlCanvasManager||b.scale(d.resolution,d.resolution),b}var b=this.el,d=this.options,e=b.children,f=[],g,h,i,j;for(h=e.length;h--;)g=e[h],!this.canvas&&g.className==="flotr-canvas"?this.canvas=g:!this.overlay&&g.className==="flotr-overlay"?this.overlay=g:f.push(g);for(h=f.length;h--;)b.removeChild(f[h]);a.setStyles(b,{position:"relative"}),i={},i.width=b.clientWidth,i.height=b.clientHeight;if(i.width<=0||i.height<=0||d.resolution<=0)throw"Invalid dimensions for plot, width = "+i.width+", height = "+i.height+", resolution = "+d.resolution;this.canvas=k(this.canvas,"canvas"),this.overlay=k(this.overlay,"overlay"),this.ctx=l(this.canvas),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.octx=l(this.overlay),this.octx.clearRect(0,0,this.overlay.width,this.overlay.height),this.canvasHeight=i.height,this.canvasWidth=i.width,this.textEnabled=!!this.ctx.drawText||!!this.ctx.fillText},_initPlugins:function(){c.each(d.plugins,function(a,b){c.each(a.callbacks,function(a,b){this.observe(this.el,b,c.bind(a,this))},this),this[b]=d.clone(a),c.each(this[b],function(a,d){c.isFunction(a)&&(this[b][d]=c.bind(a,this))},this)},this)},_initOptions:function(a){var e=d.clone(d.defaultOptions);e.x2axis=c.extend(c.clone(e.xaxis),e.x2axis),e.y2axis=c.extend(c.clone(e.yaxis),e.y2axis),this.options=d.merge(a||{},e),this.options.grid.minorVerticalLines===null&&this.options.xaxis.scaling==="logarithmic"&&(this.options.grid.minorVerticalLines=!0),this.options.grid.minorHorizontalLines===null&&this.options.yaxis.scaling==="logarithmic"&&(this.options.grid.minorHorizontalLines=!0),b.fire(this.el,"flotr:afterinitoptions",[this]),this.axes=d.Axis.getAxes(this.options);var f=[],g=[],h=this.series.length,i=this.series.length,j=this.options.colors,k=[],l=0,m,n,o,p;for(n=i-1;n>-1;--n)m=this.series[n].color,m&&(--i,c.isNumber(m)?f.push(m):k.push(d.Color.parse(m)));for(n=f.length-1;n>-1;--n)i=Math.max(i,f[n]+1);for(n=0;g.length=j.length&&(n=0,++l)}for(n=0,o=0;n10?b.minorTickFreq=0:g-h>5?b.minorTickFreq=2:b.minorTickFreq=5)}else a.tickSize=Flotr.getTickSize(b.noTicks,c,d,b.tickDecimals);a.min=c,a.max=d,b.min===null&&b.autoscale&&(a.min-=a.tickSize*e,a.min<0&&a.datamin>=0&&(a.min=0),a.min=a.tickSize*Math.floor(a.min/a.tickSize)),b.max===null&&b.autoscale&&(a.max+=a.tickSize*e,a.max>0&&a.datamax<=0&&a.datamax!=a.datamin&&(a.max=0),a.max=a.tickSize*Math.ceil(a.max/a.tickSize)),a.min==a.max&&(a.max=a.min+1)},calculateTextDimensions:function(a,b){var c="",d,e;if(this.options.showLabels)for(e=0;ec.length&&(c=this.ticks[e].label);this.maxLabel=a.dimensions(c,{size:b.fontSize,angle:Flotr.toRad(this.options.labelsAngle)},"font-size:smaller;","flotr-grid-label"),this.titleSize=a.dimensions(this.options.title,{size:b.fontSize*1.2,angle:Flotr.toRad(this.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},_cleanUserTicks:function(b,c){var d=this,e=this.options,f,g,h,i;a.isFunction(b)&&(b=b({min:d.min,max:d.max}));for(g=0;g1?i[1]:e.tickFormatter(f,{min:d.min,max:d.max})):(f=i,h=e.tickFormatter(f,{min:this.min,max:this.max})),c[g]={v:f,label:h}},_calculateTimeTicks:function(){this.ticks=Flotr.Date.generator(this)},_calculateLogTicks:function(){var a=this,b=a.options,c,d,e=Math.log(a.max);b.base!=Math.E&&(e/=Math.log(b.base)),e=Math.ceil(e);var f=Math.log(a.min);b.base!=Math.E&&(f/=Math.log(b.base)),f=Math.ceil(f);for(i=f;ie&&(e=i,g=!0),jf&&(f=j,h=!0);return{xmin:c,xmax:e,ymin:d,ymax:f,xused:g,yused:h}}},a.extend(b,{getSeries:function(c){return a.map(c,function(c){var d;return c.data?(d=new b,a.extend(d,c)):d=new b({data:c}),d})}}),Flotr.Series=b}(),Flotr.addType("lines",{options:{show:!1,lineWidth:2,fill:!1,fillBorder:!1,fillColor:null,fillOpacity:.4,steps:!1,stacked:!1},stack:{values:[]},draw:function(a){var b=a.context,c=a.lineWidth,d=a.shadowSize,e;b.save(),b.lineJoin="round",d&&(b.lineWidth=d/2,e=c/2+b.lineWidth/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,e+d/2,!1),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,e,!1)),b.lineWidth=c,b.strokeStyle=a.color,this.plot(a,0,!0),b.restore()},plot:function(a,b,c){var d=a.context,e=a.width,f=a.height,g=a.xScale,h=a.yScale,i=a.data,j=a.stacked?this.stack:!1,k=i.length-1,l=null,m=null,n=h(0),o,p,q,r,s,t,u;if(k<1)return;d.beginPath();for(u=0;uf&&r>f||q<0&&r<0||o<0&&p<0||o>e&&p>e)continue;(l!=o||m!=q+b)&&d.moveTo(o,q+b),l=p,m=r+b,a.steps?(d.lineTo(l+b/2,q+b),d.lineTo(l+b/2,m)):d.lineTo(l,m)}(!a.fill||a.fill&&!a.fillBorder)&&d.stroke(),!b&&a.fill&&(o=g(i[0][0]),d.fillStyle=a.fillStyle,d.lineTo(p,n),d.lineTo(o,n),d.lineTo(o,h(i[0][1])),d.fill(),a.fillBorder&&d.stroke()),d.closePath()},extendYRange:function(a,b,c,d){var e=a.options;if(c.stacked&&(!e.max&&e.max!==0||!e.min&&e.min!==0)){var f=a.max,g=a.min,h=d.positiveSums||{},i=d.negativeSums||{},j,k;for(k=0;k0?(h[j]=(h[j]||0)+b[k][1],f=Math.max(f,h[j])):(i[j]=(i[j]||0)+b[k][1],g=Math.min(g,i[j]));d.negativeSums=i,d.positiveSums=h,a.max=f,a.min=g}c.steps&&(this.hit=function(a){var b=a.data,c=a.args,d=a.yScale,e=c[0],f=b.length,g=c[1],h=e.x,i=e.relY,j;for(j=0;j=b[j][0]&&h<=b[j+1][0]){Math.abs(d(b[j][1])-i)<8&&(g.x=b[j][0],g.y=b[j][1],g.index=j,g.seriesIndex=a.index);break}},this.drawHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=c.index,g=e(c.x),h=a.yScale(c.y),i;d.length-1>f&&(i=a.xScale(d[f+1][0]),b.save(),b.strokeStyle=a.color,b.lineWidth=a.lineWidth,b.beginPath(),b.moveTo(g,h),b.lineTo(i,h),b.stroke(),b.closePath(),b.restore())},this.clearHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=a.lineWidth,g=c.index,h=e(c.x),i=a.yScale(c.y),j;d.length-1>g&&(j=a.xScale(d[g+1][0]),b.clearRect(h-f,i-f,j-h+2*f,2*f))})}}),Flotr.addType("bars",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,horizontal:!1,stacked:!1,centered:!0,topPadding:.1},stack:{positive:[],negative:[],_positive:[],_negative:[]},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineWidth=a.lineWidth,b.strokeStyle=a.color,a.fill&&(b.fillStyle=a.fillStyle),this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.shadowSize,e,f,g,h,i,j;if(b.length<1)return;this.translate(c,a.horizontal);for(e=0;e0?g.positive:g.negative,n=o[l]||n,o[l]=n+m),p=j(l-i),q=j(l+e-i),r=k(m+n),s=k(n),s<0&&(s=0),a===null||b===null?null:{x:l,y:m,xScale:j,yScale:k,top:r,left:Math.min(p,q)-h/2,width:Math.abs(q-p)-h,height:s-r}},hit:function(a){var b=a.data,c=a.args,d=c[0],e=c[1],f=d.x,g=d.y,h=this.getBarGeometry(f,g,a),i=h.width/2,j=h.left,k,l;for(l=b.length;l--;)k=this.getBarGeometry(b[l][0],b[l][1],a),k.y>h.y&&Math.abs(j-k.left)0?(j[l]=(j[l]||0)+m,g=Math.max(g,j[l])):(k[l]=(k[l]||0)+m,f=Math.min(f,k[l]));(i==1&&h||i==-1&&!h)&&c.topPadding&&(a.max===a.datamax||c.stacked&&this.stackMax!==g)&&(g+=c.topPadding*(g-f)),this.stackMin=f,this.stackMax=g,this.negativeSums=k,this.positiveSums=j,a.max=g,a.min=f}}),Flotr.addType("bubbles",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,baseRadius:2},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e,f,g,h,i;b=b||0;for(f=0;fr?"downFillColor":"upFillColor"],a.fill&&!a.barcharts&&(c.fillStyle="rgba(0,0,0,0.05)",c.fillRect(s+g,x+g,t-s,w-x),c.save(),c.globalAlpha=a.fillOpacity,c.fillStyle=k,c.fillRect(s,x+h,t-s,w-x),c.restore());if(h||i)m=Math.floor((s+t)/2)+j,c.strokeStyle=k,c.beginPath(),a.barcharts?(c.moveTo(m,Math.floor(v+f)),c.lineTo(m,Math.floor(u+f)),n=Math.floor(o+f)+.5,c.moveTo(Math.floor(s)+j,n),c.lineTo(m,n),n=Math.floor(r+f)+.5,c.moveTo(Math.floor(t)+j,n),c.lineTo(m,n)):(c.strokeRect(s,x+h,t-s,w-x),c.moveTo(m,Math.floor(x+h)),c.lineTo(m,Math.floor(v+h)),c.moveTo(m,Math.floor(w+h)),c.lineTo(m,Math.floor(u+h))),c.closePath(),c.stroke()}},extendXRange:function(a,b,c){a.options.max===null&&(a.max=Math.max(a.datamax+.5,a.max),a.min=Math.min(a.datamin-.5,a.min))}}),Flotr.addType("gantt",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(a){var b=this.ctx,c=a.gantt.barWidth,d=Math.min(a.gantt.lineWidth,c);b.save(),b.translate(this.plotOffset.left,this.plotOffset.top),b.lineJoin="miter",b.lineWidth=d,b.strokeStyle=a.color,b.save(),this.gantt.plotShadows(a,c,0,a.gantt.fill),b.restore();if(a.gantt.fill){var e=a.gantt.fillColor||a.color;b.fillStyle=this.processColor(e,{opacity:a.gantt.fillOpacity})}this.gantt.plot(a,c,0,a.gantt.fill),b.restore()},plot:function(a,b,c,d){var e=a.data;if(e.length<1)return;var f=a.xaxis,g=a.yaxis,h=this.ctx,i;for(i=0;if.max||sg.max)continue;pf.max&&(q=f.max,f.lastSerie!=a&&(n=!1)),rg.max&&(s=g.max,g.lastSerie!=a&&(n=!1)),d&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h.lineTo(f.d2p(p),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(r)+c),h.fill(),h.closePath()),a.gantt.lineWidth&&(m||o||n)&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h[m?"lineTo":"moveTo"](f.d2p(p),g.d2p(s)+c),h[n?"lineTo":"moveTo"](f.d2p(q),g.d2p(s)+c),h[o?"lineTo":"moveTo"](f.d2p(q),g.d2p(r)+c),h.stroke(),h.closePath())}},plotShadows:function(a,b,c){var d=a.data;if(d.length<1)return;var e,f,g,h,i=a.xaxis,j=a.yaxis,k=this.ctx,l=this.options.shadowSize;for(e=0;ei.max||pj.max)continue;mi.max&&(n=i.max),oj.max&&(p=j.max);var q=i.d2p(n)-i.d2p(m)-(i.d2p(n)+l<=this.plotWidth?0:l),r=j.d2p(o)-j.d2p(p)-(j.d2p(o)+l<=this.plotHeight?0:l);k.fillStyle="rgba(0,0,0,0.05)",k.fillRect(Math.min(i.d2p(m)+l,this.plotWidth),Math.min(j.d2p(p)+l,this.plotHeight),q,r)}},extendXRange:function(a){if(a.options.max===null){var b=a.min,c=a.max,d,e,f,g,h,i={},j={},k=null;for(d=0;db&&(b=a.max+g.barWidth)}}a.lastSerie=j,a.max=b,a.min=c,a.tickSize=Flotr.getTickSize(a.options.noTicks,c,b,a.options.tickDecimals)}}}),function(){function a(a){return typeof a=="object"&&a.constructor&&(Image?!0:a.constructor===Image)}Flotr.defaultMarkerFormatter=function(a){return Math.round(a.y*100)/100+""},Flotr.addType("markers",{options:{show:!1,lineWidth:1,color:"#000000",fill:!1,fillColor:"#FFFFFF",fillOpacity:.4,stroke:!1,position:"ct",verticalMargin:0,labelFormatter:Flotr.defaultMarkerFormatter,fontSize:Flotr.defaultOptions.fontSize,stacked:!1,stackingType:"b",horizontal:!1},stack:{positive:[],negative:[],values:[]},draw:function(a){function m(a,b){return g=d.negative[a]||0,f=d.positive[a]||0,b>0?(d.positive[a]=g+b,g+b):(d.negative[a]=f+b,f+b)}var b=a.data,c=a.context,d=a.stacked?a.stack:!1,e=a.stackingType,f,g,h,i,j,k,l;c.save(),c.lineJoin="round",c.lineWidth=a.lineWidth,c.strokeStyle="rgba(0,0,0,0.5)",c.fillStyle=a.fillStyle;for(i=0;i0?"top":"bottom",B,C,D,x,y;c.save(),c.translate(i/2,h/2),c.scale(1,q),C=Math.cos(u)*j,D=Math.sin(u)*j,f>0&&(this.plotSlice(C+f,D+f,n,s,t,c),l&&(c.fillStyle="rgba(0,0,0,0.1)",c.fill())),this.plotSlice(C,D,n,s,t,c),l&&(c.fillStyle=m,c.fill()),c.lineWidth=e,c.strokeStyle=k,c.stroke(),B={size:a.fontSize*1.2,color:a.fontColor,weight:1.5},v&&(a.htmlText||!a.textEnabled?(divStyle="position:absolute;"+A+":"+(h/2+(A==="top"?y:-y))+"px;",divStyle+=z+":"+(i/2+(z==="right"?-x:x))+"px;",p.push('',v,"
")):(B.textAlign=z,B.textBaseline=A,Flotr.drawText(c,v,x,y,B)));if(a.htmlText||!a.textEnabled){var E=Flotr.DOM.node('
');Flotr.DOM.insert(E,p.join("")),Flotr.DOM.insert(a.element,E)}c.restore(),this.startAngle=t,this.slices=this.slices||[],this.slices.push({radius:Math.min(d.width,d.height)*g/2,x:C,y:D,explode:j,start:s,end:t})},plotSlice:function(a,b,c,d,e,f){f.beginPath(),f.moveTo(a,b),f.arc(a,b,c,d,e,!1),f.lineTo(a,b),f.closePath()},hit:function(a){var b=a.data[0],c=a.args,d=a.index,e=c[0],f=c[1],g=this.slices[d],h=e.relX-a.width/2,i=e.relY-a.height/2,j=Math.sqrt(h*h+i*i),k=Math.atan(i/h),l=Math.PI*2,m=g.explode||a.explode,n=g.start%l,o=g.end%l;h<0?k+=Math.PI:h>0&&i<0&&(k+=l),jm&&(n>o&&(kn)||k>n&&k0&&(b.lineWidth=d/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,d/2+b.lineWidth/2),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,b.lineWidth/2)),b.lineWidth=a.lineWidth,b.strokeStyle=a.color,b.fillStyle=a.fillColor||a.color,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=a.xScale,f=a.yScale,g,h,i;for(g=c.length-1;g>-1;--g){i=c[g][1];if(i===null)continue;h=e(c[g][0]),i=f(i);if(h<0||h>a.width||i<0||i>a.height)continue;d.beginPath(),b?d.arc(h,i+b,a.radius,0,Math.PI,!1):(d.arc(h,i,a.radius,0,2*Math.PI,!0),a.fill&&d.fill()),d.stroke(),d.closePath()}}}),Flotr.addType("radar",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,radiusRatio:.9},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.translate(a.width/2,a.height/2),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=Math.min(a.height,a.width)*a.radiusRatio/2,f=2*Math.PI/c.length,g=-Math.PI/2,h,i;b=b||0,d.beginPath();for(h=0;hthis.plotWidth||b.relY>this.plotHeight){this.el.style.cursor=null,a.removeClass(this.el,"flotr-crosshair");return}d.hideCursor&&(this.el.style.cursor="none",a.addClass(this.el,"flotr-crosshair")),c.save(),c.strokeStyle=d.color,c.lineWidth=1,c.beginPath(),d.mode.indexOf("x")!=-1&&(c.moveTo(f,e.top),c.lineTo(f,e.top+this.plotHeight)),d.mode.indexOf("y")!=-1&&(c.moveTo(e.left,g),c.lineTo(e.left+this.plotWidth,g)),c.stroke(),c.restore()},clearCrosshair:function(){var a=this.plotOffset,b=this.lastMousePos,c=this.octx;b&&(c.clearRect(b.relX+a.left,a.top,1,this.plotHeight+1),c.clearRect(a.left,b.relY+a.top,this.plotWidth+1,1))}})}(),function(){function c(a,b,c,d){var e="image/"+a,f=b.toDataURL(e),g=new Image;return g.src=f,g}var a=Flotr.DOM,b=Flotr._;Flotr.addPlugin("download",{saveImage:function(d,e,f,g){var h=null;if(Flotr.isIE&&Flotr.isIE<9)return h=""+this.canvas.firstChild.innerHTML+"",window.open().document.write(h);if(d!=="jpeg"&&d!=="png")return;h=c(d,this.canvas,e,f);if(b.isElement(h)&&g)this.download.restoreCanvas(),a.hide(this.canvas),a.hide(this.overlay),a.setStyles({position:"absolute"}),a.insert(this.el,h),this.saveImageElement=h;else return window.open(h.src)},restoreCanvas:function(){a.show(this.canvas),a.show(this.overlay),this.saveImageElement&&this.el.removeChild(this.saveImageElement),this.saveImageElement=null}})}(),function(){var a=Flotr.EventAdapter,b=Flotr._;Flotr.addPlugin("graphGrid",{callbacks:{"flotr:beforedraw":function(){this.graphGrid.drawGrid()},"flotr:afterdraw":function(){this.graphGrid.drawOutline()}},drawGrid:function(){function p(a){for(n=0;n=l.max||(a==l.min||a==l.max)&&e.outlineWidth)return;d(Math.floor(l.d2p(a))+c.lineWidth/2)})}function r(a){c.moveTo(a,0),c.lineTo(a,j)}function s(a){c.moveTo(0,a),c.lineTo(k,a)}var c=this.ctx,d=this.options,e=d.grid,f=e.verticalLines,g=e.horizontalLines,h=e.minorVerticalLines,i=e.minorHorizontalLines,j=this.plotHeight,k=this.plotWidth,l,m,n,o;(f||h||g||i)&&a.fire(this.el,"flotr:beforegrid",[this.axes.x,this.axes.y,d,this]),c.save(),c.lineWidth=1,c.strokeStyle=e.tickColor;if(e.circular){c.translate(this.plotOffset.left+k/2,this.plotOffset.top+j/2);var t=Math.min(j,k)*d.radar.radiusRatio/2,u=this.axes.x.ticks.length,v=2*(Math.PI/u),w=-Math.PI/2;c.beginPath(),l=this.axes.y,g&&p(l.ticks),i&&p(l.minorTicks),f&&b.times(u,function(a){c.moveTo(0,0),c.lineTo(Math.cos(a*v+w)*t,Math.sin(a*v+w)*t)}),c.stroke()}else c.translate(this.plotOffset.left,this.plotOffset.top),e.backgroundColor&&(c.fillStyle=this.processColor(e.backgroundColor,{x1:0,y1:0,x2:k,y2:j}),c.fillRect(0,0,k,j)),c.beginPath(),l=this.axes.x,f&&q(l.ticks,r),h&&q(l.minorTicks,r),l=this.axes.y,g&&q(l.ticks,s),i&&q(l.minorTicks,s),c.stroke();c.restore(),(f||h||g||i)&&a.fire(this.el,"flotr:aftergrid",[this.axes.x,this.axes.y,d,this])},drawOutline:function(){var a=this,b=a.options,c=b.grid,d=c.outline,e=a.ctx,f=c.backgroundImage,g=a.plotOffset,h=g.left,j=g.top,k=a.plotWidth,l=a.plotHeight,m,n,o,p,q,r;if(!c.outlineWidth)return;e.save();if(c.circular){e.translate(h+k/2,j+l/2);var s=Math.min(l,k)*b.radar.radiusRatio/2,t=this.axes.x.ticks.length,u=2*(Math.PI/t),v=-Math.PI/2;e.beginPath(),e.lineWidth=c.outlineWidth,e.strokeStyle=c.color,e.lineJoin="round";for(i=0;i<=t;++i)e[i===0?"moveTo":"lineTo"](Math.cos(i*u+v)*s,Math.sin(i*u+v)*s);e.stroke()}else{e.translate(h,j);var w=c.outlineWidth,x=.5-w+(w+1)%2/2,y="lineTo",z="moveTo";e.lineWidth=w,e.strokeStyle=c.color,e.lineJoin="miter",e.beginPath(),e.moveTo(x,x),k-=w/2%1,l+=w/2,e[d.indexOf("n")!==-1?y:z](k,x),e[d.indexOf("e")!==-1?y:z](k,l),e[d.indexOf("s")!==-1?y:z](x,l),e[d.indexOf("w")!==-1?y:z](x,x),e.stroke(),e.closePath()}e.restore(),f&&(o=f.src||f,p=(parseInt(f.left,10)||0)+g.left,q=(parseInt(f.top,10)||0)+g.top,n=new Image,n.onload=function(){e.save(),f.alpha&&(e.globalAlpha=f.alpha),e.globalCompositeOperation="destination-over",e.drawImage(n,0,0,n.width,n.height,p,q,k,l),e.restore()},n.src=o)}})}(),function(){var a=Flotr.DOM,b=Flotr._,c=Flotr,d="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";Flotr.addPlugin("hit",{callbacks:{"flotr:mousemove":function(a,b){this.hit.track(b)},"flotr:click":function(a){this.hit.track(a)},"flotr:mouseout":function(){this.hit.clearHit()}},track:function(a){(this.options.mouse.track||b.any(this.series,function(a){return a.mouse&&a.mouse.track}))&&this.hit.hit(a)},executeOnType:function(a,d,e){function h(a,h){b.each(b.keys(c.graphTypes),function(b){a[b]&&a[b].show&&this[b][d]&&(g=this.getOptions(a,b),g.fill=!!a.mouse.fillColor,g.fillStyle=this.processColor(a.mouse.fillColor||"#ffffff",{opacity:a.mouse.fillOpacity}),g.color=a.mouse.lineColor,g.context=this.octx,g.index=h,e&&(g.args=e),this[b][d].call(this[b],g),f=!0)},this)}var f=!1,g;return b.isArray(a)||(a=[a]),b.each(a,h,this),f},drawHit:function(a){var b=this.octx,c=a.series;if(c.mouse.lineColor){b.save(),b.lineWidth=c.points?c.points.lineWidth:1,b.strokeStyle=c.mouse.lineColor,b.fillStyle=this.processColor(c.mouse.fillColor||"#ffffff",{opacity:c.mouse.fillOpacity}),b.translate(this.plotOffset.left,this.plotOffset.top);if(!this.hit.executeOnType(c,"drawHit",a)){var d=a.xaxis,e=a.yaxis;b.beginPath(),b.arc(d.d2p(a.x),e.d2p(a.y),c.points.radius||c.mouse.radius,0,2*Math.PI,!0),b.fill(),b.stroke(),b.closePath()}b.restore()}this.prevHit=a},clearHit:function(){var b=this.prevHit,c=this.octx,d=this.plotOffset;c.save(),c.translate(d.left,d.top);if(b){if(!this.hit.executeOnType(b.series,"clearHit",this.prevHit)){var e=b.series,f=e.points?e.points.lineWidth:1;offset=(e.points.radius||e.mouse.radius)+f,c.clearRect(b.xaxis.d2p(b.x)-offset,b.yaxis.d2p(b.y)-offset,offset*2,offset*2)}a.hide(this.mouseTrack),this.prevHit=null}c.restore()},hit:function(a){var c=this.options,d=this.prevHit,e,f,g,h,i,j,k,l;if(this.series.length===0)return;n={relX:a.relX,relY:a.relY,absX:a.absX,absY:a.absY};if(c.mouse.trackY&&!c.mouse.trackAll&&this.hit.executeOnType(this.series,"hit",[a,n]))b.isUndefined(n.seriesIndex)||(i=this.series[n.seriesIndex],n.series=i,n.mouse=i.mouse,n.xaxis=i.xaxis,n.yaxis=i.yaxis);else{e=this.hit.closest(a);if(e){e=c.mouse.trackY?e.point:e.x,h=e.seriesIndex,i=this.series[h],k=i.xaxis,l=i.yaxis,f=2*i.mouse.sensibility;if(c.mouse.trackAll||e.distanceXk.xaxis.max)continue;n=Math.abs(p-d),o=Math.abs(q-e),m=n*n+o*o,m '),this.mouseTrack=i,a.insert(this.el,i));if(!b.mouse.relative)f.charAt(0)=="n"?c+="top:"+(g+n)+"px;bottom:auto;":f.charAt(0)=="s"&&(c+="bottom:"+(g+m)+"px;top:auto;"),f.charAt(1)=="e"?c+="right:"+(g+l)+"px;left:auto;":f.charAt(1)=="w"&&(c+="left:"+(g+k)+"px;right:auto;");else if(e.bars.show)c+="bottom:"+(g-n-b.yaxis.d2p(b.y/2)+this.canvasHeight)+"px;top:auto;",c+="left:"+(g+k+b.xaxis.d2p(b.x-p.bars.barWidth/2))+"px;right:auto;";else if(e.pie.show){var q={x:this.plotWidth/2,y:this.plotHeight/2},r=Math.min(this.canvasWidth,this.canvasHeight)*e.pie.sizeRatio/2,s=b.sAngle=5||Math.abs(a.second.y-a.first.y)>=5}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("labels",{callbacks:{"flotr:afterdraw":function(){this.labels.draw()}},draw:function(){function s(a,b,d){var e=d?b.minorTicks:b.ticks,f=b.orientation===1,h=b.n===1,k,m;k={color:b.options.color||o.grid.color,angle:Flotr.toRad(b.options.labelsAngle),textBaseline:"middle"};for(l=0;l(f?a.plotWidth:a.plotHeight))continue;Flotr.drawText(p,c.label,k(a,f,g,i),m(a,f,g,i),h),!f&&!g&&(p.save(),p.strokeStyle=h.color,p.beginPath(),p.moveTo(a.plotOffset.left+a.plotWidth-8,a.plotOffset.top+b.d2p(c.v)),p.lineTo(a.plotOffset.left+a.plotWidth,a.plotOffset.top+b.d2p(c.v)),p.stroke(),p.restore())}}function u(a,b){var d=b.orientation===1,e=b.n===1,g="",h,i,j,k=a.plotOffset;!d&&!e&&(p.save(),p.strokeStyle=b.options.color||o.grid.color,p.beginPath());if(b.options.showLabels&&(e?!0:b.used))for(l=0;l(d?a.canvasWidth:a.canvasHeight))continue;j=k.top+(d?(e?1:-1)*(a.plotHeight+o.grid.labelMargin):b.d2p(c.v)-b.maxLabel.height/2),h=d?k.left+b.d2p(c.v)-f/2:0,g="",l===0?g=" first":l===b.ticks.length-1&&(g=" last"),g+=d?" flotr-grid-label-x":" flotr-grid-label-y",m+=[''+c.label+"
"].join(" "),!d&&!e&&(p.moveTo(k.left+a.plotWidth-8,k.top+b.d2p(c.v)),p.lineTo(k.left+a.plotWidth,k.top+b.d2p(c.v)))}}var b,c,d,e,f,g,h,i,j,k,l,m="",n=0,o=this.options,p=this.ctx,q=this.axes,r={size:o.fontSize};for(l=0;l-1;--m){if(!c[m].label||c[m].hide)continue;n=f.labelFormatter(c[m].label),v=Math.max(v,this._text.measureText(n,p).width)}var w=Math.round(q+s*3+v),x=Math.round(j*(s+r)+s);k.charAt(0)=="s"&&(u=d.top+this.plotHeight-(l+x)),k.charAt(1)=="e"&&(t=d.left+this.plotWidth-(l+w)),o=this.processColor(f.backgroundColor||"rgb(240,240,240)",{opacity:f.backgroundOpacity||.1}),i.fillStyle=o,i.fillRect(t,u,w,x),i.strokeStyle=f.labelBoxBorderColor,i.strokeRect(Flotr.toPixel(t),Flotr.toPixel(u),w,x);var y=t+s,z=u+s;for(m=0;m":" "),h=!0);var A=c[m],B=f.labelBoxWidth,C=f.labelBoxHeight,E=A.bars?A.bars.fillOpacity:f.labelBoxOpacity,F="opacity:"+E+";filter:alpha(opacity="+E*100+");";n=f.labelFormatter(A.label),o="background-color:"+(A.bars&&A.bars.show&&A.bars.fillColor&&A.bars.fill?A.bars.fillColor:A.color)+";",g.push('','"," ",'',n," ")}h&&g.push(" ");if(g.length>0){var G='";if(f.container)a.insert(f.container,G);else{var H={position:"absolute","z-index":2};k.charAt(0)=="n"?(H.top=l+d.top+"px",H.bottom="auto"):k.charAt(0)=="s"&&(H.bottom=l+d.bottom+"px",H.top="auto"),k.charAt(1)=="e"?(H.right=l+d.right+"px",H.left="auto"):k.charAt(1)=="w"&&(H.left=l+d.left+"px",H.right="auto");var I=a.create("div"),J;I.className="flotr-legend",a.setStyles(I,H),a.insert(I,G),a.insert(this.el,I);if(!f.backgroundOpacity)return;var K=f.backgroundColor||e.grid.backgroundColor||"#ffffff";b.extend(H,a.size(I),{backgroundColor:K,"z-index":1}),H.width+="px",H.height+="px",I=a.create("div"),I.className="flotr-legend-bg",a.setStyles(I,H),a.opacity(I,f.backgroundOpacity),a.insert(I," "),a.insert(this.el,I)}}}}})}(),function(){function a(a){if(this.options.spreadsheet.tickFormatter)return this.options.spreadsheet.tickFormatter(a);var b=c.find(this.axes.x.ticks,function(b){return b.v==a});return b?b.label:a}var b=Flotr.DOM,c=Flotr._;Flotr.addPlugin("spreadsheet",{options:{show:!1,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all",csvFileSeparator:",",decimalSeparator:".",tickFormatter:null,initialTab:"graph"},callbacks:{"flotr:afterconstruct":function(){if(!this.options.spreadsheet.show)return;var a=this.spreadsheet,c=b.node('
'),d=b.node(''+this.options.spreadsheet.tabGraphLabel+"
"),e=b.node(''+this.options.spreadsheet.tabDataLabel+"
"),f;a.tabsContainer=c,a.tabs={graph:d,data:e},b.insert(c,d),b.insert(c,e),b.insert(this.el,c),f=b.size(e).height+2,this.plotOffset.bottom+=f,b.setStyles(c,{top:this.canvasHeight-f+"px"}),this.observe(d,"click",function(){a.showTab("graph")}).observe(e,"click",function(){a.showTab("data")}),this.options.spreadsheet.initialTab!=="graph"&&a.showTab(this.options.spreadsheet.initialTab)}},loadDataGrid:function(){if(this.seriesData)return this.seriesData;var a=this.series,b={};return c.each(a,function(a,d){c.each(a.data,function(a){var c=a[0],e=a[1],f=b[c];if(f)f[d+1]=e;else{var g=[];g[0]=c,g[d+1]=e,b[c]=g}})}),this.seriesData=c.sortBy(b,function(a,b){return parseInt(b,10)}),this.seriesData},constructDataGrid:function(){if(this.spreadsheet.datagrid)return this.spreadsheet.datagrid;var d=this.series,e=this.spreadsheet.loadDataGrid(),f=[" "],g,h,i,j=[''];j.push(" "),c.each(d,function(a,b){j.push(''+(a.label||String.fromCharCode(65+b))+" "),f.push(" ")}),j.push(" "),c.each(e,function(b){j.push(""),c.times(d.length+1,function(d){var e="td",f=b[d],g=c.isUndefined(f)?"":Math.round(f*1e5)/1e5;if(d===0){e="th";var h=a.call(this,g);h&&(g=h)}j.push("<"+e+(e=="th"?' scope="row"':"")+">"+g+""+e+">")},this),j.push(" ")},this),f.push(""),i=b.node(j.join("")),g=b.node(''+this.options.spreadsheet.toolbarDownload+" "),h=b.node(''+this.options.spreadsheet.toolbarSelectAll+" "),this.observe(g,"click",c.bind(this.spreadsheet.downloadCSV,this)).observe(h,"click",c.bind(this.spreadsheet.selectAllData,this));var k=b.node('
');b.insert(k,g),b.insert(k,h);var l=this.canvasHeight-b.size(this.spreadsheet.tabsContainer).height-2,m=b.node('
');return b.insert(m,k),b.insert(m,i),b.insert(this.el,m),this.spreadsheet.datagrid=i,this.spreadsheet.container=m,i},showTab:function(a){if(this.spreadsheet.activeTab===a)return;switch(a){case"graph":b.hide(this.spreadsheet.container),b.removeClass(this.spreadsheet.tabs.data,"selected"),b.addClass(this.spreadsheet.tabs.graph,"selected");break;case"data":this.spreadsheet.datagrid||this.spreadsheet.constructDataGrid(),b.show(this.spreadsheet.container),b.addClass(this.spreadsheet.tabs.data,"selected"),b.removeClass(this.spreadsheet.tabs.graph,"selected");break;default:throw"Illegal tab name: "+a}this.spreadsheet.activeTab=a},selectAllData:function(){if(this.spreadsheet.tabs){var a,b,c,d,e=this.spreadsheet.constructDataGrid();return this.spreadsheet.showTab("data"),setTimeout(function(){(c=e.ownerDocument)&&(d=c.defaultView)&&d.getSelection&&c.createRange&&(a=window.getSelection())&&a.removeAllRanges?(b=c.createRange(),b.selectNode(e),a.removeAllRanges(),a.addRange(b)):document.body&&document.body.createTextRange&&(b=document.body.createTextRange())&&(b.moveToElementText(e),b.select())},0),!0}return!1},downloadCSV:function(){var b="",d=this.series,e=this.options,f=this.spreadsheet.loadDataGrid(),g=encodeURIComponent(e.spreadsheet.csvFileSeparator);if(e.spreadsheet.decimalSeparator===e.spreadsheet.csvFileSeparator)throw"The decimal separator is the same as the column separator ("+e.spreadsheet.decimalSeparator+")";c.each(d,function(a,c){b+=g+'"'+(a.label||String.fromCharCode(65+c)).replace(/\"/g,'\\"')+'"'}),b+="%0D%0A",b+=c.reduce(f,function(b,c){var d=a.call(this,c[0])||"";d='"'+(d+"").replace(/\"/g,'\\"')+'"';var f=c.slice(1).join(g);return e.spreadsheet.decimalSeparator!=="."&&(f=f.replace(/\./g,e.spreadsheet.decimalSeparator)),b+d+g+f+"%0D%0A"},"",this),Flotr.isIE&&Flotr.isIE<9?(b=b.replace(new RegExp(g,"g"),decodeURIComponent(g)).replace(/%0A/g,"\n").replace(/%0D/g,"\r"),window.open().document.write(b)):window.open("data:text/csv,"+b)}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("titles",{callbacks:{"flotr:afterdraw":function(){this.titles.drawTitles()}},drawTitles:function(){var b,c=this.options,d=c.grid.labelMargin,e=this.ctx,f=this.axes;if(!c.HtmlText&&this.textEnabled){var g={size:c.fontSize,color:c.grid.color,textAlign:"center"};c.subtitle&&Flotr.drawText(e,c.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,g),g.weight=1.5,g.size*=1.5,c.title&&Flotr.drawText(e,c.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,g),g.weight=1.8,g.size*=.8,f.x.options.title&&f.x.used&&(g.textAlign=f.x.options.titleAlign||"center",g.textBaseline="top",g.angle=Flotr.toRad(f.x.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+f.x.maxLabel.height+this.plotHeight+2*d,g)),f.x2.options.title&&f.x2.used&&(g.textAlign=f.x2.options.titleAlign||"center",g.textBaseline="bottom",g.angle=Flotr.toRad(f.x2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-f.x2.maxLabel.height-2*d,g)),f.y.options.title&&f.y.used&&(g.textAlign=f.y.options.titleAlign||"right",g.textBaseline="middle",g.angle=Flotr.toRad(f.y.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y.options.title,this.plotOffset.left-f.y.maxLabel.width-2*d,this.plotOffset.top+this.plotHeight/2,g)),f.y2.options.title&&f.y2.used&&(g.textAlign=f.y2.options.titleAlign||"left",g.textBaseline="middle",g.angle=Flotr.toRad(f.y2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y2.options.title,this.plotOffset.left+this.plotWidth+f.y2.maxLabel.width+2*d,this.plotOffset.top+this.plotHeight/2,g))}else{b=[],c.title&&b.push('',c.title,"
"),c.subtitle&&b.push('',c.subtitle,"
"),b.push(""),b.push(''),f.x.options.title&&f.x.used&&b.push('
',f.x.options.title,"
"),f.x2.options.title&&f.x2.used&&b.push('
',f.x2.options.title,"
"),f.y.options.title&&f.y.used&&b.push('
',f.y.options.title,"
"),f.y2.options.title&&f.y2.used&&b.push('
',f.y2.options.title,"
"),b=b.join("");var h=a.create("div");a.setStyles({color:c.grid.color}),h.className="flotr-titles",a.insert(this.el,h),a.insert(h,b)}}})}();
diff --git a/addons/web_graph/static/lib/flotr2/js/Axis.js b/addons/web_graph/static/lib/flotr2/js/Axis.js
new file mode 100644
index 00000000000..1c697309ef8
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/Axis.js
@@ -0,0 +1,303 @@
+/**
+ * Flotr Axis Library
+ */
+
+(function () {
+
+var
+ _ = Flotr._,
+ LOGARITHMIC = 'logarithmic';
+
+function Axis (o) {
+
+ this.orientation = 1;
+ this.offset = 0;
+ this.datamin = Number.MAX_VALUE;
+ this.datamax = -Number.MAX_VALUE;
+
+ _.extend(this, o);
+
+ this._setTranslations();
+}
+
+
+// Prototype
+Axis.prototype = {
+
+ setScale : function () {
+ var length = this.length;
+ if (this.options.scaling == LOGARITHMIC) {
+ this.scale = length / (log(this.max, this.options.base) - log(this.min, this.options.base));
+ } else {
+ this.scale = length / (this.max - this.min);
+ }
+ },
+
+ calculateTicks : function () {
+ var options = this.options;
+
+ this.ticks = [];
+ this.minorTicks = [];
+
+ // User Ticks
+ if(options.ticks){
+ this._cleanUserTicks(options.ticks, this.ticks);
+ this._cleanUserTicks(options.minorTicks || [], this.minorTicks);
+ }
+ else {
+ if (options.mode == 'time') {
+ this._calculateTimeTicks();
+ } else if (options.scaling === 'logarithmic') {
+ this._calculateLogTicks();
+ } else {
+ this._calculateTicks();
+ }
+ }
+ },
+
+ /**
+ * Calculates the range of an axis to apply autoscaling.
+ */
+ calculateRange: function () {
+
+ if (!this.used) return;
+
+ var axis = this,
+ o = axis.options,
+ min = o.min !== null ? o.min : axis.datamin,
+ max = o.max !== null ? o.max : axis.datamax,
+ margin = o.autoscaleMargin;
+
+ if (o.scaling == 'logarithmic') {
+ if (min <= 0) min = axis.datamin;
+
+ // Let it widen later on
+ if (max <= 0) max = min;
+ }
+
+ if (max == min) {
+ var widen = max ? 0.01 : 1.00;
+ if (o.min === null) min -= widen;
+ if (o.max === null) max += widen;
+ }
+
+ if (o.scaling === 'logarithmic') {
+ if (min < 0) min = max / o.base; // Could be the result of widening
+
+ var maxexp = Math.log(max);
+ if (o.base != Math.E) maxexp /= Math.log(o.base);
+ maxexp = Math.ceil(maxexp);
+
+ var minexp = Math.log(min);
+ if (o.base != Math.E) minexp /= Math.log(o.base);
+ minexp = Math.ceil(minexp);
+
+ axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals);
+
+ // Try to determine a suitable amount of miniticks based on the length of a decade
+ if (o.minorTickFreq === null) {
+ if (maxexp - minexp > 10)
+ o.minorTickFreq = 0;
+ else if (maxexp - minexp > 5)
+ o.minorTickFreq = 2;
+ else
+ o.minorTickFreq = 5;
+ }
+ } else {
+ axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals);
+ }
+
+ axis.min = min;
+ axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled
+
+ // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it
+ if(o.min === null && o.autoscale){
+ axis.min -= axis.tickSize * margin;
+ // Make sure we don't go below zero if all values are positive.
+ if(axis.min < 0 && axis.datamin >= 0) axis.min = 0;
+ axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize);
+ }
+
+ if(o.max === null && o.autoscale){
+ axis.max += axis.tickSize * margin;
+ if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0;
+ axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize);
+ }
+
+ if (axis.min == axis.max) axis.max = axis.min + 1;
+ },
+
+ calculateTextDimensions : function (T, options) {
+
+ var maxLabel = '',
+ length,
+ i;
+
+ if (this.options.showLabels) {
+ for (i = 0; i < this.ticks.length; ++i) {
+ length = this.ticks[i].label.length;
+ if (length > maxLabel.length){
+ maxLabel = this.ticks[i].label;
+ }
+ }
+ }
+
+ this.maxLabel = T.dimensions(
+ maxLabel,
+ {size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)},
+ 'font-size:smaller;',
+ 'flotr-grid-label'
+ );
+
+ this.titleSize = T.dimensions(
+ this.options.title,
+ {size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)},
+ 'font-weight:bold;',
+ 'flotr-axis-title'
+ );
+ },
+
+ _cleanUserTicks : function (ticks, axisTicks) {
+
+ var axis = this, options = this.options,
+ v, i, label, tick;
+
+ if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max});
+
+ for(i = 0; i < ticks.length; ++i){
+ tick = ticks[i];
+ if(typeof(tick) === 'object'){
+ v = tick[0];
+ label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max});
+ } else {
+ v = tick;
+ label = options.tickFormatter(v, {min : this.min, max : this.max});
+ }
+ axisTicks[i] = { v: v, label: label };
+ }
+ },
+
+ _calculateTimeTicks : function () {
+ this.ticks = Flotr.Date.generator(this);
+ },
+
+ _calculateLogTicks : function () {
+
+ var axis = this,
+ o = axis.options,
+ v,
+ decadeStart;
+
+ var max = Math.log(axis.max);
+ if (o.base != Math.E) max /= Math.log(o.base);
+ max = Math.ceil(max);
+
+ var min = Math.log(axis.min);
+ if (o.base != Math.E) min /= Math.log(o.base);
+ min = Math.ceil(min);
+
+ for (i = min; i < max; i += axis.tickSize) {
+ decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
+ // Next decade begins here:
+ var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize));
+ var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq;
+
+ axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
+ for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize)
+ axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})});
+ }
+
+ // Always show the value at the would-be start of next decade (end of this decade)
+ decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
+ axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
+ },
+
+ _calculateTicks : function () {
+
+ var axis = this,
+ o = axis.options,
+ tickSize = axis.tickSize,
+ min = axis.min,
+ max = axis.max,
+ start = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size.
+ decimals,
+ minorTickSize,
+ v, v2,
+ i, j;
+
+ if (o.minorTickFreq)
+ minorTickSize = tickSize / o.minorTickFreq;
+
+ // Then store all possible ticks.
+ for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){
+
+ // Round (this is always needed to fix numerical instability).
+ decimals = o.tickDecimals;
+ if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10);
+ if (decimals < 0) decimals = 0;
+
+ v = v.toFixed(decimals);
+ axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
+
+ if (o.minorTickFreq) {
+ for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) {
+ v = v2 + j * minorTickSize;
+ axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
+ }
+ }
+ }
+
+ },
+
+ _setTranslations : function (logarithmic) {
+ this.d2p = (logarithmic ? d2pLog : d2p);
+ this.p2d = (logarithmic ? p2dLog : p2d);
+ }
+};
+
+
+// Static Methods
+_.extend(Axis, {
+ getAxes : function (options) {
+ return {
+ x: new Axis({options: options.xaxis, n: 1, length: this.plotWidth}),
+ x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}),
+ y: new Axis({options: options.yaxis, n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}),
+ y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1})
+ };
+ }
+});
+
+
+// Helper Methods
+
+function d2p (dataValue) {
+ return this.offset + this.orientation * (dataValue - this.min) * this.scale;
+}
+
+function p2d (pointValue) {
+ return (this.offset + this.orientation * pointValue) / this.scale + this.min;
+}
+
+function d2pLog (dataValue) {
+ return this.offset + this.orientation * (log(dataValue, this.options.base) - log(this.min, this.options.base)) * this.scale;
+}
+
+function p2dLog (pointValue) {
+ return exp((this.offset + this.orientation * pointValue) / this.scale + log(this.min, this.options.base), this.options.base);
+}
+
+function log (value, base) {
+ value = Math.log(Math.max(value, Number.MIN_VALUE));
+ if (base !== Math.E)
+ value /= Math.log(base);
+ return value;
+}
+
+function exp (value, base) {
+ return (base === Math.E) ? Math.exp(value) : Math.pow(base, value);
+}
+
+Flotr.Axis = Axis;
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/Color.js b/addons/web_graph/static/lib/flotr2/js/Color.js
new file mode 100644
index 00000000000..ab4ba0b432d
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/Color.js
@@ -0,0 +1,163 @@
+/**
+ * Flotr Color
+ */
+
+(function () {
+
+var
+ _ = Flotr._;
+
+// Constructor
+function Color (r, g, b, a) {
+ this.rgba = ['r','g','b','a'];
+ var x = 4;
+ while(-1<--x){
+ this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
+ }
+ this.normalize();
+}
+
+// Constants
+var COLOR_NAMES = {
+ 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]
+};
+
+Color.prototype = {
+ scale: function(rf, gf, bf, af){
+ var x = 4;
+ while (-1 < --x) {
+ if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x];
+ }
+ return this.normalize();
+ },
+ alpha: function(alpha) {
+ if (!_.isUndefined(alpha) && !_.isNull(alpha)) {
+ this.a = alpha;
+ }
+ return this.normalize();
+ },
+ clone: function(){
+ return new Color(this.r, this.b, this.g, this.a);
+ },
+ limit: function(val,minVal,maxVal){
+ return Math.max(Math.min(val, maxVal), minVal);
+ },
+ normalize: function(){
+ var limit = this.limit;
+ this.r = limit(parseInt(this.r, 10), 0, 255);
+ this.g = limit(parseInt(this.g, 10), 0, 255);
+ this.b = limit(parseInt(this.b, 10), 0, 255);
+ this.a = limit(this.a, 0, 1);
+ return this;
+ },
+ distance: function(color){
+ if (!color) return;
+ color = new Color.parse(color);
+ var dist = 0, x = 3;
+ while(-1<--x){
+ dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]);
+ }
+ return dist;
+ },
+ toString: function(){
+ return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')';
+ },
+ contrast: function () {
+ var
+ test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255;
+ return (test < 0.5 ? '#000000' : '#ffffff');
+ }
+};
+
+_.extend(Color, {
+ /**
+ * Parses a color string and returns a corresponding Color.
+ * The different tests are in order of probability to improve speed.
+ * @param {String, Color} str - string thats representing a color
+ * @return {Color} returns a Color object or false
+ */
+ parse: function(color){
+ if (color instanceof Color) return color;
+
+ var result;
+
+ // #a0b1c2
+ if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)))
+ return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
+
+ // rgb(num,num,num)
+ if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)))
+ return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
+
+ // #fff
+ if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)))
+ return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));
+
+ // rgba(num,num,num,num)
+ if((result = /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(color)))
+ return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
+
+ // rgb(num%,num%,num%)
+ if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)))
+ return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
+
+ // rgba(num%,num%,num%,num)
+ if((result = /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(color)))
+ return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
+
+ // Otherwise, we're most likely dealing with a named color.
+ var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase();
+ if(name == 'transparent'){
+ return new Color(255, 255, 255, 0);
+ }
+ return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0);
+ },
+
+ /**
+ * Process color and options into color style.
+ */
+ processColor: function(color, options) {
+
+ var opacity = options.opacity;
+ if (!color) return 'rgba(0, 0, 0, 0)';
+ if (color instanceof Color) return color.alpha(opacity).toString();
+ if (_.isString(color)) return Color.parse(color).alpha(opacity).toString();
+
+ var grad = color.colors ? color : {colors: color};
+
+ if (!options.ctx) {
+ if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)';
+ return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString();
+ }
+ grad = _.extend({start: 'top', end: 'bottom'}, grad);
+
+ if (/top/i.test(grad.start)) options.x1 = 0;
+ if (/left/i.test(grad.start)) options.y1 = 0;
+ if (/bottom/i.test(grad.end)) options.x2 = 0;
+ if (/right/i.test(grad.end)) options.y2 = 0;
+
+ var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2);
+ for (i = 0; i < grad.colors.length; i++) {
+ c = grad.colors[i];
+ if (_.isArray(c)) {
+ stop = c[0];
+ c = c[1];
+ }
+ else stop = i / (grad.colors.length-1);
+ gradient.addColorStop(stop, Color.parse(c).alpha(opacity));
+ }
+ return gradient;
+ }
+});
+
+Flotr.Color = Color;
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/DOM.js b/addons/web_graph/static/lib/flotr2/js/DOM.js
new file mode 100644
index 00000000000..43df857395b
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/DOM.js
@@ -0,0 +1,88 @@
+(function () {
+
+var _ = Flotr._;
+
+Flotr.DOM = {
+ addClass: function(element, name){
+ var classList = (element.className ? element.className : '');
+ if (_.include(classList.split(/\s+/g), name)) return;
+ element.className = (classList ? classList + ' ' : '') + name;
+ },
+ /**
+ * Create an element.
+ */
+ create: function(tag){
+ return document.createElement(tag);
+ },
+ node: function(html) {
+ var div = Flotr.DOM.create('div'), n;
+ div.innerHTML = html;
+ n = div.children[0];
+ div.innerHTML = '';
+ return n;
+ },
+ /**
+ * Remove all children.
+ */
+ empty: function(element){
+ element.innerHTML = '';
+ /*
+ if (!element) return;
+ _.each(element.childNodes, function (e) {
+ Flotr.DOM.empty(e);
+ element.removeChild(e);
+ });
+ */
+ },
+ hide: function(element){
+ Flotr.DOM.setStyles(element, {display:'none'});
+ },
+ /**
+ * Insert a child.
+ * @param {Element} element
+ * @param {Element|String} Element or string to be appended.
+ */
+ insert: function(element, child){
+ if(_.isString(child))
+ element.innerHTML += child;
+ else if (_.isElement(child))
+ element.appendChild(child);
+ },
+ // @TODO find xbrowser implementation
+ opacity: function(element, opacity) {
+ element.style.opacity = opacity;
+ },
+ position: function(element, p){
+ if (!element.offsetParent)
+ return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)};
+
+ p = this.position(element.offsetParent);
+ p.left += element.offsetLeft;
+ p.top += element.offsetTop;
+ return p;
+ },
+ removeClass: function(element, name) {
+ var classList = (element.className ? element.className : '');
+ element.className = _.filter(classList.split(/\s+/g), function (c) {
+ if (c != name) return true; }
+ ).join(' ');
+ },
+ setStyles: function(element, o) {
+ _.each(o, function (value, key) {
+ element.style[key] = value;
+ });
+ },
+ show: function(element){
+ Flotr.DOM.setStyles(element, {display:''});
+ },
+ /**
+ * Return element size.
+ */
+ size: function(element){
+ return {
+ height : element.offsetHeight,
+ width : element.offsetWidth };
+ }
+};
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/Date.js b/addons/web_graph/static/lib/flotr2/js/Date.js
new file mode 100644
index 00000000000..fe72dba709f
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/Date.js
@@ -0,0 +1,207 @@
+/**
+ * Flotr Date
+ */
+Flotr.Date = {
+
+ set : function (date, name, mode, value) {
+ mode = mode || 'UTC';
+ name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name;
+ date[name](value);
+ },
+
+ get : function (date, name, mode) {
+ mode = mode || 'UTC';
+ name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name;
+ return date[name]();
+ },
+
+ format: function(d, format, mode) {
+ if (!d) return;
+
+ // We should maybe use an "official" date format spec, like PHP date() or ColdFusion
+ // http://fr.php.net/manual/en/function.date.php
+ // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html
+ var
+ get = this.get,
+ tokens = {
+ h: get(d, 'Hours', mode).toString(),
+ H: leftPad(get(d, 'Hours', mode)),
+ M: leftPad(get(d, 'Minutes', mode)),
+ S: leftPad(get(d, 'Seconds', mode)),
+ s: get(d, 'Milliseconds', mode),
+ d: get(d, 'Date', mode).toString(),
+ m: (get(d, 'Month') + 1).toString(),
+ y: get(d, 'FullYear').toString(),
+ b: Flotr.Date.monthNames[get(d, 'Month', mode)]
+ };
+
+ function leftPad(n){
+ n += '';
+ return n.length == 1 ? "0" + n : n;
+ }
+
+ var r = [], c,
+ escape = false;
+
+ for (var i = 0; i < format.length; ++i) {
+ c = format.charAt(i);
+
+ if (escape) {
+ r.push(tokens[c] || c);
+ escape = false;
+ }
+ else if (c == "%")
+ escape = true;
+ else
+ r.push(c);
+ }
+ return r.join('');
+ },
+ getFormat: function(time, span) {
+ var tu = Flotr.Date.timeUnits;
+ if (time < tu.second) return "%h:%M:%S.%s";
+ else if (time < tu.minute) return "%h:%M:%S";
+ else if (time < tu.day) return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M";
+ else if (time < tu.month) return "%b %d";
+ else if (time < tu.year) return (span < tu.year) ? "%b" : "%b %y";
+ else return "%y";
+ },
+ formatter: function (v, axis) {
+ var
+ options = axis.options,
+ scale = Flotr.Date.timeUnits[options.timeUnit],
+ d = new Date(v * scale);
+
+ // first check global format
+ if (axis.options.timeFormat)
+ return Flotr.Date.format(d, options.timeFormat, options.timeMode);
+
+ var span = (axis.max - axis.min) * scale,
+ t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit];
+
+ return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode);
+ },
+ generator: function(axis) {
+
+ var
+ set = this.set,
+ get = this.get,
+ timeUnits = this.timeUnits,
+ spec = this.spec,
+ options = axis.options,
+ mode = options.timeMode,
+ scale = timeUnits[options.timeUnit],
+ min = axis.min * scale,
+ max = axis.max * scale,
+ delta = (max - min) / options.noTicks,
+ ticks = [],
+ tickSize = axis.tickSize,
+ tickUnit,
+ formatter, i;
+
+ // Use custom formatter or time tick formatter
+ formatter = (options.tickFormatter === Flotr.defaultTickFormatter ?
+ this.formatter : options.tickFormatter
+ );
+
+ for (i = 0; i < spec.length - 1; ++i) {
+ var d = spec[i][0] * timeUnits[spec[i][1]];
+ if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize)
+ break;
+ }
+ tickSize = spec[i][0];
+ tickUnit = spec[i][1];
+
+ // special-case the possibility of several years
+ if (tickUnit == "year") {
+ tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0);
+
+ // Fix for 0.5 year case
+ if (tickSize == 0.5) {
+ tickUnit = "month";
+ tickSize = 6;
+ }
+ }
+
+ axis.tickUnit = tickUnit;
+ axis.tickSize = tickSize;
+
+ var
+ d = new Date(min);
+
+ var step = tickSize * timeUnits[tickUnit];
+
+ function setTick (name) {
+ set(d, name, mode, Flotr.floorInBase(
+ get(d, name, mode), tickSize
+ ));
+ }
+
+ switch (tickUnit) {
+ case "millisecond": setTick('Milliseconds'); break;
+ case "second": setTick('Seconds'); break;
+ case "minute": setTick('Minutes'); break;
+ case "hour": setTick('Hours'); break;
+ case "month": setTick('Month'); break;
+ case "year": setTick('FullYear'); break;
+ }
+
+ // reset smaller components
+ if (step >= timeUnits.second) set(d, 'Milliseconds', mode, 0);
+ if (step >= timeUnits.minute) set(d, 'Seconds', mode, 0);
+ if (step >= timeUnits.hour) set(d, 'Minutes', mode, 0);
+ if (step >= timeUnits.day) set(d, 'Hours', mode, 0);
+ if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1);
+ if (step >= timeUnits.year) set(d, 'Month', mode, 0);
+
+ var carry = 0, v = NaN, prev;
+ do {
+ prev = v;
+ v = d.getTime();
+ ticks.push({ v: v / scale, label: formatter(v / scale, axis) });
+ if (tickUnit == "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 */
+ set(d, 'Date', mode, 1);
+ var start = d.getTime();
+ set(d, 'Month', mode, get(d, 'Month', mode) + 1)
+ var end = d.getTime();
+ d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize);
+ carry = get(d, 'Hours', mode)
+ set(d, 'Hours', mode, 0);
+ }
+ else
+ set(d, 'Month', mode, get(d, 'Month', mode) + tickSize);
+ }
+ else if (tickUnit == "year") {
+ set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize);
+ }
+ else
+ d.setTime(v + step);
+
+ } while (v < max && v != prev);
+
+ return ticks;
+ },
+ timeUnits: {
+ millisecond: 1,
+ second: 1000,
+ minute: 1000 * 60,
+ hour: 1000 * 60 * 60,
+ day: 1000 * 60 * 60 * 24,
+ month: 1000 * 60 * 60 * 24 * 30,
+ year: 1000 * 60 * 60 * 24 * 365.2425
+ },
+ // the allowed tick sizes, after 1 year we use an integer algorithm
+ spec: [
+ [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"],
+ [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"]
+ ],
+ monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+};
diff --git a/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js b/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js
new file mode 100644
index 00000000000..7399553df6a
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js
@@ -0,0 +1,98 @@
+/**
+ * Flotr Defaults
+ */
+Flotr.defaultOptions = {
+ colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.
+ ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping
+ title: null, // => The graph's title
+ subtitle: null, // => The graph's subtitle
+ shadowSize: 4, // => size of the 'fake' shadow
+ defaultType: null, // => default series type
+ HtmlText: true, // => wether to draw the text using HTML or on the canvas
+ fontColor: '#545454', // => default font color
+ fontSize: 7.5, // => canvas' text font size
+ resolution: 1, // => resolution of the graph, to have printer-friendly graphs !
+ parseFloat: true, // => whether to preprocess data for floats (ie. if input is string)
+ xaxis: {
+ ticks: null, // => format: either [1, 3] or [[1, 'a'], 3]
+ minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3]
+ showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise
+ showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
+ labelsAngle: 0, // => labels' angle, in degrees
+ title: null, // => axis title
+ titleAngle: 0, // => axis title's angle, in degrees
+ noTicks: 5, // => number of ticks for automagically generated ticks
+ minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks
+ tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
+ tickDecimals: null, // => no. of decimals, null means auto
+ min: null, // => min. value to show, null means set automatically
+ max: null, // => max. value to show, null means set automatically
+ autoscale: false, // => Turns autoscaling on with true
+ autoscaleMargin: 0, // => margin in % to add if auto-setting min/max
+ color: null, // => color of the ticks
+ mode: 'normal', // => can be 'time' or 'normal'
+ timeFormat: null,
+ timeMode:'UTC', // => For UTC time ('local' for local time).
+ timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year)
+ scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic'
+ base: Math.E,
+ titleAlign: 'center',
+ margin: true // => Turn off margins with false
+ },
+ x2axis: {},
+ yaxis: {
+ ticks: null, // => format: either [1, 3] or [[1, 'a'], 3]
+ minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3]
+ showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise
+ showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide
+ labelsAngle: 0, // => labels' angle, in degrees
+ title: null, // => axis title
+ titleAngle: 90, // => axis title's angle, in degrees
+ noTicks: 5, // => number of ticks for automagically generated ticks
+ minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks
+ tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string
+ tickDecimals: null, // => no. of decimals, null means auto
+ min: null, // => min. value to show, null means set automatically
+ max: null, // => max. value to show, null means set automatically
+ autoscale: false, // => Turns autoscaling on with true
+ autoscaleMargin: 0, // => margin in % to add if auto-setting min/max
+ color: null, // => The color of the ticks
+ scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic'
+ base: Math.E,
+ titleAlign: 'center',
+ margin: true // => Turn off margins with false
+ },
+ y2axis: {
+ titleAngle: 270
+ },
+ grid: {
+ color: '#545454', // => primary color used for outline and labels
+ backgroundColor: null, // => null for transparent, else color
+ backgroundImage: null, // => background image. String or object with src, left and top
+ watermarkAlpha: 0.4, // =>
+ tickColor: '#DDDDDD', // => color used for the ticks
+ labelMargin: 3, // => margin in pixels
+ verticalLines: true, // => whether to show gridlines in vertical direction
+ minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir.
+ horizontalLines: true, // => whether to show gridlines in horizontal direction
+ minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir.
+ outlineWidth: 1, // => width of the grid outline/border in pixels
+ outline : 'nsew', // => walls of the outline to display
+ circular: false // => if set to true, the grid will be circular, must be used when radars are drawn
+ },
+ mouse: {
+ track: false, // => true to track the mouse, no tracking otherwise
+ trackAll: false,
+ position: 'se', // => position of the value box (default south-east)
+ relative: false, // => next to the mouse cursor
+ trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box
+ margin: 5, // => margin in pixels of the valuebox
+ lineColor: '#FF3F19', // => line color of points that are drawn when mouse comes near a value of a series
+ trackDecimals: 1, // => decimals for the track values
+ sensibility: 2, // => the lower this number, the more precise you have to aim to show a value
+ trackY: true, // => whether or not to track the mouse in the y axis
+ radius: 3, // => radius of the track point
+ fillColor: null, // => color to fill our select bar with only applies to bar and similar graphs (only bars for now)
+ fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ }
+};
diff --git a/addons/web_graph/static/lib/flotr2/js/EventAdapter.js b/addons/web_graph/static/lib/flotr2/js/EventAdapter.js
new file mode 100644
index 00000000000..062ceeab294
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/EventAdapter.js
@@ -0,0 +1,52 @@
+/**
+ * Flotr Event Adapter
+ */
+(function () {
+var
+ F = Flotr,
+ bean = F.bean;
+F.EventAdapter = {
+ observe: function(object, name, callback) {
+ bean.add(object, name, callback);
+ return this;
+ },
+ fire: function(object, name, args) {
+ bean.fire(object, name, args);
+ if (typeof(Prototype) != 'undefined')
+ Event.fire(object, name, args);
+ // @TODO Someone who uses mootools, add mootools adapter for existing applciations.
+ return this;
+ },
+ stopObserving: function(object, name, callback) {
+ bean.remove(object, name, callback);
+ return this;
+ },
+ eventPointer: function(e) {
+ if (!F._.isUndefined(e.touches) && e.touches.length > 0) {
+ return {
+ x : e.touches[0].pageX,
+ y : e.touches[0].pageY
+ };
+ } else if (!F._.isUndefined(e.changedTouches) && e.changedTouches.length > 0) {
+ return {
+ x : e.changedTouches[0].pageX,
+ y : e.changedTouches[0].pageY
+ };
+ } else if (e.pageX || e.pageY) {
+ return {
+ x : e.pageX,
+ y : e.pageY
+ };
+ } else if (e.clientX || e.clientY) {
+ var
+ d = document,
+ b = d.body,
+ de = d.documentElement;
+ return {
+ x: e.clientX + b.scrollLeft + de.scrollLeft,
+ y: e.clientY + b.scrollTop + de.scrollTop
+ };
+ }
+ }
+};
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/Flotr.js b/addons/web_graph/static/lib/flotr2/js/Flotr.js
new file mode 100644
index 00000000000..e8256096adb
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/Flotr.js
@@ -0,0 +1,250 @@
+/**
+ * Flotr2 (c) 2012 Carl Sutherland
+ * MIT License
+ * Special thanks to:
+ * Flotr: http://code.google.com/p/flotr/ (fork)
+ * Flot: https://github.com/flot/flot (original fork)
+ */
+(function () {
+
+var
+ global = this,
+ previousFlotr = this.Flotr,
+ Flotr;
+
+Flotr = {
+ _: _,
+ bean: bean,
+ isIphone: /iphone/i.test(navigator.userAgent),
+ isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false),
+
+ /**
+ * An object of the registered graph types. Use Flotr.addType(type, object)
+ * to add your own type.
+ */
+ graphTypes: {},
+
+ /**
+ * The list of the registered plugins
+ */
+ plugins: {},
+
+ /**
+ * Can be used to add your own chart type.
+ * @param {String} name - Type of chart, like 'pies', 'bars' etc.
+ * @param {String} graphType - The object containing the basic drawing functions (draw, etc)
+ */
+ addType: function(name, graphType){
+ Flotr.graphTypes[name] = graphType;
+ Flotr.defaultOptions[name] = graphType.options || {};
+ Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name;
+ },
+
+ /**
+ * Can be used to add a plugin
+ * @param {String} name - The name of the plugin
+ * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...)
+ */
+ addPlugin: function(name, plugin){
+ Flotr.plugins[name] = plugin;
+ Flotr.defaultOptions[name] = plugin.options || {};
+ },
+
+ /**
+ * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha.
+ * You could also draw graphs by directly calling Flotr.Graph(element, data, options).
+ * @param {Element} el - element to insert the graph into
+ * @param {Object} data - an array or object of dataseries
+ * @param {Object} options - an object containing options
+ * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph
+ * @return {Object} returns a new graph object and of course draws the graph.
+ */
+ draw: function(el, data, options, GraphKlass){
+ GraphKlass = GraphKlass || Flotr.Graph;
+ return new GraphKlass(el, data, options);
+ },
+
+ /**
+ * Recursively merges two objects.
+ * @param {Object} src - source object (likely the object with the least properties)
+ * @param {Object} dest - destination object (optional, object with the most properties)
+ * @return {Object} recursively merged Object
+ * @TODO See if we can't remove this.
+ */
+ merge: function(src, dest){
+ var i, v, result = dest || {};
+
+ for (i in src) {
+ v = src[i];
+ if (v && typeof(v) === 'object') {
+ if (v.constructor === Array) {
+ result[i] = this._.clone(v);
+ } else if (v.constructor !== RegExp && !this._.isElement(v)) {
+ result[i] = Flotr.merge(v, (dest ? dest[i] : undefined));
+ } else {
+ result[i] = v;
+ }
+ } else {
+ result[i] = v;
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Recursively clones an object.
+ * @param {Object} object - The object to clone
+ * @return {Object} the clone
+ * @TODO See if we can't remove this.
+ */
+ clone: function(object){
+ return Flotr.merge(object, {});
+ },
+
+ /**
+ * Function calculates the ticksize and returns it.
+ * @param {Integer} noTicks - number of ticks
+ * @param {Integer} min - lower bound integer value for the current axis
+ * @param {Integer} max - upper bound integer value for the current axis
+ * @param {Integer} decimals - number of decimals for the ticks
+ * @return {Integer} returns the ticksize in pixels
+ */
+ getTickSize: function(noTicks, min, max, decimals){
+ var delta = (max - min) / noTicks,
+ magn = Flotr.getMagnitude(delta),
+ tickSize = 10,
+ norm = delta / magn; // Norm is between 1.0 and 10.0.
+
+ if(norm < 1.5) tickSize = 1;
+ else if(norm < 2.25) tickSize = 2;
+ else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5);
+ else if(norm < 7.5) tickSize = 5;
+
+ return tickSize * magn;
+ },
+
+ /**
+ * Default tick formatter.
+ * @param {String, Integer} val - tick value integer
+ * @param {Object} axisOpts - the axis' options
+ * @return {String} formatted tick string
+ */
+ defaultTickFormatter: function(val, axisOpts){
+ return val+'';
+ },
+
+ /**
+ * Formats the mouse tracker values.
+ * @param {Object} obj - Track value Object {x:..,y:..}
+ * @return {String} Formatted track string
+ */
+ defaultTrackFormatter: function(obj){
+ return '('+obj.x+', '+obj.y+')';
+ },
+
+ /**
+ * Utility function to convert file size values in bytes to kB, MB, ...
+ * @param value {Number} - The value to convert
+ * @param precision {Number} - The number of digits after the comma (default: 2)
+ * @param base {Number} - The base (default: 1000)
+ */
+ engineeringNotation: function(value, precision, base){
+ var sizes = ['Y','Z','E','P','T','G','M','k',''],
+ fractionSizes = ['y','z','a','f','p','n','µ','m',''],
+ total = sizes.length;
+
+ base = base || 1000;
+ precision = Math.pow(10, precision || 2);
+
+ if (value === 0) return 0;
+
+ if (value > 1) {
+ while (total-- && (value >= base)) value /= base;
+ }
+ else {
+ sizes = fractionSizes;
+ total = sizes.length;
+ while (total-- && (value < 1)) value *= base;
+ }
+
+ return (Math.round(value * precision) / precision) + sizes[total];
+ },
+
+ /**
+ * Returns the magnitude of the input value.
+ * @param {Integer, Float} x - integer or float value
+ * @return {Integer, Float} returns the magnitude of the input value
+ */
+ getMagnitude: function(x){
+ return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
+ },
+ toPixel: function(val){
+ return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val);
+ },
+ toRad: function(angle){
+ return -angle * (Math.PI/180);
+ },
+ floorInBase: function(n, base) {
+ return base * Math.floor(n / base);
+ },
+ drawText: function(ctx, text, x, y, style) {
+ if (!ctx.fillText) {
+ ctx.drawText(text, x, y, style);
+ return;
+ }
+
+ style = this._.extend({
+ size: Flotr.defaultOptions.fontSize,
+ color: '#000000',
+ textAlign: 'left',
+ textBaseline: 'bottom',
+ weight: 1,
+ angle: 0
+ }, style);
+
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.rotate(style.angle);
+ ctx.fillStyle = style.color;
+ ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
+ ctx.textAlign = style.textAlign;
+ ctx.textBaseline = style.textBaseline;
+ ctx.fillText(text, 0, 0);
+ ctx.restore();
+ },
+ getBestTextAlign: function(angle, style) {
+ style = style || {textAlign: 'center', textBaseline: 'middle'};
+ angle += Flotr.getTextAngleFromAlign(style);
+
+ if (Math.abs(Math.cos(angle)) > 10e-3)
+ style.textAlign = (Math.cos(angle) > 0 ? 'right' : 'left');
+
+ if (Math.abs(Math.sin(angle)) > 10e-3)
+ style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom');
+
+ return style;
+ },
+ alignTable: {
+ 'right middle' : 0,
+ 'right top' : Math.PI/4,
+ 'center top' : Math.PI/2,
+ 'left top' : 3*(Math.PI/4),
+ 'left middle' : Math.PI,
+ 'left bottom' : -3*(Math.PI/4),
+ 'center bottom': -Math.PI/2,
+ 'right bottom' : -Math.PI/4,
+ 'center middle': 0
+ },
+ getTextAngleFromAlign: function(style) {
+ return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0;
+ },
+ noConflict : function () {
+ global.Flotr = previousFlotr;
+ return this;
+ }
+};
+
+global.Flotr = Flotr;
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/Graph.js b/addons/web_graph/static/lib/flotr2/js/Graph.js
new file mode 100644
index 00000000000..0e0310a5f4e
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/Graph.js
@@ -0,0 +1,745 @@
+/**
+ * Flotr Graph class that plots a graph on creation.
+ */
+(function () {
+
+var
+ D = Flotr.DOM,
+ E = Flotr.EventAdapter,
+ _ = Flotr._,
+ flotr = Flotr;
+/**
+ * Flotr Graph constructor.
+ * @param {Element} el - element to insert the graph into
+ * @param {Object} data - an array or object of dataseries
+ * @param {Object} options - an object containing options
+ */
+Graph = function(el, data, options){
+// Let's see if we can get away with out this [JS]
+// try {
+ this._setEl(el);
+ this._initMembers();
+ this._initPlugins();
+
+ E.fire(this.el, 'flotr:beforeinit', [this]);
+
+ this.data = data;
+ this.series = flotr.Series.getSeries(data);
+ this._initOptions(options);
+ this._initGraphTypes();
+ this._initCanvas();
+ this._text = new flotr.Text({
+ element : this.el,
+ ctx : this.ctx,
+ html : this.options.HtmlText,
+ textEnabled : this.textEnabled
+ });
+ E.fire(this.el, 'flotr:afterconstruct', [this]);
+ this._initEvents();
+
+ this.findDataRanges();
+ this.calculateSpacing();
+
+ this.draw(_.bind(function() {
+ E.fire(this.el, 'flotr:afterinit', [this]);
+ }, this));
+/*
+ try {
+ } catch (e) {
+ try {
+ console.error(e);
+ } catch (e2) {}
+ }*/
+};
+
+function observe (object, name, callback) {
+ E.observe.apply(this, arguments);
+ this._handles.push(arguments);
+ return this;
+}
+
+Graph.prototype = {
+
+ destroy: function () {
+ E.fire(this.el, 'flotr:destroy');
+ _.each(this._handles, function (handle) {
+ E.stopObserving.apply(this, handle);
+ });
+ this._handles = [];
+ this.el.graph = null;
+ },
+
+ observe : observe,
+
+ /**
+ * @deprecated
+ */
+ _observe : observe,
+
+ processColor: function(color, options){
+ var o = { x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx };
+ _.extend(o, options);
+ return flotr.Color.processColor(color, o);
+ },
+ /**
+ * Function determines the min and max values for the xaxis and yaxis.
+ *
+ * TODO logarithmic range validation (consideration of 0)
+ */
+ findDataRanges: function(){
+ var a = this.axes,
+ xaxis, yaxis, range;
+
+ _.each(this.series, function (series) {
+ range = series.getRange();
+ if (range) {
+ xaxis = series.xaxis;
+ yaxis = series.yaxis;
+ xaxis.datamin = Math.min(range.xmin, xaxis.datamin);
+ xaxis.datamax = Math.max(range.xmax, xaxis.datamax);
+ yaxis.datamin = Math.min(range.ymin, yaxis.datamin);
+ yaxis.datamax = Math.max(range.ymax, yaxis.datamax);
+ xaxis.used = (xaxis.used || range.xused);
+ yaxis.used = (yaxis.used || range.yused);
+ }
+ }, this);
+
+ // Check for empty data, no data case (none used)
+ if (!a.x.used && !a.x2.used) a.x.used = true;
+ if (!a.y.used && !a.y2.used) a.y.used = true;
+
+ _.each(a, function (axis) {
+ axis.calculateRange();
+ });
+
+ var
+ types = _.keys(flotr.graphTypes),
+ drawn = false;
+
+ _.each(this.series, function (series) {
+ if (series.hide) return;
+ _.each(types, function (type) {
+ if (series[type] && series[type].show) {
+ this.extendRange(type, series);
+ drawn = true;
+ }
+ }, this);
+ if (!drawn) {
+ this.extendRange(this.options.defaultType, series);
+ }
+ }, this);
+ },
+
+ extendRange : function (type, series) {
+ if (this[type].extendRange) this[type].extendRange(series, series.data, series[type], this[type]);
+ if (this[type].extendYRange) this[type].extendYRange(series.yaxis, series.data, series[type], this[type]);
+ if (this[type].extendXRange) this[type].extendXRange(series.xaxis, series.data, series[type], this[type]);
+ },
+
+ /**
+ * Calculates axis label sizes.
+ */
+ calculateSpacing: function(){
+
+ var a = this.axes,
+ options = this.options,
+ series = this.series,
+ margin = options.grid.labelMargin,
+ T = this._text,
+ x = a.x,
+ x2 = a.x2,
+ y = a.y,
+ y2 = a.y2,
+ maxOutset = options.grid.outlineWidth,
+ i, j, l, dim;
+
+ // TODO post refactor, fix this
+ _.each(a, function (axis) {
+ axis.calculateTicks();
+ axis.calculateTextDimensions(T, options);
+ });
+
+ // Title height
+ dim = T.dimensions(
+ options.title,
+ {size: options.fontSize*1.5},
+ 'font-size:1em;font-weight:bold;',
+ 'flotr-title'
+ );
+ this.titleHeight = dim.height;
+
+ // Subtitle height
+ dim = T.dimensions(
+ options.subtitle,
+ {size: options.fontSize},
+ 'font-size:smaller;',
+ 'flotr-subtitle'
+ );
+ this.subtitleHeight = dim.height;
+
+ for(j = 0; j < options.length; ++j){
+ if (series[j].points.show){
+ maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2);
+ }
+ }
+
+ var p = this.plotOffset;
+ if (x.options.margin === false) {
+ p.bottom = 0;
+ p.top = 0;
+ } else {
+ p.bottom += (options.grid.circular ? 0 : (x.used && x.options.showLabels ? (x.maxLabel.height + margin) : 0)) +
+ (x.used && x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset;
+
+ p.top += (options.grid.circular ? 0 : (x2.used && x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) +
+ (x2.used && x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset;
+ }
+ if (y.options.margin === false) {
+ p.left = 0;
+ p.right = 0;
+ } else {
+ p.left += (options.grid.circular ? 0 : (y.used && y.options.showLabels ? (y.maxLabel.width + margin) : 0)) +
+ (y.used && y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset;
+
+ p.right += (options.grid.circular ? 0 : (y2.used && y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) +
+ (y2.used && y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset;
+ }
+
+ p.top = Math.floor(p.top); // In order the outline not to be blured
+
+ this.plotWidth = this.canvasWidth - p.left - p.right;
+ this.plotHeight = this.canvasHeight - p.bottom - p.top;
+
+ // TODO post refactor, fix this
+ x.length = x2.length = this.plotWidth;
+ y.length = y2.length = this.plotHeight;
+ y.offset = y2.offset = this.plotHeight;
+ x.setScale();
+ x2.setScale();
+ y.setScale();
+ y2.setScale();
+ },
+ /**
+ * Draws grid, labels, series and outline.
+ */
+ draw: function(after) {
+
+ var
+ context = this.ctx,
+ i;
+
+ E.fire(this.el, 'flotr:beforedraw', [this.series, this]);
+
+ if (this.series.length) {
+
+ context.save();
+ context.translate(this.plotOffset.left, this.plotOffset.top);
+
+ for (i = 0; i < this.series.length; i++) {
+ if (!this.series[i].hide) this.drawSeries(this.series[i]);
+ }
+
+ context.restore();
+ this.clip();
+ }
+
+ E.fire(this.el, 'flotr:afterdraw', [this.series, this]);
+ if (after) after();
+ },
+ /**
+ * Actually draws the graph.
+ * @param {Object} series - series to draw
+ */
+ drawSeries: function(series){
+
+ function drawChart (series, typeKey) {
+ var options = this.getOptions(series, typeKey);
+ this[typeKey].draw(options);
+ }
+
+ var drawn = false;
+ series = series || this.series;
+
+ _.each(flotr.graphTypes, function (type, typeKey) {
+ if (series[typeKey] && series[typeKey].show && this[typeKey]) {
+ drawn = true;
+ drawChart.call(this, series, typeKey);
+ }
+ }, this);
+
+ if (!drawn) drawChart.call(this, series, this.options.defaultType);
+ },
+
+ getOptions : function (series, typeKey) {
+ var
+ type = series[typeKey],
+ graphType = this[typeKey],
+ options = {
+ context : this.ctx,
+ width : this.plotWidth,
+ height : this.plotHeight,
+ fontSize : this.options.fontSize,
+ fontColor : this.options.fontColor,
+ textEnabled : this.textEnabled,
+ htmlText : this.options.HtmlText,
+ text : this._text, // TODO Is this necessary?
+ element : this.el,
+ data : series.data,
+ color : series.color,
+ shadowSize : series.shadowSize,
+ xScale : _.bind(series.xaxis.d2p, series.xaxis),
+ yScale : _.bind(series.yaxis.d2p, series.yaxis)
+ };
+
+ options = flotr.merge(type, options);
+
+ // Fill
+ options.fillStyle = this.processColor(
+ type.fillColor || series.color,
+ {opacity: type.fillOpacity}
+ );
+
+ return options;
+ },
+ /**
+ * Calculates the coordinates from a mouse event object.
+ * @param {Event} event - Mouse Event object.
+ * @return {Object} Object with coordinates of the mouse.
+ */
+ getEventPosition: function (e){
+
+ var
+ d = document,
+ b = d.body,
+ de = d.documentElement,
+ axes = this.axes,
+ plotOffset = this.plotOffset,
+ lastMousePos = this.lastMousePos,
+ pointer = E.eventPointer(e),
+ dx = pointer.x - lastMousePos.pageX,
+ dy = pointer.y - lastMousePos.pageY,
+ r, rx, ry;
+
+ if ('ontouchstart' in this.el) {
+ r = D.position(this.overlay);
+ rx = pointer.x - r.left - plotOffset.left;
+ ry = pointer.y - r.top - plotOffset.top;
+ } else {
+ r = this.overlay.getBoundingClientRect();
+ rx = e.clientX - r.left - plotOffset.left - b.scrollLeft - de.scrollLeft;
+ ry = e.clientY - r.top - plotOffset.top - b.scrollTop - de.scrollTop;
+ }
+
+ return {
+ x: axes.x.p2d(rx),
+ x2: axes.x2.p2d(rx),
+ y: axes.y.p2d(ry),
+ y2: axes.y2.p2d(ry),
+ relX: rx,
+ relY: ry,
+ dX: dx,
+ dY: dy,
+ absX: pointer.x,
+ absY: pointer.y,
+ pageX: pointer.x,
+ pageY: pointer.y
+ };
+ },
+ /**
+ * Observes the 'click' event and fires the 'flotr:click' event.
+ * @param {Event} event - 'click' Event object.
+ */
+ clickHandler: function(event){
+ if(this.ignoreClick){
+ this.ignoreClick = false;
+ return this.ignoreClick;
+ }
+ E.fire(this.el, 'flotr:click', [this.getEventPosition(event), this]);
+ },
+ /**
+ * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event.
+ * @param {Event} event - 'mousemove' Event object.
+ */
+ mouseMoveHandler: function(event){
+ if (this.mouseDownMoveHandler) return;
+ var pos = this.getEventPosition(event);
+ E.fire(this.el, 'flotr:mousemove', [event, pos, this]);
+ this.lastMousePos = pos;
+ },
+ /**
+ * Observes the 'mousedown' event.
+ * @param {Event} event - 'mousedown' Event object.
+ */
+ mouseDownHandler: function (event){
+
+ /*
+ // @TODO Context menu?
+ if(event.isRightClick()) {
+ event.stop();
+
+ var overlay = this.overlay;
+ overlay.hide();
+
+ function cancelContextMenu () {
+ overlay.show();
+ E.stopObserving(document, 'mousemove', cancelContextMenu);
+ }
+ E.observe(document, 'mousemove', cancelContextMenu);
+ return;
+ }
+ */
+
+ if (this.mouseUpHandler) return;
+ this.mouseUpHandler = _.bind(function (e) {
+ E.stopObserving(document, 'mouseup', this.mouseUpHandler);
+ E.stopObserving(document, 'mousemove', this.mouseDownMoveHandler);
+ this.mouseDownMoveHandler = null;
+ this.mouseUpHandler = null;
+ // @TODO why?
+ //e.stop();
+ E.fire(this.el, 'flotr:mouseup', [e, this]);
+ }, this);
+ this.mouseDownMoveHandler = _.bind(function (e) {
+ var pos = this.getEventPosition(e);
+ E.fire(this.el, 'flotr:mousemove', [event, pos, this]);
+ this.lastMousePos = pos;
+ }, this);
+ E.observe(document, 'mouseup', this.mouseUpHandler);
+ E.observe(document, 'mousemove', this.mouseDownMoveHandler);
+ E.fire(this.el, 'flotr:mousedown', [event, this]);
+ this.ignoreClick = false;
+ },
+ drawTooltip: function(content, x, y, options) {
+ var mt = this.getMouseTrack(),
+ style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;',
+ p = options.position,
+ m = options.margin,
+ plotOffset = this.plotOffset;
+
+ if(x !== null && y !== null){
+ if (!options.relative) { // absolute to the canvas
+ if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;';
+ else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;';
+ if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;';
+ else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;';
+ }
+ else { // relative to the mouse
+ if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;';
+ else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;';
+ if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;';
+ else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;';
+ }
+
+ mt.style.cssText = style;
+ D.empty(mt);
+ D.insert(mt, content);
+ D.show(mt);
+ }
+ else {
+ D.hide(mt);
+ }
+ },
+
+ clip: function () {
+
+ var
+ ctx = this.ctx,
+ o = this.plotOffset,
+ w = this.canvasWidth,
+ h = this.canvasHeight;
+
+ if (flotr.isIE && flotr.isIE < 9) {
+ // Clipping for excanvas :-(
+ ctx.save();
+ ctx.fillStyle = this.processColor(this.options.ieBackgroundColor);
+ ctx.fillRect(0, 0, w, o.top);
+ ctx.fillRect(0, 0, o.left, h);
+ ctx.fillRect(0, h - o.bottom, w, o.bottom);
+ ctx.fillRect(w - o.right, 0, o.right,h);
+ ctx.restore();
+ } else {
+ ctx.clearRect(0, 0, w, o.top);
+ ctx.clearRect(0, 0, o.left, h);
+ ctx.clearRect(0, h - o.bottom, w, o.bottom);
+ ctx.clearRect(w - o.right, 0, o.right,h);
+ }
+ },
+
+ _initMembers: function() {
+ this._handles = [];
+ this.lastMousePos = {pageX: null, pageY: null };
+ this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0};
+ this.ignoreClick = true;
+ this.prevHit = null;
+ },
+
+ _initGraphTypes: function() {
+ _.each(flotr.graphTypes, function(handler, graphType){
+ this[graphType] = flotr.clone(handler);
+ }, this);
+ },
+
+ _initEvents: function () {
+
+ var
+ el = this.el,
+ touchendHandler, movement, touchend;
+
+ if ('ontouchstart' in el) {
+
+ touchendHandler = _.bind(function (e) {
+ touchend = true;
+ E.stopObserving(document, 'touchend', touchendHandler);
+ E.fire(el, 'flotr:mouseup', [event, this]);
+ this.multitouches = null;
+
+ if (!movement) {
+ this.clickHandler(e);
+ }
+ }, this);
+
+ this.observe(this.overlay, 'touchstart', _.bind(function (e) {
+ movement = false;
+ touchend = false;
+ this.ignoreClick = false;
+
+ if (e.touches && e.touches.length > 1) {
+ this.multitouches = e.touches;
+ }
+
+ E.fire(el, 'flotr:mousedown', [event, this]);
+ this.observe(document, 'touchend', touchendHandler);
+ }, this));
+
+ this.observe(this.overlay, 'touchmove', _.bind(function (e) {
+
+ var pos = this.getEventPosition(e);
+
+ e.preventDefault();
+
+ movement = true;
+
+ if (this.multitouches || (e.touches && e.touches.length > 1)) {
+ this.multitouches = e.touches;
+ } else {
+ if (!touchend) {
+ E.fire(el, 'flotr:mousemove', [event, pos, this]);
+ }
+ }
+ this.lastMousePos = pos;
+ }, this));
+
+ } else {
+ this.
+ observe(this.overlay, 'mousedown', _.bind(this.mouseDownHandler, this)).
+ observe(el, 'mousemove', _.bind(this.mouseMoveHandler, this)).
+ observe(this.overlay, 'click', _.bind(this.clickHandler, this)).
+ observe(el, 'mouseout', function () {
+ E.fire(el, 'flotr:mouseout');
+ });
+ }
+ },
+
+ /**
+ * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use
+ * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements
+ * are created, the elements are inserted into the container element.
+ */
+ _initCanvas: function(){
+ var el = this.el,
+ o = this.options,
+ children = el.children,
+ removedChildren = [],
+ child, i,
+ size, style;
+
+ // Empty the el
+ for (i = children.length; i--;) {
+ child = children[i];
+ if (!this.canvas && child.className === 'flotr-canvas') {
+ this.canvas = child;
+ } else if (!this.overlay && child.className === 'flotr-overlay') {
+ this.overlay = child;
+ } else {
+ removedChildren.push(child);
+ }
+ }
+ for (i = removedChildren.length; i--;) {
+ el.removeChild(removedChildren[i]);
+ }
+
+ D.setStyles(el, {position: 'relative'}); // For positioning labels and overlay.
+ size = {};
+ size.width = el.clientWidth;
+ size.height = el.clientHeight;
+
+ if(size.width <= 0 || size.height <= 0 || o.resolution <= 0){
+ throw 'Invalid dimensions for plot, width = ' + size.width + ', height = ' + size.height + ', resolution = ' + o.resolution;
+ }
+
+ // Main canvas for drawing graph types
+ this.canvas = getCanvas(this.canvas, 'canvas');
+ // Overlay canvas for interactive features
+ this.overlay = getCanvas(this.overlay, 'overlay');
+ this.ctx = getContext(this.canvas);
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ this.octx = getContext(this.overlay);
+ this.octx.clearRect(0, 0, this.overlay.width, this.overlay.height);
+ this.canvasHeight = size.height;
+ this.canvasWidth = size.width;
+ this.textEnabled = !!this.ctx.drawText || !!this.ctx.fillText; // Enable text functions
+
+ function getCanvas(canvas, name){
+ if(!canvas){
+ canvas = D.create('canvas');
+ if (typeof FlashCanvas != "undefined" && typeof canvas.getContext === 'function') {
+ FlashCanvas.initElement(canvas);
+ }
+ canvas.className = 'flotr-'+name;
+ canvas.style.cssText = 'position:absolute;left:0px;top:0px;';
+ D.insert(el, canvas);
+ }
+ _.each(size, function(size, attribute){
+ D.show(canvas);
+ if (name == 'canvas' && canvas.getAttribute(attribute) === size) {
+ return;
+ }
+ canvas.setAttribute(attribute, size * o.resolution);
+ canvas.style[attribute] = size + 'px';
+ });
+ canvas.context_ = null; // Reset the ExCanvas context
+ return canvas;
+ }
+
+ function getContext(canvas){
+ if(window.G_vmlCanvasManager) window.G_vmlCanvasManager.initElement(canvas); // For ExCanvas
+ var context = canvas.getContext('2d');
+ if(!window.G_vmlCanvasManager) context.scale(o.resolution, o.resolution);
+ return context;
+ }
+ },
+
+ _initPlugins: function(){
+ // TODO Should be moved to flotr and mixed in.
+ _.each(flotr.plugins, function(plugin, name){
+ _.each(plugin.callbacks, function(fn, c){
+ this.observe(this.el, c, _.bind(fn, this));
+ }, this);
+ this[name] = flotr.clone(plugin);
+ _.each(this[name], function(fn, p){
+ if (_.isFunction(fn))
+ this[name][p] = _.bind(fn, this);
+ }, this);
+ }, this);
+ },
+
+ /**
+ * Sets options and initializes some variables and color specific values, used by the constructor.
+ * @param {Object} opts - options object
+ */
+ _initOptions: function(opts){
+ var options = flotr.clone(flotr.defaultOptions);
+ options.x2axis = _.extend(_.clone(options.xaxis), options.x2axis);
+ options.y2axis = _.extend(_.clone(options.yaxis), options.y2axis);
+ this.options = flotr.merge(opts || {}, options);
+
+ if (this.options.grid.minorVerticalLines === null &&
+ this.options.xaxis.scaling === 'logarithmic') {
+ this.options.grid.minorVerticalLines = true;
+ }
+ if (this.options.grid.minorHorizontalLines === null &&
+ this.options.yaxis.scaling === 'logarithmic') {
+ this.options.grid.minorHorizontalLines = true;
+ }
+
+ E.fire(this.el, 'flotr:afterinitoptions', [this]);
+
+ this.axes = flotr.Axis.getAxes(this.options);
+
+ // Initialize some variables used throughout this function.
+ var assignedColors = [],
+ colors = [],
+ ln = this.series.length,
+ neededColors = this.series.length,
+ oc = this.options.colors,
+ usedColors = [],
+ variation = 0,
+ c, i, j, s;
+
+ // Collect user-defined colors from series.
+ for(i = neededColors - 1; i > -1; --i){
+ c = this.series[i].color;
+ if(c){
+ --neededColors;
+ if(_.isNumber(c)) assignedColors.push(c);
+ else usedColors.push(flotr.Color.parse(c));
+ }
+ }
+
+ // Calculate the number of colors that need to be generated.
+ for(i = assignedColors.length - 1; i > -1; --i)
+ neededColors = Math.max(neededColors, assignedColors[i] + 1);
+
+ // Generate needed number of colors.
+ for(i = 0; colors.length < neededColors;){
+ c = (oc.length == i) ? new flotr.Color(100, 100, 100) : flotr.Color.parse(oc[i]);
+
+ // Make sure each serie gets a different color.
+ var sign = variation % 2 == 1 ? -1 : 1,
+ factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
+ c.scale(factor, factor, factor);
+
+ /**
+ * @todo if we're getting too close to something else, we should probably skip this one
+ */
+ colors.push(c);
+
+ if(++i >= oc.length){
+ i = 0;
+ ++variation;
+ }
+ }
+
+ // Fill the options with the generated colors.
+ for(i = 0, j = 0; i < ln; ++i){
+ s = this.series[i];
+
+ // Assign the color.
+ if (!s.color){
+ s.color = colors[j++].toString();
+ }else if(_.isNumber(s.color)){
+ s.color = colors[s.color].toString();
+ }
+
+ // Every series needs an axis
+ if (!s.xaxis) s.xaxis = this.axes.x;
+ if (s.xaxis == 1) s.xaxis = this.axes.x;
+ else if (s.xaxis == 2) s.xaxis = this.axes.x2;
+
+ if (!s.yaxis) s.yaxis = this.axes.y;
+ if (s.yaxis == 1) s.yaxis = this.axes.y;
+ else if (s.yaxis == 2) s.yaxis = this.axes.y2;
+
+ // Apply missing options to the series.
+ for (var t in flotr.graphTypes){
+ s[t] = _.extend(_.clone(this.options[t]), s[t]);
+ }
+ s.mouse = _.extend(_.clone(this.options.mouse), s.mouse);
+
+ if (_.isUndefined(s.shadowSize)) s.shadowSize = this.options.shadowSize;
+ }
+ },
+
+ _setEl: function(el) {
+ if (!el) throw 'The target container doesn\'t exist';
+ else if (el.graph instanceof Graph) el.graph.destroy();
+ else if (!el.clientWidth) throw 'The target container must be visible';
+
+ el.graph = this;
+ this.el = el;
+ }
+};
+
+Flotr.Graph = Graph;
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/Series.js b/addons/web_graph/static/lib/flotr2/js/Series.js
new file mode 100644
index 00000000000..e9a0c0fbeaa
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/Series.js
@@ -0,0 +1,74 @@
+/**
+ * Flotr Series Library
+ */
+
+(function () {
+
+var
+ _ = Flotr._;
+
+function Series (o) {
+ _.extend(this, o);
+}
+
+Series.prototype = {
+
+ getRange: function () {
+
+ var
+ data = this.data,
+ length = data.length,
+ xmin = Number.MAX_VALUE,
+ ymin = Number.MAX_VALUE,
+ xmax = -Number.MAX_VALUE,
+ ymax = -Number.MAX_VALUE,
+ xused = false,
+ yused = false,
+ x, y, i;
+
+ if (length < 0 || this.hide) return false;
+
+ for (i = 0; i < length; i++) {
+ x = data[i][0];
+ y = data[i][1];
+ if (x < xmin) { xmin = x; xused = true; }
+ if (x > xmax) { xmax = x; xused = true; }
+ if (y < ymin) { ymin = y; yused = true; }
+ if (y > ymax) { ymax = y; yused = true; }
+ }
+
+ return {
+ xmin : xmin,
+ xmax : xmax,
+ ymin : ymin,
+ ymax : ymax,
+ xused : xused,
+ yused : yused
+ };
+ }
+};
+
+_.extend(Series, {
+ /**
+ * Collects dataseries from input and parses the series into the right format. It returns an Array
+ * of Objects each having at least the 'data' key set.
+ * @param {Array, Object} data - Object or array of dataseries
+ * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)})
+ */
+ getSeries: function(data){
+ return _.map(data, function(s){
+ var series;
+ if (s.data) {
+ series = new Series();
+ _.extend(series, s);
+ } else {
+ series = new Series({data:s});
+ }
+ return series;
+ });
+ }
+});
+
+Flotr.Series = Series;
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/Text.js b/addons/web_graph/static/lib/flotr2/js/Text.js
new file mode 100644
index 00000000000..f24cf7ac4bf
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/Text.js
@@ -0,0 +1,88 @@
+/**
+ * Text Utilities
+ */
+(function () {
+
+var
+ F = Flotr,
+ D = F.DOM,
+ _ = F._,
+
+Text = function (o) {
+ this.o = o;
+};
+
+Text.prototype = {
+
+ dimensions : function (text, canvasStyle, htmlStyle, className) {
+
+ if (!text) return { width : 0, height : 0 };
+
+ return (this.o.html) ?
+ this.html(text, this.o.element, htmlStyle, className) :
+ this.canvas(text, canvasStyle);
+ },
+
+ canvas : function (text, style) {
+
+ if (!this.o.textEnabled) return;
+ style = style || {};
+
+ var
+ metrics = this.measureText(text, style),
+ width = metrics.width,
+ height = style.size || F.defaultOptions.fontSize,
+ angle = style.angle || 0,
+ cosAngle = Math.cos(angle),
+ sinAngle = Math.sin(angle),
+ widthPadding = 2,
+ heightPadding = 6,
+ bounds;
+
+ bounds = {
+ width: Math.abs(cosAngle * width) + Math.abs(sinAngle * height) + widthPadding,
+ height: Math.abs(sinAngle * width) + Math.abs(cosAngle * height) + heightPadding
+ };
+
+ return bounds;
+ },
+
+ html : function (text, element, style, className) {
+
+ var div = D.create('div');
+
+ D.setStyles(div, { 'position' : 'absolute', 'top' : '-10000px' });
+ D.insert(div, '
' + text + '
');
+ D.insert(this.o.element, div);
+
+ return D.size(div);
+ },
+
+ measureText : function (text, style) {
+
+ var
+ context = this.o.ctx,
+ metrics;
+
+ if (!context.fillText || (F.isIphone && context.measure)) {
+ return { width : context.measure(text, style)};
+ }
+
+ style = _.extend({
+ size: F.defaultOptions.fontSize,
+ weight: 1,
+ angle: 0
+ }, style);
+
+ context.save();
+ context.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
+ metrics = context.measureText(text);
+ context.restore();
+
+ return metrics;
+ }
+};
+
+Flotr.Text = Text;
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js b/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js
new file mode 100644
index 00000000000..611ebf85800
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js
@@ -0,0 +1,84 @@
+(function () {
+
+var D = Flotr.DOM;
+
+Flotr.addPlugin('crosshair', {
+ options: {
+ mode: null, // => one of null, 'x', 'y' or 'xy'
+ color: '#FF0000', // => crosshair color
+ hideCursor: true // => hide the cursor when the crosshair is shown
+ },
+ callbacks: {
+ 'flotr:mousemove': function(e, pos) {
+ if (this.options.crosshair.mode) {
+ this.crosshair.clearCrosshair();
+ this.crosshair.drawCrosshair(pos);
+ }
+ }
+ },
+ /**
+ * Draws the selection box.
+ */
+ drawCrosshair: function(pos) {
+ var octx = this.octx,
+ options = this.options.crosshair,
+ plotOffset = this.plotOffset,
+ x = plotOffset.left + pos.relX + 0.5,
+ y = plotOffset.top + pos.relY + 0.5;
+
+ if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) {
+ this.el.style.cursor = null;
+ D.removeClass(this.el, 'flotr-crosshair');
+ return;
+ }
+
+ if (options.hideCursor) {
+ this.el.style.cursor = 'none';
+ D.addClass(this.el, 'flotr-crosshair');
+ }
+
+ octx.save();
+ octx.strokeStyle = options.color;
+ octx.lineWidth = 1;
+ octx.beginPath();
+
+ if (options.mode.indexOf('x') != -1) {
+ octx.moveTo(x, plotOffset.top);
+ octx.lineTo(x, plotOffset.top + this.plotHeight);
+ }
+
+ if (options.mode.indexOf('y') != -1) {
+ octx.moveTo(plotOffset.left, y);
+ octx.lineTo(plotOffset.left + this.plotWidth, y);
+ }
+
+ octx.stroke();
+ octx.restore();
+ },
+ /**
+ * Removes the selection box from the overlay canvas.
+ */
+ clearCrosshair: function() {
+
+ var
+ plotOffset = this.plotOffset,
+ position = this.lastMousePos,
+ context = this.octx;
+
+ if (position) {
+ context.clearRect(
+ position.relX + plotOffset.left,
+ plotOffset.top,
+ 1,
+ this.plotHeight + 1
+ );
+ context.clearRect(
+ plotOffset.left,
+ position.relY + plotOffset.top,
+ this.plotWidth + 1,
+ 1
+ );
+ }
+ }
+});
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/download.js b/addons/web_graph/static/lib/flotr2/js/plugins/download.js
new file mode 100644
index 00000000000..5d585325be9
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/download.js
@@ -0,0 +1,51 @@
+(function() {
+
+var
+ D = Flotr.DOM,
+ _ = Flotr._;
+
+function getImage (type, canvas, width, height) {
+
+ // TODO add scaling for w / h
+ var
+ mime = 'image/'+type,
+ data = canvas.toDataURL(mime),
+ image = new Image();
+ image.src = data;
+ return image;
+}
+
+Flotr.addPlugin('download', {
+
+ saveImage: function (type, width, height, replaceCanvas) {
+ var image = null;
+ if (Flotr.isIE && Flotr.isIE < 9) {
+ image = ''+this.canvas.firstChild.innerHTML+'';
+ return window.open().document.write(image);
+ }
+
+ if (type !== 'jpeg' && type !== 'png') return;
+
+ image = getImage(type, this.canvas, width, height);
+
+ if (_.isElement(image) && replaceCanvas) {
+ this.download.restoreCanvas();
+ D.hide(this.canvas);
+ D.hide(this.overlay);
+ D.setStyles({position: 'absolute'});
+ D.insert(this.el, image);
+ this.saveImageElement = image;
+ } else {
+ return window.open(image.src);
+ }
+ },
+
+ restoreCanvas: function() {
+ D.show(this.canvas);
+ D.show(this.overlay);
+ if (this.saveImageElement) this.el.removeChild(this.saveImageElement);
+ this.saveImageElement = null;
+ }
+});
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/grid.js b/addons/web_graph/static/lib/flotr2/js/plugins/grid.js
new file mode 100644
index 00000000000..ab90d8d591a
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/grid.js
@@ -0,0 +1,208 @@
+(function () {
+
+var E = Flotr.EventAdapter,
+ _ = Flotr._;
+
+Flotr.addPlugin('graphGrid', {
+
+ callbacks: {
+ 'flotr:beforedraw' : function () {
+ this.graphGrid.drawGrid();
+ },
+ 'flotr:afterdraw' : function () {
+ this.graphGrid.drawOutline();
+ }
+ },
+
+ drawGrid: function(){
+
+ var
+ ctx = this.ctx,
+ options = this.options,
+ grid = options.grid,
+ verticalLines = grid.verticalLines,
+ horizontalLines = grid.horizontalLines,
+ minorVerticalLines = grid.minorVerticalLines,
+ minorHorizontalLines = grid.minorHorizontalLines,
+ plotHeight = this.plotHeight,
+ plotWidth = this.plotWidth,
+ a, v, i, j;
+
+ if(verticalLines || minorVerticalLines ||
+ horizontalLines || minorHorizontalLines){
+ E.fire(this.el, 'flotr:beforegrid', [this.axes.x, this.axes.y, options, this]);
+ }
+ ctx.save();
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = grid.tickColor;
+
+ function circularHorizontalTicks (ticks) {
+ for(i = 0; i < ticks.length; ++i){
+ var ratio = ticks[i].v / a.max;
+ for(j = 0; j <= sides; ++j){
+ ctx[j === 0 ? 'moveTo' : 'lineTo'](
+ Math.cos(j*coeff+angle)*radius*ratio,
+ Math.sin(j*coeff+angle)*radius*ratio
+ );
+ }
+ }
+ }
+ function drawGridLines (ticks, callback) {
+ _.each(_.pluck(ticks, 'v'), function(v){
+ // Don't show lines on upper and lower bounds.
+ if ((v <= a.min || v >= a.max) ||
+ (v == a.min || v == a.max) && grid.outlineWidth)
+ return;
+ callback(Math.floor(a.d2p(v)) + ctx.lineWidth/2);
+ });
+ }
+ function drawVerticalLines (x) {
+ ctx.moveTo(x, 0);
+ ctx.lineTo(x, plotHeight);
+ }
+ function drawHorizontalLines (y) {
+ ctx.moveTo(0, y);
+ ctx.lineTo(plotWidth, y);
+ }
+
+ if (grid.circular) {
+ ctx.translate(this.plotOffset.left+plotWidth/2, this.plotOffset.top+plotHeight/2);
+ var radius = Math.min(plotHeight, plotWidth)*options.radar.radiusRatio/2,
+ sides = this.axes.x.ticks.length,
+ coeff = 2*(Math.PI/sides),
+ angle = -Math.PI/2;
+
+ // Draw grid lines in vertical direction.
+ ctx.beginPath();
+
+ a = this.axes.y;
+
+ if(horizontalLines){
+ circularHorizontalTicks(a.ticks);
+ }
+ if(minorHorizontalLines){
+ circularHorizontalTicks(a.minorTicks);
+ }
+
+ if(verticalLines){
+ _.times(sides, function(i){
+ ctx.moveTo(0, 0);
+ ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
+ });
+ }
+ ctx.stroke();
+ }
+ else {
+ ctx.translate(this.plotOffset.left, this.plotOffset.top);
+
+ // Draw grid background, if present in options.
+ if(grid.backgroundColor){
+ ctx.fillStyle = this.processColor(grid.backgroundColor, {x1: 0, y1: 0, x2: plotWidth, y2: plotHeight});
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
+ }
+
+ ctx.beginPath();
+
+ a = this.axes.x;
+ if (verticalLines) drawGridLines(a.ticks, drawVerticalLines);
+ if (minorVerticalLines) drawGridLines(a.minorTicks, drawVerticalLines);
+
+ a = this.axes.y;
+ if (horizontalLines) drawGridLines(a.ticks, drawHorizontalLines);
+ if (minorHorizontalLines) drawGridLines(a.minorTicks, drawHorizontalLines);
+
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ if(verticalLines || minorVerticalLines ||
+ horizontalLines || minorHorizontalLines){
+ E.fire(this.el, 'flotr:aftergrid', [this.axes.x, this.axes.y, options, this]);
+ }
+ },
+
+ drawOutline: function(){
+ var
+ that = this,
+ options = that.options,
+ grid = options.grid,
+ outline = grid.outline,
+ ctx = that.ctx,
+ backgroundImage = grid.backgroundImage,
+ plotOffset = that.plotOffset,
+ leftOffset = plotOffset.left,
+ topOffset = plotOffset.top,
+ plotWidth = that.plotWidth,
+ plotHeight = that.plotHeight,
+ v, img, src, left, top, globalAlpha;
+
+ if (!grid.outlineWidth) return;
+
+ ctx.save();
+
+ if (grid.circular) {
+ ctx.translate(leftOffset + plotWidth / 2, topOffset + plotHeight / 2);
+ var radius = Math.min(plotHeight, plotWidth) * options.radar.radiusRatio / 2,
+ sides = this.axes.x.ticks.length,
+ coeff = 2*(Math.PI/sides),
+ angle = -Math.PI/2;
+
+ // Draw axis/grid border.
+ ctx.beginPath();
+ ctx.lineWidth = grid.outlineWidth;
+ ctx.strokeStyle = grid.color;
+ ctx.lineJoin = 'round';
+
+ for(i = 0; i <= sides; ++i){
+ ctx[i === 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
+ }
+ //ctx.arc(0, 0, radius, 0, Math.PI*2, true);
+
+ ctx.stroke();
+ }
+ else {
+ ctx.translate(leftOffset, topOffset);
+
+ // Draw axis/grid border.
+ var lw = grid.outlineWidth,
+ orig = 0.5-lw+((lw+1)%2/2),
+ lineTo = 'lineTo',
+ moveTo = 'moveTo';
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = grid.color;
+ ctx.lineJoin = 'miter';
+ ctx.beginPath();
+ ctx.moveTo(orig, orig);
+ plotWidth = plotWidth - (lw / 2) % 1;
+ plotHeight = plotHeight + lw / 2;
+ ctx[outline.indexOf('n') !== -1 ? lineTo : moveTo](plotWidth, orig);
+ ctx[outline.indexOf('e') !== -1 ? lineTo : moveTo](plotWidth, plotHeight);
+ ctx[outline.indexOf('s') !== -1 ? lineTo : moveTo](orig, plotHeight);
+ ctx[outline.indexOf('w') !== -1 ? lineTo : moveTo](orig, orig);
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+ ctx.restore();
+
+ if (backgroundImage) {
+
+ src = backgroundImage.src || backgroundImage;
+ left = (parseInt(backgroundImage.left, 10) || 0) + plotOffset.left;
+ top = (parseInt(backgroundImage.top, 10) || 0) + plotOffset.top;
+ img = new Image();
+
+ img.onload = function() {
+ ctx.save();
+ if (backgroundImage.alpha) ctx.globalAlpha = backgroundImage.alpha;
+ ctx.globalCompositeOperation = 'destination-over';
+ ctx.drawImage(img, 0, 0, img.width, img.height, left, top, plotWidth, plotHeight);
+ ctx.restore();
+ };
+
+ img.src = src;
+ }
+ }
+});
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/handles.js b/addons/web_graph/static/lib/flotr2/js/plugins/handles.js
new file mode 100644
index 00000000000..92fd3247fdf
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/handles.js
@@ -0,0 +1,199 @@
+/**
+ * Selection Handles Plugin
+ *
+ * Depends upon options.selection.mode
+ *
+ * Options
+ * show - True enables the handles plugin.
+ * drag - Left and Right drag handles
+ * scroll - Scrolling handle
+ */
+(function () {
+
+var D = Flotr.DOM;
+
+Flotr.addPlugin('handles', {
+
+ options: {
+ show: false,
+ drag: true,
+ scroll: true
+ },
+
+ callbacks: {
+ 'flotr:afterinit': init,
+ 'flotr:select': handleSelect,
+ 'flotr:mousedown': reset,
+ 'flotr:mousemove': mouseMoveHandler
+ }
+
+});
+
+
+function init() {
+
+ var
+ options = this.options,
+ handles = this.handles,
+ el = this.el,
+ scroll, left, right, container;
+
+ if (!options.selection.mode || !options.handles.show || 'ontouchstart' in el) return;
+
+ handles.initialized = true;
+
+ container = D.node('
');
+ options = options.handles;
+
+ // Drag handles
+ if (options.drag) {
+ right = D.node('
');
+ left = D.node('
');
+ D.insert(container, right);
+ D.insert(container, left);
+ D.hide(left);
+ D.hide(right);
+ handles.left = left;
+ handles.right = right;
+
+ this.observe(left, 'mousedown', function () {
+ handles.moveHandler = leftMoveHandler;
+ });
+ this.observe(right, 'mousedown', function () {
+ handles.moveHandler = rightMoveHandler;
+ });
+ }
+
+ // Scroll handle
+ if (options.scroll) {
+ scroll = D.node('
');
+ D.insert(container, scroll);
+ D.hide(scroll);
+ handles.scroll = scroll;
+ this.observe(scroll, 'mousedown', function () {
+ handles.moveHandler = scrollMoveHandler;
+ });
+ }
+
+ this.observe(document, 'mouseup', function() {
+ handles.moveHandler = null;
+ });
+
+ D.insert(el, container);
+}
+
+
+function handleSelect(selection) {
+
+ if (!this.handles.initialized) return;
+
+ var
+ handles = this.handles,
+ options = this.options.handles,
+ left = handles.left,
+ right = handles.right,
+ scroll = handles.scroll;
+
+ if (options) {
+ if (options.drag) {
+ positionDrag(this, left, selection.x1);
+ positionDrag(this, right, selection.x2);
+ }
+
+ if (options.scroll) {
+ positionScroll(
+ this,
+ scroll,
+ selection.x1,
+ selection.x2
+ );
+ }
+ }
+}
+
+function positionDrag(graph, handle, x) {
+
+ D.show(handle);
+
+ var size = D.size(handle),
+ l = Math.round(graph.axes.x.d2p(x) - size.width / 2),
+ t = (graph.plotHeight - size.height) / 2;
+
+ D.setStyles(handle, {
+ 'left' : l+'px',
+ 'top' : t+'px'
+ });
+}
+
+function positionScroll(graph, handle, x1, x2) {
+
+ D.show(handle);
+
+ var size = D.size(handle),
+ l = Math.round(graph.axes.x.d2p(x1)),
+ t = (graph.plotHeight) - size.height / 2,
+ w = (graph.axes.x.d2p(x2) - graph.axes.x.d2p(x1));
+
+ D.setStyles(handle, {
+ 'left' : l+'px',
+ 'top' : t+'px',
+ 'width': w+'px'
+ });
+}
+
+function reset() {
+
+ if (!this.handles.initialized) return;
+
+ var
+ handles = this.handles;
+ if (handles) {
+ D.hide(handles.left);
+ D.hide(handles.right);
+ D.hide(handles.scroll);
+ }
+}
+
+function mouseMoveHandler(e, position) {
+
+ if (!this.handles.initialized) return;
+ if (!this.handles.moveHandler) return;
+
+ var
+ delta = position.dX,
+ selection = this.selection.selection,
+ area = this.selection.getArea(),
+ handles = this.handles;
+
+ handles.moveHandler(area, delta);
+ checkSwap(area, handles);
+
+ this.selection.setSelection(area);
+}
+
+function checkSwap (area, handles) {
+ var moveHandler = handles.moveHandler;
+ if (area.x1 > area.x2) {
+ if (moveHandler == leftMoveHandler) {
+ moveHandler = rightMoveHandler;
+ } else if (moveHandler == rightMoveHandler) {
+ moveHandler = leftMoveHandler;
+ }
+ handles.moveHandler = moveHandler;
+ }
+}
+
+function leftMoveHandler(area, delta) {
+ area.x1 += delta;
+}
+
+function rightMoveHandler(area, delta) {
+ area.x2 += delta;
+}
+
+function scrollMoveHandler(area, delta) {
+ area.x1 += delta;
+ area.x2 += delta;
+}
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/hit.js b/addons/web_graph/static/lib/flotr2/js/plugins/hit.js
new file mode 100644
index 00000000000..eeff570df35
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/hit.js
@@ -0,0 +1,337 @@
+(function () {
+
+var
+ D = Flotr.DOM,
+ _ = Flotr._,
+ flotr = Flotr,
+ S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;';
+
+Flotr.addPlugin('hit', {
+ callbacks: {
+ 'flotr:mousemove': function(e, pos) {
+ this.hit.track(pos);
+ },
+ 'flotr:click': function(pos) {
+ this.hit.track(pos);
+ },
+ 'flotr:mouseout': function() {
+ this.hit.clearHit();
+ }
+ },
+ track : function (pos) {
+ if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) {
+ this.hit.hit(pos);
+ }
+ },
+ /**
+ * Try a method on a graph type. If the method exists, execute it.
+ * @param {Object} series
+ * @param {String} method Method name.
+ * @param {Array} args Arguments applied to method.
+ * @return executed successfully or failed.
+ */
+ executeOnType: function(s, method, args){
+ var
+ success = false,
+ options;
+
+ if (!_.isArray(s)) s = [s];
+
+ function e(s, index) {
+ _.each(_.keys(flotr.graphTypes), function (type) {
+ if (s[type] && s[type].show && this[type][method]) {
+ options = this.getOptions(s, type);
+
+ options.fill = !!s.mouse.fillColor;
+ options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
+ options.color = s.mouse.lineColor;
+ options.context = this.octx;
+ options.index = index;
+
+ if (args) options.args = args;
+ this[type][method].call(this[type], options);
+ success = true;
+ }
+ }, this);
+ }
+ _.each(s, e, this);
+
+ return success;
+ },
+ /**
+ * Updates the mouse tracking point on the overlay.
+ */
+ drawHit: function(n){
+ var octx = this.octx,
+ s = n.series;
+
+ if (s.mouse.lineColor) {
+ octx.save();
+ octx.lineWidth = (s.points ? s.points.lineWidth : 1);
+ octx.strokeStyle = s.mouse.lineColor;
+ octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
+ octx.translate(this.plotOffset.left, this.plotOffset.top);
+
+ if (!this.hit.executeOnType(s, 'drawHit', n)) {
+ var xa = n.xaxis,
+ ya = n.yaxis;
+
+ octx.beginPath();
+ // TODO fix this (points) should move to general testable graph mixin
+ octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true);
+ octx.fill();
+ octx.stroke();
+ octx.closePath();
+ }
+ octx.restore();
+ }
+ this.prevHit = n;
+ },
+ /**
+ * Removes the mouse tracking point from the overlay.
+ */
+ clearHit: function(){
+ var prev = this.prevHit,
+ octx = this.octx,
+ plotOffset = this.plotOffset;
+ octx.save();
+ octx.translate(plotOffset.left, plotOffset.top);
+ if (prev) {
+ if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) {
+ // TODO fix this (points) should move to general testable graph mixin
+ var
+ s = prev.series,
+ lw = (s.points ? s.points.lineWidth : 1);
+ offset = (s.points.radius || s.mouse.radius) + lw;
+ octx.clearRect(
+ prev.xaxis.d2p(prev.x) - offset,
+ prev.yaxis.d2p(prev.y) - offset,
+ offset*2,
+ offset*2
+ );
+ }
+ D.hide(this.mouseTrack);
+ this.prevHit = null;
+ }
+ octx.restore();
+ },
+ /**
+ * Retrieves the nearest data point from the mouse cursor. If it's within
+ * a certain range, draw a point on the overlay canvas and display the x and y
+ * value of the data.
+ * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor.
+ */
+ hit: function(mouse){
+
+ var
+ options = this.options,
+ prevHit = this.prevHit,
+ closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis;
+
+ if (this.series.length === 0) return;
+
+ // Nearest data element.
+ // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse,
+ // xaxis, yaxis, series, index, seriesIndex
+ n = {
+ relX : mouse.relX,
+ relY : mouse.relY,
+ absX : mouse.absX,
+ absY : mouse.absY
+ };
+
+ if (options.mouse.trackY &&
+ !options.mouse.trackAll &&
+ this.hit.executeOnType(this.series, 'hit', [mouse, n]))
+ {
+
+ if (!_.isUndefined(n.seriesIndex)) {
+ series = this.series[n.seriesIndex];
+ n.series = series;
+ n.mouse = series.mouse;
+ n.xaxis = series.xaxis;
+ n.yaxis = series.yaxis;
+ }
+ } else {
+
+ closest = this.hit.closest(mouse);
+
+ if (closest) {
+
+ closest = options.mouse.trackY ? closest.point : closest.x;
+ seriesIndex = closest.seriesIndex;
+ series = this.series[seriesIndex];
+ xaxis = series.xaxis;
+ yaxis = series.yaxis;
+ sensibility = 2 * series.mouse.sensibility;
+
+ if
+ (options.mouse.trackAll ||
+ (closest.distanceX < sensibility / xaxis.scale &&
+ (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale)))
+ {
+ n.series = series;
+ n.xaxis = series.xaxis;
+ n.yaxis = series.yaxis;
+ n.mouse = series.mouse;
+ n.x = closest.x;
+ n.y = closest.y;
+ n.dist = closest.distance;
+ n.index = closest.dataIndex;
+ n.seriesIndex = seriesIndex;
+ }
+ }
+ }
+
+ if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) {
+ this.hit.clearHit();
+ if (n.series && n.mouse && n.mouse.track) {
+ this.hit.drawMouseTrack(n);
+ this.hit.drawHit(n);
+ Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]);
+ }
+ }
+ },
+
+ closest : function (mouse) {
+
+ var
+ series = this.series,
+ options = this.options,
+ mouseX = mouse.x,
+ mouseY = mouse.y,
+ compare = Number.MAX_VALUE,
+ compareX = Number.MAX_VALUE,
+ closest = {},
+ closestX = {},
+ check = false,
+ serie, data,
+ distance, distanceX, distanceY,
+ x, y, i, j;
+
+ function setClosest (o) {
+ o.distance = distance;
+ o.distanceX = distanceX;
+ o.distanceY = distanceY;
+ o.seriesIndex = i;
+ o.dataIndex = j;
+ o.x = x;
+ o.y = y;
+ }
+
+ for (i = 0; i < series.length; i++) {
+
+ serie = series[i];
+ data = serie.data;
+
+ if (data.length) check = true;
+
+ for (j = data.length; j--;) {
+
+ x = data[j][0];
+ y = data[j][1];
+
+ if (x === null || y === null) continue;
+
+ // don't check if the point isn't visible in the current range
+ if (x < serie.xaxis.min || x > serie.xaxis.max) continue;
+
+ distanceX = Math.abs(x - mouseX);
+ distanceY = Math.abs(y - mouseY);
+
+ // Skip square root for speed
+ distance = distanceX * distanceX + distanceY * distanceY;
+
+ if (distance < compare) {
+ compare = distance;
+ setClosest(closest);
+ }
+
+ if (distanceX < compareX) {
+ compareX = distanceX;
+ setClosest(closestX);
+ }
+ }
+ }
+
+ return check ? {
+ point : closest,
+ x : closestX
+ } : false;
+ },
+
+ drawMouseTrack : function (n) {
+
+ var
+ pos = '',
+ s = n.series,
+ p = n.mouse.position,
+ m = n.mouse.margin,
+ elStyle = S_MOUSETRACK,
+ mouseTrack = this.mouseTrack,
+ plotOffset = this.plotOffset,
+ left = plotOffset.left,
+ right = plotOffset.right,
+ bottom = plotOffset.bottom,
+ top = plotOffset.top,
+ decimals = n.mouse.trackDecimals,
+ options = this.options;
+
+ // Create
+ if (!mouseTrack) {
+ mouseTrack = D.node('
');
+ this.mouseTrack = mouseTrack;
+ D.insert(this.el, mouseTrack);
+ }
+
+ if (!n.mouse.relative) { // absolute to the canvas
+
+ if (p.charAt(0) == 'n') pos += 'top:' + (m + top) + 'px;bottom:auto;';
+ else if (p.charAt(0) == 's') pos += 'bottom:' + (m + bottom) + 'px;top:auto;';
+ if (p.charAt(1) == 'e') pos += 'right:' + (m + right) + 'px;left:auto;';
+ else if (p.charAt(1) == 'w') pos += 'left:' + (m + left) + 'px;right:auto;';
+
+ // Bars
+ } else if (s.bars.show) {
+ pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y/2) + this.canvasHeight) + 'px;top:auto;';
+ pos += 'left:' + (m + left + n.xaxis.d2p(n.x - options.bars.barWidth/2)) + 'px;right:auto;';
+
+ // Pie
+ } else if (s.pie.show) {
+ var center = {
+ x: (this.plotWidth)/2,
+ y: (this.plotHeight)/2
+ },
+ radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2,
+ bisection = n.sAngle
(isX ? graph.plotWidth : graph.plotHeight)) { continue; }
+
+ Flotr.drawText(
+ ctx, tick.label,
+ leftOffset(graph, isX, isFirst, offset),
+ topOffset(graph, isX, isFirst, offset),
+ style
+ );
+
+ // Only draw on axis y2
+ if (!isX && !isFirst) {
+ ctx.save();
+ ctx.strokeStyle = style.color;
+ ctx.beginPath();
+ ctx.moveTo(graph.plotOffset.left + graph.plotWidth - 8, graph.plotOffset.top + axis.d2p(tick.v));
+ ctx.lineTo(graph.plotOffset.left + graph.plotWidth, graph.plotOffset.top + axis.d2p(tick.v));
+ ctx.stroke();
+ ctx.restore();
+ }
+ }
+
+ function continueShowingLabels (axis) {
+ return axis.options.showLabels && axis.used;
+ }
+ function leftOffset (graph, isX, isFirst, offset) {
+ return graph.plotOffset.left +
+ (isX ? offset :
+ (isFirst ?
+ -options.grid.labelMargin :
+ options.grid.labelMargin + graph.plotWidth));
+ }
+ function topOffset (graph, isX, isFirst, offset) {
+ return graph.plotOffset.top +
+ (isX ? options.grid.labelMargin : offset) +
+ ((isX && isFirst) ? graph.plotHeight : 0);
+ }
+ }
+
+ function drawLabelHtml (graph, axis) {
+ var
+ isX = axis.orientation === 1,
+ isFirst = axis.n === 1,
+ name = '',
+ left, style, top,
+ offset = graph.plotOffset;
+
+ if (!isX && !isFirst) {
+ ctx.save();
+ ctx.strokeStyle = axis.options.color || options.grid.color;
+ ctx.beginPath();
+ }
+
+ if (axis.options.showLabels && (isFirst ? true : axis.used)) {
+ for (i = 0; i < axis.ticks.length; ++i) {
+ tick = axis.ticks[i];
+ if (!tick.label || !tick.label.length ||
+ ((isX ? offset.left : offset.top) + axis.d2p(tick.v) < 0) ||
+ ((isX ? offset.left : offset.top) + axis.d2p(tick.v) > (isX ? graph.canvasWidth : graph.canvasHeight))) {
+ continue;
+ }
+ top = offset.top +
+ (isX ?
+ ((isFirst ? 1 : -1 ) * (graph.plotHeight + options.grid.labelMargin)) :
+ axis.d2p(tick.v) - axis.maxLabel.height / 2);
+ left = isX ? (offset.left + axis.d2p(tick.v) - xBoxWidth / 2) : 0;
+
+ name = '';
+ if (i === 0) {
+ name = ' first';
+ } else if (i === axis.ticks.length - 1) {
+ name = ' last';
+ }
+ name += isX ? ' flotr-grid-label-x' : ' flotr-grid-label-y';
+
+ html += [
+ '' + tick.label + '
'
+ ].join(' ');
+
+ if (!isX && !isFirst) {
+ ctx.moveTo(offset.left + graph.plotWidth - 8, offset.top + axis.d2p(tick.v));
+ ctx.lineTo(offset.left + graph.plotWidth, offset.top + axis.d2p(tick.v));
+ }
+ }
+ }
+ }
+ }
+
+});
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/legend.js b/addons/web_graph/static/lib/flotr2/js/plugins/legend.js
new file mode 100644
index 00000000000..d130cb5e90b
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/legend.js
@@ -0,0 +1,179 @@
+(function () {
+
+var
+ D = Flotr.DOM,
+ _ = Flotr._;
+
+Flotr.addPlugin('legend', {
+ options: {
+ show: true, // => setting to true will show the legend, hide otherwise
+ noColumns: 1, // => number of colums in legend table // @todo: doesn't work for HtmlText = false
+ labelFormatter: function(v){return v;}, // => fn: string -> string
+ labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes
+ labelBoxWidth: 14,
+ labelBoxHeight: 10,
+ labelBoxMargin: 5,
+ labelBoxOpacity: 0.4,
+ container: null, // => container (as jQuery object) to put legend in, null means default on top of graph
+ position: 'nw', // => 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, set to 1 for a solid background
+ },
+ callbacks: {
+ 'flotr:afterinit': function() {
+ this.legend.insertLegend();
+ }
+ },
+ /**
+ * Adds a legend div to the canvas container or draws it on the canvas.
+ */
+ insertLegend: function(){
+
+ if(!this.options.legend.show)
+ return;
+
+ var series = this.series,
+ plotOffset = this.plotOffset,
+ options = this.options,
+ legend = options.legend,
+ fragments = [],
+ rowStarted = false,
+ ctx = this.ctx,
+ itemCount = _.filter(series, function(s) {return (s.label && !s.hide);}).length,
+ p = legend.position,
+ m = legend.margin,
+ i, label, color;
+
+ if (itemCount) {
+ if (!options.HtmlText && this.textEnabled && !legend.container) {
+ var style = {
+ size: options.fontSize*1.1,
+ color: options.grid.color
+ };
+
+ var lbw = legend.labelBoxWidth,
+ lbh = legend.labelBoxHeight,
+ lbm = legend.labelBoxMargin,
+ offsetX = plotOffset.left + m,
+ offsetY = plotOffset.top + m;
+
+ // We calculate the labels' max width
+ var labelMaxWidth = 0;
+ for(i = series.length - 1; i > -1; --i){
+ if(!series[i].label || series[i].hide) continue;
+ label = legend.labelFormatter(series[i].label);
+ labelMaxWidth = Math.max(labelMaxWidth, this._text.measureText(label, style).width);
+ }
+
+ var legendWidth = Math.round(lbw + lbm*3 + labelMaxWidth),
+ legendHeight = Math.round(itemCount*(lbm+lbh) + lbm);
+
+ if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight);
+ if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth);
+
+ // Legend box
+ color = this.processColor(legend.backgroundColor || 'rgb(240,240,240)', {opacity: legend.backgroundOpacity || 0.1});
+
+ ctx.fillStyle = color;
+ ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight);
+ ctx.strokeStyle = legend.labelBoxBorderColor;
+ ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight);
+
+ // Legend labels
+ var x = offsetX + lbm;
+ var y = offsetY + lbm;
+ for(i = 0; i < series.length; i++){
+ if(!series[i].label || series[i].hide) continue;
+ label = legend.labelFormatter(series[i].label);
+
+ ctx.fillStyle = series[i].color;
+ ctx.fillRect(x, y, lbw-1, lbh-1);
+
+ ctx.strokeStyle = legend.labelBoxBorderColor;
+ ctx.lineWidth = 1;
+ ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2);
+
+ // Legend text
+ Flotr.drawText(ctx, label, x + lbw + lbm, y + lbh, style);
+
+ y += lbh + lbm;
+ }
+ }
+ else {
+ for(i = 0; i < series.length; ++i){
+ if(!series[i].label || series[i].hide) continue;
+
+ if(i % legend.noColumns === 0){
+ fragments.push(rowStarted ? '' : ' ');
+ rowStarted = true;
+ }
+
+ // @TODO remove requirement on bars
+ var s = series[i],
+ boxWidth = legend.labelBoxWidth,
+ boxHeight = legend.labelBoxHeight,
+ opacityValue = (s.bars ? s.bars.fillOpacity : legend.labelBoxOpacity),
+ opacity = 'opacity:' + opacityValue + ';filter:alpha(opacity=' + opacityValue*100 + ');';
+
+ label = legend.labelFormatter(s.label);
+ color = 'background-color:' + ((s.bars && s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';';
+
+ fragments.push(
+ '',
+ '',
+ '
', // Border
+ '
', // Background
+ '
',
+ '
',
+ ' ',
+ '', label, ' '
+ );
+ }
+ if(rowStarted) fragments.push(' ');
+
+ if(fragments.length > 0){
+ var table = '' + fragments.join('') + '
';
+ if(legend.container){
+ D.insert(legend.container, table);
+ }
+ else {
+ var styles = {position: 'absolute', 'z-index': 2};
+
+ if(p.charAt(0) == 'n') { styles.top = (m + plotOffset.top) + 'px'; styles.bottom = 'auto'; }
+ else if(p.charAt(0) == 's') { styles.bottom = (m + plotOffset.bottom) + 'px'; styles.top = 'auto'; }
+ if(p.charAt(1) == 'e') { styles.right = (m + plotOffset.right) + 'px'; styles.left = 'auto'; }
+ else if(p.charAt(1) == 'w') { styles.left = (m + plotOffset.left) + 'px'; styles.right = 'auto'; }
+
+ var div = D.create('div'), size;
+ div.className = 'flotr-legend';
+ D.setStyles(div, styles);
+ D.insert(div, table);
+ D.insert(this.el, div);
+
+ if(!legend.backgroundOpacity)
+ return;
+
+ var c = legend.backgroundColor || options.grid.backgroundColor || '#ffffff';
+
+ _.extend(styles, D.size(div), {
+ 'backgroundColor': c,
+ 'z-index': 1
+ });
+ styles.width += 'px';
+ styles.height += 'px';
+
+ // Put in the transparent background separately to avoid blended labels and
+ div = D.create('div');
+ div.className = 'flotr-legend-bg';
+ D.setStyles(div, styles);
+ D.opacity(div, legend.backgroundOpacity);
+ D.insert(div, ' ');
+ D.insert(this.el, div);
+ }
+ }
+ }
+ }
+ }
+});
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/selection.js b/addons/web_graph/static/lib/flotr2/js/plugins/selection.js
new file mode 100644
index 00000000000..bb508840d6d
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/selection.js
@@ -0,0 +1,278 @@
+/**
+ * Selection Handles Plugin
+ *
+ *
+ * Options
+ * show - True enables the handles plugin.
+ * drag - Left and Right drag handles
+ * scroll - Scrolling handle
+ */
+(function () {
+
+function isLeftClick (e, type) {
+ return (e.which ? (e.which === 1) : (e.button === 0 || e.button === 1));
+}
+
+function boundX(x, graph) {
+ return Math.min(Math.max(0, x), graph.plotWidth - 1);
+}
+
+function boundY(y, graph) {
+ return Math.min(Math.max(0, y), graph.plotHeight);
+}
+
+var
+ D = Flotr.DOM,
+ E = Flotr.EventAdapter,
+ _ = Flotr._;
+
+
+Flotr.addPlugin('selection', {
+
+ options: {
+ pinchOnly: null, // Only select on pinch
+ mode: null, // => one of null, 'x', 'y' or 'xy'
+ color: '#B6D9FF', // => selection box color
+ fps: 20 // => frames-per-second
+ },
+
+ callbacks: {
+ 'flotr:mouseup' : function (event) {
+
+ var
+ options = this.options.selection,
+ selection = this.selection,
+ pointer = this.getEventPosition(event);
+
+ if (!options || !options.mode) return;
+ if (selection.interval) clearInterval(selection.interval);
+
+ if (this.multitouches) {
+ selection.updateSelection();
+ } else
+ if (!options.pinchOnly) {
+ selection.setSelectionPos(selection.selection.second, pointer);
+ }
+ selection.clearSelection();
+
+ if(selection.selecting && selection.selectionIsSane()){
+ selection.drawSelection();
+ selection.fireSelectEvent();
+ this.ignoreClick = true;
+ }
+ },
+ 'flotr:mousedown' : function (event) {
+
+ var
+ options = this.options.selection,
+ selection = this.selection,
+ pointer = this.getEventPosition(event);
+
+ if (!options || !options.mode) return;
+ if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return;
+ if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer);
+ if (selection.interval) clearInterval(selection.interval);
+
+ this.lastMousePos.pageX = null;
+ selection.selecting = false;
+ selection.interval = setInterval(
+ _.bind(selection.updateSelection, this),
+ 1000 / options.fps
+ );
+ },
+ 'flotr:destroy' : function (event) {
+ clearInterval(this.selection.interval);
+ }
+ },
+
+ // TODO This isn't used. Maybe it belongs in the draw area and fire select event methods?
+ getArea: function() {
+
+ var s = this.selection.selection,
+ first = s.first,
+ second = s.second;
+
+ return {
+ x1: Math.min(first.x, second.x),
+ x2: Math.max(first.x, second.x),
+ y1: Math.min(first.y, second.y),
+ y2: Math.max(first.y, second.y)
+ };
+ },
+
+ selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}},
+ prevSelection: null,
+ interval: null,
+
+ /**
+ * Fires the 'flotr:select' event when the user made a selection.
+ */
+ fireSelectEvent: function(name){
+ var a = this.axes,
+ s = this.selection.selection,
+ x1, x2, y1, y2;
+
+ name = name || 'select';
+
+ x1 = a.x.p2d(s.first.x);
+ x2 = a.x.p2d(s.second.x);
+ y1 = a.y.p2d(s.first.y);
+ y2 = a.y.p2d(s.second.y);
+
+ E.fire(this.el, 'flotr:'+name, [{
+ x1:Math.min(x1, x2),
+ y1:Math.min(y1, y2),
+ x2:Math.max(x1, x2),
+ y2:Math.max(y1, y2),
+ xfirst:x1, xsecond:x2, yfirst:y1, ysecond:y2
+ }, this]);
+ },
+
+ /**
+ * Allows the user the manually select an area.
+ * @param {Object} area - Object with coordinates to select.
+ */
+ setSelection: function(area, preventEvent){
+ var options = this.options,
+ xa = this.axes.x,
+ ya = this.axes.y,
+ vertScale = ya.scale,
+ hozScale = xa.scale,
+ selX = options.selection.mode.indexOf('x') != -1,
+ selY = options.selection.mode.indexOf('y') != -1,
+ s = this.selection.selection;
+
+ this.selection.clearSelection();
+
+ s.first.y = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this);
+ s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this);
+ s.first.x = boundX((selY && !selX) ? 0 : area.x1, this);
+ s.second.x = boundX((selY && !selX) ? this.plotWidth : area.x2, this);
+
+ this.selection.drawSelection();
+ if (!preventEvent)
+ this.selection.fireSelectEvent();
+ },
+
+ /**
+ * Calculates the position of the selection.
+ * @param {Object} pos - Position object.
+ * @param {Event} event - Event object.
+ */
+ setSelectionPos: function(pos, pointer) {
+ var mode = this.options.selection.mode,
+ selection = this.selection.selection;
+
+ if(mode.indexOf('x') == -1) {
+ pos.x = (pos == selection.first) ? 0 : this.plotWidth;
+ }else{
+ pos.x = boundX(pointer.relX, this);
+ }
+
+ if (mode.indexOf('y') == -1) {
+ pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1;
+ }else{
+ pos.y = boundY(pointer.relY, this);
+ }
+ },
+ /**
+ * Draws the selection box.
+ */
+ drawSelection: function() {
+
+ this.selection.fireSelectEvent('selecting');
+
+ var s = this.selection.selection,
+ octx = this.octx,
+ options = this.options,
+ plotOffset = this.plotOffset,
+ prevSelection = this.selection.prevSelection;
+
+ if (prevSelection &&
+ s.first.x == prevSelection.first.x &&
+ s.first.y == prevSelection.first.y &&
+ s.second.x == prevSelection.second.x &&
+ s.second.y == prevSelection.second.y) {
+ return;
+ }
+
+ octx.save();
+ octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8});
+ octx.lineWidth = 1;
+ octx.lineJoin = 'miter';
+ octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4});
+
+ this.selection.prevSelection = {
+ first: { x: s.first.x, y: s.first.y },
+ second: { x: s.second.x, y: s.second.y }
+ };
+
+ var x = Math.min(s.first.x, s.second.x),
+ y = Math.min(s.first.y, s.second.y),
+ w = Math.abs(s.second.x - s.first.x),
+ h = Math.abs(s.second.y - s.first.y);
+
+ octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
+ octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
+ octx.restore();
+ },
+
+ /**
+ * Updates (draws) the selection box.
+ */
+ updateSelection: function(){
+ if (!this.lastMousePos.pageX) return;
+
+ this.selection.selecting = true;
+
+ if (this.multitouches) {
+ this.selection.setSelectionPos(this.selection.selection.first, this.getEventPosition(this.multitouches[0]));
+ this.selection.setSelectionPos(this.selection.selection.second, this.getEventPosition(this.multitouches[1]));
+ } else
+ if (this.options.selection.pinchOnly) {
+ return;
+ } else {
+ this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos);
+ }
+
+ this.selection.clearSelection();
+
+ if(this.selection.selectionIsSane()) {
+ this.selection.drawSelection();
+ }
+ },
+
+ /**
+ * Removes the selection box from the overlay canvas.
+ */
+ clearSelection: function() {
+ if (!this.selection.prevSelection) return;
+
+ var prevSelection = this.selection.prevSelection,
+ lw = 1,
+ plotOffset = this.plotOffset,
+ x = Math.min(prevSelection.first.x, prevSelection.second.x),
+ y = Math.min(prevSelection.first.y, prevSelection.second.y),
+ w = Math.abs(prevSelection.second.x - prevSelection.first.x),
+ h = Math.abs(prevSelection.second.y - prevSelection.first.y);
+
+ this.octx.clearRect(x + plotOffset.left - lw + 0.5,
+ y + plotOffset.top - lw,
+ w + 2 * lw + 0.5,
+ h + 2 * lw + 0.5);
+
+ this.selection.prevSelection = null;
+ },
+ /**
+ * Determines whether or not the selection is sane and should be drawn.
+ * @return {Boolean} - True when sane, false otherwise.
+ */
+ selectionIsSane: function(){
+ var s = this.selection.selection;
+ return Math.abs(s.second.x - s.first.x) >= 5 ||
+ Math.abs(s.second.y - s.first.y) >= 5;
+ }
+
+});
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js b/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js
new file mode 100644
index 00000000000..d75cb1991b1
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js
@@ -0,0 +1,296 @@
+/** Spreadsheet **/
+(function() {
+
+function getRowLabel(value){
+ if (this.options.spreadsheet.tickFormatter){
+ //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out?
+ return this.options.spreadsheet.tickFormatter(value);
+ }
+ else {
+ var t = _.find(this.axes.x.ticks, function(t){return t.v == value;});
+ if (t) {
+ return t.label;
+ }
+ return value;
+ }
+}
+
+var
+ D = Flotr.DOM,
+ _ = Flotr._;
+
+Flotr.addPlugin('spreadsheet', {
+ options: {
+ show: false, // => show the data grid using two tabs
+ tabGraphLabel: 'Graph',
+ tabDataLabel: 'Data',
+ toolbarDownload: 'Download CSV', // @todo: add better language support
+ toolbarSelectAll: 'Select all',
+ csvFileSeparator: ',',
+ decimalSeparator: '.',
+ tickFormatter: null,
+ initialTab: 'graph'
+ },
+ /**
+ * Builds the tabs in the DOM
+ */
+ callbacks: {
+ 'flotr:afterconstruct': function(){
+ // @TODO necessary?
+ //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove');
+
+ if (!this.options.spreadsheet.show) return;
+
+ var ss = this.spreadsheet,
+ container = D.node('
'),
+ graph = D.node(''+this.options.spreadsheet.tabGraphLabel+'
'),
+ data = D.node(''+this.options.spreadsheet.tabDataLabel+'
'),
+ offset;
+
+ ss.tabsContainer = container;
+ ss.tabs = { graph : graph, data : data };
+
+ D.insert(container, graph);
+ D.insert(container, data);
+ D.insert(this.el, container);
+
+ offset = D.size(data).height + 2;
+ this.plotOffset.bottom += offset;
+
+ D.setStyles(container, {top: this.canvasHeight-offset+'px'});
+
+ this.
+ observe(graph, 'click', function(){ss.showTab('graph');}).
+ observe(data, 'click', function(){ss.showTab('data');});
+ if (this.options.spreadsheet.initialTab !== 'graph'){
+ ss.showTab(this.options.spreadsheet.initialTab);
+ }
+ }
+ },
+ /**
+ * Builds a matrix of the data to make the correspondance between the x values and the y values :
+ * X value => Y values from the axes
+ * @return {Array} The data grid
+ */
+ loadDataGrid: function(){
+ if (this.seriesData) return this.seriesData;
+
+ var s = this.series,
+ rows = {};
+
+ /* The data grid is a 2 dimensions array. There is a row for each X value.
+ * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)
+ **/
+ _.each(s, function(serie, i){
+ _.each(serie.data, function (v) {
+ var x = v[0],
+ y = v[1],
+ r = rows[x];
+ if (r) {
+ r[i+1] = y;
+ } else {
+ var newRow = [];
+ newRow[0] = x;
+ newRow[i+1] = y;
+ rows[x] = newRow;
+ }
+ });
+ });
+
+ // The data grid is sorted by x value
+ this.seriesData = _.sortBy(rows, function(row, x){
+ return parseInt(x, 10);
+ });
+ return this.seriesData;
+ },
+ /**
+ * Constructs the data table for the spreadsheet
+ * @todo make a spreadsheet manager (Flotr.Spreadsheet)
+ * @return {Element} The resulting table element
+ */
+ constructDataGrid: function(){
+ // If the data grid has already been built, nothing to do here
+ if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid;
+
+ var s = this.series,
+ datagrid = this.spreadsheet.loadDataGrid(),
+ colgroup = [' '],
+ buttonDownload, buttonSelect, t;
+
+ // First row : series' labels
+ var html = [''];
+ html.push(' ');
+ _.each(s, function(serie,i){
+ html.push(''+(serie.label || String.fromCharCode(65+i))+' ');
+ colgroup.push(' ');
+ });
+ html.push(' ');
+ // Data rows
+ _.each(datagrid, function(row){
+ html.push('');
+ _.times(s.length+1, function(i){
+ var tag = 'td',
+ value = row[i],
+ // TODO: do we really want to handle problems with floating point
+ // precision here?
+ content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : '');
+ if (i === 0) {
+ tag = 'th';
+ var label = getRowLabel.call(this, content);
+ if (label) content = label;
+ }
+
+ html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+''+tag+'>');
+ }, this);
+ html.push(' ');
+ }, this);
+ colgroup.push('');
+ t = D.node(html.join(''));
+
+ /**
+ * @TODO disabled this
+ if (!Flotr.isIE || Flotr.isIE == 9) {
+ function handleMouseout(){
+ t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover');
+ }
+ function handleMouseover(e){
+ var td = e.element(),
+ siblings = td.previousSiblings();
+ t.select('th[scope=col]')[siblings.length-1].addClassName('hover');
+ t.select('colgroup col')[siblings.length].addClassName('hover');
+ }
+ _.each(t.select('td'), function(td) {
+ Flotr.EventAdapter.
+ observe(td, 'mouseover', handleMouseover).
+ observe(td, 'mouseout', handleMouseout);
+ });
+ }
+ */
+
+ buttonDownload = D.node(
+ '' +
+ this.options.spreadsheet.toolbarDownload +
+ ' ');
+
+ buttonSelect = D.node(
+ '' +
+ this.options.spreadsheet.toolbarSelectAll+
+ ' ');
+
+ this.
+ observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)).
+ observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this));
+
+ var toolbar = D.node('
');
+ D.insert(toolbar, buttonDownload);
+ D.insert(toolbar, buttonSelect);
+
+ var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2,
+ container = D.node('
');
+
+ D.insert(container, toolbar);
+ D.insert(container, t);
+ D.insert(this.el, container);
+ this.spreadsheet.datagrid = t;
+ this.spreadsheet.container = container;
+
+ return t;
+ },
+ /**
+ * Shows the specified tab, by its name
+ * @todo make a tab manager (Flotr.Tabs)
+ * @param {String} tabName - The tab name
+ */
+ showTab: function(tabName){
+ if (this.spreadsheet.activeTab === tabName){
+ return;
+ }
+ switch(tabName) {
+ case 'graph':
+ D.hide(this.spreadsheet.container);
+ D.removeClass(this.spreadsheet.tabs.data, 'selected');
+ D.addClass(this.spreadsheet.tabs.graph, 'selected');
+ break;
+ case 'data':
+ if (!this.spreadsheet.datagrid)
+ this.spreadsheet.constructDataGrid();
+ D.show(this.spreadsheet.container);
+ D.addClass(this.spreadsheet.tabs.data, 'selected');
+ D.removeClass(this.spreadsheet.tabs.graph, 'selected');
+ break;
+ default:
+ throw 'Illegal tab name: ' + tabName;
+ }
+ this.spreadsheet.activeTab = tabName;
+ },
+ /**
+ * Selects the data table in the DOM for copy/paste
+ */
+ selectAllData: function(){
+ if (this.spreadsheet.tabs) {
+ var selection, range, doc, win, node = this.spreadsheet.constructDataGrid();
+
+ this.spreadsheet.showTab('data');
+
+ // deferred to be able to select the table
+ setTimeout(function () {
+ if ((doc = node.ownerDocument) && (win = doc.defaultView) &&
+ win.getSelection && doc.createRange &&
+ (selection = window.getSelection()) &&
+ selection.removeAllRanges) {
+ range = doc.createRange();
+ range.selectNode(node);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ else if (document.body && document.body.createTextRange &&
+ (range = document.body.createTextRange())) {
+ range.moveToElementText(node);
+ range.select();
+ }
+ }, 0);
+ return true;
+ }
+ else return false;
+ },
+ /**
+ * Converts the data into CSV in order to download a file
+ */
+ downloadCSV: function(){
+ var csv = '',
+ series = this.series,
+ options = this.options,
+ dg = this.spreadsheet.loadDataGrid(),
+ separator = encodeURIComponent(options.spreadsheet.csvFileSeparator);
+
+ if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) {
+ throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")";
+ }
+
+ // The first row
+ _.each(series, function(serie, i){
+ csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"';
+ });
+
+ csv += "%0D%0A"; // \r\n
+
+ // For each row
+ csv += _.reduce(dg, function(memo, row){
+ var rowLabel = getRowLabel.call(this, row[0]) || '';
+ rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"';
+ var numbers = row.slice(1).join(separator);
+ if (options.spreadsheet.decimalSeparator !== '.') {
+ numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator);
+ }
+ return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n
+ }, '', this);
+
+ if (Flotr.isIE && Flotr.isIE < 9) {
+ csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r');
+ window.open().document.write(csv);
+ }
+ else window.open('data:text/csv,'+csv);
+ }
+});
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/titles.js b/addons/web_graph/static/lib/flotr2/js/plugins/titles.js
new file mode 100644
index 00000000000..cf95d9e7c80
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/plugins/titles.js
@@ -0,0 +1,177 @@
+(function () {
+
+var D = Flotr.DOM;
+
+Flotr.addPlugin('titles', {
+ callbacks: {
+ 'flotr:afterdraw': function() {
+ this.titles.drawTitles();
+ }
+ },
+ /**
+ * Draws the title and the subtitle
+ */
+ drawTitles : function () {
+ var html,
+ options = this.options,
+ margin = options.grid.labelMargin,
+ ctx = this.ctx,
+ a = this.axes;
+
+ if (!options.HtmlText && this.textEnabled) {
+ var style = {
+ size: options.fontSize,
+ color: options.grid.color,
+ textAlign: 'center'
+ };
+
+ // Add subtitle
+ if (options.subtitle){
+ Flotr.drawText(
+ ctx, options.subtitle,
+ this.plotOffset.left + this.plotWidth/2,
+ this.titleHeight + this.subtitleHeight - 2,
+ style
+ );
+ }
+
+ style.weight = 1.5;
+ style.size *= 1.5;
+
+ // Add title
+ if (options.title){
+ Flotr.drawText(
+ ctx, options.title,
+ this.plotOffset.left + this.plotWidth/2,
+ this.titleHeight - 2,
+ style
+ );
+ }
+
+ style.weight = 1.8;
+ style.size *= 0.8;
+
+ // Add x axis title
+ if (a.x.options.title && a.x.used){
+ style.textAlign = a.x.options.titleAlign || 'center';
+ style.textBaseline = 'top';
+ style.angle = Flotr.toRad(a.x.options.titleAngle);
+ style = Flotr.getBestTextAlign(style.angle, style);
+ Flotr.drawText(
+ ctx, a.x.options.title,
+ this.plotOffset.left + this.plotWidth/2,
+ this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin,
+ style
+ );
+ }
+
+ // Add x2 axis title
+ if (a.x2.options.title && a.x2.used){
+ style.textAlign = a.x2.options.titleAlign || 'center';
+ style.textBaseline = 'bottom';
+ style.angle = Flotr.toRad(a.x2.options.titleAngle);
+ style = Flotr.getBestTextAlign(style.angle, style);
+ Flotr.drawText(
+ ctx, a.x2.options.title,
+ this.plotOffset.left + this.plotWidth/2,
+ this.plotOffset.top - a.x2.maxLabel.height - 2 * margin,
+ style
+ );
+ }
+
+ // Add y axis title
+ if (a.y.options.title && a.y.used){
+ style.textAlign = a.y.options.titleAlign || 'right';
+ style.textBaseline = 'middle';
+ style.angle = Flotr.toRad(a.y.options.titleAngle);
+ style = Flotr.getBestTextAlign(style.angle, style);
+ Flotr.drawText(
+ ctx, a.y.options.title,
+ this.plotOffset.left - a.y.maxLabel.width - 2 * margin,
+ this.plotOffset.top + this.plotHeight / 2,
+ style
+ );
+ }
+
+ // Add y2 axis title
+ if (a.y2.options.title && a.y2.used){
+ style.textAlign = a.y2.options.titleAlign || 'left';
+ style.textBaseline = 'middle';
+ style.angle = Flotr.toRad(a.y2.options.titleAngle);
+ style = Flotr.getBestTextAlign(style.angle, style);
+ Flotr.drawText(
+ ctx, a.y2.options.title,
+ this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin,
+ this.plotOffset.top + this.plotHeight / 2,
+ style
+ );
+ }
+ }
+ else {
+ html = [];
+
+ // Add title
+ if (options.title)
+ html.push(
+ '', options.title, '
'
+ );
+
+ // Add subtitle
+ if (options.subtitle)
+ html.push(
+ '', options.subtitle, '
'
+ );
+
+ html.push('');
+
+ html.push('');
+
+ // Add x axis title
+ if (a.x.options.title && a.x.used)
+ html.push(
+ '
', a.x.options.title, '
'
+ );
+
+ // Add x2 axis title
+ if (a.x2.options.title && a.x2.used)
+ html.push(
+ '
', a.x2.options.title, '
'
+ );
+
+ // Add y axis title
+ if (a.y.options.title && a.y.used)
+ html.push(
+ '
', a.y.options.title, '
'
+ );
+
+ // Add y2 axis title
+ if (a.y2.options.title && a.y2.used)
+ html.push(
+ '
', a.y2.options.title, '
'
+ );
+
+ html = html.join('');
+
+ var div = D.create('div');
+ D.setStyles({
+ color: options.grid.color
+ });
+ div.className = 'flotr-titles';
+ D.insert(this.el, div);
+ D.insert(div, html);
+ }
+ }
+});
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/types/bars.js b/addons/web_graph/static/lib/flotr2/js/types/bars.js
new file mode 100644
index 00000000000..03e7988b785
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/bars.js
@@ -0,0 +1,274 @@
+/** Bars **/
+Flotr.addType('bars', {
+
+ options: {
+ show: false, // => setting to true will show bars, false will hide
+ lineWidth: 2, // => in pixels
+ barWidth: 1, // => in units of the x axis
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillColor: null, // => fill color
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ horizontal: false, // => horizontal bars (x and y inverted)
+ stacked: false, // => stacked bar charts
+ centered: true, // => center the bars to their x axis value
+ topPadding: 0.1 // => top padding in percent
+ },
+
+ stack : {
+ positive : [],
+ negative : [],
+ _positive : [], // Shadow
+ _negative : [] // Shadow
+ },
+
+ draw : function (options) {
+ var
+ context = options.context;
+
+ context.save();
+ context.lineJoin = 'miter';
+ // @TODO linewidth not interpreted the right way.
+ context.lineWidth = options.lineWidth;
+ context.strokeStyle = options.color;
+ if (options.fill) context.fillStyle = options.fillStyle;
+
+ this.plot(options);
+
+ context.restore();
+ },
+
+ plot : function (options) {
+
+ var
+ data = options.data,
+ context = options.context,
+ shadowSize = options.shadowSize,
+ i, geometry, left, top, width, height;
+
+ if (data.length < 1) return;
+
+ this.translate(context, options.horizontal);
+
+ for (i = 0; i < data.length; i++) {
+
+ geometry = this.getBarGeometry(data[i][0], data[i][1], options);
+ if (geometry === null) continue;
+
+ left = geometry.left;
+ top = geometry.top;
+ width = geometry.width;
+ height = geometry.height;
+
+ if (options.fill) context.fillRect(left, top, width, height);
+ if (shadowSize) {
+ context.save();
+ context.fillStyle = 'rgba(0,0,0,0.05)';
+ context.fillRect(left + shadowSize, top + shadowSize, width, height);
+ context.restore();
+ }
+ if (options.lineWidth) {
+ context.strokeRect(left, top, width, height);
+ }
+ }
+ },
+
+ translate : function (context, horizontal) {
+ if (horizontal) {
+ context.rotate(-Math.PI / 2);
+ context.scale(-1, 1);
+ }
+ },
+
+ getBarGeometry : function (x, y, options) {
+
+ var
+ horizontal = options.horizontal,
+ barWidth = options.barWidth,
+ centered = options.centered,
+ stack = options.stacked ? this.stack : false,
+ lineWidth = options.lineWidth,
+ bisection = centered ? barWidth / 2 : 0,
+ xScale = horizontal ? options.yScale : options.xScale,
+ yScale = horizontal ? options.xScale : options.yScale,
+ xValue = horizontal ? y : x,
+ yValue = horizontal ? x : y,
+ stackOffset = 0,
+ stackValue, left, right, top, bottom;
+
+ // Stacked bars
+ if (stack) {
+ stackValue = yValue > 0 ? stack.positive : stack.negative;
+ stackOffset = stackValue[xValue] || stackOffset;
+ stackValue[xValue] = stackOffset + yValue;
+ }
+
+ left = xScale(xValue - bisection);
+ right = xScale(xValue + barWidth - bisection);
+ top = yScale(yValue + stackOffset);
+ bottom = yScale(stackOffset);
+
+ // TODO for test passing... probably looks better without this
+ if (bottom < 0) bottom = 0;
+
+ // TODO Skipping...
+ // if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue;
+
+ return (x === null || y === null) ? null : {
+ x : xValue,
+ y : yValue,
+ xScale : xScale,
+ yScale : yScale,
+ top : top,
+ left : Math.min(left, right) - lineWidth / 2,
+ width : Math.abs(right - left) - lineWidth,
+ height : bottom - top
+ };
+ },
+
+ hit : function (options) {
+ var
+ data = options.data,
+ args = options.args,
+ mouse = args[0],
+ n = args[1],
+ x = mouse.x,
+ y = mouse.y,
+ hitGeometry = this.getBarGeometry(x, y, options),
+ width = hitGeometry.width / 2,
+ left = hitGeometry.left,
+ geometry, i;
+
+ for (i = data.length; i--;) {
+ geometry = this.getBarGeometry(data[i][0], data[i][1], options);
+ if (geometry.y > hitGeometry.y && Math.abs(left - geometry.left) < width) {
+ n.x = data[i][0];
+ n.y = data[i][1];
+ n.index = i;
+ n.seriesIndex = options.index;
+ }
+ }
+ },
+
+ drawHit : function (options) {
+ // TODO hits for stacked bars; implement using calculateStack option?
+ var
+ context = options.context,
+ args = options.args,
+ geometry = this.getBarGeometry(args.x, args.y, options),
+ left = geometry.left,
+ top = geometry.top,
+ width = geometry.width,
+ height = geometry.height;
+
+ context.save();
+ context.strokeStyle = options.color;
+ context.lineWidth = options.lineWidth;
+ this.translate(context, options.horizontal);
+
+ // Draw highlight
+ context.beginPath();
+ context.moveTo(left, top + height);
+ context.lineTo(left, top);
+ context.lineTo(left + width, top);
+ context.lineTo(left + width, top + height);
+ if (options.fill) {
+ context.fillStyle = options.fillStyle;
+ context.fill();
+ }
+ context.stroke();
+ context.closePath();
+
+ context.restore();
+ },
+
+ clearHit: function (options) {
+ var
+ context = options.context,
+ args = options.args,
+ geometry = this.getBarGeometry(args.x, args.y, options),
+ left = geometry.left,
+ width = geometry.width,
+ top = geometry.top,
+ height = geometry.height,
+ lineWidth = 2 * options.lineWidth;
+
+ context.save();
+ this.translate(context, options.horizontal);
+ context.clearRect(
+ left - lineWidth,
+ Math.min(top, top + height) - lineWidth,
+ width + 2 * lineWidth,
+ Math.abs(height) + 2 * lineWidth
+ );
+ context.restore();
+ },
+
+ extendXRange : function (axis, data, options, bars) {
+ this._extendRange(axis, data, options, bars);
+ },
+
+ extendYRange : function (axis, data, options, bars) {
+ this._extendRange(axis, data, options, bars);
+ },
+ _extendRange: function (axis, data, options, bars) {
+
+ var
+ max = axis.options.max;
+
+ if (_.isNumber(max) || _.isString(max)) return;
+
+ var
+ newmin = axis.min,
+ newmax = axis.max,
+ horizontal = options.horizontal,
+ orientation = axis.orientation,
+ positiveSums = this.positiveSums || {},
+ negativeSums = this.negativeSums || {},
+ value, datum, index, j;
+
+ // Sides of bars
+ if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) {
+ if (options.centered) {
+ newmax = Math.max(axis.datamax + options.barWidth, newmax);
+ newmin = Math.min(axis.datamin - options.barWidth, newmin);
+ }
+ }
+
+ if (options.stacked &&
+ ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){
+
+ for (j = data.length; j--;) {
+ value = data[j][(orientation == 1 ? 1 : 0)]+'';
+ datum = data[j][(orientation == 1 ? 0 : 1)];
+
+ // Positive
+ if (datum > 0) {
+ positiveSums[value] = (positiveSums[value] || 0) + datum;
+ newmax = Math.max(newmax, positiveSums[value]);
+ }
+
+ // Negative
+ else {
+ negativeSums[value] = (negativeSums[value] || 0) + datum;
+ newmin = Math.min(newmin, negativeSums[value]);
+ }
+ }
+ }
+
+ // End of bars
+ if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) {
+ if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) {
+ newmax += options.topPadding * (newmax - newmin);
+ }
+ }
+
+ this.stackMin = newmin;
+ this.stackMax = newmax;
+ this.negativeSums = negativeSums;
+ this.positiveSums = positiveSums;
+
+ axis.max = newmax;
+ axis.min = newmin;
+ }
+
+});
diff --git a/addons/web_graph/static/lib/flotr2/js/types/bubbles.js b/addons/web_graph/static/lib/flotr2/js/types/bubbles.js
new file mode 100644
index 00000000000..a97c5a28a93
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/bubbles.js
@@ -0,0 +1,119 @@
+/** Bubbles **/
+Flotr.addType('bubbles', {
+ options: {
+ show: false, // => setting to true will show radar chart, false will hide
+ lineWidth: 2, // => line width in pixels
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ baseRadius: 2 // => ratio of the radar, against the plot size
+ },
+ draw : function (options) {
+ var
+ context = options.context,
+ shadowSize = options.shadowSize;
+
+ context.save();
+ context.lineWidth = options.lineWidth;
+
+ // Shadows
+ context.fillStyle = 'rgba(0,0,0,0.05)';
+ context.strokeStyle = 'rgba(0,0,0,0.05)';
+ this.plot(options, shadowSize / 2);
+ context.strokeStyle = 'rgba(0,0,0,0.1)';
+ this.plot(options, shadowSize / 4);
+
+ // Chart
+ context.strokeStyle = options.color;
+ context.fillStyle = options.fillStyle;
+ this.plot(options);
+
+ context.restore();
+ },
+ plot : function (options, offset) {
+
+ var
+ data = options.data,
+ context = options.context,
+ geometry,
+ i, x, y, z;
+
+ offset = offset || 0;
+
+ for (i = 0; i < data.length; ++i){
+
+ geometry = this.getGeometry(data[i], options);
+
+ context.beginPath();
+ context.arc(geometry.x + offset, geometry.y + offset, geometry.z, 0, 2 * Math.PI, true);
+ context.stroke();
+ if (options.fill) context.fill();
+ context.closePath();
+ }
+ },
+ getGeometry : function (point, options) {
+ return {
+ x : options.xScale(point[0]),
+ y : options.yScale(point[1]),
+ z : point[2] * options.baseRadius
+ };
+ },
+ hit : function (options) {
+ var
+ data = options.data,
+ args = options.args,
+ mouse = args[0],
+ n = args[1],
+ x = mouse.x,
+ y = mouse.y,
+ geometry,
+ dx, dy;
+
+ for (i = data.length; i--;) {
+ geometry = this.getGeometry(data[i], options);
+
+ dx = geometry.x - options.xScale(x);
+ dy = geometry.y - options.yScale(y);
+
+ if (Math.sqrt(dx * dx + dy * dy) < geometry.z) {
+ n.x = data[i][0];
+ n.y = data[i][1];
+ n.index = i;
+ n.seriesIndex = options.index;
+ }
+ }
+ },
+ drawHit : function (options) {
+
+ var
+ context = options.context,
+ geometry = this.getGeometry(options.data[options.args.index], options);
+
+ context.save();
+ context.lineWidth = options.lineWidth;
+ context.fillStyle = options.fillStyle;
+ context.strokeStyle = options.color;
+ context.beginPath();
+ context.arc(geometry.x, geometry.y, geometry.z, 0, 2 * Math.PI, true);
+ context.fill();
+ context.stroke();
+ context.closePath();
+ context.restore();
+ },
+ clearHit : function (options) {
+
+ var
+ context = options.context,
+ geometry = this.getGeometry(options.data[options.args.index], options),
+ offset = geometry.z + options.lineWidth;
+
+ context.save();
+ context.clearRect(
+ geometry.x - offset,
+ geometry.y - offset,
+ 2 * offset,
+ 2 * offset
+ );
+ context.restore();
+ }
+ // TODO Add a hit calculation method (like pie)
+});
diff --git a/addons/web_graph/static/lib/flotr2/js/types/candles.js b/addons/web_graph/static/lib/flotr2/js/types/candles.js
new file mode 100644
index 00000000000..0e484b366cc
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/candles.js
@@ -0,0 +1,127 @@
+/** Candles **/
+Flotr.addType('candles', {
+ options: {
+ show: false, // => setting to true will show candle sticks, false will hide
+ lineWidth: 1, // => in pixels
+ wickLineWidth: 1, // => in pixels
+ candleWidth: 0.6, // => in units of the x axis
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ upFillColor: '#00A8F0',// => up sticks fill color
+ downFillColor: '#CB4B4B',// => down sticks fill color
+ fillOpacity: 0.5, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ // TODO Test this barcharts option.
+ barcharts: false // => draw as barcharts (not standard bars but financial barcharts)
+ },
+
+ draw : function (options) {
+
+ var
+ context = options.context;
+
+ context.save();
+ context.lineJoin = 'miter';
+ context.lineCap = 'butt';
+ // @TODO linewidth not interpreted the right way.
+ context.lineWidth = options.wickLineWidth || options.lineWidth;
+
+ this.plot(options);
+
+ context.restore();
+ },
+
+ plot : function (options) {
+
+ var
+ data = options.data,
+ context = options.context,
+ xScale = options.xScale,
+ yScale = options.yScale,
+ width = options.candleWidth / 2,
+ shadowSize = options.shadowSize,
+ lineWidth = options.lineWidth,
+ wickLineWidth = options.wickLineWidth,
+ pixelOffset = (wickLineWidth % 2) / 2,
+ color,
+ datum, x, y,
+ open, high, low, close,
+ left, right, bottom, top, bottom2, top2,
+ i;
+
+ if (data.length < 1) return;
+
+ for (i = 0; i < data.length; i++) {
+ datum = data[i];
+ x = datum[0];
+ open = datum[1];
+ high = datum[2];
+ low = datum[3];
+ close = datum[4];
+ left = xScale(x - width);
+ right = xScale(x + width);
+ bottom = yScale(low);
+ top = yScale(high);
+ bottom2 = yScale(Math.min(open, close));
+ top2 = yScale(Math.max(open, close));
+
+ /*
+ // TODO skipping
+ if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
+ continue;
+ */
+
+ color = options[open > close ? 'downFillColor' : 'upFillColor'];
+
+ // Fill the candle.
+ // TODO Test the barcharts option
+ if (options.fill && !options.barcharts) {
+ context.fillStyle = 'rgba(0,0,0,0.05)';
+ context.fillRect(left + shadowSize, top2 + shadowSize, right - left, bottom2 - top2);
+ context.save();
+ context.globalAlpha = options.fillOpacity;
+ context.fillStyle = color;
+ context.fillRect(left, top2 + lineWidth, right - left, bottom2 - top2);
+ context.restore();
+ }
+
+ // Draw candle outline/border, high, low.
+ if (lineWidth || wickLineWidth) {
+
+ x = Math.floor((left + right) / 2) + pixelOffset;
+
+ context.strokeStyle = color;
+ context.beginPath();
+
+ // TODO Again with the bartcharts
+ if (options.barcharts) {
+
+ context.moveTo(x, Math.floor(top + width));
+ context.lineTo(x, Math.floor(bottom + width));
+
+ y = Math.floor(open + width) + 0.5;
+ context.moveTo(Math.floor(left) + pixelOffset, y);
+ context.lineTo(x, y);
+
+ y = Math.floor(close + width) + 0.5;
+ context.moveTo(Math.floor(right) + pixelOffset, y);
+ context.lineTo(x, y);
+ } else {
+ context.strokeRect(left, top2 + lineWidth, right - left, bottom2 - top2);
+
+ context.moveTo(x, Math.floor(top2 + lineWidth));
+ context.lineTo(x, Math.floor(top + lineWidth));
+ context.moveTo(x, Math.floor(bottom2 + lineWidth));
+ context.lineTo(x, Math.floor(bottom + lineWidth));
+ }
+
+ context.closePath();
+ context.stroke();
+ }
+ }
+ },
+ extendXRange: function (axis, data, options) {
+ if (axis.options.max === null) {
+ axis.max = Math.max(axis.datamax + 0.5, axis.max);
+ axis.min = Math.min(axis.datamin - 0.5, axis.min);
+ }
+ }
+});
diff --git a/addons/web_graph/static/lib/flotr2/js/types/gantt.js b/addons/web_graph/static/lib/flotr2/js/types/gantt.js
new file mode 100644
index 00000000000..1498bcb51cf
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/gantt.js
@@ -0,0 +1,229 @@
+/** Gantt
+ * Base on data in form [s,y,d] where:
+ * y - executor or simply y value
+ * s - task start value
+ * d - task duration
+ * **/
+Flotr.addType('gantt', {
+ options: {
+ show: false, // => setting to true will show gantt, false will hide
+ lineWidth: 2, // => in pixels
+ barWidth: 1, // => in units of the x axis
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillColor: null, // => fill color
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ centered: true // => center the bars to their x axis value
+ },
+ /**
+ * Draws gantt series in the canvas element.
+ * @param {Object} series - Series with options.gantt.show = true.
+ */
+ draw: function(series) {
+ var ctx = this.ctx,
+ bw = series.gantt.barWidth,
+ lw = Math.min(series.gantt.lineWidth, bw);
+
+ ctx.save();
+ ctx.translate(this.plotOffset.left, this.plotOffset.top);
+ ctx.lineJoin = 'miter';
+
+ /**
+ * @todo linewidth not interpreted the right way.
+ */
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+
+ ctx.save();
+ this.gantt.plotShadows(series, bw, 0, series.gantt.fill);
+ ctx.restore();
+
+ if(series.gantt.fill){
+ var color = series.gantt.fillColor || series.color;
+ ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity});
+ }
+
+ this.gantt.plot(series, bw, 0, series.gantt.fill);
+ ctx.restore();
+ },
+ plot: function(series, barWidth, offset, fill){
+ var data = series.data;
+ if(data.length < 1) return;
+
+ var xa = series.xaxis,
+ ya = series.yaxis,
+ ctx = this.ctx, i;
+
+ for(i = 0; i < data.length; i++){
+ var y = data[i][0],
+ s = data[i][1],
+ d = data[i][2],
+ drawLeft = true, drawTop = true, drawRight = true;
+
+ if (s === null || d === null) continue;
+
+ var left = s,
+ right = s + d,
+ bottom = y - (series.gantt.centered ? barWidth/2 : 0),
+ top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0);
+
+ if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
+ continue;
+
+ if(left < xa.min){
+ left = xa.min;
+ drawLeft = false;
+ }
+
+ if(right > xa.max){
+ right = xa.max;
+ if (xa.lastSerie != series)
+ drawTop = false;
+ }
+
+ if(bottom < ya.min)
+ bottom = ya.min;
+
+ if(top > ya.max){
+ top = ya.max;
+ if (ya.lastSerie != series)
+ drawTop = false;
+ }
+
+ /**
+ * Fill the bar.
+ */
+ if(fill){
+ ctx.beginPath();
+ ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset);
+ ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset);
+ ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset);
+ ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset);
+ ctx.fill();
+ ctx.closePath();
+ }
+
+ /**
+ * Draw bar outline/border.
+ */
+ if(series.gantt.lineWidth && (drawLeft || drawRight || drawTop)){
+ ctx.beginPath();
+ ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset);
+
+ ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset);
+ ctx[drawTop ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset);
+ ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset);
+
+ ctx.stroke();
+ ctx.closePath();
+ }
+ }
+ },
+ plotShadows: function(series, barWidth, offset){
+ var data = series.data;
+ if(data.length < 1) return;
+
+ var i, y, s, d,
+ xa = series.xaxis,
+ ya = series.yaxis,
+ ctx = this.ctx,
+ sw = this.options.shadowSize;
+
+ for(i = 0; i < data.length; i++){
+ y = data[i][0];
+ s = data[i][1];
+ d = data[i][2];
+
+ if (s === null || d === null) continue;
+
+ var left = s,
+ right = s + d,
+ bottom = y - (series.gantt.centered ? barWidth/2 : 0),
+ top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0);
+
+ if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max)
+ continue;
+
+ if(left < xa.min) left = xa.min;
+ if(right > xa.max) right = xa.max;
+ if(bottom < ya.min) bottom = ya.min;
+ if(top > ya.max) top = ya.max;
+
+ var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw);
+ var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw );
+
+ ctx.fillStyle = 'rgba(0,0,0,0.05)';
+ ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height);
+ }
+ },
+ extendXRange: function(axis) {
+ if(axis.options.max === null){
+ var newmin = axis.min,
+ newmax = axis.max,
+ i, j, x, s, g,
+ stackedSumsPos = {},
+ stackedSumsNeg = {},
+ lastSerie = null;
+
+ for(i = 0; i < this.series.length; ++i){
+ s = this.series[i];
+ g = s.gantt;
+
+ if(g.show && s.xaxis == axis) {
+ for (j = 0; j < s.data.length; j++) {
+ if (g.show) {
+ y = s.data[j][0]+'';
+ stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]);
+ lastSerie = s;
+ }
+ }
+ for (j in stackedSumsPos) {
+ newmax = Math.max(stackedSumsPos[j], newmax);
+ }
+ }
+ }
+ axis.lastSerie = lastSerie;
+ axis.max = newmax;
+ axis.min = newmin;
+ }
+ },
+ extendYRange: function(axis){
+ if(axis.options.max === null){
+ var newmax = Number.MIN_VALUE,
+ newmin = Number.MAX_VALUE,
+ i, j, s, g,
+ stackedSumsPos = {},
+ stackedSumsNeg = {},
+ lastSerie = null;
+
+ for(i = 0; i < this.series.length; ++i){
+ s = this.series[i];
+ g = s.gantt;
+
+ if (g.show && !s.hide && s.yaxis == axis) {
+ var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE;
+ for(j=0; j < s.data.length; j++){
+ datamax = Math.max(datamax,s.data[j][0]);
+ datamin = Math.min(datamin,s.data[j][0]);
+ }
+
+ if (g.centered) {
+ newmax = Math.max(datamax + 0.5, newmax);
+ newmin = Math.min(datamin - 0.5, newmin);
+ }
+ else {
+ newmax = Math.max(datamax + 1, newmax);
+ newmin = Math.min(datamin, newmin);
+ }
+ // For normal horizontal bars
+ if (g.barWidth + datamax > newmax){
+ newmax = axis.max + g.barWidth;
+ }
+ }
+ }
+ axis.lastSerie = lastSerie;
+ axis.max = newmax;
+ axis.min = newmin;
+ axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals);
+ }
+ }
+});
diff --git a/addons/web_graph/static/lib/flotr2/js/types/lines.js b/addons/web_graph/static/lib/flotr2/js/types/lines.js
new file mode 100644
index 00000000000..d65971b2dfe
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/lines.js
@@ -0,0 +1,275 @@
+/** Lines **/
+Flotr.addType('lines', {
+ options: {
+ show: false, // => setting to true will show lines, false will hide
+ lineWidth: 2, // => line width in pixels
+ fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillBorder: false, // => draw a border around the fill
+ fillColor: null, // => fill color
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ steps: false, // => draw steps
+ stacked: false // => setting to true will show stacked lines, false will show normal lines
+ },
+
+ stack : {
+ values : []
+ },
+
+ /**
+ * Draws lines series in the canvas element.
+ * @param {Object} options
+ */
+ draw : function (options) {
+
+ var
+ context = options.context,
+ lineWidth = options.lineWidth,
+ shadowSize = options.shadowSize,
+ offset;
+
+ context.save();
+ context.lineJoin = 'round';
+
+ if (shadowSize) {
+
+ context.lineWidth = shadowSize / 2;
+ offset = lineWidth / 2 + context.lineWidth / 2;
+
+ // @TODO do this instead with a linear gradient
+ context.strokeStyle = "rgba(0,0,0,0.1)";
+ this.plot(options, offset + shadowSize / 2, false);
+
+ context.strokeStyle = "rgba(0,0,0,0.2)";
+ this.plot(options, offset, false);
+ }
+
+ context.lineWidth = lineWidth;
+ context.strokeStyle = options.color;
+
+ this.plot(options, 0, true);
+
+ context.restore();
+ },
+
+ plot : function (options, shadowOffset, incStack) {
+
+ var
+ context = options.context,
+ width = options.width,
+ height = options.height,
+ xScale = options.xScale,
+ yScale = options.yScale,
+ data = options.data,
+ stack = options.stacked ? this.stack : false,
+ length = data.length - 1,
+ prevx = null,
+ prevy = null,
+ zero = yScale(0),
+ x1, x2, y1, y2, stack1, stack2, i;
+
+ if (length < 1) return;
+
+ context.beginPath();
+
+ for (i = 0; i < length; ++i) {
+
+ // To allow empty values
+ if (data[i][1] === null || data[i+1][1] === null) continue;
+
+ // Zero is infinity for log scales
+ // TODO handle zero for logarithmic
+ // if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue;
+ // if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue;
+
+ x1 = xScale(data[i][0]);
+ x2 = xScale(data[i+1][0]);
+
+ if (stack) {
+
+ stack1 = stack.values[data[i][0]] || 0;
+ stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0;
+
+ y1 = yScale(data[i][1] + stack1);
+ y2 = yScale(data[i+1][1] + stack2);
+
+ if(incStack){
+ stack.values[data[i][0]] = data[i][1]+stack1;
+
+ if(i == length-1)
+ stack.values[data[i+1][0]] = data[i+1][1]+stack2;
+ }
+ }
+ else{
+ y1 = yScale(data[i][1]);
+ y2 = yScale(data[i+1][1]);
+ }
+
+ if (
+ (y1 > height && y2 > height) ||
+ (y1 < 0 && y2 < 0) ||
+ (x1 < 0 && x2 < 0) ||
+ (x1 > width && x2 > width)
+ ) continue;
+
+ if((prevx != x1) || (prevy != y1 + shadowOffset))
+ context.moveTo(x1, y1 + shadowOffset);
+
+ prevx = x2;
+ prevy = y2 + shadowOffset;
+ if (options.steps) {
+ context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset);
+ context.lineTo(prevx + shadowOffset / 2, prevy);
+ } else {
+ context.lineTo(prevx, prevy);
+ }
+ }
+
+ if (!options.fill || options.fill && !options.fillBorder) context.stroke();
+
+ // TODO stacked lines
+ if(!shadowOffset && options.fill){
+ x1 = xScale(data[0][0]);
+ context.fillStyle = options.fillStyle;
+ context.lineTo(x2, zero);
+ context.lineTo(x1, zero);
+ context.lineTo(x1, yScale(data[0][1]));
+ context.fill();
+ if (options.fillBorder) {
+ context.stroke();
+ }
+ }
+
+ context.closePath();
+ },
+
+ // Perform any pre-render precalculations (this should be run on data first)
+ // - Pie chart total for calculating measures
+ // - Stacks for lines and bars
+ // precalculate : function () {
+ // }
+ //
+ //
+ // Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max)
+ // getBounds : function () {
+ // }
+ // getMin : function () {
+ // }
+ // getMax : function () {
+ // }
+ //
+ //
+ // Padding around rendered elements
+ // getPadding : function () {
+ // }
+
+ extendYRange : function (axis, data, options, lines) {
+
+ var o = axis.options;
+
+ // If stacked and auto-min
+ if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) {
+
+ var
+ newmax = axis.max,
+ newmin = axis.min,
+ positiveSums = lines.positiveSums || {},
+ negativeSums = lines.negativeSums || {},
+ x, j;
+
+ for (j = 0; j < data.length; j++) {
+
+ x = data[j][0] + '';
+
+ // Positive
+ if (data[j][1] > 0) {
+ positiveSums[x] = (positiveSums[x] || 0) + data[j][1];
+ newmax = Math.max(newmax, positiveSums[x]);
+ }
+
+ // Negative
+ else {
+ negativeSums[x] = (negativeSums[x] || 0) + data[j][1];
+ newmin = Math.min(newmin, negativeSums[x]);
+ }
+ }
+
+ lines.negativeSums = negativeSums;
+ lines.positiveSums = positiveSums;
+
+ axis.max = newmax;
+ axis.min = newmin;
+ }
+
+ if (options.steps) {
+
+ this.hit = function (options) {
+ var
+ data = options.data,
+ args = options.args,
+ yScale = options.yScale,
+ mouse = args[0],
+ length = data.length,
+ n = args[1],
+ x = mouse.x,
+ relY = mouse.relY,
+ i;
+
+ for (i = 0; i < length - 1; i++) {
+ if (x >= data[i][0] && x <= data[i+1][0]) {
+ if (Math.abs(yScale(data[i][1]) - relY) < 8) {
+ n.x = data[i][0];
+ n.y = data[i][1];
+ n.index = i;
+ n.seriesIndex = options.index;
+ }
+ break;
+ }
+ }
+ };
+
+ this.drawHit = function (options) {
+ var
+ context = options.context,
+ args = options.args,
+ data = options.data,
+ xScale = options.xScale,
+ index = args.index,
+ x = xScale(args.x),
+ y = options.yScale(args.y),
+ x2;
+
+ if (data.length - 1 > index) {
+ x2 = options.xScale(data[index + 1][0]);
+ context.save();
+ context.strokeStyle = options.color;
+ context.lineWidth = options.lineWidth;
+ context.beginPath();
+ context.moveTo(x, y);
+ context.lineTo(x2, y);
+ context.stroke();
+ context.closePath();
+ context.restore();
+ }
+ };
+
+ this.clearHit = function (options) {
+ var
+ context = options.context,
+ args = options.args,
+ data = options.data,
+ xScale = options.xScale,
+ width = options.lineWidth,
+ index = args.index,
+ x = xScale(args.x),
+ y = options.yScale(args.y),
+ x2;
+
+ if (data.length - 1 > index) {
+ x2 = options.xScale(data[index + 1][0]);
+ context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width);
+ }
+ };
+ }
+ }
+
+});
diff --git a/addons/web_graph/static/lib/flotr2/js/types/markers.js b/addons/web_graph/static/lib/flotr2/js/types/markers.js
new file mode 100644
index 00000000000..ee1104e3568
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/markers.js
@@ -0,0 +1,140 @@
+/** Markers **/
+/**
+ * Formats the marker labels.
+ * @param {Object} obj - Marker value Object {x:..,y:..}
+ * @return {String} Formatted marker string
+ */
+(function () {
+
+Flotr.defaultMarkerFormatter = function(obj){
+ return (Math.round(obj.y*100)/100)+'';
+};
+
+Flotr.addType('markers', {
+ options: {
+ show: false, // => setting to true will show markers, false will hide
+ lineWidth: 1, // => line width of the rectangle around the marker
+ color: '#000000', // => text color
+ fill: false, // => fill or not the marekers' rectangles
+ fillColor: "#FFFFFF", // => fill color
+ fillOpacity: 0.4, // => fill opacity
+ stroke: false, // => draw the rectangle around the markers
+ position: 'ct', // => the markers position (vertical align: b, m, t, horizontal align: l, c, r)
+ verticalMargin: 0, // => the margin between the point and the text.
+ labelFormatter: Flotr.defaultMarkerFormatter,
+ fontSize: Flotr.defaultOptions.fontSize,
+ stacked: false, // => true if markers should be stacked
+ stackingType: 'b', // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details)
+ horizontal: false // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly)
+ },
+
+ // TODO test stacked markers.
+ stack : {
+ positive : [],
+ negative : [],
+ values : []
+ },
+
+ draw : function (options) {
+
+ var
+ data = options.data,
+ context = options.context,
+ stack = options.stacked ? options.stack : false,
+ stackType = options.stackingType,
+ stackOffsetNeg,
+ stackOffsetPos,
+ stackOffset,
+ i, x, y, label;
+
+ context.save();
+ context.lineJoin = 'round';
+ context.lineWidth = options.lineWidth;
+ context.strokeStyle = 'rgba(0,0,0,0.5)';
+ context.fillStyle = options.fillStyle;
+
+ function stackPos (a, b) {
+ stackOffsetPos = stack.negative[a] || 0;
+ stackOffsetNeg = stack.positive[a] || 0;
+ if (b > 0) {
+ stack.positive[a] = stackOffsetPos + b;
+ return stackOffsetPos + b;
+ } else {
+ stack.negative[a] = stackOffsetNeg + b;
+ return stackOffsetNeg + b;
+ }
+ }
+
+ for (i = 0; i < data.length; ++i) {
+
+ x = data[i][0];
+ y = data[i][1];
+
+ if (stack) {
+ if (stackType == 'b') {
+ if (options.horizontal) y = stackPos(y, x);
+ else x = stackPos(x, y);
+ } else if (stackType == 'a') {
+ stackOffset = stack.values[x] || 0;
+ stack.values[x] = stackOffset + y;
+ y = stackOffset + y;
+ }
+ }
+
+ label = options.labelFormatter({x: x, y: y, index: i, data : data});
+ this.plot(options.xScale(x), options.yScale(y), label, options);
+ }
+ context.restore();
+ },
+ plot: function(x, y, label, options) {
+ var context = options.context;
+ if (isImage(label) && !label.complete) {
+ throw 'Marker image not loaded.';
+ } else {
+ this._plot(x, y, label, options);
+ }
+ },
+
+ _plot: function(x, y, label, options) {
+ var context = options.context,
+ margin = 2,
+ left = x,
+ top = y,
+ dim;
+
+ if (isImage(label))
+ dim = {height : label.height, width: label.width};
+ else
+ dim = options.text.canvas(label);
+
+ dim.width = Math.floor(dim.width+margin*2);
+ dim.height = Math.floor(dim.height+margin*2);
+
+ if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin;
+ else if (options.position.indexOf('l') != -1) left -= dim.width;
+
+ if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin;
+ else if (options.position.indexOf('t') != -1) top -= dim.height + options.verticalMargin;
+ else top += options.verticalMargin;
+
+ left = Math.floor(left)+0.5;
+ top = Math.floor(top)+0.5;
+
+ if(options.fill)
+ context.fillRect(left, top, dim.width, dim.height);
+
+ if(options.stroke)
+ context.strokeRect(left, top, dim.width, dim.height);
+
+ if (isImage(label))
+ context.drawImage(label, left+margin, top+margin);
+ else
+ Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color});
+ }
+});
+
+function isImage (i) {
+ return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image);
+}
+
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/types/pie.js b/addons/web_graph/static/lib/flotr2/js/types/pie.js
new file mode 100644
index 00000000000..ec932be5e6d
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/pie.js
@@ -0,0 +1,210 @@
+/** Pie **/
+/**
+ * Formats the pies labels.
+ * @param {Object} slice - Slice object
+ * @return {String} Formatted pie label string
+ */
+(function () {
+
+var
+ _ = Flotr._;
+
+Flotr.defaultPieLabelFormatter = function (total, value) {
+ return (100 * value / total).toFixed(2)+'%';
+};
+
+Flotr.addType('pie', {
+ options: {
+ show: false, // => setting to true will show bars, false will hide
+ lineWidth: 1, // => in pixels
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillColor: null, // => fill color
+ fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ explode: 6, // => the number of pixels the splices will be far from the center
+ sizeRatio: 0.6, // => the size ratio of the pie relative to the plot
+ startAngle: Math.PI/4, // => the first slice start angle
+ labelFormatter: Flotr.defaultPieLabelFormatter,
+ pie3D: false, // => whether to draw the pie in 3 dimenstions or not (ineffective)
+ pie3DviewAngle: (Math.PI/2 * 0.8),
+ pie3DspliceThickness: 20
+ },
+
+ draw : function (options) {
+
+ // TODO 3D charts what?
+
+ var
+ data = options.data,
+ context = options.context,
+ canvas = context.canvas,
+ lineWidth = options.lineWidth,
+ shadowSize = options.shadowSize,
+ sizeRatio = options.sizeRatio,
+ height = options.height,
+ width = options.width,
+ explode = options.explode,
+ color = options.color,
+ fill = options.fill,
+ fillStyle = options.fillStyle,
+ radius = Math.min(canvas.width, canvas.height) * sizeRatio / 2,
+ value = data[0][1],
+ html = [],
+ vScale = 1,//Math.cos(series.pie.viewAngle);
+ measure = Math.PI * 2 * value / this.total,
+ startAngle = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable)
+ endAngle = startAngle + measure,
+ bisection = startAngle + measure / 2,
+ label = options.labelFormatter(this.total, value),
+ //plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale;
+ explodeCoeff = explode + radius + 4,
+ distX = Math.cos(bisection) * explodeCoeff,
+ distY = Math.sin(bisection) * explodeCoeff,
+ textAlign = distX < 0 ? 'right' : 'left',
+ textBaseline = distY > 0 ? 'top' : 'bottom',
+ style,
+ x, y,
+ distX, distY;
+
+ context.save();
+ context.translate(width / 2, height / 2);
+ context.scale(1, vScale);
+
+ x = Math.cos(bisection) * explode;
+ y = Math.sin(bisection) * explode;
+
+ // Shadows
+ if (shadowSize > 0) {
+ this.plotSlice(x + shadowSize, y + shadowSize, radius, startAngle, endAngle, context);
+ if (fill) {
+ context.fillStyle = 'rgba(0,0,0,0.1)';
+ context.fill();
+ }
+ }
+
+ this.plotSlice(x, y, radius, startAngle, endAngle, context);
+ if (fill) {
+ context.fillStyle = fillStyle;
+ context.fill();
+ }
+ context.lineWidth = lineWidth;
+ context.strokeStyle = color;
+ context.stroke();
+
+ style = {
+ size : options.fontSize * 1.2,
+ color : options.fontColor,
+ weight : 1.5
+ };
+
+ if (label) {
+ if (options.htmlText || !options.textEnabled) {
+ divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;';
+ divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;';
+ html.push('
', label, '
');
+ }
+ else {
+ style.textAlign = textAlign;
+ style.textBaseline = textBaseline;
+ Flotr.drawText(context, label, distX, distY, style);
+ }
+ }
+
+ if (options.htmlText || !options.textEnabled) {
+ var div = Flotr.DOM.node('
');
+ Flotr.DOM.insert(div, html.join(''));
+ Flotr.DOM.insert(options.element, div);
+ }
+
+ context.restore();
+
+ // New start angle
+ this.startAngle = endAngle;
+ this.slices = this.slices || [];
+ this.slices.push({
+ radius : Math.min(canvas.width, canvas.height) * sizeRatio / 2,
+ x : x,
+ y : y,
+ explode : explode,
+ start : startAngle,
+ end : endAngle
+ });
+ },
+ plotSlice : function (x, y, radius, startAngle, endAngle, context) {
+ context.beginPath();
+ context.moveTo(x, y);
+ context.arc(x, y, radius, startAngle, endAngle, false);
+ context.lineTo(x, y);
+ context.closePath();
+ },
+ hit : function (options) {
+
+ var
+ data = options.data[0],
+ args = options.args,
+ index = options.index,
+ mouse = args[0],
+ n = args[1],
+ slice = this.slices[index],
+ x = mouse.relX - options.width / 2,
+ y = mouse.relY - options.height / 2,
+ r = Math.sqrt(x * x + y * y),
+ theta = Math.atan(y / x),
+ circle = Math.PI * 2,
+ explode = slice.explode || options.explode,
+ start = slice.start % circle,
+ end = slice.end % circle;
+
+ if (x < 0) {
+ theta += Math.PI;
+ } else if (x > 0 && y < 0) {
+ theta += circle;
+ }
+
+ if (r < slice.radius + explode && r > explode) {
+ if ((start > end && (theta < end || theta > start)) ||
+ (theta > start && theta < end)) {
+
+ // TODO Decouple this from hit plugin (chart shouldn't know what n means)
+ n.x = data[0];
+ n.y = data[1];
+ n.sAngle = start;
+ n.eAngle = end;
+ n.index = 0;
+ n.seriesIndex = index;
+ n.fraction = data[1] / this.total;
+ }
+ }
+ },
+ drawHit: function (options) {
+ var
+ context = options.context,
+ slice = this.slices[options.args.seriesIndex];
+
+ context.save();
+ context.translate(options.width / 2, options.height / 2);
+ this.plotSlice(slice.x, slice.y, slice.radius, slice.start, slice.end, context);
+ context.stroke();
+ context.restore();
+ },
+ clearHit : function (options) {
+ var
+ context = options.context,
+ slice = this.slices[options.args.seriesIndex],
+ padding = 2 * options.lineWidth,
+ radius = slice.radius + padding;
+
+ context.save();
+ context.translate(options.width / 2, options.height / 2);
+ context.clearRect(
+ slice.x - radius,
+ slice.y - radius,
+ 2 * radius + padding,
+ 2 * radius + padding
+ );
+ context.restore();
+ },
+ extendYRange : function (axis, data) {
+ this.total = (this.total || 0) + data[0][1];
+ }
+});
+})();
diff --git a/addons/web_graph/static/lib/flotr2/js/types/points.js b/addons/web_graph/static/lib/flotr2/js/types/points.js
new file mode 100644
index 00000000000..ebb360e6fae
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/points.js
@@ -0,0 +1,66 @@
+/** Points **/
+Flotr.addType('points', {
+ options: {
+ show: false, // => setting to true will show points, false will hide
+ radius: 3, // => point radius (pixels)
+ lineWidth: 2, // => line width in pixels
+ fill: true, // => true to fill the points with a color, false for (transparent) no fill
+ fillColor: '#FFFFFF', // => fill color
+ fillOpacity: 0.4 // => opacity of color inside the points
+ },
+
+ draw : function (options) {
+ var
+ context = options.context,
+ lineWidth = options.lineWidth,
+ shadowSize = options.shadowSize;
+
+ context.save();
+
+ if (shadowSize > 0) {
+ context.lineWidth = shadowSize / 2;
+
+ context.strokeStyle = 'rgba(0,0,0,0.1)';
+ this.plot(options, shadowSize / 2 + context.lineWidth / 2);
+
+ context.strokeStyle = 'rgba(0,0,0,0.2)';
+ this.plot(options, context.lineWidth / 2);
+ }
+
+ context.lineWidth = options.lineWidth;
+ context.strokeStyle = options.color;
+ context.fillStyle = options.fillColor || options.color;
+
+ this.plot(options);
+ context.restore();
+ },
+
+ plot : function (options, offset) {
+ var
+ data = options.data,
+ context = options.context,
+ xScale = options.xScale,
+ yScale = options.yScale,
+ i, x, y;
+
+ for (i = data.length - 1; i > -1; --i) {
+ y = data[i][1];
+ if (y === null) continue;
+
+ x = xScale(data[i][0]);
+ y = yScale(y);
+
+ if (x < 0 || x > options.width || y < 0 || y > options.height) continue;
+
+ context.beginPath();
+ if (offset) {
+ context.arc(x, y + offset, options.radius, 0, Math.PI, false);
+ } else {
+ context.arc(x, y, options.radius, 0, 2 * Math.PI, true);
+ if (options.fill) context.fill();
+ }
+ context.stroke();
+ context.closePath();
+ }
+ }
+});
diff --git a/addons/web_graph/static/lib/flotr2/js/types/radar.js b/addons/web_graph/static/lib/flotr2/js/types/radar.js
new file mode 100644
index 00000000000..d693ddc78ec
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/radar.js
@@ -0,0 +1,60 @@
+/** Radar **/
+Flotr.addType('radar', {
+ options: {
+ show: false, // => setting to true will show radar chart, false will hide
+ lineWidth: 2, // => line width in pixels
+ fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
+ fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
+ radiusRatio: 0.90 // => ratio of the radar, against the plot size
+ },
+ draw : function (options) {
+ var
+ context = options.context,
+ shadowSize = options.shadowSize;
+
+ context.save();
+ context.translate(options.width / 2, options.height / 2);
+ context.lineWidth = options.lineWidth;
+
+ // Shadow
+ context.fillStyle = 'rgba(0,0,0,0.05)';
+ context.strokeStyle = 'rgba(0,0,0,0.05)';
+ this.plot(options, shadowSize / 2);
+ context.strokeStyle = 'rgba(0,0,0,0.1)';
+ this.plot(options, shadowSize / 4);
+
+ // Chart
+ context.strokeStyle = options.color;
+ context.fillStyle = options.fillStyle;
+ this.plot(options);
+
+ context.restore();
+ },
+ plot : function (options, offset) {
+ var
+ data = options.data,
+ context = options.context,
+ radius = Math.min(options.height, options.width) * options.radiusRatio / 2,
+ step = 2 * Math.PI / data.length,
+ angle = -Math.PI / 2,
+ i, ratio;
+
+ offset = offset || 0;
+
+ context.beginPath();
+ for (i = 0; i < data.length; ++i) {
+ ratio = data[i][1] / this.max;
+
+ context[i === 0 ? 'moveTo' : 'lineTo'](
+ Math.cos(i * step + angle) * radius * ratio + offset,
+ Math.sin(i * step + angle) * radius * ratio + offset
+ );
+ }
+ context.closePath();
+ if (options.fill) context.fill();
+ context.stroke();
+ },
+ extendYRange : function (axis, data) {
+ this.max = Math.max(axis.max, this.max || -Number.MAX_VALUE);
+ }
+});
diff --git a/addons/web_graph/static/lib/flotr2/js/types/timeline.js b/addons/web_graph/static/lib/flotr2/js/types/timeline.js
new file mode 100644
index 00000000000..59ea6094c01
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/js/types/timeline.js
@@ -0,0 +1,90 @@
+Flotr.addType('timeline', {
+ options: {
+ show: false,
+ lineWidth: 1,
+ barWidth: 0.2,
+ fill: true,
+ fillColor: null,
+ fillOpacity: 0.4,
+ centered: true
+ },
+
+ draw : function (options) {
+
+ var
+ context = options.context;
+
+ context.save();
+ context.lineJoin = 'miter';
+ context.lineWidth = options.lineWidth;
+ context.strokeStyle = options.color;
+ context.fillStyle = options.fillStyle;
+
+ this.plot(options);
+
+ context.restore();
+ },
+
+ plot : function (options) {
+
+ var
+ data = options.data,
+ context = options.context,
+ xScale = options.xScale,
+ yScale = options.yScale,
+ barWidth = options.barWidth,
+ lineWidth = options.lineWidth,
+ i;
+
+ Flotr._.each(data, function (timeline) {
+
+ var
+ x = timeline[0],
+ y = timeline[1],
+ w = timeline[2],
+ h = barWidth,
+
+ xt = Math.ceil(xScale(x)),
+ wt = Math.ceil(xScale(x + w)) - xt,
+ yt = Math.round(yScale(y)),
+ ht = Math.round(yScale(y - h)) - yt,
+
+ x0 = xt - lineWidth / 2,
+ y0 = Math.round(yt - ht / 2) - lineWidth / 2;
+
+ context.strokeRect(x0, y0, wt, ht);
+ context.fillRect(x0, y0, wt, ht);
+
+ });
+ },
+
+ extendRange : function (series) {
+
+ var
+ data = series.data,
+ xa = series.xaxis,
+ ya = series.yaxis,
+ w = series.timeline.barWidth;
+
+ if (xa.options.min === null)
+ xa.min = xa.datamin - w / 2;
+
+ if (xa.options.max === null) {
+
+ var
+ max = xa.max;
+
+ Flotr._.each(data, function (timeline) {
+ max = Math.max(max, timeline[0] + timeline[2]);
+ }, this);
+
+ xa.max = max + w / 2;
+ }
+
+ if (ya.options.min === null)
+ ya.min = ya.datamin - w;
+ if (ya.options.min === null)
+ ya.max = ya.datamax + w;
+ }
+
+});
diff --git a/addons/web_graph/static/lib/flotr2/lib/base64.js b/addons/web_graph/static/lib/flotr2/lib/base64.js
new file mode 100644
index 00000000000..3306e49f9f7
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/base64.js
@@ -0,0 +1,113 @@
+/* Copyright (C) 1999 Masanao Izumo
+ * Version: 1.0
+ * LastModified: Dec 25 1999
+ * This library is free. You can redistribute it and/or modify it.
+ */
+
+/*
+ * Interfaces:
+ * b64 = base64encode(data);
+ * data = base64decode(b64);
+ */
+
+(function() {
+
+var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+var base64DecodeChars = [
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1];
+
+function base64encode(str) {
+ var out, i, len;
+ var c1, c2, c3;
+
+ len = str.length;
+ i = 0;
+ out = "";
+ while(i < len) {
+ c1 = str.charCodeAt(i++) & 0xff;
+ if(i == len)
+ {
+ out += base64EncodeChars.charAt(c1 >> 2);
+ out += base64EncodeChars.charAt((c1 & 0x3) << 4);
+ out += "==";
+ break;
+ }
+ c2 = str.charCodeAt(i++);
+ if(i == len)
+ {
+ out += base64EncodeChars.charAt(c1 >> 2);
+ out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
+ out += base64EncodeChars.charAt((c2 & 0xF) << 2);
+ out += "=";
+ break;
+ }
+ c3 = str.charCodeAt(i++);
+ out += base64EncodeChars.charAt(c1 >> 2);
+ out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
+ out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));
+ out += base64EncodeChars.charAt(c3 & 0x3F);
+ }
+ return out;
+}
+
+function base64decode(str) {
+ var c1, c2, c3, c4;
+ var i, len, out;
+
+ len = str.length;
+ i = 0;
+ out = "";
+ while(i < len) {
+ /* c1 */
+ do {
+ c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
+ } while(i < len && c1 == -1);
+ if(c1 == -1)
+ break;
+
+ /* c2 */
+ do {
+ c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
+ } while(i < len && c2 == -1);
+ if(c2 == -1)
+ break;
+
+ out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
+
+ /* c3 */
+ do {
+ c3 = str.charCodeAt(i++) & 0xff;
+ if(c3 == 61)
+ return out;
+ c3 = base64DecodeChars[c3];
+ } while(i < len && c3 == -1);
+ if(c3 == -1)
+ break;
+
+ out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
+
+ /* c4 */
+ do {
+ c4 = str.charCodeAt(i++) & 0xff;
+ if(c4 == 61)
+ return out;
+ c4 = base64DecodeChars[c4];
+ } while(i < len && c4 == -1);
+ if(c4 == -1)
+ break;
+ out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
+ }
+ return out;
+}
+
+if (!window.btoa) window.btoa = base64encode;
+if (!window.atob) window.atob = base64decode;
+
+})();
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/flotr2/lib/bean-min.js b/addons/web_graph/static/lib/flotr2/lib/bean-min.js
new file mode 100644
index 00000000000..2471420fe25
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/bean-min.js
@@ -0,0 +1,10 @@
+/*!
+ * bean.js - copyright Jacob Thornton 2011
+ * https://github.com/fat/bean
+ * MIT License
+ * special thanks to:
+ * dean edwards: http://dean.edwards.name/
+ * dperini: https://github.com/dperini/nwevents
+ * the entire mootools team: github.com/mootools/mootools-core
+ *//*global module:true, define:true*/
+!function(a,b,c){typeof module!="undefined"?module.exports=c(a,b):typeof define=="function"&&typeof define.amd=="object"?define(c):b[a]=c(a,b)}("bean",this,function(a,b){var c=window,d=b[a],e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=Array.prototype.slice,q=/click|mouse|menu|drag|drop/i,r=/^touch|^gesture/i,s={one:1},t=function(a,b,c){for(c=0;c0){b=b.split(" ");for(j=b.length;j--;)G(a,b[j],c);return a}h=l&&b.replace(g,""),h&&u[h]&&(h=u[h].type);if(!b||l){if(i=l&&b.replace(f,""))i=i.split(".");k(a,h,c,i)}else if(typeof b=="function")k(a,null,b);else for(d in b)b.hasOwnProperty(d)&&G(a,d,b[d]);return a},H=function(a,b,c,d,e){var f,g,h,i,j=c,k=c&&typeof c=="string";if(b&&!c&&typeof b=="object")for(f in b)b.hasOwnProperty(f)&&H.apply(this,[a,f,b[f]]);else{i=arguments.length>3?p.call(arguments,3):[],g=(k?c:b).split(" "),k&&(c=F(b,j=d,e))&&(i=p.call(i,1)),this===s&&(c=C(G,a,b,c,j));for(h=g.length;h--;)E(a,g[h],c,j,i)}return a},I=function(){return H.apply(s,arguments)},J=n?function(a,b,d){var e=l.createEvent(a?"HTMLEvents":"UIEvents");e[a?"initEvent":"initUIEvent"](b,!0,!0,c,1),d.dispatchEvent(e)}:function(a,b,c){c=w(c,a),a?c.fireEvent("on"+b,l.createEventObject()):c["_on"+b]++},K=function(a,b,c){var d,e,h,i,j,k=b.split(" ");for(d=k.length;d--;){b=k[d].replace(g,"");if(i=k[d].replace(f,""))i=i.split(".");if(!i&&!c&&a[o])J(t[b],b,a);else{j=y.get(a,b),c=[!1].concat(c);for(e=0,h=j.length;e 0) {
+ // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3')
+ typeSpec = typeSpec.split(' ')
+ for (i = typeSpec.length; i--;)
+ remove(element, typeSpec[i], fn)
+ return element
+ }
+ type = isString && typeSpec.replace(nameRegex, '')
+ if (type && customEvents[type])
+ type = customEvents[type].type
+ if (!typeSpec || isString) {
+ // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3)
+ if (namespaces = isString && typeSpec.replace(namespaceRegex, ''))
+ namespaces = namespaces.split('.')
+ rm(element, type, fn, namespaces)
+ } else if (typeof typeSpec === 'function') {
+ // remove(el, fn)
+ rm(element, null, typeSpec)
+ } else {
+ // remove(el, { t1: fn1, t2, fn2 })
+ for (k in typeSpec) {
+ if (typeSpec.hasOwnProperty(k))
+ remove(element, k, typeSpec[k])
+ }
+ }
+ return element
+ }
+
+ , add = function (element, events, fn, delfn, $) {
+ var type, types, i, args
+ , originalFn = fn
+ , isDel = fn && typeof fn === 'string'
+
+ if (events && !fn && typeof events === 'object') {
+ for (type in events) {
+ if (events.hasOwnProperty(type))
+ add.apply(this, [ element, type, events[type] ])
+ }
+ } else {
+ args = arguments.length > 3 ? slice.call(arguments, 3) : []
+ types = (isDel ? fn : events).split(' ')
+ isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1))
+ // special case for one()
+ this === ONE && (fn = once(remove, element, events, fn, originalFn))
+ for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args)
+ }
+ return element
+ }
+
+ , one = function () {
+ return add.apply(ONE, arguments)
+ }
+
+ , fireListener = W3C_MODEL ? function (isNative, type, element) {
+ var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
+ evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
+ element.dispatchEvent(evt)
+ } : function (isNative, type, element) {
+ element = targetElement(element, isNative)
+ // if not-native then we're using onpropertychange so we just increment a custom property
+ isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
+ }
+
+ , fire = function (element, type, args) {
+ var i, j, l, names, handlers
+ , types = type.split(' ')
+
+ for (i = types.length; i--;) {
+ type = types[i].replace(nameRegex, '')
+ if (names = types[i].replace(namespaceRegex, ''))
+ names = names.split('.')
+ if (!names && !args && element[eventSupport]) {
+ fireListener(nativeEvents[type], type, element)
+ } else {
+ // non-native event, either because of a namespace, arguments or a non DOM element
+ // iterate over all listeners and manually 'fire'
+ handlers = registry.get(element, type)
+ args = [false].concat(args)
+ for (j = 0, l = handlers.length; j < l; j++) {
+ if (handlers[j].inNamespaces(names))
+ handlers[j].handler.apply(element, args)
+ }
+ }
+ }
+ return element
+ }
+
+ , clone = function (element, from, type) {
+ var i = 0
+ , handlers = registry.get(from, type)
+ , l = handlers.length
+
+ for (;i < l; i++)
+ handlers[i].original && add(element, handlers[i].type, handlers[i].original)
+ return element
+ }
+
+ , bean = {
+ add: add
+ , one: one
+ , remove: remove
+ , clone: clone
+ , fire: fire
+ , noConflict: function () {
+ context[name] = old
+ return this
+ }
+ }
+
+ if (win[attachEvent]) {
+ // for IE, clean up on unload to avoid leaks
+ var cleanup = function () {
+ var i, entries = registry.entries()
+ for (i in entries) {
+ if (entries[i].type && entries[i].type !== 'unload')
+ remove(entries[i].element, entries[i].type)
+ }
+ win[detachEvent]('onunload', cleanup)
+ win.CollectGarbage && win.CollectGarbage()
+ }
+ win[attachEvent]('onunload', cleanup)
+ }
+
+ return bean
+});
diff --git a/addons/web_graph/static/lib/flotr2/lib/canvas2image.js b/addons/web_graph/static/lib/flotr2/lib/canvas2image.js
new file mode 100644
index 00000000000..e064cdac8b9
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/canvas2image.js
@@ -0,0 +1,198 @@
+/*
+ * Canvas2Image v0.1
+ * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
+ * MIT License [http://www.opensource.org/licenses/mit-license.php]
+ */
+
+var Canvas2Image = (function() {
+ // check if we have canvas support
+ var oCanvas = document.createElement("canvas"),
+ sc = String.fromCharCode,
+ strDownloadMime = "image/octet-stream",
+ bReplaceDownloadMime = false;
+
+ // no canvas, bail out.
+ if (!oCanvas.getContext) {
+ return {
+ saveAsBMP : function(){},
+ saveAsPNG : function(){},
+ saveAsJPEG : function(){}
+ }
+ }
+
+ var bHasImageData = !!(oCanvas.getContext("2d").getImageData),
+ bHasDataURL = !!(oCanvas.toDataURL),
+ bHasBase64 = !!(window.btoa);
+
+ // ok, we're good
+ var readCanvasData = function(oCanvas) {
+ var iWidth = parseInt(oCanvas.width),
+ iHeight = parseInt(oCanvas.height);
+ return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight);
+ }
+
+ // base64 encodes either a string or an array of charcodes
+ var encodeData = function(data) {
+ var i, aData, strData = "";
+
+ if (typeof data == "string") {
+ strData = data;
+ } else {
+ aData = data;
+ for (i = 0; i < aData.length; i++) {
+ strData += sc(aData[i]);
+ }
+ }
+ return btoa(strData);
+ }
+
+ // creates a base64 encoded string containing BMP data takes an imagedata object as argument
+ var createBMP = function(oData) {
+ var strHeader = '',
+ iWidth = oData.width,
+ iHeight = oData.height;
+
+ strHeader += 'BM';
+
+ var iFileSize = iWidth*iHeight*4 + 54; // total header size = 54 bytes
+ strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+ strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+ strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+ strHeader += sc(iFileSize % 256);
+
+ strHeader += sc(0, 0, 0, 0, 54, 0, 0, 0); // data offset
+ strHeader += sc(40, 0, 0, 0); // info header size
+
+ var iImageWidth = iWidth;
+ strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+ strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+ strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+ strHeader += sc(iImageWidth % 256);
+
+ var iImageHeight = iHeight;
+ strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+ strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+ strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+ strHeader += sc(iImageHeight % 256);
+
+ strHeader += sc(1, 0, 32, 0); // num of planes & num of bits per pixel
+ strHeader += sc(0, 0, 0, 0); // compression = none
+
+ var iDataSize = iWidth*iHeight*4;
+ strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+ strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+ strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+ strHeader += sc(iDataSize % 256);
+
+ strHeader += sc(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); // these bytes are not used
+
+ var aImgData = oData.data,
+ strPixelData = "",
+ c, x, y = iHeight,
+ iOffsetX, iOffsetY, strPixelRow;
+
+ do {
+ iOffsetY = iWidth*(y-1)*4;
+ strPixelRow = "";
+ for (x = 0; x < iWidth; x++) {
+ iOffsetX = 4*x;
+ strPixelRow += sc(
+ aImgData[iOffsetY + iOffsetX + 2], // B
+ aImgData[iOffsetY + iOffsetX + 1], // G
+ aImgData[iOffsetY + iOffsetX], // R
+ aImgData[iOffsetY + iOffsetX + 3] // A
+ );
+ }
+ strPixelData += strPixelRow;
+ } while (--y);
+
+ return encodeData(strHeader + strPixelData);
+ }
+
+ // sends the generated file to the client
+ var saveFile = function(strData) {
+ if (!window.open(strData)) {
+ document.location.href = strData;
+ }
+ }
+
+ var makeDataURI = function(strData, strMime) {
+ return "data:" + strMime + ";base64," + strData;
+ }
+
+ // generates a object containing the imagedata
+ var makeImageObject = function(strSource) {
+ var oImgElement = document.createElement("img");
+ oImgElement.src = strSource;
+ return oImgElement;
+ }
+
+ var scaleCanvas = function(oCanvas, iWidth, iHeight) {
+ if (iWidth && iHeight) {
+ var oSaveCanvas = document.createElement("canvas");
+
+ oSaveCanvas.width = iWidth;
+ oSaveCanvas.height = iHeight;
+ oSaveCanvas.style.width = iWidth+"px";
+ oSaveCanvas.style.height = iHeight+"px";
+
+ var oSaveCtx = oSaveCanvas.getContext("2d");
+
+ oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth);
+
+ return oSaveCanvas;
+ }
+ return oCanvas;
+ }
+
+ return {
+ saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) {
+ if (!bHasDataURL) return false;
+
+ var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight),
+ strMime = "image/png",
+ strData = oScaledCanvas.toDataURL(strMime);
+
+ if (bReturnImg) {
+ return makeImageObject(strData);
+ } else {
+ saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData);
+ }
+ return true;
+ },
+
+ saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) {
+ if (!bHasDataURL) return false;
+
+ var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight),
+ strMime = "image/jpeg",
+ strData = oScaledCanvas.toDataURL(strMime);
+
+ // check if browser actually supports jpeg by looking for the mime type in the data uri. if not, return false
+ if (strData.indexOf(strMime) != 5) return false;
+
+ if (bReturnImg) {
+ return makeImageObject(strData);
+ } else {
+ saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData);
+ }
+ return true;
+ },
+
+ saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) {
+ if (!(bHasDataURL && bHasImageData && bHasBase64)) return false;
+
+ var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight),
+ strMime = "image/bmp",
+ oData = readCanvasData(oScaledCanvas),
+ strImgData = createBMP(oData);
+
+ if (bReturnImg) {
+ return makeImageObject(makeDataURI(strImgData, strMime));
+ } else {
+ saveFile(makeDataURI(strImgData, strMime));
+ }
+ return true;
+ }
+ };
+})();
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/flotr2/lib/canvastext.js b/addons/web_graph/static/lib/flotr2/lib/canvastext.js
new file mode 100644
index 00000000000..3266729f6a6
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/canvastext.js
@@ -0,0 +1,429 @@
+/**
+ * This code is released to the public domain by Jim Studt, 2007.
+ * He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/
+ * It as been modified by Fabien Mnager to handle font style like size, weight, color and rotation.
+ * A partial support for special characters has been added too.
+ */
+var CanvasText = {
+ /** The letters definition. It is a list of letters,
+ * with their width, and the coordinates of points compositing them.
+ * The syntax for the points is : [x, y], null value means "pen up"
+ */
+ letters: {
+ '\n':{ width: -1, points: [] },
+ ' ': { width: 10, points: [] },
+ '!': { width: 10, points: [[5,21],[5,7],null,[5,2],[4,1],[5,0],[6,1],[5,2]] },
+ '"': { width: 16, points: [[4,21],[4,14],null,[12,21],[12,14]] },
+ '#': { width: 21, points: [[11,25],[4,-7],null,[17,25],[10,-7],null,[4,12],[18,12],null,[3,6],[17,6]] },
+ '$': { width: 20, points: [[8,25],[8,-4],null,[12,25],[12,-4],null,[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
+ '%': { width: 24, points: [[21,21],[3,0],null,[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],null,[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },
+ '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] },
+ '\'':{ width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },
+ '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },
+ ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },
+ '*': { width: 16, points: [[8,21],[8,9],null,[3,18],[13,12],null,[13,18],[3,12]] },
+ '+': { width: 26, points: [[13,18],[13,0],null,[4,9],[22,9]] },
+ ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
+ '-': { width: 26, points: [[4,9],[22,9]] },
+ '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },
+ '/': { width: 22, points: [[20,25],[2,-7]] },
+ '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },
+ '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },
+ '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },
+ '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
+ '4': { width: 20, points: [[13,21],[3,7],[18,7],null,[13,21],[13,0]] },
+ '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
+ '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },
+ '7': { width: 20, points: [[17,21],[7,0],null,[3,21],[17,21]] },
+ '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },
+ '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },
+ ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[5,2],[4,1],[5,0],[6,1],[5,2]] },
+ ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
+ '<': { width: 24, points: [[20,18],[4,9],[20,0]] },
+ '=': { width: 26, points: [[4,12],[22,12],null,[4,6],[22,6]] },
+ '>': { width: 24, points: [[4,18],[20,9],[4,0]] },
+ '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]] },
+ '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]] },
+ 'A': { width: 18, points: [[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]] },
+ 'B': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },
+ 'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },
+ 'D': { width: 21, points: [[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },
+ 'E': { width: 19, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]] },
+ 'F': { width: 18, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]] },
+ 'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]] },
+ 'H': { width: 22, points: [[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]] },
+ 'I': { width: 8, points: [[4,21],[4,0]] },
+ 'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },
+ 'K': { width: 21, points: [[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]] },
+ 'L': { width: 17, points: [[4,21],[4,0],null,[4,0],[16,0]] },
+ 'M': { width: 24, points: [[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]] },
+ 'N': { width: 22, points: [[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]] },
+ 'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },
+ 'P': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },
+ 'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]] },
+ 'R': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]] },
+ 'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
+ 'T': { width: 16, points: [[8,21],[8,0],null,[1,21],[15,21]] },
+ 'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },
+ 'V': { width: 18, points: [[1,21],[9,0],null,[17,21],[9,0]] },
+ 'W': { width: 24, points: [[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]] },
+ 'X': { width: 20, points: [[3,21],[17,0],null,[17,21],[3,0]] },
+ 'Y': { width: 18, points: [[1,21],[9,11],[9,0],null,[17,21],[9,11]] },
+ 'Z': { width: 20, points: [[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]] },
+ '[': { width: 14, points: [[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]] },
+ '\\':{ width: 14, points: [[0,21],[14,-3]] },
+ ']': { width: 14, points: [[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]] },
+ '^': { width: 14, points: [[3,10],[8,18],[13,10]] },
+ '_': { width: 16, points: [[0,-2],[16,-2]] },
+ '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },
+ 'a': { width: 19, points: [[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'b': { width: 19, points: [[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
+ 'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'd': { width: 19, points: [[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]] },
+ 'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'h': { width: 19, points: [[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
+ 'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]] },
+ 'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },
+ 'k': { width: 17, points: [[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]] },
+ 'l': { width: 8, points: [[4,21],[4,0]] },
+ 'm': { width: 30, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },
+ 'n': { width: 19, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
+ 'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },
+ 'p': { width: 19, points: [[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
+ 'q': { width: 19, points: [[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+ 'r': { width: 13, points: [[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]] },
+ 's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },
+ 't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]] },
+ 'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]] },
+ 'v': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0]] },
+ 'w': { width: 22, points: [[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]] },
+ 'x': { width: 17, points: [[3,14],[14,0],null,[14,14],[3,0]] },
+ 'y': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },
+ 'z': { width: 17, points: [[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]] },
+ '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },
+ '|': { width: 8, points: [[4,25],[4,-7]] },
+ '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },
+ '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] },
+
+ // Lower case Latin-1
+ '': { diacritic: '`', letter: 'a' },
+ '': { diacritic: '', letter: 'a' },
+ '': { diacritic: '^', letter: 'a' },
+ '': { diacritic: '', letter: 'a' },
+ '': { diacritic: '~', letter: 'a' },
+
+ '': { diacritic: '`', letter: 'e' },
+ '': { diacritic: '', letter: 'e' },
+ '': { diacritic: '^', letter: 'e' },
+ '': { diacritic: '', letter: 'e' },
+
+ '': { diacritic: '`', letter: 'i' },
+ '': { diacritic: '', letter: 'i' },
+ '': { diacritic: '^', letter: 'i' },
+ '': { diacritic: '', letter: 'i' },
+
+ '': { diacritic: '`', letter: 'o' },
+ '': { diacritic: '', letter: 'o' },
+ '': { diacritic: '^', letter: 'o' },
+ '': { diacritic: '', letter: 'o' },
+ '': { diacritic: '~', letter: 'o' },
+
+ '': { diacritic: '`', letter: 'u' },
+ '': { diacritic: '', letter: 'u' },
+ '': { diacritic: '^', letter: 'u' },
+ '': { diacritic: '', letter: 'u' },
+
+ '': { diacritic: '', letter: 'y' },
+ '': { diacritic: '', letter: 'y' },
+
+ '': { diacritic: '', letter: 'c' },
+ '': { diacritic: '~', letter: 'n' },
+
+ // Upper case Latin-1
+ '': { diacritic: '`', letter: 'A' },
+ '': { diacritic: '', letter: 'A' },
+ '': { diacritic: '^', letter: 'A' },
+ '': { diacritic: '', letter: 'A' },
+ '': { diacritic: '~', letter: 'A' },
+
+ '': { diacritic: '`', letter: 'E' },
+ '': { diacritic: '', letter: 'E' },
+ '': { diacritic: '^', letter: 'E' },
+ '': { diacritic: '', letter: 'E' },
+
+ '': { diacritic: '`', letter: 'I' },
+ '': { diacritic: '', letter: 'I' },
+ '': { diacritic: '^', letter: 'I' },
+ '': { diacritic: '', letter: 'I' },
+
+ '': { diacritic: '`', letter: 'O' },
+ '': { diacritic: '', letter: 'O' },
+ '': { diacritic: '^', letter: 'O' },
+ '': { diacritic: '', letter: 'O' },
+ '': { diacritic: '~', letter: 'O' },
+
+ '': { diacritic: '`', letter: 'U' },
+ '': { diacritic: '', letter: 'U' },
+ '': { diacritic: '^', letter: 'U' },
+ '': { diacritic: '', letter: 'U' },
+
+ '': { diacritic: '', letter: 'Y' },
+
+ '': { diacritic: '', letter: 'C' },
+ '': { diacritic: '~', letter: 'N' }
+ },
+
+ specialchars: {
+ 'pi': { width: 19, points: [[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]] }
+ },
+
+ /** Diacritics, used to draw accentuated letters */
+ diacritics: {
+ '': { entity: 'cedil', points: [[6,-4],[4,-6],[2,-7],[1,-7]] },
+ '': { entity: 'acute', points: [[8,19],[13,22]] },
+ '`': { entity: 'grave', points: [[7,22],[12,19]] },
+ '^': { entity: 'circ', points: [[5.5,19],[9.5,23],[12.5,19]] },
+ '': { entity: 'trema', points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[12,21],[13,20],[14,21],[13,22],[12,21]] },
+ '~': { entity: 'tilde', points: [[4,18],[7,22],[10,18],[13,22]] }
+ },
+
+ /** The default font styling */
+ style: {
+ size: 8, // font height in pixels
+ font: null, // not yet implemented
+ color: '#000000', // font color
+ weight: 1, // float, 1 for 'normal'
+ textAlign: 'left', // left, right, center
+ textBaseline: 'bottom', // top, middle, bottom
+ adjustAlign: false, // modifies the alignments if the angle is different from 0 to make the spin point always at the good position
+ angle: 0, // in radians, anticlockwise
+ tracking: 1, // space between the letters, float, 1 for 'normal'
+ boundingBoxColor: '#ff0000', // color of the bounding box (null to hide), can be used for debug and font drawing
+ originPointColor: '#000000' // color of the bounding box (null to hide), can be used for debug and font drawing
+ },
+
+ debug: false,
+ _bufferLexemes: {},
+
+ extend: function(dest, src) {
+ for (var property in src) {
+ if (property in dest) continue;
+ dest[property] = src[property];
+ }
+ return dest;
+ },
+
+ /** Get the letter data corresponding to a char
+ * @param {String} ch - The char
+ */
+ letter: function(ch) {
+ return CanvasText.letters[ch];
+ },
+
+ parseLexemes: function(str) {
+ if (CanvasText._bufferLexemes[str])
+ return CanvasText._bufferLexemes[str];
+
+ var i, c, matches = str.match(/&[A-Za-z]{2,5};|\s|./g),
+ result = [], chars = [];
+
+ for (i = 0; i < matches.length; i++) {
+ c = matches[i];
+ if (c.length == 1)
+ chars.push(c);
+ else {
+ var entity = c.substring(1, c.length-1);
+ if (CanvasText.specialchars[entity])
+ chars.push(entity);
+ else
+ chars = chars.concat(c.toArray());
+ }
+ }
+ for (i = 0; i < chars.length; i++) {
+ c = chars[i];
+ if (c = CanvasText.letters[c] || CanvasText.specialchars[c]) result.push(c);
+ }
+ for (i = 0; i < result.length; i++) {
+ if (result === null || typeof result === 'undefined')
+ delete result[i];
+ }
+ return CanvasText._bufferLexemes[str] = result;
+ },
+
+ /** Get the font ascent for a given style
+ * @param {Object} style - The reference style
+ */
+ ascent: function(style) {
+ style = style || CanvasText.style;
+ return (style.size || CanvasText.style.size);
+ },
+
+ /** Get the font descent for a given style
+ * @param {Object} style - The reference style
+ * */
+ descent: function(style) {
+ style = style || CanvasText.style;
+ return 7.0*(style.size || CanvasText.style.size)/25.0;
+ },
+
+ /** Measure the text horizontal size
+ * @param {String} str - The text
+ * @param {Object} style - Text style
+ * */
+ measure: function(str, style) {
+ if (!str) return;
+ style = style || CanvasText.style;
+
+ var i, width, lexemes = CanvasText.parseLexemes(str),
+ total = 0;
+
+ for (i = lexemes.length-1; i > -1; --i) {
+ c = lexemes[i];
+ width = (c.diacritic) ? CanvasText.letter(c.letter).width : c.width;
+ total += width * (style.tracking || CanvasText.style.tracking) * (style.size || CanvasText.style.size) / 25.0;
+ }
+ return total;
+ },
+
+ getDimensions: function(str, style) {
+ style = style || CanvasText.style;
+
+ var width = CanvasText.measure(str, style),
+ height = style.size || CanvasText.style.size,
+ angle = style.angle || CanvasText.style.angle;
+
+ if (style.angle == 0) return {width: width, height: height};
+ return {
+ width: Math.abs(Math.cos(angle) * width) + Math.abs(Math.sin(angle) * height),
+ height: Math.abs(Math.sin(angle) * width) + Math.abs(Math.cos(angle) * height)
+ }
+ },
+
+ /** Draws serie of points at given coordinates
+ * @param {Canvas context} ctx - The canvas context
+ * @param {Array} points - The points to draw
+ * @param {Number} x - The X coordinate
+ * @param {Number} y - The Y coordinate
+ * @param {Number} mag - The scale
+ */
+ drawPoints: function (ctx, points, x, y, mag, offset) {
+ var i, a, penUp = true, needStroke = 0;
+ offset = offset || {x:0, y:0};
+
+ ctx.beginPath();
+ for (i = 0; i < points.length; i++) {
+ a = points[i];
+ if (!a) {
+ penUp = true;
+ continue;
+ }
+ if (penUp) {
+ ctx.moveTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y);
+ penUp = false;
+ }
+ else {
+ ctx.lineTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y);
+ }
+ }
+ ctx.stroke();
+ ctx.closePath();
+ },
+
+ /** Draws a text at given coordinates and with a given style
+ * @param {String} str - The text to draw
+ * @param {Number} xOrig - The X coordinate
+ * @param {Number} yOrig - The Y coordinate
+ * @param {Object} style - The font style
+ */
+ draw: function(str, xOrig, yOrig, style) {
+ if (!str) return;
+ CanvasText.extend(style, CanvasText.style);
+
+ var i, c, total = 0,
+ mag = style.size / 25.0,
+ x = 0, y = 0,
+ lexemes = CanvasText.parseLexemes(str),
+ offset = {x: 0, y: 0},
+ measure = CanvasText.measure(str, style),
+ align;
+
+ if (style.adjustAlign) {
+ align = CanvasText.getBestAlign(style.angle, style);
+ CanvasText.extend(style, align);
+ }
+
+ switch (style.textAlign) {
+ case 'left': break;
+ case 'center': offset.x = -measure / 2; break;
+ case 'right': offset.x = -measure; break;
+ }
+
+ switch (style.textBaseline) {
+ case 'bottom': break;
+ case 'middle': offset.y = style.size / 2; break;
+ case 'top': offset.y = style.size; break;
+ }
+
+ this.save();
+ this.translate(xOrig, yOrig);
+ this.rotate(style.angle);
+ this.lineCap = "round";
+ this.lineWidth = 2.0 * mag * (style.weight || CanvasText.style.weight);
+ this.strokeStyle = style.color || CanvasText.style.color;
+
+ for (i = 0; i < lexemes.length; i++) {
+ c = lexemes[i];
+ if (c.width == -1) {
+ x = 0;
+ y = style.size * 1.4;
+ continue;
+ }
+
+ var points = c.points,
+ width = c.width;
+
+ if (c.diacritic) {
+ var dia = CanvasText.diacritics[c.diacritic],
+ character = CanvasText.letter(c.letter);
+
+ CanvasText.drawPoints(this, dia.points, x, y - (c.letter.toUpperCase() == c.letter ? 3 : 0), mag, offset);
+ points = character.points;
+ width = character.width;
+ }
+
+ CanvasText.drawPoints(this, points, x, y, mag, offset);
+
+ if (CanvasText.debug) {
+ this.save();
+ this.lineJoin = "miter";
+ this.lineWidth = 0.5;
+ this.strokeStyle = (style.boundingBoxColor || CanvasText.style.boundingBoxColor);
+ this.strokeRect(x+offset.x, y+offset.y, width*mag, -style.size);
+
+ this.fillStyle = (style.originPointColor || CanvasText.style.originPointColor);
+ this.beginPath();
+ this.arc(0, 0, 1.5, 0, Math.PI*2, true);
+ this.fill();
+ this.closePath();
+ this.restore();
+ }
+
+ x += width*mag*(style.tracking || CanvasText.style.tracking);
+ }
+ this.restore();
+ return total;
+ }
+};
+
+/** The text functions are bound to the CanvasRenderingContext2D prototype */
+CanvasText.proto = window.CanvasRenderingContext2D ? window.CanvasRenderingContext2D.prototype : document.createElement('canvas').getContext('2d').__proto__;
+
+if (CanvasText.proto) {
+ CanvasText.proto.drawText = CanvasText.draw;
+ CanvasText.proto.measure = CanvasText.measure;
+ CanvasText.proto.getTextBounds = CanvasText.getDimensions;
+ CanvasText.proto.fontAscent = CanvasText.ascent;
+ CanvasText.proto.fontDescent = CanvasText.descent;
+}
\ No newline at end of file
diff --git a/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/excanvas.js b/addons/web_graph/static/lib/flotr2/lib/excanvas.js
similarity index 52%
rename from addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/excanvas.js
rename to addons/web_graph/static/lib/flotr2/lib/excanvas.js
index 367764b4d6a..005bab1186f 100644
--- a/addons/web_graph/static/lib/dhtmlxGraph/codebase/thirdparty/excanvas/excanvas.js
+++ b/addons/web_graph/static/lib/flotr2/lib/excanvas.js
@@ -15,7 +15,7 @@
// Known Issues:
//
-// * Patterns are not implemented.
+// * Patterns only support repeat.
// * Radial gradient are not implemented. The VML version of these look very
// different from the canvas one.
// * Clipping paths are not implemented.
@@ -48,6 +48,8 @@ if (!document.createElement('canvas').getContext) {
var Z = 10;
var Z2 = Z / 2;
+ var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
+
/**
* This funtion is assigned to the elements as element.getContext().
* @this {HTMLElement}
@@ -83,41 +85,43 @@ if (!document.createElement('canvas').getContext) {
};
}
+ function encodeHtmlAttribute(s) {
+ return String(s).replace(/&/g, '&').replace(/"/g, '"');
+ }
+
+ function addNamespace(doc, prefix, urn) {
+ if (!doc.namespaces[prefix]) {
+ doc.namespaces.add(prefix, urn, '#default#VML');
+ }
+ }
+
+ function addNamespacesAndStylesheet(doc) {
+ addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
+ addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
+
+ // Setup default CSS. Only add one style sheet per document
+ if (!doc.styleSheets['ex_canvas_']) {
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = 'ex_canvas_';
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+ // default size is 300x150 in Gecko and Opera
+ 'text-align:left;width:300px;height:150px}';
+ }
+ }
+
+ // Add namespaces and stylesheet at startup.
+ addNamespacesAndStylesheet(document);
+
var G_vmlCanvasManager_ = {
init: function(opt_doc) {
- if (/MSIE/.test(navigator.userAgent) && !window.opera) {
- var doc = opt_doc || document;
- // Create a dummy element so that IE will allow canvas elements to be
- // recognized.
- doc.createElement('canvas');
- doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
- }
+ var doc = opt_doc || document;
+ // Create a dummy element so that IE will allow canvas elements to be
+ // recognized.
+ doc.createElement('canvas');
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
},
init_: function(doc) {
- // create xmlns
- if (!doc.namespaces['g_vml_']) {
- doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
- '#default#VML');
-
- }
- if (!doc.namespaces['g_o_']) {
- doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
- '#default#VML');
- }
-
- // Setup default CSS. Only add one style sheet per document
- if (!doc.styleSheets['ex_canvas_']) {
- var ss = doc.createStyleSheet();
- ss.owningElement.id = 'ex_canvas_';
- ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
- // default size is 300x150 in Gecko and Opera
- 'text-align:left;width:300px;height:150px}' +
- 'g_vml_\\:*{behavior:url(#default#VML)}' +
- 'g_o_\\:*{behavior:url(#default#VML)}';
-
- }
-
// find all canvas elements
var els = doc.getElementsByTagName('canvas');
for (var i = 0; i < els.length; i++) {
@@ -135,9 +139,11 @@ if (!document.createElement('canvas').getContext) {
*/
initElement: function(el) {
if (!el.getContext) {
-
el.getContext = getContext;
+ // Add namespaces and stylesheet to document of the element.
+ addNamespacesAndStylesheet(el.ownerDocument);
+
// Remove fallback content. There is no way to hide text nodes so we
// just remove all childNodes. We could hide all elements and remove
// text nodes but who really cares about the fallback content.
@@ -173,12 +179,19 @@ if (!document.createElement('canvas').getContext) {
switch (e.propertyName) {
case 'width':
- el.style.width = el.attributes.width.nodeValue + 'px';
el.getContext().clearRect();
+ el.style.width = el.attributes.width.nodeValue + 'px';
+ // In IE8 this does not trigger onresize.
+ if (el.firstChild) {
+ el.firstChild.style.width = el.clientWidth + 'px';
+ }
break;
case 'height':
- el.style.height = el.attributes.height.nodeValue + 'px';
el.getContext().clearRect();
+ el.style.height = el.attributes.height.nodeValue + 'px';
+ if (el.firstChild) {
+ el.firstChild.style.height = el.clientHeight + 'px';
+ }
break;
}
}
@@ -194,10 +207,10 @@ if (!document.createElement('canvas').getContext) {
G_vmlCanvasManager_.init();
// precompute "00" to "FF"
- var dec2hex = [];
+ var decToHex = [];
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
- dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+ decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
}
}
@@ -238,54 +251,326 @@ if (!document.createElement('canvas').getContext) {
o2.shadowOffsetY = o1.shadowOffsetY;
o2.strokeStyle = o1.strokeStyle;
o2.globalAlpha = o1.globalAlpha;
+ o2.font = o1.font;
+ o2.textAlign = o1.textAlign;
+ o2.textBaseline = o1.textBaseline;
o2.arcScaleX_ = o1.arcScaleX_;
o2.arcScaleY_ = o1.arcScaleY_;
o2.lineScale_ = o1.lineScale_;
}
+ var colorData = {
+ aliceblue: '#F0F8FF',
+ antiquewhite: '#FAEBD7',
+ aquamarine: '#7FFFD4',
+ azure: '#F0FFFF',
+ beige: '#F5F5DC',
+ bisque: '#FFE4C4',
+ black: '#000000',
+ blanchedalmond: '#FFEBCD',
+ blueviolet: '#8A2BE2',
+ brown: '#A52A2A',
+ burlywood: '#DEB887',
+ cadetblue: '#5F9EA0',
+ chartreuse: '#7FFF00',
+ chocolate: '#D2691E',
+ coral: '#FF7F50',
+ cornflowerblue: '#6495ED',
+ cornsilk: '#FFF8DC',
+ crimson: '#DC143C',
+ cyan: '#00FFFF',
+ darkblue: '#00008B',
+ darkcyan: '#008B8B',
+ darkgoldenrod: '#B8860B',
+ darkgray: '#A9A9A9',
+ darkgreen: '#006400',
+ darkgrey: '#A9A9A9',
+ darkkhaki: '#BDB76B',
+ darkmagenta: '#8B008B',
+ darkolivegreen: '#556B2F',
+ darkorange: '#FF8C00',
+ darkorchid: '#9932CC',
+ darkred: '#8B0000',
+ darksalmon: '#E9967A',
+ darkseagreen: '#8FBC8F',
+ darkslateblue: '#483D8B',
+ darkslategray: '#2F4F4F',
+ darkslategrey: '#2F4F4F',
+ darkturquoise: '#00CED1',
+ darkviolet: '#9400D3',
+ deeppink: '#FF1493',
+ deepskyblue: '#00BFFF',
+ dimgray: '#696969',
+ dimgrey: '#696969',
+ dodgerblue: '#1E90FF',
+ firebrick: '#B22222',
+ floralwhite: '#FFFAF0',
+ forestgreen: '#228B22',
+ gainsboro: '#DCDCDC',
+ ghostwhite: '#F8F8FF',
+ gold: '#FFD700',
+ goldenrod: '#DAA520',
+ grey: '#808080',
+ greenyellow: '#ADFF2F',
+ honeydew: '#F0FFF0',
+ hotpink: '#FF69B4',
+ indianred: '#CD5C5C',
+ indigo: '#4B0082',
+ ivory: '#FFFFF0',
+ khaki: '#F0E68C',
+ lavender: '#E6E6FA',
+ lavenderblush: '#FFF0F5',
+ lawngreen: '#7CFC00',
+ lemonchiffon: '#FFFACD',
+ lightblue: '#ADD8E6',
+ lightcoral: '#F08080',
+ lightcyan: '#E0FFFF',
+ lightgoldenrodyellow: '#FAFAD2',
+ lightgreen: '#90EE90',
+ lightgrey: '#D3D3D3',
+ lightpink: '#FFB6C1',
+ lightsalmon: '#FFA07A',
+ lightseagreen: '#20B2AA',
+ lightskyblue: '#87CEFA',
+ lightslategray: '#778899',
+ lightslategrey: '#778899',
+ lightsteelblue: '#B0C4DE',
+ lightyellow: '#FFFFE0',
+ limegreen: '#32CD32',
+ linen: '#FAF0E6',
+ magenta: '#FF00FF',
+ mediumaquamarine: '#66CDAA',
+ mediumblue: '#0000CD',
+ mediumorchid: '#BA55D3',
+ mediumpurple: '#9370DB',
+ mediumseagreen: '#3CB371',
+ mediumslateblue: '#7B68EE',
+ mediumspringgreen: '#00FA9A',
+ mediumturquoise: '#48D1CC',
+ mediumvioletred: '#C71585',
+ midnightblue: '#191970',
+ mintcream: '#F5FFFA',
+ mistyrose: '#FFE4E1',
+ moccasin: '#FFE4B5',
+ navajowhite: '#FFDEAD',
+ oldlace: '#FDF5E6',
+ olivedrab: '#6B8E23',
+ orange: '#FFA500',
+ orangered: '#FF4500',
+ orchid: '#DA70D6',
+ palegoldenrod: '#EEE8AA',
+ palegreen: '#98FB98',
+ paleturquoise: '#AFEEEE',
+ palevioletred: '#DB7093',
+ papayawhip: '#FFEFD5',
+ peachpuff: '#FFDAB9',
+ peru: '#CD853F',
+ pink: '#FFC0CB',
+ plum: '#DDA0DD',
+ powderblue: '#B0E0E6',
+ rosybrown: '#BC8F8F',
+ royalblue: '#4169E1',
+ saddlebrown: '#8B4513',
+ salmon: '#FA8072',
+ sandybrown: '#F4A460',
+ seagreen: '#2E8B57',
+ seashell: '#FFF5EE',
+ sienna: '#A0522D',
+ skyblue: '#87CEEB',
+ slateblue: '#6A5ACD',
+ slategray: '#708090',
+ slategrey: '#708090',
+ snow: '#FFFAFA',
+ springgreen: '#00FF7F',
+ steelblue: '#4682B4',
+ tan: '#D2B48C',
+ thistle: '#D8BFD8',
+ tomato: '#FF6347',
+ turquoise: '#40E0D0',
+ violet: '#EE82EE',
+ wheat: '#F5DEB3',
+ whitesmoke: '#F5F5F5',
+ yellowgreen: '#9ACD32'
+ };
+
+
+ function getRgbHslContent(styleString) {
+ var start = styleString.indexOf('(', 3);
+ var end = styleString.indexOf(')', start + 1);
+ var parts = styleString.substring(start + 1, end).split(',');
+ // add alpha if needed
+ if (parts.length != 4 || styleString.charAt(3) != 'a') {
+ parts[3] = 1;
+ }
+ return parts;
+ }
+
+ function percent(s) {
+ return parseFloat(s) / 100;
+ }
+
+ function clamp(v, min, max) {
+ return Math.min(max, Math.max(min, v));
+ }
+
+ function hslToRgb(parts){
+ var r, g, b, h, s, l;
+ h = parseFloat(parts[0]) / 360 % 360;
+ if (h < 0)
+ h++;
+ s = clamp(percent(parts[1]), 0, 1);
+ l = clamp(percent(parts[2]), 0, 1);
+ if (s == 0) {
+ r = g = b = l; // achromatic
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hueToRgb(p, q, h + 1 / 3);
+ g = hueToRgb(p, q, h);
+ b = hueToRgb(p, q, h - 1 / 3);
+ }
+
+ return '#' + decToHex[Math.floor(r * 255)] +
+ decToHex[Math.floor(g * 255)] +
+ decToHex[Math.floor(b * 255)];
+ }
+
+ function hueToRgb(m1, m2, h) {
+ if (h < 0)
+ h++;
+ if (h > 1)
+ h--;
+
+ if (6 * h < 1)
+ return m1 + (m2 - m1) * 6 * h;
+ else if (2 * h < 1)
+ return m2;
+ else if (3 * h < 2)
+ return m1 + (m2 - m1) * (2 / 3 - h) * 6;
+ else
+ return m1;
+ }
+
+ var processStyleCache = {};
+
function processStyle(styleString) {
+ if (styleString in processStyleCache) {
+ return processStyleCache[styleString];
+ }
+
var str, alpha = 1;
styleString = String(styleString);
- if (styleString.substring(0, 3) == 'rgb') {
- var start = styleString.indexOf('(', 3);
- var end = styleString.indexOf(')', start + 1);
- var guts = styleString.substring(start + 1, end).split(',');
-
- str = '#';
- for (var i = 0; i < 3; i++) {
- str += dec2hex[Number(guts[i])];
- }
-
- if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
- alpha = guts[3];
- }
- } else {
+ if (styleString.charAt(0) == '#') {
str = styleString;
+ } else if (/^rgb/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ var str = '#', n;
+ for (var i = 0; i < 3; i++) {
+ if (parts[i].indexOf('%') != -1) {
+ n = Math.floor(percent(parts[i]) * 255);
+ } else {
+ n = +parts[i];
+ }
+ str += decToHex[clamp(n, 0, 255)];
+ }
+ alpha = +parts[3];
+ } else if (/^hsl/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ str = hslToRgb(parts);
+ alpha = parts[3];
+ } else {
+ str = colorData[styleString] || styleString;
}
-
- return {color: str, alpha: alpha};
+ return processStyleCache[styleString] = {color: str, alpha: alpha};
}
- function processLineCap(lineCap) {
- switch (lineCap) {
- case 'butt':
- return 'flat';
- case 'round':
- return 'round';
- case 'square':
- default:
- return 'square';
+ var DEFAULT_STYLE = {
+ style: 'normal',
+ variant: 'normal',
+ weight: 'normal',
+ size: 10,
+ family: 'sans-serif'
+ };
+
+ // Internal text style cache
+ var fontStyleCache = {};
+
+ function processFontStyle(styleString) {
+ if (fontStyleCache[styleString]) {
+ return fontStyleCache[styleString];
}
+
+ var el = document.createElement('div');
+ var style = el.style;
+ try {
+ style.font = styleString;
+ } catch (ex) {
+ // Ignore failures to set to invalid font.
+ }
+
+ return fontStyleCache[styleString] = {
+ style: style.fontStyle || DEFAULT_STYLE.style,
+ variant: style.fontVariant || DEFAULT_STYLE.variant,
+ weight: style.fontWeight || DEFAULT_STYLE.weight,
+ size: style.fontSize || DEFAULT_STYLE.size,
+ family: style.fontFamily || DEFAULT_STYLE.family
+ };
+ }
+
+ function getComputedStyle(style, element) {
+ var computedStyle = {};
+
+ for (var p in style) {
+ computedStyle[p] = style[p];
+ }
+
+ // Compute the size
+ var canvasFontSize = parseFloat(element.currentStyle.fontSize),
+ fontSize = parseFloat(style.size);
+
+ if (typeof style.size == 'number') {
+ computedStyle.size = style.size;
+ } else if (style.size.indexOf('px') != -1) {
+ computedStyle.size = fontSize;
+ } else if (style.size.indexOf('em') != -1) {
+ computedStyle.size = canvasFontSize * fontSize;
+ } else if(style.size.indexOf('%') != -1) {
+ computedStyle.size = (canvasFontSize / 100) * fontSize;
+ } else if (style.size.indexOf('pt') != -1) {
+ computedStyle.size = fontSize / .75;
+ } else {
+ computedStyle.size = canvasFontSize;
+ }
+
+ // Different scaling between normal text and VML text. This was found using
+ // trial and error to get the same size as non VML text.
+ //computedStyle.size *= 0.981;
+
+ return computedStyle;
+ }
+
+ function buildStyle(style) {
+ return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
+ style.size + 'px ' + style.family;
+ }
+
+ var lineCapMap = {
+ 'butt': 'flat',
+ 'round': 'round'
+ };
+
+ function processLineCap(lineCap) {
+ return lineCapMap[lineCap] || 'square';
}
/**
* This class implements CanvasRenderingContext2D interface as described by
* the WHATWG.
- * @param {HTMLElement} surfaceElement The element that the 2D context should
+ * @param {HTMLElement} canvasElement The element that the 2D context should
* be associated with
*/
- function CanvasRenderingContext2D_(surfaceElement) {
+ function CanvasRenderingContext2D_(canvasElement) {
this.m_ = createMatrixIdentity();
this.mStack_ = [];
@@ -301,14 +586,22 @@ if (!document.createElement('canvas').getContext) {
this.lineCap = 'butt';
this.miterLimit = Z * 1;
this.globalAlpha = 1;
- this.canvas = surfaceElement;
+ this.font = '10px sans-serif';
+ this.textAlign = 'left';
+ this.textBaseline = 'alphabetic';
+ this.canvas = canvasElement;
- var el = surfaceElement.ownerDocument.createElement('div');
- el.style.width = surfaceElement.clientWidth + 'px';
- el.style.height = surfaceElement.clientHeight + 'px';
- el.style.overflow = 'hidden';
- el.style.position = 'absolute';
- surfaceElement.appendChild(el);
+ var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
+ canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
+ var el = canvasElement.ownerDocument.createElement('div');
+ el.style.cssText = cssText;
+ canvasElement.appendChild(el);
+
+ var overlayEl = el.cloneNode(false);
+ // Use a non transparent background.
+ overlayEl.style.backgroundColor = 'red';
+ overlayEl.style.filter = 'alpha(opacity=0)';
+ canvasElement.appendChild(overlayEl);
this.element_ = el;
this.arcScaleX_ = 1;
@@ -318,6 +611,10 @@ if (!document.createElement('canvas').getContext) {
var contextPrototype = CanvasRenderingContext2D_.prototype;
contextPrototype.clearRect = function() {
+ if (this.textMeasureEl_) {
+ this.textMeasureEl_.removeNode(true);
+ this.textMeasureEl_ = null;
+ }
this.element_.innerHTML = '';
};
@@ -328,14 +625,14 @@ if (!document.createElement('canvas').getContext) {
};
contextPrototype.moveTo = function(aX, aY) {
- var p = this.getCoords_(aX, aY);
+ var p = getCoords(this, aX, aY);
this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
this.currentX_ = p.x;
this.currentY_ = p.y;
};
contextPrototype.lineTo = function(aX, aY) {
- var p = this.getCoords_(aX, aY);
+ var p = getCoords(this, aX, aY);
this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
this.currentX_ = p.x;
@@ -345,9 +642,9 @@ if (!document.createElement('canvas').getContext) {
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY) {
- var p = this.getCoords_(aX, aY);
- var cp1 = this.getCoords_(aCP1x, aCP1y);
- var cp2 = this.getCoords_(aCP2x, aCP2y);
+ var p = getCoords(this, aX, aY);
+ var cp1 = getCoords(this, aCP1x, aCP1y);
+ var cp2 = getCoords(this, aCP2x, aCP2y);
bezierCurveTo(this, cp1, cp2, p);
};
@@ -370,8 +667,8 @@ if (!document.createElement('canvas').getContext) {
// the following is lifted almost directly from
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
- var cp = this.getCoords_(aCPx, aCPy);
- var p = this.getCoords_(aX, aY);
+ var cp = getCoords(this, aCPx, aCPy);
+ var p = getCoords(this, aX, aY);
var cp1 = {
x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
@@ -397,14 +694,19 @@ if (!document.createElement('canvas').getContext) {
var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
// IE won't render arches drawn counter clockwise if xStart == xEnd.
- if (xStart == xEnd && !aClockwise) {
+ if ((abs(xStart - xEnd) < 10e-8) && !aClockwise) {
xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
// that can be represented in binary
}
+ // IE won't render arches drawn clockwise if yStart is very close to yEnd.
+ if ((abs(yStart - yEnd) < 10e-8) && aClockwise) {
+ yStart -= 0.125; // Offset yStart by 1/80 of a pixel. Use something
+ // that can be represented in binary
+ }
- var p = this.getCoords_(aX, aY);
- var pStart = this.getCoords_(xStart, yStart);
- var pEnd = this.getCoords_(xEnd, yEnd);
+ var p = getCoords(this, aX, aY);
+ var pStart = getCoords(this, xStart, yStart);
+ var pEnd = getCoords(this, xEnd, yEnd);
this.currentPath_.push({type: arcType,
x: p.x,
@@ -518,7 +820,7 @@ if (!document.createElement('canvas').getContext) {
throw Error('Invalid number of arguments');
}
- var d = this.getCoords_(dx, dy);
+ var d = getCoords(this, dx, dy);
var w2 = sw / 2;
var h2 = sh / 2;
@@ -539,7 +841,8 @@ if (!document.createElement('canvas').getContext) {
// The following check doesn't account for skews (which don't exist
// in the canvas spec (yet) anyway.
- if (this.m_[0][0] != 1 || this.m_[0][1]) {
+ if (this.m_[0][0] != 1 || this.m_[0][1] ||
+ this.m_[1][1] != 1 || this.m_[1][0]) {
var filter = [];
// Note the 12/21 reversal
@@ -553,16 +856,17 @@ if (!document.createElement('canvas').getContext) {
// Bounding box calculation (need to minimize displayed area so that
// filters don't waste time on unused pixels.
var max = d;
- var c2 = this.getCoords_(dx + dw, dy);
- var c3 = this.getCoords_(dx, dy + dh);
- var c4 = this.getCoords_(dx + dw, dy + dh);
+ var c2 = getCoords(this, dx + dw, dy);
+ var c3 = getCoords(this, dx, dy + dh);
+ var c4 = getCoords(this, dx + dw, dy + dh);
max.x = m.max(max.x, c2.x, c3.x, c4.x);
max.y = m.max(max.y, c2.y, c3.y, c4.y);
vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
- filter.join(''), ", sizingmethod='clip');")
+ filter.join(''), ", sizingmethod='clip');");
+
} else {
vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
}
@@ -570,7 +874,7 @@ if (!document.createElement('canvas').getContext) {
vmlStr.push(' ">' ,
' ',
'');
- this.element_.insertAdjacentHTML('BeforeEnd',
- vmlStr.join(''));
+ this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
};
contextPrototype.stroke = function(aFill) {
var lineStr = [];
var lineOpen = false;
- var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
- var color = a.color;
- var opacity = a.alpha * this.globalAlpha;
var W = 10;
var H = 10;
@@ -595,7 +895,8 @@ if (!document.createElement('canvas').getContext) {
lineStr.push('');
if (!aFill) {
- var lineWidth = this.lineScale_ * this.lineWidth;
+ appendStroke(this, lineStr);
+ } else {
+ appendFill(this, lineStr, min, max);
+ }
- // VML cannot correctly render a line if the width is less than 1px.
- // In that case, we dilute the color to make the line look thinner.
- if (lineWidth < 1) {
- opacity *= lineWidth;
- }
+ lineStr.push(' ');
- lineStr.push(
- ' '
- );
- } else if (typeof this.fillStyle == 'object') {
- var fillStyle = this.fillStyle;
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ function appendStroke(ctx, lineStr) {
+ var a = processStyle(ctx.strokeStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ var lineWidth = ctx.lineScale_ * ctx.lineWidth;
+
+ // VML cannot correctly render a line if the width is less than 1px.
+ // In that case, we dilute the color to make the line look thinner.
+ if (lineWidth < 1) {
+ opacity *= lineWidth;
+ }
+
+ lineStr.push(
+ ' '
+ );
+ }
+
+ function appendFill(ctx, lineStr, min, max) {
+ var fillStyle = ctx.fillStyle;
+ var arcScaleX = ctx.arcScaleX_;
+ var arcScaleY = ctx.arcScaleY_;
+ var width = max.x - min.x;
+ var height = max.y - min.y;
+ if (fillStyle instanceof CanvasGradient_) {
+ // TODO: Gradients transformed with the transformation matrix.
var angle = 0;
var focus = {x: 0, y: 0};
@@ -689,12 +1012,12 @@ if (!document.createElement('canvas').getContext) {
var expansion = 1;
if (fillStyle.type_ == 'gradient') {
- var x0 = fillStyle.x0_ / this.arcScaleX_;
- var y0 = fillStyle.y0_ / this.arcScaleY_;
- var x1 = fillStyle.x1_ / this.arcScaleX_;
- var y1 = fillStyle.y1_ / this.arcScaleY_;
- var p0 = this.getCoords_(x0, y0);
- var p1 = this.getCoords_(x1, y1);
+ var x0 = fillStyle.x0_ / arcScaleX;
+ var y0 = fillStyle.y0_ / arcScaleY;
+ var x1 = fillStyle.x1_ / arcScaleX;
+ var y1 = fillStyle.y1_ / arcScaleY;
+ var p0 = getCoords(ctx, x0, y0);
+ var p1 = getCoords(ctx, x1, y1);
var dx = p1.x - p0.x;
var dy = p1.y - p0.y;
angle = Math.atan2(dx, dy) * 180 / Math.PI;
@@ -710,16 +1033,14 @@ if (!document.createElement('canvas').getContext) {
angle = 0;
}
} else {
- var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
- var width = max.x - min.x;
- var height = max.y - min.y;
+ var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
focus = {
x: (p0.x - min.x) / width,
y: (p0.y - min.y) / height
};
- width /= this.arcScaleX_ * Z;
- height /= this.arcScaleY_ * Z;
+ width /= arcScaleX * Z;
+ height /= arcScaleY * Z;
var dimension = m.max(width, height);
shift = 2 * fillStyle.r0_ / dimension;
expansion = 2 * fillStyle.r1_ / dimension - shift;
@@ -735,8 +1056,8 @@ if (!document.createElement('canvas').getContext) {
var length = stops.length;
var color1 = stops[0].color;
var color2 = stops[length - 1].color;
- var opacity1 = stops[0].alpha * this.globalAlpha;
- var opacity2 = stops[length - 1].alpha * this.globalAlpha;
+ var opacity1 = stops[0].alpha * ctx.globalAlpha;
+ var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
var colors = [];
for (var i = 0; i < length; i++) {
@@ -755,33 +1076,42 @@ if (!document.createElement('canvas').getContext) {
' g_o_:opacity2="', opacity1, '"',
' angle="', angle, '"',
' focusposition="', focus.x, ',', focus.y, '" />');
+ } else if (fillStyle instanceof CanvasPattern_) {
+ if (width && height) {
+ var deltaLeft = -min.x;
+ var deltaTop = -min.y;
+ lineStr.push(' ');
+ }
} else {
+ var a = processStyle(ctx.fillStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
lineStr.push(' ');
}
-
- lineStr.push('');
-
- this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
- };
+ }
contextPrototype.fill = function() {
this.stroke(true);
- }
+ };
contextPrototype.closePath = function() {
this.currentPath_.push({type: 'close'});
};
- /**
- * @private
- */
- contextPrototype.getCoords_ = function(aX, aY) {
- var m = this.m_;
+ function getCoords(ctx, aX, aY) {
+ var m = ctx.m_;
return {
x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
- }
+ };
};
contextPrototype.save = function() {
@@ -793,19 +1123,16 @@ if (!document.createElement('canvas').getContext) {
};
contextPrototype.restore = function() {
- copyState(this.aStack_.pop(), this);
- this.m_ = this.mStack_.pop();
+ if (this.aStack_.length) {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ }
};
function matrixIsFinite(m) {
- for (var j = 0; j < 3; j++) {
- for (var k = 0; k < 2; k++) {
- if (!isFinite(m[j][k]) || isNaN(m[j][k])) {
- return false;
- }
- }
- }
- return true;
+ return isFinite(m[0][0]) && isFinite(m[0][1]) &&
+ isFinite(m[1][0]) && isFinite(m[1][1]) &&
+ isFinite(m[2][0]) && isFinite(m[2][1]);
}
function setM(ctx, m, updateLineScale) {
@@ -879,6 +1206,124 @@ if (!document.createElement('canvas').getContext) {
setM(this, m, true);
};
+ /**
+ * The text drawing function.
+ * The maxWidth argument isn't taken in account, since no browser supports
+ * it yet.
+ */
+ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
+ var m = this.m_,
+ delta = 1000,
+ left = 0,
+ right = delta,
+ offset = {x: 0, y: 0},
+ lineStr = [];
+
+ var fontStyle = getComputedStyle(processFontStyle(this.font),
+ this.element_);
+
+ var fontStyleString = buildStyle(fontStyle);
+
+ var elementStyle = this.element_.currentStyle;
+ var textAlign = this.textAlign.toLowerCase();
+ switch (textAlign) {
+ case 'left':
+ case 'center':
+ case 'right':
+ break;
+ case 'end':
+ textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
+ break;
+ case 'start':
+ textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
+ break;
+ default:
+ textAlign = 'left';
+ }
+
+ // 1.75 is an arbitrary number, as there is no info about the text baseline
+ switch (this.textBaseline) {
+ case 'hanging':
+ case 'top':
+ offset.y = fontStyle.size / 1.75;
+ break;
+ case 'middle':
+ break;
+ default:
+ case null:
+ case 'alphabetic':
+ case 'ideographic':
+ case 'bottom':
+ offset.y = -fontStyle.size / 2.25;
+ break;
+ }
+
+ switch(textAlign) {
+ case 'right':
+ left = delta;
+ right = 0.05;
+ break;
+ case 'center':
+ left = right = delta / 2;
+ break;
+ }
+
+ var d = getCoords(this, x + offset.x, y + offset.y);
+
+ lineStr.push('');
+
+ if (stroke) {
+ appendStroke(this, lineStr);
+ } else {
+ // TODO: Fix the min and max params.
+ appendFill(this, lineStr, {x: -left, y: 0},
+ {x: right, y: fontStyle.size});
+ }
+
+ var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
+ m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
+
+ var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
+
+ lineStr.push(' ',
+ ' ',
+ ' ');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ contextPrototype.fillText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, false);
+ };
+
+ contextPrototype.strokeText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, true);
+ };
+
+ contextPrototype.measureText = function(text) {
+ if (!this.textMeasureEl_) {
+ var s = ' ';
+ this.element_.insertAdjacentHTML('beforeEnd', s);
+ this.textMeasureEl_ = this.element_.lastChild;
+ }
+ var doc = this.element_.ownerDocument;
+ this.textMeasureEl_.innerHTML = '';
+ this.textMeasureEl_.style.font = this.font;
+ // Don't use innerHTML or innerText because they allow markup/whitespace.
+ this.textMeasureEl_.appendChild(doc.createTextNode(text));
+ return {width: this.textMeasureEl_.offsetWidth};
+ };
+
/******** STUBS ********/
contextPrototype.clip = function() {
// TODO: Implement
@@ -888,8 +1333,8 @@ if (!document.createElement('canvas').getContext) {
// TODO: Implement
};
- contextPrototype.createPattern = function() {
- return new CanvasPattern_;
+ contextPrototype.createPattern = function(image, repetition) {
+ return new CanvasPattern_(image, repetition);
};
// Gradient / Pattern Stubs
@@ -911,14 +1356,70 @@ if (!document.createElement('canvas').getContext) {
alpha: aColor.alpha});
};
- function CanvasPattern_() {}
+ function CanvasPattern_(image, repetition) {
+ assertImageIsValid(image);
+ switch (repetition) {
+ case 'repeat':
+ case null:
+ case '':
+ this.repetition_ = 'repeat';
+ break
+ case 'repeat-x':
+ case 'repeat-y':
+ case 'no-repeat':
+ this.repetition_ = repetition;
+ break;
+ default:
+ throwException('SYNTAX_ERR');
+ }
+
+ this.src_ = image.src;
+ this.width_ = image.width;
+ this.height_ = image.height;
+ }
+
+ function throwException(s) {
+ throw new DOMException_(s);
+ }
+
+ function assertImageIsValid(img) {
+ if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
+ throwException('TYPE_MISMATCH_ERR');
+ }
+ if (img.readyState != 'complete') {
+ throwException('INVALID_STATE_ERR');
+ }
+ }
+
+ function DOMException_(s) {
+ this.code = this[s];
+ this.message = s +': DOM Exception ' + this.code;
+ }
+ var p = DOMException_.prototype = new Error;
+ p.INDEX_SIZE_ERR = 1;
+ p.DOMSTRING_SIZE_ERR = 2;
+ p.HIERARCHY_REQUEST_ERR = 3;
+ p.WRONG_DOCUMENT_ERR = 4;
+ p.INVALID_CHARACTER_ERR = 5;
+ p.NO_DATA_ALLOWED_ERR = 6;
+ p.NO_MODIFICATION_ALLOWED_ERR = 7;
+ p.NOT_FOUND_ERR = 8;
+ p.NOT_SUPPORTED_ERR = 9;
+ p.INUSE_ATTRIBUTE_ERR = 10;
+ p.INVALID_STATE_ERR = 11;
+ p.SYNTAX_ERR = 12;
+ p.INVALID_MODIFICATION_ERR = 13;
+ p.NAMESPACE_ERR = 14;
+ p.INVALID_ACCESS_ERR = 15;
+ p.VALIDATION_ERR = 16;
+ p.TYPE_MISMATCH_ERR = 17;
// set up externs
G_vmlCanvasManager = G_vmlCanvasManager_;
CanvasRenderingContext2D = CanvasRenderingContext2D_;
CanvasGradient = CanvasGradient_;
CanvasPattern = CanvasPattern_;
-
+ DOMException = DOMException_;
})();
} // if
diff --git a/addons/web_graph/static/lib/flotr2/lib/imagediff.js b/addons/web_graph/static/lib/flotr2/lib/imagediff.js
new file mode 100644
index 00000000000..ac1d2745018
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/imagediff.js
@@ -0,0 +1,343 @@
+/*! imagediff.js 1.0.2
+ * (c) 2011 Carl Sutherland, Humble Software Development
+ * imagediff.js is freely distributable under the MIT license.
+ * Thanks to Jacob Thornton for the node/amd integration bits.
+ * For details and documentation:
+ * https://github.com/HumbleSoftware/js-imagediff
+ */
+(function (name, definition) {
+ var root = this;
+ if (typeof module != 'undefined') {
+ module.exports = definition();
+ } else if (typeof define == 'function' && typeof define.amd == 'object') {
+ define(definition);
+ } else {
+ root[name] = definition(root, name);
+ }
+})('imagediff', function (root, name) {
+
+ var
+ TYPE_ARRAY = '[object Array]',
+ TYPE_CANVAS = '[object HTMLCanvasElement]',
+ TYPE_CONTEXT = '[object CanvasRenderingContext2D]',
+ TYPE_IMAGE = '[object HTMLImageElement]',
+
+ OBJECT = 'object',
+ UNDEFINED = 'undefined',
+
+ canvas = getCanvas(),
+ context = canvas.getContext('2d'),
+ previous = root[name],
+ imagediff, jasmine;
+
+ // Creation
+ function getCanvas (width, height) {
+ var
+ canvas = document.createElement('canvas');
+ if (width) canvas.width = width;
+ if (height) canvas.height = height;
+ return canvas;
+ }
+ function getImageData (width, height) {
+ canvas.width = width;
+ canvas.height = height;
+ context.clearRect(0, 0, width, height);
+ return context.createImageData(width, height);
+ }
+
+
+ // Type Checking
+ function isImage (object) {
+ return isType(object, TYPE_IMAGE);
+ }
+ function isCanvas (object) {
+ return isType(object, TYPE_CANVAS);
+ }
+ function isContext (object) {
+ return isType(object, TYPE_CONTEXT);
+ }
+ function isImageData (object) {
+ var
+ imageData = getImageData(1, 1);
+ isImageData = function (object) {
+ return (object && imageData.constructor === object.constructor ? true : false);
+ };
+ return isImageData(object);
+ }
+ function isImageType (object) {
+ return (
+ isImage(object) ||
+ isCanvas(object) ||
+ isContext(object) ||
+ isImageData(object)
+ );
+ }
+ function isType (object, type) {
+ return typeof (object) === 'object' && Object.prototype.toString.apply(object) === type;
+ }
+
+
+ // Type Conversion
+ function copyImageData (imageData) {
+ var
+ height = imageData.height,
+ width = imageData.width;
+ canvas.width = width;
+ canvas.height = height;
+ context.putImageData(imageData, 0, 0);
+ return context.getImageData(0, 0, width, height);
+ }
+ function toImageData (object) {
+ if (isImage(object)) { return toImageDataFromImage(object); }
+ if (isCanvas(object)) { return toImageDataFromCanvas(object); }
+ if (isContext(object)) { return toImageDataFromContext(object); }
+ if (isImageData(object)) { return object; }
+ }
+ function toImageDataFromImage (image) {
+ var
+ height = image.height,
+ width = image.width;
+ canvas.width = width;
+ canvas.height = height;
+ context.clearRect(0, 0, width, height);
+ context.drawImage(image, 0, 0);
+ return context.getImageData(0, 0, width, height);
+ }
+ function toImageDataFromCanvas (canvas) {
+ var
+ height = canvas.height,
+ width = canvas.width,
+ context = canvas.getContext('2d');
+ return context.getImageData(0, 0, width, height);
+ }
+ function toImageDataFromContext (context) {
+ var
+ canvas = context.canvas,
+ height = canvas.height,
+ width = canvas.width;
+ return context.getImageData(0, 0, width, height);
+ }
+ function toCanvas (object) {
+ var
+ data = toImageData(object),
+ canvas = getCanvas(data.width, data.height),
+ context = canvas.getContext('2d');
+
+ context.putImageData(data, 0, 0);
+ return canvas;
+ }
+
+
+ // ImageData Equality Operators
+ function equalWidth (a, b) {
+ return a.width === b.width;
+ }
+ function equalHeight (a, b) {
+ return a.height === b.height;
+ }
+ function equalDimensions (a, b) {
+ return equalHeight(a, b) && equalWidth(a, b);
+ }
+ function equal (a, b, tolerance) {
+
+ var
+ aData = a.data,
+ bData = b.data,
+ length = aData.length,
+ tolerance = tolerance || 0,
+ i;
+
+ if (!equalDimensions(a, b)) return false;
+ for (i = length; i--;) if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) return false;
+
+ return true;
+ }
+
+
+ // Diff
+ function diff (a, b) {
+ return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b);
+ }
+ function diffEqual (a, b) {
+
+ var
+ height = a.height,
+ width = a.width,
+ c = getImageData(width, height), // c = a - b
+ aData = a.data,
+ bData = b.data,
+ cData = c.data,
+ length = cData.length,
+ row, column,
+ i, j, k, v;
+
+ for (i = 0; i < length; i += 4) {
+ cData[i] = Math.abs(aData[i] - bData[i]);
+ cData[i+1] = Math.abs(aData[i+1] - bData[i+1]);
+ cData[i+2] = Math.abs(aData[i+2] - bData[i+2]);
+ cData[i+3] = Math.abs(255 - aData[i+3] - bData[i+3]);
+ }
+
+ return c;
+ }
+ function diffUnequal (a, b) {
+
+ var
+ height = Math.max(a.height, b.height),
+ width = Math.max(a.width, b.width),
+ c = getImageData(width, height), // c = a - b
+ aData = a.data,
+ bData = b.data,
+ cData = c.data,
+ rowOffset,
+ columnOffset,
+ row, column,
+ i, j, k, v;
+
+
+ for (i = cData.length - 1; i > 0; i = i - 4) {
+ cData[i] = 255;
+ }
+
+ // Add First Image
+ offsets(a);
+ for (row = a.height; row--;){
+ for (column = a.width; column--;) {
+ i = 4 * ((row + rowOffset) * width + (column + columnOffset));
+ j = 4 * (row * a.width + column);
+ cData[i+0] = aData[j+0]; // r
+ cData[i+1] = aData[j+1]; // g
+ cData[i+2] = aData[j+2]; // b
+ // cData[i+3] = aData[j+3]; // a
+ }
+ }
+
+ // Subtract Second Image
+ offsets(b);
+ for (row = b.height; row--;){
+ for (column = b.width; column--;) {
+ i = 4 * ((row + rowOffset) * width + (column + columnOffset));
+ j = 4 * (row * b.width + column);
+ cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r
+ cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g
+ cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b
+ }
+ }
+
+ // Helpers
+ function offsets (imageData) {
+ rowOffset = Math.floor((height - imageData.height) / 2);
+ columnOffset = Math.floor((width - imageData.width) / 2);
+ }
+
+ return c;
+ }
+
+
+ // Validation
+ function checkType () {
+ var i;
+ for (i = 0; i < arguments.length; i++) {
+ if (!isImageType(arguments[i])) {
+ throw {
+ name : 'ImageTypeError',
+ message : 'Submitted object was not an image.'
+ };
+ }
+ }
+ }
+
+
+ // Jasmine Matchers
+ function get (element, content) {
+ element = document.createElement(element);
+ if (element && content) {
+ element.innerHTML = content;
+ }
+ return element;
+ }
+ jasmine = {
+
+ toBeImageData : function () {
+ return imagediff.isImageData(this.actual);
+ },
+
+ toImageDiffEqual : function (expected, tolerance) {
+
+ this.message = function() {
+
+ var
+ div = get('div'),
+ a = get('div', 'Actual:
'),
+ b = get('div', 'Expected:
'),
+ c = get('div', 'Diff:
'),
+ diff = imagediff.diff(this.actual, expected),
+ canvas = getCanvas(),
+ context;
+
+ canvas.height = diff.height;
+ canvas.width = diff.width;
+
+ context = canvas.getContext('2d');
+ context.putImageData(diff, 0, 0);
+
+ a.appendChild(toCanvas(this.actual));
+ b.appendChild(toCanvas(expected));
+ c.appendChild(canvas);
+
+ div.appendChild(a);
+ div.appendChild(b);
+ div.appendChild(c);
+
+ return [
+ div,
+ "Expected not to be equal."
+ ];
+ };
+
+ return imagediff.equal(this.actual, expected, tolerance);
+ }
+ };
+
+ // Definition
+ imagediff = {
+
+ createCanvas : getCanvas,
+ createImageData : getImageData,
+
+ isImage : isImage,
+ isCanvas : isCanvas,
+ isContext : isContext,
+ isImageData : isImageData,
+ isImageType : isImageType,
+
+ toImageData : function (object) {
+ checkType(object);
+ if (isImageData(object)) { return copyImageData(object); }
+ return toImageData(object);
+ },
+
+ equal : function (a, b, tolerance) {
+ checkType(a, b);
+ a = toImageData(a);
+ b = toImageData(b);
+ return equal(a, b, tolerance);
+ },
+ diff : function (a, b) {
+ checkType(a, b);
+ a = toImageData(a);
+ b = toImageData(b);
+ return diff(a, b);
+ },
+
+ jasmine : jasmine,
+
+ // Compatibility
+ noConflict : function () {
+ root[name] = previous;
+ return imagediff;
+ }
+ };
+
+ return imagediff;
+});
diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE b/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE
new file mode 100644
index 00000000000..7c435baaec8
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js
new file mode 100644
index 00000000000..73834010f60
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js
@@ -0,0 +1,190 @@
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
+ this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) { el.appendChild(child); }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var showPassed, showSkipped;
+
+ this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
+ this.createDom('div', { className: 'banner' },
+ this.createDom('div', { className: 'logo' },
+ this.createDom('span', { className: 'title' }, "Jasmine"),
+ this.createDom('span', { className: 'version' }, runner.env.versionString())),
+ this.createDom('div', { className: 'options' },
+ "Show ",
+ showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+ showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+ )
+ ),
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' },
+ this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+ this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+ this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+ );
+
+ this.document.body.appendChild(this.outerDiv);
+
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+ this.suiteDivs[suite.id] = suiteDiv;
+ var parentDiv = this.outerDiv;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.id];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+
+ this.startedAt = new Date();
+
+ var self = this;
+ showPassed.onclick = function(evt) {
+ if (showPassed.checked) {
+ self.outerDiv.className += ' show-passed';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+ }
+ };
+
+ showSkipped.onclick = function(evt) {
+ if (showSkipped.checked) {
+ self.outerDiv.className += ' show-skipped';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+ }
+ };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+ var results = runner.results();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ //do it twice for IE
+ this.runnerDiv.setAttribute("className", className);
+ var specs = runner.specs();
+ var specCount = 0;
+ for (var i = 0; i < specs.length; i++) {
+ if (this.specFilter(specs[i])) {
+ specCount++;
+ }
+ }
+ var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+ this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+ this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+ var results = suite.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount === 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+ if (this.logRunningSpecs) {
+ this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+ var results = spec.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(spec.getFullName()),
+ title: spec.getFullName()
+ }, spec.description));
+
+
+ var resultItems = results.getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ specDiv.appendChild(messagesDiv);
+ }
+
+ this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap.spec) {
+ return true;
+ }
+ return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css
new file mode 100644
index 00000000000..6583fe7c66d
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css
@@ -0,0 +1,166 @@
+body {
+ font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+}
+
+
+.jasmine_reporter a:visited, .jasmine_reporter a {
+ color: #303;
+}
+
+.jasmine_reporter a:hover, .jasmine_reporter a:active {
+ color: blue;
+}
+
+.run_spec {
+ float:right;
+ padding-right: 5px;
+ font-size: .8em;
+ text-decoration: none;
+}
+
+.jasmine_reporter {
+ margin: 0 5px;
+}
+
+.banner {
+ color: #303;
+ background-color: #fef;
+ padding: 5px;
+}
+
+.logo {
+ float: left;
+ font-size: 1.1em;
+ padding-left: 5px;
+}
+
+.logo .version {
+ font-size: .6em;
+ padding-left: 1em;
+}
+
+.runner.running {
+ background-color: yellow;
+}
+
+
+.options {
+ text-align: right;
+ font-size: .8em;
+}
+
+
+
+
+.suite {
+ border: 1px outset gray;
+ margin: 5px 0;
+ padding-left: 1em;
+}
+
+.suite .suite {
+ margin: 5px;
+}
+
+.suite.passed {
+ background-color: #dfd;
+}
+
+.suite.failed {
+ background-color: #fdd;
+}
+
+.spec {
+ margin: 5px;
+ padding-left: 1em;
+ clear: both;
+}
+
+.spec.failed, .spec.passed, .spec.skipped {
+ padding-bottom: 5px;
+ border: 1px solid gray;
+}
+
+.spec.failed {
+ background-color: #fbb;
+ border-color: red;
+}
+
+.spec.passed {
+ background-color: #bfb;
+ border-color: green;
+}
+
+.spec.skipped {
+ background-color: #bbb;
+}
+
+.messages {
+ border-left: 1px dashed gray;
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+.passed {
+ background-color: #cfc;
+ display: none;
+}
+
+.failed {
+ background-color: #fbb;
+}
+
+.skipped {
+ color: #777;
+ background-color: #eee;
+ display: none;
+}
+
+
+/*.resultMessage {*/
+ /*white-space: pre;*/
+/*}*/
+
+.resultMessage span.result {
+ display: block;
+ line-height: 2em;
+ color: black;
+}
+
+.resultMessage .mismatch {
+ color: black;
+}
+
+.stackTrace {
+ white-space: pre;
+ font-size: .8em;
+ margin-left: 10px;
+ max-height: 5em;
+ overflow: auto;
+ border: 1px inset red;
+ padding: 1em;
+ background: #eef;
+}
+
+.finished-at {
+ padding-left: 1em;
+ font-size: .6em;
+}
+
+.show-passed .passed,
+.show-skipped .skipped {
+ display: block;
+}
+
+
+#jasmine_content {
+ position:fixed;
+ right: 100%;
+}
+
+.runner {
+ border: 1px solid gray;
+ display: block;
+ margin: 5px 0;
+ padding: 2px 0 2px 10px;
+}
diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js
new file mode 100644
index 00000000000..c3d2dc7d2d3
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js
@@ -0,0 +1,2476 @@
+var isCommonJS = typeof window == "undefined";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use jasmine.undefined
instead of undefined
, since undefined
is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for (var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+ this.message = this.passed_ ? 'Passed.' : params.message;
+
+ var trace = (params.trace || new Error(this.message));
+ this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+ return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log
in production code.
+ */
+jasmine.log = function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @returns a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a disabled Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ function tryIt(f) {
+ try {
+ return f();
+ } catch(e) {
+ }
+ return null;
+ }
+
+ var xhr = tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ });
+
+ if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+ return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&')
+ .replace(//g, '>');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (!jasmine.version_) {
+ return "version unknown";
+ }
+
+ var version = this.version();
+ var versionString = version.major + "." + version.minor + "." + version.build;
+ if (version.release_candidate) {
+ versionString += ".rc" + version.release_candidate;
+ }
+ versionString += " revision " + version.revision;
+ return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch(e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ this.currentSuite = parentSuite;
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a instanceof jasmine.Matchers.Any) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.Any) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.topLevelSuites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+
+ if (isSuite) {
+ var children = suiteOrSpec.children();
+ for (var i = 0; i < children.length; i++) {
+ summary.children.push(this.summarize_(children[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length;
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ }
+
+ return {
+ result : result.result,
+ messages : summaryMessages
+ };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return jasmine.undefined;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toNotEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called.",
+ "Expected spy " + this.actual.identity + " not to have been called."
+ ];
+ };
+
+ return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to not have been called.",
+ "Expected spy " + this.actual.identity + " to have been called."
+ ];
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ if (this.actual.callCount === 0) {
+ // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
+ ];
+ } else {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
+ ];
+ }
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+ ];
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toNotContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+ if (!(precision === 0)) {
+ precision = precision || 2;
+ }
+ var multiplier = Math.pow(10, precision);
+ var actual = Math.round(this.actual * multiplier);
+ expected = Math.round(expected * multiplier);
+ return expected == actual;
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} expected
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ var not = this.isNot ? "not " : "";
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.matches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.toString = function() {
+ return '';
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+ this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+ this.subReporters_.push(reporter);
+};
+
+(function() {
+ var functionNames = [
+ "reportRunnerStarting",
+ "reportRunnerResults",
+ "reportSuiteResults",
+ "reportSpecStarting",
+ "reportSpecResults",
+ "log"
+ ];
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+ return function() {
+ for (var j = 0; j < this.subReporters_.length; j++) {
+ var subReporter = this.subReporters_[j];
+ if (subReporter[functionName]) {
+ subReporter[functionName].apply(subReporter, arguments);
+ }
+ }
+ };
+ })(functionName);
+ }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+ /**
+ * The total count of results
+ */
+ this.totalCount = 0;
+ /**
+ * Number of passed results
+ */
+ this.passedCount = 0;
+ /**
+ * Number of failed results
+ */
+ this.failedCount = 0;
+ /**
+ * Was this suite/spec skipped?
+ */
+ this.skipped = false;
+ /**
+ * @ignore
+ */
+ this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+ this.totalCount += result.totalCount;
+ this.passedCount += result.passedCount;
+ this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+ this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+ return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+ if (result.type != 'log') {
+ if (result.items_) {
+ this.rollupCounts(result);
+ } else {
+ this.totalCount++;
+ if (result.passed()) {
+ this.passedCount++;
+ } else {
+ this.failedCount++;
+ }
+ }
+ }
+ this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if everything below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+ return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+ this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+ if (this.ppNestLevel_ > 40) {
+ throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
+ }
+
+ this.ppNestLevel_++;
+ try {
+ if (value === jasmine.undefined) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === jasmine.getGlobal()) {
+ this.emitScalar('');
+ } else if (value instanceof jasmine.Matchers.Any) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (jasmine.isSpy(value)) {
+ this.emitScalar("spy on " + value.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('');
+ } else if (jasmine.isArray_(value) || typeof value == 'object') {
+ value.__Jasmine_been_here_before__ = true;
+ if (jasmine.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+ jasmine.PrettyPrinter.call(this);
+
+ this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+};
+jasmine.Queue = function(env) {
+ this.env = env;
+ this.blocks = [];
+ this.running = false;
+ this.index = 0;
+ this.offset = 0;
+ this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block) {
+ this.blocks.unshift(block);
+};
+
+jasmine.Queue.prototype.add = function(block) {
+ this.blocks.push(block);
+};
+
+jasmine.Queue.prototype.insertNext = function(block) {
+ this.blocks.splice((this.index + this.offset + 1), 0, block);
+ this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+ this.running = true;
+ this.onComplete = onComplete;
+ this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+ return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+ var self = this;
+ var goAgain = true;
+
+ while (goAgain) {
+ goAgain = false;
+
+ if (self.index < self.blocks.length && !this.abort) {
+ var calledSynchronously = true;
+ var completedSynchronously = false;
+
+ var onComplete = function () {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+ completedSynchronously = true;
+ return;
+ }
+
+ if (self.blocks[self.index].abort) {
+ self.abort = true;
+ }
+
+ self.offset = 0;
+ self.index++;
+
+ var now = new Date().getTime();
+ if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+ self.env.lastUpdate = now;
+ self.env.setTimeout(function() {
+ self.next_();
+ }, 0);
+ } else {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+ goAgain = true;
+ } else {
+ self.next_();
+ }
+ }
+ };
+ self.blocks[self.index].execute(onComplete);
+
+ calledSynchronously = false;
+ if (completedSynchronously) {
+ onComplete();
+ }
+
+ } else {
+ self.running = false;
+ if (self.onComplete) {
+ self.onComplete();
+ }
+ }
+ }
+};
+
+jasmine.Queue.prototype.results = function() {
+ var results = new jasmine.NestedResults();
+ for (var i = 0; i < this.blocks.length; i++) {
+ if (this.blocks[i].results) {
+ results.addResult(this.blocks[i].results());
+ }
+ }
+ return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+ var self = this;
+ self.env = env;
+ self.queue = new jasmine.Queue(env);
+ self.before_ = [];
+ self.after_ = [];
+ self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+ var self = this;
+ if (self.env.reporter.reportRunnerStarting) {
+ self.env.reporter.reportRunnerStarting(this);
+ }
+ self.queue.start(function () {
+ self.finishCallback();
+ });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+ this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+ this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.addSuite(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+ var suites = this.suites();
+ var specs = [];
+ for (var i = 0; i < suites.length; i++) {
+ specs = specs.concat(suites[i].specs());
+ }
+ return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+ var topLevelSuites = [];
+ for (var i = 0; i < this.suites_.length; i++) {
+ if (!this.suites_[i].parentSuite) {
+ topLevelSuites.push(this.suites_[i]);
+ }
+ }
+ return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+ return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+ if (!env) {
+ throw new Error('jasmine.Env() required');
+ }
+ if (!suite) {
+ throw new Error('jasmine.Suite() required');
+ }
+ var spec = this;
+ spec.id = env.nextSpecId ? env.nextSpecId() : null;
+ spec.env = env;
+ spec.suite = suite;
+ spec.description = description;
+ spec.queue = new jasmine.Queue(env);
+
+ spec.afterCallbacks = [];
+ spec.spies_ = [];
+
+ spec.results_ = new jasmine.NestedResults();
+ spec.results_.description = description;
+ spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+ return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+ return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log
in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+ return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ var latchFunction_ = null;
+ var optional_timeoutMessage_ = null;
+ var optional_timeout_ = null;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ switch (typeof arg) {
+ case 'function':
+ latchFunction_ = arg;
+ break;
+ case 'string':
+ optional_timeoutMessage_ = arg;
+ break;
+ case 'number':
+ optional_timeout_ = arg;
+ break;
+ }
+ }
+
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception',
+ trace: { stack: e.stack }
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this));
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+
+ this.env.reporter.reportSpecStarting(this);
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.children_ = [];
+ self.suites_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+ this.children_.push(suiteOrSpec);
+ if (suiteOrSpec instanceof jasmine.Suite) {
+ this.suites_.push(suiteOrSpec);
+ this.env.currentRunner().addSuite(suiteOrSpec);
+ } else {
+ this.specs_.push(suiteOrSpec);
+ }
+ this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+ return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ }
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout || env.defaultTimeoutInterval;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+ }
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = this.latchFunction.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+ var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+ this.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+
+ this.abort = true;
+ onComplete();
+ } else {
+ this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ var self = this;
+ this.env.setTimeout(function() {
+ self.execute(onComplete);
+ }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ }
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: jasmine.getGlobal().setTimeout,
+ clearTimeout: jasmine.getGlobal().clearTimeout,
+ setInterval: jasmine.getGlobal().setInterval,
+ clearInterval: jasmine.getGlobal().clearInterval
+ },
+
+ assertInstalled: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ isInstalled: function() {
+ return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+jasmine.version_= {
+ "major": 1,
+ "minor": 1,
+ "build": 0,
+ "revision": 1315677058
+};
diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..218f3b43713598fa5a3e78b57aceb909c33f46df
GIT binary patch
literal 905
zcmV;419tq0P)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF*
zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_0008u
zNkl3{fod28|PjmA)7fYg4w8-(2my9xtBGOs}K`n&t1VzxMO^X)M
zrW+Ln1udc?q6TP)z5gAjt)P&D!M$+HJK#x<`xnD030zwD?KrxxY!2tlA
zGc-58?0D7SsT)7Km=v+tNVNUk`?s@;^OxCF)y6P}_mL;~7;S<@b|MzmKq)m8l@yky
zT1~ECpxZw@64!nkI34QLiUsA%i%N>-$&zGYR7WJyi9ERMyS(%kf
z7A_r)X>!90&m(FwDQZ>q;+nOa*KR2+E6Fz)QwU=W1Oyo*4>_qlm|~joa|{4_A_3W8
z#FFZzRp-xMIx5a7D_Fj3r^TbIY@cND1d0f*^qDIs{!pw!IWGQ_%l4#ASm_D5Vet
z0%ek7^)@xPihX_G0&hIc9*14ca=D!8oG}vW?H%~w^F?f_s>zU|fKrNJXJ_d6{v!t(
zpEoqMws_yQws>3o?VW8Txq~#->dJG^ELW5irR!s`(_JvD^6;r+ho~eIK@ia8_lH(h
zt*-p?CFC1_h2MV=?jP){uW!7WjLjCaO&c1D+tf582!XEaoB#xWAYcN5f$sLtf$koW
zQs{{>)ZTq?FC6|J_%n}AWbiFK(Bo-%^-{H`*)E(ucjo-r%SYm)W5f6tN=xz=S646E
fNXW#U{x?4WXWJ -1,
+ WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+ Gecko: navigator.userAgent.indexOf('Gecko') > -1 &&
+ navigator.userAgent.indexOf('KHTML') === -1,
+ MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+ },
+
+ BrowserFeatures: {
+ XPath: !!document.evaluate,
+ SelectorsAPI: !!document.querySelector,
+ ElementExtensions: !!window.HTMLElement,
+ SpecificElementExtensions:
+ document.createElement('div')['__proto__'] &&
+ document.createElement('div')['__proto__'] !==
+ document.createElement('form')['__proto__']
+ },
+
+ ScriptFragment: '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/web_graph/static/lib/flotr2/spec/helpers/stableFlotr.js b/addons/web_graph/static/lib/flotr2/spec/helpers/stableFlotr.js
new file mode 100644
index 00000000000..869b2b7635f
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/spec/helpers/stableFlotr.js
@@ -0,0 +1 @@
+var StableFlotr = Flotr.noConflict();
diff --git a/addons/web_graph/static/lib/flotr2/spec/helpers/testFlotr.js b/addons/web_graph/static/lib/flotr2/spec/helpers/testFlotr.js
new file mode 100644
index 00000000000..5bb7fd78201
--- /dev/null
+++ b/addons/web_graph/static/lib/flotr2/spec/helpers/testFlotr.js
@@ -0,0 +1 @@
+var TestFlotr = Flotr.noConflict();
diff --git a/addons/web_graph/static/lib/flotr2/spec/images/butterfly.jpg b/addons/web_graph/static/lib/flotr2/spec/images/butterfly.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..93fb22cc0974cb5febf5f15b1496445ac25cb9ca
GIT binary patch
literal 19050
zcmce-Wl&sEv^CfSO9&FAaS1LB2@Z`Dg1a?NaQDVFfnbe8fCP7M+@T2scXtWy?w;Yz
zH}%b%sd{g|nICg*t*U$L+`6^SkGs#_Yp?Tf?%yilot%`c6aWbc2_W-w0scJ$*sNS#
zodiC9v~aX?_~2mf`qAFe!Pv#rip|8$)z#d^!p@7$$=cM_&BYx0@!ulgJK*i>*BGzS
z-(tMMz{Gru^^Oeh9S+VrYGM)sGDaF^CI%V?dJwxXH;7H(13d$eEU$p5xTK^c3zxjA
zyo8FdgybhAOiavo*zexs;l2OF%E0>R|MmFSg_HvTAfdjj;y)Jhe+;Bo$SA02=>k
zyhQ>a|Bt!Q
z+D}y-K(+BxIxZ8Jz&99#M8qVd^bCwl%q-kIynOs%0g2C&QqnTAa_SnI5G`#TT~jl2
z3rj0&8>p+ByN9QjchL9XkkGJjSbV~d#H8e(zfy8?^YRM{i;7FCYijH28ycIM|8#bB
z_w@Gl4@^u>P0!5E%`dF2Z)|RD@9ggFpPgS^UR~eZ-reKAP=1Aif{cRxAC!?^dAtA_
z7X_7?0}W4H72VhwpN8|>Yl2U4*;O5HXt~r*fhI2F7=(1(YxHOT!TNtE{lCUJ@c$F(
z|H=7(QvTNtzy+cRdX*YS8I2^p+YP+
zt3T}sRXy&Kad3_SkwHxFoP3tc?@UiR
zTC-tdwRy@x|41^5x0FgDBR~$_v^l3y)-8ryU$6F{aL-%cl#vf=8KWfltl7EbIooq{
z+IN;3wx*EeJk>+RB=#u$)bX}No@yVgkN+dq$v82>$jR$@AlxmFd#ihYlvL?^k%y?-t#jNUKgD#ii3tsS}DLe?$D}H`ySW|UF$w5
z)$_NAxmEJzi@C$p{RjA~xmv$MHC>U7w_x1>mFi?U>yqJtJjl0A>JUDU_?WEzz?ahU
zDCd&@30BUfIWr7m2~o7@M1PpYz}Zxk{RepSA^aaeJWBK*;FbEL?Y3~+)Ji(4U~o(c
zTOA10y=?2@C~!ICf<~3E(|1*oFe9y``OE)1Z1MEy)Id>kyUwqmOB(-=ywNp^Wj+xw
zCf}+;B;+4Jy{_h>_AiOGzz-`gz18Ax&*y17`yk%YjrTgfa0x}of|FTYGIi5F(mwzx
zz5=Ob@wk3Q`Wa(qN(fTx16Sr+qd{hc@=SBs2kNFsS(IWH%E-vq++DJg1S&}X>%b$x
zlL5HMW?@@)X`vnHO}f^K(%^fDK9v5!>HeK)ty}nu>9P!qmFyuEVIw}gVIL&giE;Wn
z^GlbOVqx!hR-Xw8zld5@Nsj;XN13OWQE4qN3W^_cLWPnxbbZ0uqsujT%29oMhxNBY
z?gMwKK*}3{S(;Xy&2QrJEmZDQl&`LMN{ca@?4s$;-pQ~!ghpn)jTUy^1!4tcE*_bLwN*~B)|K*3BOSi;0Uo5AS9BLKe_G8|e{N}H
z`f3D@xC8Di(P#1px_eT6XlLtU$QQe<`5rZ9;+9ZF^xX-2zNA*N&QlGvw*3QiHO~~~
z8SZ;<7VoE?{*GPGijisZhXt@S5QP>RC!wDL6X35;Xsy;t2CaUE!Wj<91H~y5TRw&s
zipdzJor5^nd#pt6sRq(m{sg=w*wqN_ibyh~pFS+W(_5~QL!0vY`lIlnr6%T2b7Son
zLeFpL*F9B2?EL7aqATc6iS3VAUMKHwfKBIRz5Pm@{~
z44L##=c6!Rwts-vtM%s1L~lf-mliZ&J6Ns)D~O`oj0L=FJ<|rdq;Of3xO**;_F|Uy
zkV|2!tbLb{)@N=QaauR-wC$I`hP7cd$9MYum|T+Yf+eaF8b=kdZ3$+2gX;ng?JdCb
z8a@92Exu_+ly{{iJAHS21bEx}iMy>G1o<*n3hBEUgFJ=pdpp_e8+15{7$G^VBnnOFGSK
z{iqOm4%!_irNXw=)o{_)of~u*KvKoq^4yi;=UKhs<>U4utMPEEfk?DamK;h}C
z7&7W~8Q_Av{NhA8mypPs3f*q=ihaWReF$+mg$jNe=9zr%zHULfcc@H?Yx*1#x&Dbr
zs?Su)-{7kNY}qKe`9$I~&h)?JR`0-g)NrEl{RY|~q4e|GGG+!~8Lp+nq!bHkg@QZao
z_I&PZ2+SaEB#SB`J@=UfS_v{>{JBP*6or;ohiHJJTYBrYttX3XFi?wn&}c(mbD`AhMNzjjjs#^K9Sm
zbhkVVkL}hUSd*;EzyHg)Du6SS*pncvr9Q*TZ3`92bptlLuPkL|?c0v>{Eaw!Z{-S-
zpmoI`{S)JaP~L!DH04aWc`dA8dojeuz8&gLJuR*Gr?PKJ7iXm6IP@zenf=aZDJjIg
zQ_Z~ZPd(%I$A8&JsKjdyd+IS*HF`oylx63%yo8N`pyyOc0J+;Rupd8Bd{(5fTy`U;
zUfN9v@jE=NmXtjH{8n6-_a4PIeP25|aXi8(`+l|*CI)~un9V)&htY1w+Wd8>EBM2
zde?O7ReDr9QSeVRcdB8V_VFB}E;eh_NUWM-`^q-C9&jC9---`PT){g$Dm#u#x(SQ~
zh@)r^_0#!azRu*G9N3Oopv=#*umS0!@@fNh)v38&bB8rSJW*ZRQ^a!nM-P5AAGZp_
z4C=|4la$}xl<;a;2s6EJ1l&b
zDP7G~KJIib==pXMxMtg~N1Q49EZ+xV@6*`y)C{Iaiy~j(zB#bxqEY}TXj6#S@mjz)@RSHEwB
z-MKQjwU;D(q*a8G@byADeKjL0>(|lb+dix(#k0LyTTO@e&9%9H)stOuPY4&m;N_h|TG(%xIE>#@6FXy;%;2bF?9u+IL1Ndv${48R8DLBhV?#^}Dy
zyp>uM$+VBTSI0Lz!)h1gwv{)X8}@3s)Py_RY$dibW!oSoRdRV9=w{U6}F|8g?k^1jOh
z;xbee&gxGxF)5f8uYYx6;r?vVtZLC#ds1D9AQT@OaTWsS<~ooX6pO&$Iqs;*u064Q
zW?3+Zqq^z{_|uH!kn!V$UYMyG+j8mdriaw6AZA@KLeD*+Z+_cLE?~L_JMDTmfYj_-
zAU$XCV2oqR&VI(jju+QPYJyT
z`o5H)sUu%7Z(zLiHM0wQ-&-a|I^?S&kG5dABGp@i-Em!8B7tH(KPev0d07&3qb^DU
zY=MoKPqr1Oz1>!M(I5khHz>)MBNAf;H5l}+48_i>4t_VXJSj*WE-&mgHs=6&=nRrw+tj5!V|KkP|EbGbyD
z!$X9!ZlZujMstnUz~>zjVW@O=C(^-zHm9KtCw`K~BMUxd?nfQ7$5LQOfQ`)V{hu$G
zI+JLh!58mvrz|q6^}Z^nHIk>1ZiOWKP$Rp3zgo|H3U=>8TM?wR^7Qtzpj;`GpE*V_0u5|9h}RS4+Wdpz&A|Yl?fPS
z-g{iqX<(J@&!Y3rw4hh^!y^c)Gbm=pd+#hyOV`=ifnvSJ`p0l4K_|y>`bNmXwCdR+64zK+dv*NN$gj^Atc-5yijxB
zY6%B_=|8qUO7rJ&R8%VZ_75b$j((x%(QETqIfL_lw2{?7D)?QnN
z3XT;`2#4yPg`^fP5(ch=!f)r|>ct;zM?P*Teo;%=XZlQIv81Xb#uGYr)h$bYE`37=
zB(}q3KUn(P#91+#mlRwYK^P^=7`F(N_W9t{<Rv1`W}@CEz272T>a8`nn*MtCWmd{Fa;ToFH6
zXE3*&9%s`x-#RK}OcnS}{Oz42H@nLob
z_1=IDw$vq!?Ca@##s9p`rvkk|4w5&ucJuR&wH_b3!AdVB^zgYXZ}@Zn`Hi{2%H9Fb
zAv`!qO*yHp^I`r(AxU`+K|@$m-`z;W&mb$?PnQ9>B*hjLEfH1JHYGVj)YP8~7W7
z>R|LyVyYTqn<`-h?)f<|0Mq{sf%{0@o+ql>3$MlLpj!Xe!2Sw0FzYK{;OYK%c*;TZ
z%|qPB4==Uuv!s_{!H|oP|h_3o%4t>48@nL*)X~Pm}2?cZ<-USz8A$6%a+FTGu
z9sR%mRL_nk
zU{3X-WTdSz^Oq`H+&{5%x54EQ^Gp;iDmMXd1~;1a+D+fO36+yOj|Gj`X1EeGOdSQg
zO-fWn^#AgmGoY6eqMR1U(p;0`8UaOx4>@k@H0qy^?*!@22>kx_&?GCp!Qxqt(Wt|w
z-n1-_Pn0Xb^ryzYZ*OU8fZ(QoPa`YNj70ix+DpXVU%@m*c5Le9>7+NU5{%!{hz37v
zi1Z*E3ArsFs2=&^k)c;`XPBBDt0+Ivg$F1+f8=}LhXMDZbnYwRkQIvLi%8-*!7(IggL;nWTlW0%z
zif4)?pl6YoJg^YxXfO%CF8vnhgQTApUe$M5Ar;I5BoYYk2!k7DHqUz-sOiPkJO37b
znC8Ri`Z?lPs{Us-#Xh(GJ|s%rd_zIY$suS4i$DY^_0;=Owst79kmse?%?+Bg9m7;$$q
zi*c1!S_1h|cWvWp5<;)#IlEg~;zpzuAY0`z(Dm-7X*WMZ{&KE{!E=|BZ#{r^Vq_O)~&aq$i{H^lAK1dIsfVv
zaM@aya-s}UZ(sXa0T0!-6XQ>#s)o#T0vuy4MnCKMc2|
z9S;yEY}771Zgvg8iW(j4`^##rdBsoCGdZIb)@yRZ}iwn8qh~
zn2sRp6KA^eWO*Q(pc|@NGvsOIU74~mpPRhgI`)sPss@0WJIH4=Nze4679L2eNl{oyd@s)d{jo$lxT=VtfDRv
z=sl%{gu9U!FyCvf;r(zS6^HlF(G}qL-SH-AwK9RA&x{zbGt(cI%2M_^HZ{7u9u9`z
zf?eGOE;(1_qrDT$-xwrh7eib`?2a5At`hZGS;!{32S~XlbD9IygMS#^N0R<5atNgr
zE*(C)(#aMLaDqoT;#aCv4PSE%c{@?9y-qHIKpLg_(K(1D=UvI&SV
zE))080j>8mrYn4j|$xslhS;(R_AZ}aGx7LPP-lNL?4(AmB582gnYMuWm+!N24e>TRAF
znOwt?B?#@WkQ=+SAMg1|M-xmXu1KTD<;z^SWR3AG_sKGW=^eUDo>9Zcl>GItV$f}z0|WhBaNjB>#W_r&d!DGrWW&1j2NWGCo(U;vAMt~p!g5BmMVxagl(1j#`=#-qBsm$5uiudGsc
zQkLDTH|Q!%4dJ>O!_2}l6cQPVY$IYYxf-b5^+c@_4h2q85pUX=ZXuL|V+16xgp?*V
zkCGWz6GEx0%zgLETUl$<`m+L_meqsn7=TV
zQJvKpdRcH@wfI>DWP=0E6E${>AKGW}sp&_gd5($$&&bpB)nyNGi
zxWVS7Xk=O|rH7UmugVG(OcC7XiXN0+7T4Wnj^=e9vXqQW3YISM!Z_Y5r{z)R!cz%0
z*OLR`&n5KK67>OLSKX@PAK|s(3&%dD&fWxmQDn!onH$D`Da6VjI_p0=J#3lMMam0O
zmdW;WPtdmz{jOP5ZIty5Ta#1?C%64tB7oz4;2OnYBqmb|fxN+#0BN;junTCu1DO0L
z&F4SsKmV8el6~F8`b4uEt$4d|VKjNl_LmJ*wb+JtK`E2x=rE9<+F+>n4oA&)$jvb<
z>9=W&Y6Hl*M2T3heJLMy*?$Fn_S{b;)dE)#->y7#&0?WZ&MHT>>G=nT9X~%Wm!<}y
zH#rrxTBf{}_i`Gg{l$%QsVGsKdsE(X=CeAl2gV_BUxP`l$kV2Div6RVzRX^KU*
zkef6j46o`GKTCbTlko(4+K9|}A2!e+zF1Q5`CyM9Znd^tnDsBY^iH(UMM@HOgE81y
zlH#oDXNnuKb}E25vZEBtCbmAZckgo*)uWmd(>)vZkka{)?Q+xF_CSxV%AVSMLf@Jp|+vr*_pc(AB_
zBwh5?G(FZ1*eF1h$syiUFnGFd*%aTU8nTSl%eAR(zLTT+qFK(IjNxoJT(saxAakH`R%FNEO+)?exJ!d-vDUo6)6Ah?
z6rW@@6jRfXzvH$BDb9|PPb?&$izA8W-mg$h&f$c?V
zXvPf-#AlS*vxRyJ~^GR2u}Q`}2oe1o2H{RQna$-9j@uYZ8h
z?HJv&9}Q9?Je+#N%wbrA+pkB5gHA?g5Q2YHyqt@VV9s;D+y!2|5k+7B`;?yvP@-F3
zVV*rFe4Nb=Be>e{9mC$vnRPq(h`l%J6TmPb=RaA_N<65uRD@Fyuz~KaCCGf
zfwM^jl&VJOP6fV{x~Zg}4Y|({qpIda8o{z;%65G0-W#Riswv
zYMQ(IQ3yB}0*Pbm!9C88d+(g%T@=ApBci%HS8%KDR9lH(IBJ4w`AVARgSb=0q|V#1
z&BxW$>h^XXGM(Ej-Vn!7`y`)OgAiy!rfy5#t#32QaVq9U<95zutoH4ZBde0yw#%F)^yVyG`ufM)*k$XDLD=jnN6JMuOENQ
z%?rf~K>Qo5ZgidF$)9pw(MM4HgnnU`(y!57_y?E>VDRBF9mXGaE
zctAKQhDdM6XMw@i-gJ?GlxJT?CVNQ|kg8BU0D&V+l~SMcIFEwNuwJ=yHhwcn_LMvojK2;BJH
zz`&)sWwzxKferpn90`5T0VTX`f!$$Q^4W0KSCvf-Mio>-8qI{iud}z5=3+6SE1ByN
zB$Uf};gyYBP<_^qbX>2RmJkTr791)5Na56J@@bo99*3mJt2+C^UhjdaT6sT7ZO-7>
ze*hvmi(p?z)+8mqk0=&(KMBb~QM8ioc9r_)bKAz#dld-b^R(4I&5184IEAyS3F0mJr|SPKJb5lp^L)rwM|+E
z(MI!)yW#|y<_QmVQ?j|78R!Jz`57&Wj#rJk+X3p99HV_FEbevFLR@~oFog=
zuYi5q_ExVA9baXCqXr8_UbLr)fKiF3izi_wQmv0Vp7A%`h9a=5L;rblq=khAS>9d%
za;WKKbl!^?SIXDD&~%1YS=B!5#i|2I*LXg?d5ws5zuLD&>$yH>+ODce9@JZ}>W_TY
zDTH~|D-?Zopo<=S_$Y|B^|UoJx}wloSVS4vc&1&nw&Bq-x0^3J8rQR=3~ci!ww7N>ifB4fs*b32GmkRu
zVtEtTckKSmv5=W<_0?p12lJ>8t>&b*@pAD&Mzz7gL2qi-6fbOCTHjw2VcnBRPio-(
zOD(cQ!=-r4Qw3ibut8k@#Xz~{fOnQ3NvHm-+fAp}W=BYigtuz&A3$hP6mA@pe4=)e
zT~6(P)V7e0!szmfT^cT;xtA8rrOCbbxt_g>I$SkPGYO17(XwlK1^Ba$=R)Eo@l};>JK1TdrqPM0F*%0=PQvJ
z3E&bQwj95aDCzx6qat@Fp*BIdD6Q(U@B{d=?1)Ph_PlI>W0)%}TyiUtcsBih{czN?
zXS{E+?g7DKXkXru^v>Jt3fOnh%s=5K%SOd8W7^DcNtrlz3!5Hp<(s*?ILsU(^G-Y#
zWwB^L5ural^iaXcBpbUyZ>*5O3ENDwaZQSuwi{uoJ#qDN>k_rdG{sEhHy6ptW|i#+
z!{4r7(A>fJy3d<>`HwV_VEp7lKATRw6mxGz_icAgj$=6GhN389Lkx+@gA6m*(j^|G
z&fna-oytFYcs{Jj(^*gjypUaXmxAsgd-Oa&hJlz|v$giA6Q{Wt*teX)vQ@
z4eLHcZC5w-vn&Vx&&6q5AFxNIB(n2@e@V?d;
z`-j`UiUZSOBLC>RG*Ncn0tEgcQ&j4q>Ey(~)jk>O
z>IM)i7(Xhp@wXFg)snq_`4jFmaIQ&WZIz*(w2(B*hLF2R;>Y=EsiRhpa+jH%XWSz-
zTe3jK()2UFd;xi}btu!PcjL0KPL*
zw8s9_q1Xnvyq9Py*l1pTBKwZllF=jIhu9nA2dfu<%5hn1h$$UXKjW)f9zV(hi7C>7
z%lE%D^)~H0QOqFTo9ypbgJS)#To*p8d!`z+Ow~zomZ_33*clP_Oc@HF-69(>GLPTt
zr0mF6Ri8t+zt`~o-LMe#eAsw};8Um}91Dsx)1qEW=QE{#+wD=pp5={E!+YHQ66)Ul
z*OAWXwzDhF(^Nd^tkQvMvq9of$=e^fI15{^XCdluxR5w{&}A_%bLU?z#{8EYPO1>bmHa5d
zUhbdn8P65>ukS;Jj%kUzoeU|e9vt%WlU<7Y%UOA8atpx_NR2?gq(EGxKCVQ*n*&_nyNR#HU@b>-dT}m~PQ8V?Umys#+yUk`yqzR{7@RA%H(AXwID&v*kr;)*rKY{f}i06Pry
zfU(77E^~q{q&JmrX}SGuGm#h01c!G?Lpdk$Spc9E5;Dq5G%B`N81KIh%e8#>?$|jN
z&1bUQKBMa}5tv>RicT9Dh`y)78lA~mMZ8m?dpw?&YUgc{>HYbvNzgQjT}&7mH%&`;
zJ&Nye_SN;WQZG1x0vQ5N8!nb0yj9?o%Whw+M2_y9C?kqKUXiy>b{B!D&$RM4e1gx8
zj$P*ibu%|DZ+$eDzxEznl_TizJ`=lOd8Ov=resIPn=)?M9C5?NWQoZdd58}Mp6Pha
zjZ45EbR42&f%=>cEaGTQ6ogx@%O>mX^~Q14zYcA)r%z^Pm68;KdblzYw`&Y)mhRfu
zYSN~Sm;_=|d_;%R6wT#S{Q@MWX^r0W7kwySuTq;}$M7N%wjKFv&_o+b8;P62LQ)4l
zUga32rdHBosEyVEJ&b-bj|rWZibj-{7wmsJVshfx(;f^N+3v>uMh!M%=Njfhcu(#1
zq1u59*p}l@U-+=Xi8@yzS+6=%3b6dc-oBA(euJxSR`JeEYHI2r+2a3
z-(BW*fL*gbYXz!SK<j9$vWRaOUsvnLL&MM``c3QFrXk`GNlvZomq@rq2njPbtx
zGO8|iG!o=!gl);!M;N$wqVY&h;h>5aI;wlH<}|=h8*TfW0IozC%7gBuJe7$|n-P#G
z>6*Xlw)$9jr}1cHqydcvlbfcjMNI93zhKo59{ju~PdQM}SefqG6J_|#8T69+Q7Rj5
zGEJL`k&BO%tk&Ir#oqo6Qk8o+!5&r{>ZT0oNw8!Ko
z6=rPqa);^33_=l{1x+u;nZLT6|JPr^UEm7F)Pn&wYc|#$G#+^v5
zLLJ`)x77Rg%=_0Rx-(If#WtQ@10qt-i5H$i%9TZv^BKc0f?P`dHcm(d{kM2#7msff
zE#f!gCo4@~u=?VEv6cJl6=Adc!1i%N07bpr`@(N?$;54-HGGe>ZPYsLi-mv5-Z-U+xj~;H-Gw+}Igor73_s%u3EHP_xz78C>(_ZQ8R
zIBa@Zenr)Srd1dp!Rp6D7QM)jk`@m19g>TA)4`(ce?1{(BGgf`uuq>qxX2vo^PEmX
zU1HRKrVAQEO){h2IztWNMBChVR<
zPYvq_MGjY9DS~c_RRiPp`T^DGYKj2ky`&(OwuYK`M+OGFng(ig7j(D>VG6M7
z2brLIG&|sK5z5yjQl}+-y1zJ_I20-*h)M1TnsD`_|NOqPqzc<0td@<5CTeh^nLC`8
ziitoPud7j2xj^Q>PRR%W5cl73@wF$?4V5NZJNff+CxaHI`;_sD_qXGv$<0+0rq1;|
zTm$IEYtUQEi!7^)C{sxD%oX_LJYa3e{>_Hvr{~)uA?+fytfbDN1glT0c7Gymnw6d~GI((V*g-vbhnL
zD@3}$8N!mdQS(|(gN3rn(y8Gc7i}m28l>sqm>tcDnFU1WFBknhV>n}pENH+kWy`t!
ztV1sfZtp99y)~L%a4F1env<26l;4sX#{ee_s9R`>0C%>>v;eUE+UmAxr0_tNvCx#M
z7=sl2b37?_sDXAGsF)ykY`b%Piaf13Bknh~HhJ+e3pkC{EMx~A*t>8WweUxSJ}4+I
zXd;)ADema80Z>Vf|9CK}jnN?Z+$6Ucr>CIaMFQEM+isZ~9cZMDTWHNb)?Omfh@C$$
zpvZ)+2)fFG%~$1A}@||=m>n=99QT0)N
zk|z!)%7Mmtho%|3k$AoE_nDdc=d-ZR4$gNHF49GCYopSMVjlzHCxM?M2+}zJvq|ox
zlpJX&b`QNc`Nd_qq0jfRM}?Y_IQ;Y)Mn^alQURsgb2Q%;iWi}c@}j$b)m$V5DiJT$
zR$MQ&h)}RR-%cGScR+Vo$D!gyQ}ibGrR^Nq*OI2UQ?QxbvQ_&X*(yp=8KW`TWB&nc
zgaF2XDSJwGNgG00U5ibpFSQUYALRIbUkMABR-WcvCzl3fE7BM&E)I=%Qf~@*2`caD
z{{w{iA`3^BU(+>P(r|S=VtHfp30)2e^L451deDmV-?h`
zgF+H%5L4#Jzp%d`?_1N!;wwK7lWN_oB>Rj8>PHWt8$a$pWrq1GW?Og4*T?(F81$AYcoC(Bz!%~vCSG>1D}+0L42_K+^&G|wLj
zp3UZS)J8$O)atG)yh}bE>3krg+ql)#2vF9Q$H8c9hP1rWo_gwtrP}wNtow5f9;dDS
zM8z?H97nm)AkugRS6xe&{IFX8i#m84C2*pg0O!Q$?2>0mIY-i$t*UUL{k>WVbqvX9
zMTOE;srB0OF6ZxjLHr@-&_BF`OwnY?O{zC}Z))-XB2TU~Blu*JG{fmFok>v?dj&OI
zkr$c;u~1dkk}u&cKCf3h0Ge-Fe$@-m^dN!soO@`*N|pYfk4V1zVkl9lJ?iDOXdK8z
zn`y-L1?-u^CUy|Y0RZv_^vH$eo5euZ3HA-dS7oIER~tMP&fgqw
zUCRotWcG6qGeZXi7asw;-m~gkNH&GE#V7hJDF@eu{qOCGugasgUX3Gdz@a7;u5r_W
z4YZc*6Hlp{c(lN|_CV02t0Sql!J^EECEY>N%_LFK6KL>b7@y;}XqzK=rIo>A^(hu{
z8Jo~&B5*Kszid&8KkFHnEi}xey)HOhVi_?ErMpl{M(U$Lbr;2c1Uat1jVO(nzF<50
zs{W3jwU=L131pTXnx8XHEZZF40tQ)f482*!h3)#j&7Qvv>SCC9>Wdk)z$io=&Ely!
z14&QR7EaFm;SO9&Z0$>U$R%c6Y2+`6fAc-I@2szo`o}8FDY-6`b7K8Bs0y*6j3d1^
ziYssKu(ifi&2U_T?J1h5I`$}BbPL-%x}5Xvm0a3PL#28#40s7|7T6Z&rKkZu(h~CD
zr~Ezjh+;U6oRtzZYl>dQEVj?X9osqM$xngM{b{VvRBTC^+2@V6>X(+7o3tw{=ON3h
zuL;lpH3B4Xv9XtW_JdrnKkshqEY-xaBV88RlZcaMv`N+?~Yf>_whCF~c?wTn6z9{7>Y<
z@HQ`oIS|F;4j=0@S}4C_ra;INOenD&JoV)4)gcj}ho3TDZ!XdEHA&eP+LV^KS3G4c=~cdrbgESC9YraL
zQd){`?s$2fo@cg4v^nRuKluF>g41q{ZB6d};JrN!(tRzvWV`!G%1=j)(TZ-ym8w}M
zEnsKj22CBSUNFM3I#rP1t$gxt^izBh{JFRpHc;9~mBp_B=Xy`^t2
z&Bbd6C}luOCW_CS7~mNvNxse4tl6jfbO@OV_Oe}dBv59%lr3g;4#<_aTP+@yq6(3&
zdqsPCsz|wJnX?LRWV>*b8>(3;ZS>YO!H~-m`Z4@bna3)$>+WbueF{KN0GT7v6$#euODqGH^efwRc2W)*muq7
ztL|oV;xg(;0y&|z>U8UNkAi`mbu~nxV*q#9U+rmcra~1|V%NT0g?B7dPIMAm|KT?>
z!VdgjaVWgJgF|@R;&VMpu%He4`aT$%&~7QZ20J=_rm`k71coYU=fbp2F&k8~R`kf@
zeFA;7qCNlI)lm`dLu>G`I$0iA(va>jrW$TT;gVVXdB03OBMsFCr068~Iw+Sk>Rd)N
zVptq-B(%zp2|feB^v(t!gMDB5YGp00^m=VBNpnQ=fD-uu7k?4XH?fNL&h&^s1W61`
z`#!h}39{82d<5gUWd&ZTaG
zfzwvQ`EcJvjA;9rMp?I^z?Sb`P|o$Cm~BCGVGElRr1hum#iRE&r`hh}bhf9YG^@AP0vA9JjE7CF7-ve0(hMwEh
zE^osYQ%j1BaO_Ozo2QK~F-(q00CZGnmR*`>ZzhH3yMR%>09#%H@j%_61FLcoNjr}k
z6`J@8FXM0S3E(*F=Ds4FJ5`ZPVO-NbtJC6j_=k2=C`XxgZE6sdR3BIuzIi@$zA06>
z1iRwre%LL`t(MK#v+5z_;U~Vif|Ci8%4E5o$7`Dr$CGHe@ZV6)M6fq)k+vn+o7%;v
z`%kK*QVSKm1#9-|XK?7;d++sJa}*&47A_FkDtf2GuKqly!}Y&hKe+bMN3V!K^kv48
zc{3l2)CK1-#NKq-RxeA~R<6OP2>-WM&NZ9~1&rfMqUOG~atoz$ODMORdoDwWVm9iy
zte8u3jFB9&LP#1`455J{64&u-XF!I9kjjWAz*%|{MtOdw
z^ooNjs5P^XkpvYJ2c3KyH|J%yJjk9dj0+Er40(>IUJFxQzOa$%wb4$94*EnG>+Jeqc(DMihInIk+U(lBB4cCC~R|(i@>a?@j(@eqmtGe3J
z{1p>t$^=P61vi|^9{9M-;wyGnSe8qiKjudO0hJd&?ww<1%6;uh~YYlbCk
zdqgdf!OnFcJH;fp-a#EzRHoN}oM0Eg<9o~EMn*e3qgZ<_
z7?US=d{SV^K?oUcQ;{SX(R`6&98Nm`WeS#XI+ZBcWr9fllh3LiYQ9`GS=xItsy@*R
z8taLPb^Jx06hk6p*Un<&0=W>X)l!H2>r(RA2c(JRhSlNW?*bD_INa
zzqwP^v7P=lbwgFikt?%~@_D4+}W96PKhc99re_bsc4o7EF
z?@6W!aw^7W2Pju_43QSegkPolTpmvrXLNWDa&P5o*_YNe9I;L<)WqA`#`3GoB{@2h
zMG`}Tr`|Slw9J_#Gj3Zi0~0XRU|dz2jd-WhUEpr)n!epYR~ALSE7!j^)-X<<3Z5Tn
z`Z7u8)#tgl80{_A(gkQ97rIFYa*o|FlTL(l%ly1fg_>LEvIj@K>hHJBMA%kFWzE`z
zp~)h=1w6N%E%QCOJ=VR2c&5cjm|-cD!&rNINh!$p&=
zbfNJMBRIP|01DU>ZZhT0@EMYq#=La&-8UasV4C+l
zfPUu-N{`(b7694L|6#@c!deLLzZSEcDXE|c$IpHPNL2>p{=wv+UuYlHtR%MCv7UaZ
z@vRg%I%aH0TJ#^S)~Dr7)0axf_`_-adWEfW6tf9t;nzdX`=~CBcE;J?huN8vtkfjT
zt3f{K0Gw(cC|VvCU1az)e|oVk^~RC(8m2Ix7EL?tH|q`{tRR1
Kf7FPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipk|
z6$c*vJKp#J000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0008wNkl3whYOaGj_4QfN(x7Bo{gdl5n9(h$3>0DN8xFhxVr
zKIGzT0Cbj_PdHbc$5{u9HgN-n9|{+ftoX2FInMzgtKZmcGtX#-cE{&5&T|0v%SJi;
zcP(p<&31DSfqIt-S1*KS4P4k+Xp*9S0vSHHGu$p4<*lXbS~kF8g!>3o?1OszLcWvr
zL7I%g;<`9RijMdK3_G5PS6~2M*1=|nSqu#~eN2rR9!|UU?ziQ%8G*^5ZknTl0Jpt%
zo&6-4Hf^xz76)mD4)~a1{v?0Ox|Q^4funb$1<=tI6jMrFfQ8AGa-`KxJj1Qz(q>;E
z2M=Rav7y!ENZGOI5xYRR=zEz6PfSlwUtVpVK(J|HZKmPu5QUNDpkm
z5Z6SD7SYD@TJtt7-CWdDbd;uPr!Pwc<4(xwPNj15;C0rz9l{W!6ru*`*cdS)_fd|2
z&dwvOd-~PDEDE)##9V4%3x;SGEg?Phnhe@L-=jo(#cpvARmcGVu}xHB4qJRB&f#IZ
z${tMl4ltX{axGu$PqJrRks8!A?Ib*G@R`+BsZ-;;N)@cfN8)3VvPdq4scgA}OrTOQ
z@&lT2NY=>qbXrma0Qp`vu*)fxi*;rY8q>Y`ejly@c=C%p1{ZHvtxVvlU2VU~3=9Cb
z;U-k#5WDQG>TrNMGx@f;G(;-gZkg5%6d%k1%}U2BhI4GaJ=EA|WuZOKF`p*k`h
zAOf#U$Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipk|
z6$bzWG~=59000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}00099NklJvo_uzA{@z|oaEp}9OQ(f6iIR+F2YH1;6!^-6p57lNs`(<*pmZ^
zh+K%nI&h*;mgm{X{>I^*@tb${op;`y`t8i_@0t0|_j#Uqp6?nOhQe&%2(TV_XW<$!
z)`dv{x-^f_MqvOLu<*MJmJIN91*>4i%?c+43V(14Q2S@h5ZIt_M&XXYUV)i8;1}8l
z>;>za0)qqqi3=)!5Y0hdzM
zt<%b9Fk#?h3tMN-H(-wkEv5_XyNhEwKL_lx8Vmah5J`ZxddF^kUg2+|ki?(+O`Xe+
zffQ)bXMGFUUG;opST408R_p4*feYMlpp_<(iSugOrhn{SfkNSOOV`>+ER0FQleLVs
z&$MIUB53rE
z5(;yHt3F>O1*|ZrrQwzY%kXc{ENry!J}8fMp4_5vh$b~Ea7ycYY5W;cnCJE^S9qw+
zz527jniS}ENlO(k?5x7D!osjP2rQ6Nvc_W0&sJbl>In_!Km{I$l0~MTG34#t9w{sf!hKza&Iy?=b*rMxBoCs
z<}`L8Ed^W1^qiFcJ7o*LRM#5>j%kCJrgR}GyHHQy=ybbT1m4MA_#ZJJH!WcJ`i1}i
N002ovPDHLkV1kajss#W5
literal 0
HcmV?d00001
diff --git a/addons/web_graph/static/lib/flotr2/spec/img/test-background.png b/addons/web_graph/static/lib/flotr2/spec/img/test-background.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a6ac87d7a5298d9104984b5ef70a13fa8d54373
GIT binary patch
literal 25218
zcmXtf1z40_*Y40UG}0j<-6>s4h;(;%cStvqFAXB8fOJc@G?LOS(%lVb^ZnN$^kSHK
zWjZag(H$%@=R&`)?!G6^VF
z6cr8S8%mVCH)csJ=l7-_yj-u?myN6m{(bw*#Ned$hhm@i*HhRCao2Zoiiy!O@87>V$(JGEPN?K|40K!Q
z3>~`8qAstZF@uBHInU=~{R|^m^XSQh1V%Qy4))SeQBgr?sc`o86DF+#R<6)OudR7x
z(6i-PdBK~)uwzez?M_-pR1Cjsbp4(ZR0ipmfWWPZd*x_^T%*?x^wLpLo%tki7*WMX
z`!~FTj0-LBA6B+vLBtvcWRV+wj72!9C!5q8d-TE{OexP*W_)l!{NHtzf2X-G^KZ!q
zyMCc%TEW;KO6Cd8MByv?$x+CGcZ0~Owb^`Rs}Us{RhCs{M)LwYje&eTrIL^3Aq|In
zbF!%*o*2yLiK?Rhp5j1Xo62Y4kAzfVPysqDWT)2iEKz=NQo*h6QR|9yL}ZJotTqka
zit^>IAz{zAd5>9K160TLJsL&Ip;dx+=e+Ed(A7QjbzA=(>A{U(Yr1$?9hV0%tylAp
z|BRIqdF42u<eWrCZNS>iPO$uDX*!$*O+dQ0XT|{(x=i&aNufHU;It+V4oYcP$Mj>6g
zARk?JHqALD3EM$w&Lg`T3X9A3MZfFLGQWyN=evhzpC*Q3nu;Cv*cO;P&~K?@MNTgG
znnUZUN1;uzr9z(U7^i?_k1XCZwwM?d90^GuaNXm+fI`Ih_V^0$cpGga-fY6JGitib
z62B9ysn>kMC2^=xPN@6vJB-ctk`!5mF%rcUQr^TYFW)S6fuU%$_gUSF*OnUYz*
z%prd9NA{EuCv+ThR(G-V5_-wg8!kw{fI}VkS2c>4P_M9EN+*K^e$S;Zs*5@EMI36_
z;}f4d9EzEGEW6h=%z2RJT{639m^59`yFqQq=}qP;C6%&8=*FKt-*fEXIq*CU9k(ED
zOchw`)UngskA;z8hcgyb(hp`&s2wTDJ(#`jiw?_0r$p8zl&gS(+L2I$FX|ZsQ#s+S
z9*U-#4L4*00v&~>3*&}(0lhNyc_cj3?xFpHuz}FGw*niIUWfQfzGH(o)KQKhv3w0C
z5cRWLja^u-dn>VoUO0P#WtnM7{wqnYAU$3FxC|E~1xTtQZaj
zYrEbAvl#raDjgKEDxJ^~7zxPx+kT#bjK}e#tRBvC%F<0L_jI%ys{6blJ#AP62
zU|b)e(5i8!nSnh1YSMx|vMY-*hFVx^m6|5^wUCNwSDb@|f@f_KSE}I3IHORWx{vrr
zFR|#Y&1`9s??Z53><3N^Xj;E{_kSQwvSr#%DZnihGD|97=^#>hq~00~Bp|X`%7zJI{iGcq
zAR|e~3-?}C7_5sZ!ux9sX~aQTAXmLJ`!5WpLiou&D&=RyY#oC{V_%Qj$Q|Am+nBQe
z4UJby;?pE;{6pA(hQC;TFDgE1Vvi)wIO5TKog`@SMPWX)8gEOlDrLY&9LaR&`d}^v
zk$WY>n0*pqw%O;tfGwdFTcd$EBhYm5m#XWXU(QCGsdxl^Vd;O2$Vg;e+!8;!k;m5&
zc9bf51}UH_%)HvjNcpc?a`Xc$ulgne@1N}&2X%Y=Rgp{__)mk~%LL`SU|DtyG(Z=W
zsUDZ9XMT@qTi_@uo5lYNQRW_dHD(X1D*O~9DYjpzx7+8!47X~^3-uxgH~1rG;P
zSAv)OmV@=(-0^w|`x6t=YHn@LYzgPonrMJ4tMta$K>8My=`X}Fn2d$r5hwmEizl_l
ztIOBpO%gr2LsJ|LB00{l{uFx9;!%RMtaLx$-Iz>eD++c(XeZjd<-0oM>v%p6Y5rIw
z?1))6y)07jb4MqgITUUv3eUMSF~sqt>#dBqG9{=Fr@xq@W;hz`sxh=yuv&)Zlta+7
z`1h>^XXfIDB4|ZXXMRiP4JvdVlF~k|T@Jan`UQJ0`^7Aof=V5~GZWb+N5iE0p>bjQ
ztMO0|fuYFO_48vI%EG{W;faj;Nj8%HOt{euoUmU8eg_Q^Y
z8lLBW>NQJn>BoB5oYrFD*M@3Cp#8z~tX7c3Q7s8atLj~urD7RYMe)QS$DK6&fgZ!(
z0>Y1lW?u+>o4O2GtsLfZ1PecH_Nh12nXW*ysy}Jnmd3bOREe5$>Q8(*fG^DcuR2s1
zW&Ken*0i3(<}L1rqfeE^|BNZ|oeqQ9bhQP`3{&(++fsk!5|}*nLxk#