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 scaleCustom 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.

-
- - -
- - - - - 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.

-
-
- - -

- - - - - 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 positionPie 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 blockPieChart 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 scaleCustom 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 originAutomatic 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 scaleAutomatic 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='
    '+'
    View All
    '+'
    '+'
    '+"
    ";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='
    ',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(''); + 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+''); + }, 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( + ''); + + buttonSelect = D.node( + ''); + + 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;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=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('",'")}h&&g.push("");if(g.length>0){var G='
       '+(serie.label || String.fromCharCode(65+i))+'
      ','
      ','
      ','
      ',"
      ","
      ","
      ',n,"
      '+g.join("")+"
      ";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('"),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+"")},this),j.push("")},this),f.push(""),i=b.node(j.join("")),g=b.node('"),h=b.node('"),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( + '', + '' + ); + } + if(rowStarted) fragments.push(''); + + if(fragments.length > 0){ + var table = '
       '+(a.label||String.fromCharCode(65+b))+"
      ', + '
      ', + '
      ', // Border + '
      ', // Background + '
      ', + '
      ', + '
      ', label, '
      ' + 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(''); + 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+''); + }, 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( + ''); + + buttonSelect = D.node( + ''); + + 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_Fj3&#r^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: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +/* Based on Alex Arnell's inheritance implementation. */ +var Class = { + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + var subclass = function() { }; + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + + return klass; + } +}; + +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "valueOf"); + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments) }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; + +Object.extend = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; + +Object.extend(Object, { + inspect: function(object) { + try { + if (Object.isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (Object.isElement(object)) return; + + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (!Object.isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + }, + + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({ }, object); + }, + + isElement: function(object) { + return !!(object && object.nodeType == 1); + }, + + isArray: function(object) { + return object != null && typeof object == "object" && + 'splice' in object && 'join' in object; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; + } +}); + +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + }, + + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + defer: function() { + var args = [0.01].concat($A(arguments)); + return this.delay.apply(this, args); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + } finally { + this.currentlyExecuting = false; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = new Element('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +}; + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = { + each: function(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + }, + + all: function(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + }, + + detect: function(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + }, + + grep: function(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + }, + + include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +}; + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + filter: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray, + every: Enumerable.all, + some: Enumerable.any +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +if (Prototype.Browser.WebKit) { + $A = function(iterable) { + if (!iterable) return []; + // In Safari, only use the `toArray` method if it's not a NodeList. + // A NodeList is a function, has an function `item` property, and a numeric + // `length` property. Adapted from Google Doctype. + if (!(typeof iterable === 'function' && typeof iterable.length === + 'number' && typeof iterable.item === 'function') && iterable.toArray) + return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; + }; +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(Object.isArray(value) ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + intersect: function(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +// use native browser JS 1.6 implementation if available +if (Object.isFunction(Array.prototype.forEach)) + Array.prototype._each = Array.prototype.forEach; + +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; +}; + +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; +}; + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (Object.isArray(arguments[i])) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + }; +} +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + return { + initialize: function(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + }, + + _each: function(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + set: function(key, value) { + return this._object[key] = value; + }, + + get: function(key) { + // simulating poorly supported hasOwnProperty + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + }, + + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, + + toObject: function() { + return Object.clone(this._object); + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } + } +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); + +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); + +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || { }); + if (element) this.Element.prototype = element.prototype; +}).call(window); + +Element.cache = { }; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + content = Object.toHTML(content); + element.innerHTML = content.stripScripts(); + content.evalScripts.bind(content).defer(); + return element; + }, + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $(element).select("*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? element.descendants()[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + select: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = element.getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return element; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return element; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Element.Methods.identify.counter = 1; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + // returns '0px' for hidden elements; we want it to return null + if (!Element.visible(element)) return null; + + // returns the border-box dimensions rather than the content-box + // dimensions, so we subtract padding and borders from the value + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + // IE doesn't report offsets correctly for static elements, so we change them + // to "relative" to get the values, then change them back. + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + // IE throws an error if element is not in document + try { element.offsetParent } + catch(e) { return $(document.body) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + // Trigger hasLayout on the offset parent so that IE6 reports + // accurate offsetTop and offsetLeft values for position: fixed. + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + attribute = element.getAttribute(attribute); + return attribute ? attribute.toString().slice(23, -2) : null; + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr, + src: v._getAttr, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Element#cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName in Element._insertionTranslations.tags) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +if ('outerHTML' in document.createElement('div')) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['
       '+(serie.label || String.fromCharCode(65+i))+'
      ', '
      ', 1], + TBODY: ['', '
      ', 2], + TR: ['', '
      ', 3], + TD: ['
      ', '
      ', 4], + SELECT: ['', 1] + } +}; + +(function() { + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD + }); +}).call(Element._insertionTranslations); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div')['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = document.createElement('div')['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(), property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = { }; + window[klass].prototype = document.createElement(tagName)['__proto__']; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + +document.viewport = { + getDimensions: function() { + var dimensions = { }, B = Prototype.Browser; + $w('width height').each(function(d) { + var D = d.capitalize(); + if (B.WebKit && !document.evaluate) { + // Safari <3.0 needs self.innerWidth/Height + dimensions[d] = self['inner' + D]; + } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { + // Opera <9.5 needs document.body.clientWidth/Height + dimensions[d] = document.body['client' + D] + } else { + dimensions[d] = document.documentElement['client' + D]; + } + }); + return dimensions; + }, + + getWidth: function() { + return this.getDimensions().width; + }, + + getHeight: function() { + return this.getDimensions().height; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + return true; + }, + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + // querySelectorAll queries document-wide, then filters to descendants + // of the context element. That's not what we want. + // Add an explicit context to the selector if necessary. + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + this.tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + // reluctantly do a document-wide search + // and look for a match in the array + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}); + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + // for Selector.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._countedByPrototype) { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }, + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML. + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + // a key is already present; construct an array of values + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) var Event = { }; + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: { }, + + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } +}); + +Event.Methods = (function() { + var isButton; + + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; + + } else if (Prototype.Browser.WebKit) { + isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + + } else { + isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + return { + isLeftClick: function(event) { return isButton(event, 0) }, + isMiddleClick: function(event) { return isButton(event, 1) }, + isRightClick: function(event) { return isButton(event, 2) }, + + element: function(event) { + event = Event.extend(event); + + var node = event.target, + type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + // Firefox screws up the "click" event when moving between radio buttons + // via arrow keys. It also screws up the "load" and "error" events on images, + // reporting the document as the target instead of the original image. + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; + return Element.extend(node); + }, + + findElement: function(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + }, + + pointer: function(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0, scrollTop: 0 }; + return { + x: event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)), + y: event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; + } + }; +})(); + +Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { + target: event.srcElement, + relatedTarget: Event.relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._prototypeEventID) return element._prototypeEventID[0]; + arguments.callee.id = arguments.callee.id || 1; + return element._prototypeEventID = [++arguments.callee.id]; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + return eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event); + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + + // Internet Explorer needs to remove event handlers on page unload + // in order to avoid memory leaks. + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + // Safari has a dummy event handler on page unload so that it won't + // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" + // object when page is returned to via the back button using its bfcache. + if (Prototype.Browser.WebKit) { + window.addEventListener('unload', Prototype.emptyFunction, false); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize(), + loaded: false +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + document.loaded = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write(" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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>BR|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<&#xj9$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#d42e+l}f5+4Q#vL_*Q2IMp6aOz0Vjg`5hidJzmQ5jE@G z4d2CXf^BtB_ez{ojObi0Fr%KbvAHM5gCYS5e2+{G!5~c$x}*92 z;IvG1T!2nFjj_6V)IPS_h0?LKyn;YSx}OM;Veg^;R`+$c^fmvbgqLjH=s?VAZ7IHW zY;GB?+LGWO3`>g#<$z0R6`2xcX1OmhdbAnRsO`)@Y)a;Gx$XY@0{ueLI6H$378LTv zl>2jrJmjE*@$i~MwRkvaHte>oP6Xcgb(qF}z^9f6xUJZkTf+jajG>*}I0;WE%bwF; zbcWdHnv><+Omjy{NJGTK$O_Hi`6sl#r2@D^tcp-q{t@R#acwyTN=g_p-LK-PiScgc zS?9@ZbDNXH#vrzNo^)}0CHQ)hBQnMwqRdRWwI!n#XG9g`J8mWh2;0@QO;+=l$?+rm$m$_6^WS)&XM|Kh z3ZuV%dkXg_MP}-p&1Ayc4Tjw7v`HFCwXj$$+JI3Ft6^pRLs*3RNb^n2aI%sB;4Z~OU2FswWzNDiBh7PWR-`SvHQlB#JDRsjC*TjfOmOmmdF}O~QBW&krT| zxohz>Qz)grAmE|_&F%_o8f55oZ6)@29t7WwyKk+Lfq*P_0t2~rAEjY3biK74d4UOYa;z%Otkr0AjU%`!8~!T9PORm-TS#9OeM>c2d{YW3Zd+XJH#_D2$RZ=X3LI{JNFS~$*Qhd_cAA;ERHZk zOF?lkKF%sgdnIF`3^i07g}X|PvPczI{j|)$jtb+%vw!q>j@Pwsb?6FEbqu{DS9bA9 zOkV3lYwr2gL+ZdQC5|Ujf zJv!n)ozZ_j)Fno!aT>KHS4s)xH+12F*NH;=^FnOORRVhf94{xXY0w5?+`P3 ze8JOnf%))jE%N{|66Q%@yCLU+?MIZ#pGA>m$lT&diYskCqwFCTExt7cW3Q~>nr)dV z(htM)XIG`IqD1Sh!Vt5y12n^GBhqOU%ORWK;-2WhQRIzk%mFw9=o>T{Dgv%d=&C|r zN$>X}bjn7qwZ*{js#V=i+3Z$30)zzL;RGL+ur5BKrg6S_f;b~mjeZda_BhRfGYf@D z!}QLVQH{z#H!!+i3pyWl!>WhihjUm{`TLtwDdUm zj9nRtSr&!QFq$C9O_n~plFBG=sC%o zFNVm*JMXa0&fmM>+6Mhgb^q6`x#L9BqVba=mNPB?D1HMuP#E2ahv`viOGPBGH*l8@ za)sc#|K!;Eubwg6OM70W0@k;7mF{B#LU36gnb}?M_w{|&K<`RYY3iz#)Q%() z)ev=V)dkIdly(P5I%gWyzqHPhfCu7En)ov1EKOZE1EO{p2)@pc4dg%)0Bc=&S>}Xm zaZ*;YNL4)FQe>l_UHyl7>%7e*zDW`eD(I831W{x`iXSe zM@T32)3yg_X41n!&@Bo=MONy9Ke#L(qs|VwWRbQwT^G9DRxCemM0|h#2J2hu*1@-~ z)MWlN6X^%oUR5}6QaK6dP{!G+=Lb^nn@6qKRL%*6XgY(0(Kj%w#M+9tEeK^lq0L8_ zrpgm8-`!|oe0d7IC%2PA9BP327uAlFFT3nco02_3_8l}OnqGK`S$!-o8ol)OEP6=Uy zx)i04l49TB+}gs07h0>;5mQe!zUrJM>G*6)=4zT%>-sJ!I6px-J3?Qz0tXo>vT>aj zaC6PpYc=%RIJj?6onE!?d#MG^q7f-9{sjnm3<#(KwNc4ZR2`}74{7v@Un%r4>a&l( zq?W{_;FI1Obz`^Il|Y-UVZM)7-aH|LG^%9WZ*0~eIS+mCpSH|%4WC1VG~_I3|AuGv z6wK#}GHO`N-XBjOj3?h@affaqQ<*)7(OBORC>DR>VFyCr>td>Sm;D@lui^+YL za1S7jm=qb}=!3J@OV{dDPv$8rJ%HV>Ga@s}zPz86QmKiw2@S@4{^%B9-Jz(KVp}9* z|NcFdle!*3pjM|rO&}htuRER||4q%VNp>E5JC7y}4Go|7*&9wyPEuV93)=i|-zM_j zA@zGaKVDNaGL{UjUf&;gnD)n!HM(q~*3{HEZ4TkR!o#B!5Fm~vc`K!-NBZ~g-|=j%X>Qc72pVd@034aeXR12awIN*DvxBpD<+ z4i2VPEui*n9jLgTXfavGrO;s3ZA+Ce59Jh*axKo%h5?T}w2Ij=iw%xUTjhowBB`wU zQ5>dyjhFg@o=3kH9Xn$E?yrhPXsM`Rze&Y*bBzgweEl=%TiVx{z7> zy!`mC0k|sG08EAtJXoC#j;nqD29r)NXAN5(mfuBS^!C3FhzUn0DG{-FxH-M~8_gz! zDrX3*RaY&16)Heq8p;Oo{Vo_<7C!~bEiPnf)7*V(k(t(N$PocP&^^fIbUO6g1&{Bv zw@7BI0A1pbJNKP&${0cp*fdssm;D>Zjz|3a>*M~3Z_?OAM8)aP-{M6|IDH?L^K26{ zq^?6n@%Dqpe$PT`A8t8^&S8IqQ>)Mv+;Ml#gkd5>we0z14Rt!}I1KnPE$az&Y(sz2 zXT8cLwtz`XPR^B##vd5o&^3_HFPk6RMej|8lR;b zoUm8F(EUNJR=uphg4d@{0-r8+V4{0f=A&kIPsZP28vh$ekdc#nZ)WxeD7;E~dNE~X z%>6$t$H?tg+sQ0y=h;|{>WR551R)d*^g8725P!@jSX23LUgRepz@wt?C7I^ozMu4(vULLb{B*3uhZiCRm=iHGXy z>eg1x3O#WN35jx8c7kq~W}-gsm_|s&j`cg`BZ_{sf6$NN#cu{l%WReE9w8%0}T%habMkBkj+T8KvQzT~j|;BQT=!pn)X zT~sXE5^VwZsMQ~Ps~sJS+p-D@TYgXHiq>pvy!HzxA_2FXY4vA!mVRHuGmU8nV^es# z=3NwjvQS|6+EZa|b!m6MTUatvs(+;G@SOA%)1ZAM*Y`W_(G1%cEJuem z->m%fo#8eq`71F!<(t?3bC0QYSe*e6rx9i=zka?Yx6EDY;daXi1bY9z9qSkP~CXQQu3S50W_l8UdA0Tx)0LY4p}3k&+ z5q<)N)d{+luiW6Lb}IR9S(rN>WXatEY~GY=xsT!k(A#JEJXi@+25+c&-h^Nj#)@9- z2D|!&rODxMNfe3MJGsiXD`I>WozOL-)AUB(O!WTv*ZpUk!yH!Q6ro{Z#%rBHS}h(d zcD%Z=p;f<+cA1yF@N5;`pR>ZMSXv>ehW=rP!=eLl$8;y^oZ<91D8sIIW`95Q-{W3+ z`R--sEZ-Z-5z~G@|+OdpWV7@5V-krUL;t#NaJAL|U`1 zaQx}Z6rq|2?C2&3-ecbWh*;!vC;sukR{a+Q^-RZ4U2{6+3aO8p|N2Am$BBzbG@C+;Kha8lIN=5 zT#fn1*cLivYL*{6*0{@!C+;oEuX4g>CggfrGKI~q_trZj6gf!}uK#f_(N&*r-=F0% zeqy!1oZ0YqgsF8BwjN3vne7^o!`7+Zq8#heH1Bkj-r<^o<=sx0RDm+Dfo^oJA+k_s zjjN)n`uMkFsnHqE=lZBOTiB<{=h}f(#CMKJLs@xXWaPb;mR4+aAdI_ejMo0FI+PId z!;W_6^Eghsc~17SApQ=PxgpJ89$^NpY}l%0l#j7#Tr4INHEVbwmDH7;u!ZhYgj}gu zP%Vshn@Rol`98#4ck%qprFUX$x;}rJc>>lkIR@0zK=_A>haj$llG~s6F2d>V!a9ab zFg1-%RP^<6+qFS#3mY9bms-y($=xNf6Xz<%O2bH1`4coXCm>>Td-X$JaY^IH7FoMM zKXP_kyhU^9XGF4BLt<&mp(o{W9k1w5+K8Ej`|vb1R!y;f{u25zj)(K>z19Q~oWm{? z)z}9@tc~%!cmDUfxVX60pDu0wl&230TWm=8OOTEPzp8 z3{jO%x&N(?=ix%{J#&LyK2|4K5Bl6a_YhxZ13GKP#+=DfD1CRDd*@l0HUC9$Lm5trCO6R21bC0TawaO%|3wwvj?~OmkA+W;0(y zvo)F(eS5wwc;$DuTg2yk!=B2l9gIZoC3e{a07>|9$20kAz_ak^SAOb@O85O48F_h0 z8ym)E_kCKy&B#)a!mT${qBeg(FtcTda&RPE-%crth8a}$?@WBdCLxKAj6|L|$qdTP zCG^*9)%(T7>Z^@uWmsBjPg(Q)&GU~lzpG)HyLl|j> z8lAcr4vUMjC1hn$b$;4?2F0IJM1)*YQgUE$u&ds7_P?a05YV1X_h-r*eQ#?@j<=ph zcXq7MNcpf17aOEE`eLyO2qOAoNvHut2Z-#~<^HVm)(BCVZUcs($HAIA|7@`;l5`xY zGx4y6`$iu+x9yCadmyG}MMj4#>t8&Ab`{nCv@U;Q=G0Cl*qv&R*e{F2Qx_s2%@B?9rA9(&^KTDr zp+YT8DJqf$GT&CtEPebzz6Ies>yGH_mJs`GCvNTlalz<7E^7BivsJe^A%;P~g)mS#yy`K3u- zo2xu_P5VyNl@DaND@|S#YOFe`CMYafh+VyZv(WFe>F}L3O|FE_i~IITTjv2t+W#D?(UYf_+5SymWm_&%Bhf*z)kDe zUZ}U7`)N<2kS(+eG4=4^9U2pZyoI!iFaXY|7JtsBHMK2BJ9 z%ZcfB*68eDpoaH0?d%!Kh3E(=QG_j@!_cO9`b@-H-@m26u&R95Ce(cB13 z@<0D`7!te+uzYox?ex6~qW`$b@?8T=1-X2T-Iu-o)kcSF7ZS#{_;|p8@FrB9Iaf@3 z2&P`x11ltM~M>9LSmkzHqRe%)Xnc z7>h;I6u{nMC2k0#Ml_~D$kKghg8&wh!|3?y)yj5goq3{n(}}ro?(#%=!u^Dietc(> zj5?a+uI<1O14SyenodKK{I*W#QuMx!q5t@Fc?fAZ<3e7O>8h%^0kv}g9*!GNk~KYhpjb2hT%?pg?}nQa@v z#cxXmnBjlx!`<^R#sql27s4tfpCcn-Wr4Kw+AukehXu)y0ju_%XL8dkns`6-^pDEd zbkAA`;*A!SKQ4y{rHg}{c)DjRVe}pzBs;PG2C}IWvienbLnjJH0$m6?sBgx7RT5vW zsPUKcZ;OphT+1_z9^=!({MX&K*Liu<>I}EZWeDYDx0g;+Y^_8Hx36V?rvX z&g;L8VlPODbw-8i1PsWP6x-AZg@5*Bt~uS4ND<+r!SvH9f9_8maC#4c7cXbatb7MbMe1XA&g2* zkwFbTT(Uz`#`NezrkyLw6>4R*m{z=SsI*hR5(zG*^&M;YSpKxTvw)FXrNTDF5 z;zGd+eijpo@rz&@@I_3P3S?$b6|Ap5jI+DgoT+JN8NvaQ!T2;MA~TX9gwG&V^A_&> z-*)ZkHqt)B}=mX4mPKIPB_7(*28zf7|?zv;)4M(B`0j=rz| z_)7AW7mQQ-GL;@Fjse*hZ~1+>Dc%cz-8cb$cOprqBAD-!k40GaT2~Rvzt-x}mRrD4 z6l6B$ZzOMKebis3%J8j*(TLPb&?uF(vt$0du|cK_)F2MKId*sigajRs5kH1dZ~YKu zU+cZmcXV#~&ax@;>}Qj&SzJH!XwI0}53>ZWJ@2Lq`#Yp65is^b&*-vwgdUrEDnUV# z`T697s=wZ8_Jez+!OvC5ftPS&d}i_ANiDqYDT8UTP(t-g*E=0A(#Z*W!YG~ z15DuYpc>(~&-i?wJ()K6^CQ@)f6WTX_pM>VXxc(((8;Q;vGL&8SU*!`Yp~~vH&W(X zN2w0~M;UYpP~2z`Ui(AMuiFaK73IgF@vH{wO(0Pg0t`4jnv0(1%T zpenmxOc=g?#@MCAwdyd29?hjVX7QOW6R%MW?nMnwgEc}c^%?LpP4_*8Up-btbo;v1 znyLnaTlkVI;J%luU2Czk(k2*=Mih)r!n+2*h1UUxe`9?#0h7xg&OBFzdTDr^8 zc*T1YjSB>{G*xt`rQE8EJy$Mh5*~Ft`>$-*T8=gQ#<^~f8iPB!0kTy>N=g{eea$Q_ zskymdmFv_?fL}}iMf43V?cclz@-r(4q;@_AypD>C3%5uyM+!hQPA;wlKpp@=1dtyM zXwj13>^V$+A^$1W96LV#;NZZDLCO~hl=gMNvH3g>2=`{nqX8^?LqoHEyPYEqp5Asx z!m8I8I41f``l6LjmuiAb!X*vDmuounU}_)BPL;8*CkY^8gy0`}v!JBbi4`qU`hUfS45h=fW0 z>*?{nI^fx#l-~uOl9DnqK3*DJiB>K>@cyV}jI9#aoB^pEai-vwJgicMURGKp!&(i+ zdiVsk8Hj)cU`%lu(GZerMT1E}@T%>Wx-hz11d(|ffyEN07BXYjZ)*4{7_M`nk#z95 z-oPG>!U@6*uchuF4nQdm4r3Pl7he0Qk7=w7)y#xUXuU+TvRFQhXDjcRzprYvG zAL^8&t`H;+{|B$>*l<8BO@2jUr}H`Izv*0R`^hbDI#Z^tRcDnjbt`hS1~>ARUlrI7 zj~A+~z`kJ5{9SWP?t3EsLd^h9c6f9segK(5_|}0G$X&Dsls4ReN!M(%($jao7%f16 z`f&#g9kkB9=~8Tb{H=4&wc>pX-b@akD?4VxcD>UTbU*_QeNV$aK5PB}Y~aN$pl4u6 z_??!R7z~IYH7o1rhI_=tcG^a}lNg2Ew3iL{26p;hg(xmn;`s``^3VDWyvC+|~b zdIPhxq0wqJXPU<;cdPZBa*cmmoXTp|`#R+}CON<(Ji9#FNEoDReh!o0KbLIa;NqV4 zkvKNO8$#whg@7- z9Cd;a2s|wB2maGK8s>U$m6>siv#I#G?M%N)6SQ>wu(4$~#er}77(S~!RvUuB5@3Oyt}OSKDW3yTGmzXF#;N@!nV@n3#0$?B}%nO!TVnT8ZkFA zFbb6GE(*8bHHu3qiu{ z>y0K*E>a*<@1L{d{a#S84hpSDxxw$@^EubQM8sdz$N~m6O7rWbzl3uG4>@jf8l`uA^bLi)9P$()I)X|?ON zg4@ox__Q}Zo52CRVO0Lf#grm74d4lao>IUsdyxuA1wGJS6j4n>KZGxZFBNVuiB9vj zLoQor9>%a-Bib^@4}ZDY{T-+rpgR;1a`_&ww0UjBzWsUyJVtU55<8OxPyx>)v{2#E)fj2^%&$Y*y{E22kLBZ*oAh?%{!$kscFIKY^WJfD)(ctjb zJA>f7fc|O3sV+eSq);Y}vd%K?S~b7fyUTsa^-OTYK(xyR@c`swo)?nb6M<|VN|efi7vFBh^8a{F7_ZCR<#{DVBYUW&3l=$9}j_b!5y6T&Jb^kZgxG9(RRI1A&F zL50AAMe(g*DOsej!V0ePZ)y0R{eN>FI>uWB;#4&>!uMw@slh6MD=^pSO#RbtzPl6+ zvQ|H9(DQJ6uKpMfEZZ0&E=1tqO32B@l$c_W3q>k_lRAY6fOY`Nk;3$;W9w=7)UoYK zE5AmiLF|jDrv7aM*noXtJHvrJt-iZpVPWz2<*cGAmkZ@dfIos+LW0-euq**W#rb?o zzQ28ZT*>!xTC-+U0NiiE!(ISZGYg$u-`wFUu-wm)m__`bJpx}F4&F_Enoue(>{roa&TeSA{wiAK9v_qSq zB)p0+aC2=q?BsgYt^djQMmSWsO^#er+3WtW;WR$rp=ZD2aU_GQ@%ibVY%P}_RXUzr z68QP%@hx7bRG>_H$A*K}M+{;_X*q1W7}fjwX!%7S@fwFGj7S4zyQ!`!tV$W1oIDN= z31e-==U83n3iL%W@H}Nby#s6r-J3USC+j_tQBf|tU+fp^{-+N)uGW|jtLf^2yl^3= z13y3!(TQJSK^&GEIe=AEu-E=tzzrD`$?ZekdKT)bXLT=EpPw|< zAFf^pJYa*jeMOd2!3vq`Q_^Ni5~LXrRN5jdVV*Bji$}-e9JawYzx*{pss96 z3Uq0&lP)ChdjtPlb4f?rfQjxfsQT))!*cU_f;73ajSPc!hhtlBeE9+* z#<&|T9wiWJ6?+Pwn`)8dTx|GeixdcIPBiFyCLlLTgcyZs)^D<%Yy8U{^ z2N?t7YWMPP=UY5GU?<6G=fd1mlap1>8&ZU^QfZ^t0QQ|X|3?D+7Xm}yi2_%-ECIZ{ zygcP%{6K@fvv%3LRHPI@sR%-CcA6M5iyXwOf?4<<|~m zW4k9X>5;O1SUGLO;d!K&)_%U3W*9EDVVIcTYhIWD9;j#N0N=|bcx_wO4gYu zy4M6xuJ?OAEHRVtd&_kGfEJjW=)TyU0)0I=Kc6bc*Rn_hXXI-5wB6 zaK{2=(g+w>3gqp84e)}2l0A)0+J{wKS~(|vC7Bm{)wny9@wp!bmsYHDw#9cT3`*dY z^V<6#b`h%v&)$4(QIaH`P=n~+ae${E5y85eD9xCwG2Av#$yJs=c9*D^YAE!;PcsVY zrIZ;BxefEMYdD8d^+e49RJR8&+_>{x)4t9@@>%*@UCCl#}WKKno38G}mAS{ZIuuk+Hu&CJaB z@{Ou9$0t6bMDMx8zQ45xkMH5veBGKP};#wGdFi zzXgbEjV{hfLT~OqTFlRlkR2+Q&ei+U?5om5(v6Q>Fx;=-!2S{lU?ny0=}^`jbfGw) zf5YJkxe1t)sE-071^B~00gr=7P%w>N@Mwp>@XL&kpl5n`|7Tpf6R={_-N}NIQEg@A zAOJ}IE_<$)tC;UH1}=w%34Xemkgm3!WnF21fww*3=nAIZ>W`~{Ke+D;9g%N;c11q+ zskQjM^n~7RYRecnzhhno5WfB=g+GYH$~CQ}zxfz{N3gt+#jdG<-?+IBeV1r$*C^wG zB5u8ZEj@l`q`o{Qi*=QPSN0BK)uGYs^WS9|B1G30|5@By6z0#t7dYcQgtwnAtO(H% z4LpxP@CSLHZ&3;f>0RA`J_PX9v^8;@pa}`&2^la|YHI3g(|(NjUnW;-y?ZE-miy&w z_!HslC0r*+T)d~Sf1p#NL^0AwD;Ok!K)#q=3#;>}nm96hDdg0XyPZy@ zTP{n{49{1~#-9s;4|VIf;z@P!f7<=FL^=~#UY*Q)`5BN_FYg9gJtAZy{c?~lXDvn3 zAVFvgfLTILvqY;B-Y%O@OiVar^)GKUx=i0^Xmwr< zE%vCwPD8W~NnJa@AI^1795RR6H7w$sXV^oXIXY^{-&SlI6$z83(hJf=qQ@l|4-tg1 zYl?5Td%{*SVO1#H+zAJ2-yBVSAV_=#jeXQXCx;j7H*h_Y)xxxjn6*Q`K3-jgF@i)H zUxKQ+ZPvEyCU9{uj|(dO`v?p&EMnp)Fj_?g*d(Ya3g!NWYI@m9a&l;x$uDzXU>f5D z5D#1B9=0d!ra#s+nz9D2mCu{9j9$4a)T2+wWmnc2+h^JI znQ+~OMJ!+s-2>AOF<3uyesgHgCdi9|8Z+FSeWF(DOnYRJ{jZH~XzOzS$LTf6|7eAY zQiU~+j+@IAmB-Q2XAo%oFs-?{kdB8lsKrQnP`lrKKH$QQ+u1(jKa{EIZVWp7mkUsKZT?hY#(OF?IBC7 zf6j)fI5*1`qEo@LSZA`PaLye;_;Ty0VL-rs-~J{Fg*X2dBInN1bz(H<4{cm6l#2Gi z%2l}ZuzitoDHqp&Owe$a)=q0U$na5)QW6dGnoG)$aRGnOXI=_L+EzYlOR_$~2}to> z)lR^Hkjix%whmVTKl*fi6x-b`5f3xBj9SAue$E{@&1_zb-`VXttbK6A3|VI#C~5I+ zzW;iI7sx_8(kQI1uE8;^g=`SdGxV};!q@NT-@N5DR2cNBUxL*jgwv31V1PgUfr1lP zKZvk0HZZW}=l^uOt&bswEdxOe1hoHPrHuse@zzHGMtVXpMIvoym-cC}VfnbYrpnL1 z^1BvFLA3bFD3R6%WS-3Y*1ekFX7}tR77^P#S-@zf1&=u*+9P8xL)@3gU1DK*cI$g! zfA(BPN>VprBllSIXJR=b&D->@ZRBpXE84myn%k-Ki9U;sY#GaC(j~7Wc5GYyHQnf`Blms5wQFTgT$q(jj z=AKk~x3VQ`w(DgdD5{;$r){9=VPz@;WXcpx({3m|LPIcGR@RL;_k>0>;*S6!=nF^@ zTKxsc{AMiRY2$qVPp&xJOKI1{~u zzBKE6hrmO20%A|vSgq%=0UcyG76#hCxm$pOSwW&G>Sq8OvnEWpYHl!B@({Zaf zjj(=SnT1bwzZnNpnt-F{2V>ZQ#7@v2u1`EDk21+a)D3)FR~-x`j7}Eapa1+s{2v-W zuD-P#B~4CEZFZ9b*bUIZJvVa2io;Tk7kC7efUo|FfB=5F+}oVIM213LbppJ*f)S#E zK4|HK=@=-1hGXMT0MH=-2(BOcfKfYrk@%DJU%%cLtswG4Z-&`A+;;C@fZhYB^f1a& zfGz!Q>C-61J8s)vnETsu+`d1<*R(A^qC0(74@8q4gWQTVS(ZpuW?nEQHVNMiXE#@% zEZ3n_q~`(`covLl`^xLL1s7-~8+EBxUS5rMeByaLccA$Fcp9Z-GyM1eX90kJi?Ql^ z9;@5o38-OfF98^5Ww)Muv()A@yFCkJ4dfR=WoP0+T@V!tooMlNOvA@c=dr~n=C8kwhJz9YRWd+z#w)Ym% zN;c^B$BbY8EWH*9< zU3dW(>o=zxK-=j8{in)hQ&#AFgo}{d>Oa#Dzt(#qH1}&$KpdHs13w!(iB?`hQZnqH zGi?&eMt^+&pJHD}Ef0~Vui==lk1oD~ogxMjVXCkLh)t{2_BLGw-RWWg-%g3iHa9nS zRy#z&6atOU5}3O>0q``c0)f;DYYTA$3$Y6e+E@KlDt^;nBKsPzA}K|WppID4il*`E zBmoy+^8VDUIeask3^Mb?fZI@ua8B?vT_mv;yzn0(XFuChT0=1H$QK^yi`Pyyuvllk z!TneW$aK1}4=*O3A9Tv8dDsuK5@31+2n8|VB_a9r zFRCOMr04>w=2ub(j75y$?>_}-__=krH= z&+B!+&M)V=&NJ@&zOMKCdSBOLSj(MZsFtM;K&ToJ9|OhebP;JBhhiKJm%5?lzvwcx?(Efm;o&45SN^2=3fO5ZIjk zIriHdke*?GI_g}Tq8O@hc03eX)N2jc3h=9Dhk<8+`~|DQCrxE_2*P7{ZS4-A_})s4 zNK=GN$=WxinYl-H)pb191EYH~i5-j&6tnj^9gqB3)F7gSaQKmM_vb2OqFYCHrLB3~ zGYqb}HUhDcBLeh8>zAny=n<04lJ&1o>il`Ba|9wusqA+e$=`{~=DP$KcCU@1cbnqxrCiexCyqfoIp+O-tb?+WLJ%eMO}O{P!4xqfj^N)kco6gKr+M-``HhDKfM+qO zrAFu#bbv_*2dejal=i2&Ih&xP`N$u&Pm{F?RyHxkkzi*N=x%yuVvw)%8HCxNz1VY{ znBQF2?*<;Cc!O261xF}ijf%ymS@Mz}ssQHZ9*}CPYG`}};*yjbwuE>Z36Nzl5fLhc z*R`Y#iY9DKPFp`Ul{7@5k*}qKC*1X0OkS#djSstk*36x z!A>wAG6E8FnVo^aWo@!n(7E}x3`u``t-!kEEFlTv$gIz>eCo&HxlXjQ3^b?jOcHek z&s2gSO9t>9;UMcF8^&_eZTgCbhX;Q50*`(9z*$a9Kvc8`tZt8uQT|zDbMxr`Doy}= zRtES^3=oz^Nyb@D^X=ic%V)tnnSUic`)Rf}E+4O-!e0g0`>8@9#8oCeJ-ufwz%!3= zVH<&m0*v7z$N=KYI)I0xW@CHBtDBYucv8QEi6=zNEqaVr9%A_1%HPvNyYsWZw>+0f z!P27U;D`q?lTONw&!tK-l19?JVxP@NV+o{BOhfL|_YJkZFs8|RDG-Y&!@j6xLhD8f zne=z4{5Vn$-`Jxir8|xRk}qUBnK)+Z>NjVK4@Z9v6oKSlXK_(oc>kX)LCkFHlsq zZFX9+<=u^H@?HaeT>ea~l2&p(e1DxerTMj+BW!3LM4NuU8drh;7s6BWAaS4!VCpyE zRXRI6uU)@>5g)gm{n^vUGnYBp0+Ey~>Lu2&904U$S5`;eXM{lgRVMXDfEBa7*lyrq}mrUBm_4Q^UC;3$gX z3qie(!s(WT%T(Pfpia0O9+$k#*=H=!=&=XnAx6VbTZq9b`Y5K}O*oi@tg(bHgDktM zGp5ksdWmTzAvvQE8el*;ZcZ7WSOgvvq_+Wm2^^7nx|Azx`QeN&2k^#UySQMUH0^9J zK01sf{oa(!@+}d!m-CxY-#Zm?#1}gC=r+EFzY-`E^mhrFp}l#vjlx7hpG#jdQNs{q zDDZOO3&Yo7yA^4%W)atKmL|Hy8Y8zr9W_GLi29xOVV!nvrUjZJh-G;AowJylwu(6w z$|V3;+O?+xDhJ{yy_DSqA&Hy*?cA4FNl505)p+cecl|AZpc*f&6U}!tJL3K7XYhpj z46lkHMuKvk1bX^X(ur8led;oiBf^kh$gXfvqx<+fvmCcrF7`Fty-IA-iG?K%=`fUe zO&{Ot={`cKL129bCdesR`(Ln8?%7oRw22z!X@2mBEH^k!=Yt!_=H@_=6|bAr2qVh` z?=+18Y*_x__Zt?c@$ZgTy)b2ifi#DdR$OG(&-xpZP@L|e-%ENGURDs3?7qcc&2gg0 zrh87IjG-8IdsCw@4DKU%L&zrlDD!Qjp%1C}{iDgDq1qaEodu5ZwZ&VzzyBR0gB0C# zhb_!S@~w%mZ?^f=kge{ej&vmgZCQhq+emHQUoYU>zk z2Nj=jLxf2gS)^vN3gqpIkjjESEPb)l_Di#A1U1GA^TF}LJprL7P{Y(F(X7nIR>;e@ z;C_M?sL~*StCbvl&Rb9kvv+;vWi$AfOj&F;=B=q+KIeCmRkyXIryL#=myBI`1kp?@ z&HW164$_f@i$W`}FiUTF4Zo!h-F3q2S*kWCJNcvj+P{R^4`}v2ZLJ0qtbCvG#$|6$ z#xey}+F>DN(h7me)i0NDc|yytaXu}pTN2kP63&{l0Z+-wY{H^Tgli z)%jG$7#}J@C8Qul)=l$t*}PlE`Fu2p|z(`kOYo*#|ZsCo5dk{mD+ z5@}DadM8&fCDi{SS0YTR6Ovu`cORY6&c&~tsGmQSxS9p$@XMPg$VqrrzPZg-qhNcOk>ff~CXmgT z8->_$Qg^oKS58*w7q>**i^k;Nm9|!v!U@|*w`O4kwz%r#()@)(4qE7t6GI`v8PnmS z&|2901G7hG-JzzQpQ70M{Un%}IEzhF%`)`(&Ihm9Mkj&opp(9IO<`pqwv#`S{hZXTAe=`!LD^(x z6ajaW-yYwtm7~vN0W!SVtG_cG>wVCg;9?SY7J;Ii2%=ybd&7*^zS!ZC=*Vpwy?LsB z{%c1QoiJ1I5vbpRZxMll3j-`N7lK88HitdG0i|q*ja_B4YOfKA+Ho6WCMbTHyy9zI zMHu8HSJ5~#x%`uNcaX;WgziBMSUQal&xJ$?o3s#^M;!{yT*lExvR8V3U-O~4XhzzX zHy%Kq@<4{Nu&Hz?!AcaTfNc#$!H@)}4W)x?@FLHpJ?U%OXp??fDIOCMqQ|#)I17z{ zRLtJ!)2s@gen@C5NIks@e$G}|)rjq*FKFIu0T zeqqI62UJW&GEC8r&rM@Lr;GpDskKi7CBW=RwbI9~)b0Wg_Dy?Ok0zVTCxUA@FXXi< zzINrt{KZ!10?k$t5A!ISA4-zQT8)HW9YG~Jr`%A4n<>X+MMm@Mp0-lhic@|a%IiBe z^KL6umb*?WH&^nVK#H7?aZ;!v7QzYb0B~CVdm^qYF1{`otf`ASWO~jH9O@| zw|iW==g)F~eEPaK;bXzTI7kTici2Cmqop~%*QNt8Wlkz{KO?~hUEElBhqONjhx&v-Zd7}eFCLOoDT-i3u>YJ8n4S!#Z za~P+t<-I{XI2oos8W(@>dwmu3?7P_Zgd6{u@}07Th|zRyXCoMlX|A~Gmni9~u38TF zBWV`%IFI9c`zKGSIou79F3;*n93M;x+k{u@m!r889OgCrvM+4A?oS#%l!m?$A1^T% zg_l!L&mu&j>o-&9U9xaVPl@CwZ*QX3Y@u_l(8L0Y7K20+>HbuNKb7WELsvBR@=r8Y`x7 zx$GH$GLiq%lHXgCdyaZ{J6ToIpl;b8vb3NwCbg~f{`uys(U~g>;erN&W}L%FZ6{$_ zbPe%E|IC4%c*r=g-bZ-b>Jx3ykX%Q7`pgbD?*XrYa@Uj3P09->ighJ+y&S|_pDEjW z6J$56j4oU28n@U9yyjI@Z$cHpf+z!Tf*L7=hNy>|H!2%c==T;|T-Io2dG)?GI-34=+sx+Z(^$wVx zsj3M7+B9=PaQ>3h(us+(+vjnSGNvc%9xK89l{~R|*9`nEGtP5N&iXQ*v!sgiN7<~V zV%NU@zZwwjB7pQm7I+na9vgpI&{Vj}x$-q?FZF;3REl8$t@Hz|IOYuotP z7wh-^Y#JWKdkNj2${;KF_=k1Z??vej)kDR{R%?DkB<(gG%FpV*0Z;U$tXy!kU{wHr z(n5wufwTO+Ldarr6aj&amc4R-7HHyo^1WLXz}6xzBZ(1*qtGV*ycpqlKM zuOC%yFZOQ89kkv!xDF{*C7Q7`ze&96-mLLts3UdkIcME^-xc4ZaYn`KKmT*GqX^6_zkyx1OQaMsO!B!gvgDm9x|5KKz7n+OXlmuWWLx(d zE)BmFXn7}1MNIAEM==r*Qm#41c6%7G%U=1&YLGa&&gJj|exa#|5KGm)#!O^wd#3cF zeAKXc15sW}_ES6vQxs^=V;@e<=)9?neaq>}xSYaspAARJM-G}Ng6%Q%`O&xZ-I=%X z<-T=s19{NJ!D$}$#-x}&>cJ+er-YQQ??}cju+)S;sHn@b{gLOPqbB?4dsfU~H~F^@ zkFpuKmXDf)h_TM=WP98}!JMM#ajU}c@&X=Yg=s*BZ;+RqN=$6S)yEHzE{=)5nek|N z`-3pSoqkF_%;`3~AhL1UJ`~^! zCLN8_;^$M&kbp=qFPW`yO5xpU<1?gjzhUlx$w-eHnpG2*Yb?5@4xuT!n@%kPbBMnlu@Beg5cSpy$Cp{Ziur zEl0c3vLQEUD(bCD@Tnb+Hv?;{_v5+*9xIZXQA-Y|&~`Kc_8Mnx6PEKrKVb<}88eiS~RbXD5FBhANr zb=kiN00ZVwROL#3A^Y>2H20NEhvZM?r0#5fzfr^fDhs!Mw@&otpp{VAW(M1EqQCpP z{h_07R`A(Wl5qv$`02QldA!X=+?fAH+_=NGGM&Yrt_h;_%w$DZt#+a*3zFz4jR53K z*@ojGeB1FbNMF2(>h0gO-tJk2j9`(bcN4w|Bh)qFOgJIi`TT7uv?$oEKn6(^x_47| zBUJrjG4@|9sk^KqYbjsZ;=7phvx z(sLgXrb31QB58csar9bk{|fx*;;he`;iJ>82a8BGAmCM1%{4v~C)pZ*_^nK+Gi!)< z43toSu#V7VD{=qrwLXf{o7Wg`RQ|`vZL;~I+;IVNQ#avzYhk3wf0jo*)GAccEm7v~ z1b6t`8D1j(nO%Y*i(fw!6tXG!x&zDcQ75#;Ljzd6$`2-(9s1uN*IsN9==`i$AM3aaQ@tV+f8MWZ}C>#`I9H< zGc|=ywN4{r`S&)-OPO*H?;AV($AdhP{T+dG&`*nkxx=-PwO%TFRWg<+N%e;5yhAaqqnlI`7jgrwIF zOW#5pCjqoUY8}BTDH61qdKpq`ou4e)>{xdX8=FrX=usrKg_Pu{;gVYCT?)TF_k2;4 zqeknoylc6e=xIuFC`;nm=AEKP`PChnoE>%oNRtvRiuHUAi=z5R{_6I)mAQymXAwT8 z*LRqqZ>ybXwXJ1H}gsQO>Lkx!fs}dew13IyXU)4Lk^D%y4bJiQA`*c zZhRsjf+h{>4&6&z_?V0wdYMqVeMd+wi{jl}J7=`y`IS)wL6+T*Vt0y&_be#kQpbX< zVRcc*-)|dtq+eXjbZ`5*O&ea1!f>P4{0{Mx-;SXjh=>PPhWEA zz2V68Rf%V8dO^oqugA}O?JkantZsYP;H>B5-HWjG3w4TY4lac9J9ZELS+o700quz)4Y$TM8pGsFifrfkyTAu_rrT^>(Qwx1|!qT zl9Kw5AJC!L@^t6Z8J)##>#9L$qwTkpB?2ZAhfwI z8#D14v9$cC=CnKm%^I>MaxbM8YVnKvKl91Tk|_~j+dcAe4mdpNJ^W$8JF{+xF$+Qo zE8BiNxAdiJh393BZOqPZANorJ2wzj*GIV-kry2>rW%#OnDJN8~_)gu&r|1|mYqlRV ztU$)6Yb!YMe%xP>wY_N05*Li~G;xh|DLC7iq(wo9hzste)Kn5$BUX*_4e-d>Te$s~ zhMgIh_qg-7REMl(GOBzh689CgL)&bVF{jJk`>nDHN8hvJ~>Koa9dW&Me zzu!RC|Kjj@T_U%cR|fYE)O(^=?9`B48wI))j73G26AO;M_8FPf(jIGf!!(=MBiG+m zF7OsNp0=E0fAJV8^gGPza~je_mkAMP2FCx%x^=89rZr*|@1_QE%{({L=nMbNBRVC< zX(>5qdigJ(ak!6PY#t4>)WUfP*KP04R+?bHFIvraOygf!d6+Wju7-{;I=@(S!Fbhf zEumW`>$3HbgZXVO4*Myyn-Q3%ndO7Jn4C&7nIJ9X>*N`enw_!NDi-D7EuWYV>~w0wRI_H=qkQpnu~Uf&jt~h=Afh<9}xG-~Ufc2~D00rq^64Ld-sbz(-R} LPqj?NChY$J!QFcG literal 0 HcmV?d00001 diff --git a/addons/web_graph/static/lib/flotr2/spec/jasmine.yml b/addons/web_graph/static/lib/flotr2/spec/jasmine.yml new file mode 100644 index 00000000000..ff70261dfaf --- /dev/null +++ b/addons/web_graph/static/lib/flotr2/spec/jasmine.yml @@ -0,0 +1,48 @@ +src_files: + - "lib/imagediff.js" + - "examples/lib/randomseed.js" + - "spec/js/flotr2.stable.js" + - "spec/helpers/stableFlotr.js" + - "lib/bean.js" + - "lib/underscore.js" + - "js/Flotr.js" + - "js/DefaultOptions.js" + - "js/Color.js" + - "js/Date.js" + - "js/DOM.js" + - "js/EventAdapter.js" + - "js/Text.js" + - "js/Graph.js" + - "js/Axis.js" + - "js/Series.js" + - "js/types/lines.js" + - "js/types/bars.js" + - "js/types/bubbles.js" + - "js/types/candles.js" + - "js/types/gantt.js" + - "js/types/markers.js" + - "js/types/pie.js" + - "js/types/points.js" + - "js/types/radar.js" + - "js/types/timeline.js" + - "js/plugins/crosshair.js" + - "js/plugins/download.js" + - "js/plugins/grid.js" + - "js/plugins/hit.js" + - "js/plugins/selection.js" + - "js/plugins/labels.js" + - "js/plugins/legend.js" + - "js/plugins/spreadsheet.js" + - "js/plugins/titles.js" + - "flotr2.examples.types.js" + - "spec/js/test-background.js" + - "spec/js/test-boundaries.js" + - "spec/helpers/testFlotr.js" +helpers: +spec_files: + - "Flotr.js" + - "Color.js" + - "Graph.js" + - "Chart.js" +src_dir: ".." +spec_dir: "../spec/" diff --git a/addons/web_graph/static/lib/flotr2/spec/js/flotr2.stable.js b/addons/web_graph/static/lib/flotr2/spec/js/flotr2.stable.js new file mode 100644 index 00000000000..8431b4d9ee9 --- /dev/null +++ b/addons/web_graph/static/lib/flotr2/spec/js/flotr2.stable.js @@ -0,0 +1,6865 @@ +/*! + * 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 (name, context, definition) { + if (typeof module !== 'undefined') module.exports = definition(name, context); + else if (typeof define === 'function' && typeof define.amd === 'object') define(definition); + else context[name] = definition(name, context); +}('bean', this, function (name, context) { + var win = window + , old = context[name] + , overOut = /over|out/ + , namespaceRegex = /[^\.]*(?=\..*)\.|.*/ + , nameRegex = /\..*/ + , addEvent = 'addEventListener' + , attachEvent = 'attachEvent' + , removeEvent = 'removeEventListener' + , detachEvent = 'detachEvent' + , doc = document || {} + , root = doc.documentElement || {} + , W3C_MODEL = root[addEvent] + , eventSupport = W3C_MODEL ? addEvent : attachEvent + , slice = Array.prototype.slice + , mouseTypeRegex = /click|mouse|menu|drag|drop/i + , touchTypeRegex = /^touch|^gesture/i + , ONE = { one: 1 } // singleton for quick matching making add() do one() + + , nativeEvents = (function (hash, events, i) { + for (i = 0; i < events.length; i++) + hash[events[i]] = 1 + return hash + })({}, ( + 'click dblclick mouseup mousedown contextmenu ' + // mouse buttons + 'mousewheel DOMMouseScroll ' + // mouse wheel + 'mouseover mouseout mousemove selectstart selectend ' + // mouse movement + 'keydown keypress keyup ' + // keyboard + 'orientationchange ' + // mobile + 'focus blur change reset select submit ' + // form elements + 'load unload beforeunload resize move DOMContentLoaded readystatechange ' + // window + 'error abort scroll ' + // misc + (W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event + // that doesn't actually exist, so make sure we only do these on newer browsers + 'show ' + // mouse buttons + 'input invalid ' + // form elements + 'touchstart touchmove touchend touchcancel ' + // touch + 'gesturestart gesturechange gestureend ' + // gesture + 'message readystatechange pageshow pagehide popstate ' + // window + 'hashchange offline online ' + // window + 'afterprint beforeprint ' + // printing + 'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd + 'loadstart progress suspend emptied stalled loadmetadata ' + // media + 'loadeddata canplay canplaythrough playing waiting seeking ' + // media + 'seeked ended durationchange timeupdate play pause ratechange ' + // media + 'volumechange cuechange ' + // media + 'checking noupdate downloading cached updateready obsolete ' + // appcache + '' : '') + ).split(' ') + ) + + , customEvents = (function () { + function isDescendant(parent, node) { + while ((node = node.parentNode) !== null) { + if (node === parent) return true + } + return false + } + + function check(event) { + var related = event.relatedTarget + if (!related) return related === null + return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)) + } + + return { + mouseenter: { base: 'mouseover', condition: check } + , mouseleave: { base: 'mouseout', condition: check } + , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } + } + })() + + , fixEvent = (function () { + var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ') + , mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' ')) + , keyProps = commonProps.concat('char charCode key keyCode'.split(' ')) + , touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' ')) + , preventDefault = 'preventDefault' + , createPreventDefault = function (event) { + return function () { + if (event[preventDefault]) + event[preventDefault]() + else + event.returnValue = false + } + } + , stopPropagation = 'stopPropagation' + , createStopPropagation = function (event) { + return function () { + if (event[stopPropagation]) + event[stopPropagation]() + else + event.cancelBubble = true + } + } + , createStop = function (synEvent) { + return function () { + synEvent[preventDefault]() + synEvent[stopPropagation]() + synEvent.stopped = true + } + } + , copyProps = function (event, result, props) { + var i, p + for (i = props.length; i--;) { + p = props[i] + if (!(p in result) && p in event) result[p] = event[p] + } + } + + return function (event, isNative) { + var result = { originalEvent: event, isNative: isNative } + if (!event) + return result + + var props + , type = event.type + , target = event.target || event.srcElement + + result[preventDefault] = createPreventDefault(event) + result[stopPropagation] = createStopPropagation(event) + result.stop = createStop(result) + result.target = target && target.nodeType === 3 ? target.parentNode : target + + if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive + if (type.indexOf('key') !== -1) { + props = keyProps + result.keyCode = event.which || event.keyCode + } else if (mouseTypeRegex.test(type)) { + props = mouseProps + result.rightClick = event.which === 3 || event.button === 2 + result.pos = { x: 0, y: 0 } + if (event.pageX || event.pageY) { + result.clientX = event.pageX + result.clientY = event.pageY + } else if (event.clientX || event.clientY) { + result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft + result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop + } + if (overOut.test(type)) + result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element'] + } else if (touchTypeRegex.test(type)) { + props = touchProps + } + copyProps(event, result, props || commonProps) + } + return result + } + })() + + // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both + , targetElement = function (element, isNative) { + return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element + } + + // we use one of these per listener, of any type + , RegEntry = (function () { + function entry(element, type, handler, original, namespaces) { + this.element = element + this.type = type + this.handler = handler + this.original = original + this.namespaces = namespaces + this.custom = customEvents[type] + this.isNative = nativeEvents[type] && element[eventSupport] + this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange' + this.customType = !W3C_MODEL && !this.isNative && type + this.target = targetElement(element, this.isNative) + this.eventSupport = this.target[eventSupport] + } + + entry.prototype = { + // given a list of namespaces, is our entry in any of them? + inNamespaces: function (checkNamespaces) { + var i, j + if (!checkNamespaces) + return true + if (!this.namespaces) + return false + for (i = checkNamespaces.length; i--;) { + for (j = this.namespaces.length; j--;) { + if (checkNamespaces[i] === this.namespaces[j]) + return true + } + } + return false + } + + // match by element, original fn (opt), handler fn (opt) + , matches: function (checkElement, checkOriginal, checkHandler) { + return this.element === checkElement && + (!checkOriginal || this.original === checkOriginal) && + (!checkHandler || this.handler === checkHandler) + } + } + + return entry + })() + + , registry = (function () { + // our map stores arrays by event type, just because it's better than storing + // everything in a single array. uses '$' as a prefix for the keys for safety + var map = {} + + // generic functional search of our registry for matching listeners, + // `fn` returns false to break out of the loop + , forAll = function (element, type, original, handler, fn) { + if (!type || type === '*') { + // search the whole registry + for (var t in map) { + if (t.charAt(0) === '$') + forAll(element, t.substr(1), original, handler, fn) + } + } else { + var i = 0, l, list = map['$' + type], all = element === '*' + if (!list) + return + for (l = list.length; i < l; i++) { + if (all || list[i].matches(element, original, handler)) + if (!fn(list[i], list, i, type)) + return + } + } + } + + , has = function (element, type, original) { + // we're not using forAll here simply because it's a bit slower and this + // needs to be fast + var i, list = map['$' + type] + if (list) { + for (i = list.length; i--;) { + if (list[i].matches(element, original, null)) + return true + } + } + return false + } + + , get = function (element, type, original) { + var entries = [] + forAll(element, type, original, null, function (entry) { return entries.push(entry) }) + return entries + } + + , put = function (entry) { + (map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry) + return entry + } + + , del = function (entry) { + forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) { + list.splice(i, 1) + if (list.length === 0) + delete map['$' + entry.type] + return false + }) + } + + // dump all entries, used for onunload + , entries = function () { + var t, entries = [] + for (t in map) { + if (t.charAt(0) === '$') + entries = entries.concat(map[t]) + } + return entries + } + + return { has: has, get: get, put: put, del: del, entries: entries } + })() + + // add and remove listeners to DOM elements + , listener = W3C_MODEL ? function (element, type, fn, add) { + element[add ? addEvent : removeEvent](type, fn, false) + } : function (element, type, fn, add, custom) { + if (custom && add && element['_on' + custom] === null) + element['_on' + custom] = 0 + element[add ? attachEvent : detachEvent]('on' + type, fn) + } + + , nativeHandler = function (element, fn, args) { + return function (event) { + event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true) + return fn.apply(element, [event].concat(args)) + } + } + + , customHandler = function (element, fn, type, condition, args, isNative) { + return function (event) { + if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) { + if (event) + event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative) + fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)) + } + } + } + + , once = function (rm, element, type, fn, originalFn) { + // wrap the handler in a handler that does a remove as well + return function () { + rm(element, type, originalFn) + fn.apply(this, arguments) + } + } + + , removeListener = function (element, orgType, handler, namespaces) { + var i, l, entry + , type = (orgType && orgType.replace(nameRegex, '')) + , handlers = registry.get(element, type, handler) + + for (i = 0, l = handlers.length; i < l; i++) { + if (handlers[i].inNamespaces(namespaces)) { + if ((entry = handlers[i]).eventSupport) + listener(entry.target, entry.eventType, entry.handler, false, entry.type) + // TODO: this is problematic, we have a registry.get() and registry.del() that + // both do registry searches so we waste cycles doing this. Needs to be rolled into + // a single registry.forAll(fn) that removes while finding, but the catch is that + // we'll be splicing the arrays that we're iterating over. Needs extra tests to + // make sure we don't screw it up. @rvagg + registry.del(entry) + } + } + } + + , addListener = function (element, orgType, fn, originalFn, args) { + var entry + , type = orgType.replace(nameRegex, '') + , namespaces = orgType.replace(namespaceRegex, '').split('.') + + if (registry.has(element, type, fn)) + return element // no dupe + if (type === 'unload') + fn = once(removeListener, element, type, fn, originalFn) // self clean-up + if (customEvents[type]) { + if (customEvents[type].condition) + fn = customHandler(element, fn, type, customEvents[type].condition, true) + type = customEvents[type].base || type + } + entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces)) + entry.handler = entry.isNative ? + nativeHandler(element, entry.handler, args) : + customHandler(element, entry.handler, type, false, args, false) + if (entry.eventSupport) + listener(entry.target, entry.eventType, entry.handler, true, entry.customType) + } + + , del = function (selector, fn, $) { + return function (e) { + var target, i, array = typeof selector === 'string' ? $(selector, this) : selector + for (target = e.target; target && target !== this; target = target.parentNode) { + for (i = array.length; i--;) { + if (array[i] === target) { + return fn.apply(target, arguments) + } + } + } + } + } + + , remove = function (element, typeSpec, fn) { + var k, m, type, namespaces, i + , rm = removeListener + , isString = typeSpec && typeof typeSpec === 'string' + + if (isString && typeSpec.indexOf(' ') > 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(''); + 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+''); + }, 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( + ''); + + buttonSelect = D.node( + ''); + + 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/spec/js/test-background.js b/addons/web_graph/static/lib/flotr2/spec/js/test-background.js new file mode 100644 index 00000000000..8ac1edd622d --- /dev/null +++ b/addons/web_graph/static/lib/flotr2/spec/js/test-background.js @@ -0,0 +1,68 @@ +(function () { + +Flotr.ExampleList.add({ + key : 'test-background', + name : 'Test Background', + callback : test_background, + timeout : 100, + tolerance : 10 +}); + +function test_background (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, + backgroundImage : { + src : 'img/test-background.png?' + Math.random() + } + }, + legend : { + position : 'nw' + }, + title : 'Basic Axis example', + subtitle : 'This is a subtitle' + }); +} + +})(); diff --git a/addons/web_graph/static/lib/flotr2/spec/js/test-boundaries.js b/addons/web_graph/static/lib/flotr2/spec/js/test-boundaries.js new file mode 100644 index 00000000000..cf6fe8a3eb6 --- /dev/null +++ b/addons/web_graph/static/lib/flotr2/spec/js/test-boundaries.js @@ -0,0 +1,31 @@ +(function () { + +Flotr.ExampleList.add({ + key : 'test-boundaries', + name : 'Test Boundaries', + callback : test_boundaries +}); + +function test_boundaries (container) { + + var + d1 = [[0, 0], [5, 0], [6, 10], [9, 10]], // First data series + i, graph; + + // Draw Graph + graph = Flotr.draw(container, [ d1 ], { + title : 'test', + xaxis: { + minorTickFreq: 4 + }, + lines: { + lineWidth : 2 + }, + grid: { + outlineWidth : 2, + minorVerticalLines: true + } + }); +} + +})(); diff --git a/addons/web_graph/static/src/css/flotr2.css b/addons/web_graph/static/src/css/flotr2.css new file mode 100644 index 00000000000..08c8fe7a211 --- /dev/null +++ b/addons/web_graph/static/src/css/flotr2.css @@ -0,0 +1,212 @@ +body { + font-family : sans-serif; + padding: 0px; + margin: 0px; +} + +/* Example */ + +.flotr-example { + display: none; + margin: 0px auto 48px auto; + position: relative; +} +.flotr-example-label { + font-size: 18px; + padding: 14px 0px; +} +.flotr-example-editor .editor .render { + width: 600px; + height: 400px; + margin: 12px auto 18px auto; +} +.flotr-example-editor .editor .source { + width: 720px; +} + +/* Chart no-select */ + +.flotr-example-editor .editor .render, +.flotr-examples-thumb { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; +} + + +/* Examples */ + +.flotr-examples-thumbs { + text-align: center; +} +.flotr-examples-reset { + z-index: 100; + cursor: pointer; + text-decoration: underline; + position: absolute; + top: 260px; + right: 24px; + display: none; +} +.flotr-examples-collapsed .flotr-examples-reset { + display: block; +} +.flotr-examples-thumb { + display: inline-block; + font-size : 11px; + width : 300px; + height : 200px; + margin: 10px 15px; + border: 2px solid transparent; +} +.flotr-examples-thumb.flotr-examples-highlight{ + font-size : 12px; + width : 330px; + height : 220px; + margin: 0px 0px; +} +.flotr-examples-thumb .flotr-legend, +.flotr-examples-thumb .flotr-mouse-value { + display : none; +} + +.flotr-examples-collapsed .flotr-examples-container { + margin-top: 20px; + position: relative; + margin: 0px auto; +} + +.flotr-examples-collapsed .flotr-examples-thumbs { + position: relative; + overflow-x: auto; + white-space: nowrap; +} + + +/* Flotr Styles */ + +.flotr-datagrid-container { + border: 1px solid #999; + border-bottom: none; + background: #fff; +} +.flotr-datagrid { + border-collapse: collapse; + border-spacing: 0; +} +.flotr-datagrid td, .flotr-datagrid th { + border: 1px solid #ccc; + padding: 1px 3px; + min-width: 2em; +} +.flotr-datagrid tr:hover, .flotr-datagrid col.hover { + background: #f3f3f3; +} +.flotr-datagrid tr:hover th, .flotr-datagrid th.hover { + background: #999; + color: #fff; +} +.flotr-datagrid th { + text-align: left; + background: #e3e3e3; + border: 2px outset #fff; +} +.flotr-datagrid-toolbar { + padding: 1px; + border-bottom: 1px solid #ccc; + background: #f9f9f9; +} +.flotr-datagrid td:hover { + background: #ccc; +} +.flotr-datagrid .first-row th { + text-align: center; +} +.flotr-canvas { + margin-bottom: -3px; + padding-bottom: 1px; +} +.flotr-tabs-group { + border-top: 1px solid #999; +} +.flotr-tab { + border: 1px solid #666; + border-top: none; + margin: 0 3px; + padding: 1px 4px; + cursor: pointer; + -moz-border-radius: 0 0 4px 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + border-radius: 0 0 4px 4px; + opacity: 0.5; + -moz-opacity: 0.5; +} +.flotr-tab.selected { + background: #ddd; + opacity: 1; + -moz-opacity: 1; +} +.flotr-tab:hover { + background: #ccc; +} + +/* Large */ +.flotr-examples-large .flotr-example { + width: 1360px; + margin: 0px auto; + position: relative; +} +.flotr-examples-large .flotr-example-editor .editor .render { + margin-left: 0px; + margin-right: 0px; +} +.flotr-examples-large .flotr-example-editor .controls { + top: 0px; +} +.flotr-examples-large .flotr-example-editor .source { + position: absolute; + top: 0px; + right: 0px; +} + +/* Veritical Thumbs */ + +.flotr-examples-large.flotr-examples-collapsed .flotr-examples-reset, +.flotr-examples-medium.flotr-examples-collapsed .flotr-examples-reset { + top: 16px; +} + +.flotr-examples-large.flotr-examples-collapsed .flotr-examples-thumbs, +.flotr-examples-medium.flotr-examples-collapsed .flotr-examples-thumbs { + position: fixed; + width: 400px; + left: 0px; + top: 0px; + overflow-y: auto; + background: #fff; +} +.flotr-examples-large.flotr-examples-collapsed .flotr-examples-thumb, +.flotr-examples-medium.flotr-examples-collapsed .flotr-examples-thumb { + display: block; + float: center; + margin: 10px auto; +} + +.flotr-examples-large.flotr-examples-collapsed .flotr-examples-container, +.flotr-examples-medium.flotr-examples-collapsed .flotr-examples-container { + margin-left: 400px; +} + +/* Vertical Example */ + +.flotr-examples-small .flotr-example, +.flotr-examples-medium .flotr-example { + width: 720px; +} +.flotr-examples-small .flotr-example-editor .source, +.flotr-examples-medium .flotr-example-editor .source { + position: relative; +} diff --git a/addons/web_graph/static/src/css/graph.css b/addons/web_graph/static/src/css/graph.css new file mode 100644 index 00000000000..793d04e0819 --- /dev/null +++ b/addons/web_graph/static/src/css/graph.css @@ -0,0 +1,46 @@ +.dropdown-menu-icon { + z-index: 5000; + position: absolute; + right: 0px; +} + +.editor-render { + width: 650px; + position: relative; +} +#editor-render-body { + position: relative; + width: 650px; + height: 440px; +} +.graph-dropdown { + padding: 5px; + width: 200px; + border: 1px #333 solid; + display: none; + position: absolute; + right: 0px; + background: #fafaf5; + z-index: 100; +} +.open .graph-dropdown { + display: block; +} +.graph-menu { + padding: 3px; +} +.graph-menu .active { + background: #aaaaff; +} +li { + padding: 2px; + margin-left: 10px; +} +a .graph-menu-options { + color: white; +} +div .graph-menu-options { + float: right; + top: 0px; +} + diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js index 7016b9d8d85..27ae5ee3842 100644 --- a/addons/web_graph/static/src/js/graph.js +++ b/addons/web_graph/static/src/js/graph.js @@ -3,17 +3,10 @@ *---------------------------------------------------------*/ openerp.web_graph = function (instance) { -var COLOR_PALETTE = [ - '#cc99ff', '#ccccff', '#48D1CC', '#CFD784', '#8B7B8B', '#75507b', - '#b0008c', '#ff0000', '#ff8e00', '#9000ff', '#0078ff', '#00ff00', - '#e6ff00', '#ffff00', '#905000', '#9b0000', '#840067', '#9abe00', - '#ffc900', '#510090', '#0000c9', '#009b00', '#75507b', '#3465a4', - '#73d216', '#c17d11', '#edd400', '#fcaf3e', '#ef2929', '#ff00c9', - '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f', '#f57900', - '#cc0000', '#d400a8']; var QWeb = instance.web.qweb, _lt = instance.web._lt; + instance.web.views.add('graph', 'instance.web_graph.GraphView'); instance.web_graph.GraphView = instance.web.View.extend({ display_name: _lt('Graph'), @@ -25,13 +18,16 @@ instance.web_graph.GraphView = instance.web.View.extend({ this.dataset = dataset; this.view_id = view_id; - this.first_field = null; - this.abscissa = null; - this.ordinate = null; - this.columns = []; - this.group_field = null; - this.is_loaded = $.Deferred(); + this.mode="pie"; // line, bar, area, pie, radar + this.orientation=true; // true: horizontal, false: vertical + this.stacked=true; + this.spreadsheet=false; // Display data gris, allows copy to CSV + this.forcehtml=false; + this.legend_container; + this.legend="top"; // top, inside, no + + this.is_loaded = $.Deferred(); this.renderer = null; }, destroy: function () { @@ -40,6 +36,9 @@ instance.web_graph.GraphView = instance.web.View.extend({ } this._super(); }, + + + on_loaded: function(fields_view_get) { var self = this; self.fields_view = fields_view_get; @@ -80,6 +79,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ this.is_loaded.resolve(); return $.when(); }, + schedule_chart: function(results) { var self = this; this.$element.html(QWeb.render("GraphView", { @@ -90,330 +90,8 @@ instance.web_graph.GraphView = instance.web.View.extend({ var fields = _(this.columns).pluck('name').concat([this.abscissa]); if (this.group_field) { fields.push(this.group_field); } - // transform search result into usable records (convert from OpenERP - // value shapes to usable atomic types - var records = _(results).map(function (result) { - var point = {}; - _(result).each(function (value, field) { - if (!_(fields).contains(field)) { return; } - if (value === false) { point[field] = false; return; } - switch (self.fields[field].type) { - case 'selection': - point[field] = _(self.fields[field].selection).detect(function (choice) { - return choice[0] === value; - })[1]; - break; - case 'many2one': - point[field] = value[1]; - break; - case 'integer': case 'float': case 'char': - case 'date': case 'datetime': - point[field] = value; - break; - default: - throw new Error( - "Unknown field type " + self.fields[field].type - + "for field " + field + " (" + value + ")"); - } - }); - return point; - }); - // aggregate data, because dhtmlx is crap. Aggregate on abscissa field, - // leave split on group field => max m*n records where m is the # of - // values for the abscissa and n is the # of values for the group field - var graph_data = []; - _(records).each(function (record) { - var abscissa = record[self.abscissa], - group = record[self.group_field]; - var r = _(graph_data).detect(function (potential) { - return potential[self.abscissa] === abscissa - && (!self.group_field - || potential[self.group_field] === group); - }); - var datapoint = r || {}; - datapoint[self.abscissa] = abscissa; - if (self.group_field) { datapoint[self.group_field] = group; } - _(self.columns).each(function (column) { - var val = record[column.name], - aggregate = datapoint[column.name]; - switch(column.operator) { - case '+': - datapoint[column.name] = (aggregate || 0) + val; - return; - case '*': - datapoint[column.name] = (aggregate || 1) * val; - return; - case 'min': - datapoint[column.name] = (aggregate || Infinity) > val - ? val - : aggregate; - return; - case 'max': - datapoint[column.name] = (aggregate || -Infinity) < val - ? val - : aggregate; - } - }); - - if (!r) { graph_data.push(datapoint); } - }); - graph_data = _(graph_data).sortBy(function (point) { - return point[self.abscissa] + '[[--]]' + point[self.group_field]; - }); - if (_.include(['bar','line','area'],this.chart)) { - return this.schedule_bar_line_area(graph_data); - } else if (this.chart == "pie") { - return this.schedule_pie(graph_data); - } }, - schedule_bar_line_area: function(results) { - var self = this; - var group_list, - view_chart = (self.chart == 'line')?'line':(self.chart == 'area')?'area':''; - if (!this.group_field || !results.length) { - if (self.chart == 'bar'){ - view_chart = (this.orientation === 'horizontal') ? 'barH' : 'bar'; - } - group_list = _(this.columns).map(function (column, index) { - return { - group: column.name, - text: self.fields[column.name].string, - color: COLOR_PALETTE[index % (COLOR_PALETTE.length)] - } - }); - } else { - // dhtmlx handles clustered bar charts (> 1 column per abscissa - // value) and stacked bar charts (basically the same but with the - // columns on top of one another instead of side by side), but it - // does not handle clustered stacked bar charts - if (self.chart == 'bar' && (this.columns.length > 1)) { - this.$element.text( - 'OpenERP Web does not support combining grouping and ' - + 'multiple columns in graph at this time.'); - throw new Error( - 'dhtmlx can not handle columns counts of that magnitude'); - } - // transform series for clustered charts into series for stacked - // charts - if (self.chart == 'bar'){ - view_chart = (this.orientation === 'horizontal') - ? 'stackedBarH' : 'stackedBar'; - } - group_list = _(results).chain() - .pluck(this.group_field) - .uniq() - .map(function (value, index) { - var groupval = ''; - if(value) { - groupval = value.toLowerCase().replace(/[\s\/]+/g,'_'); - } - return { - group: _.str.sprintf('%s_%s', self.ordinate, groupval), - text: value, - color: COLOR_PALETTE[index % COLOR_PALETTE.length] - }; - }).value(); - - results = _(results).chain() - .groupBy(function (record) { return record[self.abscissa]; }) - .map(function (records) { - var r = {}; - // second argument is coerced to a str, no good for boolean - r[self.abscissa] = records[0][self.abscissa]; - _(records).each(function (record) { - var value = record[self.group_field]; - if(value) { - value = value.toLowerCase().replace(/[\s\/]+/g,'_'); - } - var key = _.str.sprintf('%s_%s', self.ordinate, value); - r[key] = record[self.ordinate]; - }); - return r; - }) - .value(); - } - var abscissa_description = { - title: "" + this.fields[this.abscissa].string + "", - template: function (obj) { - return obj[self.abscissa] || 'Undefined'; - } - }; - var ordinate_description = { - lines: true, - title: "" + this.fields[this.ordinate].string + "" - }; - - var x_axis, y_axis; - if (self.chart == 'bar' && self.orientation == 'horizontal') { - x_axis = ordinate_description; - y_axis = abscissa_description; - } else { - x_axis = abscissa_description; - y_axis = ordinate_description; - } - var renderer = function () { - if (self.$element.is(':hidden')) { - self.renderer = setTimeout(renderer, 100); - return; - } - self.renderer = null; - var charts = new dhtmlXChart({ - view: view_chart, - container: self.getParent().element_id+"-"+self.chart+"chart", - value:"#"+group_list[0].group+"#", - gradient: (self.chart == "bar") ? "3d" : "light", - alpha: (self.chart == "area") ? 0.6 : 1, - border: false, - width: 1024, - tooltip:{ - template: _.str.sprintf("#%s#, %s=#%s#", - self.abscissa, group_list[0].text, group_list[0].group) - }, - radius: 0, - color: (self.chart != "line") ? group_list[0].color : "", - item: (self.chart == "line") ? { - borderColor: group_list[0].color, - color: "#000000" - } : "", - line: (self.chart == "line") ? { - color: group_list[0].color, - width: 3 - } : "", - origin:0, - xAxis: x_axis, - yAxis: y_axis, - padding: { - left: 75 - }, - legend: { - values: group_list, - align:"left", - valign:"top", - layout: "x", - marker: { - type:"round", - width:12 - } - } - }); - self.$element.find("#"+self.getParent().element_id+"-"+self.chart+"chart").width( - self.$element.find("#"+self.getParent().element_id+"-"+self.chart+"chart").width()+120); - - for (var m = 1; m + + + + + + + + + + + +

      + Hello World! +

      + +
      +
      +
      + B +
      + + + + + + +
      + +
      +
      +
      + + + + + + + diff --git a/addons/web_graph/static/src/xml/web_graph.xml b/addons/web_graph/static/src/xml/web_graph.xml index 90349623946..710036d40d3 100644 --- a/addons/web_graph/static/src/xml/web_graph.xml +++ b/addons/web_graph/static/src/xml/web_graph.xml @@ -1,4 +1,49 @@ \ No newline at end of file +
      +
      +
      + B +
      + + + + + + +
      +
      +
      +
      + + From 7b0a381091b04ac8a2783083a7dfaf95c6176c95 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 7 May 2012 10:23:37 +0200 Subject: [PATCH 005/195] [FIX] small fix for legend position bzr revid: fp@tinyerp.com-20120507082337-98g5jufnr7ejca9m --- addons/web_graph/static/src/js/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_graph/static/src/js/index.html b/addons/web_graph/static/src/js/index.html index 4c02a86376f..c88423f4791 100644 --- a/addons/web_graph/static/src/js/index.html +++ b/addons/web_graph/static/src/js/index.html @@ -131,7 +131,7 @@ } if (legend=="top") { result.noColumns = 4; - result.legend_container = $("div .graph_header_legend")[0]; + result.container = $("div .graph_header_legend")[0]; } else if (legend=="inside") { result.position = 'nw'; result.backgroundColor = '#D2E8FF'; From 74634666958328457612870873c34613bbde0f71 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 7 May 2012 10:50:36 +0200 Subject: [PATCH 006/195] [IMP] adding sample controllers bzr revid: fp@tinyerp.com-20120507085036-i3z2blu1d1nus4ir --- addons/web_graph/__init__.py | 1 + addons/web_graph/controllers/__init__.py | 1 + addons/web_graph/controllers/graph.py | 31 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 addons/web_graph/controllers/__init__.py create mode 100644 addons/web_graph/controllers/graph.py diff --git a/addons/web_graph/__init__.py b/addons/web_graph/__init__.py index e69de29bb2d..ee5959455ad 100644 --- a/addons/web_graph/__init__.py +++ b/addons/web_graph/__init__.py @@ -0,0 +1 @@ +import controllers diff --git a/addons/web_graph/controllers/__init__.py b/addons/web_graph/controllers/__init__.py new file mode 100644 index 00000000000..7f317cca530 --- /dev/null +++ b/addons/web_graph/controllers/__init__.py @@ -0,0 +1 @@ +import graph diff --git a/addons/web_graph/controllers/graph.py b/addons/web_graph/controllers/graph.py new file mode 100644 index 00000000000..f7af6180e41 --- /dev/null +++ b/addons/web_graph/controllers/graph.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +try: + import openerp.addons.web.common.http as openerpweb +except ImportError: + import web.common.http as openerpweb + +WIDGET_CONTENT_PATTERN = """ + + [[Widget %(id)d]] + + %(content)s + + + +""" +class Widgets(openerpweb.Controller): + _cp_path = '/web_dashboard/widgets' + + @openerpweb.httprequest + def content(self, request, widget_id): + return WIDGET_CONTENT_PATTERN % request.session.model('res.widget').read( + [int(widget_id)], ['content'], request.session.eval_context(request.context) + )[0] From 76b1514392a4dee3fd07ec89a7aa5de3ef88ca16 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 7 May 2012 11:40:59 +0200 Subject: [PATCH 007/195] [IMP] code for graph lib bzr revid: fp@tinyerp.com-20120507094059-y0ogjwcocp71hfue --- addons/web_graph/static/src/js/graph.js | 280 ++++++++++++++---- addons/web_graph/static/src/xml/web_graph.xml | 93 +++--- 2 files changed, 272 insertions(+), 101 deletions(-) diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js index 27ae5ee3842..7f123e3584a 100644 --- a/addons/web_graph/static/src/js/graph.js +++ b/addons/web_graph/static/src/js/graph.js @@ -21,6 +21,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ this.mode="pie"; // line, bar, area, pie, radar this.orientation=true; // true: horizontal, false: vertical this.stacked=true; + this.spreadsheet=false; // Display data gris, allows copy to CSV this.forcehtml=false; this.legend_container; @@ -37,71 +38,240 @@ instance.web_graph.GraphView = instance.web.View.extend({ this._super(); }, - - on_loaded: function(fields_view_get) { - var self = this; - self.fields_view = fields_view_get; - return this.dataset.call_and_eval('fields_get', [false, {}], null, 1).pipe(function(fields_result) { - self.fields = fields_result; - return self.on_loaded_2(); - }); - }, - /** - * Returns all object fields involved in the graph view - */ - list_fields: function () { - var fs = [this.abscissa]; - fs.push.apply(fs, _(this.columns).pluck('name')); - if (this.group_field) { - fs.push(this.group_field); - } - return fs; - }, - on_loaded_2: function() { - this.chart = this.fields_view.arch.attrs.type || 'pie'; - this.orientation = this.fields_view.arch.attrs.orientation || 'vertical'; + this.$element.html(QWeb.render("GraphView", {})); - _.each(this.fields_view.arch.children, function (field) { - var attrs = field.attrs; - if (attrs.group) { - this.group_field = attrs.name; - } else if(!this.abscissa) { - this.first_field = this.abscissa = attrs.name; - } else { - this.columns.push({ - name: attrs.name, - operator: attrs.operator || '+' - }); + // Should I add this, in every $(...) call ? + container = $("#editor-render-body"); + $("#graph_bar,#graph_bar_stacked").click( + {mode: 'bar', stacked: true, legend: 'top'}, graph_render) + + $("#graph_bar_not_stacked").click( + {mode: 'bar', stacked: false, legend: 'top'}, graph_render) + + $("#graph_area,#graph_area_stacked").click( + {mode: "area", stacked: true, legend: "top"}, graph_render); + + $("#graph_area_not_stacked").click( + {mode: "area", stacked: false, legend: "top"}, graph_render); + + $("#graph_radar").click( + {orientation: 0, mode: "radar", legend: "inside"}, graph_render); + + $("#graph_pie").click( + {mode: "pie", legend: "inside"}, graph_render); + + $("#graph_legend_top").click( + {legend: "top"}, graph_render); + + $("#graph_legend_inside").click( + {legend: "inside"}, graph_render); + + $("#graph_legend_no").click( + {legend: "no"}, graph_render); + + $("#graph_line").click( + {mode: "line"}, graph_render); + + $("#graph_show_data").click( + function() { + spreadsheet = ! spreadsheet; + graph_render(); } - }, this); - this.ordinate = this.columns[0].name; - this.is_loaded.resolve(); - return $.when(); + ); + $("#graph_switch").click( + function() { + orientation = ! orientation; + graph_render(); + } + ); + + $("#graph_download").click( + function() { + var graph; + 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 (legend=="top") legend="inside"; + forcehtml = true; + graph = graph_render(); + graph.download.saveImage('png'); + forcehtml = false; + } + ); + + this._super(); }, + get_format: function get_format(options) { + var result = { + show: this.legend!='no', + } + if (legend=="top") { + result.noColumns = 4; + // todo: I guess I should add something like this.renderer ? + result.container = $("div .graph_header_legend", this)[0]; + } else if (legend=="inside") { + result.position = 'nw'; + result.backgroundColor = '#D2E8FF'; + } + return $.extend({ + legend: result, + mouse: { + track: true, + relative: true + }, + spreadsheet : { + show: this.spreadsheet, + initialTab: "data" + }, + HtmlText : (options && options.labelsAngle)?false:!this.forcehtml, + }, options) + }, + + graph_get_data: function (options) { + var i, + d1 = [], + d2 = [], + d3 = []; + for (i = -3; i < 3; i++) { + if (this.orientation % 2) { + 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()]); + } + }; + return [ + $.extend({ data : d2, label : 'Serie 2'}, options), + $.extend({ data : d3, label : 'Serie 3'}, options), + $.extend({ data : d1, label : 'Serie 1'}, options), + ]; + }, + + + graph_bar: function (container, data) { + return Flotr.draw(container, data, get_format({ + bars : { + show : true, + stacked : this.stacked, + horizontal : this.orientation, + barWidth : 0.7, + lineWidth : 1 + }, + grid : { + verticalLines : this.orientation, + horizontalLines : !this.orientation, + outline : "sw", + }, + labelsAngle: 45 + }) + ) + }, + + graph_pie: function (container, data) { + return Flotr.draw(container, data, get_format({ + pie : { + show: true + }, + grid : { + verticalLines : false, + horizontalLines : false, + outline : "", + }, + xaxis : {showLabels: false}, + yaxis : {showLabels: false}, + }) + ) + } + + graph_radar: function (container, data) { + return Flotr.draw(container, data, get_format({ + radar : { + show : true, + stacked : this.stacked + }, + grid : { + circular : true, + minorHorizontalLines : true + } + }) + ) + } + + graph_line: function (container, data) { + return Flotr.draw(container, data, get_format({ + lines : { + show : true, + stacked : this.stacked + }, + grid : { + verticalLines : this.orientation, + horizontalLines : !this.orientation, + outline : "sw", + }, + labelsAngle : 45 + }) + ) + } + + // Render the graph and update menu styles + graph_render: function (options) { + var graph, data, mode_options, i; + + if (options) + for (i in options.data) + this[i] = options.data[i]; + + mode_options = (this.mode=='area')?{lines: {fill: true}}:{} + + // Render the graph + $(".graph_header_legend").children().remove() + data = this.get_data(mode_options); + graph = { + radar: graph_radar, + pie: graph_pie, + bar: graph_bar, + area: graph_line, + line: graph_line + }[this.mode](container, data) + + // Update styles of menus + + $("a[id^='graph_']").removeClass("active"); + $("a[id='graph_"+mode+"']").addClass("active"); + $("a[id='graph_"+mode+(this.stacked?"_stacked":"_not_stacked")+"']").addClass("active"); + + if (this.legend=='inside') + $("a[id='graph_legend_inside']").addClass("active"); + else if (this.legend=='top') + $("a[id='graph_legend_top']").addClass("active"); + else + $("a[id='graph_legend_no']").addClass("active"); + + if (this.spreadsheet) + $("a[id='graph_show_data']").addClass("active"); + return graph; + } + + schedule_chart: function(results) { - var self = this; - this.$element.html(QWeb.render("GraphView", { - "fields_view": this.fields_view, - "chart": this.chart, - 'element_id': this.getParent().element_id - })); - - var fields = _(this.columns).pluck('name').concat([this.abscissa]); - if (this.group_field) { fields.push(this.group_field); } - + self.graph_render(...) }, + + // render the graph using the domain, context and group_by + // calls the 'graph_data_get' python controller to process all data do_search: function(domain, context, group_by) { var self = this; return $.when(this.is_loaded).pipe(function() { - // TODO: handle non-empty group_by with read_group? - if (!_(group_by).isEmpty()) { - self.abscissa = group_by[0]; - } else { - self.abscissa = self.first_field; - } - return self.dataset.read_slice(self.list_fields()).then($.proxy(self, 'schedule_chart')); + // todo: find the right syntax to perform an Ajax call + return self.rpc.graph_get_data(self.view_id, domain, context, group_by).then($.proxy(self, 'schedule_chart')); }); }, diff --git a/addons/web_graph/static/src/xml/web_graph.xml b/addons/web_graph/static/src/xml/web_graph.xml index 710036d40d3..c11525ba353 100644 --- a/addons/web_graph/static/src/xml/web_graph.xml +++ b/addons/web_graph/static/src/xml/web_graph.xml @@ -1,49 +1,50 @@ From 63b583aead9ac20961eab1c6c2cd5b3ada8e26f4 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 7 May 2012 11:51:47 +0200 Subject: [PATCH 008/195] [IMP] graphs code bzr revid: fp@tinyerp.com-20120507095147-qxelcvv2m12t50l4 --- addons/web_graph/controllers/graph.py | 50 ++++++++++++------------- addons/web_graph/static/src/js/graph.js | 5 ++- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/addons/web_graph/controllers/graph.py b/addons/web_graph/controllers/graph.py index f7af6180e41..8c09a74ccae 100644 --- a/addons/web_graph/controllers/graph.py +++ b/addons/web_graph/controllers/graph.py @@ -4,28 +4,28 @@ try: except ImportError: import web.common.http as openerpweb -WIDGET_CONTENT_PATTERN = """ - - [[Widget %(id)d]] - - %(content)s - - - -""" -class Widgets(openerpweb.Controller): - _cp_path = '/web_dashboard/widgets' - - @openerpweb.httprequest - def content(self, request, widget_id): - return WIDGET_CONTENT_PATTERN % request.session.model('res.widget').read( - [int(widget_id)], ['content'], request.session.eval_context(request.context) - )[0] +#WIDGET_CONTENT_PATTERN = """ +# +# [[Widget %(id)d]] +# +# %(content)s +# +# +# +#""" +#class Widgets(openerpweb.Controller): +# _cp_path = '/web_dashboard/widgets' +# +# @openerpweb.httprequest +# def content(self, request, widget_id): +# return WIDGET_CONTENT_PATTERN % request.session.model('res.widget').read( +# [int(widget_id)], ['content'], request.session.eval_context(request.context) +# )[0] diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js index 7f123e3584a..0dff7ca64e5 100644 --- a/addons/web_graph/static/src/js/graph.js +++ b/addons/web_graph/static/src/js/graph.js @@ -262,7 +262,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ schedule_chart: function(results) { - self.graph_render(...) + self.graph_render({}) }, // render the graph using the domain, context and group_by @@ -271,7 +271,8 @@ instance.web_graph.GraphView = instance.web.View.extend({ var self = this; return $.when(this.is_loaded).pipe(function() { // todo: find the right syntax to perform an Ajax call - return self.rpc.graph_get_data(self.view_id, domain, context, group_by).then($.proxy(self, 'schedule_chart')); + // return self.rpc.graph_get_data(self.view_id, domain, context, group_by).then($.proxy(self, 'schedule_chart')); + $.proxy(self, "schedule_chart"); }); }, From ddcd41dc33176f2df07a653ac82fcda4bec04c65 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 7 May 2012 12:34:07 +0200 Subject: [PATCH 009/195] [IMP] loading but crashing bzr revid: fp@tinyerp.com-20120507103407-h39dmn0u7bekqozd --- addons/web_graph/__openerp__.py | 31 +++++++- addons/web_graph/static/lib/flotr2/Makefile | 5 ++ addons/web_graph/static/src/js/graph.js | 85 ++++++++++----------- 3 files changed, 77 insertions(+), 44 deletions(-) diff --git a/addons/web_graph/__openerp__.py b/addons/web_graph/__openerp__.py index 72a60ef240f..5cd5765aedf 100644 --- a/addons/web_graph/__openerp__.py +++ b/addons/web_graph/__openerp__.py @@ -13,7 +13,36 @@ "version": "3.0", "depends": ['web'], "js": [ - "static/lib/flotr2/flotr2.js", + "static/lib/flotr2/lib/bean.js", + "static/lib/flotr2/js/Flotr.js", + "static/lib/flotr2/js/DefaultOptions.js", + "static/lib/flotr2/js/Color.js", + "static/lib/flotr2/js/Date.js", + "static/lib/flotr2/js/DOM.js", + "static/lib/flotr2/js/EventAdapter.js", + "static/lib/flotr2/js/Text.js", + "static/lib/flotr2/js/Graph.js", + "static/lib/flotr2/js/Axis.js", + "static/lib/flotr2/js/Series.js", + "static/lib/flotr2/js/types/lines.js", + "static/lib/flotr2/js/types/bars.js", + "static/lib/flotr2/js/types/bubbles.js", + "static/lib/flotr2/js/types/candles.js", + "static/lib/flotr2/js/types/gantt.js", + "static/lib/flotr2/js/types/markers.js", + "static/lib/flotr2/js/types/pie.js", + "static/lib/flotr2/js/types/points.js", + "static/lib/flotr2/js/types/radar.js", + "static/lib/flotr2/js/types/timeline.js", + "static/lib/flotr2/js/plugins/crosshair.js", + "static/lib/flotr2/js/plugins/download.js", + "static/lib/flotr2/js/plugins/grid.js", + "static/lib/flotr2/js/plugins/hit.js", + "static/lib/flotr2/js/plugins/selection.js", + "static/lib/flotr2/js/plugins/labels.js", + "static/lib/flotr2/js/plugins/legend.js", + "static/lib/flotr2/js/plugins/spreadsheet.js", + "static/lib/flotr2/js/plugins/titles.js", "static/src/js/graph.js" ], "css": [ diff --git a/addons/web_graph/static/lib/flotr2/Makefile b/addons/web_graph/static/lib/flotr2/Makefile index 3fd8e8f3a64..70157b0f3fa 100644 --- a/addons/web_graph/static/lib/flotr2/Makefile +++ b/addons/web_graph/static/lib/flotr2/Makefile @@ -28,6 +28,11 @@ flotr2-basic: libraries ie cat build/lib.min.js > flotr2-basic.min.js cat build/flotr2-basic.min.js >> flotr2-basic.min.js +flotr2-standalone: ie + smoosh make/flotr2.json + cat build/flotr2.js > flotr2.js + cp build/ie.min.js flotr2.ie.min.js + flotr-examples: smoosh make/examples.json cp build/examples.min.js flotr2.examples.min.js diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js index 0dff7ca64e5..50f58a942e2 100644 --- a/addons/web_graph/static/src/js/graph.js +++ b/addons/web_graph/static/src/js/graph.js @@ -39,54 +39,54 @@ instance.web_graph.GraphView = instance.web.View.extend({ }, on_loaded: function(fields_view_get) { + var container; this.$element.html(QWeb.render("GraphView", {})); - // Should I add this, in every $(...) call ? - container = $("#editor-render-body"); - $("#graph_bar,#graph_bar_stacked").click( - {mode: 'bar', stacked: true, legend: 'top'}, graph_render) + container = this.$element.find("#editor-render-body"); + this.$element.find("#graph_bar,#graph_bar_stacked").click( + {mode: 'bar', stacked: true, legend: 'top'}, this.graph_render) - $("#graph_bar_not_stacked").click( - {mode: 'bar', stacked: false, legend: 'top'}, graph_render) + this.$element.find("#graph_bar_not_stacked").click( + {mode: 'bar', stacked: false, legend: 'top'}, this.graph_render) - $("#graph_area,#graph_area_stacked").click( - {mode: "area", stacked: true, legend: "top"}, graph_render); + this.$element.find("#graph_area,#graph_area_stacked").click( + {mode: "area", stacked: true, legend: "top"}, this.graph_render); - $("#graph_area_not_stacked").click( - {mode: "area", stacked: false, legend: "top"}, graph_render); + this.$element.find("#graph_area_not_stacked").click( + {mode: "area", stacked: false, legend: "top"}, this.graph_render); - $("#graph_radar").click( - {orientation: 0, mode: "radar", legend: "inside"}, graph_render); + this.$element.find("#graph_radar").click( + {orientation: 0, mode: "radar", legend: "inside"}, this.graph_render); - $("#graph_pie").click( - {mode: "pie", legend: "inside"}, graph_render); + this.$element.find("#graph_pie").click( + {mode: "pie", legend: "inside"}, this.graph_render); - $("#graph_legend_top").click( - {legend: "top"}, graph_render); + this.$element.find("#graph_legend_top").click( + {legend: "top"}, this.graph_render); - $("#graph_legend_inside").click( - {legend: "inside"}, graph_render); + this.$element.find("#graph_legend_inside").click( + {legend: "inside"}, this.graph_render); - $("#graph_legend_no").click( - {legend: "no"}, graph_render); + this.$element.find("#graph_legend_no").click( + {legend: "no"}, this.graph_render); - $("#graph_line").click( - {mode: "line"}, graph_render); + this.$element.find("#graph_line").click( + {mode: "line"}, this.graph_render); - $("#graph_show_data").click( + this.$element.find("#graph_show_data").click( function() { spreadsheet = ! spreadsheet; - graph_render(); + this.graph_render(); } ); - $("#graph_switch").click( + this.$element.find("#graph_switch").click( function() { orientation = ! orientation; - graph_render(); + this.graph_render(); } ); - $("#graph_download").click( + this.$element.find("#graph_download").click( function() { var graph; if (Flotr.isIE && Flotr.isIE < 9) { @@ -97,7 +97,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ } if (legend=="top") legend="inside"; forcehtml = true; - graph = graph_render(); + graph = this.graph_render(); graph.download.saveImage('png'); forcehtml = false; } @@ -113,7 +113,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ if (legend=="top") { result.noColumns = 4; // todo: I guess I should add something like this.renderer ? - result.container = $("div .graph_header_legend", this)[0]; + result.container = this.$element.find("div .graph_header_legend", this)[0]; } else if (legend=="inside") { result.position = 'nw'; result.backgroundColor = '#D2E8FF'; @@ -189,7 +189,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ yaxis : {showLabels: false}, }) ) - } + }, graph_radar: function (container, data) { return Flotr.draw(container, data, get_format({ @@ -203,7 +203,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ } }) ) - } + }, graph_line: function (container, data) { return Flotr.draw(container, data, get_format({ @@ -219,7 +219,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ labelsAngle : 45 }) ) - } + }, // Render the graph and update menu styles graph_render: function (options) { @@ -232,7 +232,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ mode_options = (this.mode=='area')?{lines: {fill: true}}:{} // Render the graph - $(".graph_header_legend").children().remove() + this.$element.find(".graph_header_legend").children().remove() data = this.get_data(mode_options); graph = { radar: graph_radar, @@ -244,25 +244,24 @@ instance.web_graph.GraphView = instance.web.View.extend({ // Update styles of menus - $("a[id^='graph_']").removeClass("active"); - $("a[id='graph_"+mode+"']").addClass("active"); - $("a[id='graph_"+mode+(this.stacked?"_stacked":"_not_stacked")+"']").addClass("active"); + this.$element.find("a[id^='graph_']").removeClass("active"); + this.$element.find("a[id='graph_"+mode+"']").addClass("active"); + this.$element.find("a[id='graph_"+mode+(this.stacked?"_stacked":"_not_stacked")+"']").addClass("active"); if (this.legend=='inside') - $("a[id='graph_legend_inside']").addClass("active"); + this.$element.find("a[id='graph_legend_inside']").addClass("active"); else if (this.legend=='top') - $("a[id='graph_legend_top']").addClass("active"); + this.$element.find("a[id='graph_legend_top']").addClass("active"); else - $("a[id='graph_legend_no']").addClass("active"); + this.$element.find("a[id='graph_legend_no']").addClass("active"); if (this.spreadsheet) - $("a[id='graph_show_data']").addClass("active"); + this.$element.find("a[id='graph_show_data']").addClass("active"); return graph; - } - + }, schedule_chart: function(results) { - self.graph_render({}) + this.graph_render({}) }, // render the graph using the domain, context and group_by From e7c0f2cf2bd40d74948040f670e8c736b3ffeecd Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 7 May 2012 12:34:48 +0200 Subject: [PATCH 010/195] remove unsued file bzr revid: fp@tinyerp.com-20120507103448-7sw1wsgn9pn0s6cn --- addons/web_graph/static/src/js/index.html | 358 ---------------------- 1 file changed, 358 deletions(-) delete mode 100644 addons/web_graph/static/src/js/index.html diff --git a/addons/web_graph/static/src/js/index.html b/addons/web_graph/static/src/js/index.html deleted file mode 100644 index c88423f4791..00000000000 --- a/addons/web_graph/static/src/js/index.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - - - - - - - -

      - Hello World! -

      - -
      -
      -
      - B -
      - - - - - - -
      - -
      -
      -
      - - - - - - - From 22fb74570078646915791118d2869f0818a8526c Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 7 May 2012 15:38:21 +0200 Subject: [PATCH 011/195] [IMP] working version of graphs, remaining: size & fetch bzr revid: fp@tinyerp.com-20120507133821-7mmwxo81vfjpq3e5 --- addons/web_graph/__openerp__.py | 1 + addons/web_graph/static/lib/dropdown.js | 92 ++++++++++++++++ addons/web_graph/static/src/js/graph.js | 101 +++++++++--------- addons/web_graph/static/src/xml/web_graph.xml | 7 +- 4 files changed, 145 insertions(+), 56 deletions(-) create mode 100644 addons/web_graph/static/lib/dropdown.js diff --git a/addons/web_graph/__openerp__.py b/addons/web_graph/__openerp__.py index 5cd5765aedf..dc4cc6c47d2 100644 --- a/addons/web_graph/__openerp__.py +++ b/addons/web_graph/__openerp__.py @@ -13,6 +13,7 @@ "version": "3.0", "depends": ['web'], "js": [ + "static/lib/dropdown.js", "static/lib/flotr2/lib/bean.js", "static/lib/flotr2/js/Flotr.js", "static/lib/flotr2/js/DefaultOptions.js", diff --git a/addons/web_graph/static/lib/dropdown.js b/addons/web_graph/static/lib/dropdown.js new file mode 100644 index 00000000000..54b61c5e9d0 --- /dev/null +++ b/addons/web_graph/static/lib/dropdown.js @@ -0,0 +1,92 @@ +/* ============================================================ + * bootstrap-dropdown.js v2.0.2 + * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * ============================================================ + * Copyright 2012 Twitter, 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. + * ============================================================ */ + + +!function( $ ){ + + "use strict" + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle="dropdown"]' + , Dropdown = function ( element ) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function ( e ) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + , isActive + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + $parent.length || ($parent = $this.parent()) + + isActive = $parent.hasClass('open') + + clearMenus() + !isActive && $parent.toggleClass('open') + + return false + } + + } + + function clearMenus() { + $(toggle).parent().removeClass('open') + } + + + /* DROPDOWN PLUGIN DEFINITION + * ========================== */ + + $.fn.dropdown = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('dropdown') + if (!data) $this.data('dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.dropdown.Constructor = Dropdown + + + /* APPLY TO STANDARD DROPDOWN ELEMENTS + * =================================== */ + + $(function () { + $('html').on('click.dropdown.data-api', clearMenus) + $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) + }) + +}( window.jQuery ); \ No newline at end of file diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js index 50f58a942e2..75490e076f9 100644 --- a/addons/web_graph/static/src/js/graph.js +++ b/addons/web_graph/static/src/js/graph.js @@ -9,6 +9,7 @@ var QWeb = instance.web.qweb, instance.web.views.add('graph', 'instance.web_graph.GraphView'); instance.web_graph.GraphView = instance.web.View.extend({ + template: "GraphView", display_name: _lt('Graph'), view_type: "graph", @@ -18,7 +19,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ this.dataset = dataset; this.view_id = view_id; - this.mode="pie"; // line, bar, area, pie, radar + this.mode="bar"; // line, bar, area, pie, radar this.orientation=true; // true: horizontal, false: vertical this.stacked=true; @@ -29,60 +30,59 @@ instance.web_graph.GraphView = instance.web.View.extend({ this.is_loaded = $.Deferred(); - this.renderer = null; + this.graph = null; }, destroy: function () { - if (this.renderer) { - clearTimeout(this.renderer); - } + if (this.graph) + this.graph.destroy(); this._super(); }, on_loaded: function(fields_view_get) { - var container; - this.$element.html(QWeb.render("GraphView", {})); - - container = this.$element.find("#editor-render-body"); + // TODO: move to load_view and document + var self = this; + this.fields_view = fields_view_get; + this.container = this.$element.find("#editor-render-body")[0]; this.$element.find("#graph_bar,#graph_bar_stacked").click( - {mode: 'bar', stacked: true, legend: 'top'}, this.graph_render) + {mode: 'bar', stacked: true, legend: 'top'}, $.proxy(this,"graph_render")) this.$element.find("#graph_bar_not_stacked").click( - {mode: 'bar', stacked: false, legend: 'top'}, this.graph_render) + {mode: 'bar', stacked: false, legend: 'top'}, $.proxy(this,"graph_render")) this.$element.find("#graph_area,#graph_area_stacked").click( - {mode: "area", stacked: true, legend: "top"}, this.graph_render); + {mode: "area", stacked: true, legend: "top"}, $.proxy(this,"graph_render")); this.$element.find("#graph_area_not_stacked").click( - {mode: "area", stacked: false, legend: "top"}, this.graph_render); + {mode: "area", stacked: false, legend: "top"}, $.proxy(this,"graph_render")); this.$element.find("#graph_radar").click( - {orientation: 0, mode: "radar", legend: "inside"}, this.graph_render); + {orientation: 0, mode: "radar", legend: "inside"}, $.proxy(this,"graph_render")); this.$element.find("#graph_pie").click( - {mode: "pie", legend: "inside"}, this.graph_render); + {mode: "pie", legend: "inside"}, $.proxy(this,"graph_render")); this.$element.find("#graph_legend_top").click( - {legend: "top"}, this.graph_render); + {legend: "top"}, $.proxy(self,"graph_render")); this.$element.find("#graph_legend_inside").click( - {legend: "inside"}, this.graph_render); + {legend: "inside"}, $.proxy(self,"graph_render")); this.$element.find("#graph_legend_no").click( - {legend: "no"}, this.graph_render); + {legend: "no"}, $.proxy(self,"graph_render")); this.$element.find("#graph_line").click( - {mode: "line"}, this.graph_render); + {mode: "line"}, $.proxy(this,"graph_render")); this.$element.find("#graph_show_data").click( function() { - spreadsheet = ! spreadsheet; - this.graph_render(); + self.spreadsheet = ! self.spreadsheet; + self.graph_render(); } ); this.$element.find("#graph_switch").click( function() { - orientation = ! orientation; - this.graph_render(); + self.orientation = ! self.orientation; + self.graph_render(); } ); @@ -95,26 +95,25 @@ instance.web_graph.GraphView = instance.web.View.extend({ "you can only get a VML image that you can use in Microsoft Office." ); } - if (legend=="top") legend="inside"; - forcehtml = true; - graph = this.graph_render(); + if (self.legend=="top") self.legend="inside"; + self.forcehtml = true; + graph = self.graph_render(); graph.download.saveImage('png'); - forcehtml = false; + self.forcehtml = false; } ); - this._super(); + this.graph_render() }, get_format: function get_format(options) { var result = { show: this.legend!='no', } - if (legend=="top") { + if (this.legend=="top") { result.noColumns = 4; - // todo: I guess I should add something like this.renderer ? - result.container = this.$element.find("div .graph_header_legend", this)[0]; - } else if (legend=="inside") { + result.container = this.$element.find("div.graph_header_legend")[0]; + } else if (this.legend=="inside") { result.position = 'nw'; result.backgroundColor = '#D2E8FF'; } @@ -157,7 +156,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ graph_bar: function (container, data) { - return Flotr.draw(container, data, get_format({ + return Flotr.draw(container, data, this.get_format({ bars : { show : true, stacked : this.stacked, @@ -176,7 +175,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ }, graph_pie: function (container, data) { - return Flotr.draw(container, data, get_format({ + return Flotr.draw(container, data, this.get_format({ pie : { show: true }, @@ -192,7 +191,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ }, graph_radar: function (container, data) { - return Flotr.draw(container, data, get_format({ + return Flotr.draw(container, data, this.get_format({ radar : { show : true, stacked : this.stacked @@ -206,7 +205,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ }, graph_line: function (container, data) { - return Flotr.draw(container, data, get_format({ + return Flotr.draw(container, data, this.get_format({ lines : { show : true, stacked : this.stacked @@ -230,23 +229,25 @@ instance.web_graph.GraphView = instance.web.View.extend({ this[i] = options.data[i]; mode_options = (this.mode=='area')?{lines: {fill: true}}:{} + if (this.graph) + this.graph.destroy(); // Render the graph this.$element.find(".graph_header_legend").children().remove() - data = this.get_data(mode_options); - graph = { - radar: graph_radar, - pie: graph_pie, - bar: graph_bar, - area: graph_line, - line: graph_line - }[this.mode](container, data) + data = this.graph_get_data(mode_options); + this.graph = { + radar: $.proxy(this, "graph_radar"), + pie: $.proxy(this, "graph_pie"), + bar: $.proxy(this, "graph_bar"), + area: $.proxy(this, "graph_line"), + line: $.proxy(this, "graph_line") + }[this.mode](this.container, data) // Update styles of menus this.$element.find("a[id^='graph_']").removeClass("active"); - this.$element.find("a[id='graph_"+mode+"']").addClass("active"); - this.$element.find("a[id='graph_"+mode+(this.stacked?"_stacked":"_not_stacked")+"']").addClass("active"); + this.$element.find("a[id='graph_"+this.mode+"']").addClass("active"); + this.$element.find("a[id='graph_"+this.mode+(this.stacked?"_stacked":"_not_stacked")+"']").addClass("active"); if (this.legend=='inside') this.$element.find("a[id='graph_legend_inside']").addClass("active"); @@ -257,11 +258,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ if (this.spreadsheet) this.$element.find("a[id='graph_show_data']").addClass("active"); - return graph; - }, - - schedule_chart: function(results) { - this.graph_render({}) + return this.graph; }, // render the graph using the domain, context and group_by @@ -271,7 +268,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ return $.when(this.is_loaded).pipe(function() { // todo: find the right syntax to perform an Ajax call // return self.rpc.graph_get_data(self.view_id, domain, context, group_by).then($.proxy(self, 'schedule_chart')); - $.proxy(self, "schedule_chart"); + $.proxy(self, "graph_render"); }); }, diff --git a/addons/web_graph/static/src/xml/web_graph.xml b/addons/web_graph/static/src/xml/web_graph.xml index c11525ba353..dbc19880ff2 100644 --- a/addons/web_graph/static/src/xml/web_graph.xml +++ b/addons/web_graph/static/src/xml/web_graph.xml @@ -1,9 +1,8 @@
       '+(serie.label || String.fromCharCode(65+i))+'