diff --git a/README.md b/README.md index 16c3c9a..9557eee 100644 --- a/README.md +++ b/README.md @@ -1 +1,20 @@ -# vk-friends-statistics \ No newline at end of file +НАЗВАНИЕ +======== + +Описание: +--------- +Плагин, который позволяет ... + +TODO: +----- +* Придумать название, описание +* Сделать иконку (в разных размерах) +* ~~Исправить отображение статистик при~~: + * ~~Переходе на ту же страницу~~ + * ~~Переходе вперёд / назад по истории~~ +* Доделать страницу настроек: + * Тултипы (нужны ли?) + * ~~Ограничения~~ + * ~~Стандартное состояние~~ +* Найти жертв для скриншотов +* Заплатить 5$ :( diff --git a/css/jquery-ui.css b/css/jquery-ui.css new file mode 100644 index 0000000..1c22746 --- /dev/null +++ b/css/jquery-ui.css @@ -0,0 +1,1225 @@ +/*! jQuery UI - v1.11.4 - 2015-03-11 +* http://jqueryui.com +* Includes: core.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, draggable.css, menu.css, progressbar.css, resizable.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-clearfix { + min-height: 0; /* support: IE7 */ +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; +} + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + display: block; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-accordion .ui-accordion-header { + display: block; + cursor: pointer; + position: relative; + margin: 2px 0 0 0; + padding: .5em .5em .5em .7em; + min-height: 0; /* support: IE7 */ + font-size: 100%; +} +.ui-accordion .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-icons .ui-accordion-icons { + padding-left: 2.2em; +} +.ui-accordion .ui-accordion-header .ui-accordion-header-icon { + position: absolute; + left: .5em; + top: 50%; + margin-top: -8px; +} +.ui-accordion .ui-accordion-content { + padding: 1em 2.2em; + border-top: 0; + overflow: auto; +} +.ui-autocomplete { + position: absolute; + top: 0; + left: 0; + cursor: default; +} +.ui-button { + display: inline-block; + position: relative; + padding: 0; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + overflow: visible; /* removes extra width in IE */ +} +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2.2em; +} +/* button elements seem to need a little more width */ +button.ui-button-icon-only { + width: 2.4em; +} +.ui-button-icons-only { + width: 3.4em; +} +button.ui-button-icons-only { + width: 3.7em; +} + +/* button text element */ +.ui-button .ui-button-text { + display: block; + line-height: normal; +} +.ui-button-text-only .ui-button-text { + padding: .4em 1em; +} +.ui-button-icon-only .ui-button-text, +.ui-button-icons-only .ui-button-text { + padding: .4em; + text-indent: -9999999px; +} +.ui-button-text-icon-primary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 1em .4em 2.1em; +} +.ui-button-text-icon-secondary .ui-button-text, +.ui-button-text-icons .ui-button-text { + padding: .4em 2.1em .4em 1em; +} +.ui-button-text-icons .ui-button-text { + padding-left: 2.1em; + padding-right: 2.1em; +} +/* no icon support for input elements, provide padding by default */ +input.ui-button { + padding: .4em 1em; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon, +.ui-button-text-icon-primary .ui-icon, +.ui-button-text-icon-secondary .ui-icon, +.ui-button-text-icons .ui-icon, +.ui-button-icons-only .ui-icon { + position: absolute; + top: 50%; + margin-top: -8px; +} +.ui-button-icon-only .ui-icon { + left: 50%; + margin-left: -8px; +} +.ui-button-text-icon-primary .ui-button-icon-primary, +.ui-button-text-icons .ui-button-icon-primary, +.ui-button-icons-only .ui-button-icon-primary { + left: .5em; +} +.ui-button-text-icon-secondary .ui-button-icon-secondary, +.ui-button-text-icons .ui-button-icon-secondary, +.ui-button-icons-only .ui-button-icon-secondary { + right: .5em; +} + +/* button sets */ +.ui-buttonset { + margin-right: 7px; +} +.ui-buttonset .ui-button { + margin-left: 0; + margin-right: -.3em; +} + +/* workarounds */ +/* reset extra padding in Firefox, see h5bp.com/l */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-datepicker { + width: 17em; + padding: .2em .2em 0; + display: none; +} +.ui-datepicker .ui-datepicker-header { + position: relative; + padding: .2em 0; +} +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + position: absolute; + top: 2px; + width: 1.8em; + height: 1.8em; +} +.ui-datepicker .ui-datepicker-prev-hover, +.ui-datepicker .ui-datepicker-next-hover { + top: 1px; +} +.ui-datepicker .ui-datepicker-prev { + left: 2px; +} +.ui-datepicker .ui-datepicker-next { + right: 2px; +} +.ui-datepicker .ui-datepicker-prev-hover { + left: 1px; +} +.ui-datepicker .ui-datepicker-next-hover { + right: 1px; +} +.ui-datepicker .ui-datepicker-prev span, +.ui-datepicker .ui-datepicker-next span { + display: block; + position: absolute; + left: 50%; + margin-left: -8px; + top: 50%; + margin-top: -8px; +} +.ui-datepicker .ui-datepicker-title { + margin: 0 2.3em; + line-height: 1.8em; + text-align: center; +} +.ui-datepicker .ui-datepicker-title select { + font-size: 1em; + margin: 1px 0; +} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { + width: 45%; +} +.ui-datepicker table { + width: 100%; + font-size: .9em; + border-collapse: collapse; + margin: 0 0 .4em; +} +.ui-datepicker th { + padding: .7em .3em; + text-align: center; + font-weight: bold; + border: 0; +} +.ui-datepicker td { + border: 0; + padding: 1px; +} +.ui-datepicker td span, +.ui-datepicker td a { + display: block; + padding: .2em; + text-align: right; + text-decoration: none; +} +.ui-datepicker .ui-datepicker-buttonpane { + background-image: none; + margin: .7em 0 0 0; + padding: 0 .2em; + border-left: 0; + border-right: 0; + border-bottom: 0; +} +.ui-datepicker .ui-datepicker-buttonpane button { + float: right; + margin: .5em .2em .4em; + cursor: pointer; + padding: .2em .6em .3em .6em; + width: auto; + overflow: visible; +} +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: left; +} + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { + width: auto; +} +.ui-datepicker-multi .ui-datepicker-group { + float: left; +} +.ui-datepicker-multi .ui-datepicker-group table { + width: 95%; + margin: 0 auto .4em; +} +.ui-datepicker-multi-2 .ui-datepicker-group { + width: 50%; +} +.ui-datepicker-multi-3 .ui-datepicker-group { + width: 33.3%; +} +.ui-datepicker-multi-4 .ui-datepicker-group { + width: 25%; +} +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { + border-left-width: 0; +} +.ui-datepicker-multi .ui-datepicker-buttonpane { + clear: left; +} +.ui-datepicker-row-break { + clear: both; + width: 100%; + font-size: 0; +} + +/* RTL support */ +.ui-datepicker-rtl { + direction: rtl; +} +.ui-datepicker-rtl .ui-datepicker-prev { + right: 2px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next { + left: 2px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-prev:hover { + right: 1px; + left: auto; +} +.ui-datepicker-rtl .ui-datepicker-next:hover { + left: 1px; + right: auto; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane { + clear: right; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button { + float: left; +} +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, +.ui-datepicker-rtl .ui-datepicker-group { + float: right; +} +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { + border-right-width: 0; + border-left-width: 1px; +} +.ui-dialog { + overflow: hidden; + position: absolute; + top: 0; + left: 0; + padding: .2em; + outline: 0; +} +.ui-dialog .ui-dialog-titlebar { + padding: .4em 1em; + position: relative; +} +.ui-dialog .ui-dialog-title { + float: left; + margin: .1em 0; + white-space: nowrap; + width: 90%; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-dialog .ui-dialog-titlebar-close { + position: absolute; + right: .3em; + top: 50%; + width: 20px; + margin: -10px 0 0 0; + padding: 1px; + height: 20px; +} +.ui-dialog .ui-dialog-content { + position: relative; + border: 0; + padding: .5em 1em; + background: none; + overflow: auto; +} +.ui-dialog .ui-dialog-buttonpane { + text-align: left; + border-width: 1px 0 0 0; + background-image: none; + margin-top: .5em; + padding: .3em 1em .5em .4em; +} +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: right; +} +.ui-dialog .ui-dialog-buttonpane button { + margin: .5em .4em .5em 0; + cursor: pointer; +} +.ui-dialog .ui-resizable-se { + width: 12px; + height: 12px; + right: -5px; + bottom: -5px; + background-position: 16px 16px; +} +.ui-draggable .ui-dialog-titlebar { + cursor: move; +} +.ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: none; +} +.ui-menu .ui-menu { + position: absolute; +} +.ui-menu .ui-menu-item { + position: relative; + margin: 0; + padding: 3px 1em 3px .4em; + cursor: pointer; + min-height: 0; /* support: IE7 */ + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} +.ui-menu .ui-menu-divider { + margin: 5px 0; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-state-focus, +.ui-menu .ui-state-active { + margin: -1px; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item { + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + margin: auto 0; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} +.ui-progressbar { + height: 2em; + text-align: left; + overflow: hidden; +} +.ui-progressbar .ui-progressbar-value { + margin: -1px; + height: 100%; +} +.ui-progressbar .ui-progressbar-overlay { + background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); + height: 100%; + filter: alpha(opacity=25); /* support: IE8 */ + opacity: 0.25; +} +.ui-progressbar-indeterminate .ui-progressbar-value { + background-image: none; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; + -ms-touch-action: none; + touch-action: none; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-selectable { + -ms-touch-action: none; + touch-action: none; +} +.ui-selectable-helper { + position: absolute; + z-index: 100; + border: 1px dotted black; +} +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + /* Support: IE7 */ + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-button { + display: inline-block; + overflow: hidden; + position: relative; + text-decoration: none; + cursor: pointer; +} +.ui-selectmenu-button span.ui-icon { + right: 0.5em; + left: auto; + margin-top: -8px; + position: absolute; + top: 50%; +} +.ui-selectmenu-button span.ui-selectmenu-text { + text-align: left; + padding: 0.4em 2.1em 0.4em 1em; + display: block; + line-height: 1.4; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; + -ms-touch-action: none; + touch-action: none; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* support: IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} +.ui-sortable-handle { + -ms-touch-action: none; + touch-action: none; +} +.ui-spinner { + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} +.ui-spinner-input { + border: none; + background: none; + color: inherit; + padding: 0; + margin: .2em 0; + vertical-align: middle; + margin-left: .4em; + margin-right: 22px; +} +.ui-spinner-button { + width: 16px; + height: 50%; + font-size: .5em; + padding: 0; + margin: 0; + text-align: center; + position: absolute; + cursor: default; + display: block; + overflow: hidden; + right: 0; +} +/* more specificity required here to override default borders */ +.ui-spinner a.ui-spinner-button { + border-top: none; + border-bottom: none; + border-right: none; +} +/* vertically center icon */ +.ui-spinner .ui-icon { + position: absolute; + margin-top: -8px; + top: 50%; + left: 0; +} +.ui-spinner-up { + top: 0; +} +.ui-spinner-down { + bottom: 0; +} + +/* TR overrides */ +.ui-spinner .ui-icon-triangle-1-s { + /* need to fix icons sprite */ + background-position: -65px -16px; +} +.ui-tabs { + position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ + padding: .2em; +} +.ui-tabs .ui-tabs-nav { + margin: 0; + padding: .2em .2em 0; +} +.ui-tabs .ui-tabs-nav li { + list-style: none; + float: left; + position: relative; + top: 0; + margin: 1px .2em 0 0; + border-bottom-width: 0; + padding: 0; + white-space: nowrap; +} +.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + float: left; + padding: .5em 1em; + text-decoration: none; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active { + margin-bottom: -1px; + padding-bottom: 1px; +} +.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, +.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { + cursor: text; +} +.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { + cursor: pointer; +} +.ui-tabs .ui-tabs-panel { + display: block; + border-width: 0; + padding: 1em 1.4em; + background: none; +} +.ui-tooltip { + padding: 8px; + position: absolute; + z-index: 9999; + max-width: 300px; + -webkit-box-shadow: 0 0 5px #aaa; + box-shadow: 0 0 5px #aaa; +} +body .ui-tooltip { + border-width: 2px; +} + +/* Component containers +----------------------------------*/ +.ui-widget { + font-family: Verdana,Arial,sans-serif; + font-size: 1.1em; +} +.ui-widget .ui-widget { + font-size: 1em; +} +.ui-widget input, +.ui-widget select, +.ui-widget textarea, +.ui-widget button { + font-family: Verdana,Arial,sans-serif; + font-size: 1em; +} +.ui-widget-content { + border: 1px solid #aaaaaa; + background: #ffffff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x; + color: #222222; +} +.ui-widget-content a { + color: #222222; +} +.ui-widget-header { + border: 1px solid #aaaaaa; + background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; + color: #222222; + font-weight: bold; +} +.ui-widget-header a { + color: #222222; +} + +/* Interaction states +----------------------------------*/ +.ui-state-default, +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default { + border: 1px solid #d3d3d3; + background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #555555; +} +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited { + color: #555555; + text-decoration: none; +} +.ui-state-hover, +.ui-widget-content .ui-state-hover, +.ui-widget-header .ui-state-hover, +.ui-state-focus, +.ui-widget-content .ui-state-focus, +.ui-widget-header .ui-state-focus { + border: 1px solid #999999; + background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-hover a, +.ui-state-hover a:hover, +.ui-state-hover a:link, +.ui-state-hover a:visited, +.ui-state-focus a, +.ui-state-focus a:hover, +.ui-state-focus a:link, +.ui-state-focus a:visited { + color: #212121; + text-decoration: none; +} +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active { + border: 1px solid #aaaaaa; + background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + font-weight: normal; + color: #212121; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #212121; + text-decoration: none; +} + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, +.ui-widget-content .ui-state-highlight, +.ui-widget-header .ui-state-highlight { + border: 1px solid #fcefa1; + background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; + color: #363636; +} +.ui-state-highlight a, +.ui-widget-content .ui-state-highlight a, +.ui-widget-header .ui-state-highlight a { + color: #363636; +} +.ui-state-error, +.ui-widget-content .ui-state-error, +.ui-widget-header .ui-state-error { + border: 1px solid #cd0a0a; + background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; + color: #cd0a0a; +} +.ui-state-error a, +.ui-widget-content .ui-state-error a, +.ui-widget-header .ui-state-error a { + color: #cd0a0a; +} +.ui-state-error-text, +.ui-widget-content .ui-state-error-text, +.ui-widget-header .ui-state-error-text { + color: #cd0a0a; +} +.ui-priority-primary, +.ui-widget-content .ui-priority-primary, +.ui-widget-header .ui-priority-primary { + font-weight: bold; +} +.ui-priority-secondary, +.ui-widget-content .ui-priority-secondary, +.ui-widget-header .ui-priority-secondary { + opacity: .7; + filter:Alpha(Opacity=70); /* support: IE8 */ + font-weight: normal; +} +.ui-state-disabled, +.ui-widget-content .ui-state-disabled, +.ui-widget-header .ui-state-disabled { + opacity: .35; + filter:Alpha(Opacity=35); /* support: IE8 */ + background-image: none; +} +.ui-state-disabled .ui-icon { + filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ +} + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { + width: 16px; + height: 16px; +} +.ui-icon, +.ui-widget-content .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-widget-header .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png"); +} +.ui-state-default .ui-icon { + background-image: url("images/ui-icons_888888_256x240.png"); +} +.ui-state-hover .ui-icon, +.ui-state-focus .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-active .ui-icon { + background-image: url("images/ui-icons_454545_256x240.png"); +} +.ui-state-highlight .ui-icon { + background-image: url("images/ui-icons_2e83ff_256x240.png"); +} +.ui-state-error .ui-icon, +.ui-state-error-text .ui-icon { + background-image: url("images/ui-icons_cd0a0a_256x240.png"); +} + +/* positioning */ +.ui-icon-blank { background-position: 16px 16px; } +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-on { background-position: -96px -144px; } +.ui-icon-radio-off { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, +.ui-corner-top, +.ui-corner-left, +.ui-corner-tl { + border-top-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-top, +.ui-corner-right, +.ui-corner-tr { + border-top-right-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-left, +.ui-corner-bl { + border-bottom-left-radius: 4px; +} +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-br { + border-bottom-right-radius: 4px; +} + +/* Overlays */ +.ui-widget-overlay { + background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); /* support: IE8 */ +} +.ui-widget-shadow { + margin: -8px 0 0 -8px; + padding: 8px; + background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; + opacity: .3; + filter: Alpha(Opacity=30); /* support: IE8 */ + border-radius: 8px; +} diff --git a/css/vkinject.css b/css/vkinject.css new file mode 100644 index 0000000..f7dee0e --- /dev/null +++ b/css/vkinject.css @@ -0,0 +1,3 @@ +.friends-select { + margin: 10px; +} diff --git a/images/icons/icon38.png b/images/icons/icon38.png new file mode 100644 index 0000000..2aa17d7 Binary files /dev/null and b/images/icons/icon38.png differ diff --git a/js/background.js b/js/background.js new file mode 100644 index 0000000..9bb891a --- /dev/null +++ b/js/background.js @@ -0,0 +1,156 @@ +/** + * Gets object by parsing json in given url. + * @param {string} url JSON url. + * @return {object} Parsed JSON. + */ + +/** + * Gets object by parsing json in given url. + * @param {string} url - JSON url. + * @return {object} - Parsed JSON. + */ +function sendRequest(url) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + var result = null; + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + if ('response' in JSON.parse(xhr.responseText)) { + result = JSON.parse(xhr.responseText).response; + } + } + }; + xhr.send(); + return result; +} + +/** + * Gets age by date of birth. + * @param {date} bdate - Date of birth. + * @return {int} - Age of person born in bdate. + */ +function getAge(bdate) { + var datefields = bdate.split('.'); + if (bdate.length < 3) { + return 0; + } + var birthday = new Date(datefields[2], datefields[1] - 1, datefields[0]); + var ageDifMs = Date.now() - birthday.getTime(); + var ageDate = new Date(ageDifMs); + return Math.abs(ageDate.getUTCFullYear() - 1970); +} + +/** + * Returns value by key from url (e.g. http://domain.com/index.php#key1=val1&key2=val2&key3=val3). + * @param {string} url - Url to parse. + * @param {string} key - Key to extract value from url. + * @return {string} - Value by given key in url (or undefined if no key in url). + */ +function getUrlParameterValue(url, key) { + var params = url.substr(url.indexOf("#") + 1).split("&"); + for (var index = 0; index < params.length; index += 1) { + var param = params[index].split("="); + if (param[0] === key) { + return param[1]; + } + } + return undefined; +} + +/** Deletes Access Token from local storage. */ +function clearSession() { + localStorage.vkaccess_token = ''; +} + +/** Saves vkontakte API key into the local strorage. */ +function getToken() { + var client_id = 5300315; + var authUrl = 'https://oauth.vk.com/authorize?client_id=' + client_id + '&scope=users&redirect_uri=http%3A%2F%2Foauth.vk.com%2Fblank.html&display=page&response_type=token'; + chrome.tabs.create({url: authUrl,selected: true}, function(tab) { + authTabId = tab.id; + chrome.tabs.onUpdated.addListener(function tabUpdateListener(tabId, changeInfo) { + if(tabId === authTabId && typeof changeInfo.url != 'undefined' && changeInfo.status === "loading") { + if (changeInfo.url.indexOf('oauth.vk.com/blank.html') > -1 ) { + chrome.tabs.onUpdated.removeListener(tabUpdateListener); + vkAccessToken = getUrlParameterValue(changeInfo.url, 'access_token'); + localStorage.vkaccess_token = vkAccessToken; + chrome.tabs.remove(tabId, function(tab){}); + } + } + }); + }); +} + +/** Changing age bounds. */ +function changeAgeBounds(lowerbound, upperbound) { + localStorage.ageLowerbound = +lowerbound; + localStorage.ageUpperbound = +upperbound; +} + +/** Change default chart state. */ +function changeDefaultState(stateIndex) { + localStorage.stateIndex = +stateIndex; +} + +/** Returns default chart state. */ +function getDefaultState() { + return localStorage.stateIndex || 0; +} + +/** Handles data from client. */ +chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { + var responsingFunctions = { + 'isUser': { + 'function': isUser, + 'params': ['user'], + }, + 'getCurrentUserInfo': { + 'function': getCurrentUserInfo, + 'params': [], + }, + 'logout': { + 'function': clearSession, + 'params': [], + }, + 'auth': { + 'function': getToken, + 'params': [], + }, + 'getFriendsInfo': { + 'function': getFriendsInfo, + 'params': ['user'], + }, + 'changeAgeBounds': { + 'function': changeAgeBounds, + 'params': ['lowerbound', 'upperbound'], + }, + 'changeDefaultState': { + 'function': changeDefaultState, + 'params': ['stateIndex'], + }, + 'getDefaultState': { + 'function': getDefaultState, + 'params': [], + }, + }; + if (!(request.method in responsingFunctions)) { + sendResponse({}); + return; + } + var func = responsingFunctions[request.method].function; + var params_list = responsingFunctions[request.method].params; + var params = []; + for (var i = 0; i < params_list.length; i++) { + if (!(params_list[i] in request)) { + sendResponse({}); + return; + } + params.push(request[params_list[i]]); + } + sendResponse(func.apply(this, params) || {}); +}); + +/** Opening settings on icon click. */ +chrome.browserAction.onClicked.addListener(function(activeTab) { + chrome.tabs.create({ url: "pages/information.html" }); +}); diff --git a/js/information.js b/js/information.js new file mode 100644 index 0000000..6a2b15d --- /dev/null +++ b/js/information.js @@ -0,0 +1,44 @@ +$(function() { + chrome.runtime.sendMessage({method: "getCurrentUserInfo"}, function(response) { + if (!$.isEmptyObject(response)) { + $('.name').attr('href', 'http://vk.com/id' + response.id); + $('.name').text(response.first_name + ' ' + response.last_name); + $('.avatar').css("background-image", "url(" + response.photo_max + ")"); + $('.user-auth').hide(); + } else { + $('.user-description').hide(); + } + }); + + $('.logout').click(function() { + chrome.runtime.sendMessage({method: "logout"}, function(response) { + location.reload(); + }); + }); + + $('.auth').click(function() { + chrome.runtime.sendMessage({method: "auth"}, function(response) { + location.reload(); + }); + }); + + $("#slider").slider({ + range: true, + min: 14, + max: 80, + values: [14, 80], + slide: function(event, ui) { + $(".age-value").html(ui.values[0] + " - " + ui.values[1] + " лет"); + chrome.runtime.sendMessage({method: "changeAgeBounds", lowerbound: ui.values[0], upperbound: ui.values[1]}); + } + }); + $(".age-value").html($( "#slider").slider("values", 0) + " - " + $("#slider").slider("values", 1) + " лет"); + + chrome.runtime.sendMessage({method: "getDefaultState"}, function(response) { + $('.chart-state :nth-child(' + (+response + 1) + ')').prop('selected', true); + }); + + $('.chart-state').change(function() { + chrome.runtime.sendMessage({method: "changeDefaultState", stateIndex: $(".chart-state").prop('selectedIndex')}, function(response) {}); + }); +}); diff --git a/js/libs/Chart.min.js b/js/libs/Chart.min.js new file mode 100644 index 0000000..20d1403 --- /dev/null +++ b/js/libs/Chart.min.js @@ -0,0 +1,7744 @@ +/* + * ChartNew.js + * + * Vancoppenolle Francois - January 2014 + * francois.vancoppenolle@favomo.be + * + * GitHub community : https://github.com/FVANCOP/ChartNew.js + * + * This file is originally an adaptation of the chart.js source developped by Nick Downie (2013) + * https://github.com/nnnick/Chart.js. But since june 2014, Nick puts a new version with a + * refunded code. Current code of ChartNew.js is no more comparable to the code of Chart.js + * + * new charts compared to Chart.js + * + * horizontalBar + * horizontalStackedBar + * + * Added items compared to Chart.js: + * + * Title, Subtitle, footnotes, axis labels, unit label + * Y Axis on the right and/or the left + * canvas Border + * Legend + * crossText, crossImage + * graphMin, graphMax + * logarithmic y-axis (for line and bar) + * rotateLabels + * and lot of others... + * + */ + +// ctx.firstPass=0 or "undefined" : Chart has never been drawn (because dynamicDisplay = true and ctx has never been displayed in current screen) +// ctx.firstPass=1 : Chart has to be drawn with animation (if config.animation = true); +// ctx.firstPass=2 : chart has to be drawn without animation; +// ctx.firstPass=9 : chart is completely drawn; +// If chartJsResize called : increment the value of ctx.firstPass with a value of 10. + + +// non standard functions; + +var chartJSLineStyle=[]; + +chartJSLineStyle["solid"]=[]; +chartJSLineStyle["dotted"]=[1,4]; +chartJSLineStyle["shortDash"]=[2,1]; +chartJSLineStyle["dashed"]=[4,2]; +chartJSLineStyle["dashSpace"]=[4,6]; +chartJSLineStyle["longDashDot"]=[7,2,1,2]; +chartJSLineStyle["longDashShortDash"]=[10,4,4,4]; +chartJSLineStyle["gradient"]=[1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,9,9,8,8,7,7,6,6,5,5,4,4,3,3,2,2,1]; + +function lineStyleFn(data) +{ +if ((typeof chartJSLineStyle[data]) === "object")return chartJSLineStyle[data]; +else return chartJSLineStyle["solid"]; +}; + +if (typeof String.prototype.trim !== 'function') { + String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g, ''); + } +}; +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(searchElement /*, fromIndex */ ) { + "use strict"; + if (this == null) { + throw new TypeError(); + } + var t = Object(this); + var len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n != n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n != 0 && n != Infinity && n != -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + } +}; +var charJSPersonalDefaultOptions = {}; +var charJSPersonalDefaultOptionsLine = {} ; +var charJSPersonalDefaultOptionsRadar = {} ; +var charJSPersonalDefaultOptionsPolarArea = {} ; +var charJSPersonalDefaultOptionsPie = {}; +var charJSPersonalDefaultOptionsDoughnut = {}; +var charJSPersonalDefaultOptionsBar = {}; +var charJSPersonalDefaultOptionsStackedBar = {}; +var charJSPersonalDefaultOptionsHorizontalBar = {}; +var charJSPersonalDefaultOptionsHorizontalStackedBar = {}; + ///////// FUNCTIONS THAN CAN BE USED IN THE TEMPLATES /////////////////////////////////////////// + +function roundToWithThousands(config, num, place) { + var newval = 1 * unFormat(config, num); + if (typeof(newval) == "number" && place != "none") { + var roundVal; + if (place <= 0) { + roundVal = -place; + newval = +(Math.round(newval + "e+" + roundVal) + "e-" + roundVal); + } else { + roundVal = place; + var divval = "1e+" + roundVal; + newval = +(Math.round(newval / divval)) * divval; + } + } + newval = fmtChartJS(config, newval, "none"); + return (newval); +}; + +function unFormat(config, num) { + if ((config.decimalSeparator != "." || config.thousandSeparator != "") && typeof(num) == "string") { + var v1 = "" + num; + if (config.thousandSeparator != "") { + while (v1.indexOf(config.thousandSeparator) >= 0) v1 = "" + v1.replace(config.thousandSeparator, ""); + } + if (config.decimalSeparator != ".") v1 = "" + v1.replace(config.decimalSeparator, ".") + return 1 * v1; + } else { + return num; + } +}; +///////// ANNOTATE PART OF THE SCRIPT /////////////////////////////////////////// +/******************************************************************************** +Copyright (C) 1999 Thomas Brattli +This script is made by and copyrighted to Thomas Brattli +Visit for more great scripts. This may be used freely as long as this msg is intact! +I will also appriciate any links you could give me. +Distributed by Hypergurl +********************************************************************************/ +var cachebis = {}; + +function fmtChartJSPerso(config, value, fmt) { + switch (fmt) { + case "SampleJS_Format": + if (typeof(value) == "number") return_value = "My Format : " + value.toString() + " $"; + else return_value = value + "XX"; + break; + case "Change_Month": + if (typeof(value) == "string") return_value = value.toString() + " 2014"; + else return_value = value.toString() + "YY"; + break; + default: + return_value = value; + break; + } + return (return_value); +}; + +function fmtChartJS(config, value, fmt) { + var return_value; + if (fmt == "notformatted") { + return_value = value; + } else if (fmt == "none" && typeof(value) == "number") { + if (config.roundNumber != "none") { + var roundVal; + if (config.roundNumber <= 0) { + roundVal = -config.roundNumber; + value = +(Math.round(value + "e+" + roundVal) + "e-" + roundVal); + } else { + roundVal = config.roundNumber; + var divval = "1e+" + roundVal; + value = +(Math.round(value / divval)) * divval; + } + } + if (config.decimalSeparator != "." || config.thousandSeparator != "") { + return_value = value.toString().replace(/\./g, config.decimalSeparator); + if (config.thousandSeparator != "") { + var part1 = return_value; + var part2 = ""; + var posdec = part1.indexOf(config.decimalSeparator); + if (posdec >= 0) { + part2 = part1.substring(posdec + 1, part1.length); + part2 = part2.split('').reverse().join(''); // reverse string + part1 = part1.substring(0, posdec); + } + part1 = part1.toString().replace(/\B(?=(\d{3})+(?!\d))/g, config.thousandSeparator); + part2 = part2.split('').reverse().join(''); // reverse string + return_value = part1 + if (part2 != "") return_value = return_value + config.decimalSeparator + part2; + } + } else return_value = value; + } else if (fmt != "none" && fmt != "notformatted") { + return_value = fmtChartJSPerso(config, value, fmt); + } else { + return_value = value; + } + return (return_value); +}; + +function addParameters2Function(data, fctName, fctList) { + var mathFunctions = { + mean: { + data: data.data, + datasetNr: data.v11 + }, + varianz: { + data: data.data, + datasetNr: data.v11 + }, + stddev: { + data: data.data, + datasetNr: data.v11 + }, + cv: { + data: data.data, + datasetNr: data.v11 + }, + median: { + data: data.data, + datasetNr: data.v11 + } + }; + // difference to current value (v3) + dif = false; + if (fctName.substr(-3) == "Dif") { + fctName = fctName.substr(0, fctName.length - 3); + dif = true; + } + if (typeof eval(fctName) == "function") { + var parameter = eval(fctList + "." + fctName); + if (dif) { + // difference between v3 (current value) and math function + return data.v3 - window[fctName](parameter); + } + return window[fctName](parameter); + } + return null; +}; + +function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); +}; + +function tmplbis(str, data,config) { + newstr=str; + if(newstr.substr(0,config.templatesOpenTag.length)==config.templatesOpenTag)newstr="<%="+newstr.substr(config.templatesOpenTag.length,newstr.length-config.templatesOpenTag.length); + if(newstr.substr(newstr.length-config.templatesCloseTag.length,config.templatesCloseTag.length)==config.templatesCloseTag)newstr=newstr.substr(0,newstr.length-config.templatesCloseTag.length)+"%>"; + return tmplter(newstr,data); +} + +function tmplter(str, data) { + var mathFunctionList = ["mean", "varianz", "stddev", "cv", "median"]; + var regexMath = new RegExp('<%=((?:(?:.*?)\\W)??)((?:' + mathFunctionList.join('|') + ')(?:Dif)?)\\(([0-9]*?)\\)(.*?)%>', 'g'); + while (regexMath.test(str)) { + str = str.replace(regexMath, function($0, $1, $2, $3, $4) { + var rndFac; + if ($3) rndFac = $3; + else rndFac = 2; + var value = addParameters2Function(data, $2, "mathFunctions"); + if (isNumber(value)) + return '<%=' + $1 + '' + Math.round(Math.pow(10, rndFac) * value) / Math.pow(10, rndFac) + '' + $4 + '%>'; + return '<%= %>'; + }); + } + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + // first check if it's can be an id + var fn = /^[A-Za-z][-A-Za-z0-9_:.]*$/.test(str) ? cachebis[str] = cachebis[str] || + tmplter(document.getElementById(str).innerHTML) : + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + // Convert the template into pure JavaScript + str + .replace(/[\r\n]/g, "\\n") + .replace(/[\t]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + "');}return p.join('');"); + // Provide some basic currying to the user + return data ? fn(data) : fn; +}; +if (typeof CanvasRenderingContext2D !== 'undefined') { + /** + * ctx.prototype + * fillText option for canvas Multiline Support + * @param text string \n for newline + * @param x x position + * @param y y position + * @param yLevel = "bottom" => last line has this y-Pos [default], = "middle" => the middle line has this y-Pos) + * @param lineHeight lineHeight + * @param horizontal horizontal + */ + CanvasRenderingContext2D.prototype.fillTextMultiLine = function(text, x, y, yLevel, lineHeight,horizontal,detectMouseOnText,ctx,idText,rotate,x_decal,y_decal,posi,posj) { + var lines = ("" + text).split("\n"); + // if its one line => in the middle + // two lines one above the mid one below etc. + if (yLevel == "middle") { + if(horizontal)y -= ((lines.length - 1) / 2) * lineHeight; + } else if (yLevel == "bottom") { // default + if(horizontal)y -= (lines.length - 1) * lineHeight; + } + + var y_pos=y-lineHeight; + + for (var i = 0; i < lines.length; i++) { + this.fillText(lines[i], x, y); + y += lineHeight; + } + if(detectMouseOnText) { + var txtSize=ctx.measureTextMultiLine(text,lineHeight); + var x_pos=[]; + var y_pos=[]; + x_pos.p1=x_decal+x; + y_pos.p1=y_decal+y-lineHeight; + var rotateRV=(Math.PI/2)+rotate; + if(ctx.textAlign=="left" && yLevel=="top"){ + x_pos.p1+=lineHeight*Math.cos(rotateRV); + y_pos.p1+=lineHeight*Math.sin(rotateRV); + } else if(ctx.textAlign=="left" && yLevel=="middle"){ + x_pos.p1+=(lineHeight/2)*Math.cos(rotateRV); + y_pos.p1+=(lineHeight/2)*Math.sin(rotateRV); + } else if(ctx.textAlign=="left" && yLevel=="bottom"){ + // nothing to adapt; + } else if(ctx.textAlign=="center" && yLevel=="top"){ + x_pos.p1+=lineHeight*Math.cos(rotateRV)-(txtSize.textWidth/2)*Math.cos(rotate); + y_pos.p1+=lineHeight*Math.sin(rotateRV)-(txtSize.textWidth/2)*Math.sin(rotate); + } else if(ctx.textAlign=="center" && yLevel=="middle"){ + x_pos.p1+=(lineHeight/2)*Math.cos(rotateRV)-(txtSize.textWidth/2)*Math.cos(rotate); + y_pos.p1+=(lineHeight/2)*Math.sin(rotateRV)-(txtSize.textWidth/2)*Math.sin(rotate); + } else if(ctx.textAlign=="center" && yLevel=="bottom"){ + x_pos.p1-=(txtSize.textWidth/2)*Math.cos(rotate); + y_pos.p1-=(txtSize.textWidth/2)*Math.sin(rotate); + } else if(ctx.textAlign=="right" && yLevel=="top"){ + x_pos.p1+=(lineHeight*Math.cos(rotateRV)-txtSize.textWidth*Math.cos(rotate)); + y_pos.p1+=(lineHeight*Math.sin(rotateRV)-txtSize.textWidth*Math.sin(rotate)); + } else if(ctx.textAlign=="right" && yLevel=="middle"){ + x_pos.p1+=(lineHeight/2)*Math.cos(rotateRV)-txtSize.textWidth*Math.cos(rotate); + y_pos.p1+=(lineHeight/2)*Math.sin(rotateRV)-txtSize.textWidth*Math.sin(rotate); + } else if(ctx.textAlign=="right" && yLevel=="bottom"){ + x_pos.p1-=txtSize.textWidth*Math.cos(rotate); + y_pos.p1-=txtSize.textWidth*Math.sin(rotate); + } + + // Other corners of the rectangle; + + x_pos.p2=x_pos.p1+txtSize.textWidth*Math.cos(rotate); + y_pos.p2=y_pos.p1+txtSize.textWidth*Math.sin(rotate); + + x_pos.p3=x_pos.p1-lineHeight*Math.cos(rotateRV); + y_pos.p3=y_pos.p1-lineHeight*Math.sin(rotateRV); + + x_pos.p4=x_pos.p3+txtSize.textWidth*Math.cos(rotate); + y_pos.p4=y_pos.p3+txtSize.textWidth*Math.sin(rotate); + + jsTextMousePos[ctx.ChartNewId][jsTextMousePos[ctx.ChartNewId].length] = [idText,text,x_pos,y_pos,rotate,txtSize.textWidth,txtSize.textHeight,posi,posj]; + + } + }; + CanvasRenderingContext2D.prototype.measureTextMultiLine = function(text, lineHeight) { + var textWidth = 0; + var lg; + var lines = ("" + text).replace(/
/ig, "\n").split("\n"); + var textHeight = lines.length * lineHeight; + // if its one line => in the middle + // two lines one above the mid one below etc. + for (var i = 0; i < lines.length; i++) { + lg = this.measureText(lines[i]).width; + if (lg > textWidth) textWidth = lg; + } + return { + textWidth: textWidth, + textHeight: 1.5*textHeight + }; + }; + if (typeof CanvasRenderingContext2D.prototype.setLineDash !== 'function') { + CanvasRenderingContext2D.prototype.setLineDash = function( listdash) { + return 0; + }; + }; +}; +cursorDivCreated = false; + +function createCursorDiv() { + if (cursorDivCreated == false) { + var div = document.createElement('divCursor'); + div.id = 'divCursor'; + div.style.position = 'absolute'; + document.body.appendChild(div); + cursorDivCreated = true; + } +}; + +initChartJsResize = false; +var jsGraphResize = new Array(); + +function addResponsiveChart(id,ctx,data,config) { + initChartResize(); + var newSize=resizeGraph(ctx,config); + if(typeof ctx.prevWidth != "undefined") { + resizeCtx(ctx,newSize.newWidth,newSize.newHeight,config); + ctx.prevWidth=newSize.newWidth; + } else if (config.responsiveScaleContent && config.responsiveWindowInitialWidth) { + ctx.initialWidth =newSize.newWidth; + } + + ctx.prevWidth=newSize.newWidth; + ctx.prevHeight=newSize.newHeight; + jsGraphResize[jsGraphResize.length]= [id,ctx.tpchart,ctx,data,config]; +}; + +function initChartResize() { + if(initChartJsResize==false) { + if (window.addEventListener) { + window.addEventListener("resize", chartJsResize); + } else { + window.attachEvent("resize", chartJsResize); + } + } +}; + +var container; +function getMaximumWidth(domNode){ + if(domNode.parentNode!=null) + if(domNode.parentNode!=undefined) + container = domNode.parentNode; + return container.clientWidth; +}; + +function getMaximumHeight(domNode){ + if(domNode.parentNode!=null) + if(domNode.parentNode!=undefined) + container = domNode.parentNode; + return container.clientHeight; +}; + +function resizeCtx(ctx,newWidth,newHeight,config) +{ + if(typeof ctx.DefaultchartTextScale=="undefined")ctx.DefaultchartTextScale=config.chartTextScale; + if(typeof ctx.DefaultchartLineScale=="undefined")ctx.DefaultchartLineScale=config.chartLineScale; + if(typeof ctx.DefaultchartSpaceScale=="undefined")ctx.DefaultchartSpaceScale=config.chartSpaceScale; + + ctx.canvas.height = newHeight ; + ctx.canvas.width = newWidth; + /* new ratio */ + if(typeof ctx.chartTextScale != "undefined" && config.responsiveScaleContent) { + ctx.chartTextScale=ctx.DefaultchartTextScale*(newWidth/ctx.initialWidth); + ctx.chartLineScale=ctx.DefaultchartLineScale*(newWidth/ctx.initialWidth); + ctx.chartSpaceScale=ctx.DefaultchartSpaceScale*(newWidth/ctx.initialWidth); + } +}; + +function resizeGraph(ctx,config) { + if(typeof config.maintainAspectRatio == "undefined")config.maintainAspectRatio=true; + if(typeof config.responsiveMinWidth == "undefined")config.responsiveMinWidth=0; + if(typeof config.responsiveMinHeight == "undefined")config.responsiveMinHeight=0; + if(typeof config.responsiveMaxWidth == "undefined")config.responsiveMaxWidth=9999999; + if(typeof config.responsiveMaxHeight == "undefined")config.responsiveMaxHeight=9999999; + var canvas = ctx.canvas; + if(typeof ctx.aspectRatio == "undefined") { + ctx.aspectRatio = canvas.width / canvas.height; + } + + var newWidth = getMaximumWidth(canvas); + var newHeight = config.maintainAspectRatio ? newWidth / ctx.aspectRatio : getMaximumHeight(canvas); + newWidth=Math.min(config.responsiveMaxWidth,Math.max(config.responsiveMinWidth,newWidth)); + newHeight=Math.min(config.responsiveMaxHeight,Math.max(config.responsiveMinHeight,newHeight)); + return { newWidth : parseInt(newWidth), newHeight : parseInt(newHeight)}; +}; + + + +function chartJsResize() { + for (var i=0;i10) { + ctx.firstPass=2; + redrawGraph(ctx,data,config) ; + return true; + } else { + return false; + } +}; + +function updateChart(ctx,data,config,animation,runanimationcompletefunction) { + + if (ctx.firstPass==9) + { + if (window.devicePixelRatio && !(config.responsive==true)){ + ctx.canvas.width=ctx.canvas.width/window.devicePixelRatio; + ctx.canvas.height=ctx.canvas.height/window.devicePixelRatio; + } + + ctx.runanimationcompletefunction=runanimationcompletefunction; + + if(animation)ctx.firstPass=1; + else ctx.firstPass=2; + + if(config.responsive) { + // update jsGraphResize; + for (var i=0;i -1 && this.dom) ? 1 : 0; + this.ie4 = (document.all && !this.dom) ? 1 : 0; + this.ns5 = (this.dom && parseInt(this.ver) >= 5) ? 1 : 0; + this.ns4 = (document.layers && !this.dom) ? 1 : 0; + this.bw = (this.ie5 || this.ie4 || this.ns4 || this.ns5) + return this +}; +bw = new checkBrowser(); +//Set these variables: +fromLeft = 10; // How much from the left of the cursor should the div be? +fromTop = 10; // How much from the top of the cursor should the div be? +/******************************************************************** +Initilizes the objects +*********************************************************************/ +function cursorInit() { + scrolled = bw.ns4 || bw.ns5 ? "window.pageYOffset" : "document.body.scrollTop" + if (bw.ns4) document.captureEvents(Event.MOUSEMOVE) +}; +/******************************************************************** +Contructs the cursorobjects +*********************************************************************/ +function makeCursorObj(obj, nest) { + createCursorDiv(); + nest = (!nest) ? '' : 'document.' + nest + '.' + this.css = bw.dom ? document.getElementById(obj).style : bw.ie4 ? document.all[obj].style : bw.ns4 ? eval(nest + "document.layers." + obj) : 0; + this.moveIt = b_moveIt; + cursorInit(); + return this +}; + +function b_moveIt(x, y) { + this.x = x; + this.y = y; + this.css.left = this.x + "px"; + this.css.top = this.y + "px"; +}; + +function isIE() { + var myNav = navigator.userAgent.toLowerCase(); + return (myNav.indexOf('msie') != -1) ? parseInt(myNav.split('msie')[1]) : false; +}; + +function mergeChartConfig(defaults, userDefined) { + var returnObj = {}; + for (var attrname in defaults) { + returnObj[attrname] = defaults[attrname]; + } + for (var attrnameBis in userDefined) { + returnObj[attrnameBis] = userDefined[attrnameBis]; + } + return returnObj; +}; + +function sleep(ms) { + var dt = new Date(); + dt.setTime(dt.getTime() + ms); + while (new Date().getTime() < dt.getTime()) {}; +}; + +function saveCanvas(ctx, data, config) { + cvSave = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); + var saveCanvasConfig = { + savePng: false, + annotateDisplay: false, + animation: false, + dynamicDisplay: false + }; + var savePngConfig = mergeChartConfig(config, saveCanvasConfig); + savePngConfig.clearRect = false; + /* And ink them */ + + redrawGraph(ctx,data,savePngConfig); + var image; + if (config.savePngOutput == "NewWindow") { + image = ctx.canvas.toDataURL(); + ctx.putImageData(cvSave, 0, 0); + window.open(image, '_blank'); + } + if (config.savePngOutput == "CurrentWindow") { + image = ctx.canvas.toDataURL(); + ctx.putImageData(cvSave, 0, 0); + window.location.href = image; + } + if (config.savePngOutput == "Save") { + image = ctx.canvas.toDataURL(); + var downloadLink = document.createElement("a"); + downloadLink.href = image; + downloadLink.download = config.savePngName + ".png"; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + } +}; +if (typeof String.prototype.trim !== 'function') { + String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g, ''); + } +}; +var dynamicDisplay = new Array(); +var dynamicDisplayList = new Array(); + +function dynamicFunction(data, config, ctx) { + if (isIE() < 9 && isIE() != false) return(true); + + if (config.dynamicDisplay && ctx.firstPass==0) { + if (ctx.canvas.id == "") { + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.canvas.id = "Canvas_" + cvmillsec; + } + if (typeof(dynamicDisplay[ctx.canvas.id]) == "undefined") { + dynamicDisplayList[dynamicDisplayList["length"]] = ctx.canvas.id; + dynamicDisplay[ctx.canvas.id] = [ctx, data, config]; + window.onscroll = scrollFunction; + } + if (!isScrolledIntoView(ctx.canvas,config)) return false; + } + return true; +}; + +function isScrolledIntoView(element,config) { + var xPosition = 0; + var yPosition = 0; + var eltWidth, eltHeight; + if(typeof element.recomputedHeight=="undefined") { + if (window.devicePixelRatio) { + // 31/12/2015 - On retina display, the size of the canvas changes after the canvas is displayed. + // before it is displayd, the size on the screen is the size on non retina display; + // If we do not divide the height & width by the devicePixelRatio, and if the + // value of config.dynamicDisplayYPartOfChart and if there is a chart on to bottom of the + // web page, this chart will never be displayed.... + // If the Size of the canvas was directly the real size displayed on the web page, we should not + // divide the height/width by the devicePixelRatio.... (Bug in Brosers ?) + element.recomputedHeight=element.height/window.devicePixelRatio; + element.recomputedWidth=element.width/window.devicePixelRatio; + } else { + element.recomputedHeight=element.height; + element.recomputedWidth=element.width; + } + } + eltWidth=element.recomputedWidth; + eltHeight=element.recomputedHeight; + elem = element; + while (elem) { + xPosition += (elem.offsetLeft + elem.clientLeft); + yPosition += (elem.offsetTop + elem.clientTop); + elem = elem.offsetParent; + } + + if (xPosition + (eltWidth * config.dynamicDisplayXPartOfChart) >= window.pageXOffset && + xPosition + (eltWidth * config.dynamicDisplayXPartOfChart) <= window.pageXOffset + window.innerWidth && + yPosition + (eltHeight * config.dynamicDisplayYPartOfChart) >= window.pageYOffset && + yPosition + (eltHeight * config.dynamicDisplayYPartOfChart) <= window.pageYOffset + window.innerHeight + ) { + return (true); + } + else { + return false; + } +}; + +function scrollFunction() { + for (var i = 0; i < dynamicDisplayList["length"]; i++) { + if ((dynamicDisplay[dynamicDisplayList[i]][0]).firstPass==0) { + redrawGraph(dynamicDisplay[dynamicDisplayList[i]][0],dynamicDisplay[dynamicDisplayList[i]][1], dynamicDisplay[dynamicDisplayList[i]][2]); + } + } +}; + +var jsGraphAnnotate = new Array(); +var jsTextMousePos = new Array(); + +function clearAnnotate(ctxid) { + jsGraphAnnotate[ctxid] = []; + jsTextMousePos[ctxid] = []; +}; + +function getMousePos(canvas, evt) { + var rect = canvas.getBoundingClientRect(); + return { + x: evt.clientX - rect.left, + y: evt.clientY - rect.top + }; +}; + +var annotatePrevShow=-1; + +function isHighLighted(reference,ctx,data,statData,posi,posj,othervars){ + var i; + if(ctx.tpdata==0) { + if(typeof data.special=="object") { + for(i=data.special.length-1;i>=0;i--){ + if(data.special[i].typespecial=="highLight" && data.special[i].posi==posi && data.special[i].posj==posj) return true; + } + } + } else { + if(typeof data[0].special=="object") { + for(i=data[0].special.length-1;i>=0;i--){ + if(data[0].special[i].typespecial=="highLight" && data[0].special[i].posi==posi) return true; + } + } + } + return false; +}; + +function isNotHighLighted(reference,ctx,data,statData,posi,posj,othervars) { + return(!isHighLighted(reference,ctx,data,statData,posi,posj,othervars)); +} +function deleteHighLight(ctx,data) { + var i; + if(ctx.tpdata==0) { + if(typeof data.special=="object") { + for(i=data.special.length-1;i>=0;i--){ + if(data.special[i].typespecial=="highLight") data.special.splice(i,1); + } + } + } else { + if(typeof data[0].special=="object") { + for(i=data[0].special.length-1;i>=0;i--){ + if(data[0].special[i].typespecial=="highLight") data[0].special.splice(i,1); + } + } + } + +}; + +function highLightAction(action,ctx,data,config,v1,v2) { + if (ctx.firstPass!=9)return; + + var currentlyDisplayed=[-1,-1]; + var redisplay=false; + var i,j; + + + if(ctx.tpdata==0) { + if(typeof data.special=="object") { + for(i=0;i=0;i--){ + if(data.special[i].typespecial=="highLight") data.special.splice(i,1); + } + } else { + for(i=data[0].special.length-1;i>=0;i--){ + if(data[0].special[i].typespecial=="highLight") data[0].special.splice(i,1); + } + } + } + if(addNew) { + redisplay=true; + if(ctx.tpdata==0) { + if(typeof data.special == "undefined") data.special=[]; + if(config.highLightFullLine == "group") { + for(j=0;j myStatData.int_radius && distance < myStatData.radiusOffset) { + angle = (Math.acos((canvas_pos.x - myStatData.midPosX) / distance) % (2* Math.PI) + 2*Math.PI) % (2*Math.PI); + if (canvas_pos.y < myStatData.midPosY) angle = -angle; + angle = (((angle + 2 * Math.PI) % (2 * Math.PI)) + 2* Math.PI) % (2* Math.PI) ; + myStatData.startAngle=(((myStatData.startAngle + 2 * Math.PI) % (2 * Math.PI)) + 2* Math.PI) % (2* Math.PI); + myStatData.endAngle=(((myStatData.endAngle + 2 * Math.PI) % (2 * Math.PI)) + 2* Math.PI) % (2* Math.PI); + if(myStatData.endAngle myStatData.startAngle && angle < myStatData.endAngle) || (angle > myStatData.startAngle - 2 * Math.PI && angle < myStatData.endAngle - 2 * Math.PI) || (angle > myStatData.startAngle + 2 * Math.PI && angle < myStatData.endAngle + 2 * Math.PI)) { + myStatData.graphPosX = canvas_pos.x; + myStatData.graphPosY = canvas_pos.y; + onData = true; + if (action == "annotate" && jsGraphAnnotate[ctx.ChartNewId][i][4]) { + dispString = tmplbis(setOptionValue(true,1,"ANNOTATELABEL",ctx,data,jsGraphAnnotate[ctx.ChartNewId][i][3],undefined,config.annotateLabel,"annotateLabel",jsGraphAnnotate[ctx.ChartNewId][i][1],-1,{otherVal:true}), myStatData,config); + textMsr=ctx.measureTextMultiLine(dispString,1*annotateDIV.style.fontSize.replace("pt","")); + ctx.restore(); + annotateDIV.innerHTML = dispString; + show = true; + } else if(action=="highLight") { + show = true; + highLightAction("ARC",ctx,data,config,jsGraphAnnotate[ctx.ChartNewId][i][1],-1); + } else if(typeof funct=="function") { + funct(event, ctx, config, data, myStatData ); + } + if (action == "annotate" && jsGraphAnnotate[ctx.ChartNewId][i][4]) { + x = bw.ns4 || bw.ns5 ? event.pageX : event.x; + y = bw.ns4 || bw.ns5 ? event.pageY : event.y; + if (bw.ie4 || bw.ie5) y = y + eval(scrolled); + if(config.annotateRelocate===true) { + var relocateX, relocateY; + relocateX=0;relocateY=0; + if(x+fromLeft+textMsr.textWidth > window.innerWidth-rect.left-fromLeft)relocateX=-textMsr.textWidth; + if(y+fromTop+textMsr.textHeight > 1*window.innerHeight-1*rect.top+fromTop)relocateY-=(textMsr.textHeight+2*fromTop); + oCursor.moveIt(Math.max(8-rect.left,x + fromLeft+relocateX), Math.max(8-rect.top,y + fromTop + relocateY)); + } + else oCursor.moveIt(x + fromLeft, y + fromTop); + } + } + } + } else if (jsGraphAnnotate[ctx.ChartNewId][i][0] == "RECT") { + myStatData=jsGraphAnnotate[ctx.ChartNewId][i][3][jsGraphAnnotate[ctx.ChartNewId][i][1]][jsGraphAnnotate[ctx.ChartNewId][i][2]]; + topY=Math.max(myStatData.yPosBottom,myStatData.yPosTop); + bottomY=Math.min(myStatData.yPosBottom,myStatData.yPosTop); + if (topY-bottomY leftX && canvas_pos.x < rightX && canvas_pos.y < topY && canvas_pos.y > bottomY) { + myStatData.graphPosX = canvas_pos.x; + myStatData.graphPosY = canvas_pos.y; + onData = true; + if (action == "annotate" && jsGraphAnnotate[ctx.ChartNewId][i][4]) { + dispString = tmplbis(setOptionValue(true,1,"ANNOTATELABEL",ctx,data,jsGraphAnnotate[ctx.ChartNewId][i][3],undefined,config.annotateLabel,"annotateLabel",jsGraphAnnotate[ctx.ChartNewId][i][1],jsGraphAnnotate[ctx.ChartNewId][i][2],{otherVal:true}), myStatData,config); + textMsr=ctx.measureTextMultiLine(dispString,1*annotateDIV.style.fontSize.replace("pt","")); + ctx.restore(); + annotateDIV.innerHTML = dispString; + show = true; + } else if(action=="highLight") { + show = true; + highLightAction("RECT",ctx,data,config,jsGraphAnnotate[ctx.ChartNewId][i][1],jsGraphAnnotate[ctx.ChartNewId][i][2]); + } else if(typeof funct=="function") { + funct(event, ctx, config, data, myStatData ); + } + if (action == "annotate" && jsGraphAnnotate[ctx.ChartNewId][i][4]) { + x = bw.ns4 || bw.ns5 ? event.pageX : event.x; + y = bw.ns4 || bw.ns5 ? event.pageY : event.y; + if (bw.ie4 || bw.ie5) y = y + eval(scrolled); + if(config.annotateRelocate===true) { + var relocateX, relocateY; + relocateX=0;relocateY=0; + + if(x+fromLeft+textMsr.textWidth > window.innerWidth-rect.left-fromLeft)relocateX=-textMsr.textWidth; + if(y+fromTop+textMsr.textHeight > 1*window.innerHeight-1*rect.top+fromTop)relocateY-=(textMsr.textHeight+2*fromTop); + oCursor.moveIt(Math.max(8-rect.left,x + fromLeft+relocateX), Math.max(8-rect.top,y + fromTop + relocateY)); + } else oCursor.moveIt(x + fromLeft, y + fromTop); + } + } + } else if (jsGraphAnnotate[ctx.ChartNewId][i][0] == "POINT") { + myStatData=jsGraphAnnotate[ctx.ChartNewId][i][3][jsGraphAnnotate[ctx.ChartNewId][i][1]][jsGraphAnnotate[ctx.ChartNewId][i][2]]; + var distance; + if(config.detectAnnotateOnFullLine) { + if(canvas_pos.x < Math.min(myStatData.annotateStartPosX,myStatData.annotateEndPosX)-Math.ceil(ctx.chartSpaceScale*config.pointHitDetectionRadius) || canvas_pos.x > Math.max(myStatData.annotateStartPosX,myStatData.annotateEndPosX)+Math.ceil(ctx.chartSpaceScale*config.pointHitDetectionRadius) || canvas_pos.y < Math.min(myStatData.annotateStartPosY,myStatData.annotateEndPosY)-Math.ceil(ctx.chartSpaceScale*config.pointHitDetectionRadius) || canvas_pos.y > Math.max(myStatData.annotateStartPosY,myStatData.annotateEndPosY)+Math.ceil(ctx.chartSpaceScale*config.pointHitDetectionRadius)) { + distance=Math.ceil(ctx.chartSpaceScale*config.pointHitDetectionRadius)+1; + } else { + if(typeof myStatData.D1A=="undefined") { + distance=Math.abs(canvas_pos.x-myStatData.posX); + } else if(typeof myStatData.D2A=="undefined") { + distance=Math.abs(canvas_pos.y-myStatData.posY); + } else { + var D2B=-myStatData.D2A*canvas_pos.x+canvas_pos.y; + var g=-(myStatData.D1B-D2B)/(myStatData.D1A-myStatData.D2A); + var h=myStatData.D2A*g+D2B; + distance=Math.sqrt((canvas_pos.x - g) * (canvas_pos.x - g) + (canvas_pos.y - h) * (canvas_pos.y - h)); + } + + } + + } else { + distance = Math.sqrt((canvas_pos.x - myStatData.posX) * (canvas_pos.x - myStatData.posX) + (canvas_pos.y - myStatData.posY) * (canvas_pos.y - myStatData.posY)); + } + if (distance < Math.ceil(ctx.chartSpaceScale*config.pointHitDetectionRadius)) { + myStatData.graphPosX = canvas_pos.x; + myStatData.graphPosY = canvas_pos.y; + onData = true; + if (action == "annotate" && jsGraphAnnotate[ctx.ChartNewId][i][4]) { + dispString = tmplbis(setOptionValue(true,1,"ANNOTATELABEL",ctx,data,jsGraphAnnotate[ctx.ChartNewId][i][3],undefined,config.annotateLabel,"annotateLabel",jsGraphAnnotate[ctx.ChartNewId][i][1],jsGraphAnnotate[ctx.ChartNewId][i][2],{otherVal:true}), myStatData,config); + textMsr=ctx.measureTextMultiLine(dispString,1*annotateDIV.style.fontSize.replace("pt","")); + ctx.restore(); + annotateDIV.innerHTML = dispString; + show = true; + } else if(action=="highLight") { + show = true; + highLightAction("POINT",ctx,data,config,jsGraphAnnotate[ctx.ChartNewId][i][1],jsGraphAnnotate[ctx.ChartNewId][i][2]); + } else if(typeof funct=="function"){ + funct(event, ctx, config, data, myStatData); + } + if (action == "annotate" && jsGraphAnnotate[ctx.ChartNewId][i][4]) { + x = bw.ns4 || bw.ns5 ? event.pageX : event.x; + y = bw.ns4 || bw.ns5 ? event.pageY : event.y; + if (bw.ie4 || bw.ie5) y = y + eval(scrolled); + if(config.annotateRelocate===true) { + var relocateX, relocateY; + relocateX=0;relocateY=0; + if(x+fromLeft+textMsr.textWidth > window.innerWidth-rect.left-fromLeft)relocateX=-textMsr.textWidth; + if(y+fromTop+textMsr.textHeight > 1*window.innerHeight-1*rect.top+fromTop)relocateY-=(textMsr.textHeight+2*fromTop); + oCursor.moveIt(Math.max(8-rect.left,x + fromLeft+relocateX), Math.max(8-rect.top,y + fromTop + relocateY)); + } + else oCursor.moveIt(x + fromLeft, y + fromTop); + } + } + } + if (action == "annotate" && jsGraphAnnotate[ctx.ChartNewId][i][4]) { + annotateDIV.style.display = show ? '' : 'none'; + if(show && annotatePrevShow != i){ + if(annotatePrevShow >=0 && typeof config.annotateFunctionOut=="function") { + if(jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][0] == "ARC") config.annotateFunctionOut("OUTANNOTATE",ctx,data,jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][3],jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][1],-1,null); + else config.annotateFunctionOut("OUTANNOTATE",ctx,data,jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][3],jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][1],jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][2],null); + } + annotatePrevShow=i; + if(typeof config.annotateFunctionIn=="function") { + if(jsGraphAnnotate[ctx.ChartNewId][i][0] == "ARC")config.annotateFunctionIn("INANNOTATE",ctx,data,jsGraphAnnotate[ctx.ChartNewId][i][3],jsGraphAnnotate[ctx.ChartNewId][i][1],-1,null); + else config.annotateFunctionIn("INANNOTATE",ctx,data,jsGraphAnnotate[ctx.ChartNewId][i][3],jsGraphAnnotate[ctx.ChartNewId][i][1],jsGraphAnnotate[ctx.ChartNewId][i][2],null); + } + } + //show=false; + } + } + if(show==false && action=="annotate" && annotatePrevShow >=0) { + if(typeof config.annotateFunctionOut=="function") { + if(jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][0] == "ARC") config.annotateFunctionOut("OUTANNOTATE",ctx,data,jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][3],jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][1],-1,null); + else config.annotateFunctionOut("OUTANNOTATE",ctx,data,jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][3],jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][1],jsGraphAnnotate[ctx.ChartNewId][annotatePrevShow][2],null); + } + annotatePrevShow=-1; + } + if(show==false && action=="highLight") { + highLightAction("HIDE",ctx,data,config,null,null); + } + + var inRect; + if (action != "annotate" && action != "highLight") { + if(config.detectMouseOnText) { + for(var i=0;i Math.max(jsTextMousePos[ctx.ChartNewId][i][2].p1,jsTextMousePos[ctx.ChartNewId][i][2].p2))inRect=false; + if(canvas_pos.y < Math.min(jsTextMousePos[ctx.ChartNewId][i][3].p1,jsTextMousePos[ctx.ChartNewId][i][3].p3))inRect=false; + if(canvas_pos.y > Math.max(jsTextMousePos[ctx.ChartNewId][i][3].p1,jsTextMousePos[ctx.ChartNewId][i][3].p3))inRect=false; + } else if(Math.abs(jsTextMousePos[ctx.ChartNewId][i][2].p1 - jsTextMousePos[ctx.ChartNewId][i][2].p2) Math.max(jsTextMousePos[ctx.ChartNewId][i][2].p1,jsTextMousePos[ctx.ChartNewId][i][2].p3))inRect=false; + if(canvas_pos.y < Math.min(jsTextMousePos[ctx.ChartNewId][i][3].p1,jsTextMousePos[ctx.ChartNewId][i][3].p2))inRect=false; + if(canvas_pos.y > Math.max(jsTextMousePos[ctx.ChartNewId][i][3].p1,jsTextMousePos[ctx.ChartNewId][i][3].p2))inRect=false; + } else { + // D12 & D34; + var P12=Math.tan(jsTextMousePos[ctx.ChartNewId][i][4]); + var D12=jsTextMousePos[ctx.ChartNewId][i][3].p1-P12*jsTextMousePos[ctx.ChartNewId][i][2].p1; + var D34=jsTextMousePos[ctx.ChartNewId][i][3].p3-P12*jsTextMousePos[ctx.ChartNewId][i][2].p3; + // D13 & D24; + var P13=-1/P12; + var D13=jsTextMousePos[ctx.ChartNewId][i][3].p1-P13*jsTextMousePos[ctx.ChartNewId][i][2].p1; + var D24=jsTextMousePos[ctx.ChartNewId][i][3].p4-P13*jsTextMousePos[ctx.ChartNewId][i][2].p4; + // Check if in rectangle; + + var y1=P12*canvas_pos.x+D12; + var y2=P12*canvas_pos.x+D34; + var y3=P13*canvas_pos.x+D13; + var y4=P13*canvas_pos.x+D24; + + if(canvas_pos.y < Math.min(y1,y2))inRect=false; + if(canvas_pos.y > Math.max(y1,y2))inRect=false; + if(canvas_pos.y < Math.min(y3,y4))inRect=false; + if(canvas_pos.y > Math.max(y3,y4))inRect=false; + } + if(inRect){onData=true;funct(event, ctx, config, data, {type:"CLICKONTEXT",values:jsTextMousePos[ctx.ChartNewId][i]});} + } + } + if(onData==false)funct(event, ctx, config, data, null); + } +}; +///////// GRAPHICAL PART OF THE SCRIPT /////////////////////////////////////////// +//Define the global Chart Variable as a class. +window.Chart = function(context) { + var chart = this; + //Easing functions adapted from Robert Penner's easing equations + //http://www.robertpenner.com/easing/ + var animationOptions = { + linear: function(t) { + return t; + }, + easeInQuad: function(t) { + return t * t; + }, + easeOutQuad: function(t) { + return -1 * t * (t - 2); + }, + easeInOutQuad: function(t) { + if ((t /= 1 / 2) < 1) return 1 / 2 * t * t; + return -1 / 2 * ((--t) * (t - 2) - 1); + }, + easeInCubic: function(t) { + return t * t * t; + }, + easeOutCubic: function(t) { + return 1 * ((t = t / 1 - 1) * t * t + 1); + }, + easeInOutCubic: function(t) { + if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t; + return 1 / 2 * ((t -= 2) * t * t + 2); + }, + easeInQuart: function(t) { + return t * t * t * t; + }, + easeOutQuart: function(t) { + return -1 * ((t = t / 1 - 1) * t * t * t - 1); + }, + easeInOutQuart: function(t) { + if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t; + return -1 / 2 * ((t -= 2) * t * t * t - 2); + }, + easeInQuint: function(t) { + return 1 * (t /= 1) * t * t * t * t; + }, + easeOutQuint: function(t) { + return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); + }, + easeInOutQuint: function(t) { + if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t; + return 1 / 2 * ((t -= 2) * t * t * t * t + 2); + }, + easeInSine: function(t) { + return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; + }, + easeOutSine: function(t) { + return 1 * Math.sin(t / 1 * (Math.PI / 2)); + }, + easeInOutSine: function(t) { + return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); + }, + easeInExpo: function(t) { + return (t == 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); + }, + easeOutExpo: function(t) { + return (t == 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); + }, + easeInOutExpo: function(t) { + if (t == 0) return 0; + if (t == 1) return 1; + if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1)); + return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); + }, + easeInCirc: function(t) { + if (t >= 1) return t; + return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); + }, + easeOutCirc: function(t) { + return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); + }, + easeInOutCirc: function(t) { + if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1); + return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t == 0) return 0; + if ((t /= 1) == 1) return 1; + if (!p) p = 1 * .3; + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); + }, + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t == 0) return 0; + if ((t /= 1) == 1) return 1; + if (!p) p = 1 * .3; + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; + }, + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t == 0) return 0; + if ((t /= 1 / 2) == 2) return 1; + if (!p) p = 1 * (.3 * 1.5); + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * .5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return 1 * (t /= 1) * t * ((s + 1) * t - s); + }, + easeOutBack: function(t) { + var s = 1.70158; + return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); + }, + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); + return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + easeInBounce: function(t) { + return 1 - animationOptions.easeOutBounce(1 - t); + }, + easeOutBounce: function(t) { + if ((t /= 1) < (1 / 2.75)) { + return 1 * (7.5625 * t * t); + } else if (t < (2 / 2.75)) { + return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + .75); + } else if (t < (2.5 / 2.75)) { + return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375); + } else { + return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375); + } + }, + easeInOutBounce: function(t) { + if (t < 1 / 2) return animationOptions.easeInBounce(t * 2) * .5; + return animationOptions.easeOutBounce(t * 2 - 1) * .5 + 1 * .5; + } + }; + //Variables global to the chart + + var width = context.canvas.width; + var height = context.canvas.height; + //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. + if (window.devicePixelRatio) { + context.canvas.style.width = width + "px"; + context.canvas.style.height = height + "px"; + context.canvas.height = height * window.devicePixelRatio; + context.canvas.width = width * window.devicePixelRatio; + context.scale(window.devicePixelRatio, window.devicePixelRatio); + }; + this.PolarArea = function(data, options) { + chart.PolarArea.defaults = { + highLightSet : { color : "red" }, + inGraphDataShow: false, + inGraphDataPaddingRadius: 5, + inGraphDataPaddingAngle: 0, + inGraphDataTmpl: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + inGraphDataAlign: "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataVAlign: "off-center", // "bottom", "center", "top", "off-center" or "to-center" + inGraphDataRotate: 0, // rotateAngle value (0->360) , "inRadiusAxis" or "inRadiusAxisRotateLabels" + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataRadiusPosition: 3, + inGraphDataAnglePosition: 2, + scaleOverlay: true, + scaleOverride: false, + scaleOverride2: false, + scaleGridLinesStep : 1, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleShowLine: true, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineWidth: 1, + scaleLineStyle: "solid", + scaleShowLabels: true, + scaleShowLabels2: true, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowLabelBackdrop: true, + scaleBackdropColor: "rgba(255,255,255,0.75)", + scaleBackdropPaddingY: 2, + scaleBackdropPaddingX: 2, + segmentShowStroke: true, + segmentStrokeColor: "#fff", + segmentStrokeStyle: "solid", + segmentStrokeWidth: 2, + animation: true, + animationByData : "ByArc", + animationSteps: 100, + animationEasing: "easeOutBounce", + animateRotate: true, + animateScale: false, + onAnimationComplete: null, + annotateLabel: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + startAngle: 90, + totalAmplitude : 360, + radiusScale : 1 + }; + if(isIE()<9 && isIE() != false)chart.PolarArea.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.PolarArea.defaults); + chart.PolarArea.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.PolarArea.defaults); + chart.PolarArea.defaults = mergeChartConfig(chart.PolarArea.defaults, charJSPersonalDefaultOptions); + chart.PolarArea.defaults = mergeChartConfig(chart.PolarArea.defaults, charJSPersonalDefaultOptionsPolarArea); + var config = (options) ? mergeChartConfig(chart.PolarArea.defaults, options) : chart.PolarArea.defaults; + return new PolarArea(data, config, context); + }; + this.Radar = function(data, options) { + chart.Radar.defaults = { + highLightSet : { pointDotRadius: 15, pointDot : true }, + inGraphDataShow: false, + inGraphDataPaddingRadius: 5, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign: "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataVAlign: "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataRotate: 0, // rotateAngle value (0->360) , "inRadiusAxis" or "inRadiusAxisRotateLabels" + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataRadiusPosition: 3, + yAxisMinimumInterval: "none", + scaleGridLinesStep : 1, + scaleOverlay: false, + scaleOverride: false, + scaleOverride2: false, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleShowLine: true, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineStyle: "solid", + scaleLineWidth: 1, + scaleShowLabels: false, + scaleShowLabels2: true, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowLabelBackdrop: true, + scaleBackdropColor: "rgba(255,255,255,0.75)", + scaleBackdropPaddingY: 2, + scaleBackdropPaddingX: 2, + angleShowLineOut: true, + angleLineColor: "rgba(0,0,0,.1)", + angleLineStyle: "solid", + angleLineWidth: 1, + pointLabelFontFamily: "'Arial'", + pointLabelFontStyle: "normal", + pointLabelFontSize: 12, + pointLabelFontColor: "#666", + pointDot: true, + pointDotRadius: 3, + pointDotStrokeWidth: 1, + pointDotStrokeStyle:"solid", + datasetFill: true, + datasetStrokeWidth: 2, + datasetStrokeStyle:"solid", + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3%>", + pointHitDetectionRadius : 10, + startAngle: 90 + }; + // merge annotate defaults + if(isIE()<9 && isIE() != false)chart.Radar.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.Radar.defaults); + chart.Radar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Radar.defaults); + chart.Radar.defaults = mergeChartConfig(chart.Radar.defaults, charJSPersonalDefaultOptions); + chart.Radar.defaults = mergeChartConfig(chart.Radar.defaults, charJSPersonalDefaultOptionsRadar); + var config = (options) ? mergeChartConfig(chart.Radar.defaults, options) : chart.Radar.defaults; + return new Radar(data, config, context); + }; + this.Pie = function(data, options) { + chart.Pie.defaults = chart.defaults.PieAndDoughnut; + // merge annotate defaults + if(isIE()<9 && isIE() != false)chart.Pie.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.Pie.defaults); + chart.Pie.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Pie.defaults); + chart.Pie.defaults = mergeChartConfig(chart.Pie.defaults, charJSPersonalDefaultOptions); + chart.Pie.defaults = mergeChartConfig(chart.Pie.defaults, charJSPersonalDefaultOptionsPie); + var config = (options) ? mergeChartConfig(chart.Pie.defaults, options) : chart.Pie.defaults; + return new Pie(data, config, context); + }; + this.Doughnut = function(data, options) { + chart.Doughnut.defaults = chart.defaults.PieAndDoughnut; + // merge annotate defaults + if(isIE()<9 && isIE() != false)chart.Doughnut.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.Doughnut.defaults); + chart.Doughnut.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Doughnut.defaults); + chart.Doughnut.defaults = mergeChartConfig(chart.Doughnut.defaults, charJSPersonalDefaultOptions); + chart.Doughnut.defaults = mergeChartConfig(chart.Doughnut.defaults, charJSPersonalDefaultOptionsDoughnut); + var config = (options) ? mergeChartConfig(chart.Doughnut.defaults, options) : chart.Doughnut.defaults; + return new Doughnut(data, config, context); + }; + this.Line = function(data, options) { + chart.Line.defaults = { + inGraphDataShow: false, + highLightSet : { pointDotRadius: 15, pointDot : true }, + inGraphDataPaddingX: 3, + inGraphDataPaddingY: 3, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign: "left", + inGraphDataVAlign: "bottom", + inGraphDataRotate: 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + drawXScaleLine: [{ + position: "bottom" + }], + scaleOverlay: false, + scaleOverride: false, + scaleOverride2: false, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleSteps2: null, + scaleStepWidth2: null, + scaleStartValue2: null, + scaleLabel2 : "<%=value%>", + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineStyle: "solid", + scaleLineWidth: 1, + scaleShowLabels: true, + scaleShowLabels2: true, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowGridLines: true, + scaleXGridLinesStep: 1, + scaleYGridLinesStep: 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineStyle: "solid", + scaleGridLineWidth: 1, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + // you can force an integer value between 0 and 180 degres + logarithmic: false, // can be 'fuzzy',true and false ('fuzzy' => if the gap between min and maximum is big it's using a logarithmic y-Axis scale + logarithmic2: false, // can be 'fuzzy',true and false ('fuzzy' => if the gap between min and maximum is big it's using a logarithmic y-Axis scale + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + bezierCurve: true, + linkType : 0, //0 : direct point to point; 1 = vertical lines; 2=angular link; + bezierCurveTension : 0.4, + pointDot: true, + pointDotRadius: 4, + pointDotStrokeStyle: "solid", + pointDotStrokeWidth: 2, + datasetStrokeStyle: "solid", + datasetStrokeWidth: 2, + datasetFill: true, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + extrapolateMissingData: true, + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3%>", + pointHitDetectionRadius : 10 + }; + // merge annotate defaults + if(isIE()<9 && isIE() != false)chart.Line.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.Line.defaults); + chart.Line.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Line.defaults); + chart.Line.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.Line.defaults); + chart.Line.defaults = mergeChartConfig(chart.Line.defaults, charJSPersonalDefaultOptions); + chart.Line.defaults = mergeChartConfig(chart.Line.defaults, charJSPersonalDefaultOptionsLine); + var config = (options) ? mergeChartConfig(chart.Line.defaults, options) : chart.Line.defaults; + return new Line(data, config, context); + }; + this.StackedBar = function(data, options) { + chart.StackedBar.defaults = { + annotateBarMinimumDetectionHeight : 0, + highLightSet : { pointDotRadius: 15, barStrokeWidth : 5, pointDot : true }, + inGraphDataShow: false, + inGraphDataPaddingX: 0, + inGraphDataPaddingY: -3, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign: "center", + inGraphDataVAlign: "top", + inGraphDataRotate: 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataXPosition: 2, + inGraphDataYPosition: 3, + scaleOverlay: false, + scaleOverride: false, + scaleOverride2: false, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineStyle: "solid", + scaleLineWidth: 1, + scaleShowLabels: true, + scaleShowLabels2: true, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowGridLines: true, + scaleXGridLinesStep: 1, + scaleYGridLinesStep: 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineStyle: "solid", + scaleGridLineWidth: 1, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + // you can force an integer value between 0 and 180 degres + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + pointDot: true, + pointDotRadius: 4, + pointDotStrokeStyle: "solid", + pointDotStrokeWidth: 2, + barShowStroke: true, +// barStrokeStyle: "solid", + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + spaceBetweenBar : 0, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + bezierCurve: true, + linkType : 0, //0 : direct point to point; 1 = vertical lines; 2=angular link; + bezierCurveTension : 0.4, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3 + ' (' + v6 + ' %)'%>", + pointHitDetectionRadius : 10 + }; + // merge annotate defaults + if(isIE()<9 && isIE() != false)chart.StackedBar.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.StackedBar.defaults); + chart.StackedBar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.StackedBar.defaults); + chart.StackedBar.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.StackedBar.defaults); + chart.StackedBar.defaults = mergeChartConfig(chart.StackedBar.defaults, charJSPersonalDefaultOptions); + chart.StackedBar.defaults = mergeChartConfig(chart.StackedBar.defaults, charJSPersonalDefaultOptionsStackedBar); + var config = (options) ? mergeChartConfig(chart.StackedBar.defaults, options) : chart.StackedBar.defaults; + return new StackedBar(data, config, context); + }; + this.HorizontalStackedBar = function(data, options) { + chart.HorizontalStackedBar.defaults = { + annotateBarMinimumDetectionHeight : 0, + highLightSet : { pointDotRadius: 15, barStrokeWidth : 5, pointDot : true }, + inGraphDataShow: false, + inGraphDataPaddingX: -3, + inGraphDataPaddingY: 0, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign: "right", + inGraphDataVAlign: "middle", + inGraphDataRotate: 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataXPosition: 3, + inGraphDataYPosition: 2, + scaleOverlay: false, + scaleOverride: false, + scaleOverride2: false, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineStyle: "solid", + scaleLineWidth: 1, + scaleShowLabels: true, + scaleShowLabels2: true, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowGridLines: true, + scaleXGridLinesStep: 1, + scaleYGridLinesStep: 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineStyle: "solid", + scaleGridLineWidth: 1, + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + barShowStroke: true, +// barStrokeStyle: "solid", + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + spaceBetweenBar : 0, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3 + ' (' + v6 + ' %)'%>", + reverseOrder: false + }; + // merge annotate defaults + if(isIE()<9 && isIE() != false)chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.HorizontalStackedBar.defaults); + chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.HorizontalStackedBar.defaults); + chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.HorizontalStackedBar.defaults); + chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.HorizontalStackedBar.defaults, charJSPersonalDefaultOptions); + chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.HorizontalStackedBar.defaults, charJSPersonalDefaultOptionsHorizontalStackedBar); + var config = (options) ? mergeChartConfig(chart.HorizontalStackedBar.defaults, options) : chart.HorizontalStackedBar.defaults; + return new HorizontalStackedBar(data, config, context); + }; + this.Bar = function(data, options) { + chart.Bar.defaults = { + annotateBarMinimumDetectionHeight : 0, + highLightSet : { pointDotRadius: 15, barStrokeWidth : 5, pointDot : true }, + inGraphDataShow: false, + inGraphDataPaddingX: 0, + inGraphDataPaddingY: 3, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign: "center", + inGraphDataVAlign: "bottom", + inGraphDataRotate: 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataXPosition: 2, + inGraphDataYPosition: 3, + scaleOverlay: false, + scaleOverride: false, + scaleOverride2: false, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleSteps2: null, + scaleStepWidth2: null, + scaleStartValue2: null, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineStyle: "solid", + scaleLineWidth: 1, + scaleShowLabels: true, + scaleShowLabels2: true, + scaleLabel: "<%=value%>", + scaleLabel2: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowGridLines: true, + scaleXGridLinesStep: 1, + scaleYGridLinesStep: 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineWidth: 1, + scaleGridLineStyle: "solid", + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + // you can force an integer value between 0 and 180 degres + logarithmic: false, // can be 'fuzzy',true and false ('fuzzy' => if the gap between min and maximum is big it's using a logarithmic y-Axis scale + logarithmic2: false, // can be 'fuzzy',true and false ('fuzzy' => if the gap between min and maximum is big it's using a logarithmic y-Axis scale + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + barShowStroke: true, +// barStrokeStyle: "solid", + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + barBorderRadius: 0, + pointDot: true, + pointDotRadius: 4, + pointDotStrokeStyle: "solid", + pointDotStrokeWidth: 2, + extrapolateMissingData: true, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + bezierCurve: true, + linkType : 0, //0 : direct point to point; 1 = vertical lines; 2=angular link; + bezierCurveTension : 0.4, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3 + ' (' + v6 + ' %)'%>", + pointHitDetectionRadius : 10 + }; + // merge annotate defaults + if(isIE()<9 && isIE() != false)chart.Bar.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.Bar.defaults); + chart.Bar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Bar.defaults); + chart.Bar.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.Bar.defaults); + chart.Bar.defaults = mergeChartConfig(chart.Bar.defaults, charJSPersonalDefaultOptions); + chart.Bar.defaults = mergeChartConfig(chart.Bar.defaults, charJSPersonalDefaultOptionsBar); + var config = (options) ? mergeChartConfig(chart.Bar.defaults, options) : chart.Bar.defaults; + return new Bar(data, config, context); + }; + this.HorizontalBar = function(data, options) { + chart.HorizontalBar.defaults = { + annotateBarMinimumDetectionHeight : 0, + highLightSet : { pointDotRadius: 15, barStrokeWidth : 5, pointDot : true }, + inGraphDataShow: false, + inGraphDataPaddingX: 3, + inGraphDataPaddingY: 0, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign: "left", + inGraphDataVAlign: "middle", + inGraphDataRotate: 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataXPosition: 3, + inGraphDataYPosition: 2, + scaleOverlay: false, + scaleOverride: false, + scaleOverride2: false, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineStyle: "solid", + scaleLineWidth: 1, + scaleShowLabels: true, + scaleShowLabels2: true, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowGridLines: true, + scaleXGridLinesStep: 1, + scaleYGridLinesStep: 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineStyle: "solid", + scaleGridLineWidth: 1, + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + barShowStroke: true, +// barStrokeStyle: "solid", + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + barBorderRadius: 0, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3 + ' (' + v6 + ' %)'%>", + reverseOrder: false + }; + // merge annotate defaults + if(isIE()<9 && isIE() != false)chart.HorizontalBar.defaults = mergeChartConfig(chart.defaults.IExplorer8, chart.HorizontalBar.defaults); + chart.HorizontalBar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.HorizontalBar.defaults); + chart.HorizontalBar.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.HorizontalBar.defaults); + chart.HorizontalBar.defaults = mergeChartConfig(chart.HorizontalBar.defaults, charJSPersonalDefaultOptions); + chart.HorizontalBar.defaults = mergeChartConfig(chart.HorizontalBar.defaults, charJSPersonalDefaultOptionsHorizontalBar); + var config = (options) ? mergeChartConfig(chart.HorizontalBar.defaults, options) : chart.HorizontalBar.defaults; + return new HorizontalBar(data, config, context); + }; + chart.defaults = {}; + + chart.defaults.IExplorer8 ={ + annotateBackgroundColor : "black", + annotateFontColor: "white" + }; + chart.defaults.commonOptions = { + chartTextScale : 1, + chartLineScale : 1, + chartSpaceScale : 1, + multiGraph: false, + clearRect: true, // do not change clearRect options; for internal use only + dynamicDisplay: false, + dynamicDisplayXPartOfChart : 0.5, + dynamicDisplayYPartOfChart : 0.5, + animationForceSetTimeOut : false, + graphSpaceBefore: 5, + graphSpaceAfter: 5, + canvasBorders: false, + canvasBackgroundColor: "none", + canvasBordersWidth: 3, + canvasBordersStyle: "solid", + canvasBordersColor: "black", + zeroValue : 0.0000000001, + graphTitle: "", + graphTitleFontFamily: "'Arial'", + graphTitleFontSize: 24, + graphTitleFontStyle: "bold", + graphTitleFontColor: "#666", + graphTitleSpaceBefore: 5, + graphTitleSpaceAfter: 5, + graphTitleBorders : false, + graphTitleBordersColor : "black", + graphTitleBordersXSpace : 3, + graphTitleBordersYSpace : 3, + graphTitleBordersWidth : 1, + graphTitleBordersStyle : "solid", + graphTitleBackgroundColor : "none", + graphSubTitle: "", + graphSubTitleFontFamily: "'Arial'", + graphSubTitleFontSize: 18, + graphSubTitleFontStyle: "normal", + graphSubTitleFontColor: "#666", + graphSubTitleSpaceBefore: 5, + graphSubTitleSpaceAfter: 5, + graphSubTitleBorders : false, + graphSubTitleBordersColor : "black", + graphSubTitleBordersXSpace : 3, + graphSubTitleBordersYSpace : 3, + graphSubTitleBordersWidth : 1, + graphSubTitleBordersStyle : "solid", + graphSubTitleBackgroundColor : "none", + footNote: "", + footNoteFontFamily: "'Arial'", + footNoteFontSize: 8, + footNoteFontStyle: "bold", + footNoteFontColor: "#666", + footNoteSpaceBefore: 5, + footNoteSpaceAfter: 5, + footNoteBorders : false, + footNoteBordersColor : "black", + footNoteBordersXSpace : 3, + footNoteBordersYSpace : 3, + footNoteBordersWidth : 1, + footNoteBordersStyle : "solid", + footNoteBackgroundColor : "none", + legend : false, + showSingleLegend: false, + maxLegendCols : 999, + legendPosY :4, + legendPosX : -2, + legendFontFamily: "'Arial'", + legendFontSize: 12, + legendFontStyle: "normal", + legendFontColor: "#666", + legendBlockSize: 15, + legendBorders: true, + legendBordersStyle: "solid", + legendBordersWidth: 1, + legendBordersColors: "#666", + legendBordersSpaceBefore: 5, + legendBordersSpaceAfter: 5, + legendBordersSpaceLeft: 5, + legendBordersSpaceRight: 5, + legendSpaceBeforeText: 5, + legendSpaceAfterText: 5, + legendSpaceLeftText: 5, + legendSpaceRightText: 5, + legendSpaceBetweenTextVertical: 5, + legendSpaceBetweenTextHorizontal: 5, + legendSpaceBetweenBoxAndText: 5, + legendFillColor : "rgba(0,0,0,0)", + legendXPadding : 0, + legendYPadding : 0, + inGraphDataBorders : false, + inGraphDataBordersColor : "black", + inGraphDataBordersXSpace : 3, + inGraphDataBordersYSpace : 3, + inGraphDataBordersWidth : 1, + inGraphDataBordersStyle : "solid", + inGraphDataBackgroundColor : "none", + annotateDisplay: false, + annotateRelocate: false, + savePng: false, + savePngOutput: "NewWindow", // Allowed values : "NewWindow", "CurrentWindow", "Save" + savePngFunction: "mousedown right", + savePngBackgroundColor: 'WHITE', + annotateFunction: "mousemove", + annotateFontFamily: "'Arial'", + annotateBorder: 'none', + annotateBorderRadius: '2px', + annotateBackgroundColor: 'rgba(0,0,0,0.8)', + annotateFontSize: 12, + annotateFontColor: 'white', + annotateFontStyle: "normal", + annotatePadding: "3px", + annotateClassName: "", + annotateFunctionIn: null, + annotateFunctionOut : null, + detectMouseOnText: false, + crossText: [""], + crossTextIter: ["all"], + crossTextOverlay: [true], + crossTextFontFamily: ["'Arial'"], + crossTextFontSize: [12], + crossTextFontStyle: ["normal"], + crossTextFontColor: ["rgba(220,220,220,1)"], + crossTextRelativePosX: [2], + crossTextRelativePosY: [2], + crossTextBaseline: ["middle"], + crossTextAlign: ["center"], + crossTextPosX: [0], + crossTextPosY: [0], + crossTextAngle: [0], + crossTextFunction: null, + crossTextBorders : [false], + crossTextBordersColor : ["black"], + crossTextBordersXSpace : [3], + crossTextBordersYSpace : [3], + crossTextBordersWidth : [1], + crossTextBordersStyle : ["solid"], + crossTextBackgroundColor : ["none"], + crossImage: [undefined], + crossImageIter: ["all"], + crossImageOverlay: [true], + crossImageRelativePosX: [2], + crossImageRelativePosY: [2], + crossImageBaseline: ["middle"], + crossImageAlign: ["center"], + crossImagePosX: [0], + crossImagePosY: [0], + crossImageAngle: [0], + spaceTop: 0, + spaceBottom: 0, + spaceRight: 0, + spaceLeft: 0, + decimalSeparator: ".", + thousandSeparator: "", + roundNumber: "none", + roundPct: -1, + templatesOpenTag : "<%=", + templatesCloseTag : "%>", + fmtV1: "none", + fmtV2: "none", + fmtV3: "none", + fmtV4: "none", + fmtV5: "none", + fmtV6: "none", + fmtV6T: "none", + fmtV7: "none", + fmtV8: "none", + fmtV8T: "none", + fmtV9: "none", + fmtV10: "none", + fmtV11: "none", + fmtV12: "none", + fmtV13: "none", + fmtXLabel: "none", + fmtYLabel: "none", + fmtYLabel2: "none", + fmtLegend: "none", + animationStartValue: 0, + animationStopValue: 1, + animationCount: 1, + animationPauseTime: 5, + animationBackward: false, + animationStartWithDataset: 1, + animationStartWithData: 1, + animationLeftToRight: false, + animationByDataset: false, + defaultStrokeColor: "rgba(220,220,220,1)", + defaultFillColor: "rgba(220,220,220,0.5)", + defaultLineWidth : 2, + graphMaximized: false, + contextMenu: true, + mouseDownRight: null, + mouseDownLeft: null, + mouseDownMiddle: null, + mouseMove: null, + mouseOut: null, + mouseWheel : null, + highLight : false, + highLightMouseFunction : "mousemove", + highLightFullLine : false, // true, false or "group" + highLightRerunEndFunction : false, + savePngName: "canvas", + responsive : false, + responsiveMinWidth : 0, + responsiveMinHeight : 0, + responsiveMaxWidth : 9999999, + responsiveMaxHeight : 9999999, + maintainAspectRatio: true, + responsiveScaleContent : false, + responsiveWindowInitialWidth : false, + pointMarker : "circle", // "circle","cross","plus","diamond","triangle","square" + initFunction : null, + beforeDrawFunction : null, + endDrawDataFunction : null, + endDrawScaleFunction : null + }; + + + chart.defaults.PieAndDoughnut = { + highLightSet : { expandOutRadius : 0.10, expandInRadius : -0.10 }, + inGraphDataShow: false, + inGraphDataPaddingRadius: 5, + inGraphDataPaddingAngle: 0, + inGraphDataTmpl: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + inGraphDataAlign: "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataVAlign: "off-center", // "bottom", "middle", "top", "off-center" or "to-center" + inGraphDataRotate: 0, // rotateAngle value (0->360) , "inRadiusAxis" or "inRadiusAxisRotateLabels" + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataRadiusPosition: 3, + inGraphDataAnglePosition: 2, + inGraphDataMinimumAngle : 0, + segmentShowStroke: true, + segmentStrokeColor: "#fff", + segmentStrokeStyle: "solid", + segmentStrokeWidth: 2, + percentageInnerCutout: 50, + animation: true, + animationByData : false, + animationSteps: 100, + animationEasing: "easeOutBounce", + animateRotate: true, + animateScale: false, + onAnimationComplete: null, + annotateLabel: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + startAngle: 90, + totalAmplitude : 360, + radiusScale: 1 + }; + + chart.defaults.xyAxisCommonOptions = { + maxBarWidth : -1, + yAxisMinimumInterval: "none", + yAxisMinimumInterval2: "none", + yScaleLabelsMinimumWidth: 0, + xScaleLabelsMinimumWidth: 0, + yAxisLeft: true, + yAxisRight: false, + xAxisBottom: true, + xAxisTop: false, + xAxisSpaceBetweenLabels: 5, + fullWidthGraph: false, + yAxisLabel: "", + yAxisLabel2: "", + yAxisFontFamily: "'Arial'", + yAxisFontSize: 16, + yAxisFontStyle: "normal", + yAxisFontColor: "#666", + yAxisLabelSpaceRight: 5, + yAxisLabelSpaceLeft: 5, + yAxisSpaceRight: 5, + yAxisSpaceLeft: 5, + yAxisLabelBorders : false, + yAxisLabelBordersColor : "black", + yAxisLabelBordersXSpace : 3, + yAxisLabelBordersYSpace : 3, + yAxisLabelBordersWidth : 1, + yAxisLabelBordersStyle : "solid", + yAxisLabelBackgroundColor : "none", + xAxisLabel: "", + xAxisFontFamily: "'Arial'", + xAxisFontSize: 16, + xAxisFontStyle: "normal", + xAxisFontColor: "#666", + xAxisLabelSpaceBefore: 5, + xAxisLabelSpaceAfter: 5, + xAxisSpaceBefore: 5, + xAxisSpaceAfter: 5, + xAxisLabelBorders : false, + xAxisLabelBordersColor : "black", + xAxisLabelBordersXSpace : 3, + xAxisLabelBordersYSpace : 3, + xAxisLabelBordersWidth : 1, + xAxisLabelBordersStyle : "solid", + xAxisLabelBackgroundColor : "none", + showXLabels : 1, + firstLabelToShow : 1, + showYLabels : 1, + firstYLabelToShow : 1, + yAxisUnit: "", + yAxisUnit2: "", + yAxisUnitFontFamily: "'Arial'", + yAxisUnitFontSize: 8, + yAxisUnitFontStyle: "normal", + yAxisUnitFontColor: "#666", + yAxisUnitSpaceBefore: 5, + yAxisUnitSpaceAfter: 5, + yAxisUnitBorders : false, + yAxisUnitBordersColor : "black", + yAxisUnitBordersXSpace : 3, + yAxisUnitBordersYSpace : 3, + yAxisUnitBordersWidth : 1, + yAxisUnitBordersStyle : "solid", + yAxisUnitBackgroundColor : "none" + }; + var clear = function(c) { + c.clearRect(0, 0, width, height); + }; + + + function init_and_start(ctx,data,config) { + + var i; + + if (typeof ctx.initialWidth == "undefined") ctx.initialWidth =ctx.canvas.width; + if (typeof ctx.chartTextScale == "undefined") ctx.chartTextScale=config.chartTextScale; + if (typeof ctx.chartLineScale == "undefined") ctx.chartLineScale=config.chartLineScale; + if (typeof ctx.chartSpaceScale == "undefined") ctx.chartSpaceScale=config.chartSpaceScale; + + if(typeof ctx.firstPass=="undefined"){ + ctx.firstPass=0; + if(config.responsive && !config.multiGraph) { + addResponsiveChart(ctx.ChartNewId,ctx,data,config); + // resize chart; + newSize=resizeGraph(ctx,config); + resizeCtx(ctx,newSize.newWidth,newSize.newHeight,config); + ctx.prevWidth=newSize.newWidth; + ctx.prevHeight=newSize.newHeight; + // call the redraw function if it is the first display, otherwise chart is not correct; + redrawGraph(ctx,data,config); + return false; + } + } + if(config.responsive && !config.multiGraph) { + // resize chart; + newSize=resizeGraph(ctx,config); + resizeCtx(ctx,newSize.newWidth,newSize.newHeight,config); + ctx.prevWidth=newSize.newWidth; + ctx.prevHeight=newSize.newHeight; + } + + if (typeof ctx.ChartNewId == "undefined") { + ctx.runanimationcompletefunction=true; + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.ChartNewId = ctx.tpchart + '_' + cvmillsec; + ctx._eventListeners = {}; + } + + if (!dynamicFunction(data, config, ctx)) return false; // if config.dynamicDisplay=true, chart has to be displayed only if in current screen; + + if (!config.multiGraph && ctx.firstPass!=0) + { + clearAnnotate(ctx.ChartNewId); +//ctx.removeEventListener("mousedown", getEventListeners(ctx.mousedown[0].listener)); +// ctx._eventListeners = undefined; +// ctx._eventListeners = {}; + } + + if (typeof jsGraphAnnotate[ctx.ChartNewId] == "undefined") { + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + jsTextMousePos[ctx.ChartNewId] = new Array(); + }; + + // convert label to title - for compatibility reasons with Chart.js; + switch(ctx.tpdata) { + case 1: + for(i=0;i 0) { + initPassVariableData_part2(statData,data,config,ctx,{midPosX : midPosX,midPosY : midPosY,int_radius : 0,ext_radius : scaleHop*calculatedScale.steps, calculatedScale : calculatedScale, scaleHop : scaleHop,outerVal : outerVal}); + animationLoop(config, drawScale, drawAllSegments, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, midPosX, midPosY, midPosX - ((Min([msr.availableHeight, msr.availableWidth]) / 2) - 5), midPosY + ((Min([msr.availableHeight, msr.availableWidth]) / 2) - 5), data, statData); + } else { + testRedraw(ctx,data,config); + ctx.firstPass=9; + } + + function drawAllSegments(animationDecimal) { + + var prevAngle=statData[0].firstAngle; + + var fixAngle=0; + var firstAngle=statData[0].firstAngle; + if(1*config.animationStartWithData>1 && 1*config.animationStartWithData-1 < data.length) { + fixAngle=(statData[config.animationStartWithData-1].startAngle-statData[0].firstAngle); + firstAngle=statData[config.animationStartWithData-1].startAngle; + } + + for (var i = 0; i < data.length; i++) { + var scaleAnimation = 1, + rotateAnimation = 1; + + if (config.animation) { + if (config.animateScale) { + scaleAnimation = animationDecimal; + } + if (config.animateRotate) { + rotateAnimation = animationDecimal; + } + } + correctedRotateAnimation = animationCorrection(rotateAnimation, data, config, i, -1,false).mainVal; + if (!(typeof(data[i].value) == 'undefined')) { + ctx.beginPath(); + if(config.animationByData == "ByArc") { + endAngle=statData[i].startAngle+correctedRotateAnimation*statData[i].segmentAngle; + ctx.arc(midPosX, midPosY, scaleAnimation * statData[i].radiusOffset, statData[i].startAngle, endAngle, false); + } else if(config.animationByData) { + if (i<1*config.animationStartWithData-1) { + ctx.arc(midPieX, midPieY, scaleAnimation * statData[i].radiusOffset, statData[i].startAngle, statData[i].endAngle,false); + } else if(statData[i].startAngle <= firstAngle+correctedRotateAnimation*(2*Math.PI-fixAngle) ) { + endAngle=statData[i].endAngle; + if(statData[i].endAngle > firstAngle+correctedRotateAnimation*(2*Math.PI-fixAngle)) { + endAngle=firstAngle+correctedRotateAnimation*(2*Math.PI-fixAngle); + } + ctx.arc(midPieX, midPieY, scaleAnimation * statData[i].radiusOffset, statData[i].startAngle, endAngle,false); + } else { + continue; + } + } else { + ctx.arc(midPieX, midPieY, scaleAnimation * statData[i].radiusOffset, prevAngle, prevAngle+correctedRotateAnimation * (statData[i].endAngle-statData[i].startAngle),false); + prevAngle=prevAngle+correctedRotateAnimation * (statData[i].endAngle-statData[i].startAngle); + } + ctx.lineTo(midPosX, midPosY); + ctx.closePath(); + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data[i].color,config.defaultFillColor,"color",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ); + ctx.fill(); + + if(config.segmentShowStroke=="merge") { /* avoid blank stripes between piece of chart */ + ctx.lineWidth =0; + ctx.strokeStyle =setOptionValue(true,1,"COLOR",ctx,data,statData,data[i].color,config.defaultFillColor,"color",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ); + ctx.setLineDash([]); + ctx.stroke(); + } + else if (config.segmentShowStroke) { + ctx.strokeStyle = config.segmentStrokeColor; + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.segmentStrokeWidth); + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"SEGMENTSTROKESTYLE",ctx,data,statData,data[i].segmentStrokeStyle,config.segmentStrokeStyle,"segmentStrokeStyle",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ))); + ctx.stroke(); + ctx.setLineDash([]); + } + } + } + + + if (animationDecimal >= config.animationStopValue) { + for (i = 0; i < data.length; i++) { + if (typeof(data[i].value) == 'undefined') continue; + jsGraphAnnotate[ctx.ChartNewId][jsGraphAnnotate[ctx.ChartNewId].length] = ["ARC", i, -1,statData,setOptionValue(true,1,"ANNOTATEDISPLAY",ctx,data,statData,data[i].annotateDisplay,config.annotateDisplay,"annotateDisplay",i,-1,{nullValue : true})]; + if (setOptionValue(true,1,"INGRAPHDATASHOW",ctx,data,statData,data[i].inGraphDataShow,config.inGraphDataShow,"inGraphDataShow",i,-1,{nullValue : true})) { + if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 1) posAngle = statData[i].realStartAngle + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + else if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 2) posAngle = (2*statData[i].realStartAngle - statData[i].segmentAngle) / 2 + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + else if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 3) posAngle = statData[i].realStartAngle - statData[i].segmentAngle + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,-1,{nullValue : true} ) == 1) labelRadius = 0 + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,-1,{nullValue: true} ); + else if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,-1,{nullValue : true} ) == 2) labelRadius = statData[i].radiusOffset / 2 + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,-1,{nullValue: true} ); + else if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,-1,{nullValue : true} ) == 3) labelRadius = statData[i].radiusOffset + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,-1,{nullValue: true} ); + else if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,-1,{nullValue : true} ) == 4) labelRadius = scaleHop * calculatedScale.steps + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,-1,{nullValue: true} ); + ctx.save() + if (setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,-1,{nullValue: true }) == "off-center") { + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) == "inRadiusAxis" || (posAngle + 2 * Math.PI) % (2 * Math.PI) >= 3 * Math.PI / 2 || (posAngle + 2 * Math.PI) % (2 * Math.PI) <= Math.PI / 2) ctx.textAlign = "left"; + + else ctx.textAlign = "right"; + } else if (setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,-1,{nullValue: true }) == "to-center") { + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) == "inRadiusAxis" || (posAngle + 2 * Math.PI) % (2 * Math.PI) >= 3 * Math.PI / 2 || (posAngle + 2 * Math.PI) % (2 * Math.PI) <= Math.PI / 2) ctx.textAlign = "right"; + else ctx.textAlign = "left"; + } else ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,-1,{nullValue: true }); + if (setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,-1,{nullValue : true} ) == "off-center") { + if ((posAngle + 2 * Math.PI) % (2 * Math.PI) > Math.PI) ctx.textBaseline = "top"; + else ctx.textBaseline = "bottom"; + } else if (setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,-1,{nullValue : true} ) == "to-center") { + if ((posAngle + 2 * Math.PI) % (2 * Math.PI) > Math.PI) ctx.textBaseline = "bottom"; + else ctx.textBaseline = "top"; + } else ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,-1,{nullValue : true} ); + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,-1,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,-1,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,-1,{nullValue : true} ); + ctx.fillStyle = setOptionValue(true,1,"INGRAPHDATAFONTCOLOR",ctx,data,statData,undefined,config.inGraphDataFontColor,"inGraphDataFontColor",i,-1,{nullValue : true} ); + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,-1,{nullValue : true} ), statData[i],config); + ctx.translate(midPosX + labelRadius * Math.cos(posAngle), midPosY - labelRadius * Math.sin(posAngle)); + var rotateVal=0; + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) == "inRadiusAxis") rotateVal=2 * Math.PI - posAngle; + else if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) == "inRadiusAxisRotateLabels") { + if ((posAngle + 2 * Math.PI) % (2 * Math.PI) > Math.PI / 2 && (posAngle + 2 * Math.PI) % (2 * Math.PI) < 3 * Math.PI / 2) rotateVal=3 * Math.PI - posAngle; + else rotateVal=2 * Math.PI - posAngle; + } else rotateVal=setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) * (Math.PI / 180); + ctx.rotate(rotateVal); + setTextBordersAndBackground(ctx,dispString,setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,-1,{nullValue : true} ),0,0,setOptionValue(true,1,"INGRAPHDATABORDERS",ctx,data,statData,undefined,config.inGraphDataBorders,"inGraphDataBorders",i,-1,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSCOLOR",ctx,data,statData,undefined,config.inGraphDataBordersColor,"inGraphDataBordersColor",i,-1,{nullValue : true} ),setOptionValue(true,ctx.chartLineScale,"INGRAPHDATABORDERSWIDTH",ctx,data,statData,undefined,config.inGraphDataBordersWidth,"inGraphDataBordersWidth",i,-1,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSXSPACE",ctx,data,statData,undefined,config.inGraphDataBordersXSpace,"inGraphDataBordersXSpace",i,-1,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSYSPACE",ctx,data,statData,undefined,config.inGraphDataBordersYSpace,"inGraphDataBordersYSpace",i,-1,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSSTYLE",ctx,data,statData,undefined,config.inGraphDataBordersStyle,"inGraphDataBordersStyle",i,-1,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABACKGROUNDCOLOR",ctx,data,statData,undefined,config.inGraphDataBackgroundColor,"inGraphDataBackgroundColor",i,-1,{nullValue : true} ),"INGRAPHDATA"); + ctx.fillTextMultiLine(dispString, 0, 0, ctx.textBaseline, setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,-1,{nullValue : true} ), true,config.detectMouseOnText,ctx,"INGRAPHDATA_TEXTMOUSE",rotateVal,midPosX + labelRadius * Math.cos(posAngle), midPosY - labelRadius * Math.sin(posAngle),i,-1); + ctx.restore(); + } + } + } + if(msr.legendMsr.dispLegend)drawLegend(msr.legendMsr,data,config,ctx,"PolarArea"); + }; + + function drawScale() { + for (var i = 0; i < calculatedScale.steps; i++) { + if (config.scaleShowLine && (i+1) % config.scaleGridLinesStep==0) { + ctx.beginPath(); + ctx.arc(midPosX, midPosY, scaleHop * (i + 1), 4*Math.PI-debAngle, 4*Math.PI-finAngle, true); + ctx.strokeStyle = config.scaleLineColor; + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + + ctx.stroke(); + ctx.setLineDash([]); + } + if (config.scaleShowLabels) { + + if(Math.abs(config.totalAmplitude-360) upperValue) { + upperValue = 1 * data[i].value; + } + if (1 * data[i].value < lowerValue) { + lowerValue = 1 * data[i].value; + } + }; + if(upperValue0) { + upperValue=upperValue*1.1; + lowerValue=lowerValue*0.9; + } else { + upperValue=upperValue*0.9; + lowerValue=lowerValue*1.1; + } + } + if(typeof config.graphMin=="function") lowerValue= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin,"graphMin",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if(typeof config.graphMax=="function") upperValue= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax,"graphMax",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax)) upperValue = config.graphMax; + var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight * 0.5)); + if(upperValue 1) currentAnimPc = currentAnimPc - 1; + if (!(typeof(data.datasets[i].data[j]) == 'undefined')) { + if (fPt == -1) { + ctx.beginPath(); + ctx.moveTo(midPosX + currentAnimPc * statData[i][j].offsetX, midPosY - currentAnimPc * statData[i][j].offsetY); + fPt = j; + } else { + ctx.lineTo(midPosX + currentAnimPc * statData[i][j].offsetX, midPosY - currentAnimPc * statData[i][j].offsetY); + } + } + } + ctx.closePath(); + if (config.datasetFill) { + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,-1,{animationValue : currentAnimPc, midPosX : statData[i][0].midPosX, midPosY : statData[i][0].midPosY, ext_radius : (config.animationLeftToRight ? 1 : currentAnimPc) * (statData[i][0].calculated_offset_max)} ); + } else ctx.fillStyle = "rgba(0,0,0,0)"; + + ctx.strokeStyle=setOptionValue(true,1,"STROKECOLOR",ctx,data,statData,data.datasets[i].strokeColor,config.defaultStrokeColor,"strokeColor",i,-1,{nullvalue : null} ); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*setOptionValue(true,1,"LINEWIDTH",ctx,data,statData,data.datasets[i].datasetStrokeWidth,config.datasetStrokeWidth,"datasetStrokeWidth",i,-1,{nullvalue : null} )); + ctx.fill(); + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"LINEDASH",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + if (animationDecimal >= config.animationStopValue) { + ctx.beginPath(); + + for (var k = 0; k < data.datasets[i].data.length; k++) { + if (!(typeof(data.datasets[i].data[k]) == 'undefined')) { + if(setOptionValue(true,1,"POINTDOT",ctx,data,statData,undefined,config.pointDot,"pointDot",i,k,{nullvalue : null} )) { + ctx.beginPath(); + ctx.fillStyle=setOptionValue(true,1,"MARKERFILLCOLOR",ctx,data,statData,data.datasets[i].pointColor,config.defaultStrokeColor,"pointColor",i,k,{nullvalue: true} ); + ctx.strokeStyle=setOptionValue(true,1,"MARKERSTROKESTYLE",ctx,data,statData,data.datasets[i].pointStrokeColor,config.defaultStrokeColor,"pointStrokeColor",i,k,{nullvalue: true} ); + ctx.lineWidth=setOptionValue(true,ctx.chartLineScale,"MARKERLINEWIDTH",ctx,data,statData,data.datasets[i].pointDotStrokeWidth,config.pointDotStrokeWidth,"pointDotStrokeWidth",i,k,{nullvalue: true} ); + var markerShape=setOptionValue(true,1,"MARKERSHAPE",ctx,data,statData,data.datasets[i].markerShape,config.markerShape,"markerShape",i,k,{nullvalue: true} ); + var markerRadius=setOptionValue(true,ctx.chartSpaceScale,"MARKERRADIUS",ctx,data,statData,data.datasets[i].pointDotRadius,config.pointDotRadius,"pointDotRadius",i,k,{nullvalue: true} ); + var markerStrokeStyle=setOptionValue(true,1,"MARKERSTROKESTYLE",ctx,data,statData,data.datasets[i].pointDotStrokeStyle,config.pointDotStrokeStyle,"pointDotStrokeStyle",i,k,{nullvalue: true} ); + drawMarker(ctx,midPosX + currentAnimPc * statData[i][k].offsetX, midPosY - currentAnimPc * statData[i][k].offsetY, markerShape,markerRadius,markerStrokeStyle); + } + } + } + } + } + ctx.restore(); + if (animationDecimal >= config.animationStopValue) { + for (i = 0; i < data.datasets.length; i++) { + for (j = 0; j < data.datasets[i].data.length; j++) { + if (typeof(data.datasets[i].data[j]) == 'undefined') continue; + jsGraphAnnotate[ctx.ChartNewId][jsGraphAnnotate[ctx.ChartNewId].length] = ["POINT", i,j,statData,setOptionValue(true,1,"ANNOTATEDISPLAY",ctx,data,statData,data.datasets[i].annotateDisplay,config.annotateDisplay,"annotateDisplay",i,j,{nullValue : true})]; + if(setOptionValue(true,1,"INGRAPHDATASHOW",ctx,data,statData,data.datasets[i].inGraphDataShow,config.inGraphDataShow,"inGraphDataShow",i,j,{nullValue : true})) { + ctx.save(); + ctx.beginPath(); + ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }); + ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ); + if (setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }) == "off-center") { + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) == "inRadiusAxis" || (config.startAngle * Math.PI / 180 - j * rotationDegree + 4 * Math.PI) % (2 * Math.PI) > 3 * Math.PI / 2 || (config.startAngle * Math.PI / 180 - j * rotationDegree + 4 * Math.PI) % (2 * Math.PI) <= Math.PI / 2) ctx.textAlign = "left"; + else ctx.textAlign = "right"; + } else if (setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }) == "to-center") { + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) == "inRadiusAxis" || (config.startAngle * Math.PI / 180 - j * rotationDegree + 4 * Math.PI) % (2 * Math.PI) > 3 * Math.PI / 2 || (config.startAngle * Math.PI / 180 - j * rotationDegree + 4 * Math.PI) % (2 * Math.PI) < Math.PI / 2) ctx.textAlign = "right"; + else ctx.textAlign = "left"; + } else ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }); + if (setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ) == "off-center") { + if ((config.startAngle * Math.PI / 180 - j * rotationDegree + 4 * Math.PI) % (2 * Math.PI) > Math.PI) ctx.textBaseline = "bottom"; + else ctx.textBaseline = "top"; + } else if (setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ) == "to-center") { + if ((config.startAngle * Math.PI / 180 - j * rotationDegree + 4 * Math.PI) % (2 * Math.PI) > Math.PI) ctx.textBaseline = "top"; + else ctx.textBaseline = "bottom"; + } else ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ); + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,j,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,j,{nullValue : true} ); + ctx.fillStyle = setOptionValue(true,1,"INGRAPHDATAFONTCOLOR",ctx,data,statData,undefined,config.inGraphDataFontColor,"inGraphDataFontColor",i,j,{nullValue : true} ); + var radiusPrt; + if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,j,{nullValue : true} ) == 1) radiusPrt = 0 + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,j,{nullValue: true} ); + else if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,j,{nullValue : true} ) == 2) radiusPrt = (statData[i][j].calculated_offset) / 2 + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,j,{nullValue: true} ); + else if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,j,{nullValue : true} ) == 3) radiusPrt = (statData[i][j].calculated_offset) + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,j,{nullValue: true} ); + var x_pos,y_pos; + if(statData[i][j].calculated_offset>0) { + x_pos=midPosX + statData[i][j].offsetX * (radiusPrt/statData[i][j].calculated_offset); + y_pos=midPosY - statData[i][j].offsetY * (radiusPrt/statData[i][j].calculated_offset); +// ctx.translate(midPosX + statData[i][j].offsetX * (radiusPrt/statData[i][j].calculated_offset), midPosY - statData[i][j].offsetY * (radiusPrt/statData[i][j].calculated_offset)); + } else { + x_pos=midPosX; + y_pos=midPosY; +// ctx.translate(midPosX, midPosY); + } + ctx.translate(x_pos,y_pos); + var rotateVal=0; + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) == "inRadiusAxis") rotateVal= j * rotationDegree; + else if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) == "inRadiusAxisRotateLabels") { + if ((j * rotationDegree + 2 * Math.PI) % (2 * Math.PI) > Math.PI / 2 && (j * rotationDegree + 2 * Math.PI) % (2 * Math.PI) < 3 * Math.PI / 2) rotateVal= 3 * Math.PI + j * rotationDegree; + else rotateVal = 2 * Math.PI + j * rotationDegree; + } else rotateVal=setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) * (Math.PI / 180); + ctx.rotate(rotateVal); + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,j,{nullValue : true} ), statData[i][j],config); + setTextBordersAndBackground(ctx,dispString,setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),0,0,setOptionValue(true,1,"INGRAPHDATABORDERS",ctx,data,statData,undefined,config.inGraphDataBorders,"inGraphDataBorders",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSCOLOR",ctx,data,statData,undefined,config.inGraphDataBordersColor,"inGraphDataBordersColor",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartLineScale,"INGRAPHDATABORDERSWIDTH",ctx,data,statData,undefined,config.inGraphDataBordersWidth,"inGraphDataBordersWidth",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSXSPACE",ctx,data,statData,undefined,config.inGraphDataBordersXSpace,"inGraphDataBordersXSpace",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSYSPACE",ctx,data,statData,undefined,config.inGraphDataBordersYSpace,"inGraphDataBordersYSpace",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSSTYLE",ctx,data,statData,undefined,config.inGraphDataBordersStyle,"inGraphDataBordersStyle",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABACKGROUNDCOLOR",ctx,data,statData,undefined,config.inGraphDataBackgroundColor,"inGraphDataBackgroundColor",i,j,{nullValue : true} ),"INGRAPHDATA"); + ctx.fillTextMultiLine(dispString, 0, 0, ctx.textBaseline, setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),true,config.detectMouseOnText,ctx,"INGRAPHDATA_TEXTMOUSE",rotateVal,x_pos,y_pos,i,j); + ctx.restore(); + } + } + } + } + if(msr.legendMsr.dispLegend)drawLegend(msr.legendMsr,data,config,ctx,"Radar"); + }; + + function drawScale() { + var rotationDegree = (2 * Math.PI) / data.datasets[0].data.length; + ctx.save(); + ctx.translate(midPosX, midPosY); + ctx.rotate((90 - config.startAngle) * Math.PI / 180); + if (config.angleShowLineOut) { + ctx.strokeStyle = config.angleLineColor; + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.angleLineWidth); + for (var h = 0; h < data.datasets[0].data.length; h++) { + ctx.rotate(rotationDegree); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, -maxSize); +// ctx.setLineDash(lineStyleFn(config.angleLineStyle)); + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"ANGLELINESTYLE",ctx,data,statData,undefined,config.angleLineStyle,"angleLineStyle",h,-1,{nullValue : true} ))); + ctx.stroke(); + ctx.setLineDash([]); + } + } + for (var i = 0; i < calculatedScale.steps; i++) { + ctx.beginPath(); + if (config.scaleShowLine && (i+1) % config.scaleGridLinesStep == 0 ) { + ctx.strokeStyle = config.scaleLineColor; + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.moveTo(0, -scaleHop * (i + 1)); + for (var j = 0; j < data.datasets[0].data.length; j++) { + ctx.rotate(rotationDegree); + ctx.lineTo(0, -scaleHop * (i + 1)); + } + ctx.closePath(); + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.stroke(); + ctx.setLineDash([]); + } + } + ctx.rotate(-(90 - config.startAngle) * Math.PI / 180); + if (config.scaleShowLabels) { + for (i = 0; i < calculatedScale.steps; i++) { + ctx.textAlign = 'center'; + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + ctx.textBaseline = "middle"; + if (config.scaleShowLabelBackdrop) { + var textWidth = ctx.measureTextMultiLine(calculatedScale.labels[i + 1], (Math.ceil(ctx.chartTextScale*config.scaleFontSize))).textWidth; + ctx.fillStyle = config.scaleBackdropColor; + ctx.beginPath(); + ctx.rect( + Math.round(Math.cos(config.startAngle * Math.PI / 180) * (scaleHop * (i + 1)) - textWidth / 2 - Math.ceil(ctx.chartSpaceScale*config.scaleBackdropPaddingX)), //X + Math.round((-Math.sin(config.startAngle * Math.PI / 180) * scaleHop * (i + 1)) - (Math.ceil(ctx.chartTextScale*config.scaleFontSize)) * 0.5 - Math.ceil(ctx.chartSpaceScale*config.scaleBackdropPaddingY)), //Y + Math.round(textWidth + (Math.ceil(ctx.chartSpaceScale*config.scaleBackdropPaddingX) * 2)), //Width + Math.round((Math.ceil(ctx.chartTextScale*config.scaleFontSize)) + (Math.ceil(ctx.chartSpaceScale*config.scaleBackdropPaddingY) * 2)) //Height + ); + ctx.fill(); + } + ctx.fillStyle = config.scaleFontColor; + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], Math.cos(config.startAngle * Math.PI / 180) * (scaleHop * (i + 1)), -Math.sin(config.startAngle * Math.PI / 180) * scaleHop * (i + 1), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"SCALE_TEXTMOUSE",0,midPosX, midPosY,i,-1); + } + } + for (var k = 0; k < data.labels.length; k++) { + ctx.font = config.pointLabelFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.pointLabelFontSize)).toString() + "px " + config.pointLabelFontFamily; + ctx.fillStyle = config.pointLabelFontColor; + var opposite = Math.sin((90 - config.startAngle) * Math.PI / 180 + rotationDegree * k) * (maxSize + (Math.ceil(ctx.chartTextScale*config.pointLabelFontSize))); + var adjacent = Math.cos((90 - config.startAngle) * Math.PI / 180 + rotationDegree * k) * (maxSize + (Math.ceil(ctx.chartTextScale*config.pointLabelFontSize))); + var vangle = (90 - config.startAngle) * Math.PI / 180 + rotationDegree * k; + while (vangle < 0) vangle = vangle + 2 * Math.PI; + while (vangle > 2 * Math.PI) vangle = vangle - 2 * Math.PI; + if (vangle == Math.PI || vangle == 0) { + ctx.textAlign = "center"; + } else if (vangle > Math.PI) { + ctx.textAlign = "right"; + } else { + ctx.textAlign = "left"; + } + ctx.textBaseline = "middle"; + ctx.fillTextMultiLine(data.labels[k], opposite, -adjacent, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.pointLabelFontSize)),true,config.detectMouseOnText,ctx,"LABEL_TEXTMOUSE",0,midPosX, midPosY,k,-1); + } + ctx.restore(); + }; + + function calculateDrawingSizes() { + var midX, mxlb, maxL, maxR, iter, nbiter, prevMaxSize, prevMidX,i,textMeasurement; + var rotationDegree = (2 * Math.PI) / data.datasets[0].data.length; + var rotateAngle = config.startAngle * Math.PI / 180; + // Compute range for Mid Point of graph + ctx.font = config.pointLabelFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.pointLabelFontSize)).toString() + "px " + config.pointLabelFontFamily; + if (!config.graphMaximized) { + maxR = msr.availableWidth / 2; + maxL = msr.availableWidth / 2; + nbiter = 1; + } else { + maxR = msr.availableWidth / 2; + maxL = msr.availableWidth / 2; + nbiter = 40; + for (i = 0; i < data.labels.length; i++) { + textMeasurement = ctx.measureTextMultiLine(data.labels[i], (Math.ceil(ctx.chartTextScale*config.scaleFontSize))).textWidth + ctx.measureTextMultiLine(data.labels[i], (Math.ceil(ctx.chartTextScale*config.scaleFontSize))).textHeight; + mxlb = (msr.availableWidth - textMeasurement) / (1 + Math.abs(Math.cos(rotateAngle))); + if ((rotateAngle < Math.PI / 2 && rotateAngle > -Math.PI / 2) || rotateAngle > 3 * Math.PI / 2) { + if (mxlb < maxR) maxR = mxlb; + } else if (Math.cos(rotateAngle) != 0) { + if (mxlb < maxL) maxL = mxlb; + } + rotateAngle -= rotationDegree; + } + } + // compute max Radius and midPoint in that range + prevMaxSize = 0; + prevMidX = 0; + midPosX = maxR + msr.leftNotUsableSize; + for (midX = maxR, iter = 0; iter < nbiter; ++iter, midX += (msr.availableWidth - maxL - maxR) / nbiter) { + maxSize = Max([midX, msr.availableWidth - midX]); + rotateAngle = config.startAngle * Math.PI / 180; + mxlb = msr.available; + for (i = 0; i < data.labels.length; i++) { + textMeasurement = ctx.measureTextMultiLine(data.labels[i], (Math.ceil(ctx.chartTextScale*config.scaleFontSize))).textWidth + ctx.measureTextMultiLine(data.labels[i], (Math.ceil(ctx.chartTextScale*config.scaleFontSize))).textHeight; + if ((rotateAngle < Math.PI / 2 && rotateAngle > -Math.PI / 2) || rotateAngle > 3 * Math.PI / 2) { + mxlb = ((msr.availableWidth - midX) - textMeasurement) / Math.abs(Math.cos(rotateAngle)); + } else if (Math.cos(rotateAngle != 0)) { + mxlb = (midX - textMeasurement) / Math.abs(Math.cos(rotateAngle)); + } + if (mxlb < maxSize) maxSize = mxlb; + if (Math.sin(rotateAngle) * msr.availableHeight / 2 > msr.availableHeight / 2 - (Math.ceil(ctx.chartTextScale*config.scaleFontSize)) * 2) { + mxlb = Math.sin(rotateAngle) * msr.availableHeight / 2 - 1.5 * (Math.ceil(ctx.chartTextScale*config.scaleFontSize)); + if (mxlb < maxSize) maxSize = mxlb; + } + rotateAngle -= rotationDegree; + } + if (maxSize > prevMaxSize) { + prevMaxSize = maxSize; + midPosX = midX + msr.leftNotUsableSize; + } + } + maxSize = prevMaxSize - (Math.ceil(ctx.chartTextScale*config.scaleFontSize)) / 2; + //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. + labelHeight = Default(labelHeight, 5); + }; + + function getValueBounds() { + var upperValue = -Number.MAX_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + if(typeof data.datasets[i].data[j]=="undefined")continue; + if (1 * data.datasets[i].data[j] > upperValue) { + upperValue = 1 * data.datasets[i].data[j] + } + if (1 * data.datasets[i].data[j] < lowerValue) { + lowerValue = 1 * data.datasets[i].data[j] + } + } + } + if(upperValue0) { + upperValue=upperValue*1.1; + lowerValue=lowerValue*0.9; + } else { + upperValue=upperValue*0.9; + lowerValue=lowerValue*1.1; + } + } + if(typeof config.graphMin=="function") lowerValue= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin,"graphMin",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if(typeof config.graphMax=="function") upperValue= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax,"graphMax",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax)) upperValue = config.graphMax; + var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight * 0.5)); + if(upperValue 0) { + initPassVariableData_part2(statData,data,config,ctx,{midPosX : midPieX,midPosY : midPieY ,int_radius : cutoutRadius ,ext_radius : doughnutRadius,outerVal : -1}); + animationLoop(config, null, drawPieSegments, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, midPieX, midPieY, midPieX - doughnutRadius, midPieY + doughnutRadius, data, statData); + } else { + testRedraw(ctx,data,config); + ctx.firstPass=9; + } + + + function drawPieSegments(animationDecimal) { + var cumulativeAngle = (((-config.startAngle * (Math.PI / 180) + 2 * Math.PI) % (2 * Math.PI)) + 2* Math.PI) % (2* Math.PI) ; + + var dataCutoutRadius, dataDoughnutRadius; + + var prevAngle=statData[0].firstAngle; + + var fixAngle=0; + var firstAngle=statData[0].firstAngle; + if(1*config.animationStartWithData>1 && 1*config.animationStartWithData-1 < data.length) { + fixAngle=(statData[config.animationStartWithData-1].startAngle-statData[0].firstAngle); + firstAngle=statData[config.animationStartWithData-1].startAngle; + } + + + for (var i = 0; i < data.length; i++) { + var scaleAnimation = 1, + rotateAnimation = 1; + if (ctx.tpchart=="Pie")dataCutoutRadius=cutoutRadius; + else dataCutoutRadius=cutoutRadius-(doughnutRadius-cutoutRadius)*setOptionValue(true,1,"EXPANDINRADIUS",ctx,data,statData,data[i].expandInRadius,0,"expandInRadius",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ); + dataDoughnutRadius=doughnutRadius+(doughnutRadius-cutoutRadius)*setOptionValue(true,1,"EXPANDOUTRADIUS",ctx,data,statData,data[i].expandOutRadius,0,"expandOutRadius",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ); + if (config.animation) { + if (config.animateScale) { + scaleAnimation = animationDecimal; + } + if (config.animateRotate) { + rotateAnimation = animationDecimal; + } + } + + correctedRotateAnimation = animationCorrection(rotateAnimation, data, config, i, -1, false).mainVal; + if (!(typeof(data[i].value) == 'undefined') && 1*data[i].value >=0) { + ctx.beginPath(); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.segmentStrokeWidth); + ctx.strokeStyle = "rgba(0,0,0,0)"; + if (config.animationByData == "ByArc") { + endAngle=statData[i].startAngle+correctedRotateAnimation*statData[i].segmentAngle; + ctx.arc(midPieX, midPieY, scaleAnimation * dataDoughnutRadius, statData[i].startAngle, endAngle,false); + ctx.arc(midPieX, midPieY, scaleAnimation * dataCutoutRadius, endAngle,statData[i].startAngle, true); + } else if(config.animationByData) { + if (i<1*config.animationStartWithData-1) { + ctx.arc(midPieX, midPieY, scaleAnimation * dataDoughnutRadius, statData[i].startAngle, statData[i].endAngle,false); + ctx.arc(midPieX, midPieY, scaleAnimation * dataCutoutRadius, statData[i].endAngle,statData[i].startAngle, true); + } else if(statData[i].startAngle <= firstAngle+correctedRotateAnimation*(2*Math.PI-fixAngle) ) { + endAngle=statData[i].endAngle; + if(statData[i].endAngle > firstAngle+correctedRotateAnimation*(2*Math.PI-fixAngle)) { + endAngle=firstAngle+correctedRotateAnimation*(2*Math.PI-fixAngle); + } + ctx.arc(midPieX, midPieY, scaleAnimation * dataDoughnutRadius, statData[i].startAngle, endAngle,false); + ctx.arc(midPieX, midPieY, scaleAnimation * dataCutoutRadius, endAngle,statData[i].startAngle, true); + + } else { + continue; + } + } else { + ctx.arc(midPieX, midPieY, scaleAnimation * dataDoughnutRadius, prevAngle, prevAngle+correctedRotateAnimation * (statData[i].endAngle-statData[i].startAngle),false); + ctx.arc(midPieX, midPieY, scaleAnimation * dataCutoutRadius, prevAngle+correctedRotateAnimation * (statData[i].endAngle-statData[i].startAngle), prevAngle, true); + prevAngle=prevAngle+correctedRotateAnimation * (statData[i].endAngle-statData[i].startAngle); + } + ctx.closePath(); + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data[i].color,config.defaultFillColor,"color",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ); + ctx.fill(); + if(config.segmentShowStroke=="merge") { /* avoid blank stripes between piece of chart */ + ctx.lineWidth =1.5; + ctx.strokeStyle =setOptionValue(true,1,"COLOR",ctx,data,statData,data[i].color,config.defaultFillColor,"color",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ); + ctx.setLineDash([]); + ctx.stroke(); + } + else if (config.segmentShowStroke) { + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.segmentStrokeWidth); + ctx.strokeStyle = config.segmentStrokeColor; + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"SEGMENTSTROKESTYLE",ctx,data,statData,data[i].segmentStrokeStyle,config.segmentStrokeStyle,"segmentStrokeStyle",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ))); + ctx.stroke(); + ctx.setLineDash([]); + } + } + } + if (animationDecimal >= config.animationStopValue) { + for (i = 0; i < data.length; i++) { + if (ctx.tpchart=="Pie")dataCutOutRadius=cutoutRadius; + else dataCutoutRadius=cutoutRadius-(doughnutRadius-cutoutRadius)*setOptionValue(true,1,"EXPANDINRADIUS",ctx,data,statData,data[i].expandInRadius,0,"expandInRadius",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ); + dataDoughnutRadius=doughnutRadius+(doughnutRadius-cutoutRadius)*setOptionValue(true,1,"EXPANDOUTRADIUS",ctx,data,statData,data[i].expandOutRadius,0,"expandOutRadius",i,-1,{animationDecimal: animationDecimal, scaleAnimation : scaleAnimation} ); + if (typeof(data[i].value) == 'undefined' || 1*data[i].value<0) continue; + jsGraphAnnotate[ctx.ChartNewId][jsGraphAnnotate[ctx.ChartNewId].length] = ["ARC", i,-1,statData,setOptionValue(true,1,"ANNOTATEDISPLAY",ctx,data,statData,data[i].annotateDisplay,config.annotateDisplay,"annotateDisplay",i,-1,{nullValue : true})]; + if (setOptionValue(true,1,"INGRAPHDATASHOW",ctx,data,statData,data[i].inGraphDataShow,config.inGraphDataShow,"inGraphDataShow",i,-1,{nullValue : true}) && statData[i].segmentAngle >= (Math.PI/180) * setOptionValue(true,1,"INGRAPHDATAMINIMUMANGLE",ctx,data,statData,undefined,config.inGraphDataMinimumAngle,"inGraphDataMinimumAngle",i,-1,{nullValue : true} )) { + if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 1) posAngle = statData[i].realStartAngle + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + else if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 2) posAngle = statData[i].realStartAngle- statData[i].segmentAngle / 2 + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + else if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 3) posAngle = statData[i].realStartAngle - statData[i].segmentAngle + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,-1,{nullValue : true} ) == 1) labelRadius = dataCutoutRadius + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,-1,{nullValue: true} ); + else if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,-1,{nullValue : true} ) == 2) labelRadius = dataCutoutRadius + (dataDoughnutRadius - dataCutoutRadius) / 2 + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,-1,{nullValue: true} ); + else if (setOptionValue(true,1,"INGRAPHDATARADIUSPOSITION",ctx,data,statData,undefined,config.inGraphDataRadiusPosition,"inGraphDataRadiusPosition",i,-1,{nullValue : true} ) == 3) labelRadius = dataDoughnutRadius + setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,-1,{nullValue: true} ); + ctx.save(); + if (setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,-1,{nullValue: true }) == "off-center") { + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) == "inRadiusAxis" || (posAngle + 2 * Math.PI) % (2 * Math.PI) >= 3 * Math.PI / 2 || (posAngle + 2 * Math.PI) % (2 * Math.PI) <= Math.PI / 2) ctx.textAlign = "left"; + else ctx.textAlign = "right"; + } else if (setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,-1,{nullValue: true }) == "to-center") { + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) == "inRadiusAxis" || (posAngle + 2 * Math.PI) % (2 * Math.PI) >= 3 * Math.PI / 2 || (posAngle + 2 * Math.PI) % (2 * Math.PI) <= Math.PI / 2) ctx.textAlign = "right"; + else ctx.textAlign = "left"; + } else ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,-1,{nullValue: true }); + if (setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,-1,{nullValue : true} ) == "off-center") { + if ((posAngle + 2 * Math.PI) % (2 * Math.PI) > Math.PI) ctx.textBaseline = "top"; + else ctx.textBaseline = "bottom"; + } else if (setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,-1,{nullValue : true} ) == "to-center") { + if ((posAngle + 2 * Math.PI) % (2 * Math.PI) > Math.PI) ctx.textBaseline = "bottom"; + else ctx.textBaseline = "top"; + } else ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,-1,{nullValue : true} ); + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,-1,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,-1,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,-1,{nullValue : true} ); + ctx.fillStyle = setOptionValue(true,1,"INGRAPHDATAFONTCOLOR",ctx,data,statData,undefined,config.inGraphDataFontColor,"inGraphDataFontColor",i,-1,{nullValue : true} ); + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,-1,{nullValue : true} ), statData[i],config); + ctx.translate(midPieX + labelRadius * Math.cos(posAngle), midPieY - labelRadius * Math.sin(posAngle)); + var rotateVal=0; + if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) == "inRadiusAxis") rotateVal=2 * Math.PI - posAngle; + else if (setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) == "inRadiusAxisRotateLabels") { + if ((posAngle + 2 * Math.PI) % (2 * Math.PI) > Math.PI / 2 && (posAngle + 2 * Math.PI) % (2 * Math.PI) < 3 * Math.PI / 2) rotateVal=3 * Math.PI - posAngle; + else rotateVal=2 * Math.PI - posAngle; + } else rotateVal=setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,-1,{nullValue : true} ) * (Math.PI / 180); + ctx.rotate(rotateVal); + setTextBordersAndBackground(ctx,dispString,setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,-1,{nullValue : true} ),0,0,setOptionValue(true,1,"INGRAPHDATABORDERS",ctx,data,statData,undefined,config.inGraphDataBorders,"inGraphDataBorders",i,-1,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSCOLOR",ctx,data,statData,undefined,config.inGraphDataBordersColor,"inGraphDataBordersColor",i,-1,{nullValue : true} ),setOptionValue(true,ctx.chartLineScale,"INGRAPHDATABORDERSWIDTH",ctx,data,statData,undefined,config.inGraphDataBordersWidth,"inGraphDataBordersWidth",i,-1,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSXSPACE",ctx,data,statData,undefined,config.inGraphDataBordersXSpace,"inGraphDataBordersXSpace",i,-1,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSYSPACE",ctx,data,statData,undefined,config.inGraphDataBordersYSpace,"inGraphDataBordersYSpace",i,-1,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSSTYLE",ctx,data,statData,undefined,config.inGraphDataBordersStyle,"inGraphDataBordersStyle",i,-1,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABACKGROUNDCOLOR",ctx,data,statData,undefined,config.inGraphDataBackgroundColor,"inGraphDataBackgroundColor",i,-1,{nullValue : true} ),"INGRAPHDATA"); + ctx.fillTextMultiLine(dispString, 0, 0, ctx.textBaseline, setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,-1,{nullValue : true} ),true,config.detectMouseOnText,ctx,"INGRAPHDATA_TEXTMOUSE",rotateVal,midPieX + labelRadius * Math.cos(posAngle), midPieY - labelRadius * Math.sin(posAngle),i,-1); + ctx.restore(); + } + } + } + if(msr.legendMsr.dispLegend)drawLegend(msr.legendMsr,data,config,ctx,"Doughnut"); + }; + + return { + data:data, + config:config, + ctx:ctx + }; + }; + var Line = function(data, config, ctx) { + var maxSize, scaleHop, scaleHop2, calculatedScale, calculatedScale2, labelHeight, scaleHeight, valueBounds, labelTemplateString, labelTemplateString2; + var valueHop, widestXLabel, xAxisLength, yAxisPosX, xAxisPosY, rotateLabels = 0, + msr; + var zeroY = 0; + var zeroY2 = 0; + ctx.tpchart="Line"; + ctx.tpdata=0; + + if (!init_and_start(ctx,data,config)) return; + // adapt data when length is 1; + var mxlgt = 0; + for (var i = 0; i < data.datasets.length; i++) {mxlgt = Max([mxlgt, data.datasets[i].data.length]);} + if (mxlgt == 1) { + if (typeof(data.labels[0]) == "string") data.labels = ["", data.labels[0], ""]; + for (i = 0; i < data.datasets.length; i++) { + if (typeof(data.datasets[i].data[0] != "undefined")) data.datasets[i].data = [undefined, data.datasets[i].data[0], undefined]; + } + mxlgt=3; + } + var statData=initPassVariableData_part1(data,config,ctx); + for (i = 0; i < data.datasets.length; i++) statData[i][0].tpchart="Line"; + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, "nihil", [""], false, false, true, true, config.datasetFill, "Line"); + valueBounds = getValueBounds(); + // true or fuzzy (error for negativ values (included 0)) + if (config.logarithmic !== false) { + if (valueBounds.minValue <= 0) { + config.logarithmic = false; + } + } + if (config.logarithmic2 !== false) { + if (valueBounds.minValue2 <= 0) { + config.logarithmic2 = false; + } + } + // Check if logarithmic is meanigful + var OrderOfMagnitude = calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.maxValue) + 1)) - calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.minValue))); + if ((config.logarithmic == 'fuzzy' && OrderOfMagnitude < 4) || config.scaleOverride) { + config.logarithmic = false; + } + // Check if logarithmic is meanigful + var OrderOfMagnitude2 = calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.maxValue2) + 1)) - calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.minValue2))); + if ((config.logarithmic2 == 'fuzzy' && OrderOfMagnitude2 < 4) || config.scaleOverride2) { + config.logarithmic2 = false; + } + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + labelTemplateString2 = (config.scaleShowLabels2) ? config.scaleLabel2 : ""; + if (!config.scaleOverride) { + if(valueBounds.maxSteps>0 && valueBounds.minSteps>0) { + calculatedScale = calculateScale(1, config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + } + } else { + var scaleStartValue= setOptionValue(true,1,"SCALESTARTVALUE",ctx,data,statData,undefined,config.scaleStartValue,"scaleStartValue",-1,-1,{nullValue : true} ); + var scaleSteps =setOptionValue(true,1,"SCALESTEPS",ctx,data,statData,undefined,config.scaleSteps,"scaleSteps",-1,-1,{nullValue : true} ); + var scaleStepWidth = setOptionValue(true,1,"SCALESTEPWIDTH",ctx,data,statData,undefined,config.scaleStepWidth,"scaleStepWidth",-1,-1,{nullValue : true} ); + calculatedScale = { + steps: scaleSteps, + stepValue: scaleStepWidth, + graphMin: scaleStartValue, + graphMax: scaleStartValue + scaleSteps * scaleStepWidth, + labels: [] + } + populateLabels(1, config, labelTemplateString, calculatedScale.labels, calculatedScale.steps, scaleStartValue, calculatedScale.graphMax, scaleStepWidth); + } + + if (valueBounds.dbAxis) { + if (!config.scaleOverride2) { + if(valueBounds.maxSteps>0 && valueBounds.minSteps>0) { + calculatedScale2 = calculateScale(2, config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue2, valueBounds.minValue2, labelTemplateString2); + } + } else { + var scaleStartValue2= setOptionValue(true,1,"SCALESTARTVALUE2",ctx,data,statData,undefined,config.scaleStartValue2,"scaleStartValue2",-1,-1,{nullValue : true} ); + var scaleSteps2 =setOptionValue(true,1,"SCALESTEPS2",ctx,data,statData,undefined,config.scaleSteps2,"scaleSteps2",-1,-1,{nullValue : true} ); + var scaleStepWidth2 = setOptionValue(true,1,"SCALESTEPWIDTH2",ctx,data,statData,undefined,config.scaleStepWidth2,"scaleStepWidth2",-1,-1,{nullValue : true} ); + + calculatedScale2 = { + steps: scaleSteps2, + stepValue: scaleStepWidth2, + graphMin: scaleStartValue2, + graphMax: scaleStartValue2 + scaleSteps2 * scaleStepWidth2, + labels: [] + } + populateLabels(2, config, labelTemplateString2, calculatedScale2.labels, calculatedScale2.steps, scaleStartValue2, calculatedScale2.graphMax, scaleStepWidth2); + } + } else { + calculatedScale2 = { + steps: 0, + stepValue: 0, + graphMin: 0, + graphMax: 0, + labels: null + } + } + if(valueBounds.maxSteps>0 && valueBounds.minSteps>0) { + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, calculatedScale.labels, calculatedScale2.labels, false, false, true, true, config.datasetFill, "Line"); + var prevHeight=msr.availableHeight; + msr.availableHeight = msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + msr.availableWidth = msr.availableWidth - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight); + scaleHop = Math.floor(msr.availableHeight / calculatedScale.steps); + scaleHop2 = Math.floor(msr.availableHeight / calculatedScale2.steps); + valueHop = Math.floor(msr.availableWidth / (data.labels.length - 1)); + if (valueHop == 0 || config.fullWidthGraph) valueHop = (msr.availableWidth / (data.labels.length - 1)); + msr.clrwidth = msr.clrwidth - (msr.availableWidth - (data.labels.length - 1) * valueHop); + msr.availableWidth = (data.labels.length - 1) * valueHop; + msr.availableHeight = (calculatedScale.steps) * scaleHop; + msr.xLabelPos+=(Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop) - (prevHeight-msr.availableHeight)); + msr.clrheight+=(Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop) - (prevHeight-msr.availableHeight)); + yAxisPosX = msr.leftNotUsableSize + Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft); + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + drawLabels(); + + zeroY = calculateOffset(config.logarithmic, 0, calculatedScale, scaleHop); + if(typeof calculatedScale2 ==="object")zeroY2 = calculateOffset(config.logarithmic2, 0, calculatedScale2, scaleHop2); + initPassVariableData_part2(statData,data,config,ctx,{ + xAxisPosY : xAxisPosY, + yAxisPosX : yAxisPosX, + valueHop : valueHop, + nbValueHop : data.labels.length - 1, + scaleHop : scaleHop, + zeroY : zeroY, + calculatedScale : calculatedScale, + logarithmic : config.logarithmic, + scaleHop2: scaleHop2, + zeroY2: zeroY2, + msr : msr, + calculatedScale2: calculatedScale2, + logarithmic2: config.logarithmic2} ); + animationLoop(config, drawScale, drawLines, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data, statData); + } else { + testRedraw(ctx,data,config); + ctx.firstPass=9; + } + + + function drawLines(animPc) { + + drawLinesDataset(animPc, data, config, ctx, statData,{xAxisPosY : xAxisPosY,yAxisPosX : yAxisPosX, valueHop : valueHop, nbValueHop : data.labels.length - 1 }); + if (animPc >= 1) { + if (typeof drawMath == "function") { + drawMath(ctx, config, data, msr, { + xAxisPosY: xAxisPosY, + yAxisPosX: yAxisPosX, + valueHop: valueHop, + scaleHop: scaleHop, + zeroY: zeroY, + calculatedScale: calculatedScale, + calculateOffset: calculateOffset, + statData : statData + + }); + } + } + if(msr.legendMsr.dispLegend)drawLegend(msr.legendMsr,data,config,ctx,"Line"); + }; + + function drawScale() { + //X axis line + // if the xScale should be drawn + if (config.drawXScaleLine !== false) { + for (var s = 0; s < config.drawXScaleLine.length; s++) { + // get special lineWidth and lineColor for this xScaleLine + ctx.lineWidth = config.drawXScaleLine[s].lineWidth ? config.drawXScaleLine[s].lineWidth : Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.drawXScaleLine[s].lineColor ? config.drawXScaleLine[s].lineColor : config.scaleLineColor; + ctx.beginPath(); + var yPosXScale; + switch (config.drawXScaleLine[s].position) { + case "bottom": + yPosXScale = xAxisPosY; + break; + case "top": + yPosXScale = xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + break; + case "0": + case 0: + // check if zero exists + if (zeroY != 0) yPosXScale = xAxisPosY - zeroY; + break; + } + // draw the scale line + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), yPosXScale); + ctx.lineTo(yAxisPosX + msr.availableWidth + Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight), yPosXScale); + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.stroke(); + ctx.setLineDash([]); + + } + } + for (var i = 0; i < data.labels.length; i++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i > 0 && i % config.scaleXGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + } else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + ctx.stroke(); + ctx.setLineDash([]); + + } + //Y axis + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.stroke(); + ctx.setLineDash([]); + for (var j = 0; j < calculatedScale.steps; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && (j+1) % config.scaleYGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + msr.availableWidth + Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight), xAxisPosY - ((j + 1) * scaleHop)); + } else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + ctx.stroke(); + ctx.setLineDash([]); + } + }; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + //X Labels + if (config.xAxisTop || config.xAxisBottom) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + if (config.xAxisBottom) { + for (var i = 0; i < data.labels.length; i++) { + if(showLabels(ctx,data,config,i)){ + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + i * valueHop - msr.highestXLabel / 2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[i], config.fmtXLabel), 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XSCALE_TEXTMOUSE",-(msr.rotateLabels * (Math.PI / 180)),yAxisPosX + i * valueHop - msr.highestXLabel / 2, msr.xLabelPos,i,-1); + } else { + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[i], config.fmtXLabel), yAxisPosX + i * valueHop, msr.xLabelPos, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XSCALE_TEXTMOUSE",0,0,0,i,-1); + } + ctx.restore(); + } + } + } + } + //Y Labels + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = ((config.showYAxisMin) ? -1 : 0); j < calculatedScale.steps; j++) { + if (config.scaleShowLabels) { + if(showYLabels(ctx,data,config,j+1,calculatedScale.labels[j + 1])) { + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX - (Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YLEFTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + if (config.yAxisRight && !valueBounds.dbAxis) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX + msr.availableWidth + (Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YRIGHTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + } + } + } + if (config.yAxisRight && valueBounds.dbAxis) { + for (j = ((config.showYAxisMin) ? -1 : 0); j < calculatedScale2.steps; j++) { + if (config.scaleShowLabels) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(calculatedScale2.labels[j + 1], yAxisPosX + msr.availableWidth + (Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop2), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YRIGHTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + } + } + }; + + function getValueBounds() { + var upperValue = -Number.MAX_VALUE; + var lowerValue = Number.MAX_VALUE; + var upperValue2 = -Number.MAX_VALUE; + var lowerValue2 = Number.MAX_VALUE; + var secondAxis = false; + var firstAxis = false; + var mathValueHeight; + for (var i = 0; i < data.datasets.length; i++) { + var mathFctName = data.datasets[i].drawMathDeviation; + var mathValueHeight = 0; + if (typeof eval(mathFctName) == "function") { + var parameter = { + data: data, + datasetNr: i + }; + mathValueHeightVal = window[mathFctName](parameter); + } else mathValueHeightVal=0; + for (var j = 0; j < data.datasets[i].data.length; j++) { + if(typeof mathValueHeightVal=="object") mathValueHeight=mathValueHeightVal[Math.min(mathValueHeightVal.length,j)]; + else mathValueHeight=mathValueHeightVal; + + if(typeof data.datasets[i].data[j] == "undefined") continue; + if (data.datasets[i].axis == 2) { + secondAxis = true; + if (1 * data.datasets[i].data[j] + mathValueHeight > upperValue2) { + upperValue2 = 1 * data.datasets[i].data[j] + mathValueHeight + }; + if (1 * data.datasets[i].data[j] - mathValueHeight < lowerValue2) { + lowerValue2 = 1 * data.datasets[i].data[j] - mathValueHeight + }; + } else { + firstAxis = true; + if (1 * data.datasets[i].data[j] + mathValueHeight > upperValue) { + upperValue = 1 * data.datasets[i].data[j] + mathValueHeight + }; + if (1 * data.datasets[i].data[j] - mathValueHeight < lowerValue) { + lowerValue = 1 * data.datasets[i].data[j] - mathValueHeight + }; + } + } + }; + if(upperValue0) { + upperValue=upperValue*1.1; + lowerValue=lowerValue*0.9; + } else { + upperValue=upperValue*0.9; + lowerValue=lowerValue*1.1; + } + + } + if(typeof config.graphMin=="function")lowerValue= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin,"graphMin",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if(typeof config.graphMax=="function") upperValue= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax,"graphMax",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax)) upperValue = config.graphMax; + if (secondAxis) { + if(upperValue20) { + upperValue2=upperValue2*1.1; + lowerValue2=lowerValue2*0.9; + } else { + upperValue2=upperValue2*0.9; + lowerValue2=lowerValue2*1.1; + } + } + if(typeof config.graphMin2=="function")lowerValue2= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin2,"graphMin2",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin2)) lowerValue2 = config.graphMin2; + if(typeof config.graphMax2=="function") upperValue2= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax2,"graphMax2",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax2)) upperValue2 = config.graphMax2; + } + if (!firstAxis && secondAxis) { + upperValue = upperValue2; + lowerValue = lowerValue2; + } + labelHeight = (Math.ceil(ctx.chartTextScale*config.scaleFontSize)); + scaleHeight = msr.availableHeight; + var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight * 0.5)); + if(upperValue0 && valueBounds.minSteps>0) { + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + if (!config.scaleOverride) { + calculatedScale = calculateScale(1, config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, calculatedScale.labels, null, true, false, true, true, true, "StackedBar"); + } else { + var scaleStartValue= setOptionValue(true,1,"SCALESTARTVALUE",ctx,data,statData,undefined,config.scaleStartValue,"scaleStartValue",-1,-1,{nullValue : true} ); + var scaleSteps =setOptionValue(true,1,"SCALESTEPS",ctx,data,statData,undefined,config.scaleSteps,"scaleSteps",-1,-1,{nullValue : true} ); + var scaleStepWidth = setOptionValue(true,1,"SCALESTEPWIDTH",ctx,data,statData,undefined,config.scaleStepWidth,"scaleStepWidth",-1,-1,{nullValue : true} ); + + calculatedScale = { + steps: scaleSteps, + stepValue: scaleStepWidth, + graphMin: scaleStartValue, + labels: [] + } + + for (var i = 0; i <= calculatedScale.steps; i++) { + if (labelTemplateString) { + calculatedScale.labels.push(tmpl(labelTemplateString, { + value: fmtChartJS(config, 1 * ((scaleStartValue + (scaleStepWidth * i)).toFixed(getDecimalPlaces(scaleStepWidth))), config.fmtYLabel) + },config)); + } + } + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, calculatedScale.labels, null, true, false, true, true, true, "StackedBar"); + } + var prevHeight=msr.availableHeight; + + msr.availableHeight = msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + msr.availableWidth = msr.availableWidth - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight); + scaleHop = Math.floor(msr.availableHeight / calculatedScale.steps); + valueHop = Math.floor(msr.availableWidth / (data.labels.length)); + if (valueHop == 0 || config.fullWidthGraph) valueHop = (msr.availableWidth / data.labels.length); + msr.clrwidth = msr.clrwidth - (msr.availableWidth - ((data.labels.length) * valueHop)); + msr.availableWidth = (data.labels.length) * valueHop; + + msr.availableHeight = (calculatedScale.steps) * scaleHop; + msr.xLabelPos+=(Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop) - (prevHeight-msr.availableHeight)); + msr.clrheight+=(Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop) - (prevHeight-msr.availableHeight)); + + yAxisPosX = msr.leftNotUsableSize + Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft); + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + barWidth = (valueHop - Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth) * 2 - (Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) * 2) - (Math.ceil(ctx.chartSpaceScale*config.barDatasetSpacing) * 1 - 1) - (Math.ceil(ctx.chartLineScale*config.barStrokeWidth) / 2) - 1); + if(barWidth>=0 && barWidth<=1)barWidth=1; + if(barWidth<0 && barWidth>=-1)barWidth=-1; + var additionalSpaceBetweenBars; + if(1*config.maxBarWidth >0 && barWidth > 1*config.maxBarWidth) { + additionalSpaceBetweenBars=(barWidth-1*config.maxBarWidth)/2; + barWidth=1*config.maxBarWidth; + } else additionalSpaceBetweenBars=0; + + var zeroY = 0; + var zeroY2 = 0; + + zeroY = calculateOffset(false, 0, calculatedScale, scaleHop); + if(typeof calculatedScale2 ==="object") zeroY2 = calculateOffset(config.logarithmic2, 0, calculatedScale2, scaleHop2); + drawLabels(); + initPassVariableData_part2(statData,data,config,ctx,{ + msr: msr, + zeroY : zeroY, + zeroY2 : zeroY2, + logarithmic : false, + logarithmic2 : false, + calculatedScale : calculatedScale, + additionalSpaceBetweenBars : additionalSpaceBetweenBars, + scaleHop : scaleHop, + valueHop : valueHop, + yAxisPosX : yAxisPosX, + xAxisPosY : xAxisPosY, + barWidth : barWidth + }); + animationLoop(config, drawScale, drawBars, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data, statData); + } else { + testRedraw(ctx,data,config); + ctx.firstPass=9; + } + function drawBars(animPc) { + var prevTopPos = new Array(); + var prevTopNeg = new Array(); + +// ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.barStrokeWidth); + for (var i = 0; i < data.datasets.length; i++) { + if(data.datasets[i].type=="Line") continue; + for (var j = 0; j < data.datasets[i].data.length; j++) { + ctx.lineWidth=Math.ceil(ctx.chartLineScale*setOptionValue(true,1,"BARSTROKEWIDTH",ctx,data,statData,data.datasets[i].barStrokeWidth,config.barStrokeWidth,"barStrokeWidth",i,j,{animationValue: currentAnimPc, xPosLeft : statData[i][j].xPosLeft, yPosBottom : botBar, xPosRight : statData[i][j].xPosRight, yPosTop : topBar} )); + var currentAnimPc = animationCorrection(animPc, data, config, i, j, false).animVal; + if (currentAnimPc > 1) currentAnimPc = currentAnimPc - 1; + if ((typeof data.datasets[i].data[j] == 'undefined') || 1*data.datasets[i].data[j] == 0 ) continue; + if(typeof prevTopPos[j]=="undefined"){ + prevTopPos[j]=statData[statData[i][j].firstNotMissing][j].yPosBottom; + prevTopNeg[j]=statData[statData[i][j].firstNotMissing][j].yPosBottom; + } + var botBar, topBar; +//// if(config.animationByDataset) { +//// botBar=statData[i][j].yPosBottom; +//// topBar=statData[i][j].yPosTop; +//// topBar=botBar+currentAnimPc*(topBar-botBar); +//// } else { + if(1*data.datasets[i].data[j] > 0) botBar=prevTopPos[j]; + else botBar=prevTopNeg[j]; +// botBar=statData[statData[i][j].firstNotMissing][j].yPosBottom - currentAnimPc*(statData[statData[i][j].firstNotMissing][j].yPosBottom-statData[i][j].yPosBottom); +// topBar=statData[statData[i][j].firstNotMissing][j].yPosBottom - currentAnimPc*(statData[statData[i][j].firstNotMissing][j].yPosBottom-statData[i][j].yPosTop); +// topBar=botBar+currentAnimPc*(statData[i][j].yPosTop-statData[i][j].yPosBottom); + topBar=botBar+currentAnimPc*(statData[i][j].yPosTop-statData[i][j].yPosBottom); + if(1*data.datasets[i].data[j] > 0) prevTopPos[j]=topBar; + else prevTopNeg[j]=topBar; + +//// } + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,j,{animationValue: currentAnimPc, xPosLeft : statData[i][j].xPosLeft, yPosBottom : botBar, xPosRight : statData[i][j].xPosRight, yPosTop : topBar} ); + ctx.strokeStyle=setOptionValue(true,1,"STROKECOLOR",ctx,data,statData,data.datasets[i].strokeColor,config.defaultStrokeColor,"strokeColor",i,j,{nullvalue : null} ); + + if(currentAnimPc !=0 && botBar!=topBar) { + ctx.beginPath(); + ctx.moveTo(statData[i][j].xPosLeft, botBar+(topBar-botBar)/2); + ctx.lineTo(statData[i][j].xPosLeft, topBar); + ctx.lineTo(statData[i][j].xPosRight, topBar); + ctx.lineTo(statData[i][j].xPosRight, botBar); + ctx.lineTo(statData[i][j].xPosLeft, botBar); + ctx.lineTo(statData[i][j].xPosLeft, botBar+(topBar-botBar)/2); + if (config.barShowStroke) { + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"STROKESTYLE",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + }; + ctx.closePath(); + ctx.fill(); + } + } + } + drawLinesDataset(animPc, data, config, ctx, statData,{xAxisPosY : xAxisPosY,yAxisPosX : yAxisPosX, valueHop : valueHop, nbValueHop : data.labels.length }); + + if (animPc >= config.animationStopValue) { + var yPos = 0, + xPos = 0; + for (i = 0; i < data.datasets.length; i++) { + for (j = 0; j < data.datasets[i].data.length; j++) { + if (typeof(data.datasets[i].data[j]) == 'undefined') continue; + jsGraphAnnotate[ctx.ChartNewId][jsGraphAnnotate[ctx.ChartNewId].length] = ["RECT", i, j, statData,setOptionValue(true,1,"ANNOTATEDISPLAY",ctx,data,statData,data.datasets[i].annotateDisplay,config.annotateDisplay,"annotateDisplay",i,j,{nullValue : true})]; + if(setOptionValue(true,1,"INGRAPHDATASHOW",ctx,data,statData,data.datasets[i].inGraphDataShow,config.inGraphDataShow,"inGraphDataShow",i,j,{nullValue : true})) { + ctx.save(); + ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }); + ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ); + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,j,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,j,{nullValue : true} ); + ctx.fillStyle = setOptionValue(true,1,"INGRAPHDATAFONTCOLOR",ctx,data,statData,undefined,config.inGraphDataFontColor,"inGraphDataFontColor",i,j,{nullValue : true} ); + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,j,{nullValue : true} ), statData[i][j],config); + ctx.beginPath(); + ctx.beginPath(); + yPos = 0; + xPos = 0; + if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 1) { + xPos = statData[i][j].xPosLeft + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 2) { + xPos = statData[i][j].xPosLeft + barWidth / 2 + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 3) { + xPos = statData[i][j].xPosLeft+ barWidth + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } + if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 1) { + yPos = statData[i][j].yPosBottom - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 2) { + yPos = (statData[i][j].yPosTop + statData[i][j].yPosBottom)/2 - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 3) { + yPos = statData[i][j].yPosTop - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } + if(yPos>msr.topNotUsableSize) { + ctx.translate(xPos, yPos); + var rotateVal=setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) * (Math.PI / 180); + ctx.rotate(rotateVal); + setTextBordersAndBackground(ctx,dispString,setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),0,0,setOptionValue(true,1,"INGRAPHDATABORDERS",ctx,data,statData,undefined,config.inGraphDataBorders,"inGraphDataBorders",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSCOLOR",ctx,data,statData,undefined,config.inGraphDataBordersColor,"inGraphDataBordersColor",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartLineScale,"INGRAPHDATABORDERSWIDTH",ctx,data,statData,undefined,config.inGraphDataBordersWidth,"inGraphDataBordersWidth",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSXSPACE",ctx,data,statData,undefined,config.inGraphDataBordersXSpace,"inGraphDataBordersXSpace",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSYSPACE",ctx,data,statData,undefined,config.inGraphDataBordersYSpace,"inGraphDataBordersYSpace",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSSTYLE",ctx,data,statData,undefined,config.inGraphDataBordersStyle,"inGraphDataBordersStyle",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABACKGROUNDCOLOR",ctx,data,statData,undefined,config.inGraphDataBackgroundColor,"inGraphDataBackgroundColor",i,j,{nullValue : true} ),"INGRAPHDATA"); + ctx.fillTextMultiLine(dispString, 0, 0, ctx.textBaseline, setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),true,config.detectMouseOnText,ctx,"INGRAPHDATA_TEXTMOUSE",rotateVal,xPos, yPos,i,j); + } + ctx.restore(); + } + } + } + } + if(msr.legendMsr.dispLegend)drawLegend(msr.legendMsr,data,config,ctx,"StackedBar"); + }; + + function drawScale() { + //X axis line + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth + Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight), xAxisPosY); + ctx.stroke(); + ctx.setLineDash([]); + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + for (var i = 0; i < data.labels.length; i++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i > 0 && i % config.scaleXGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + } else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + ctx.setLineDash([]); + + //Y axis + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + ctx.stroke(); + ctx.setLineDash([]); + + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + for (var j = ((config.showYAxisMin) ? -1 : 0); j < calculatedScale.steps; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && (j+1) % config.scaleYGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + msr.availableWidth + Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight), xAxisPosY - ((j + 1) * scaleHop)); + } else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + ctx.setLineDash([]); + }; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + //X axis labels + if (config.xAxisTop || config.xAxisBottom) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + if (config.xAxisBottom) { + for (var i = 0; i < data.labels.length; i++) { + if(showLabels(ctx,data,config,i)){ + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) + i * valueHop + additionalSpaceBetweenBars+ (barWidth / 2) - msr.highestXLabel / 2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[i], config.fmtXLabel), 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XAXIS_TEXTMOUSE",-(msr.rotateLabels * (Math.PI / 180)),yAxisPosX + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) + i * valueHop + additionalSpaceBetweenBars+(barWidth / 2) - msr.highestXLabel / 2, msr.xLabelPos,i,-1); + } else { + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[i], config.fmtXLabel), yAxisPosX + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) + i * valueHop + additionalSpaceBetweenBars+(barWidth / 2), msr.xLabelPos, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XAXIS_TEXTMOUSE",0,0,0,i,-1); + } + ctx.restore(); + } + } + } + } + //Y axis + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = ((config.showYAxisMin) ? -1 : 0); j < calculatedScale.steps; j++) { + if (config.scaleShowLabels) { + if(showYLabels(ctx,data,config,j+1,calculatedScale.labels[j + 1])) { + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX - (Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YLEFTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + if (config.yAxisRight) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX + msr.availableWidth + (Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YRIGHTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + } + } + } + }; + + function getValueBounds() { + var maxValp = -Number.MAX_VALUE; + var minValp = Number.MAX_VALUE; + var maxValn = -Number.MAX_VALUE; + var minValn = Number.MAX_VALUE; + + var tempp=[]; + var tempn=[]; + var inp=0; + var inn=0; + + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + if(1 * data.datasets[i].data[j] > 0) { + if(statData[i][0].tpchart=="Bar") { + if(typeof tempp[j] == "undefined") tempp[j]=0; + tempp[j] += 1 * data.datasets[i].data[j]; + maxValp=Math.max(maxValp,tempp[j]); + } else maxValp=Math.max(maxValp,1 * data.datasets[i].data[j]); + minValp=Math.min(minValp,1 * data.datasets[i].data[j]); + inp=1; + } else if(typeof (1 * data.datasets[i].data[j])==="number" && typeof data.datasets[i].data[j]!="undefined") { + if(statData[i][0].tpchart=="Bar") { + if(typeof tempn[j] == "undefined") tempn[j]=0; + tempn[j] += (1 * data.datasets[i].data[j]); + minValn=Math.min(minValn,tempn[j]); + } else minValn=Math.min(minValn,1 * data.datasets[i].data[j]); + maxValn=Math.max(maxValn,1 * data.datasets[i].data[j]); + inn=1; + } + } + }; + var upperValue, lowerValue; + if (inp==0){upperValue=maxValn;lowerValue=minValn;} + else if(inn==0) { upperValue=maxValp;lowerValue=minValp;} + else { upperValue=maxValp;lowerValue=minValn; } + + if(typeof config.graphMin=="function")lowerValue= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin,"graphMin",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if(typeof config.graphMax=="function") upperValue= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax,"graphMax",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + if(upperValue0) { + upperValue=upperValue*1.1; + lowerValue=lowerValue*0.9; + } else { + upperValue=upperValue*0.9; + lowerValue=lowerValue*1.1; + } + } + + labelHeight = (Math.ceil(ctx.chartTextScale*config.scaleFontSize)); + scaleHeight = msr.availableHeight; + var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight * 0.5)); + if(upperValue0 && valueBounds.minSteps>0) { + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + if (!config.scaleOverride) { + calculatedScale = calculateScale(1, config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, calculatedScale.labels, null, true, true, true, true, true, "HorizontalStackedBar"); + } else { + var scaleStartValue= setOptionValue(true,1,"SCALESTARTVALUE",ctx,data,statData,undefined,config.scaleStartValue,"scaleStartValue",-1,-1,{nullValue : true} ); + var scaleSteps =setOptionValue(true,1,"SCALESTEPS",ctx,data,statData,undefined,config.scaleSteps,"scaleSteps",-1,-1,{nullValue : true} ); + var scaleStepWidth = setOptionValue(true,1,"SCALESTEPWIDTH",ctx,data,statData,undefined,config.scaleStepWidth,"scaleStepWidth",-1,-1,{nullValue : true} ); + + calculatedScale = { + steps: scaleSteps, + stepValue: scaleStepWidth, + graphMin: scaleStartValue, + labels: [] + } + for (var i = 0; i <= calculatedScale.steps; i++) { + if (labelTemplateString) { + calculatedScale.labels.push(tmpl(labelTemplateString, { + value: fmtChartJS(config, 1 * ((scaleStartValue + (scaleStepWidth * i)).toFixed(getDecimalPlaces(scaleStepWidth))), config.fmtYLabel) + },config)); + } + } + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, calculatedScale.labels, null, true, true, true, true, true, "HorizontalStackedBar"); + } + msr.availableHeight = msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + msr.availableWidth = msr.availableWidth - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight); + scaleHop = Math.floor(msr.availableHeight / data.labels.length); + valueHop = Math.floor(msr.availableWidth / (calculatedScale.steps)); + if (valueHop == 0 || config.fullWidthGraph) valueHop = (msr.availableWidth / (calculatedScale.steps)); + msr.clrwidth = msr.clrwidth - (msr.availableWidth - (calculatedScale.steps * valueHop)); + msr.availableWidth = (calculatedScale.steps) * valueHop; + msr.availableHeight = (data.labels.length) * scaleHop; + yAxisPosX = msr.leftNotUsableSize + Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft); + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + barWidth = (scaleHop - Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth) * 2 - (Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) * 2) - (Math.ceil(ctx.chartSpaceScale*config.barDatasetSpacing) * 1 - 1) - (Math.ceil(ctx.chartLineScale*config.barStrokeWidth) / 2) - 1); + if(barWidth>=0 && barWidth<=1)barWidth=1; + if(barWidth<0 && barWidth>=-1)barWidth=-1; + var additionalSpaceBetweenBars; + if(1*config.maxBarWidth >0 && barWidth > 1*config.maxBarWidth) { + additionalSpaceBetweenBars= (barWidth-1*config.maxBarWidth)/2; + barWidth=1*config.maxBarWidth; + } else additionalSpaceBetweenBars=0; + + drawLabels(); + var zeroY= HorizontalCalculateOffset(0 , calculatedScale, scaleHop); + initPassVariableData_part2(statData,data,config,ctx,{ + yAxisPosX : yAxisPosX, + additionalSpaceBetweenBars : additionalSpaceBetweenBars, + xAxisPosY : xAxisPosY, + barWidth : barWidth, + zeroY : zeroY, + scaleHop : scaleHop, + valueHop : valueHop, + calculatedScale : calculatedScale + }); + + animationLoop(config, drawScale, drawBars, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data, statData); + } else { + testRedraw(ctx,data,config); + ctx.firstPass=9; + } + function HorizontalCalculateOffset(val, calculatedScale, scaleHop) { + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue / outerValue, 1, 0); + return (scaleHop * calculatedScale.steps) * scalingFactor; + }; + + function drawBars(animPc) { + var prevLeftPos = new Array(); + var prevLeftNeg = new Array(); + +// ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.barStrokeWidth); + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + ctx.lineWidth=Math.ceil(ctx.chartLineScale*setOptionValue(true,1,"BARSTROKEWIDTH",ctx,data,statData,data.datasets[i].barStrokeWidth,config.barStrokeWidth,"barStrokeWidth",i,j,{animationValue: currentAnimPc, xPosLeft : leftBar, yPosBottom : statData[i][j].yPosBottom, xPosRight : rightBar, yPosTop : statData[i][j].yPosBottom} )); + var currentAnimPc = animationCorrection(animPc, data, config, i, j, false).animVal; + if (currentAnimPc > 1) currentAnimPc = currentAnimPc - 1; + if ((typeof(data.datasets[i].data[j]) == 'undefined') || 1*data.datasets[i].data[j] == 0 ) continue; + if(typeof prevLeftPos[j]=="undefined"){ + prevLeftPos[j]=statData[statData[i][j].firstNotMissing][j].xPosLeft; + prevLeftNeg[j]=statData[statData[i][j].firstNotMissing][j].xPosLeft; + } + + var leftBar, rightBar; +//// if(config.animationByDataset) { +//// leftBar= statData[i][j].xPosLeft; +//// rightBar= statData[i][j].xPosRight; +//// rightBar=leftBar+currentAnimPc*(rightBar-leftBar); +//// } else { + if(1*data.datasets[i].data[j] > 0) leftBar=prevLeftPos[j]; + else leftBar=prevLeftNeg[j]; +// leftBar=statData[statData[i][j].firstNotMissing][j].xPosLeft + currentAnimPc*(statData[i][j].xPosLeft-statData[statData[i][j].firstNotMissing][j].xPosLeft); +// rightBar=statData[statData[i][j].firstNotMissing][j].xPosLeft + currentAnimPc*(statData[i][j].xPosRight-statData[statData[i][j].firstNotMissing][j].xPosLeft); + rightBar=leftBar+currentAnimPc*(statData[i][j].xPosRight-statData[i][j].xPosLeft); + + if(1*data.datasets[i].data[j] > 0) prevLeftPos[j]=rightBar; + else prevLeftNeg[j]=rightBar; +//// } + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,j,{animationValue: currentAnimPc, xPosLeft : leftBar, yPosBottom : statData[i][j].yPosBottom, xPosRight : rightBar, yPosTop : statData[i][j].yPosBottom} ); + + ctx.strokeStyle=setOptionValue(true,1,"STROKECOLOR",ctx,data,statData,data.datasets[i].strokeColor,config.defaultStrokeColor,"strokeColor",i,j,{nullvalue : null} ); + + if(currentAnimPc !=0 && statData[i][j].xPosLeft!=statData[i][j].xPosRight ) { + ctx.beginPath(); + ctx.moveTo(leftBar+(rightBar-leftBar)/2, statData[i][j].yPosTop); + ctx.lineTo(rightBar, statData[i][j].yPosTop); + ctx.lineTo(rightBar, statData[i][j].yPosBottom); + ctx.lineTo(leftBar, statData[i][j].yPosBottom); + ctx.lineTo(leftBar, statData[i][j].yPosTop); + ctx.lineTo(leftBar+(rightBar-leftBar)/2, statData[i][j].yPosTop); + if (config.barShowStroke){ + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"STROKESTYLE",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + } + ctx.closePath(); + ctx.fill(); + } + } + } + if (animPc >= config.animationStopValue) { + var yPos = 0, + xPos = 0; + for (i = 0; i < data.datasets.length; i++) { + for (j = 0; j < data.datasets[i].data.length; j++) { + if ((typeof(data.datasets[i].data[j]) == 'undefined')) continue; + jsGraphAnnotate[ctx.ChartNewId][jsGraphAnnotate[ctx.ChartNewId].length] = ["RECT", i ,j, statData,setOptionValue(true,1,"ANNOTATEDISPLAY",ctx,data,statData,data.datasets[i].annotateDisplay,config.annotateDisplay,"annotateDisplay",i,j,{nullValue : true})]; + if(setOptionValue(true,1,"INGRAPHDATASHOW",ctx,data,statData,data.datasets[i].inGraphDataShow,config.inGraphDataShow,"inGraphDataShow",i,j,{nullValue : true})) { + ctx.save(); + ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }); + ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ); + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,j,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,j,{nullValue : true} ); + ctx.fillStyle = setOptionValue(true,1,"INGRAPHDATAFONTCOLOR",ctx,data,statData,undefined,config.inGraphDataFontColor,"inGraphDataFontColor",i,j,{nullValue : true} ); + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,j,{nullValue : true} ),statData[i][j],config); + ctx.beginPath(); + yPos = 0; + xPos = 0; + if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 1) { + xPos = statData[i][j].xPosLeft + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 2) { + xPos = statData[i][j].xPosLeft + (statData[i][j].xPosRight-statData[i][j].xPosLeft)/2 + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 3) { + xPos = statData[i][j].xPosRight + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } + if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 1) { + yPos = statData[i][j].yPosBottom - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 2) { + yPos = statData[i][j].yPosBottom - barWidth / 2 - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 3) { + yPos = statData[i][j].yPosTop - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } +// if(xPos<=msr.availableWidth+msr.leftNotUsableSize) { + ctx.translate(xPos, yPos); + rotateVal=setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) * (Math.PI / 180); + ctx.rotate(rotateVal); + setTextBordersAndBackground(ctx,dispString,setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),0,0,setOptionValue(true,1,"INGRAPHDATABORDERS",ctx,data,statData,undefined,config.inGraphDataBorders,"inGraphDataBorders",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSCOLOR",ctx,data,statData,undefined,config.inGraphDataBordersColor,"inGraphDataBordersColor",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartLineScale,"INGRAPHDATABORDERSWIDTH",ctx,data,statData,undefined,config.inGraphDataBordersWidth,"inGraphDataBordersWidth",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSXSPACE",ctx,data,statData,undefined,config.inGraphDataBordersXSpace,"inGraphDataBordersXSpace",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSYSPACE",ctx,data,statData,undefined,config.inGraphDataBordersYSpace,"inGraphDataBordersYSpace",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSSTYLE",ctx,data,statData,undefined,config.inGraphDataBordersStyle,"inGraphDataBordersStyle",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABACKGROUNDCOLOR",ctx,data,statData,undefined,config.inGraphDataBackgroundColor,"inGraphDataBackgroundColor",i,j,{nullValue : true} ),"INGRAPHDATA"); + ctx.fillTextMultiLine(dispString, 0, 0, ctx.textBaseline, setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),true,config.detectMouseOnText,ctx,"INGRAPHDATA_TEXTMOUSE",rotateVal,xPos, yPos,i,j); + ctx.restore(); +// } + } + } + } + } + if(msr.legendMsr.dispLegend)drawLegend(msr.legendMsr,data,config,ctx,"HorizontalStackedBar"); + }; + + function drawScale() { + //X axis line + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth, xAxisPosY); + ctx.stroke(); + ctx.setLineDash([]); + + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + for (var i = ((config.showYAxisMin) ? -1 : 0); i < calculatedScale.steps; i++) { + if (i >= 0) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i > 0 && i % config.scaleXGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + } else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + ctx.setLineDash([]); + } + //Y axis + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + ctx.stroke(); + ctx.setLineDash([]); + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + for (var j = 0; j < data.labels.length; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && (j+1) % config.scaleYGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + msr.availableWidth, xAxisPosY - ((j + 1) * scaleHop)); + } else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + ctx.setLineDash([]); + }; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + //X axis line + if (config.scaleShowLabels && (config.xAxisTop || config.xAxisBottom)) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + if (config.xAxisBottom) { + for (var i = ((config.showYAxisMin) ? -1 : 0); i < calculatedScale.steps; i++) { + if(showYLabels(ctx,data,config,i+1,calculatedScale.labels[i+ 1])) { + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + (i + 1) * valueHop - msr.highestXLabel / 2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XAXIS_TEXTMOUSE",-(msr.rotateLabels * (Math.PI / 180)),yAxisPosX + (i + 1) * valueHop - msr.highestXLabel / 2, msr.xLabelPos,i,-1); + } else { + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], yAxisPosX + ((i + 1) * valueHop), msr.xLabelPos, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XAXIS_TEXTMOUSE",0,0,0,i,-1); + } + ctx.restore(); + } + } + } + } + //Y axis + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = 0; j < data.labels.length; j++) { + if(showLabels(ctx,data,config,j)){ + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[j], config.fmtXLabel), yAxisPosX - (Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop) + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) + additionalSpaceBetweenBars + (barWidth / 2), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YLEFTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + if (config.yAxisRight) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[j], config.fmtXLabel), yAxisPosX + msr.availableWidth + (Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop) + additionalSpaceBetweenBars+ (barWidth / 2), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YRIGHTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + } + } + }; + + function getValueBounds() { + var upperValue = -Number.MAX_VALUE; + var lowerValue = Number.MAX_VALUE; + var minvl = new Array(data.datasets.length); + var maxvl = new Array(data.datasets.length); + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + var k = i; + var tempp = 0; + var tempn = 0; + if (!(typeof(data.datasets[0].data[j]) == 'undefined')) { + if(1 * data.datasets[0].data[j] > 0) { + tempp += 1 * data.datasets[0].data[j]; + if (tempp > upperValue) { + upperValue = tempp; + }; + if (tempp < lowerValue) { + lowerValue = tempp; + }; + } else { + tempn += 1 * data.datasets[0].data[j]; + if (tempn > upperValue) { + upperValue = tempn; + }; + if (tempn < lowerValue) { + lowerValue = tempn; + }; + } + } + while (k > 0) { //get max of stacked data + if (!(typeof(data.datasets[k].data[j]) == 'undefined')) { + if(1 * data.datasets[k].data[j] > 0) { + tempp += 1 * data.datasets[k].data[j]; + if (tempp > upperValue) { + upperValue = tempp; + }; + if (tempp < lowerValue) { + lowerValue = tempp; + }; + } else { + tempn += 1 * data.datasets[k].data[j]; + if (tempn > upperValue) { + upperValue = tempn; + }; + if (tempn < lowerValue) { + lowerValue = tempn; + }; + } + } + k--; + } + } + }; + if(typeof config.graphMin=="function")lowerValue= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin,"graphMin",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if(typeof config.graphMax=="function") upperValue= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax,"graphMax",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax)) upperValue = config.graphMax; + if(upperValue0) { + upperValue=upperValue*1.1; + lowerValue=lowerValue*0.9; + } else { + upperValue=upperValue*0.9; + lowerValue=lowerValue*1.1; + } + } + labelHeight = (Math.ceil(ctx.chartTextScale*config.scaleFontSize)); + scaleHeight = msr.availableHeight; + var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight * 0.5)); + if(upperValue0 && valueBounds.minSteps>0) { + // true or fuzzy (error for negativ values (included 0)) + if (config.logarithmic !== false) { + if (valueBounds.minValue <= 0) { + config.logarithmic = false; + } + } + if (config.logarithmic2 !== false) { + if (valueBounds.minValue2 <= 0) { + config.logarithmic2 = false; + } + } + // Check if logarithmic is meanigful + var OrderOfMagnitude = calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.maxValue) + 1)) - calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.minValue))); + if ((config.logarithmic == 'fuzzy' && OrderOfMagnitude < 4) || config.scaleOverride) { + config.logarithmic = false; + } + // Check if logarithmic is meanigful + var OrderOfMagnitude2 = calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.maxValue2) + 1)) - calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.minValue2))); + if ((config.logarithmic2 == 'fuzzy' && OrderOfMagnitude2 < 4) || config.scaleOverride2) { + config.logarithmic2 = false; + } + + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + labelTemplateString2 = (config.scaleShowLabels2) ? config.scaleLabel2 : ""; + if (!config.scaleOverride) { + calculatedScale = calculateScale(1, config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + } else { + var scaleStartValue= setOptionValue(true,1,"SCALESTARTVALUE",ctx,data,statData,undefined,config.scaleStartValue,"scaleStartValue",-1,-1,{nullValue : true} ); + var scaleSteps =setOptionValue(true,1,"SCALESTEPS",ctx,data,statData,undefined,config.scaleSteps,"scaleSteps",-1,-1,{nullValue : true} ); + var scaleStepWidth = setOptionValue(true,1,"SCALESTEPWIDTH",ctx,data,statData,undefined,config.scaleStepWidth,"scaleStepWidth",-1,-1,{nullValue : true} ); + + calculatedScale = { + steps: scaleSteps, + stepValue: scaleStepWidth, + graphMin: scaleStartValue, + graphMax: scaleStartValue + scaleSteps * scaleStepWidth, + labels: [] + } + populateLabels(1, config, labelTemplateString, calculatedScale.labels, calculatedScale.steps, scaleStartValue, calculatedScale.graphMax, scaleStepWidth); + } + if (valueBounds.dbAxis) { + if (!config.scaleOverride2) { + calculatedScale2 = calculateScale(2, config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue2, valueBounds.minValue2, labelTemplateString2); + } else { + var scaleStartValue2= setOptionValue(true,1,"SCALESTARTVALUE2",ctx,data,statData,undefined,config.scaleStartValue2,"scaleStartValue2",-1,-1,{nullValue : true} ); + var scaleSteps2 =setOptionValue(true,1,"SCALESTEPS2",ctx,data,statData,undefined,config.scaleSteps2,"scaleSteps2",-1,-1,{nullValue : true} ); + var scaleStepWidth2 = setOptionValue(true,1,"SCALESTEPWIDTH2",ctx,data,statData,undefined,config.scaleStepWidth2,"scaleStepWidth2",-1,-1,{nullValue : true} ); + + calculatedScale2 = { + steps: scaleSteps2, + stepValue: scaleStepWidth2, + graphMin: scaleStartValue2, + graphMax: scaleStartValue2 + scaleSteps2 * scaleStepWidth2, + labels: [] + } + populateLabels(2, config, labelTemplateString2, calculatedScale2.labels, calculatedScale2.steps, scaleStartValue2, calculatedScale2.graphMax, scaleStepWidth2); + } + } else { + calculatedScale2 = { + steps: 0, + stepValue: 0, + graphMin: 0, + graphMax: 0, + labels: null + } + } + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, calculatedScale.labels, calculatedScale2.labels, true, false, true, true, true, "Bar"); + + var prevHeight=msr.availableHeight; + + msr.availableHeight = msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + msr.availableWidth = msr.availableWidth - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight); + scaleHop = Math.floor(msr.availableHeight / calculatedScale.steps); + scaleHop2 = Math.floor(msr.availableHeight / calculatedScale2.steps); + valueHop = Math.floor(msr.availableWidth / (data.labels.length)); + if (valueHop == 0 || config.fullWidthGraph) valueHop = (msr.availableWidth / data.labels.length); + msr.clrwidth = msr.clrwidth - (msr.availableWidth - ((data.labels.length) * valueHop)); + msr.availableWidth = (data.labels.length) * valueHop; + msr.availableHeight = (calculatedScale.steps) * scaleHop; + msr.xLabelPos+=(Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop) - (prevHeight-msr.availableHeight)); + msr.clrheight+=(Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop) - (prevHeight-msr.availableHeight)); + + yAxisPosX = msr.leftNotUsableSize + Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft); + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + barWidth = (valueHop - Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth) * 2 - (Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) * 2) - (Math.ceil(ctx.chartSpaceScale*config.barDatasetSpacing) * nrOfBars - 1) - ((Math.ceil(ctx.chartLineScale*config.barStrokeWidth) / 2) * nrOfBars - 1)) / nrOfBars; + if(barWidth>=0 && barWidth<=1)barWidth=1; + if(barWidth<0 && barWidth>=-1)barWidth=-1; + var additionalSpaceBetweenBars; + if(1*config.maxBarWidth >0 && barWidth > 1*config.maxBarWidth) { + additionalSpaceBetweenBars=nrOfBars*(barWidth-1*config.maxBarWidth)/2; + barWidth=1*config.maxBarWidth; + } else additionalSpaceBetweenBars=0; + + var zeroY2 = 0; + var zeroY = calculateOffset(config.logarithmic, 0, calculatedScale, scaleHop); + if(typeof calculatedScale2 ==="object") zeroY2 = calculateOffset(config.logarithmic2, 0, calculatedScale2, scaleHop2); + initPassVariableData_part2(statData,data,config,ctx,{ + msr: msr, + yAxisPosX : yAxisPosX, + xAxisPosY : xAxisPosY, + valueHop : valueHop, + nbValueHop : data.labels.length - 1, + barWidth : barWidth, + additionalSpaceBetweenBars : additionalSpaceBetweenBars, + zeroY : zeroY, + zeroY2 : zeroY2, + calculatedScale : calculatedScale, + calculatedScale2 : calculatedScale2, + scaleHop : scaleHop, + scaleHop2 : scaleHop2 + }); + drawLabels(); + animationLoop(config, drawScale, drawBars, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data, statData); + } else { + testRedraw(ctx,data,config); + ctx.firstPass=9; + } + + function drawBars(animPc) { + var t1, t2, t3; + + +// ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.barStrokeWidth); + for (var i = 0; i < data.datasets.length; i++) { +// ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.barStrokeWidth); +// ctx.lineWidth=Math.ceil(ctx.chartLineScale*setOptionValue(true,1,"BARSTROKEWIDTH",ctx,data,statData,data.datasets[i].barStrokeWidth,config.barStrokeWidth,"barStrokeWidth",i,j,{animationValue: currentAnimPc, xPosLeft : statData[i][j].xPosLeft, yPosBottom : statData[i][j].yPosBottom, xPosRight : statData[i][j].xPosLeft+barWidth, yPosTop : statData[i][j].yPosBottom-barHeight} )); + if(data.datasets[i].type=="Line") continue; + for (var j = 0; j < data.datasets[i].data.length; j++) { +ctx.lineWidth=Math.ceil(ctx.chartLineScale*setOptionValue(true,1,"BARSTROKEWIDTH",ctx,data,statData,data.datasets[i].barStrokeWidth,config.barStrokeWidth,"barStrokeWidth",i,j,{animationValue: currentAnimPc, xPosLeft : statData[i][j].xPosLeft, yPosBottom : statData[i][j].yPosBottom, xPosRight : statData[i][j].xPosLeft+barWidth, yPosTop : statData[i][j].yPosBottom-barHeight} )); + if (!(typeof(data.datasets[i].data[j]) == 'undefined')) { + var currentAnimPc = animationCorrection(animPc, data, config, i, j, false).animVal; + if (currentAnimPc > 1) currentAnimPc = currentAnimPc - 1; + var barHeight = currentAnimPc * (statData[i][j].barHeight) + (Math.ceil(ctx.chartLineScale*config.barStrokeWidth) / 2); + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,j,{animationValue: currentAnimPc, xPosLeft : statData[i][j].xPosLeft, yPosBottom : statData[i][j].yPosBottom, xPosRight : statData[i][j].xPosLeft+barWidth, yPosTop : statData[i][j].yPosBottom-barHeight} ); + ctx.strokeStyle=setOptionValue(true,1,"STROKECOLOR",ctx,data,statData,data.datasets[i].strokeColor,config.defaultStrokeColor,"strokeColor",i,j,{nullvalue : null} ); + roundRect(ctx, statData[i][j].xPosLeft, statData[i][j].yPosBottom, barWidth, barHeight, config.barShowStroke, config.barBorderRadius,i,j,(data.datasets[i].data[j] < 0 ? -1 : 1)); + } + } + } + drawLinesDataset(animPc, data, config, ctx, statData,{xAxisPosY : xAxisPosY,yAxisPosX : yAxisPosX, valueHop : valueHop, nbValueHop : data.labels.length }); + + if (animPc >= config.animationStopValue) { + + for (i = 0; i < data.datasets.length; i++) { + for (j = 0; j < data.datasets[i].data.length; j++) { + if (typeof(data.datasets[i].data[j]) == 'undefined') continue; + if (data.datasets[i].type == "Line") continue; + jsGraphAnnotate[ctx.ChartNewId][jsGraphAnnotate[ctx.ChartNewId].length] = ["RECT", i , j, statData,setOptionValue(true,1,"ANNOTATEDISPLAY",ctx,data,statData,data.datasets[i].annotateDisplay,config.annotateDisplay,"annotateDisplay",i,j,{nullValue : true})]; + if(setOptionValue(true,1,"INGRAPHDATASHOW",ctx,data,statData,data.datasets[i].inGraphDataShow,config.inGraphDataShow,"inGraphDataShow",i,j,{nullValue : true})) { + ctx.save(); + ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }); + ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ); + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,j,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,j,{nullValue : true} ); + ctx.fillStyle = setOptionValue(true,1,"INGRAPHDATAFONTCOLOR",ctx,data,statData,undefined,config.inGraphDataFontColor,"inGraphDataFontColor",i,j,{nullValue : true} ); + t1 = statData[i][j].yPosBottom; + t2 = statData[i][j].yPosTop; + ctx.beginPath(); + var yPos = 0, + xPos = 0; + if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 1) { + xPos = statData[i][j].xPosLeft + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 2) { + xPos = statData[i][j].xPosLeft + barWidth / 2 + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 3) { + xPos = statData[i][j].xPosLeft + barWidth + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } + if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 1) { + yPos = statData[i][j].yPosBottom - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 2) { + yPos = (statData[i][j].yPosBottom+statData[i][j].yPosTop)/2 - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 3) { + yPos = statData[i][j].yPosTop - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } + ctx.translate(xPos, yPos); + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,j,{nullValue : true} ), statData[i][j],config); + rotateVal=setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) * (Math.PI / 180); + ctx.rotate(rotateVal); + setTextBordersAndBackground(ctx,dispString,setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),0,0,setOptionValue(true,1,"INGRAPHDATABORDERS",ctx,data,statData,undefined,config.inGraphDataBorders,"inGraphDataBorders",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSCOLOR",ctx,data,statData,undefined,config.inGraphDataBordersColor,"inGraphDataBordersColor",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartLineScale,"INGRAPHDATABORDERSWIDTH",ctx,data,statData,undefined,config.inGraphDataBordersWidth,"inGraphDataBordersWidth",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSXSPACE",ctx,data,statData,undefined,config.inGraphDataBordersXSpace,"inGraphDataBordersXSpace",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSYSPACE",ctx,data,statData,undefined,config.inGraphDataBordersYSpace,"inGraphDataBordersYSpace",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSSTYLE",ctx,data,statData,undefined,config.inGraphDataBordersStyle,"inGraphDataBordersStyle",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABACKGROUNDCOLOR",ctx,data,statData,undefined,config.inGraphDataBackgroundColor,"inGraphDataBackgroundColor",i,j,{nullValue : true} ),"INGRAPHDATA"); + ctx.fillTextMultiLine(dispString, 0, 0, ctx.textBaseline, setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),true,config.detectMouseOnText,ctx,"INGRAPHDATA_TEXTMOUSE",rotateVal,xPos, yPos,i,j); + ctx.restore(); + } + } + } + } + if (animPc >= 1 && typeof drawMath == "function") { + drawMath(ctx, config, data, msr, { + xAxisPosY: xAxisPosY, + yAxisPosX: yAxisPosX, + valueHop: valueHop, + scaleHop: scaleHop, + zeroY: zeroY, + calculatedScale: calculatedScale, + calculateOffset: calculateOffset, + additionalSpaceBetweenBars : additionalSpaceBetweenBars, + barWidth: barWidth + }); + } + if(msr.legendMsr.dispLegend)drawLegend(msr.legendMsr,data,config,ctx,"Bar"); + }; + + function roundRect(ctx, x, y, w, h, stroke, radius,i,j,fact) { + ctx.beginPath(); + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"STROKESTYLE",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.moveTo(x + (w/2), y); + ctx.lineTo(x + w , y); + ctx.lineTo(x + w, y - h + fact*radius); + ctx.quadraticCurveTo(x + w, y - h, x + w - radius, y - h); + ctx.lineTo(x + radius, y - h); + ctx.quadraticCurveTo(x, y - h, x, y - h + fact*radius); + ctx.lineTo(x, y); + ctx.lineTo(x + (w/2), y); + if (stroke) ctx.stroke(); + ctx.closePath(); + ctx.fill(); + ctx.setLineDash([]); + }; + + function drawScale() { + //X axis line + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth + Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight), xAxisPosY); + ctx.stroke(); + ctx.setLineDash([]); + + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + for (var i = 0; i < data.labels.length; i++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i > 0 && i % config.scaleXGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + } else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + ctx.setLineDash([]); + + //Y axis + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + ctx.stroke(); + ctx.setLineDash([]); + + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + for (var j = 0; j < calculatedScale.steps; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && (j+1) % config.scaleYGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + msr.availableWidth + Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight), xAxisPosY - ((j + 1) * scaleHop)); + } else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + ctx.setLineDash([]); + }; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + //X axis line + if (config.xAxisTop || config.xAxisBottom) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + if (config.xAxisBottom) { + for (var i = 0; i < data.labels.length; i++) { + if(showLabels(ctx,data,config,i)){ + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + i * valueHop + (valueHop / 2) - msr.highestXLabel / 2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[i], config.fmtXLabel), 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XAXIS_TEXTMOUSE",-(msr.rotateLabels * (Math.PI / 180)),yAxisPosX + i * valueHop + (valueHop / 2) - msr.highestXLabel / 2, msr.xLabelPos,i,-1); + } else { + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[i], config.fmtXLabel), yAxisPosX + i * valueHop + (valueHop / 2), msr.xLabelPos, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XAXIS_TEXTMOUSE",0,0,0,i,-1); + } + ctx.restore(); + } + } + } + } + //Y axis + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = ((config.showYAxisMin) ? -1 : 0); j < calculatedScale.steps; j++) { + if (config.scaleShowLabels) { + if(showYLabels(ctx,data,config,j+1,calculatedScale.labels[j+ 1])) { + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX - (Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YLEFTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + if (config.yAxisRight && !valueBounds.dbAxis) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX + msr.availableWidth + (Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YRIGHTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + } + } + } + if (config.yAxisRight && valueBounds.dbAxis) { + for (j = ((config.showYAxisMin) ? -1 : 0); j < calculatedScale2.steps; j++) { + if (config.scaleShowLabels) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(calculatedScale2.labels[j + 1], yAxisPosX + msr.availableWidth + (Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - ((j + 1) * scaleHop2), ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YRIGHTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + } + } + }; + + function getValueBounds() { + var upperValue = -Number.MAX_VALUE; + var lowerValue = Number.MAX_VALUE; + var upperValue2 = -Number.MAX_VALUE; + var lowerValue2 = Number.MAX_VALUE; + var secondAxis = false; + var firstAxis = false; + var mathValueHeight; + for (var i = 0; i < data.datasets.length; i++) { + var mathFctName = data.datasets[i].drawMathDeviation; + var mathValueHeight = 0; + if (typeof eval(mathFctName) == "function") { + var parameter = { + data: data, + datasetNr: i + }; + mathValueHeightVal = window[mathFctName](parameter); + } else mathValueHeightVal=0; + for (var j = 0; j < data.datasets[i].data.length; j++) { + if(typeof mathValueHeightVal=="object") mathValueHeight=mathValueHeightVal[Math.min(mathValueHeightVal.length,j)]; + else mathValueHeight=mathValueHeightVal; + if(typeof data.datasets[i].data[j]=="undefined")continue; + if (data.datasets[i].axis == 2) { + secondAxis = true; + if (1 * data.datasets[i].data[j] + mathValueHeight > upperValue2) { + upperValue2 = 1 * data.datasets[i].data[j] + mathValueHeight; + }; + if (1 * data.datasets[i].data[j] - mathValueHeight < lowerValue2) { + lowerValue2 = 1 * data.datasets[i].data[j] - mathValueHeight; + }; + } else { + firstAxis=true; + if (1 * data.datasets[i].data[j] + mathValueHeight > upperValue) { + upperValue = 1 * data.datasets[i].data[j] + mathValueHeight; + }; + if (1 * data.datasets[i].data[j] - mathValueHeight < lowerValue) { + lowerValue = 1 * data.datasets[i].data[j] - mathValueHeight; + }; + } + } + }; + if(upperValue0) { + upperValue=upperValue*1.1; + lowerValue=lowerValue*0.9; + } else { + upperValue=upperValue*0.9; + lowerValue=lowerValue*1.1; + } + } + if(typeof config.graphMin=="function")lowerValue= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin,"graphMin",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if(typeof config.graphMax=="function") upperValue= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax,"graphMax",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + if (secondAxis) { + if(upperValue20) { + upperValue2=upperValue2*1.1; + lowerValue2=lowerValue2*0.9; + } else { + upperValue2=upperValue2*0.9; + lowerValue2=lowerValue2*1.1; + } + } + if(typeof config.graphMin2=="function")lowerValue2= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin2,"graphMin2",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin2)) lowerValue2 = config.graphMin2; + if(typeof config.graphMax2=="function") upperValue2= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax2,"graphMax2",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax2)) upperValue2 = config.graphMax2; + } + if (!firstAxis && secondAxis) { + upperValue = upperValue2; + lowerValue = lowerValue2; + } + + labelHeight = (Math.ceil(ctx.chartTextScale*config.scaleFontSize)); + scaleHeight = msr.availableHeight; + var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight * 0.5)); + if(upperValue0 && valueBounds.minSteps>0) { + if (config.logarithmic !== false) { + if (valueBounds.minValue <= 0) { + config.logarithmic = false; + } + } + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + if (!config.scaleOverride) { + calculatedScale = calculateScale(1, config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, calculatedScale.labels, null, true, true, true, true, true, "HorizontalBar"); + } else { + var scaleStartValue= setOptionValue(true,1,"SCALESTARTVALUE",ctx,data,statData,undefined,config.scaleStartValue,"scaleStartValue",-1,-1,{nullValue : true} ); + var scaleSteps =setOptionValue(true,1,"SCALESTEPS",ctx,data,statData,undefined,config.scaleSteps,"scaleSteps",-1,-1,{nullValue : true} ); + var scaleStepWidth = setOptionValue(true,1,"SCALESTEPWIDTH",ctx,data,statData,undefined,config.scaleStepWidth,"scaleStepWidth",-1,-1,{nullValue : true} ); + + calculatedScale = { + steps: scaleSteps, + stepValue: scaleStepWidth, + graphMin: scaleStartValue, + graphMax: scaleStartValue + scaleSteps * scaleStepWidth, + labels: [] + } + populateLabels(1, config, labelTemplateString, calculatedScale.labels, calculatedScale.steps, scaleStartValue, calculatedScale.graphMax, scaleStepWidth); + msr = setMeasures(data, config, ctx, ctx.canvas.height, ctx.canvas.width, calculatedScale.labels, null, true, true, true, true, true, "HorizontalBar"); + } + msr.availableHeight = msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + msr.availableWidth = msr.availableWidth - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) - Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight); + scaleHop = Math.floor(msr.availableHeight / data.labels.length); + valueHop = Math.floor(msr.availableWidth / (calculatedScale.steps)); + if (valueHop == 0 || config.fullWidthGraph) valueHop = (msr.availableWidth / calculatedScale.steps); + msr.clrwidth = msr.clrwidth - (msr.availableWidth - (calculatedScale.steps * valueHop)); + msr.availableWidth = (calculatedScale.steps) * valueHop; + msr.availableHeight = (data.labels.length) * scaleHop; + yAxisPosX = msr.leftNotUsableSize + Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft); + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop); + barWidth = (scaleHop - Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth) * 2 - (Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) * 2) - (Math.ceil(ctx.chartSpaceScale*config.barDatasetSpacing) * data.datasets.length - 1) - ((Math.ceil(ctx.chartLineScale*config.barStrokeWidth) / 2) * data.datasets.length - 1)) / data.datasets.length; + if(barWidth>=0 && barWidth<=1)barWidth=1; + if(barWidth<0 && barWidth>=-1)barWidth=-1; + var additionalSpaceBetweenBars; + if(1*config.maxBarWidth >0 && barWidth > 1*config.maxBarWidth) { + additionalSpaceBetweenBars=data.datasets.length*(barWidth-1*config.maxBarWidth)/2; + barWidth=1*config.maxBarWidth; + } else additionalSpaceBetweenBars=0; + + var zeroY = 0; + zeroY = calculateOffset(config.logarithmic, 0, calculatedScale, valueHop); + drawLabels(); + initPassVariableData_part2(statData,data,config,ctx,{ + yAxisPosX : yAxisPosX, + xAxisPosY : xAxisPosY, + barWidth : barWidth, + additionalSpaceBetweenBars : additionalSpaceBetweenBars, + zeroY : zeroY, + scaleHop : scaleHop, + valueHop : valueHop, + calculatedScale : calculatedScale + }); + animationLoop(config, drawScale, drawBars, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data, statData); + } else { + testRedraw(ctx,data,config); + ctx.firstPass=9; + } + + function drawBars(animPc) { + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { +// ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.barStrokeWidth); +ctx.lineWidth=Math.ceil(ctx.chartLineScale*setOptionValue(true,1,"BARSTROKEWIDTH",ctx,data,statData,data.datasets[i].barStrokeWidth,config.barStrokeWidth,"barStrokeWidth",i,j,{animationValue: currentAnimPc, xPosLeft : statData[i][j].xPosLeft, yPosBottom : statData[i][j].yPosBottom, xPosRight : statData[i][j].xPosLeft+barWidth, yPosTop : statData[i][j].yPosBottom-barHeight} )); + var currentAnimPc = animationCorrection(animPc, data, config, i, j, false).animVal; + if (currentAnimPc > 1) currentAnimPc = currentAnimPc - 1; + var barHeight = currentAnimPc * statData[i][j].barWidth + (Math.ceil(ctx.chartLineScale*config.barStrokeWidth) / 2); + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,j,{animationValue: currentAnimPc, xPosLeft : statData[i][j].xPosLeft, yPosBottom : statData[i][j].yPosBottom, xPosRight : statData[i][j].xPosLeft+barHeight, yPosTop : statData[i][j].yPosBottom} ); + ctx.strokeStyle=setOptionValue(true,1,"STROKECOLOR",ctx,data,statData,data.datasets[i].strokeColor,config.defaultStrokeColor,"strokeColor",i,j,{nullvalue : null} ); + + if (!(typeof(data.datasets[i].data[j]) == 'undefined')) { + roundRect(ctx, statData[i][j].yPosTop, statData[i][j].xPosLeft , barWidth, barHeight, config.barShowStroke, config.barBorderRadius, 0,i,j,(data.datasets[i].data[j] < 0 ? -1 : 1)); + } + } + } + if (animPc >= config.animationStopValue) { + for (i = 0; i < data.datasets.length; i++) { + for (j = 0; j < data.datasets[i].data.length; j++) { + if (typeof(data.datasets[i].data[j]) == 'undefined') continue; + jsGraphAnnotate[ctx.ChartNewId][jsGraphAnnotate[ctx.ChartNewId].length] = ["RECT", i ,j ,statData,setOptionValue(true,1,"ANNOTATEDISPLAY",ctx,data,statData,data.datasets[i].annotateDisplay,config.annotateDisplay,"annotateDisplay",i,j,{nullValue : true})]; + if(setOptionValue(true,1,"INGRAPHDATASHOW",ctx,data,statData,data.datasets[i].inGraphDataShow,config.inGraphDataShow,"inGraphDataShow",i,j,{nullValue : true})) { + ctx.save(); + ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }); + ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ); + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,j,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,j,{nullValue : true} ); + ctx.fillStyle = setOptionValue(true,1,"INGRAPHDATAFONTCOLOR",ctx,data,statData,undefined,config.inGraphDataFontColor,"inGraphDataFontColor",i,j,{nullValue : true} ); + ctx.beginPath(); + var yPos = 0, + xPos = 0; + if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 1) { + yPos = statData[i][j].yPosTop - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ) + barWidth; + } else if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 2) { + yPos = statData[i][j].yPosTop + barWidth / 2 - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAYPOSITION",ctx,data,statData,undefined,config.inGraphDataYPosition,"inGraphDataYPosition",i,j,{nullValue : true} ) == 3) { + yPos = statData[i][j].yPosTop - setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + } + if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 1) { + xPos = statData[i][j].xPosLeft + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 2) { + xPos = (statData[i][j].xPosLeft+statData[i][j].xPosRight)/2 + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } else if (setOptionValue(true,1,"INGRAPHDATAXPOSITION",ctx,data,statData,undefined,config.inGraphDataXPosition,"inGraphDataXPosition",i,j,{nullValue : true} ) == 3) { + xPos = statData[i][j].xPosRight + setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ); + } + ctx.translate(xPos, yPos); + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,j,{nullValue : true} ), statData[i][j],config); + var rotateVal=setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) * (Math.PI / 180); + ctx.rotate(rotateVal); + setTextBordersAndBackground(ctx,dispString,setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),0,0,setOptionValue(true,1,"INGRAPHDATABORDERS",ctx,data,statData,undefined,config.inGraphDataBorders,"inGraphDataBorders",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSCOLOR",ctx,data,statData,undefined,config.inGraphDataBordersColor,"inGraphDataBordersColor",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartLineScale,"INGRAPHDATABORDERSWIDTH",ctx,data,statData,undefined,config.inGraphDataBordersWidth,"inGraphDataBordersWidth",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSXSPACE",ctx,data,statData,undefined,config.inGraphDataBordersXSpace,"inGraphDataBordersXSpace",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSYSPACE",ctx,data,statData,undefined,config.inGraphDataBordersYSpace,"inGraphDataBordersYSpace",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSSTYLE",ctx,data,statData,undefined,config.inGraphDataBordersStyle,"inGraphDataBordersStyle",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABACKGROUNDCOLOR",ctx,data,statData,undefined,config.inGraphDataBackgroundColor,"inGraphDataBackgroundColor",i,j,{nullValue : true} ),"INGRAPHDATA"); + ctx.fillTextMultiLine(dispString, 0, 0, ctx.textBaseline, setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),true,config.detectMouseOnText,ctx,"INGRAPHDATA_TEXTMOUSE",rotateVal,xPos, yPos,i,j); + ctx.restore(); + } + } + } + } + if(msr.legendMsr.dispLegend)drawLegend(msr.legendMsr,data,config,ctx,"HorizontalBar"); + }; + + function roundRect(ctx, x, y, w, h, stroke, radius, zeroY,i,j,fact) { + ctx.beginPath(); + ctx.moveTo(y + zeroY, x ); + ctx.lineTo(y + zeroY, x + w); + ctx.lineTo(y + h - fact*radius, x + w); + ctx.quadraticCurveTo(y + h, x + w, y + h, x + w - radius); + ctx.lineTo(y + h, x + radius); + ctx.quadraticCurveTo(y + h, x, y + h - fact*radius, x); + ctx.lineTo(y + zeroY, x); + ctx.quadraticCurveTo(y + zeroY, x, y + zeroY, x + w); + + if (stroke) { + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"STROKESTYLE",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + }; + ctx.closePath(); + ctx.fill(); + }; + + function drawScale() { + //X axis line + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth + Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight), xAxisPosY); + ctx.stroke(); + ctx.setLineDash([]); + + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + for (var i = ((config.showYAxisMin) ? -1 : 0); i < calculatedScale.steps; i++) { + if (i >= 0) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i > 0 && i % config.scaleXGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + } else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + + } + ctx.setLineDash([]); + //Y axis + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleLineWidth); + ctx.strokeStyle = config.scaleLineColor; + ctx.setLineDash(lineStyleFn(config.scaleLineStyle)); + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + Math.ceil(ctx.chartLineScale*config.scaleTickSizeBottom)); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - Math.ceil(ctx.chartLineScale*config.scaleTickSizeTop)); + ctx.stroke(); + ctx.setLineDash([]); + ctx.setLineDash(lineStyleFn(config.scaleGridLineStyle)); + for (var j = 0; j < data.labels.length; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft), xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.scaleGridLineWidth); + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && (j+1) % config.scaleYGridLinesStep == 0) { + ctx.lineTo(yAxisPosX + msr.availableWidth + Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight), xAxisPosY - ((j + 1) * scaleHop)); + } else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + ctx.setLineDash([]); + }; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + //X axis line + if (config.scaleShowLabels && (config.xAxisTop || config.xAxisBottom)) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + if (config.xAxisBottom) { + for (var i = ((config.showYAxisMin) ? -1 : 0); i < calculatedScale.steps; i++) { + if(showYLabels(ctx,data,config,i+1,calculatedScale.labels[i+ 1])) { + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + (i + 1) * valueHop - msr.highestXLabel / 2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XAXIS_TEXTMOUSE",-(msr.rotateLabels * (Math.PI / 180)),yAxisPosX + (i + 1) * valueHop - msr.highestXLabel / 2, msr.xLabelPos,i,-1); + } else { + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], yAxisPosX + (i + 1) * valueHop, msr.xLabelPos, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"XAXIS_TEXTMOUSE",0,0,0,i,-1); + } + ctx.restore(); + } + } + } + } + //Y axis + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = 0; j < data.labels.length; j++) { + if(showLabels(ctx,data,config,j)){ + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[j], config.fmtXLabel), yAxisPosX - (Math.ceil(ctx.chartLineScale*config.scaleTickSizeLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - (j * scaleHop) - scaleHop / 2, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YLEFTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + if (config.yAxisRight) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(fmtChartJS(config, data.labels[j], config.fmtXLabel), yAxisPosX + msr.availableWidth + (Math.ceil(ctx.chartLineScale*config.scaleTickSizeRight) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight)), xAxisPosY - (j * scaleHop) - scaleHop / 2, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.scaleFontSize)),true,config.detectMouseOnText,ctx,"YRIGHTAXIS_TEXTMOUSE",0,0,0,-1,j); + } + } + } + }; + + function getValueBounds() { + var upperValue = -Number.MAX_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + if(typeof data.datasets[i].data[j]=="undefined")continue; + if (1 * data.datasets[i].data[j] > upperValue) { + upperValue = 1 * data.datasets[i].data[j] + }; + if (1 * data.datasets[i].data[j] < lowerValue) { + lowerValue = 1 * data.datasets[i].data[j] + }; + } + }; + if(upperValue0) { + upperValue=upperValue*1.1; + lowerValue=lowerValue*0.9; + } else { + upperValue=upperValue*0.9; + lowerValue=lowerValue*1.1; + } + } + // AJOUT CHANGEMENT + if(typeof config.graphMin=="function")lowerValue= setOptionValue(true,1,"GRAPHMIN",ctx,data,statData,undefined,config.graphMin,"graphMin",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if(typeof config.graphMax=="function") upperValue= setOptionValue(true,1,"GRAPHMAX",ctx,data,statData,undefined,config.graphMax,"graphMax",-1,-1,{nullValue : true}) + else if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + labelHeight = (Math.ceil(ctx.chartTextScale*config.scaleFontSize)); + scaleHeight = msr.availableHeight; + + var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight * 0.5)); + if(upperValue 1) config.animation.StartValue = 0; + if (config.animationStopValue < 0 || config.animationStopValue > 1) config.animation.StopValue = 1; + if (config.animationStopValue < config.animationStartValue) config.animationStopValue = config.animationStartValue; + if (isIE() < 9 && isIE() != false) config.animation = false; + var animFrameAmount = (config.animation) ? 1 / CapValue(config.animationSteps, Number.MAX_VALUE, 1) : 1, + easingFunction = animationOptions[config.animationEasing], + percentAnimComplete = (config.animation) ? 0 : 1; + if (config.animation && config.animationStartValue > 0 && config.animationStartValue <= 1) { + while (percentAnimComplete < config.animationStartValue) { + cntiter++; + percentAnimComplete += animFrameAmount; + } + } + var beginAnim = cntiter; + var beginAnimPct = percentAnimComplete; + if (typeof drawScale !== "function") drawScale = function() {}; + if (config.clearRect) { + if(config.animationForceSetTimeOut)requestAnimFrameSetTimeOut(animLoop); + else requestAnimFrame(animLoop); + } else animLoop(); + + function animateFrame() { + var easeAdjustedAnimationPercent = (config.animation) ? CapValue(easingFunction(percentAnimComplete), null, 0) : 1; + if (1 * cntiter >= 1 * CapValue(config.animationSteps, Number.MAX_VALUE, 1) || config.animation == false || ctx.firstPass%10!=1) easeAdjustedAnimationPercent = 1; + else if (easeAdjustedAnimationPercent >= 1) easeAdjustedAnimationPercent = 0.9999; + if (config.animation && !(isIE() < 9 && isIE() != false) && config.clearRect) ctx.clearRect(clrx, clry, clrwidth, clrheight); + dispCrossImage(ctx, config, midPosX, midPosY, borderX, borderY, false, data, easeAdjustedAnimationPercent, cntiter); + dispCrossText(ctx, config, midPosX, midPosY, borderX, borderY, false, data, easeAdjustedAnimationPercent, cntiter); + if(typeof config.beforeDrawFunction == "function") config.beforeDrawFunction("BEFOREDRAWFUNCTION",ctx,data,statData,-1,-1,{animationValue : easeAdjustedAnimationPercent, cntiter: cntiter, config : config, borderX : borderX, borderY : borderY, midPosX : midPosX, midPosY : midPosY}); + if (config.scaleOverlay) { + drawData(easeAdjustedAnimationPercent); + if(typeof config.endDrawDataFunction == "function")config.endDrawDataFunction("ENDDATAFUNCTION",ctx,data,statData,-1,-1,{animationValue : easeAdjustedAnimationPercent, cntiter: cntiter, config : config, borderX : borderX, borderY : borderY, midPosX : midPosX, midPosY : midPosY}); + drawScale(); + if(typeof config.endDrawScaleFunction == "function")config.endDrawScaleFunction("ENDSCALEFUNCTION",ctx,data,statData,-1,-1,{animationValue : easeAdjustedAnimationPercent, cntiter: cntiter, config : config, borderX : borderX, borderY : borderY, midPosX : midPosX, midPosY : midPosY}); + } else { + drawScale(); + if(typeof config.endDrawScaleFunction == "function")config.endDrawScaleFunction("ENDSCALEFUNCTION",ctx,data,statData,-1,-1,{animationValue : easeAdjustedAnimationPercent, cntiter: cntiter, config : config, borderX : borderX, borderY : borderY, midPosX : midPosX, midPosY : midPosY}); + drawData(easeAdjustedAnimationPercent); + if(typeof config.endDrawDataFunction == "function")config.endDrawDataFunction("ENDDATAFUNCTION",ctx,data,statData,-1,-1,{animationValue : easeAdjustedAnimationPercent, cntiter: cntiter, config : config, borderX : borderX, borderY : borderY, midPosX : midPosX, midPosY : midPosY}); + } + dispCrossImage(ctx, config, midPosX, midPosY, borderX, borderY, true, data, easeAdjustedAnimationPercent, cntiter); + dispCrossText(ctx, config, midPosX, midPosY, borderX, borderY, true, data, easeAdjustedAnimationPercent, cntiter); + }; + + function animLoop() { + //We need to check if the animation is incomplete (less than 1), or complete (1). + cntiter += multAnim; + percentAnimComplete += multAnim * animFrameAmount; + if (cntiter == config.animationSteps || config.animation == false || ctx.firstPass%10!=1) percentAnimComplete = 1; + else if (percentAnimComplete >= 1) percentAnimComplete = 0.999; + animateFrame(); + //Stop the loop continuing forever + if (multAnim == -1 && cntiter <= beginAnim) { + if (typeof config.onAnimationComplete == "function" && ctx.runanimationcompletefunction==true) config.onAnimationComplete(ctx, config, data, 0, animationCount + 1); + multAnim = 1; + if(config.animationForceSetTimeOut)requestAnimFrameSetTimeOut(animLoop); + else requestAnimFrame(animLoop); + } else if (percentAnimComplete < config.animationStopValue) { + if(config.animationForceSetTimeOut)requestAnimFrameSetTimeOut(animLoop); + else requestAnimFrame(animLoop); + } else { + if ((animationCount < config.animationCount || config.animationCount == 0) && (ctx.firstPass%10==1)) { + animationCount++; + if (config.animationBackward && multAnim == 1) { + percentAnimComplete -= animFrameAmount; + multAnim = -1; + } else { + multAnim = 1; + cntiter = beginAnim - 1; + percentAnimComplete = beginAnimPct - animFrameAmount; + } + window.setTimeout(animLoop, config.animationPauseTime*1000); + } else { + if(!testRedraw(ctx,data,config)) { + if (typeof config.onAnimationComplete == "function" && ctx.runanimationcompletefunction==true) { + config.onAnimationComplete(ctx, config, data, 1, animationCount + 1); + ctx.runanimationcompletefunction=false; + } + } + ctx.firstPass=9; + } + + } + }; + }; + //Declare global functions to be called within this namespace here. + // shim layer with setTimeout fallback + var requestAnimFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + })(); + var requestAnimFrameSetTimeOut = (function() { + return function(callback) { window.setTimeout(callback, 1000 / 60); }; + })(); + + + function calculateScale(axis, config, maxSteps, minSteps, maxValue, minValue, labelTemplateString) { + var graphMin, graphMax, graphRange, stepValue, numberOfSteps, valueRange, rangeOrderOfMagnitude, decimalNum; + var logarithmic, yAxisMinimumInterval; + if (axis == 2) { + logarithmic = config.logarithmic2; + yAxisMinimumInterval = config.yAxisMinimumInterval2; + } else { + logarithmic = config.logarithmic; + yAxisMinimumInterval = config.yAxisMinimumInterval; + } + + if (!logarithmic) { // no logarithmic scale + valueRange = maxValue - minValue; + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); + if(Math.abs(minValue)>config.zeroValue)graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + else graphMin=0; + if(Math.abs(maxValue)>config.zeroValue)graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + else graphMax=0; + if (typeof yAxisMinimumInterval == "number") { + if(graphMax>=0) { + graphMin = graphMin - (graphMin % yAxisMinimumInterval); + while (graphMin > minValue) graphMin = graphMin - yAxisMinimumInterval; + if (graphMax % yAxisMinimumInterval > config.zeroValue && graphMax % yAxisMinimumInterval < yAxisMinimumInterval - config.zeroValue) { + graphMax = roundScale(config, (1 + Math.floor(graphMax / yAxisMinimumInterval)) * yAxisMinimumInterval); + } + while (graphMax < maxValue) graphMax = graphMax + yAxisMinimumInterval; + } + } + } else { // logarithmic scale + if(minValue==maxValue)maxValue=maxValue+1; + if(minValue==0)minValue=0.01; + var minMag = calculateOrderOfMagnitude(minValue); + var maxMag = calculateOrderOfMagnitude(maxValue) + 1; + graphMin = Math.pow(10, minMag); + graphMax = Math.pow(10, maxMag); + rangeOrderOfMagnitude = maxMag - minMag; + } + graphRange = graphMax - graphMin; + stepValue = Math.pow(10, rangeOrderOfMagnitude); + numberOfSteps = Math.round(graphRange / stepValue); + if (!logarithmic) { // no logarithmic scale + //Compare number of steps to the max and min for that size graph, and add in half steps if need be. + var stopLoop = false; + while (!stopLoop && (numberOfSteps < minSteps || numberOfSteps > maxSteps)) { + if (numberOfSteps < minSteps) { + if (typeof yAxisMinimumInterval == "number") { + if (stepValue / 2 < yAxisMinimumInterval) { + stopLoop = true; + stepValue=yAxisMinimumInterval; + numberOfSteps=Math.ceil(graphRange / stepValue); + } + } + if (!stopLoop) { + stepValue /=2; + numberOfSteps = Math.round(graphRange / stepValue); + } + } else { + stepValue *= 2; + numberOfSteps = Math.round(graphRange / stepValue); + } + + } + + if (typeof yAxisMinimumInterval == "number") { + if (stepValue < yAxisMinimumInterval) { + stepValue = yAxisMinimumInterval; + numberOfSteps = Math.ceil(graphRange / stepValue); + } + if (stepValue % yAxisMinimumInterval > config.zeroValue && stepValue % yAxisMinimumInterval < yAxisMinimumInterval - config.zeroValue) { + if ((2 * stepValue) % yAxisMinimumInterval < config.zeroValue || (2 * stepValue) % yAxisMinimumInterval > yAxisMinimumInterval - config.zeroValue) { + stepValue = 2 * stepValue; + numberOfSteps = Math.ceil(graphRange / stepValue); + } else { + stepValue = roundScale(config, (1 + Math.floor(stepValue / yAxisMinimumInterval)) * yAxisMinimumInterval); + numberOfSteps = Math.ceil(graphRange / stepValue); + } + } + } + if(config.graphMaximized==true || config.graphMaximized=="bottom" || typeof config.graphMin!=="undefined") { + while (graphMin+stepValue < minValue && numberOfSteps>=3){graphMin+=stepValue;numberOfSteps--}; + } + if(config.graphMaximized==true || config.graphMaximized=="top" || typeof config.graphMax!=="undefined") { + + while (graphMin+(numberOfSteps-1)*stepValue >= maxValue && numberOfSteps>=3) numberOfSteps--; + } + } else { // logarithmic scale + numberOfSteps = rangeOrderOfMagnitude; // so scale is 10,100,1000,... + } + var labels = []; + populateLabels(1, config, labelTemplateString, labels, numberOfSteps, graphMin, graphMax, stepValue); + return { + steps: numberOfSteps, + stepValue: stepValue, + graphMin: graphMin, + labels: labels, + maxValue: maxValue + } + }; + + function roundScale(config, value) { + var scldec = 0; + var sscl = "" + config.yAxisMinimumInterval; + if (sscl.indexOf(".") > 0) { + scldec = sscl.substr(sscl.indexOf(".")).length; + } + return (Math.round(value * Math.pow(10, scldec)) / Math.pow(10, scldec)); + } ; + + function calculateOrderOfMagnitude(val) { + if (val==0)return 0; + return Math.floor(Math.log(val) / Math.LN10); + }; + //Populate an array of all the labels by interpolating the string. + function populateLabels(axis, config, labelTemplateString, labels, numberOfSteps, graphMin, graphMax, stepValue) { + var logarithmic; + if (axis == 2) { + logarithmic = config.logarithmic2; + fmtYLabel = config.fmtYLabel2; + } else { + logarithmic = config.logarithmic; + fmtYLabel = config.fmtYLabel; + } + if (labelTemplateString) { + //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. + var i; + if (!logarithmic) { // no logarithmic scale + for (i = 0; i < numberOfSteps + 1; i++) { + labels.push(tmpl(labelTemplateString, { + value: fmtChartJS(config, 1 * ((graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))), fmtYLabel) + },config)); + } + } else { // logarithmic scale 10,100,1000,... + var value = graphMin; + for (i = 0; i < numberOfSteps + 1; i++) { + labels.push(tmpl(labelTemplateString, { + value: fmtChartJS(config, 1 * value.toFixed(getDecimalPlaces(value)), fmtYLabel) + },config)); + value *= 10; + } + } + } + }; + //Max value from array + function Max(array) { + return Math.max.apply(Math, array); + }; + //Min value from array + function Min(array) { + return Math.min.apply(Math, array); + }; + //Default if undefined + function Default(userDeclared, valueIfFalse) { + if (!userDeclared) { + return valueIfFalse; + } else { + return userDeclared; + } + }; + //Apply cap a value at a high or low number + function CapValue(valueToCap, maxValue, minValue) { + if (isNumber(maxValue)) { + if (valueToCap > maxValue) { + return maxValue; + } + } + if (isNumber(minValue)) { + if (valueToCap < minValue) { + return minValue; + } + } + return valueToCap; + }; + + function getDecimalPlaces(num) { + var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + if (!match) { + return 0; + } + return Math.max( + 0, + (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0) + ); + }; + + function mergeChartConfig(defaults, userDefined) { + var returnObj = {}; + for (var attrname in defaults) { + returnObj[attrname] = defaults[attrname]; + } + for (var attrnameBis in userDefined) { + returnObj[attrnameBis] = userDefined[attrnameBis]; + } + return returnObj; + }; + //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ + var cache = {}; + + function tmpl(str, data,config) { + newstr=str; + if(newstr.substr(0,config.templatesOpenTag.length)==config.templatesOpenTag)newstr="<%="+newstr.substr(config.templatesOpenTag.length,newstr.length-config.templatesOpenTag.length); + if(newstr.substr(newstr.length-config.templatesCloseTag.length,config.templatesCloseTag.length)==config.templatesCloseTag)newstr=newstr.substr(0,newstr.length-config.templatesCloseTag.length)+"%>"; + return tmplpart2(newstr,data); + } + + function tmplpart2(str, data) { + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + var fn = !/\W/.test(str) ? + cache[str] = cache[str] || + tmplpart2(document.getElementById(str).innerHTML) : + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + "');}return p.join('');"); + // Provide some basic currying to the user + return data ? fn(data) : fn; + }; + + function dispCrossText(ctx, config, posX, posY, borderX, borderY, overlay, data, animPC, cntiter) { + var i, disptxt, txtposx, txtposy, textAlign, textBaseline; + for (i = 0; i < config.crossText.length; i++) { + if (config.crossText[i] != "" && config.crossTextOverlay[Min([i, config.crossTextOverlay.length - 1])] == overlay && ((cntiter == 1 && config.crossTextIter[Min([i, config.crossTextIter.length - 1])] == "first") || config.crossTextIter[Min([i, config.crossTextIter.length - 1])] == cntiter || config.crossTextIter[Min([i, config.crossTextIter.length - 1])] == "all" || (animPC == 1 && config.crossTextIter[Min([i, config.crossTextIter.length - 1])] == "last"))) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.crossTextFontStyle[Min([i, config.crossTextFontStyle.length - 1])] + " " + (Math.ceil(ctx.chartTextScale*config.crossTextFontSize[Min([i, config.crossTextFontSize.length - 1])])).toString() + "px " + config.crossTextFontFamily[Min([i, config.crossTextFontFamily.length - 1])]; + ctx.fillStyle = config.crossTextFontColor[Min([i, config.crossTextFontColor.length - 1])]; + textAlign = config.crossTextAlign[Min([i, config.crossTextAlign.length - 1])]; + textBaseline = config.crossTextBaseline[Min([i, config.crossTextBaseline.length - 1])]; + txtposx = 1 * Math.ceil(ctx.chartSpaceScale*config.crossTextPosX[Min([i, config.crossTextPosX.length - 1])]); + txtposy = 1 * Math.ceil(ctx.chartSpaceScale*config.crossTextPosY[Min([i, config.crossTextPosY.length - 1])]); + switch (1 * config.crossTextRelativePosX[Min([i, config.crossTextRelativePosX.length - 1])]) { + case 0: + if (textAlign == "default") textAlign = "left"; + break; + case 1: + txtposx += borderX; + if (textAlign == "default") textAlign = "right"; + break; + case 2: + txtposx += posX; + if (textAlign == "default") textAlign = "center"; + break; + case -2: + txtposx += context.canvas.width / 2; + if (textAlign == "default") textAlign = "center"; + break; + case 3: + txtposx += txtposx + 2 * posX - borderX; + if (textAlign == "default") textAlign = "left"; + break; + case 4: + txtposx += context.canvas.width; + if (textAlign == "default") textAlign = "right"; + break; + default: + txtposx += posX; + if (textAlign == "default") textAlign = "center"; + break; + } + switch (1 * config.crossTextRelativePosY[Min([i, config.crossTextRelativePosY.length - 1])]) { + case 0: + if (textBaseline == "default") textBaseline = "top"; + break; + case 3: + txtposy += borderY; + if (textBaseline == "default") textBaseline = "top"; + break; + case 2: + txtposy += posY; + if (textBaseline == "default") textBaseline = "middle"; + break; + case -2: + txtposy += context.canvas.height / 2; + if (textBaseline == "default") textBaseline = "middle"; + break; + case 1: + txtposy += txtposy + 2 * posY - borderY; + if (textBaseline == "default") textBaseline = "bottom"; + break; + case 4: + txtposy += context.canvas.height; + if (textBaseline == "default") textBaseline = "bottom"; + break; + default: + txtposy += posY; + if (textBaseline == "default") textBaseline = "middle"; + break; + } + ctx.textAlign = textAlign; + ctx.textBaseline = textBaseline; + ctx.translate(1 * txtposx, 1 * txtposy); + var rotateVal=Math.PI * config.crossTextAngle[Min([i, config.crossTextAngle.length - 1])] / 180; + ctx.rotate(rotateVal); + if (config.crossText[i].substring(0, 1) == "%") { + if (typeof config.crossTextFunction == "function") disptxt = config.crossTextFunction(i, config.crossText[i], ctx, config, posX, posY, borderX, borderY, overlay, data, animPC); + } else disptxt = config.crossText[i]; + + setTextBordersAndBackground(ctx,disptxt,Math.ceil(ctx.chartTextScale*config.crossTextFontSize[Min([i, config.crossTextFontSize.length - 1])]),0,0,config.crossTextBorders[Min([i, config.crossTextBorders.length - 1])],config.crossTextBordersColor[Min([i, config.crossTextBordersColor.length - 1])],Math.ceil(ctx.chartLineScale*config.crossTextBordersWidth[Min([i, config.crossTextBordersWidth.length - 1])]),Math.ceil(ctx.chartSpaceScale*config.crossTextBordersXSpace[Min([i, config.crossTextBordersXSpace.length - 1])]),Math.ceil(ctx.chartSpaceScale*config.crossTextBordersYSpace[Min([i, config.crossTextBordersYSpace.length - 1])]),config.crossTextBordersStyle[Min([i, config.crossTextBordersStyle.length - 1])],config.crossTextBackgroundColor[Min([i, config.crossTextBackgroundColor.length - 1])],"CROSSTEXT"); + if((animPC==1 && config.crossTextIter[Min([i, config.crossTextIter.length - 1])] == "all") || config.crossTextIter[Min([i, config.crossTextIter.length - 1])] != "last") { + ctx.fillTextMultiLine(disptxt, 0, 0, ctx.textBaseline, Math.ceil(ctx.chartTextScale*config.crossTextFontSize[Min([i, config.crossTextFontSize.length - 1])]),true,config.detectMouseOnText,ctx,"CROSSTEXT_TEXTMOUSE",rotateVal,1 * txtposx, 1 * txtposy,i,-1); + } else ctx.fillTextMultiLine(disptxt, 0, 0, ctx.textBaseline, Math.ceil(ctx.chartTextScale*config.crossTextFontSize[Min([i, config.crossTextFontSize.length - 1])]),true,false,ctx,"CROSSTEXT_TEXTMOUSE",rotateVal,1 * txtposx, 1 * txtposy,i,-1); + ctx.restore(); + } + } + }; + + function dispCrossImage(ctx, config, posX, posY, borderX, borderY, overlay, data, animPC, cntiter) { + var i, disptxt, imageposx, imageposy, imageAlign, imageBaseline; + for (i = 0; i < config.crossImage.length; i++) { + if (typeof config.crossImage[i] != "undefined" && config.crossImageOverlay[Min([i, config.crossImageOverlay.length - 1])] == overlay && ((cntiter == -1 && config.crossImageIter[Min([i, config.crossImageIter.length - 1])] == "background") || (cntiter == 1 && config.crossImageIter[Min([i, config.crossImageIter.length - 1])] == "first") || config.crossImageIter[Min([i, config.crossImageIter.length - 1])] == cntiter || (cntiter != -1 && config.crossImageIter[Min([i, config.crossImageIter.length - 1])] == "all") || (animPC == 1 && config.crossImageIter[Min([i, config.crossImageIter.length - 1])] == "last"))) { + ctx.save(); + ctx.beginPath(); + imageAlign = config.crossImageAlign[Min([i, config.crossImageAlign.length - 1])]; + imageBaseline = config.crossImageBaseline[Min([i, config.crossImageBaseline.length - 1])]; + imageposx = 1 * Math.ceil(ctx.chartSpaceScale*config.crossImagePosX[Min([i, config.crossImagePosX.length - 1])]); + imageposy = 1 * Math.ceil(ctx.chartSpaceScale*config.crossImagePosY[Min([i, config.crossImagePosY.length - 1])]); + switch (1 * config.crossImageRelativePosX[Min([i, config.crossImageRelativePosX.length - 1])]) { + case 0: + if (imageAlign == "default") imageAlign = "left"; + break; + case 1: + imageposx += borderX; + if (imageAlign == "default") imageAlign = "right"; + break; + case 2: + imageposx += posX; + if (imageAlign == "default") imageAlign = "center"; + break; + case -2: + imageposx += context.canvas.width / 2; + if (imageAlign == "default") imageAlign = "center"; + break; + case 3: + imageposx += imageposx + 2 * posX - borderX; + if (imageAlign == "default") imageAlign = "left"; + break; + case 4: + imageposx += context.canvas.width; + if (imageAlign == "default") imageAlign = "right"; + break; + default: + imageposx += posX; + if (imageAlign == "default") imageAlign = "center"; + break; + } + switch (1 * config.crossImageRelativePosY[Min([i, config.crossImageRelativePosY.length - 1])]) { + case 0: + if (imageBaseline == "default") imageBaseline = "top"; + break; + case 3: + imageposy += borderY; + if (imageBaseline == "default") imageBaseline = "top"; + break; + case 2: + imageposy += posY; + if (imageBaseline == "default") imageBaseline = "middle"; + break; + case -2: + imageposy += context.canvas.height / 2; + if (imageBaseline == "default") imageBaseline = "middle"; + break; + case 1: + imageposy += imageposy + 2 * posY - borderY; + if (imageBaseline == "default") imageBaseline = "bottom"; + break; + case 4: + imageposy += context.canvas.height; + if (imageBaseline == "default") imageBaseline = "bottom"; + break; + default: + imageposy += posY; + if (imageBaseline == "default") imageBaseline = "middle"; + break; + } + var imageWidth = config.crossImage[i].width; + switch (imageAlign) { + case "left": + break; + case "right": + imageposx -= imageWidth; + break; + case "center": + imageposx -= (imageWidth / 2); + break; + default: + break; + } + var imageHeight = config.crossImage[i].height; + switch (imageBaseline) { + case "top": + break; + case "bottom": + imageposy -= imageHeight; + break; + case "middle": + imageposy -= (imageHeight / 2); + break; + default: + break; + } + ctx.translate(1 * imageposx, 1 * imageposy); + ctx.rotate(Math.PI * config.crossImageAngle[Min([i, config.crossImageAngle.length - 1])] / 180); + ctx.drawImage(config.crossImage[i], 0, 0); + ctx.restore(); + } + } + }; + //**************************************************************************************** + function setMeasures(data, config, ctx, canvasheight, canvaswidth, ylabels, ylabels2, reverseLegend, reverseAxis, drawAxis, drawLegendOnData, legendBox, typegraph) { + + var height=canvasheight; + var width=canvaswidth; + if (window.devicePixelRatio && config.responsive==false) { + height=height/window.devicePixelRatio; + width=width/window.devicePixelRatio; + } + + + if (config.canvasBackgroundColor != "none") ctx.canvas.style.background = config.canvasBackgroundColor; + var borderWidth = 0; + var xAxisLabelPos = 0; + var graphTitleHeight = 0; + var graphTitlePosY = 0; + var graphSubTitleHeight = 0; + var graphSubTitlePosY = 0; + var footNoteHeight = 0; + var footNotePosY = 0; + var yAxisUnitHeight = 0; + var yAxisUnitPosY = 0; + var widestLegend = 0; + var nbeltLegend = 0; + var nbLegendLines = 0; + var nbLegendCols = 0; + var spaceLegendHeight = 0; + var xFirstLegendTextPos = 0; + var yFirstLegendTextPos = 0; + var xLegendBorderPos = 0; + var yLegendBorderPos = 0; + var yAxisLabelWidth = 0; + var yAxisLabelPosLeft = 0; + var yAxisLabelPosRight = 0; + var xAxisLabelHeight = 0; + var xLabelHeight = 0; + var widestXLabel = 1; + var highestXLabel = 1; + var widestYLabel = 0; + var highestYLabel = 1; + var widestYLabel2 = 0; + var highestYLabel2 = 1; + var leftNotUsableSize = 0; + var rightNotUsableSize = 0; + var rotateLabels = 0; + var xLabelPos = 0; + var legendBorderWidth = 0; + var legendBorderHeight = 0; + + ctx.widthAtSetMeasures=width; + ctx.heightAtSetMeasures=height; + + // Borders + if (config.canvasBorders) borderWidth = Math.ceil(ctx.chartLineScale*config.canvasBordersWidth); + // compute widest X label + var textMsr,i; + if (drawAxis) { + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + for (i = 0; i < data.labels.length; i++) { + if(showLabels(ctx,data,config,i) === true) { + textMsr = ctx.measureTextMultiLine(fmtChartJS(config, data.labels[i], config.fmtXLabel), (Math.ceil(ctx.chartTextScale*config.scaleFontSize))); + //If the text length is longer - make that equal to longest text! + widestXLabel = (textMsr.textWidth > widestXLabel) ? textMsr.textWidth : widestXLabel; + highestXLabel = (textMsr.textHeight > highestXLabel) ? textMsr.textHeight : highestXLabel; + } + } + if (widestXLabel < Math.ceil(ctx.chartTextScale*config.xScaleLabelsMinimumWidth)) widestXLabel = Math.ceil(ctx.chartTextScale*config.xScaleLabelsMinimumWidth); + } + // compute Y Label Width + if (drawAxis) { + widestYLabel = 1; + if (ylabels != null && ylabels != "nihil") { + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + for (i = ylabels.length - 1; i >= 0; i--) { + if (typeof(ylabels[i]) == "string") { + if(showYLabels(ctx,data,config,i,ylabels[i])) { + if (ylabels[i].trim() != "") { + textMsr = ctx.measureTextMultiLine(fmtChartJS(config, ylabels[i], config.fmtYLabel), (Math.ceil(ctx.chartTextScale*config.scaleFontSize))); + //If the text length is longer - make that equal to longest text! + widestYLabel = (textMsr.textWidth > widestYLabel) ? textMsr.textWidth : widestYLabel; + highestYLabel = (textMsr.textHeight > highestYLabel) ? textMsr.textHeight : highestYLabel; + } + } + } + } + } + if (widestYLabel < Math.ceil(ctx.chartTextScale*config.yScaleLabelsMinimumWidth)) { + widestYLabel = Math.ceil(ctx.chartTextScale*config.yScaleLabelsMinimumWidth); + } + widestYLabel2 = 1; + if (ylabels2 != null && config.yAxisRight) { + ctx.font = config.scaleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.scaleFontSize)).toString() + "px " + config.scaleFontFamily; + for (i = ylabels2.length - 1; i >= 0; i--) { + if (typeof(ylabels2[i]) == "string") { + if (ylabels2[i].trim() != "") { + textMsr = ctx.measureTextMultiLine(fmtChartJS(config, ylabels2[i], config.fmtYLabel2), (Math.ceil(ctx.chartTextScale*config.scaleFontSize))); + //If the text length is longer - make that equal to longest text! + widestYLabel2 = (textMsr.textWidth > widestYLabel2) ? textMsr.textWidth : widestYLabel2; + highestYLabel2 = (textMsr.textHeight > highestYLabel2) ? textMsr.textHeight : highestYLabel2; + } + } + } + } else { + widestYLabel2 = widestYLabel; + } + if (widestYLabel2 < Math.ceil(ctx.chartTextScale*config.yScaleLabelsMinimumWidth)) { + widestYLabel2 = Math.ceil(ctx.chartTextScale*config.yScaleLabelsMinimumWidth); + } + } + // yAxisLabel + leftNotUsableSize = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + rightNotUsableSize = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceRight); + if (drawAxis) { + if (typeof(config.yAxisLabel) != "undefined") { + if (config.yAxisLabel.trim() != "") { + yAxisLabelWidth = (Math.ceil(ctx.chartTextScale*config.yAxisFontSize)) * (config.yAxisLabel.split("\n").length || 1) + Math.ceil(ctx.chartSpaceScale*config.yAxisLabelSpaceLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisLabelSpaceRight); + yAxisLabelPosLeft = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisLabelSpaceLeft) + (Math.ceil(ctx.chartTextScale*config.yAxisFontSize)); + yAxisLabelPosRight = width - borderWidth - Math.ceil(ctx.chartSpaceScale*config.spaceRight) - Math.ceil(ctx.chartSpaceScale*config.yAxisLabelSpaceLeft) - (Math.ceil(ctx.chartTextScale*config.yAxisFontSize)); + } + if(config.yAxisLabelBackgroundColor !="none" || config.yAxisLabelBorders) { + yAxisLabelWidth+=2*(Math.ceil(ctx.chartSpaceScale*config.yAxisLabelBordersYSpace)); + yAxisLabelPosLeft+=Math.ceil(ctx.chartSpaceScale*config.yAxisLabelBordersYSpace); + yAxisLabelPosRight-=Math.ceil(ctx.chartSpaceScale*config.yAxisLabelBordersYSpace); + } + + if(config.graphTitleBorders) { + yAxisLabelWidth+=2*(Math.ceil(ctx.chartLineScale*config.yAxisLabelBordersWidth)); + yAxisLabelPosLeft+=Math.ceil(ctx.chartLineScale*config.yAxisLabelBordersWidth); + yAxisLabelPosRight-=Math.ceil(ctx.chartLineScale*config.yAxisLabelBordersWidth); + } + } + if (config.yAxisLeft) { + if (reverseAxis == false) leftNotUsableSize = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + yAxisLabelWidth + widestYLabel + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight); + else leftNotUsableSize = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + yAxisLabelWidth + widestXLabel + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight); + } + if (config.yAxisRight) { + if (reverseAxis == false) rightNotUsableSize = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceRight) + yAxisLabelWidth + widestYLabel2 + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight); + else rightNotUsableSize = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceRight) + yAxisLabelWidth + widestXLabel + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceLeft) + Math.ceil(ctx.chartSpaceScale*config.yAxisSpaceRight); + } + } + availableWidth = width - leftNotUsableSize - rightNotUsableSize; + // Title + if (config.graphTitle.trim() != "") { + graphTitleHeight = (Math.ceil(ctx.chartTextScale*config.graphTitleFontSize)) * (config.graphTitle.split("\n").length || 1) + Math.ceil(ctx.chartSpaceScale*config.graphTitleSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.graphTitleSpaceAfter); + graphTitlePosY = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceTop) + graphTitleHeight - Math.ceil(ctx.chartSpaceScale*config.graphTitleSpaceAfter); + if(config.graphTitleBackgroundColor !="none" || config.graphTitleBorders) { + graphTitleHeight+=2*(Math.ceil(ctx.chartSpaceScale*config.graphTitleBordersYSpace)); + graphTitlePosY+=Math.ceil(ctx.chartSpaceScale*config.graphTitleBordersYSpace); + } + + if(config.graphTitleBorders) { + graphTitleHeight+=2*(Math.ceil(ctx.chartLineScale*config.graphTitleBordersWidth)); + graphTitlePosY+=Math.ceil(ctx.chartLineScale*config.graphTitleBordersWidth); + } + } + // subTitle + if (config.graphSubTitle.trim() != "") { + graphSubTitleHeight = (Math.ceil(ctx.chartTextScale*config.graphSubTitleFontSize)) * (config.graphSubTitle.split("\n").length || 1) + Math.ceil(ctx.chartSpaceScale*config.graphSubTitleSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.graphSubTitleSpaceAfter); + graphSubTitlePosY = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceTop) + graphTitleHeight + graphSubTitleHeight - Math.ceil(ctx.chartSpaceScale*config.graphSubTitleSpaceAfter); + if(config.graphSubTitleBackgroundColor !="none" || config.graphSubTitleBorders) { + graphSubTitleHeight+=2*(Math.ceil(ctx.chartSpaceScale*config.graphSubTitleBordersYSpace)); + graphSubTitlePosY+=Math.ceil(ctx.chartSpaceScale*config.graphSubTitleBordersYSpace); + } + + if(config.graphSubTitleBorders) { + graphSubTitleHeight+=2*(Math.ceil(ctx.chartLineScale*config.graphSubTitleBordersWidth)); + graphSubTitlePosY+=Math.ceil(ctx.chartLineScale*config.graphSubTitleBordersWidth); + } + } + // yAxisUnit + if (drawAxis) { + if (config.yAxisUnit.trim() != "") { + yAxisUnitHeight = (Math.ceil(ctx.chartTextScale*config.yAxisUnitFontSize)) * (config.yAxisUnit.split("\n").length || 1) + Math.ceil(ctx.chartSpaceScale*config.yAxisUnitSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.yAxisUnitSpaceAfter); + yAxisUnitPosY = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceTop) + graphTitleHeight + graphSubTitleHeight + yAxisUnitHeight - Math.ceil(ctx.chartSpaceScale*config.yAxisUnitSpaceAfter); + } + if(config.yAxisUnitBackgroundColor !="none" || config.yAxisUnitBorders) { + yAxisUnitHeight+=2*(Math.ceil(ctx.chartSpaceScale*config.yAxisUnitBordersYSpace)); + yAxisUnitPosY+=Math.ceil(ctx.chartSpaceScale*config.yAxisUnitBordersYSpace); + } + + if(config.yAxisUnitBorders) { + yAxisUnitHeight+=2*(Math.ceil(ctx.chartLineScale*config.yAxisUnitBordersWidth)); + yAxisUnitPosY+=Math.ceil(ctx.chartLineScale*config.yAxisUnitBordersWidth); + } + + + } + topNotUsableSize = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceTop) + graphTitleHeight + graphSubTitleHeight + yAxisUnitHeight + Math.ceil(ctx.chartTextScale*config.graphSpaceBefore); + // footNote + if (typeof(config.footNote) != "undefined") { + if (config.footNote.trim() != "") { + footNoteHeight = (Math.ceil(ctx.chartTextScale*config.footNoteFontSize)) * (config.footNote.split("\n").length || 1) + Math.ceil(ctx.chartSpaceScale*config.footNoteSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.footNoteSpaceAfter); + footNotePosY = height - Math.ceil(ctx.chartSpaceScale*config.spaceBottom) - borderWidth - Math.ceil(ctx.chartSpaceScale*config.footNoteSpaceAfter); + if(config.footNoteBackgroundColor !="none" || config.footNoteBorders) { + footNoteHeight+=2*(Math.ceil(ctx.chartSpaceScale*config.footNoteBordersYSpace)); + footNotePosY-=Math.ceil(ctx.chartSpaceScale*config.footNoteBordersYSpace); + } + if(config.footNoteBorders) { + footNoteHeight+=2*(Math.ceil(ctx.chartLineScale*config.footNoteBordersWidth)); + footNotePosY-=Math.ceil(ctx.chartLineScale*config.footNoteBordersWidth); + } + } + } + + // xAxisLabel + if (drawAxis) { + if (typeof(config.xAxisLabel) != "undefined") { + if (config.xAxisLabel.trim() != "") { + xAxisLabelHeight = (Math.ceil(ctx.chartTextScale*config.xAxisFontSize)) * (config.xAxisLabel.split("\n").length || 1) + Math.ceil(ctx.chartSpaceScale*config.xAxisLabelSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.xAxisLabelSpaceAfter); + xAxisLabelPos = height - borderWidth - Math.ceil(ctx.chartSpaceScale*config.spaceBottom) - footNoteHeight - Math.ceil(ctx.chartSpaceScale*config.xAxisLabelSpaceAfter); + if(config.xAxisLabelBackgroundColor !="none" || config.footNoteBorders) { + xAxisLabelHeight+=2*(Math.ceil(ctx.chartSpaceScale*config.xAxisLabelBordersYSpace)); + xAxisLabelPos-=Math.ceil(ctx.chartSpaceScale*config.xAxisLabelBordersYSpace); + } + if(config.footNoteBorders) { + xAxisLabelHeight+=2*(Math.ceil(ctx.chartLineScale*config.xAxisLabelBordersWidth)); + xAxisLabelPos-=Math.ceil(ctx.chartLineScale*config.xAxisLabelBordersWidth); + } + } + } + } + + bottomNotUsableHeightWithoutXLabels = borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceBottom) + footNoteHeight + xAxisLabelHeight + Math.ceil(ctx.chartTextScale*config.graphSpaceAfter); + + // compute space for Legend + if (typeof(config.legend) != "undefined") { + if (config.legend == true) { + ctx.font = config.legendFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.legendFontSize)).toString() + "px " + config.legendFontFamily; + var textLength; + if (drawLegendOnData) { + for (i = data.datasets.length - 1; i >= 0; i--) { + if (typeof(data.datasets[i].title) == "string") { + if (data.datasets[i].title.trim() != "") { + nbeltLegend++; + textLength = ctx.measureText(fmtChartJS(config, data.datasets[i].title, config.fmtLegend)).width; + //If the text length is longer - make that equal to longest text! + widestLegend = (textLength > widestLegend) ? textLength : widestLegend; + } + } + } + } else { + for (i = data.length - 1; i >= 0; i--) { + if (typeof(data[i].title) == "string") { + if (data[i].title.trim() != "") { + nbeltLegend++; + textLength = ctx.measureText(fmtChartJS(config, data[i].title, config.fmtLegend)).width; + //If the text length is longer - make that equal to longest text! + widestLegend = (textLength > widestLegend) ? textLength : widestLegend; + } + } + } + } + if (nbeltLegend > 1 || (nbeltLegend == 1 && config.showSingleLegend)) { + widestLegend += Math.ceil(ctx.chartTextScale*config.legendBlockSize) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenBoxAndText); + if(config.legendPosY==1 || config.legendPosY==2 || config.legendPosY==3) { + availableLegendWidth = availableWidth- Math.ceil(ctx.chartSpaceScale*Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText)) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText); + } else { + availableLegendWidth = width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight) - 2 * (borderWidth) - Math.ceil(ctx.chartSpaceScale*Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText)) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText); + } + if (config.legendBorders == true) availableLegendWidth -= 2 * (Math.ceil(ctx.chartLineScale*config.legendBordersWidth)) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceLeft) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceRight); + maxLegendOnLine = Min([Math.floor((availableLegendWidth + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) / (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal))),config.maxLegendCols]); + nbLegendLines = Math.ceil(nbeltLegend / maxLegendOnLine); + nbLegendCols = Math.ceil(nbeltLegend / nbLegendLines); + + var legendHeight = nbLegendLines * ((Math.ceil(ctx.chartTextScale*config.legendFontSize)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextVertical)) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextVertical) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceAfterText); + + switch (config.legendPosY) { + case 0: + xFirstLegendTextPos = Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight) - nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) / 2; + spaceLegendHeight = legendHeight; + if (config.legendBorders == true) { + yLegendBorderPos = topNotUsableSize + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) + (Math.ceil(ctx.chartLineScale*config.legendBordersWidth)/2); + yFirstLegendTextPos = yLegendBorderPos + (Math.ceil(ctx.chartLineScale*config.legendBordersWidth)/2) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText)+(Math.ceil(ctx.chartTextScale*config.legendFontSize)); + spaceLegendHeight += 2 * Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + xLegendBorderPos = Math.floor(xFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) / 2)); + legendBorderHeight = Math.ceil(spaceLegendHeight - Math.ceil(ctx.chartLineScale*config.legendBordersWidth)) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + legendBorderWidth = Math.ceil(nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal))) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal) + Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText); + } else { + yFirstLegendTextPos = topNotUsableSize + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) + (Math.ceil(ctx.chartLineScale*config.legendBordersWidth)/2); + } + if(yAxisUnitHeight>0) { + yAxisUnitPosY+=spaceLegendHeight; + if(config.legendBorders==true)yLegendBorderPos-=yAxisUnitHeight; + yFirstLegendTextPos-=yAxisUnitHeight; + } + topNotUsableSize += spaceLegendHeight; + break; + case 1: + spaceLegendHeight = legendHeight; + xFirstLegendTextPos = Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight) - nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) / 2; + yFirstLegendTextPos = topNotUsableSize + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText)+(Math.ceil(ctx.chartTextScale*config.legendFontSize)); + if (config.legendBorders == true) { + yFirstLegendTextPos += Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore)+Math.ceil(ctx.chartLineScale*config.legendBordersWidth); + yLegendBorderPos = yFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText) - (Math.ceil(ctx.chartTextScale*config.legendFontSize)) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) /2 ); + spaceLegendHeight += 2 * Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + xLegendBorderPos = Math.floor(xFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) / 2)); + legendBorderHeight = Math.ceil(spaceLegendHeight - Math.ceil(ctx.chartLineScale*config.legendBordersWidth)) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + legendBorderWidth = Math.ceil(nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal))) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal) + Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText); + } + break; + case 2: + spaceLegendHeight = legendHeight; + xFirstLegendTextPos = Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight) - nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) / 2; + yFirstLegendTextPos = topNotUsableSize + (height - topNotUsableSize - bottomNotUsableHeightWithoutXLabels - spaceLegendHeight) /2 + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText)+(Math.ceil(ctx.chartTextScale*config.legendFontSize)); + if (config.legendBorders == true) { + yFirstLegendTextPos += Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + yLegendBorderPos = yFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText) - (Math.ceil(ctx.chartTextScale*config.legendFontSize)) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) /2 ); + spaceLegendHeight += 2 * Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + xLegendBorderPos = Math.floor(xFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) / 2)); + legendBorderHeight = Math.ceil(spaceLegendHeight - Math.ceil(ctx.chartLineScale*config.legendBordersWidth)) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + legendBorderWidth = Math.ceil(nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal))) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal) + Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText); + } + break; + case -2: + spaceLegendHeight = legendHeight; + xFirstLegendTextPos = Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight) - nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) / 2; + yFirstLegendTextPos = (height - spaceLegendHeight) /2 + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText)+(Math.ceil(ctx.chartTextScale*config.legendFontSize)); + if (config.legendBorders == true) { + yFirstLegendTextPos += Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + yLegendBorderPos = yFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText) - (Math.ceil(ctx.chartTextScale*config.legendFontSize)) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) /2 ); + spaceLegendHeight += 2 * Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + xLegendBorderPos = Math.floor(xFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) / 2)); + legendBorderHeight = Math.ceil(spaceLegendHeight - Math.ceil(ctx.chartLineScale*config.legendBordersWidth)) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + legendBorderWidth = Math.ceil(nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal))) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal) + Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText); + } + break; + case 3: + spaceLegendHeight = legendHeight; + xFirstLegendTextPos = Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight) - nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) / 2; + availableHeight = height - topNotUsableSize - bottomNotUsableHeightWithoutXLabels; + yFirstLegendTextPos = topNotUsableSize + availableHeight - spaceLegendHeight + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText)+(Math.ceil(ctx.chartTextScale*config.legendFontSize)); + if (config.legendBorders == true) { + yFirstLegendTextPos -= (Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter)+Math.ceil(ctx.chartLineScale*config.legendBordersWidth)); + yLegendBorderPos = yFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText) - (Math.ceil(ctx.chartTextScale*config.legendFontSize)) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) /2 ); + spaceLegendHeight += 2 * Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + xLegendBorderPos = Math.floor(xFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) / 2)); + legendBorderHeight = Math.ceil(spaceLegendHeight - Math.ceil(ctx.chartLineScale*config.legendBordersWidth)) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + legendBorderWidth = Math.ceil(nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal))) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal) + Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText); + } + break; + default: + spaceLegendHeight = legendHeight; + yFirstLegendTextPos = height - borderWidth - Math.ceil(ctx.chartSpaceScale*config.spaceBottom) - footNoteHeight - spaceLegendHeight + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBeforeText) + (Math.ceil(ctx.chartTextScale*config.legendFontSize)); + xFirstLegendTextPos = Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight) - nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) / 2; + if (config.legendBorders == true) { + spaceLegendHeight += 2 * Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + yFirstLegendTextPos -= (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter)); + yLegendBorderPos = Math.floor(height - borderWidth - Math.ceil(ctx.chartSpaceScale*config.spaceBottom) - footNoteHeight - spaceLegendHeight + (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) / 2) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore)); + xLegendBorderPos = Math.floor(xFirstLegendTextPos - Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText) - (Math.ceil(ctx.chartLineScale*config.legendBordersWidth) / 2)); + legendBorderHeight = Math.ceil(spaceLegendHeight - Math.ceil(ctx.chartLineScale*config.legendBordersWidth)) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceBefore) - Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceAfter); + legendBorderWidth = Math.ceil(nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal))) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal) + Math.ceil(ctx.chartLineScale*config.legendBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText); + } + xAxisLabelPos -= spaceLegendHeight; + bottomNotUsableHeightWithoutXLabels +=spaceLegendHeight; + break; + } + var fullLegendWidth=Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText) + nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal) +Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText); + if (config.legendBorders == true) { + fullLegendWidth+=2*Math.ceil(ctx.chartLineScale*config.legendBordersWidth)+Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceLeft)+Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceRight); + } + + switch (config.legendPosX) { + case 0: + case 1: + xFirstLegendTextPos = Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + config.canvasBorders * Math.ceil(ctx.chartLineScale*config.canvasBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText); + if (config.legendBorders == true) { + xFirstLegendTextPos += (Math.ceil(ctx.chartLineScale*config.legendBordersWidth)/2)+Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceLeft); + xLegendBorderPos = Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + config.canvasBorders * Math.ceil(ctx.chartLineScale*config.canvasBordersWidth) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceLeft); + } + if(config.legendPosX==0 && ((config.legendPosY>=1 && config.legendPosY <=3) || config.legendPosY==-2)) { + leftNotUsableSize+=fullLegendWidth; + yAxisLabelPosLeft+=fullLegendWidth; + } + break; + case 2: + xFirstLegendTextPos = leftNotUsableSize + (width - rightNotUsableSize - leftNotUsableSize)/2 - (Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText)-Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText)) - (nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) - Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) / 2; + if (config.legendBorders == true) { + xFirstLegendTextPos -= ((Math.ceil(ctx.chartLineScale*config.legendBordersWidth)/2) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceRight)); + xLegendBorderPos = xFirstLegendTextPos - Math.ceil(ctx.chartLineScale*config.legendBordersWidth)/2 - Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText) ; + } + break; + if((config.legendPosY>=1 && config.legendPosY <=3) || config.legendPosY==-2) { + rightNotUsableSize+=fullLegendWidth; + yAxisLabelPosRight-=fullLegendWidth; + } + case 3: + case 4: + xFirstLegendTextPos = width - rightNotUsableSize - Math.ceil(ctx.chartSpaceScale*config.legendSpaceRightText) - nbLegendCols * (widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal) / 2; + if (config.legendBorders == true) { + xFirstLegendTextPos -= ((Math.ceil(ctx.chartLineScale*config.legendBordersWidth)/2) + Math.ceil(ctx.chartSpaceScale*config.legendBordersSpaceRight)); + xLegendBorderPos = xFirstLegendTextPos - Math.ceil(ctx.chartLineScale*config.legendBordersWidth)/2 - Math.ceil(ctx.chartSpaceScale*config.legendSpaceLeftText) ; + } + if(config.legendPosX==4 && ((config.legendPosY>=1 && config.legendPosY <=3) || config.legendPosY==-2)) { + rightNotUsableSize+=fullLegendWidth; + yAxisLabelPosRight-=fullLegendWidth; + } + break; + default: + break; + } + if(config.legendBorders==true) { + yLegendBorderPos+=Math.ceil(ctx.chartSpaceScale*config.legendYPadding); + xLegendBorderPos+=Math.ceil(ctx.chartSpaceScale*config.legendXPadding); + + } + yFirstLegendTextPos+=Math.ceil(ctx.chartSpaceScale*config.legendYPadding); + xFirstLegendTextPos+=Math.ceil(ctx.chartSpaceScale*config.legendXPadding); + + } + } + + } + xLabelWidth = 0; + bottomNotUsableHeightWithXLabels = bottomNotUsableHeightWithoutXLabels; + if (drawAxis && (config.xAxisBottom || config.xAxisTop)) { + var widestLabel,highestLabel; + if (reverseAxis == false) { + widestLabel = widestXLabel; + highestLabel = highestXLabel; + nblab = data.labels.length; + } else { + widestLabel = widestYLabel; + highestLabel = highestYLabel; + nblab = ylabels.length; + } + if (config.rotateLabels == "smart") { + rotateLabels = 0; + if ((availableWidth + Math.ceil(ctx.chartTextScale*config.xAxisSpaceBetweenLabels)) / nblab < (widestLabel + Math.ceil(ctx.chartTextScale*config.xAxisSpaceBetweenLabels))) { + rotateLabels = 45; + if (availableWidth / nblab < Math.abs(Math.cos(rotateLabels * Math.PI / 180) * widestLabel)) { + rotateLabels = 90; + } + } + } else { + rotateLabels = config.rotateLabels + if (rotateLabels < 0) rotateLabels = 0; + if (rotateLabels > 180) rotateLabels = 180; + } + if (rotateLabels > 90) rotateLabels += 180; + xLabelHeight = Math.abs(Math.sin(rotateLabels * Math.PI / 180) * widestLabel) + Math.abs(Math.sin((rotateLabels + 90) * Math.PI / 180) * highestLabel) + Math.ceil(ctx.chartSpaceScale*config.xAxisSpaceBefore) + Math.ceil(ctx.chartSpaceScale*config.xAxisSpaceAfter); + xLabelPos = height - borderWidth - Math.ceil(ctx.chartSpaceScale*config.spaceBottom) - footNoteHeight - xAxisLabelHeight - (xLabelHeight - Math.ceil(ctx.chartSpaceScale*config.xAxisSpaceBefore)) - Math.ceil(ctx.chartTextScale*config.graphSpaceAfter); + xLabelWidth = Math.abs(Math.cos(rotateLabels * Math.PI / 180) * widestLabel) + Math.abs(Math.cos((rotateLabels + 90) * Math.PI / 180) * highestLabel); + leftNotUsableSize = Max([leftNotUsableSize, borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + xLabelWidth / 2]); + rightNotUsableSize = Max([rightNotUsableSize, borderWidth + Math.ceil(ctx.chartSpaceScale*config.spaceRight) + xLabelWidth / 2]); + availableWidth = width - leftNotUsableSize - rightNotUsableSize; + if (config.legend && config.xAxisBottom && config.legendPosY==4) { + xLabelPos-=spaceLegendHeight; + } + bottomNotUsableHeightWithXLabels = bottomNotUsableHeightWithoutXLabels + xLabelHeight ; + } else { + availableWidth = width - leftNotUsableSize - rightNotUsableSize; + } + + availableHeight = height - topNotUsableSize - bottomNotUsableHeightWithXLabels; + + // ----------------------- DRAW EXTERNAL ELEMENTS ------------------------------------------------- + dispCrossImage(ctx, config, width / 2, height / 2, width / 2, height / 2, false, data, -1, -1); + + if(typeof config.initFunction == "function") config.initFunction("INITFUNCTION",ctx,data,null,-1,-1,{animationValue : 0, cntiter: 0, config : config, borderX : 0, borderY : 0, midPosX : 0, midPosY : 0}); + + + if (ylabels != "nihil") { + // Draw Borders + if (borderWidth > 0) { + ctx.save(); + ctx.beginPath(); + ctx.lineWidth = 2 * borderWidth; + ctx.setLineDash(lineStyleFn(config.canvasBordersStyle)); + ctx.strokeStyle = config.canvasBordersColor; + ctx.moveTo(0, 0); + ctx.lineTo(0, height); + ctx.lineTo(width, height); + ctx.lineTo(width, 0); + ctx.lineTo(0, 0); + ctx.stroke(); + ctx.setLineDash([]); + ctx.restore(); + } + // Draw Graph Title + if (graphTitleHeight > 0) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.graphTitleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.graphTitleFontSize)).toString() + "px " + config.graphTitleFontFamily; + ctx.fillStyle = config.graphTitleFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + + setTextBordersAndBackground(ctx,config.graphTitle,(Math.ceil(ctx.chartTextScale*config.graphTitleFontSize)),Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight)) / 2,graphTitlePosY,config.graphTitleBorders,config.graphTitleBordersColor,Math.ceil(ctx.chartLineScale*config.graphTitleBordersWidth),Math.ceil(ctx.chartSpaceScale*config.graphTitleBordersXSpace),Math.ceil(ctx.chartSpaceScale*config.graphTitleBordersYSpace),config.graphTitleBordersStyle,config.graphTitleBackgroundColor,"GRAPHTITLE"); + + ctx.translate(Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight)) / 2, graphTitlePosY); + ctx.fillTextMultiLine(config.graphTitle, 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.graphTitleFontSize)),true,config.detectMouseOnText,ctx,"TITLE_TEXTMOUSE",0,Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight)) / 2, graphTitlePosY,-1,-1); + + ctx.stroke(); + ctx.restore(); + } + // Draw Graph Sub-Title + if (graphSubTitleHeight > 0) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.graphSubTitleFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.graphSubTitleFontSize)).toString() + "px " + config.graphSubTitleFontFamily; + ctx.fillStyle = config.graphSubTitleFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + setTextBordersAndBackground(ctx,config.graphSubTitle,(Math.ceil(ctx.chartTextScale*config.graphSubTitleFontSize)),Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight)) / 2,graphSubTitlePosY,config.graphSubTitleBorders,config.graphSubTitleBordersColor,Math.ceil(ctx.chartLineScale*config.graphSubTitleBordersWidth),Math.ceil(ctx.chartSpaceScale*config.graphSubTitleBordersXSpace),Math.ceil(ctx.chartSpaceScale*config.graphSubTitleBordersYSpace),config.graphSubTitleBordersStyle,config.graphSubTitleBackgroundColor,"GRAPHSUBTITLE"); + + ctx.translate(Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight)) / 2, graphSubTitlePosY); + ctx.fillTextMultiLine(config.graphSubTitle, 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.graphSubTitleFontSize)),true,config.detectMouseOnText,ctx,"SUBTITLE_TEXTMOUSE",0,Math.ceil(ctx.chartSpaceScale*config.spaceLeft) + (width - Math.ceil(ctx.chartSpaceScale*config.spaceLeft) - Math.ceil(ctx.chartSpaceScale*config.spaceRight)) / 2, graphSubTitlePosY,-1,-1); + ctx.stroke(); + ctx.restore(); + } + // Draw Y Axis Unit + if (yAxisUnitHeight > 0) { + if (config.yAxisLeft) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.yAxisUnitFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.yAxisUnitFontSize)).toString() + "px " + config.yAxisUnitFontFamily; + ctx.fillStyle = config.yAxisUnitFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + + setTextBordersAndBackground(ctx,config.yAxisUnit,(Math.ceil(ctx.chartTextScale*config.yAxisUnitFontSize)),leftNotUsableSize, yAxisUnitPosY,config.yAxisUnitBorders,config.yAxisUnitBordersColor,Math.ceil(ctx.chartLineScale*config.yAxisUnitBordersWidth),Math.ceil(ctx.chartSpaceScale*config.yAxisUnitBordersXSpace),Math.ceil(ctx.chartSpaceScale*config.yAxisUnitBordersYSpace),config.yAxisUnitBordersStyle,config.yAxisUnitBackgroundColor,"YAXISUNIT"); + ctx.translate(leftNotUsableSize, yAxisUnitPosY); + ctx.fillTextMultiLine(config.yAxisUnit, 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.yAxisUnitFontSize)),true,config.detectMouseOnText,ctx,"YLEFTAXISUNIT_TEXTMOUSE",0,leftNotUsableSize, yAxisUnitPosY,-1,-1); + ctx.stroke(); + ctx.restore(); + } + if (config.yAxisRight) { + if (config.yAxisUnit2 == '') config.yAxisUnit2 = config.yAxisUnit; + ctx.save(); + ctx.beginPath(); + ctx.font = config.yAxisUnitFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.yAxisUnitFontSize)).toString() + "px " + config.yAxisUnitFontFamily; + ctx.fillStyle = config.yAxisUnitFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + setTextBordersAndBackground(ctx,config.yAxisUnit2,(Math.ceil(ctx.chartTextScale*config.yAxisUnitFontSize)),width - rightNotUsableSize, yAxisUnitPosY,config.yAxisUnitBorders,config.yAxisUnitBordersColor,Math.ceil(ctx.chartLineScale*config.yAxisUnitBordersWidth),Math.ceil(ctx.chartSpaceScale*config.yAxisUnitBordersXSpace),Math.ceil(ctx.chartSpaceScale*config.yAxisUnitBordersYSpace),config.yAxisUnitBordersStyle,config.yAxisUnitBackgroundColor,"YAXISUNIT"); + ctx.translate(width - rightNotUsableSize, yAxisUnitPosY); + ctx.fillTextMultiLine(config.yAxisUnit2, 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.yAxisUnitFontSize)),true,config.detectMouseOnText,ctx,"YRIGHTAXISUNIT_TEXTMOUSE",0,width - rightNotUsableSize, yAxisUnitPosY,-1,-1); + ctx.stroke(); + ctx.restore(); + } + } + // Draw Y Axis Label + if (yAxisLabelWidth > 0) { + if (config.yAxisLeft) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.yAxisFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.yAxisFontSize)).toString() + "px " + config.yAxisFontFamily; + ctx.fillStyle = config.yAxisFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + + ctx.translate(yAxisLabelPosLeft, topNotUsableSize + (availableHeight / 2)); + ctx.rotate(-(90 * (Math.PI / 180))); + setTextBordersAndBackground(ctx,config.yAxisLabel,(Math.ceil(ctx.chartTextScale*config.yAxisFontSize)), 0,0, config.yAxisLabelBorders,config.yAxisLabelBordersColor,Math.ceil(ctx.chartLineScale*config.yAxisLabelBordersWidth),Math.ceil(ctx.chartSpaceScale*config.yAxisLabelBordersXSpace),Math.ceil(ctx.chartSpaceScale*config.yAxisLabelBordersYSpace),config.yAxisLabelBordersStyle,config.yAxisLabelBackgroundColor,"YAXISLABELLEFT"); + ctx.fillTextMultiLine(config.yAxisLabel, 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.yAxisFontSize)),false,config.detectMouseOnText,ctx,"YLEFTAXISLABEL_TEXTMOUSE",-(90 * (Math.PI / 180)),yAxisLabelPosLeft, topNotUsableSize + (availableHeight / 2),-1,-1); + ctx.stroke(); + ctx.restore(); + } + if (config.yAxisRight) { + if (config.yAxisLabel2 == '') config.yAxisLabel2 = config.yAxisLabel; + ctx.save(); + ctx.beginPath(); + ctx.font = config.yAxisFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.yAxisFontSize)).toString() + "px " + config.yAxisFontFamily; + ctx.fillStyle = config.yAxisFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(yAxisLabelPosRight, topNotUsableSize + (availableHeight / 2)); + ctx.rotate(+(90 * (Math.PI / 180))); + setTextBordersAndBackground(ctx,config.yAxisLabel2,(Math.ceil(ctx.chartTextScale*config.yAxisFontSize)), 0,0, config.yAxisLabelBorders,config.yAxisLabelBordersColor,Math.ceil(ctx.chartLineScale*config.yAxisLabelBordersWidth),Math.ceil(ctx.chartSpaceScale*config.yAxisLabelBordersXSpace),Math.ceil(ctx.chartSpaceScale*config.yAxisLabelBordersYSpace),config.yAxisLabelBordersStyle,config.yAxisLabelBackgroundColor,"YAXISLABELLEFT"); + ctx.fillTextMultiLine(config.yAxisLabel2, 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.yAxisFontSize)),false,config.detectMouseOnText,ctx,"YRIGHTAXISLABEL_TEXTMOUSE",+(90 * (Math.PI / 180)),yAxisLabelPosRight, topNotUsableSize + (availableHeight / 2),-1,-1); + ctx.stroke(); + ctx.restore(); + } + } + // Draw X Axis Label + if (xAxisLabelHeight > 0) { + if (config.xAxisBottom) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.xAxisFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.xAxisFontSize)).toString() + "px " + config.xAxisFontFamily; + ctx.fillStyle = config.xAxisFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + setTextBordersAndBackground(ctx,config.xAxisLabel,(Math.ceil(ctx.chartTextScale*config.xAxisFontSize)),leftNotUsableSize + (availableWidth / 2), xAxisLabelPos,config.xAxisLabelBorders,config.xAxisLabelBordersColor,Math.ceil(ctx.chartLineScale*config.xAxisLabelBordersWidth),Math.ceil(ctx.chartSpaceScale*config.xAxisLabelBordersXSpace),Math.ceil(ctx.chartSpaceScale*config.xAxisLabelBordersYSpace),config.xAxisLabelBordersStyle,config.xAxisLabelBackgroundColor,"XAXISLABEL"); + ctx.translate(leftNotUsableSize + (availableWidth / 2), xAxisLabelPos); + ctx.fillTextMultiLine(config.xAxisLabel, 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.xAxisFontSize)),true,config.detectMouseOnText,ctx,"XAXISLABEL_TEXTMOUSE",0,leftNotUsableSize + (availableWidth / 2), xAxisLabelPos,-1,-1); + ctx.stroke(); + ctx.restore(); + } + } + // Draw Legend + var legendMsr; + if (nbeltLegend > 1 || (nbeltLegend == 1 && config.showSingleLegend)) { + legendMsr={dispLegend : true, xLegendBorderPos : xLegendBorderPos, + yLegendBorderPos : yLegendBorderPos, legendBorderWidth : legendBorderWidth, legendBorderHeight : legendBorderHeight, + nbLegendCols: nbLegendCols, xFirstLegendTextPos : xFirstLegendTextPos , yFirstLegendTextPos : yFirstLegendTextPos, + drawLegendOnData : drawLegendOnData, reverseLegend : reverseLegend, legendBox : legendBox, widestLegend : widestLegend }; + if(config.legendPosY==0 || config.legendPosY==4 || config.legendPosX==0 || config.legendPosX==4) { + + drawLegend(legendMsr,data,config,ctx,typegraph); + legendMsr={dispLegend : false}; + } + } else { + legendMsr={dispLegend : false }; + } + // Draw FootNote + if (config.footNote.trim() != "") { + ctx.save(); + ctx.font = config.footNoteFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.footNoteFontSize)).toString() + "px " + config.footNoteFontFamily; + ctx.fillStyle = config.footNoteFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + + setTextBordersAndBackground(ctx,config.footNote,(Math.ceil(ctx.chartTextScale*config.footNoteFontSize)),leftNotUsableSize + (availableWidth / 2), footNotePosY,config.footNoteBorders,config.footNoteBordersColor,Math.ceil(ctx.chartLineScale*config.footNoteBordersWidth),Math.ceil(ctx.chartSpaceScale*config.footNoteBordersXSpace),Math.ceil(ctx.chartSpaceScale*config.footNoteBordersYSpace),config.footNoteBordersStyle,config.footNoteBackgroundColor,"FOOTNOTE"); + + ctx.translate(leftNotUsableSize + (availableWidth / 2), footNotePosY); + ctx.fillTextMultiLine(config.footNote, 0, 0, ctx.textBaseline, (Math.ceil(ctx.chartTextScale*config.footNoteFontSize)),true,config.detectMouseOnText,ctx,"FOOTNOTE_TEXTMOUSE",0,leftNotUsableSize + (availableWidth / 2), footNotePosY,-1,-1); + ctx.stroke(); + ctx.restore(); + } + } + clrx = leftNotUsableSize; + clrwidth = availableWidth; + clry = topNotUsableSize; + clrheight = availableHeight; + return { + leftNotUsableSize: leftNotUsableSize, + rightNotUsableSize: rightNotUsableSize, + availableWidth: availableWidth, + topNotUsableSize: topNotUsableSize, + bottomNotUsableHeightWithoutXLabels: bottomNotUsableHeightWithoutXLabels, + bottomNotUsableHeightWithXLabels: bottomNotUsableHeightWithXLabels, + availableHeight: availableHeight, + widestXLabel: widestXLabel, + highestXLabel: highestXLabel, + widestYLabel: widestYLabel, + widestYLabel2: widestYLabel2, + highestYLabel: highestYLabel, + rotateLabels: rotateLabels, + xLabelPos: xLabelPos, + clrx: clrx, + clry: clry, + clrwidth: clrwidth, + clrheight: clrheight, + legendMsr : legendMsr + }; + }; + + + // Function for drawing lines (BarLine|Line) + + function drawLinesDataset(animPc, data, config, ctx, statData,vars) { + var y1,y2,y3,diffnb,diffnbj,fact, currentAnimPc; + var prevypos; + var pts=[]; + for (var i = 0; i < data.datasets.length; i++) { + if(setOptionValue(true,1,"ANIMATION",ctx,data,statData,data.datasets[i].animation,config.animation,"animation",i,-1,{nullvalue : null} )==true || animPc >= 1) { + prevypos="undefined"; + if(statData[i][0].tpchart!="Line")continue; + if (statData[i].length == 0) continue; + if (statData[i][0].firstNotMissing == -1) continue; + + ctx.save(); + ctx.beginPath(); + + prevAnimPc={ mainVal:0 , subVal : 0,animVal : 0 }; + var firstpt=-1; + var lastxPos=-1; + for (var j = statData[i][0].firstNotMissing; j <= statData[i][0].lastNotMissing; j++) { + if(prevAnimPc.animVal==0 && j>statData[i][0].firstNotMissing) continue; + currentAnimPc = animationCorrection(animPc, data, config, i, j, true); + if (currentAnimPc.mainVal == 0 && (prevAnimPc.mainVal > 0 && firstpt !=-1)) { + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"LINEDASH",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + if(config.extrapolateMissingData) { + y1=statData[i][statData[i][j].prevNotMissing].yAxisPos - prevAnimPc.mainVal*statData[i][statData[i][j].prevNotMissing].yPosOffset; + y2=statData[i][j].yAxisPos - prevAnimPc.mainVal*statData[i][statData[i][j-1].nextNotMissing].yPosOffset; + diffnb=statData[i][j-1].nextNotMissing-statData[i][j].prevNotMissing; + diffnbj=(j-1)-statData[i][j].prevNotMissing; + fact=(diffnbj+prevAnimPc.subVal)/diffnb; + y3=y1+fact*(y2-y1); + traceLine(pts,ctx,statData[i][statData[i][j].prevNotMissing].xPos + fact*(statData[i][statData[i][j-1].nextNotMissing].xPos-statData[i][statData[i][j].prevNotMissing].xPos) , y3,config,data,statData,i,setYposOrigin(i,j,data,statData)); + closebz(pts,ctx,config,i); + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"LINEDASH",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + ctx.strokeStyle = "rgba(0,0,0,0)"; + if(config.datasetFill && setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )!=1) { + ctx.lineTo(statData[i][statData[i][j].prevNotMissing].xPos + fact*(statData[i][statData[i][j-1].nextNotMissing].xPos-statData[i][statData[i][j].prevNotMissing].xPos) , statData[i][j].yAxisPos ); + ctx.lineTo(statData[i][firstpt].xPos, statData[i][firstpt].xAxisPosY-statData[i][0].zeroY); + ctx.closePath(); + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,j,{animationValue: currentAnimPc.mainVal, xPosLeft : statData[i][0].xPos, yPosBottom : Math.max(statData[i][0].yAxisPos,statData[i][0].yAxisPos- ((config.animationLeftToRight) ? 1 : 1*currentAnimPc.mainVal) * statData[i][0].lminvalue_offset), xPosRight : statData[i][data.datasets[i].data.length-1].xPos, yPosTop : Math.min(statData[i][0].yAxisPos, statData[i][0].yAxisPos - ((config.animationLeftToRight) ? 1 : 1*currentAnimPc.mainVal) * statData[i][0].lmaxvalue_offset)} ); + ctx.fill(); + firstpt=-1; + } + } else if (!(typeof statData[i][j].value == "undefined")) { + traceLine(pts,ctx,statData[i][j-1].xPos + prevAnimPc.subVal*(statData[i][j].xPos-statData[i][j-1].xPos) , statData[i][j].yAxisPos - prevAnimPc.mainVal*statData[i][statData[i][j-1].nextNotMissing].yPosOffset,config,data,statData,i,setYposOrigin(i,j,data,statData)); + closebz(pts,ctx,config,i); + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"LINEDASH",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + ctx.strokeStyle = "rgba(0,0,0,0)"; + if(config.datasetFill && setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )!=1) { + ctx.lineTo(statData[i][j-1].xPos + prevAnimPc.subVal*(statData[i][j].xPos-statData[i][j-1].xPos) , statData[i][j].yAxisPos ); + ctx.lineTo(statData[i][firstpt].xPos, statData[i][firstpt].xAxisPosY-statData[i][0].zeroY); + ctx.closePath(); + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,j,{animationValue: currentAnimPc.mainVal, xPosLeft : statData[i][0].xPos, yPosBottom : Math.max(statData[i][0].yAxisPos,statData[i][0].yAxisPos- ((config.animationLeftToRight) ? 1 : 1*currentAnimPc.mainVal) * statData[i][0].lminvalue_offset), xPosRight : statData[i][data.datasets[i].data.length-1].xPos, yPosTop : Math.min(statData[i][0].yAxisPos, statData[i][0].yAxisPos - ((config.animationLeftToRight) ? 1 : 1*currentAnimPc.mainVal) * statData[i][0].lmaxvalue_offset)} ); + ctx.fill(); + } + } + prevAnimPc = currentAnimPc; + continue; + } else if(currentAnimPc.totVal ==0) { + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"LINEDASH",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + ctx.strokeStyle = "rgba(0,0,0,0)"; + } else { + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"LINEDASH",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + ctx.strokeStyle=setOptionValue(true,1,"STROKECOLOR",ctx,data,statData,data.datasets[i].strokeColor,config.defaultStrokeColor,"strokeColor",i,j,{nullvalue : null} ); + } + prevAnimPc = currentAnimPc; + + switch(typeof data.datasets[i].data[j]) { + case "undefined" : + if (!config.extrapolateMissingData) { + if(firstpt==-1) continue; + closebz(pts,ctx,config,i); + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"LINEDASH",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + if (config.datasetFill && firstpt != -1 && setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )!=1) { + lastxPos=-1; + ctx.strokeStyle = "rgba(0,0,0,0)"; + ctx.lineTo(statData[i][j-1].xPos, statData[i][j-1].yAxisPos); + ctx.lineTo(statData[i][firstpt].xPos, statData[i][firstpt].yAxisPos); + ctx.closePath(); + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,j,{animationValue: currentAnimPc.mainVal, xPosLeft : statData[i][0].xPos, yPosBottom : Math.max(statData[i][0].yAxisPos,statData[i][0].yAxisPos- ((config.animationLeftToRight) ? 1 : 1*currentAnimPc.mainVal) * statData[i][0].lminvalue_offset), xPosRight : statData[i][data.datasets[i].data.length-1].xPos, yPosTop : Math.min(statData[i][0].yAxisPos, statData[i][0].yAxisPos - ((config.animationLeftToRight) ? 1 : 1*currentAnimPc.mainVal) * statData[i][0].lmaxvalue_offset)} ); + ctx.fill(); + } + ctx.beginPath(); + prevAnimPc={ mainVal:0 , subVal : 0 }; + firstpt=-1; + } else if (currentAnimPc.subVal > 0) { + lastxPos=statData[i][j].xPos + currentAnimPc.subVal*(statData[i][j+1].xPos-statData[i][j].xPos); + y1=statData[i][statData[i][j+1].prevNotMissing].yAxisPos - statData[i][statData[i][j+1].prevNotMissing].yPosOffset; + y2=statData[i][statData[i][j].nextNotMissing].yAxisPos - statData[i][statData[i][j].nextNotMissing].yPosOffset; + diffnb=statData[i][j].nextNotMissing-statData[i][j+1].prevNotMissing; + diffnbj=(j)-statData[i][j+1].prevNotMissing; + fact=(diffnbj+prevAnimPc.subVal)/diffnb; + y3=y1+fact*(y2-y1); +// traceLine(pts,ctx,statData[i][j].xPos + currentAnimPc.subVal*(statData[i][j+1].xPos-statData[i][j].xPos), y3,config,data,statData,i); + traceLine(pts,ctx,statData[i][statData[i][j].prevNotMissing].xPos + fact*(statData[i][statData[i][j-1].nextNotMissing].xPos-statData[i][statData[i][j].prevNotMissing].xPos) , y3,config,data,statData,i,setYposOrigin(i,j,data,statData)); + } + break; + default : + ctx.lineWidth = Math.ceil(ctx.chartLineScale*setOptionValue(true,1,"LINEWIDTH",ctx,data,statData,data.datasets[i].datasetStrokeWidth,config.datasetStrokeWidth,"datasetStrokeWidth",i,j,{nullvalue : null} )); + if (firstpt==-1) { + firstpt=j; + ctx.beginPath(); + if(setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )==1) { + if(typeof statData[i][j].yPosOffsetOrigin != "undefined")ctx.moveTo(statData[i][j].xPos, statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffsetOrigin); + else ctx.moveTo(statData[i][firstpt].xPos, statData[i][firstpt].xAxisPosY-statData[i][0].zeroY); + ctx.lineTo(statData[i][j].xPos, statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffset); + } else ctx.moveTo(statData[i][j].xPos, statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffset); + initbz(pts,statData[i][j].xPos, statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffset,i); + lastxPos=statData[i][j].xPos; + } else { + lastxPos=statData[i][j].xPos; + traceLine(pts,ctx,statData[i][j].xPos, statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffset,config,data,statData,i,setYposOrigin(i,j,data,statData)); + } + if (currentAnimPc.subVal > 0 && statData[i][j].nextNotMissing !=-1 && (config.extrapolateMissing || statData[i][j].nextNotMissing==j+1)) { + lastxPos=statData[i][j].xPos + currentAnimPc.subVal*(statData[i][j+1].xPos-statData[i][j].xPos); + y1=statData[i][statData[i][j+1].prevNotMissing].yAxisPos - statData[i][statData[i][j+1].prevNotMissing].yPosOffset; + y2=statData[i][statData[i][j].nextNotMissing].yAxisPos - statData[i][statData[i][j].nextNotMissing].yPosOffset; + y3=y1+currentAnimPc.subVal*(y2-y1); + traceLine(pts,ctx,statData[i][j].xPos + currentAnimPc.subVal*(statData[i][statData[i][j].nextNotMissing].xPos-statData[i][j].xPos) , y3,config,data,statData,i,setYposOrigin(i,j,data,statData)); + } + break + } + } + closebz(pts,ctx,config,i); + ctx.setLineDash(lineStyleFn(setOptionValue(true,1,"LINEDASH",ctx,data,statData,data.datasets[i].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",i,j,{nullvalue : null} ))); + ctx.stroke(); + ctx.setLineDash([]); + if (config.datasetFill && setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )!=1) { + if (firstpt>=0 ) { + ctx.strokeStyle = "rgba(0,0,0,0)"; + ctx.lineTo(lastxPos, statData[i][0].xAxisPosY-statData[i][0].zeroY); + ctx.lineTo(statData[i][firstpt].xPos, statData[i][firstpt].xAxisPosY-statData[i][0].zeroY); + ctx.closePath(); + ctx.fillStyle=setOptionValue(true,1,"COLOR",ctx,data,statData,data.datasets[i].fillColor,config.defaultFillColor,"fillColor",i,-1,{animationValue: currentAnimPc.mainVal, xPosLeft : statData[i][0].xPos, yPosBottom : Math.max(statData[i][0].yAxisPos,statData[i][0].yAxisPos- ((config.animationLeftToRight) ? 1 : 1*currentAnimPc.mainVal) * statData[i][0].lminvalue_offset), xPosRight : statData[i][data.datasets[i].data.length-1].xPos, yPosTop : Math.min(statData[i][0].yAxisPos, statData[i][0].yAxisPos - ((config.animationLeftToRight) ? 1 : 1*currentAnimPc.mainVal) * statData[i][0].lmaxvalue_offset)} ); + ctx.fill(); + } + } + ctx.restore(); + if (animPc >= 1) { + for (j = 0; j < data.datasets[i].data.length; j++) { + if (!(typeof(data.datasets[i].data[j]) == 'undefined')) { + currentAnimPc = animationCorrection(animPc, data, config, i, j, true); + if (currentAnimPc.mainVal > 0 || !config.animationLeftToRight) { + if(setOptionValue(true,1,"POINTDOT",ctx,data,statData,undefined,config.pointDot,"pointDot",i,j,{nullvalue : null} )) { + ctx.beginPath(); + ctx.fillStyle=setOptionValue(true,1,"MARKERFILLCOLOR",ctx,data,statData,data.datasets[i].pointColor,config.defaultStrokeColor,"pointColor",i,j,{nullvalue: true} ); + ctx.strokeStyle=setOptionValue(true,1,"MARKERSTROKESTYLE",ctx,data,statData,data.datasets[i].pointStrokeColor,config.defaultStrokeColor,"pointStrokeColor",i,j,{nullvalue: true} ); + ctx.lineWidth=setOptionValue(true,ctx.chartLineScale,"MARKERLINEWIDTH",ctx,data,statData,data.datasets[i].pointDotStrokeWidth,config.pointDotStrokeWidth,"pointDotStrokeWidth",i,j,{nullvalue: true} ); + var markerShape=setOptionValue(true,1,"MARKERSHAPE",ctx,data,statData,data.datasets[i].markerShape,config.markerShape,"markerShape",i,j,{nullvalue: true} ); + var markerRadius=setOptionValue(true,ctx.chartSpaceScale,"MARKERRADIUS",ctx,data,statData,data.datasets[i].pointDotRadius,config.pointDotRadius,"pointDotRadius",i,j,{nullvalue: true} ); + var markerStrokeStyle=setOptionValue(true,1,"MARKERSTROKESTYLE",ctx,data,statData,data.datasets[i].pointDotStrokeStyle,config.pointDotStrokeStyle,"pointDotStrokeStyle",i,j,{nullvalue: true} ); + drawMarker(ctx, statData[i][j].xPos , statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffset, markerShape,markerRadius,markerStrokeStyle); + } + } + } + } + } + + if (animPc >= config.animationStopValue) { + for (j = 0; j < data.datasets[i].data.length; j++) { + if (typeof(data.datasets[i].data[j]) == 'undefined') continue; + if(!(data.datasets[i].mouseDetection==false))jsGraphAnnotate[ctx.ChartNewId][jsGraphAnnotate[ctx.ChartNewId].length] = ["POINT", i, j, statData,setOptionValue(true,1,"ANNOTATEDISPLAY",ctx,data,statData,data.datasets[i].annotateDisplay,config.annotateDisplay,"annotateDisplay",i,j,{nullValue : true})]; + if (setOptionValue(true,1,"INGRAPHDATASHOW",ctx,data,statData,data.datasets[i].inGraphDataShow,config.inGraphDataShow,"inGraphDataShow",i,j,{nullValue : true})) { + ctx.save(); + ctx.textAlign = setOptionValue(true,1,"INGRAPHDATAALIGN",ctx,data,statData,undefined,config.inGraphDataAlign,"inGraphDataAlign",i,j,{nullValue: true }); + ctx.textBaseline = setOptionValue(true,1,"INGRAPHDATAVALIGN",ctx,data,statData,undefined,config.inGraphDataVAlign,"inGraphDataVAlign",i,j,{nullValue : true} ); + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,j,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,j,{nullValue : true} ); + ctx.fillStyle = setOptionValue(true,1,"INGRAPHDATAFONTCOLOR",ctx,data,statData,undefined,config.inGraphDataFontColor,"inGraphDataFontColor",i,j,{nullValue : true} ); + var paddingTextX = setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGX",ctx,data,statData,undefined,config.inGraphDataPaddingX,"inGraphDataPaddingX",i,j,{nullValue : true} ), + paddingTextY = setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATAPADDINGY",ctx,data,statData,undefined,config.inGraphDataPaddingY,"inGraphDataPaddingY",i,j,{nullValue : true} ); + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,j,{nullValue : true} ), statData[i][j],config); + ctx.translate(statData[i][j].xPos + paddingTextX, statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffset - paddingTextY); + var rotateVal=setOptionValue(true,1,"INGRAPHDATAROTATE",ctx,data,statData,undefined,config.inGraphDataRotate,"inGraphDataRotate",i,j,{nullValue : true} ) * (Math.PI / 180); + ctx.rotate(rotateVal); + setTextBordersAndBackground(ctx,dispString,setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),0,0,setOptionValue(true,1,"INGRAPHDATABORDERS",ctx,data,statData,undefined,config.inGraphDataBorders,"inGraphDataBorders",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSCOLOR",ctx,data,statData,undefined,config.inGraphDataBordersColor,"inGraphDataBordersColor",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartLineScale,"INGRAPHDATABORDERSWIDTH",ctx,data,statData,undefined,config.inGraphDataBordersWidth,"inGraphDataBordersWidth",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSXSPACE",ctx,data,statData,undefined,config.inGraphDataBordersXSpace,"inGraphDataBordersXSpace",i,j,{nullValue : true} ),setOptionValue(true,ctx.chartSpaceScale,"INGRAPHDATABORDERSYSPACE",ctx,data,statData,undefined,config.inGraphDataBordersYSpace,"inGraphDataBordersYSpace",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABORDERSSTYLE",ctx,data,statData,undefined,config.inGraphDataBordersStyle,"inGraphDataBordersStyle",i,j,{nullValue : true} ),setOptionValue(true,1,"INGRAPHDATABACKGROUNDCOLOR",ctx,data,statData,undefined,config.inGraphDataBackgroundColor,"inGraphDataBackgroundColor",i,j,{nullValue : true} ),"INGRAPHDATA"); + ctx.fillTextMultiLine(dispString, 0, 0, ctx.textBaseline, setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,j,{nullValue : true} ),true,config.detectMouseOnText,ctx,"INGRAPHDATA_TEXTMOUSE",rotateVal,statData[i][j].xPos + paddingTextX, statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffset - paddingTextY,i,j); + ctx.restore(); + } + } + } + } + }; + + + function initbz(pts,xpos,ypos,i) { + if (setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )==0 && setOptionValue(true,1,"BEZIERCURVE",ctx,data,statData,undefined,config.bezierCurve,"bezierCurve",i,-1,{nullValue : true})) { + pts.length=0; + pts.push(xpos);pts.push(ypos); + } + prevypos=ypos; + } ; + + function setYposOrigin(i,j,data,statData) { + yposOrigin="undefined"; + if(typeof statData[i][j].yPosOffsetOrigin != "undefined")yposOrigin= statData[i][j].yAxisPos - currentAnimPc.mainVal * statData[i][j].yPosOffsetOrigin; + return(yposOrigin); + }; + + + function traceLine(pts,ctx,xpos,ypos,config,data,statData,i,yposOrigin) { + if (setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )==0 && setOptionValue(true,1,"BEZIERCURVE",ctx,data,statData,undefined,config.bezierCurve,"bezierCurve",i,-1,{nullValue : true})) { + pts.push(xpos); pts.push(ypos); + } else { + if(setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )==0)ctx.lineTo(xpos,ypos); + else if(setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )==1){ + if(yposOrigin != "undefined")ctx.moveTo(xpos, yposOrigin); + else ctx.moveTo(xpos, statData[i][0].xAxisPosY-statData[i][0].zeroY); +// ctx.moveTo(xpos, statData[i][0].xAxisPosY-statData[i][0].zeroY); + ctx.lineTo(xpos,ypos); + } else if (setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )==2 && typeof prevypos!="undefined"){ + ctx.lineTo(xpos,prevypos); + ctx.lineTo(xpos,ypos); + prevypos=ypos; + } + } + } ; + + function closebz(pts,ctx,config,i){ + + if(setOptionValue(true,1,"LINKTYPE",ctx,data,statData,data.datasets[i].linkType,config.linkType,"linkType",i,j,{nullvalue : null} )==0 && setOptionValue(true,1,"BEZIERCURVE",ctx,data,statData,undefined,config.bezierCurve,"bezierCurve",i,-1,{nullValue : true})) { + minimumpos= statData[i][0].xAxisPosY; + maximumpos= statData[i][0].xAxisPosY - statData[i][0].calculatedScale.steps*statData[i][0].scaleHop; + drawSpline(ctx,pts,setOptionValue(true,1,"BEZIERCURVETENSION",ctx,data,statData,undefined,config.bezierCurveTension,"bezierCurveTension",i,-1,{nullValue : true}),minimumpos,maximumpos); + pts.length=0; + } + prevypos=undefined; + }; + + //Props to Rob Spencer at scaled innovation for his post on splining between points + //http://scaledinnovation.com/analytics/splines/aboutSplines.html + + function getControlPoints(x0,y0,x1,y1,x2,y2,t){ + // x0,y0,x1,y1 are the coordinates of the end (knot) pts of this segment + // x2,y2 is the next knot -- not connected here but needed to calculate p2 + // p1 is the control point calculated here, from x1 back toward x0. + // p2 is the next control point, calculated here and returned to become the + // next segment's p1. + // t is the 'tension' which controls how far the control points spread. + + // Scaling factors: distances from this knot to the previous and following knots. + var d01=Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2)); + var d12=Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2)); + + var fa=t*d01/(d01+d12); + var fb=t-fa; + + var p1x=x1+fa*(x0-x2); + var p1y=y1+fa*(y0-y2); + + var p2x=x1-fb*(x0-x2); + var p2y=y1-fb*(y0-y2); + + return [p1x,p1y,p2x,p2y] + }; + + function drawSpline(ctx,pts,t,minimumpos,maximumpos){ + var cp=[]; // array of control points, as x0,y0,x1,y1,... + var n=pts.length; + + pts.push(2*pts[n-2]-pts[n-4]); + pts.push(2*pts[n-1]-pts[n-3]); + + if (n==4){ + ctx.moveTo(pts[0],pts[1]); + ctx.lineTo(pts[2],pts[3]); + return; + } + // Draw an open curve, not connected at the ends + for(var ti=0;ti=config.firstLabelToShow-1)doShowLabels=((i+config.firstLabelToShow-1) % parseInt(doShowLabels) ==0 ? true : false); + else doShowLabels=false; + } + return doShowLabels; +}; + +function showYLabels(ctx,data,config,i,text) { + var doShowLabels=setOptionValue(true,1,"SHOWYLABEL",ctx,data,undefined,undefined,config.showYLabels,"showYLabels",-1,i,undefined,{labelValue: text}); + if(typeof doShowLabels=="number") { + if(i>=config.firstYLabelToShow-1)doShowLabels=((i+config.firstYLabelToShow-1) % parseInt(doShowLabels) ==0 ? true : false); + else doShowLabels=false; + } + return doShowLabels; +}; + + +function drawLegend(legendMsr,data,config,ctx,typegraph) { + var lgtxt; + if (config.legendBorders == true) { + ctx.save(); + ctx.setLineDash(lineStyleFn(config.legendBordersStyle)); + ctx.beginPath(); + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.legendBordersWidth); + ctx.strokeStyle = config.legendBordersColors; + ctx.moveTo(legendMsr.xLegendBorderPos, legendMsr.yLegendBorderPos); + ctx.lineTo(legendMsr.xLegendBorderPos, legendMsr.yLegendBorderPos + legendMsr.legendBorderHeight); + ctx.lineTo(legendMsr.xLegendBorderPos + legendMsr.legendBorderWidth, legendMsr.yLegendBorderPos + legendMsr.legendBorderHeight); + ctx.lineTo(legendMsr.xLegendBorderPos + legendMsr.legendBorderWidth, legendMsr.yLegendBorderPos); + ctx.lineTo(legendMsr.xLegendBorderPos, legendMsr.yLegendBorderPos); + //ctx.lineTo(legendMsr.xLegendBorderPos + legendMsr.legendBorderWidth, legendMsr.yLegendBorderPos); + //ctx.lineTo(legendMsr.xLegendBorderPos, legendMsr.yLegendBorderPos); + //ctx.lineTo(legendMsr.xLegendBorderPos, legendMsr.yLegendBorderPos + legendMsr.legendBorderHeight); + + + ctx.stroke(); + ctx.closePath(); + ctx.setLineDash([]); + + ctx.fillStyle = "rgba(0,0,0,0)"; // config.legendFillColor; + ctx.fillStyle = config.legendFillColor; + ctx.fill(); + ctx.restore(); + } + nbcols = legendMsr.nbLegendCols - 1; + ypos = legendMsr.yFirstLegendTextPos - ((Math.ceil(ctx.chartTextScale*config.legendFontSize)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextVertical)); + xpos = 0; + if (legendMsr.drawLegendOnData) fromi = data.datasets.length; + else fromi = data.length; + for (var i = fromi - 1; i >= 0; i--) { + orderi = i; + if (legendMsr.reverseLegend) { + if (legendMsr.drawLegendOnData) orderi = data.datasets.length - i - 1; + else orderi = data.length - i - 1; + } + if (legendMsr.drawLegendOnData) tpof = typeof(data.datasets[orderi].title); + else tpof = typeof(data[orderi].title) + if (tpof == "string") { + if (legendMsr.drawLegendOnData) lgtxt = fmtChartJS(config, data.datasets[orderi].title, config.fmtLegend).trim(); + else lgtxt = fmtChartJS(config, data[orderi].title, config.fmtLegend).trim(); + if (lgtxt != "") { + nbcols++; + if (nbcols == legendMsr.nbLegendCols) { + nbcols = 0; + xpos = legendMsr.xFirstLegendTextPos; + ypos += (Math.ceil(ctx.chartTextScale*config.legendFontSize)) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextVertical); + } else { + xpos += legendMsr.widestLegend + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenTextHorizontal); + } + ctx.save(); + ctx.beginPath(); + var lgdbox=legendMsr.legendBox; + if(ctx.tpchart=="Bar" || ctx.tpchart=="StackedBar") if (data.datasets[orderi].type=="Line" && (!config.datasetFill || setOptionValue(false,1,"LINKTYPE",ctx,data,statData,data.datasets[orderi].linkType,config.linkType,"linkType",orderi,-1,{nullvalue : null} )==1)) lgdbox=false; + if (lgdbox) { + if (legendMsr.drawLegendOnData) { + ctx.lineWidth = Math.ceil(ctx.chartLineScale*setOptionValue(false,1,"LINEWIDTH",ctx,data,undefined,data.datasets[orderi].datasetStrokeWidth,config.datasetStrokeWidth,"datasetStrokeWidth",orderi,-1,{nullvalue : null} )); + } else { + ctx.lineWidth = Math.ceil(ctx.chartLineScale*config.datasetStrokeWidth); + } + ctx.beginPath(); + if (legendMsr.drawLegendOnData) { + ctx.strokeStyle=setOptionValue(false,1,"LEGENDSTROKECOLOR",ctx,data,undefined,data.datasets[orderi].strokeColor,config.defaultFillColor,"strokeColor",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ); + ctx.setLineDash(lineStyleFn(setOptionValue(false,1,"LEGENDLINEDASH",ctx,data,undefined,data.datasets[orderi].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ))); + + } else { + ctx.strokeStyle=setOptionValue(false,1,"LEGENDSTROKECOLOR",ctx,data,undefined,data[orderi].strokeColor,config.defaultFillColor,"strokeColor",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ); + ctx.setLineDash(lineStyleFn(setOptionValue(false,1,"LEGENDSEGMENTTROKESTYLE",ctx,data,undefined,data[orderi].segmentStrokeStyle,config.segmentStrokeStyle,"segmentStrokeStyle",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ))); + } + ctx.moveTo(xpos, ypos); + ctx.lineTo(xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), ypos); + ctx.lineTo(xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))); + ctx.lineTo(xpos, ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))); + ctx.lineTo(xpos, ypos); + ctx.stroke(); + ctx.closePath(); + if (legendMsr.drawLegendOnData) { + ctx.fillStyle=setOptionValue(false,1,"LEGENDFILLCOLOR",ctx,data,undefined,data.datasets[orderi].fillColor,config.defaultFillColor,"fillColor",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ); + } else { + ctx.fillStyle=setOptionValue(false,1,"LEGENDFILLCOLOR",ctx,data,undefined,data[orderi].color,config.defaultFillColor,"color",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ); + } + ctx.fill(); + } else { + ctx.lineWidth = config.legendColorIndicatorStrokeWidth ? + config.legendColorIndicatorStrokeWidth : Math.ceil(ctx.chartLineScale*setOptionValue(false,1,"LINEWIDTH",ctx,data,undefined,data.datasets[orderi].datasetStrokeWidth,config.datasetStrokeWidth,"datasetStrokeWidth",orderi,-1,{nullvalue : null} )); + if (config.legendColorIndicatorStrokeWidth && config.legendColorIndicatorStrokeWidth > (Math.ceil(ctx.chartTextScale*config.legendFontSize))) { + ctx.lineWidth = (Math.ceil(ctx.chartTextScale*config.legendFontSize)); + } + if (legendMsr.drawLegendOnData) { + ctx.strokeStyle=setOptionValue(false,1,"LEGENDSTROKECOLOR",ctx,data,undefined,data.datasets[orderi].strokeColor,config.defaultFillColor,"strokeColor",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ); + ctx.setLineDash(lineStyleFn(setOptionValue(false,1,"LEGENDLINEDASH",ctx,data,undefined,data.datasets[orderi].datasetStrokeStyle,config.datasetStrokeStyle,"datasetStrokeStyle",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ))); + } else { + ctx.strokeStyle=setOptionValue(false,1,"LEGENDSTROKECOLOR",ctx,data,undefined,data[orderi].strokeColor,config.defaultFillColor,"strokeColor",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ); + ctx.setLineDash(lineStyleFn(setOptionValue(false,1,"LEGENDSEGMENTTROKESTYLE",ctx,data,undefined,data[orderi].segmentStrokeStyle,config.segmentStrokeStyle,"segmentStrokeStyle",orderi,-1,{animationValue: 1, xPosLeft : xpos, yPosBottom : ypos, xPosRight : xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize), yPosTop : ypos - (Math.ceil(ctx.chartTextScale*config.legendFontSize))} ))); + } + + ctx.moveTo(xpos + 2, ypos - ((Math.ceil(ctx.chartTextScale*config.legendFontSize)) / 2)); + ctx.lineTo(xpos + 2 + Math.ceil(ctx.chartTextScale*config.legendBlockSize), ypos - ((Math.ceil(ctx.chartTextScale*config.legendFontSize)) / 2)); + ctx.stroke(); + + + ctx.fill(); + + if(config.pointDot) { + ctx.beginPath(); + ctx.fillStyle=setOptionValue(false,1,"LEGENDMARKERFILLCOLOR",ctx,data,undefined,data.datasets[orderi].pointColor,config.defaultStrokeColor,"pointColor",orderi,-1,{nullvalue: true} ); + ctx.strokeStyle=setOptionValue(false,1,"LEGENDMARKERSTROKESTYLE",ctx,data,undefined,data.datasets[orderi].pointStrokeColor,config.defaultStrokeColor,"pointStrokeColor",orderi,-1,{nullvalue: true} ); + ctx.lineWidth=setOptionValue(false,ctx.chartLineScale,"LEGENDMARKERLINEWIDTH",ctx,data,undefined,data.datasets[orderi].pointDotStrokeWidth,config.pointDotStrokeWidth,"pointDotStrokeWidth",orderi,-1,{nullvalue: true} ); + + var markerShape=setOptionValue(false,1,"LEGENDMARKERSHAPE",ctx,data,undefined,data.datasets[orderi].markerShape,config.markerShape,"markerShape",orderi,-1,{nullvalue: true} ); + var markerRadius=setOptionValue(false,ctx.chartSpaceScale,"LEGENDMARKERRADIUS",ctx,data,undefined,data.datasets[orderi].pointDotRadius,config.pointDotRadius,"pointDotRadius",orderi,-1,{nullvalue: true} ); + var markerStrokeStyle=setOptionValue(false,1,"LEGENDMARKERSTROKESTYLE",ctx,data,undefined,data.datasets[orderi].pointDotStrokeStyle,config.pointDotStrokeStyle,"pointDotStrokeStyle",orderi,-1,{nullvalue: true} ); + drawMarker(ctx,xpos + 2 + Math.ceil(ctx.chartTextScale*config.legendBlockSize)/2, ypos - ((Math.ceil(ctx.chartTextScale*config.legendFontSize)) / 2), markerShape,markerRadius,markerStrokeStyle); + } + ctx.fill(); + + } + ctx.restore(); + ctx.save(); + ctx.beginPath(); + ctx.font = config.legendFontStyle + " " + (Math.ceil(ctx.chartTextScale*config.legendFontSize)).toString() + "px " + config.legendFontFamily; + ctx.fillStyle = setOptionValue(false,1,"LEGENDFONTCOLOR",ctx,data,undefined,undefined,config.legendFontColor,"legendFontColor",orderi,-1,{nullvalue: true} ); + ctx.textAlign = "left"; + ctx.textBaseline = "bottom"; + ctx.translate(xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenBoxAndText), ypos); +// ctx.fillText(lgtxt, 0, 0); + ctx.fillTextMultiLine(lgtxt, 0, 0, ctx.textBaseline, Math.ceil(ctx.chartTextScale*config.legendFontSize), true,config.detectMouseOnText,ctx,"LEGEND_TEXTMOUSE",0,xpos + Math.ceil(ctx.chartTextScale*config.legendBlockSize) + Math.ceil(ctx.chartSpaceScale*config.legendSpaceBetweenBoxAndText), ypos,orderi,-1); + + ctx.restore(); + } + } + } +}; + +function drawMarker(ctx,xpos,ypos,marker,markersize,markerStrokeStyle) { + ctx.setLineDash(lineStyleFn(markerStrokeStyle)); + switch (marker) { + case "square": + ctx.rect(xpos-markersize,ypos-markersize,2*markersize,2*markersize); + ctx.stroke(); + ctx.fill(); + ctx.setLineDash([]); + break; + case "triangle": + pointA_x=0; + pointA_y=2/3*markersize; + ctx.moveTo(xpos,ypos-pointA_y); + ctx.lineTo(xpos+pointA_y*Math.sin(4/3),ypos+pointA_y*Math.cos(4/3)); + ctx.lineTo(xpos-pointA_y*Math.sin(4/3),ypos+pointA_y*Math.cos(4/3)); + ctx.lineTo(xpos,ypos-pointA_y); + ctx.stroke(); + ctx.fill(); + ctx.setLineDash([]); + break; + case "diamond": + ctx.moveTo(xpos, ypos+markersize); + ctx.lineTo(xpos+markersize, ypos); + ctx.lineTo(xpos, ypos-markersize); + ctx.lineTo(xpos-markersize, ypos); + ctx.lineTo(xpos, ypos+markersize); + ctx.stroke(); + ctx.fill(); + ctx.setLineDash([]); + break; + case "plus": + ctx.moveTo(xpos, ypos-markersize); + ctx.lineTo(xpos, ypos+markersize); + ctx.moveTo(xpos-markersize, ypos); + ctx.lineTo(xpos+markersize, ypos); + ctx.stroke(); + ctx.setLineDash([]); + break; + case "cross": + ctx.moveTo(xpos-markersize, ypos-markersize); + ctx.lineTo(xpos+markersize, ypos+markersize); + ctx.moveTo(xpos-markersize, ypos+markersize); + ctx.lineTo(xpos+markersize, ypos-markersize); + ctx.stroke(); + ctx.setLineDash([]); + break; + case "circle": + default: + ctx.arc(xpos, ypos, markersize, 0, 2*Math.PI * 1, true); + ctx.stroke(); + ctx.fill(); + ctx.setLineDash([]); + break; + } +}; + +function initPassVariableData_part1(data,config,ctx) { +var i,j,result, mxvalue ,mnvalue, cumvalue, totvalue,lmaxvalue,lminvalue,lgtxt,lgtxt2,tp,prevpos,firstNotMissingi,lastNotMissingi,firstNotMissingj,lastNotMissingj,grandtotal; +switch(ctx.tpdata) { + case 1 : + + result=[]; + var segmentAngle,cumulativeAngle,realCumulativeAngle; + + var realAmplitude = (((config.totalAmplitude * (Math.PI / 180) + 2 * Math.PI) % (2 * Math.PI)) + 2* Math.PI) % (2* Math.PI) ; + if(realAmplitude <= config.zeroValue)realAmplitude=2*Math.PI; + + cumulativeAngle = (((-config.startAngle * (Math.PI / 180) + 2 * Math.PI) % (2 * Math.PI)) + 2* Math.PI) % (2* Math.PI) ; + realCumulativeAngle = (((config.startAngle * (Math.PI / 180) + 2 * Math.PI) % (2 * Math.PI)) + 2* Math.PI) % (2* Math.PI) ; + + startAngle=cumulativeAngle; + totvalue = 0; + notemptyval=0; + var firstNotMissing = -1; + var lastNotMissing = -1; + var prevNotMissing = -1; + mxvalue=-Number.MAX_VALUE; + mnvalue=Number.MAX_VALUE; + for (i = 0; i < data.length; i++) { + if(ctx.tpchart != "PolarArea" && 1*data[i].value<0)continue; + if (!(typeof(data[i].value) == 'undefined')) { + if(firstNotMissing==-1)firstNotMissing=i; + mxvalue=Math.max(mxvalue,1*data[i].value); + mnvalue=Math.min(mnvalue,1*data[i].value); + notemptyval++; + totvalue += 1 * data[i].value; + lastNotMissing=i; + } + } + + cumvalue=0; + var prevMissing=-1; + for(i=0;i=0)) { +// if(ctx.tpchart=="PolarArea") { if(notemptyval>0)segmentAngle= (Math.PI *2)/notemptyval; else segmentAngle=0; } +// else segmentAngle = (1 * data[i].value / totvalue) * (Math.PI * 2); + if(ctx.tpchart=="PolarArea") { if(notemptyval>0)segmentAngle= realAmplitude/notemptyval; else segmentAngle=0; } + else segmentAngle = (1 * data[i].value / totvalue) * realAmplitude; + if (segmentAngle >= Math.PI * 2) segmentAngle = Math.PI * 2 - 0.001; // bug on Android when segmentAngle is >= 2*PI; + cumvalue += 1 * data[i].value; + result[i]= { + config: config, + v1: fmtChartJS(config, lgtxt, config.fmtV1), + v2: fmtChartJS(config, 1 * data[i].value, config.fmtV2), + v3: fmtChartJS(config, cumvalue, config.fmtV3), + v4: fmtChartJS(config, totvalue, config.fmtV4), + v5: fmtChartJS(config, segmentAngle, config.fmtV5), + v6: roundToWithThousands(config, fmtChartJS(config, 100 * data[i].value / totvalue, config.fmtV6), config.roundPct), + v7 : 0, + v8 : 0, + v9 : 0, + v10 : 0, + v11: fmtChartJS(config, cumulativeAngle - segmentAngle, config.fmtV11), + v12: fmtChartJS(config, cumulativeAngle, config.fmtV12), + v13: fmtChartJS(config, i, config.fmtV13), + lgtxt: lgtxt, + datavalue: 1 * data[i].value, + cumvalue: cumvalue, + totvalue: totvalue, + segmentAngle: segmentAngle, + firstAngle : startAngle, + pctvalue: 100 * data[i].value / totvalue, + startAngle: cumulativeAngle, + realStartAngle : realCumulativeAngle, + endAngle: cumulativeAngle+segmentAngle, + maxvalue : mxvalue, + minvalue : mnvalue, + i: i, + firstNotMissing : firstNotMissing, + lastNotMissing : lastNotMissing, + prevNotMissing : prevNotMissing, + prevMissing : prevMissing, + nextNotMissing : -1, + radiusOffset : 0, + midPosX : 0, + midPosY : 0, + int_radius : 0, + ext_radius : 0, + data: data + }; + cumulativeAngle += segmentAngle; + realCumulativeAngle -= segmentAngle; + if(prevNotMissing != -1) result[prevNotMissing].nextNotMissing=i; + prevNotMissing = i; + } else { + result[i]={ + v1:lgtxt, + maxvalue : mxvalue, + minvalue : mnvalue, + i: i, + firstNotMissing : firstNotMissing, + lastNotMissing : lastNotMissing, + prevNotMissing : prevNotMissing + }; + prevMissing=i; + } + } + break; + case 0: + default : + var axis; + result=[]; + mxvalue=[]; + mxvalue[0]=[]; + mxvalue[1]=[]; + mnvalue=[]; + mnvalue[0]=[]; + mnvalue[1]=[]; + cumvalue=[]; + cumvalue[0]=[]; + cumvalue[1]=[]; + totvalue=[]; + totvalue[0]=[]; + totvalue[1]=[]; + lmaxvalue=[]; + lmaxvalue[0]=[]; + lmaxvalue[1]=[]; + lminvalue=[]; + lminvalue[0]=[]; + lminvalue[1]=[]; + prevpos=[]; + firstNotMissingi=[]; + lastNotMissingi=[]; + firstNotMissingj=[]; + lastNotMissingj=[]; + prevpos[0]=[]; + prevpos[1]=[]; + grandtotal=0; + + for (i = 0; i < data.datasets.length; i++) { + // BUG when all data are missing ! + if (typeof data.datasets[i].xPos != "undefined" && tpdraw(ctx,data.datasets[i])=="Line") { + for(j=data.datasets[i].data.length;j=0){ + result[i][j].v4=fmtChartJS(config, (prevpos[axis][j] != -1 ? 1 * data.datasets[i].data[j]-result[prevpos[axis][j]][j].datavalue : 1 * data.datasets[i].data[j]), config.fmtV4); + result[i][j].diffprev=(prevpos[axis][j] != -1 ? 1 * data.datasets[i].data[j]-result[prevpos[axis][j]][j].datavalue : 1 * data.datasets[i].data[j]); + result[prevpos[axis][j]][j].diffnext=data.datasets[prevpos[axis][j]].data[j] - data.datasets[i].data[j]; + result[prevpos[axis][j]][j].v5=result[prevpos[axis][j]][j].diffnext; + } else { + result[i][j].v4=1 * data.datasets[i].data[j]; + + } + prevpos[axis][j]=i; + break; + default: + break; + } + if(!(typeof(data.datasets[i].data[j]) == 'undefined')) { + if(prevnotemptyj!= -1) {for(k=prevnotemptyj;k=config.zeroValue)statData[i][j].D2A=-(1/statData[i][j].D1A); + else statData[i][j].D2A=undefined; + } + + } + break; + case "Bar" : + for (j = 0; j < data.datasets[i].data.length; j++) { + + statData[i][j].xAxisPosY = othervars.xAxisPosY; + statData[i][j].yAxisPosX = othervars.yAxisPosX; + statData[i][j].valueHop = othervars.valueHop; + statData[i][j].barWidth = othervars.barWidth; + statData[i][j].additionalSpaceBetweenBars= othervars.additionalSpaceBetweenBars; + statData[i][j].nbValueHop = othervars.nbValueHop; + statData[i][j].calculatedScale = othervars.calculatedScale; + statData[i][j].scaleHop = othervars.scaleHop; + + statData[i][j].xPosLeft= othervars.yAxisPosX + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) + othervars.valueHop * j + othervars.additionalSpaceBetweenBars+othervars.barWidth * realbars + Math.ceil(ctx.chartSpaceScale*config.barDatasetSpacing) * realbars + Math.ceil(ctx.chartLineScale*config.barStrokeWidth) * realbars; + statData[i][j].xPosRight = statData[i][j].xPosLeft + othervars.barWidth; + statData[i][j].yPosBottom =othervars.xAxisPosY - othervars.zeroY + statData[i][j].barHeight=calculateOffset(config.logarithmic, 1 * data.datasets[i].data[j], othervars.calculatedScale, othervars.scaleHop) - othervars.zeroY; + if (data.datasets[i].axis == 2) { + statData[i][j].yPosBottom =othervars.xAxisPosY - othervars.zeroY2; + statData[i][j].barHeight=calculateOffset(config.logarithmic2, 1 * data.datasets[i].data[j], othervars.calculatedScale2, othervars.scaleHop2) - othervars.zeroY2; + } else { + statData[i][j].yPosBottom =othervars.xAxisPosY - othervars.zeroY + statData[i][j].barHeight=calculateOffset(config.logarithmic, 1 * data.datasets[i].data[j], othervars.calculatedScale, othervars.scaleHop) - othervars.zeroY; + } + statData[i][j].yPosTop = statData[i][j].yPosBottom - statData[i][j].barHeight + (Math.ceil(ctx.chartLineScale*config.barStrokeWidth) / 2); + statData[i][j].v7=statData[i][j].xPosLeft; + statData[i][j].v8=statData[i][j].yPosBottom; + statData[i][j].v9=statData[i][j].xPosRight; + statData[i][j].v10=statData[i][j].yPosTop; + + } + realbars++; + break; + case "StackedBar" : + for (j = 0; j < data.datasets[i].data.length; j++) { + statData[i][j].xAxisPosY = othervars.xAxisPosY; + statData[i][j].yAxisPosX = othervars.yAxisPosX; + statData[i][j].valueHop = othervars.valueHop; + statData[i][j].barWidth = othervars.barWidth; + statData[i][j].additionalSpaceBetweenBars= othervars.additionalSpaceBetweenBars; + statData[i][j].nbValueHop = othervars.nbValueHop; + statData[i][j].calculatedScale = othervars.calculatedScale; + statData[i][j].scaleHop = othervars.scaleHop; +// statData[i][j].nbValueHop = othervars.nbValueHop; + + if (typeof tempp[j]=="undefined") { + tempp[j]=0; + tempn[j]=0; + zeroY= calculateOffset(config.logarithmic, 0 , othervars.calculatedScale, othervars.scaleHop); + } + if ((typeof data.datasets[i].data[j] == 'undefined')) continue; + statData[i][j].xPosLeft= othervars.yAxisPosX + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) + othervars.valueHop * j+othervars.additionalSpaceBetweenBars; + if (1*data.datasets[i].data[j]<0) { + statData[i][j].botval=tempn[j]; + statData[i][j].topval=tempn[j]+1*data.datasets[i].data[j] ; + tempn[j]=tempn[j]+1*data.datasets[i].data[j] ; + } else { + statData[i][j].botval=tempp[j]; + statData[i][j].topval=tempp[j]+1*data.datasets[i].data[j] ; + tempp[j]=tempp[j]+1*data.datasets[i].data[j] ; + } + statData[i][j].xPosRight = statData[i][j].xPosLeft + othervars.barWidth; + statData[i][j].botOffset = calculateOffset(config.logarithmic, statData[i][j].botval , othervars.calculatedScale, othervars.scaleHop); + statData[i][j].topOffset = calculateOffset(config.logarithmic, statData[i][j].topval , othervars.calculatedScale, othervars.scaleHop); + statData[i][j].yPosBottom =othervars.xAxisPosY - statData[i][j].botOffset; + statData[i][j].yPosTop = othervars.xAxisPosY - statData[i][j].topOffset; + // treat spaceBetweenBar + if(Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar) > 0) + { + if(1*data.datasets[i].data[j]<0) { + statData[i][j].yPosBottom+=Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar); + if(tempn[j]==1*data.datasets[i].data[j])statData[i][j].yPosBottom-=(Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar)/2); + if(statData[i][j].yPosTop0) { + statData[i][j].yPosBottom-=Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar); + if(tempp[j]==1*data.datasets[i].data[j])statData[i][j].yPosBottom+=(Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar)/2); + if(statData[i][j].yPosTop>statData[i][j].yPosBottom)statData[i][j].yPosBottom=statData[i][j].yPosTop; + } + } + statData[i][j].v7=statData[i][j].xPosLeft; + statData[i][j].v8=statData[i][j].yPosBottom; + statData[i][j].v9=statData[i][j].xPosRight; + statData[i][j].v10=statData[i][j].yPosTop; + } + break; + case "HorizontalBar" : + for (j = 0; j < data.datasets[i].data.length; j++) { + + statData[i][j].xAxisPosY = othervars.xAxisPosY; + statData[i][j].yAxisPosX = othervars.yAxisPosX; + statData[i][j].valueHop = othervars.valueHop; + statData[i][j].barWidth = othervars.barWidth; + statData[i][j].additionalSpaceBetweenBars= othervars.additionalSpaceBetweenBars; + statData[i][j].nbValueHop = othervars.nbValueHop; + statData[i][j].calculatedScale = othervars.calculatedScale; + statData[i][j].scaleHop = othervars.scaleHop; + + + statData[i][j].xPosLeft= othervars.yAxisPosX + othervars.zeroY; + statData[i][j].yPosTop=othervars.xAxisPosY + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) - othervars.scaleHop * (j + 1) + othervars.additionalSpaceBetweenBars + othervars.barWidth * i + Math.ceil(ctx.chartSpaceScale*config.barDatasetSpacing) * i + Math.ceil(ctx.chartLineScale*config.barStrokeWidth) * i; + statData[i][j].yPosBottom=statData[i][j].yPosTop+othervars.barWidth; + statData[i][j].barWidth = calculateOffset(config.logarithmic, 1 * data.datasets[i].data[j], othervars.calculatedScale, othervars.valueHop) - othervars.zeroY; + statData[i][j].xPosRight = statData[i][j].xPosLeft + statData[i][j].barWidth; + + statData[i][j].v7=statData[i][j].xPosLeft; + statData[i][j].v8=statData[i][j].yPosBottom; + statData[i][j].v9=statData[i][j].xPosRight; + statData[i][j].v10=statData[i][j].yPosTop; + } + break; + case "HorizontalStackedBar" : + for (j = 0; j < data.datasets[i].data.length; j++) { + statData[i][j].xAxisPosY = othervars.xAxisPosY; + statData[i][j].yAxisPosX = othervars.yAxisPosX; + statData[i][j].valueHop = othervars.valueHop; + statData[i][j].barWidth = othervars.barWidth; + statData[i][j].additionalSpaceBetweenBars= othervars.additionalSpaceBetweenBars; + statData[i][j].nbValueHop = othervars.nbValueHop; + statData[i][j].calculatedScale = othervars.calculatedScale; + statData[i][j].scaleHop = othervars.scaleHop; + + if (i == 0) { + tempp[j]=0; + tempn[j]=0; + } + if (typeof(data.datasets[i].data[j]) == 'undefined') continue; +// if ((typeof(data.datasets[i].data[j]) == 'undefined') || 1*data.datasets[i].data[j] == 0 ) continue; + + statData[i][j].xPosLeft= othervars.yAxisPosX + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) + othervars.valueHop * j; + if (1*data.datasets[i].data[j]<0) { + statData[i][j].leftval=tempn[j]; + statData[i][j].rightval=tempn[j]+1*data.datasets[i].data[j] ; + tempn[j]=tempn[j]+1*data.datasets[i].data[j] ; + } else { + statData[i][j].leftval=tempp[j]; + statData[i][j].rightval=tempp[j]+1*data.datasets[i].data[j] ; + tempp[j]=tempp[j]+1*data.datasets[i].data[j] ; + } + statData[i][j].rightOffset = HorizontalCalculateOffset(statData[i][j].rightval , othervars.calculatedScale, othervars.valueHop); + statData[i][j].leftOffset = HorizontalCalculateOffset(statData[i][j].leftval , othervars.calculatedScale, othervars.valueHop); + statData[i][j].xPosRight = othervars.yAxisPosX + statData[i][j].rightOffset; + statData[i][j].xPosLeft = othervars.yAxisPosX + statData[i][j].leftOffset; + statData[i][j].yPosTop =othervars.xAxisPosY + Math.ceil(ctx.chartSpaceScale*config.barValueSpacing) - othervars.scaleHop * (j + 1) + othervars.additionalSpaceBetweenBars; + statData[i][j].yPosBottom = statData[i][j].yPosTop+othervars.barWidth; + // treat spaceBetweenBar + if(Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar) > 0) + { + if(1*data.datasets[i].data[j]<0) { + statData[i][j].xPosLeft-=Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar); + if(tempn[j]==1*data.datasets[i].data[j])statData[i][j].xPosLeft+=(Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar)/2); + if(statData[i][j].xPosLeft0) { + statData[i][j].xPosLeft+=Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar); + if(tempp[j]==1*data.datasets[i].data[j])statData[i][j].xPosLeft-=(Math.ceil(ctx.chartSpaceScale*config.spaceBetweenBar)/2); + if(statData[i][j].xPosLeft>statData[i][j].xPosRight)statData[i][j].xPosLeft=statData[i][j].xPosRight; + } + } + + statData[i][j].v7=statData[i][j].xPosLeft; + statData[i][j].v8=statData[i][j].yPosBottom; + statData[i][j].v9=statData[i][j].xPosRight; + statData[i][j].v10=statData[i][j].yPosTop; + } + break; + default : + break; + } + } + +} ; + + + + function xPos(ival, iteration, data,yAxisPosX,valueHop,nbValueHop) { +//nbValueHop=8; + if (typeof data.datasets[ival].xPos == "object") { + if (!(typeof data.datasets[ival].xPos[Math.floor(iteration + config.zeroValue)] == "undefined")) { + var width = valueHop * nbValueHop; + + var deb = (typeof data.xBegin != "undefined") ? data.xBegin : 1 * data.labels[0]; + var fin = (typeof data.xEnd != "undefined") ? data.xEnd : 1 * data.labels[data.labels.length - 1]; + if (fin <= deb) fin = deb + 100; + if (1 * data.datasets[ival].xPos[Math.floor(iteration + config.zeroValue)] >= deb && data.datasets[ival].xPos[Math.floor(iteration + config.zeroValue)] <= fin) { + var p1 = yAxisPosX + width * ((1 * data.datasets[ival].xPos[Math.floor(iteration + config.zeroValue)] - deb) / (fin - deb)); + var p2 = p1; + if (Math.abs(iteration - Math.floor(iteration + config.zeroValue)) > config.zeroValue) { + p2 = xPos(ival, Math.ceil(iteration - config.zeroValue), data); + } + return p1 + (iteration - Math.floor(iteration + config.zeroValue)) * (p2 - p1); + } + } + } + return yAxisPosX + (valueHop * iteration); + }; + + + function calculateOrderOfMagnitude(val) { + return Math.floor(Math.log(val) / Math.LN10); + }; + + function calculateOffset(logarithmic, val, calculatedScale, scaleHop) { + if (!logarithmic) { // no logarithmic scale + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue / outerValue, 1, 0); + return (scaleHop * calculatedScale.steps) * scalingFactor; + } else { // logarithmic scale +// return CapValue(log10(val) * scaleHop - calculateOrderOfMagnitude(calculatedScale.graphMin) * scaleHop, undefined, 0); + return CapValue(log10(val) * scaleHop - log10(calculatedScale.graphMin) * scaleHop, undefined, 0); + } + }; + + function HorizontalCalculateOffset(val, calculatedScale, scaleHop) { + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue / outerValue, 1, 0); + return (scaleHop * calculatedScale.steps) * scalingFactor; + }; + + //Apply cap a value at a high or low number + function CapValue(valueToCap, maxValue, minValue) { + if (isNumber(maxValue)) { + if (valueToCap > maxValue) { + return maxValue; + } + } + if (isNumber(minValue)) { + if (valueToCap < minValue) { + return minValue; + } + } + return valueToCap; + }; + function log10(val) { + return Math.log(val) / Math.LN10; + }; +}; + +function isBooleanOptionTrue(optionVar,defaultvalue) { + var j; + if(typeof optionvar == "undefined") { + if(typeof defaultvalue=="function") return true; + else if(typeof defaultvalue == "object") { + for(j=0;j=0;t--){ + if(typeof data.special[t].posi == "undefined" || typeof data.special[t].posj == "undefined") continue; + if(data.special[t].posi!=posi || data.special[t].posj != posj || posi==-1 || posj==-1) continue; + if(typeof data.special[t][varname]!="undefined") { + return data.special[t][varname]; + } + + } + } + } else { + if(typeof (data[0].special)=="object") { + for(var t=0;t=0) { + ctx.font = setOptionValue(true,1,"INGRAPHDATAFONTSTYLE",ctx,data,statData,undefined,config.inGraphDataFontStyle,"inGraphDataFontStyle",i,-1,{nullValue : true} ) + ' ' + setOptionValue(true,ctx.chartTextScale,"INGRAPHDATAFONTSIZE",ctx,data,statData,undefined,config.inGraphDataFontSize,"inGraphDataFontSize",i,-1,{nullValue : true} ) + 'px ' + setOptionValue(true,1,"INGRAPHDATAFONTFAMILY",ctx,data,statData,undefined,config.inGraphDataFontFamily,"inGraphDataFontFamily",i,-1,{nullValue : true} ); + if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 1) posAngle = realCumulativeAngle + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + else if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 2) posAngle = realCumulativeAngle - statData[i].segmentAngle / 2 + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + else if (setOptionValue(true,1,"INGRAPHDATAANGLEPOSITION",ctx,data,statData,undefined,config.inGraphDataAnglePosition,"inGraphDataAnglePosition",i,-1,{nullValue : true} ) == 3) posAngle = realCumulativeAngle - statData[i].segmentAngle + setOptionValue(true,1,"INGRAPHDATAPADDINANGLE",ctx,data,statData,undefined,config.inGraphDataPaddingAngle,"inGraphDataPaddingAngle",i,-1,{nullValue: true }) * (Math.PI / 180); + realCumulativeAngle -= statData[i].segmentAngle; + var dispString = tmplbis(setOptionValue(true,1,"INGRAPHDATATMPL",ctx,data,statData,undefined,config.inGraphDataTmpl,"inGraphDataTmpl",i,-1,{nullValue : true} ), statData[i],config); + var textMeasurement = ctx.measureText(dispString).width; + var MaxRadiusX = Math.abs((realAvailableWidth - textMeasurement) / Math.cos(posAngle)) - setOptionValue(true,1,"INGRAPHDATAPADDINGRADIUS",ctx,data,statData,undefined,config.inGraphDataPaddingRadius,"inGraphDataPaddingRadius",i,-1,{nullValue: true} ) - 5; + if (MaxRadiusX < doughnutRadius) doughnutRadius = MaxRadiusX; + } + } + } + doughnutRadius = doughnutRadius * config.radiusScale; + return { + radius : doughnutRadius, + midPieX : midPieX, + midPieY : midPieY + }; +}; + diff --git a/js/libs/jquery-ui.js b/js/libs/jquery-ui.js new file mode 100644 index 0000000..31ee9cd --- /dev/null +++ b/js/libs/jquery-ui.js @@ -0,0 +1,16617 @@ +/*! jQuery UI - v1.11.4 - 2015-03-11 +* http://jqueryui.com +* Includes: core.js, widget.js, mouse.js, position.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, draggable.js, droppable.js, effect.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js, menu.js, progressbar.js, resizable.js, selectable.js, selectmenu.js, slider.js, sortable.js, spinner.js, tabs.js, tooltip.js +* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ + +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define([ "jquery" ], factory ); + } else { + + // Browser globals + factory( jQuery ); + } +}(function( $ ) { +/*! + * jQuery UI Core 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/category/ui-core/ + */ + + +// $.ui might exist from components with no dependencies, e.g., $.ui.position +$.ui = $.ui || {}; + +$.extend( $.ui, { + version: "1.11.4", + + keyCode: { + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 + } +}); + +// plugins +$.fn.extend({ + scrollParent: function( includeHidden ) { + var position = this.css( "position" ), + excludeStaticParent = position === "absolute", + overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, + scrollParent = this.parents().filter( function() { + var parent = $( this ); + if ( excludeStaticParent && parent.css( "position" ) === "static" ) { + return false; + } + return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) ); + }).eq( 0 ); + + return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent; + }, + + uniqueId: (function() { + var uuid = 0; + + return function() { + return this.each(function() { + if ( !this.id ) { + this.id = "ui-id-" + ( ++uuid ); + } + }); + }; + })(), + + removeUniqueId: function() { + return this.each(function() { + if ( /^ui-id-\d+$/.test( this.id ) ) { + $( this ).removeAttr( "id" ); + } + }); + } +}); + +// selectors +function focusable( element, isTabIndexNotNaN ) { + var map, mapName, img, + nodeName = element.nodeName.toLowerCase(); + if ( "area" === nodeName ) { + map = element.parentNode; + mapName = map.name; + if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { + return false; + } + img = $( "img[usemap='#" + mapName + "']" )[ 0 ]; + return !!img && visible( img ); + } + return ( /^(input|select|textarea|button|object)$/.test( nodeName ) ? + !element.disabled : + "a" === nodeName ? + element.href || isTabIndexNotNaN : + isTabIndexNotNaN) && + // the element and all of its ancestors must be visible + visible( element ); +} + +function visible( element ) { + return $.expr.filters.visible( element ) && + !$( element ).parents().addBack().filter(function() { + return $.css( this, "visibility" ) === "hidden"; + }).length; +} + +$.extend( $.expr[ ":" ], { + data: $.expr.createPseudo ? + $.expr.createPseudo(function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + }) : + // support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + }, + + focusable: function( element ) { + return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); + }, + + tabbable: function( element ) { + var tabIndex = $.attr( element, "tabindex" ), + isTabIndexNaN = isNaN( tabIndex ); + return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); + } +}); + +// support: jQuery <1.8 +if ( !$( "" ).outerWidth( 1 ).jquery ) { + $.each( [ "Width", "Height" ], function( i, name ) { + var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], + type = name.toLowerCase(), + orig = { + innerWidth: $.fn.innerWidth, + innerHeight: $.fn.innerHeight, + outerWidth: $.fn.outerWidth, + outerHeight: $.fn.outerHeight + }; + + function reduce( elem, size, border, margin ) { + $.each( side, function() { + size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; + if ( border ) { + size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; + } + if ( margin ) { + size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; + } + }); + return size; + } + + $.fn[ "inner" + name ] = function( size ) { + if ( size === undefined ) { + return orig[ "inner" + name ].call( this ); + } + + return this.each(function() { + $( this ).css( type, reduce( this, size ) + "px" ); + }); + }; + + $.fn[ "outer" + name] = function( size, margin ) { + if ( typeof size !== "number" ) { + return orig[ "outer" + name ].call( this, size ); + } + + return this.each(function() { + $( this).css( type, reduce( this, size, true, margin ) + "px" ); + }); + }; + }); +} + +// support: jQuery <1.8 +if ( !$.fn.addBack ) { + $.fn.addBack = function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + }; +} + +// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) +if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { + $.fn.removeData = (function( removeData ) { + return function( key ) { + if ( arguments.length ) { + return removeData.call( this, $.camelCase( key ) ); + } else { + return removeData.call( this ); + } + }; + })( $.fn.removeData ); +} + +// deprecated +$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +$.fn.extend({ + focus: (function( orig ) { + return function( delay, fn ) { + return typeof delay === "number" ? + this.each(function() { + var elem = this; + setTimeout(function() { + $( elem ).focus(); + if ( fn ) { + fn.call( elem ); + } + }, delay ); + }) : + orig.apply( this, arguments ); + }; + })( $.fn.focus ), + + disableSelection: (function() { + var eventType = "onselectstart" in document.createElement( "div" ) ? + "selectstart" : + "mousedown"; + + return function() { + return this.bind( eventType + ".ui-disableSelection", function( event ) { + event.preventDefault(); + }); + }; + })(), + + enableSelection: function() { + return this.unbind( ".ui-disableSelection" ); + }, + + zIndex: function( zIndex ) { + if ( zIndex !== undefined ) { + return this.css( "zIndex", zIndex ); + } + + if ( this.length ) { + var elem = $( this[ 0 ] ), position, value; + while ( elem.length && elem[ 0 ] !== document ) { + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + //
+ value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } + } + + return 0; + } +}); + +// $.ui.plugin is deprecated. Use $.widget() extensions instead. +$.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; + + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } +}; + + +/*! + * jQuery UI Widget 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/jQuery.widget/ + */ + + +var widget_uuid = 0, + widget_slice = Array.prototype.slice; + +$.cleanData = (function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; (elem = elems[i]) != null; i++ ) { + try { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + + // http://bugs.jquery.com/ticket/8235 + } catch ( e ) {} + } + orig( elems ); + }; +})( $.cleanData ); + +$.widget = function( name, base, prototype ) { + var fullName, existingConstructor, constructor, basePrototype, + // proxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + proxiedPrototype = {}, + namespace = name.split( "." )[ 0 ]; + + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); + + return constructor; +}; + +$.widget.extend = function( target ) { + var input = widget_slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = widget_slice.call( arguments, 1 ), + returnValue = this; + + if ( isMethodCall ) { + this.each(function() { + var methodValue, + instance = $.data( this, fullName ); + if ( options === "instance" ) { + returnValue = instance; + return false; + } + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + }); + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat(args) ); + } + + this.each(function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + $.data( this, fullName, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = widget_uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( arguments.length === 1 ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( arguments.length === 1 ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled", !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + } + + return this; + }, + + enable: function() { + return this._setOptions({ disabled: false }); + }, + disable: function() { + return this._setOptions({ disabled: true }); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^([\w:-]*)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +var widget = $.widget; + + +/*! + * jQuery UI Mouse 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/mouse/ + */ + + +var mouseHandled = false; +$( document ).mouseup( function() { + mouseHandled = false; +}); + +var mouse = $.widget("ui.mouse", { + version: "1.11.4", + options: { + cancel: "input,textarea,button,select,option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .bind("mousedown." + this.widgetName, function(event) { + return that._mouseDown(event); + }) + .bind("click." + this.widgetName, function(event) { + if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { + $.removeData(event.target, that.widgetName + ".preventClickEvent"); + event.stopImmediatePropagation(); + return false; + } + }); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.unbind("." + this.widgetName); + if ( this._mouseMoveDelegate ) { + this.document + .unbind("mousemove." + this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup." + this.widgetName, this._mouseUpDelegate); + } + }, + + _mouseDown: function(event) { + // don't let more than one widget handle mouseStart + if ( mouseHandled ) { + return; + } + + this._mouseMoved = false; + + // we may have missed mouseup (out of window) + (this._mouseStarted && this._mouseUp(event)); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = (event.which === 1), + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); + if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if (!this.mouseDelayMet) { + this._mouseDelayTimer = setTimeout(function() { + that.mouseDelayMet = true; + }, this.options.delay); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = (this._mouseStart(event) !== false); + if (!this._mouseStarted) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { + $.removeData(event.target, this.widgetName + ".preventClickEvent"); + } + + // these delegates are required to keep context + this._mouseMoveDelegate = function(event) { + return that._mouseMove(event); + }; + this._mouseUpDelegate = function(event) { + return that._mouseUp(event); + }; + + this.document + .bind( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .bind( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function(event) { + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { + return this._mouseUp(event); + + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + return this._mouseUp( event ); + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; + } + + if (this._mouseStarted) { + this._mouseDrag(event); + return event.preventDefault(); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = + (this._mouseStart(this._mouseDownEvent, event) !== false); + (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); + } + + return !this._mouseStarted; + }, + + _mouseUp: function(event) { + this.document + .unbind( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .unbind( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + if (this._mouseStarted) { + this._mouseStarted = false; + + if (event.target === this._mouseDownEvent.target) { + $.data(event.target, this.widgetName + ".preventClickEvent", true); + } + + this._mouseStop(event); + } + + mouseHandled = false; + return false; + }, + + _mouseDistanceMet: function(event) { + return (Math.max( + Math.abs(this._mouseDownEvent.pageX - event.pageX), + Math.abs(this._mouseDownEvent.pageY - event.pageY) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function(/* event */) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function(/* event */) {}, + _mouseDrag: function(/* event */) {}, + _mouseStop: function(/* event */) {}, + _mouseCapture: function(/* event */) { return true; } +}); + + +/*! + * jQuery UI Position 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/position/ + */ + +(function() { + +$.ui = $.ui || {}; + +var cachedScrollbarWidth, supportsOffsetFractions, + max = Math.max, + abs = Math.abs, + round = Math.round, + rhorizontal = /left|center|right/, + rvertical = /top|center|bottom/, + roffset = /[\+\-]\d+(\.[\d]+)?%?/, + rposition = /^\w+/, + rpercent = /%$/, + _position = $.fn.position; + +function getOffsets( offsets, width, height ) { + return [ + parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), + parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) + ]; +} + +function parseCss( element, property ) { + return parseInt( $.css( element, property ), 10 ) || 0; +} + +function getDimensions( elem ) { + var raw = elem[0]; + if ( raw.nodeType === 9 ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: 0, left: 0 } + }; + } + if ( $.isWindow( raw ) ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: elem.scrollTop(), left: elem.scrollLeft() } + }; + } + if ( raw.preventDefault ) { + return { + width: 0, + height: 0, + offset: { top: raw.pageY, left: raw.pageX } + }; + } + return { + width: elem.outerWidth(), + height: elem.outerHeight(), + offset: elem.offset() + }; +} + +$.position = { + scrollbarWidth: function() { + if ( cachedScrollbarWidth !== undefined ) { + return cachedScrollbarWidth; + } + var w1, w2, + div = $( "
" ), + innerDiv = div.children()[0]; + + $( "body" ).append( div ); + w1 = innerDiv.offsetWidth; + div.css( "overflow", "scroll" ); + + w2 = innerDiv.offsetWidth; + + if ( w1 === w2 ) { + w2 = div[0].clientWidth; + } + + div.remove(); + + return (cachedScrollbarWidth = w1 - w2); + }, + getScrollInfo: function( within ) { + var overflowX = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-x" ), + overflowY = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-y" ), + hasOverflowX = overflowX === "scroll" || + ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), + hasOverflowY = overflowY === "scroll" || + ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); + return { + width: hasOverflowY ? $.position.scrollbarWidth() : 0, + height: hasOverflowX ? $.position.scrollbarWidth() : 0 + }; + }, + getWithinInfo: function( element ) { + var withinElement = $( element || window ), + isWindow = $.isWindow( withinElement[0] ), + isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9; + return { + element: withinElement, + isWindow: isWindow, + isDocument: isDocument, + offset: withinElement.offset() || { left: 0, top: 0 }, + scrollLeft: withinElement.scrollLeft(), + scrollTop: withinElement.scrollTop(), + + // support: jQuery 1.6.x + // jQuery 1.6 doesn't support .outerWidth/Height() on documents or windows + width: isWindow || isDocument ? withinElement.width() : withinElement.outerWidth(), + height: isWindow || isDocument ? withinElement.height() : withinElement.outerHeight() + }; + } +}; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, + target = $( options.of ), + within = $.position.getWithinInfo( options.within ), + scrollInfo = $.position.getScrollInfo( within ), + collision = ( options.collision || "flip" ).split( " " ), + offsets = {}; + + dimensions = getDimensions( target ); + if ( target[0].preventDefault ) { + // force left top to allow flipping + options.at = "left top"; + } + targetWidth = dimensions.width; + targetHeight = dimensions.height; + targetOffset = dimensions.offset; + // clone to reuse original targetOffset later + basePosition = $.extend( {}, targetOffset ); + + // force my and at to have valid horizontal and vertical positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[ this ] || "" ).split( " " ), + horizontalOffset, + verticalOffset; + + if ( pos.length === 1) { + pos = rhorizontal.test( pos[ 0 ] ) ? + pos.concat( [ "center" ] ) : + rvertical.test( pos[ 0 ] ) ? + [ "center" ].concat( pos ) : + [ "center", "center" ]; + } + pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; + pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; + + // calculate offsets + horizontalOffset = roffset.exec( pos[ 0 ] ); + verticalOffset = roffset.exec( pos[ 1 ] ); + offsets[ this ] = [ + horizontalOffset ? horizontalOffset[ 0 ] : 0, + verticalOffset ? verticalOffset[ 0 ] : 0 + ]; + + // reduce to just the positions without the offsets + options[ this ] = [ + rposition.exec( pos[ 0 ] )[ 0 ], + rposition.exec( pos[ 1 ] )[ 0 ] + ]; + }); + + // normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + if ( options.at[ 0 ] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[ 0 ] === "center" ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[ 1 ] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[ 1 ] === "center" ) { + basePosition.top += targetHeight / 2; + } + + atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); + basePosition.left += atOffset[ 0 ]; + basePosition.top += atOffset[ 1 ]; + + return this.each(function() { + var collisionPosition, using, + elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseCss( this, "marginLeft" ), + marginTop = parseCss( this, "marginTop" ), + collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, + collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, + position = $.extend( {}, basePosition ), + myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); + + if ( options.my[ 0 ] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[ 0 ] === "center" ) { + position.left -= elemWidth / 2; + } + + if ( options.my[ 1 ] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[ 1 ] === "center" ) { + position.top -= elemHeight / 2; + } + + position.left += myOffset[ 0 ]; + position.top += myOffset[ 1 ]; + + // if the browser doesn't support fractions, then round for consistent results + if ( !supportsOffsetFractions ) { + position.left = round( position.left ); + position.top = round( position.top ); + } + + collisionPosition = { + marginLeft: marginLeft, + marginTop: marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[ i ] ] ) { + $.ui.position[ collision[ i ] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], + my: options.my, + at: options.at, + within: within, + elem: elem + }); + } + }); + + if ( options.using ) { + // adds feedback as second argument to using callback, if present + using = function( props ) { + var left = targetOffset.left - position.left, + right = left + targetWidth - elemWidth, + top = targetOffset.top - position.top, + bottom = top + targetHeight - elemHeight, + feedback = { + target: { + element: target, + left: targetOffset.left, + top: targetOffset.top, + width: targetWidth, + height: targetHeight + }, + element: { + element: elem, + left: position.left, + top: position.top, + width: elemWidth, + height: elemHeight + }, + horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", + vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" + }; + if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { + feedback.horizontal = "center"; + } + if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { + feedback.vertical = "middle"; + } + if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { + feedback.important = "horizontal"; + } else { + feedback.important = "vertical"; + } + options.using.call( this, props, feedback ); + }; + } + + elem.offset( $.extend( position, { using: using } ) ); + }); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, + outerWidth = within.width, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight; + + // element is wider than within + if ( data.collisionWidth > outerWidth ) { + // element is initially over the left side of within + if ( overLeft > 0 && overRight <= 0 ) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; + position.left += overLeft - newOverRight; + // element is initially over right side of within + } else if ( overRight > 0 && overLeft <= 0 ) { + position.left = withinOffset; + // element is initially over both left and right sides of within + } else { + if ( overLeft > overRight ) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + // too far left -> align with left edge + } else if ( overLeft > 0 ) { + position.left += overLeft; + // too far right -> align with right edge + } else if ( overRight > 0 ) { + position.left -= overRight; + // adjust based on position and margin + } else { + position.left = max( position.left - collisionPosLeft, position.left ); + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollTop : within.offset.top, + outerHeight = data.within.height, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverBottom; + + // element is taller than within + if ( data.collisionHeight > outerHeight ) { + // element is initially over the top of within + if ( overTop > 0 && overBottom <= 0 ) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; + position.top += overTop - newOverBottom; + // element is initially over bottom of within + } else if ( overBottom > 0 && overTop <= 0 ) { + position.top = withinOffset; + // element is initially over both top and bottom of within + } else { + if ( overTop > overBottom ) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + // too far up -> align with top + } else if ( overTop > 0 ) { + position.top += overTop; + // too far down -> align with bottom edge + } else if ( overBottom > 0 ) { + position.top -= overBottom; + // adjust based on position and margin + } else { + position.top = max( position.top - collisionPosTop, position.top ); + } + } + }, + flip: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.offset.left + within.scrollLeft, + outerWidth = within.width, + offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - offsetLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + data.at[ 0 ] === "right" ? + -data.targetWidth : + 0, + offset = -2 * data.offset[ 0 ], + newOverRight, + newOverLeft; + + if ( overLeft < 0 ) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; + if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { + position.left += myOffset + atOffset + offset; + } + } else if ( overRight > 0 ) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; + if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { + position.left += myOffset + atOffset + offset; + } + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.offset.top + within.scrollTop, + outerHeight = within.height, + offsetTop = within.isWindow ? within.scrollTop : within.offset.top, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - offsetTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, + top = data.my[ 1 ] === "top", + myOffset = top ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + data.at[ 1 ] === "bottom" ? + -data.targetHeight : + 0, + offset = -2 * data.offset[ 1 ], + newOverTop, + newOverBottom; + if ( overTop < 0 ) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; + if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { + position.top += myOffset + atOffset + offset; + } + } else if ( overBottom > 0 ) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; + if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { + position.top += myOffset + atOffset + offset; + } + } + } + }, + flipfit: { + left: function() { + $.ui.position.flip.left.apply( this, arguments ); + $.ui.position.fit.left.apply( this, arguments ); + }, + top: function() { + $.ui.position.flip.top.apply( this, arguments ); + $.ui.position.fit.top.apply( this, arguments ); + } + } +}; + +// fraction support test +(function() { + var testElement, testElementParent, testElementStyle, offsetLeft, i, + body = document.getElementsByTagName( "body" )[ 0 ], + div = document.createElement( "div" ); + + //Create a "fake body" for testing based on method used in jQuery.support + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if ( body ) { + $.extend( testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for ( i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || document.documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + + div.style.cssText = "position: absolute; left: 10.7432222px;"; + + offsetLeft = $( div ).offset().left; + supportsOffsetFractions = offsetLeft > 10 && offsetLeft < 11; + + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); +})(); + +})(); + +var position = $.ui.position; + + +/*! + * jQuery UI Accordion 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/accordion/ + */ + + +var accordion = $.widget( "ui.accordion", { + version: "1.11.4", + options: { + active: 0, + animate: {}, + collapsible: false, + event: "click", + header: "> li > :first-child,> :not(li):even", + heightStyle: "auto", + icons: { + activeHeader: "ui-icon-triangle-1-s", + header: "ui-icon-triangle-1-e" + }, + + // callbacks + activate: null, + beforeActivate: null + }, + + hideProps: { + borderTopWidth: "hide", + borderBottomWidth: "hide", + paddingTop: "hide", + paddingBottom: "hide", + height: "hide" + }, + + showProps: { + borderTopWidth: "show", + borderBottomWidth: "show", + paddingTop: "show", + paddingBottom: "show", + height: "show" + }, + + _create: function() { + var options = this.options; + this.prevShow = this.prevHide = $(); + this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) + // ARIA + .attr( "role", "tablist" ); + + // don't allow collapsible: false and active: false / null + if ( !options.collapsible && (options.active === false || options.active == null) ) { + options.active = 0; + } + + this._processPanels(); + // handle negative values + if ( options.active < 0 ) { + options.active += this.headers.length; + } + this._refresh(); + }, + + _getCreateEventData: function() { + return { + header: this.active, + panel: !this.active.length ? $() : this.active.next() + }; + }, + + _createIcons: function() { + var icons = this.options.icons; + if ( icons ) { + $( "" ) + .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) + .prependTo( this.headers ); + this.active.children( ".ui-accordion-header-icon" ) + .removeClass( icons.header ) + .addClass( icons.activeHeader ); + this.headers.addClass( "ui-accordion-icons" ); + } + }, + + _destroyIcons: function() { + this.headers + .removeClass( "ui-accordion-icons" ) + .children( ".ui-accordion-header-icon" ) + .remove(); + }, + + _destroy: function() { + var contents; + + // clean up main element + this.element + .removeClass( "ui-accordion ui-widget ui-helper-reset" ) + .removeAttr( "role" ); + + // clean up headers + this.headers + .removeClass( "ui-accordion-header ui-accordion-header-active ui-state-default " + + "ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) + .removeAttr( "role" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-selected" ) + .removeAttr( "aria-controls" ) + .removeAttr( "tabIndex" ) + .removeUniqueId(); + + this._destroyIcons(); + + // clean up content panels + contents = this.headers.next() + .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom " + + "ui-accordion-content ui-accordion-content-active ui-state-disabled" ) + .css( "display", "" ) + .removeAttr( "role" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-labelledby" ) + .removeUniqueId(); + + if ( this.options.heightStyle !== "content" ) { + contents.css( "height", "" ); + } + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + if ( key === "event" ) { + if ( this.options.event ) { + this._off( this.headers, this.options.event ); + } + this._setupEvents( value ); + } + + this._super( key, value ); + + // setting collapsible: false while collapsed; open first panel + if ( key === "collapsible" && !value && this.options.active === false ) { + this._activate( 0 ); + } + + if ( key === "icons" ) { + this._destroyIcons(); + if ( value ) { + this._createIcons(); + } + } + + // #5332 - opacity doesn't cascade to positioned elements in IE + // so we need to add the disabled class to the headers and panels + if ( key === "disabled" ) { + this.element + .toggleClass( "ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + this.headers.add( this.headers.next() ) + .toggleClass( "ui-state-disabled", !!value ); + } + }, + + _keydown: function( event ) { + if ( event.altKey || event.ctrlKey ) { + return; + } + + var keyCode = $.ui.keyCode, + length = this.headers.length, + currentIndex = this.headers.index( event.target ), + toFocus = false; + + switch ( event.keyCode ) { + case keyCode.RIGHT: + case keyCode.DOWN: + toFocus = this.headers[ ( currentIndex + 1 ) % length ]; + break; + case keyCode.LEFT: + case keyCode.UP: + toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; + break; + case keyCode.SPACE: + case keyCode.ENTER: + this._eventHandler( event ); + break; + case keyCode.HOME: + toFocus = this.headers[ 0 ]; + break; + case keyCode.END: + toFocus = this.headers[ length - 1 ]; + break; + } + + if ( toFocus ) { + $( event.target ).attr( "tabIndex", -1 ); + $( toFocus ).attr( "tabIndex", 0 ); + toFocus.focus(); + event.preventDefault(); + } + }, + + _panelKeyDown: function( event ) { + if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { + $( event.currentTarget ).prev().focus(); + } + }, + + refresh: function() { + var options = this.options; + this._processPanels(); + + // was collapsed or no panel + if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { + options.active = false; + this.active = $(); + // active false only when collapsible is true + } else if ( options.active === false ) { + this._activate( 0 ); + // was active, but active panel is gone + } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + // all remaining panel are disabled + if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { + options.active = false; + this.active = $(); + // activate previous panel + } else { + this._activate( Math.max( 0, options.active - 1 ) ); + } + // was active, active panel still exists + } else { + // make sure active index is correct + options.active = this.headers.index( this.active ); + } + + this._destroyIcons(); + + this._refresh(); + }, + + _processPanels: function() { + var prevHeaders = this.headers, + prevPanels = this.panels; + + this.headers = this.element.find( this.options.header ) + .addClass( "ui-accordion-header ui-state-default ui-corner-all" ); + + this.panels = this.headers.next() + .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) + .filter( ":not(.ui-accordion-content-active)" ) + .hide(); + + // Avoid memory leaks (#10056) + if ( prevPanels ) { + this._off( prevHeaders.not( this.headers ) ); + this._off( prevPanels.not( this.panels ) ); + } + }, + + _refresh: function() { + var maxHeight, + options = this.options, + heightStyle = options.heightStyle, + parent = this.element.parent(); + + this.active = this._findActive( options.active ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) + .removeClass( "ui-corner-all" ); + this.active.next() + .addClass( "ui-accordion-content-active" ) + .show(); + + this.headers + .attr( "role", "tab" ) + .each(function() { + var header = $( this ), + headerId = header.uniqueId().attr( "id" ), + panel = header.next(), + panelId = panel.uniqueId().attr( "id" ); + header.attr( "aria-controls", panelId ); + panel.attr( "aria-labelledby", headerId ); + }) + .next() + .attr( "role", "tabpanel" ); + + this.headers + .not( this.active ) + .attr({ + "aria-selected": "false", + "aria-expanded": "false", + tabIndex: -1 + }) + .next() + .attr({ + "aria-hidden": "true" + }) + .hide(); + + // make sure at least one header is in the tab order + if ( !this.active.length ) { + this.headers.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active.attr({ + "aria-selected": "true", + "aria-expanded": "true", + tabIndex: 0 + }) + .next() + .attr({ + "aria-hidden": "false" + }); + } + + this._createIcons(); + + this._setupEvents( options.event ); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + this.element.siblings( ":visible" ).each(function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + }); + + this.headers.each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); + + this.headers.next() + .each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.headers.next() + .each(function() { + maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); + }) + .height( maxHeight ); + } + }, + + _activate: function( index ) { + var active = this._findActive( index )[ 0 ]; + + // trying to activate the already active panel + if ( active === this.active[ 0 ] ) { + return; + } + + // trying to collapse, simulate a click on the currently active header + active = active || this.active[ 0 ]; + + this._eventHandler({ + target: active, + currentTarget: active, + preventDefault: $.noop + }); + }, + + _findActive: function( selector ) { + return typeof selector === "number" ? this.headers.eq( selector ) : $(); + }, + + _setupEvents: function( event ) { + var events = { + keydown: "_keydown" + }; + if ( event ) { + $.each( event.split( " " ), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); + } + + this._off( this.headers.add( this.headers.next() ) ); + this._on( this.headers, events ); + this._on( this.headers.next(), { keydown: "_panelKeyDown" }); + this._hoverable( this.headers ); + this._focusable( this.headers ); + }, + + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + clicked = $( event.currentTarget ), + clickedIsActive = clicked[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : clicked.next(), + toHide = active.next(), + eventData = { + oldHeader: active, + oldPanel: toHide, + newHeader: collapsing ? $() : clicked, + newPanel: toShow + }; + + event.preventDefault(); + + if ( + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.headers.index( clicked ); + + // when the call to ._toggle() comes after the class changes + // it causes a very odd bug in IE 8 (see #6720) + this.active = clickedIsActive ? $() : clicked; + this._toggle( eventData ); + + // switch classes + // corner classes on the previously active header stay after the animation + active.removeClass( "ui-accordion-header-active ui-state-active" ); + if ( options.icons ) { + active.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.activeHeader ) + .addClass( options.icons.header ); + } + + if ( !clickedIsActive ) { + clicked + .removeClass( "ui-corner-all" ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); + if ( options.icons ) { + clicked.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.header ) + .addClass( options.icons.activeHeader ); + } + + clicked + .next() + .addClass( "ui-accordion-content-active" ); + } + }, + + _toggle: function( data ) { + var toShow = data.newPanel, + toHide = this.prevShow.length ? this.prevShow : data.oldPanel; + + // handle activating a panel during the animation for another activation + this.prevShow.add( this.prevHide ).stop( true, true ); + this.prevShow = toShow; + this.prevHide = toHide; + + if ( this.options.animate ) { + this._animate( toShow, toHide, data ); + } else { + toHide.hide(); + toShow.show(); + this._toggleComplete( data ); + } + + toHide.attr({ + "aria-hidden": "true" + }); + toHide.prev().attr({ + "aria-selected": "false", + "aria-expanded": "false" + }); + // if we're switching panels, remove the old header from the tab order + // if we're opening from collapsed state, remove the previous header from the tab order + // if we're collapsing, then keep the collapsing header in the tab order + if ( toShow.length && toHide.length ) { + toHide.prev().attr({ + "tabIndex": -1, + "aria-expanded": "false" + }); + } else if ( toShow.length ) { + this.headers.filter(function() { + return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0; + }) + .attr( "tabIndex", -1 ); + } + + toShow + .attr( "aria-hidden", "false" ) + .prev() + .attr({ + "aria-selected": "true", + "aria-expanded": "true", + tabIndex: 0 + }); + }, + + _animate: function( toShow, toHide, data ) { + var total, easing, duration, + that = this, + adjust = 0, + boxSizing = toShow.css( "box-sizing" ), + down = toShow.length && + ( !toHide.length || ( toShow.index() < toHide.index() ) ), + animate = this.options.animate || {}, + options = down && animate.down || animate, + complete = function() { + that._toggleComplete( data ); + }; + + if ( typeof options === "number" ) { + duration = options; + } + if ( typeof options === "string" ) { + easing = options; + } + // fall back from options to animation in case of partial down settings + easing = easing || options.easing || animate.easing; + duration = duration || options.duration || animate.duration; + + if ( !toHide.length ) { + return toShow.animate( this.showProps, duration, easing, complete ); + } + if ( !toShow.length ) { + return toHide.animate( this.hideProps, duration, easing, complete ); + } + + total = toShow.show().outerHeight(); + toHide.animate( this.hideProps, { + duration: duration, + easing: easing, + step: function( now, fx ) { + fx.now = Math.round( now ); + } + }); + toShow + .hide() + .animate( this.showProps, { + duration: duration, + easing: easing, + complete: complete, + step: function( now, fx ) { + fx.now = Math.round( now ); + if ( fx.prop !== "height" ) { + if ( boxSizing === "content-box" ) { + adjust += fx.now; + } + } else if ( that.options.heightStyle !== "content" ) { + fx.now = Math.round( total - toHide.outerHeight() - adjust ); + adjust = 0; + } + } + }); + }, + + _toggleComplete: function( data ) { + var toHide = data.oldPanel; + + toHide + .removeClass( "ui-accordion-content-active" ) + .prev() + .removeClass( "ui-corner-top" ) + .addClass( "ui-corner-all" ); + + // Work around for rendering bug in IE (#5421) + if ( toHide.length ) { + toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; + } + this._trigger( "activate", null, data ); + } +}); + + +/*! + * jQuery UI Menu 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/menu/ + */ + + +var menu = $.widget( "ui.menu", { + version: "1.11.4", + defaultElement: "
    ", + delay: 300, + options: { + icons: { + submenu: "ui-icon-carat-1-e" + }, + items: "> *", + menus: "ul", + position: { + my: "left-1 top", + at: "right top" + }, + role: "menu", + + // callbacks + blur: null, + focus: null, + select: null + }, + + _create: function() { + this.activeMenu = this.element; + + // Flag used to prevent firing of the click handler + // as the event bubbles up through nested menus + this.mouseHandled = false; + this.element + .uniqueId() + .addClass( "ui-menu ui-widget ui-widget-content" ) + .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ) + .attr({ + role: this.options.role, + tabIndex: 0 + }); + + if ( this.options.disabled ) { + this.element + .addClass( "ui-state-disabled" ) + .attr( "aria-disabled", "true" ); + } + + this._on({ + // Prevent focus from sticking to links inside menu after clicking + // them (focus should always stay on UL during navigation). + "mousedown .ui-menu-item": function( event ) { + event.preventDefault(); + }, + "click .ui-menu-item": function( event ) { + var target = $( event.target ); + if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { + this.select( event ); + + // Only set the mouseHandled flag if the event will bubble, see #9469. + if ( !event.isPropagationStopped() ) { + this.mouseHandled = true; + } + + // Open submenu on click + if ( target.has( ".ui-menu" ).length ) { + this.expand( event ); + } else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) { + + // Redirect focus to the menu + this.element.trigger( "focus", [ true ] ); + + // If the active item is on the top level, let it stay active. + // Otherwise, blur the active item since it is no longer visible. + if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { + clearTimeout( this.timer ); + } + } + } + }, + "mouseenter .ui-menu-item": function( event ) { + // Ignore mouse events while typeahead is active, see #10458. + // Prevents focusing the wrong item when typeahead causes a scroll while the mouse + // is over an item in the menu + if ( this.previousFilter ) { + return; + } + var target = $( event.currentTarget ); + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border + target.siblings( ".ui-state-active" ).removeClass( "ui-state-active" ); + this.focus( event, target ); + }, + mouseleave: "collapseAll", + "mouseleave .ui-menu": "collapseAll", + focus: function( event, keepActiveItem ) { + // If there's already an active item, keep it active + // If not, activate the first item + var item = this.active || this.element.find( this.options.items ).eq( 0 ); + + if ( !keepActiveItem ) { + this.focus( event, item ); + } + }, + blur: function( event ) { + this._delay(function() { + if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { + this.collapseAll( event ); + } + }); + }, + keydown: "_keydown" + }); + + this.refresh(); + + // Clicks outside of a menu collapse any open menus + this._on( this.document, { + click: function( event ) { + if ( this._closeOnDocumentClick( event ) ) { + this.collapseAll( event ); + } + + // Reset the mouseHandled flag + this.mouseHandled = false; + } + }); + }, + + _destroy: function() { + // Destroy (sub)menus + this.element + .removeAttr( "aria-activedescendant" ) + .find( ".ui-menu" ).addBack() + .removeClass( "ui-menu ui-widget ui-widget-content ui-menu-icons ui-front" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-disabled" ) + .removeUniqueId() + .show(); + + // Destroy menu items + this.element.find( ".ui-menu-item" ) + .removeClass( "ui-menu-item" ) + .removeAttr( "role" ) + .removeAttr( "aria-disabled" ) + .removeUniqueId() + .removeClass( "ui-state-hover" ) + .removeAttr( "tabIndex" ) + .removeAttr( "role" ) + .removeAttr( "aria-haspopup" ) + .children().each( function() { + var elem = $( this ); + if ( elem.data( "ui-menu-submenu-carat" ) ) { + elem.remove(); + } + }); + + // Destroy menu dividers + this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" ); + }, + + _keydown: function( event ) { + var match, prev, character, skip, + preventDefault = true; + + switch ( event.keyCode ) { + case $.ui.keyCode.PAGE_UP: + this.previousPage( event ); + break; + case $.ui.keyCode.PAGE_DOWN: + this.nextPage( event ); + break; + case $.ui.keyCode.HOME: + this._move( "first", "first", event ); + break; + case $.ui.keyCode.END: + this._move( "last", "last", event ); + break; + case $.ui.keyCode.UP: + this.previous( event ); + break; + case $.ui.keyCode.DOWN: + this.next( event ); + break; + case $.ui.keyCode.LEFT: + this.collapse( event ); + break; + case $.ui.keyCode.RIGHT: + if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { + this.expand( event ); + } + break; + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + this._activate( event ); + break; + case $.ui.keyCode.ESCAPE: + this.collapse( event ); + break; + default: + preventDefault = false; + prev = this.previousFilter || ""; + character = String.fromCharCode( event.keyCode ); + skip = false; + + clearTimeout( this.filterTimer ); + + if ( character === prev ) { + skip = true; + } else { + character = prev + character; + } + + match = this._filterMenuItems( character ); + match = skip && match.index( this.active.next() ) !== -1 ? + this.active.nextAll( ".ui-menu-item" ) : + match; + + // If no matches on the current filter, reset to the last character pressed + // to move down the menu to the first item that starts with that character + if ( !match.length ) { + character = String.fromCharCode( event.keyCode ); + match = this._filterMenuItems( character ); + } + + if ( match.length ) { + this.focus( event, match ); + this.previousFilter = character; + this.filterTimer = this._delay(function() { + delete this.previousFilter; + }, 1000 ); + } else { + delete this.previousFilter; + } + } + + if ( preventDefault ) { + event.preventDefault(); + } + }, + + _activate: function( event ) { + if ( !this.active.is( ".ui-state-disabled" ) ) { + if ( this.active.is( "[aria-haspopup='true']" ) ) { + this.expand( event ); + } else { + this.select( event ); + } + } + }, + + refresh: function() { + var menus, items, + that = this, + icon = this.options.icons.submenu, + submenus = this.element.find( this.options.menus ); + + this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ); + + // Initialize nested menus + submenus.filter( ":not(.ui-menu)" ) + .addClass( "ui-menu ui-widget ui-widget-content ui-front" ) + .hide() + .attr({ + role: this.options.role, + "aria-hidden": "true", + "aria-expanded": "false" + }) + .each(function() { + var menu = $( this ), + item = menu.parent(), + submenuCarat = $( "" ) + .addClass( "ui-menu-icon ui-icon " + icon ) + .data( "ui-menu-submenu-carat", true ); + + item + .attr( "aria-haspopup", "true" ) + .prepend( submenuCarat ); + menu.attr( "aria-labelledby", item.attr( "id" ) ); + }); + + menus = submenus.add( this.element ); + items = menus.find( this.options.items ); + + // Initialize menu-items containing spaces and/or dashes only as dividers + items.not( ".ui-menu-item" ).each(function() { + var item = $( this ); + if ( that._isDivider( item ) ) { + item.addClass( "ui-widget-content ui-menu-divider" ); + } + }); + + // Don't refresh list items that are already adapted + items.not( ".ui-menu-item, .ui-menu-divider" ) + .addClass( "ui-menu-item" ) + .uniqueId() + .attr({ + tabIndex: -1, + role: this._itemRole() + }); + + // Add aria-disabled attribute to any disabled menu item + items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); + + // If the active item has been removed, blur the menu + if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + this.blur(); + } + }, + + _itemRole: function() { + return { + menu: "menuitem", + listbox: "option" + }[ this.options.role ]; + }, + + _setOption: function( key, value ) { + if ( key === "icons" ) { + this.element.find( ".ui-menu-icon" ) + .removeClass( this.options.icons.submenu ) + .addClass( value.submenu ); + } + if ( key === "disabled" ) { + this.element + .toggleClass( "ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + } + this._super( key, value ); + }, + + focus: function( event, item ) { + var nested, focused; + this.blur( event, event && event.type === "focus" ); + + this._scrollIntoView( item ); + + this.active = item.first(); + focused = this.active.addClass( "ui-state-focus" ).removeClass( "ui-state-active" ); + // Only update aria-activedescendant if there's a role + // otherwise we assume focus is managed elsewhere + if ( this.options.role ) { + this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); + } + + // Highlight active parent menu item, if any + this.active + .parent() + .closest( ".ui-menu-item" ) + .addClass( "ui-state-active" ); + + if ( event && event.type === "keydown" ) { + this._close(); + } else { + this.timer = this._delay(function() { + this._close(); + }, this.delay ); + } + + nested = item.children( ".ui-menu" ); + if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) { + this._startOpening(nested); + } + this.activeMenu = item.parent(); + + this._trigger( "focus", event, { item: item } ); + }, + + _scrollIntoView: function( item ) { + var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + if ( this._hasScroll() ) { + borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; + paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; + offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; + scroll = this.activeMenu.scrollTop(); + elementHeight = this.activeMenu.height(); + itemHeight = item.outerHeight(); + + if ( offset < 0 ) { + this.activeMenu.scrollTop( scroll + offset ); + } else if ( offset + itemHeight > elementHeight ) { + this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); + } + } + }, + + blur: function( event, fromFocus ) { + if ( !fromFocus ) { + clearTimeout( this.timer ); + } + + if ( !this.active ) { + return; + } + + this.active.removeClass( "ui-state-focus" ); + this.active = null; + + this._trigger( "blur", event, { item: this.active } ); + }, + + _startOpening: function( submenu ) { + clearTimeout( this.timer ); + + // Don't open if already open fixes a Firefox bug that caused a .5 pixel + // shift in the submenu position when mousing over the carat icon + if ( submenu.attr( "aria-hidden" ) !== "true" ) { + return; + } + + this.timer = this._delay(function() { + this._close(); + this._open( submenu ); + }, this.delay ); + }, + + _open: function( submenu ) { + var position = $.extend({ + of: this.active + }, this.options.position ); + + clearTimeout( this.timer ); + this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) + .hide() + .attr( "aria-hidden", "true" ); + + submenu + .show() + .removeAttr( "aria-hidden" ) + .attr( "aria-expanded", "true" ) + .position( position ); + }, + + collapseAll: function( event, all ) { + clearTimeout( this.timer ); + this.timer = this._delay(function() { + // If we were passed an event, look for the submenu that contains the event + var currentMenu = all ? this.element : + $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); + + // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway + if ( !currentMenu.length ) { + currentMenu = this.element; + } + + this._close( currentMenu ); + + this.blur( event ); + this.activeMenu = currentMenu; + }, this.delay ); + }, + + // With no arguments, closes the currently active menu - if nothing is active + // it closes all menus. If passed an argument, it will search for menus BELOW + _close: function( startMenu ) { + if ( !startMenu ) { + startMenu = this.active ? this.active.parent() : this.element; + } + + startMenu + .find( ".ui-menu" ) + .hide() + .attr( "aria-hidden", "true" ) + .attr( "aria-expanded", "false" ) + .end() + .find( ".ui-state-active" ).not( ".ui-state-focus" ) + .removeClass( "ui-state-active" ); + }, + + _closeOnDocumentClick: function( event ) { + return !$( event.target ).closest( ".ui-menu" ).length; + }, + + _isDivider: function( item ) { + + // Match hyphen, em dash, en dash + return !/[^\-\u2014\u2013\s]/.test( item.text() ); + }, + + collapse: function( event ) { + var newItem = this.active && + this.active.parent().closest( ".ui-menu-item", this.element ); + if ( newItem && newItem.length ) { + this._close(); + this.focus( event, newItem ); + } + }, + + expand: function( event ) { + var newItem = this.active && + this.active + .children( ".ui-menu " ) + .find( this.options.items ) + .first(); + + if ( newItem && newItem.length ) { + this._open( newItem.parent() ); + + // Delay so Firefox will not hide activedescendant change in expanding submenu from AT + this._delay(function() { + this.focus( event, newItem ); + }); + } + }, + + next: function( event ) { + this._move( "next", "first", event ); + }, + + previous: function( event ) { + this._move( "prev", "last", event ); + }, + + isFirstItem: function() { + return this.active && !this.active.prevAll( ".ui-menu-item" ).length; + }, + + isLastItem: function() { + return this.active && !this.active.nextAll( ".ui-menu-item" ).length; + }, + + _move: function( direction, filter, event ) { + var next; + if ( this.active ) { + if ( direction === "first" || direction === "last" ) { + next = this.active + [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) + .eq( -1 ); + } else { + next = this.active + [ direction + "All" ]( ".ui-menu-item" ) + .eq( 0 ); + } + } + if ( !next || !next.length || !this.active ) { + next = this.activeMenu.find( this.options.items )[ filter ](); + } + + this.focus( event, next ); + }, + + nextPage: function( event ) { + var item, base, height; + + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isLastItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.nextAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base - height < 0; + }); + + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.find( this.options.items ) + [ !this.active ? "first" : "last" ]() ); + } + }, + + previousPage: function( event ) { + var item, base, height; + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isFirstItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.prevAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base + height > 0; + }); + + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.find( this.options.items ).first() ); + } + }, + + _hasScroll: function() { + return this.element.outerHeight() < this.element.prop( "scrollHeight" ); + }, + + select: function( event ) { + // TODO: It should never be possible to not have an active item at this + // point, but the tests don't trigger mouseenter before click. + this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); + var ui = { item: this.active }; + if ( !this.active.has( ".ui-menu" ).length ) { + this.collapseAll( event, true ); + } + this._trigger( "select", event, ui ); + }, + + _filterMenuItems: function(character) { + var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ), + regex = new RegExp( "^" + escapedCharacter, "i" ); + + return this.activeMenu + .find( this.options.items ) + + // Only match on items, not dividers or other content (#10571) + .filter( ".ui-menu-item" ) + .filter(function() { + return regex.test( $.trim( $( this ).text() ) ); + }); + } +}); + + +/*! + * jQuery UI Autocomplete 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/autocomplete/ + */ + + +$.widget( "ui.autocomplete", { + version: "1.11.4", + defaultElement: "", + options: { + appendTo: null, + autoFocus: false, + delay: 300, + minLength: 1, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + source: null, + + // callbacks + change: null, + close: null, + focus: null, + open: null, + response: null, + search: null, + select: null + }, + + requestIndex: 0, + pending: 0, + + _create: function() { + // Some browsers only repeat keydown events, not keypress events, + // so we use the suppressKeyPress flag to determine if we've already + // handled the keydown event. #7269 + // Unfortunately the code for & in keypress is the same as the up arrow, + // so we use the suppressKeyPressRepeat flag to avoid handling keypress + // events when we know the keydown event was used to modify the + // search term. #7799 + var suppressKeyPress, suppressKeyPressRepeat, suppressInput, + nodeName = this.element[ 0 ].nodeName.toLowerCase(), + isTextarea = nodeName === "textarea", + isInput = nodeName === "input"; + + this.isMultiLine = + // Textareas are always multi-line + isTextarea ? true : + // Inputs are always single-line, even if inside a contentEditable element + // IE also treats inputs as contentEditable + isInput ? false : + // All other element types are determined by whether or not they're contentEditable + this.element.prop( "isContentEditable" ); + + this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; + this.isNewMenu = true; + + this.element + .addClass( "ui-autocomplete-input" ) + .attr( "autocomplete", "off" ); + + this._on( this.element, { + keydown: function( event ) { + if ( this.element.prop( "readOnly" ) ) { + suppressKeyPress = true; + suppressInput = true; + suppressKeyPressRepeat = true; + return; + } + + suppressKeyPress = false; + suppressInput = false; + suppressKeyPressRepeat = false; + var keyCode = $.ui.keyCode; + switch ( event.keyCode ) { + case keyCode.PAGE_UP: + suppressKeyPress = true; + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + suppressKeyPress = true; + this._move( "nextPage", event ); + break; + case keyCode.UP: + suppressKeyPress = true; + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + suppressKeyPress = true; + this._keyEvent( "next", event ); + break; + case keyCode.ENTER: + // when menu is open and has focus + if ( this.menu.active ) { + // #6055 - Opera still allows the keypress to occur + // which causes forms to submit + suppressKeyPress = true; + event.preventDefault(); + this.menu.select( event ); + } + break; + case keyCode.TAB: + if ( this.menu.active ) { + this.menu.select( event ); + } + break; + case keyCode.ESCAPE: + if ( this.menu.element.is( ":visible" ) ) { + if ( !this.isMultiLine ) { + this._value( this.term ); + } + this.close( event ); + // Different browsers have different default behavior for escape + // Single press can mean undo or clear + // Double press in IE means clear the whole form + event.preventDefault(); + } + break; + default: + suppressKeyPressRepeat = true; + // search timeout should be triggered before the input value is changed + this._searchTimeout( event ); + break; + } + }, + keypress: function( event ) { + if ( suppressKeyPress ) { + suppressKeyPress = false; + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + event.preventDefault(); + } + return; + } + if ( suppressKeyPressRepeat ) { + return; + } + + // replicate some key handlers to allow them to repeat in Firefox and Opera + var keyCode = $.ui.keyCode; + switch ( event.keyCode ) { + case keyCode.PAGE_UP: + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + this._move( "nextPage", event ); + break; + case keyCode.UP: + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + this._keyEvent( "next", event ); + break; + } + }, + input: function( event ) { + if ( suppressInput ) { + suppressInput = false; + event.preventDefault(); + return; + } + this._searchTimeout( event ); + }, + focus: function() { + this.selectedItem = null; + this.previous = this._value(); + }, + blur: function( event ) { + if ( this.cancelBlur ) { + delete this.cancelBlur; + return; + } + + clearTimeout( this.searching ); + this.close( event ); + this._change( event ); + } + }); + + this._initSource(); + this.menu = $( "
      " ) + .addClass( "ui-autocomplete ui-front" ) + .appendTo( this._appendTo() ) + .menu({ + // disable ARIA support, the live region takes care of that + role: null + }) + .hide() + .menu( "instance" ); + + this._on( this.menu.element, { + mousedown: function( event ) { + // prevent moving focus out of the text field + event.preventDefault(); + + // IE doesn't prevent moving focus even with event.preventDefault() + // so we set a flag to know when we should ignore the blur event + this.cancelBlur = true; + this._delay(function() { + delete this.cancelBlur; + }); + + // clicking on the scrollbar causes focus to shift to the body + // but we can't detect a mouseup or a click immediately afterward + // so we have to track the next mousedown and close the menu if + // the user clicks somewhere outside of the autocomplete + var menuElement = this.menu.element[ 0 ]; + if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { + this._delay(function() { + var that = this; + this.document.one( "mousedown", function( event ) { + if ( event.target !== that.element[ 0 ] && + event.target !== menuElement && + !$.contains( menuElement, event.target ) ) { + that.close(); + } + }); + }); + } + }, + menufocus: function( event, ui ) { + var label, item; + // support: Firefox + // Prevent accidental activation of menu items in Firefox (#7024 #9118) + if ( this.isNewMenu ) { + this.isNewMenu = false; + if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { + this.menu.blur(); + + this.document.one( "mousemove", function() { + $( event.target ).trigger( event.originalEvent ); + }); + + return; + } + } + + item = ui.item.data( "ui-autocomplete-item" ); + if ( false !== this._trigger( "focus", event, { item: item } ) ) { + // use value to match what will end up in the input, if it was a key event + if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { + this._value( item.value ); + } + } + + // Announce the value in the liveRegion + label = ui.item.attr( "aria-label" ) || item.value; + if ( label && $.trim( label ).length ) { + this.liveRegion.children().hide(); + $( "
      " ).text( label ).appendTo( this.liveRegion ); + } + }, + menuselect: function( event, ui ) { + var item = ui.item.data( "ui-autocomplete-item" ), + previous = this.previous; + + // only trigger when focus was lost (click on menu) + if ( this.element[ 0 ] !== this.document[ 0 ].activeElement ) { + this.element.focus(); + this.previous = previous; + // #6109 - IE triggers two focus events and the second + // is asynchronous, so we need to reset the previous + // term synchronously and asynchronously :-( + this._delay(function() { + this.previous = previous; + this.selectedItem = item; + }); + } + + if ( false !== this._trigger( "select", event, { item: item } ) ) { + this._value( item.value ); + } + // reset the term after the select event + // this allows custom select handling to work properly + this.term = this._value(); + + this.close( event ); + this.selectedItem = item; + } + }); + + this.liveRegion = $( "", { + role: "status", + "aria-live": "assertive", + "aria-relevant": "additions" + }) + .addClass( "ui-helper-hidden-accessible" ) + .appendTo( this.document[ 0 ].body ); + + // turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._on( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + }); + }, + + _destroy: function() { + clearTimeout( this.searching ); + this.element + .removeClass( "ui-autocomplete-input" ) + .removeAttr( "autocomplete" ); + this.menu.element.remove(); + this.liveRegion.remove(); + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "source" ) { + this._initSource(); + } + if ( key === "appendTo" ) { + this.menu.element.appendTo( this._appendTo() ); + } + if ( key === "disabled" && value && this.xhr ) { + this.xhr.abort(); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + + if ( element ) { + element = element.jquery || element.nodeType ? + $( element ) : + this.document.find( element ).eq( 0 ); + } + + if ( !element || !element[ 0 ] ) { + element = this.element.closest( ".ui-front" ); + } + + if ( !element.length ) { + element = this.document[ 0 ].body; + } + + return element; + }, + + _initSource: function() { + var array, url, + that = this; + if ( $.isArray( this.options.source ) ) { + array = this.options.source; + this.source = function( request, response ) { + response( $.ui.autocomplete.filter( array, request.term ) ); + }; + } else if ( typeof this.options.source === "string" ) { + url = this.options.source; + this.source = function( request, response ) { + if ( that.xhr ) { + that.xhr.abort(); + } + that.xhr = $.ajax({ + url: url, + data: request, + dataType: "json", + success: function( data ) { + response( data ); + }, + error: function() { + response([]); + } + }); + }; + } else { + this.source = this.options.source; + } + }, + + _searchTimeout: function( event ) { + clearTimeout( this.searching ); + this.searching = this._delay(function() { + + // Search if the value has changed, or if the user retypes the same value (see #7434) + var equalValues = this.term === this._value(), + menuVisible = this.menu.element.is( ":visible" ), + modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; + + if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) { + this.selectedItem = null; + this.search( null, event ); + } + }, this.options.delay ); + }, + + search: function( value, event ) { + value = value != null ? value : this._value(); + + // always save the actual value, not the one passed as an argument + this.term = this._value(); + + if ( value.length < this.options.minLength ) { + return this.close( event ); + } + + if ( this._trigger( "search", event ) === false ) { + return; + } + + return this._search( value ); + }, + + _search: function( value ) { + this.pending++; + this.element.addClass( "ui-autocomplete-loading" ); + this.cancelSearch = false; + + this.source( { term: value }, this._response() ); + }, + + _response: function() { + var index = ++this.requestIndex; + + return $.proxy(function( content ) { + if ( index === this.requestIndex ) { + this.__response( content ); + } + + this.pending--; + if ( !this.pending ) { + this.element.removeClass( "ui-autocomplete-loading" ); + } + }, this ); + }, + + __response: function( content ) { + if ( content ) { + content = this._normalize( content ); + } + this._trigger( "response", null, { content: content } ); + if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { + this._suggest( content ); + this._trigger( "open" ); + } else { + // use ._close() instead of .close() so we don't cancel future searches + this._close(); + } + }, + + close: function( event ) { + this.cancelSearch = true; + this._close( event ); + }, + + _close: function( event ) { + if ( this.menu.element.is( ":visible" ) ) { + this.menu.element.hide(); + this.menu.blur(); + this.isNewMenu = true; + this._trigger( "close", event ); + } + }, + + _change: function( event ) { + if ( this.previous !== this._value() ) { + this._trigger( "change", event, { item: this.selectedItem } ); + } + }, + + _normalize: function( items ) { + // assume all items have the right format when the first item is complete + if ( items.length && items[ 0 ].label && items[ 0 ].value ) { + return items; + } + return $.map( items, function( item ) { + if ( typeof item === "string" ) { + return { + label: item, + value: item + }; + } + return $.extend( {}, item, { + label: item.label || item.value, + value: item.value || item.label + }); + }); + }, + + _suggest: function( items ) { + var ul = this.menu.element.empty(); + this._renderMenu( ul, items ); + this.isNewMenu = true; + this.menu.refresh(); + + // size and position menu + ul.show(); + this._resizeMenu(); + ul.position( $.extend({ + of: this.element + }, this.options.position ) ); + + if ( this.options.autoFocus ) { + this.menu.next(); + } + }, + + _resizeMenu: function() { + var ul = this.menu.element; + ul.outerWidth( Math.max( + // Firefox wraps long text (possibly a rounding bug) + // so we add 1px to avoid the wrapping (#7513) + ul.width( "" ).outerWidth() + 1, + this.element.outerWidth() + ) ); + }, + + _renderMenu: function( ul, items ) { + var that = this; + $.each( items, function( index, item ) { + that._renderItemData( ul, item ); + }); + }, + + _renderItemData: function( ul, item ) { + return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); + }, + + _renderItem: function( ul, item ) { + return $( "
    • " ).text( item.label ).appendTo( ul ); + }, + + _move: function( direction, event ) { + if ( !this.menu.element.is( ":visible" ) ) { + this.search( null, event ); + return; + } + if ( this.menu.isFirstItem() && /^previous/.test( direction ) || + this.menu.isLastItem() && /^next/.test( direction ) ) { + + if ( !this.isMultiLine ) { + this._value( this.term ); + } + + this.menu.blur(); + return; + } + this.menu[ direction ]( event ); + }, + + widget: function() { + return this.menu.element; + }, + + _value: function() { + return this.valueMethod.apply( this.element, arguments ); + }, + + _keyEvent: function( keyEvent, event ) { + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + this._move( keyEvent, event ); + + // prevents moving cursor to beginning/end of the text field in some browsers + event.preventDefault(); + } + } +}); + +$.extend( $.ui.autocomplete, { + escapeRegex: function( value ) { + return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); + }, + filter: function( array, term ) { + var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" ); + return $.grep( array, function( value ) { + return matcher.test( value.label || value.value || value ); + }); + } +}); + +// live region extension, adding a `messages` option +// NOTE: This is an experimental API. We are still investigating +// a full solution for string manipulation and internationalization. +$.widget( "ui.autocomplete", $.ui.autocomplete, { + options: { + messages: { + noResults: "No search results.", + results: function( amount ) { + return amount + ( amount > 1 ? " results are" : " result is" ) + + " available, use up and down arrow keys to navigate."; + } + } + }, + + __response: function( content ) { + var message; + this._superApply( arguments ); + if ( this.options.disabled || this.cancelSearch ) { + return; + } + if ( content && content.length ) { + message = this.options.messages.results( content.length ); + } else { + message = this.options.messages.noResults; + } + this.liveRegion.children().hide(); + $( "
      " ).text( message ).appendTo( this.liveRegion ); + } +}); + +var autocomplete = $.ui.autocomplete; + + +/*! + * jQuery UI Button 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/button/ + */ + + +var lastActive, + baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", + typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", + formResetHandler = function() { + var form = $( this ); + setTimeout(function() { + form.find( ":ui-button" ).button( "refresh" ); + }, 1 ); + }, + radioGroup = function( radio ) { + var name = radio.name, + form = radio.form, + radios = $( [] ); + if ( name ) { + name = name.replace( /'/g, "\\'" ); + if ( form ) { + radios = $( form ).find( "[name='" + name + "'][type=radio]" ); + } else { + radios = $( "[name='" + name + "'][type=radio]", radio.ownerDocument ) + .filter(function() { + return !this.form; + }); + } + } + return radios; + }; + +$.widget( "ui.button", { + version: "1.11.4", + defaultElement: "").addClass(this._triggerClass). + html(!buttonImage ? buttonText : $("").attr( + { src:buttonImage, alt:buttonText, title:buttonText }))); + input[isRTL ? "before" : "after"](inst.trigger); + inst.trigger.click(function() { + if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { + $.datepicker._hideDatepicker(); + } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { + $.datepicker._hideDatepicker(); + $.datepicker._showDatepicker(input[0]); + } else { + $.datepicker._showDatepicker(input[0]); + } + return false; + }); + } + }, + + /* Apply the maximum length for the date format. */ + _autoSize: function(inst) { + if (this._get(inst, "autoSize") && !inst.inline) { + var findMax, max, maxI, i, + date = new Date(2009, 12 - 1, 20), // Ensure double digits + dateFormat = this._get(inst, "dateFormat"); + + if (dateFormat.match(/[DM]/)) { + findMax = function(names) { + max = 0; + maxI = 0; + for (i = 0; i < names.length; i++) { + if (names[i].length > max) { + max = names[i].length; + maxI = i; + } + } + return maxI; + }; + date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? + "monthNames" : "monthNamesShort")))); + date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? + "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); + } + inst.input.attr("size", this._formatDate(inst, date).length); + } + }, + + /* Attach an inline date picker to a div. */ + _inlineDatepicker: function(target, inst) { + var divSpan = $(target); + if (divSpan.hasClass(this.markerClassName)) { + return; + } + divSpan.addClass(this.markerClassName).append(inst.dpDiv); + $.data(target, "datepicker", inst); + this._setDate(inst, this._getDefaultDate(inst), true); + this._updateDatepicker(inst); + this._updateAlternate(inst); + //If disabled option is true, disable the datepicker before showing it (see ticket #5665) + if( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements + // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height + inst.dpDiv.css( "display", "block" ); + }, + + /* Pop-up the date picker in a "dialog" box. + * @param input element - ignored + * @param date string or Date - the initial date to display + * @param onSelect function - the function to call when a date is selected + * @param settings object - update the dialog date picker instance's settings (anonymous object) + * @param pos int[2] - coordinates for the dialog's position within the screen or + * event - with x/y coordinates or + * leave empty for default (screen centre) + * @return the manager object + */ + _dialogDatepicker: function(input, date, onSelect, settings, pos) { + var id, browserWidth, browserHeight, scrollX, scrollY, + inst = this._dialogInst; // internal instance + + if (!inst) { + this.uuid += 1; + id = "dp" + this.uuid; + this._dialogInput = $(""); + this._dialogInput.keydown(this._doKeyDown); + $("body").append(this._dialogInput); + inst = this._dialogInst = this._newInst(this._dialogInput, false); + inst.settings = {}; + $.data(this._dialogInput[0], "datepicker", inst); + } + datepicker_extendRemove(inst.settings, settings || {}); + date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); + this._dialogInput.val(date); + + this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); + if (!this._pos) { + browserWidth = document.documentElement.clientWidth; + browserHeight = document.documentElement.clientHeight; + scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; + scrollY = document.documentElement.scrollTop || document.body.scrollTop; + this._pos = // should use actual width/height below + [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; + } + + // move input on screen for focus, but hidden behind dialog + this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); + inst.settings.onSelect = onSelect; + this._inDialog = true; + this.dpDiv.addClass(this._dialogClass); + this._showDatepicker(this._dialogInput[0]); + if ($.blockUI) { + $.blockUI(this.dpDiv); + } + $.data(this._dialogInput[0], "datepicker", inst); + return this; + }, + + /* Detach a datepicker from its control. + * @param target element - the target input field or division or span + */ + _destroyDatepicker: function(target) { + var nodeName, + $target = $(target), + inst = $.data(target, "datepicker"); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + $.removeData(target, "datepicker"); + if (nodeName === "input") { + inst.append.remove(); + inst.trigger.remove(); + $target.removeClass(this.markerClassName). + unbind("focus", this._showDatepicker). + unbind("keydown", this._doKeyDown). + unbind("keypress", this._doKeyPress). + unbind("keyup", this._doKeyUp); + } else if (nodeName === "div" || nodeName === "span") { + $target.removeClass(this.markerClassName).empty(); + } + + if ( datepicker_instActive === inst ) { + datepicker_instActive = null; + } + }, + + /* Enable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _enableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, "datepicker"); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = false; + inst.trigger.filter("button"). + each(function() { this.disabled = false; }).end(). + filter("img").css({opacity: "1.0", cursor: ""}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().removeClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", false); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + }, + + /* Disable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _disableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, "datepicker"); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = true; + inst.trigger.filter("button"). + each(function() { this.disabled = true; }).end(). + filter("img").css({opacity: "0.5", cursor: "default"}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().addClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", true); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + this._disabledInputs[this._disabledInputs.length] = target; + }, + + /* Is the first field in a jQuery collection disabled as a datepicker? + * @param target element - the target input field or division or span + * @return boolean - true if disabled, false if enabled + */ + _isDisabledDatepicker: function(target) { + if (!target) { + return false; + } + for (var i = 0; i < this._disabledInputs.length; i++) { + if (this._disabledInputs[i] === target) { + return true; + } + } + return false; + }, + + /* Retrieve the instance data for the target control. + * @param target element - the target input field or division or span + * @return object - the associated instance data + * @throws error if a jQuery problem getting data + */ + _getInst: function(target) { + try { + return $.data(target, "datepicker"); + } + catch (err) { + throw "Missing instance data for this datepicker"; + } + }, + + /* Update or retrieve the settings for a date picker attached to an input field or division. + * @param target element - the target input field or division or span + * @param name object - the new settings to update or + * string - the name of the setting to change or retrieve, + * when retrieving also "all" for all instance settings or + * "defaults" for all global defaults + * @param value any - the new value for the setting + * (omit if above is an object or to retrieve a value) + */ + _optionDatepicker: function(target, name, value) { + var settings, date, minDate, maxDate, + inst = this._getInst(target); + + if (arguments.length === 2 && typeof name === "string") { + return (name === "defaults" ? $.extend({}, $.datepicker._defaults) : + (inst ? (name === "all" ? $.extend({}, inst.settings) : + this._get(inst, name)) : null)); + } + + settings = name || {}; + if (typeof name === "string") { + settings = {}; + settings[name] = value; + } + + if (inst) { + if (this._curInst === inst) { + this._hideDatepicker(); + } + + date = this._getDateDatepicker(target, true); + minDate = this._getMinMaxDate(inst, "min"); + maxDate = this._getMinMaxDate(inst, "max"); + datepicker_extendRemove(inst.settings, settings); + // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided + if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) { + inst.settings.minDate = this._formatDate(inst, minDate); + } + if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) { + inst.settings.maxDate = this._formatDate(inst, maxDate); + } + if ( "disabled" in settings ) { + if ( settings.disabled ) { + this._disableDatepicker(target); + } else { + this._enableDatepicker(target); + } + } + this._attachments($(target), inst); + this._autoSize(inst); + this._setDate(inst, date); + this._updateAlternate(inst); + this._updateDatepicker(inst); + } + }, + + // change method deprecated + _changeDatepicker: function(target, name, value) { + this._optionDatepicker(target, name, value); + }, + + /* Redraw the date picker attached to an input field or division. + * @param target element - the target input field or division or span + */ + _refreshDatepicker: function(target) { + var inst = this._getInst(target); + if (inst) { + this._updateDatepicker(inst); + } + }, + + /* Set the dates for a jQuery selection. + * @param target element - the target input field or division or span + * @param date Date - the new date + */ + _setDateDatepicker: function(target, date) { + var inst = this._getInst(target); + if (inst) { + this._setDate(inst, date); + this._updateDatepicker(inst); + this._updateAlternate(inst); + } + }, + + /* Get the date(s) for the first entry in a jQuery selection. + * @param target element - the target input field or division or span + * @param noDefault boolean - true if no default date is to be used + * @return Date - the current date + */ + _getDateDatepicker: function(target, noDefault) { + var inst = this._getInst(target); + if (inst && !inst.inline) { + this._setDateFromField(inst, noDefault); + } + return (inst ? this._getDate(inst) : null); + }, + + /* Handle keystrokes. */ + _doKeyDown: function(event) { + var onSelect, dateStr, sel, + inst = $.datepicker._getInst(event.target), + handled = true, + isRTL = inst.dpDiv.is(".ui-datepicker-rtl"); + + inst._keyEvent = true; + if ($.datepicker._datepickerShowing) { + switch (event.keyCode) { + case 9: $.datepicker._hideDatepicker(); + handled = false; + break; // hide on tab out + case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." + + $.datepicker._currentClass + ")", inst.dpDiv); + if (sel[0]) { + $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); + } + + onSelect = $.datepicker._get(inst, "onSelect"); + if (onSelect) { + dateStr = $.datepicker._formatDate(inst); + + // trigger custom callback + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); + } else { + $.datepicker._hideDatepicker(); + } + + return false; // don't submit the form + case 27: $.datepicker._hideDatepicker(); + break; // hide on escape + case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + break; // previous month/year on page up/+ ctrl + case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + break; // next month/year on page down/+ ctrl + case 35: if (event.ctrlKey || event.metaKey) { + $.datepicker._clearDate(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // clear on ctrl or command +end + case 36: if (event.ctrlKey || event.metaKey) { + $.datepicker._gotoToday(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // current on ctrl or command +home + case 37: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // -1 day on ctrl or command +left + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +left on Mac + break; + case 38: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, -7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // -1 week on ctrl or command +up + case 39: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // +1 day on ctrl or command +right + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +right + break; + case 40: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, +7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // +1 week on ctrl or command +down + default: handled = false; + } + } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home + $.datepicker._showDatepicker(this); + } else { + handled = false; + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }, + + /* Filter entered characters - based on date format. */ + _doKeyPress: function(event) { + var chars, chr, + inst = $.datepicker._getInst(event.target); + + if ($.datepicker._get(inst, "constrainInput")) { + chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat")); + chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode); + return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1); + } + }, + + /* Synchronise manual entry and field/alternate field. */ + _doKeyUp: function(event) { + var date, + inst = $.datepicker._getInst(event.target); + + if (inst.input.val() !== inst.lastVal) { + try { + date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + (inst.input ? inst.input.val() : null), + $.datepicker._getFormatConfig(inst)); + + if (date) { // only if valid + $.datepicker._setDateFromField(inst); + $.datepicker._updateAlternate(inst); + $.datepicker._updateDatepicker(inst); + } + } + catch (err) { + } + } + return true; + }, + + /* Pop-up the date picker for a given input field. + * If false returned from beforeShow event handler do not show. + * @param input element - the input field attached to the date picker or + * event - if triggered by focus + */ + _showDatepicker: function(input) { + input = input.target || input; + if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger + input = $("input", input.parentNode)[0]; + } + + if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here + return; + } + + var inst, beforeShow, beforeShowSettings, isFixed, + offset, showAnim, duration; + + inst = $.datepicker._getInst(input); + if ($.datepicker._curInst && $.datepicker._curInst !== inst) { + $.datepicker._curInst.dpDiv.stop(true, true); + if ( inst && $.datepicker._datepickerShowing ) { + $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); + } + } + + beforeShow = $.datepicker._get(inst, "beforeShow"); + beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; + if(beforeShowSettings === false){ + return; + } + datepicker_extendRemove(inst.settings, beforeShowSettings); + + inst.lastVal = null; + $.datepicker._lastInput = input; + $.datepicker._setDateFromField(inst); + + if ($.datepicker._inDialog) { // hide cursor + input.value = ""; + } + if (!$.datepicker._pos) { // position below input + $.datepicker._pos = $.datepicker._findPos(input); + $.datepicker._pos[1] += input.offsetHeight; // add the height + } + + isFixed = false; + $(input).parents().each(function() { + isFixed |= $(this).css("position") === "fixed"; + return !isFixed; + }); + + offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; + $.datepicker._pos = null; + //to avoid flashes on Firefox + inst.dpDiv.empty(); + // determine sizing offscreen + inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"}); + $.datepicker._updateDatepicker(inst); + // fix width for dynamic number of date pickers + // and adjust position before showing + offset = $.datepicker._checkOffset(inst, offset, isFixed); + inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? + "static" : (isFixed ? "fixed" : "absolute")), display: "none", + left: offset.left + "px", top: offset.top + "px"}); + + if (!inst.inline) { + showAnim = $.datepicker._get(inst, "showAnim"); + duration = $.datepicker._get(inst, "duration"); + inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); + $.datepicker._datepickerShowing = true; + + if ( $.effects && $.effects.effect[ showAnim ] ) { + inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration); + } else { + inst.dpDiv[showAnim || "show"](showAnim ? duration : null); + } + + if ( $.datepicker._shouldFocusInput( inst ) ) { + inst.input.focus(); + } + + $.datepicker._curInst = inst; + } + }, + + /* Generate the date picker content. */ + _updateDatepicker: function(inst) { + this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) + datepicker_instActive = inst; // for delegate hover events + inst.dpDiv.empty().append(this._generateHTML(inst)); + this._attachHandlers(inst); + + var origyearshtml, + numMonths = this._getNumberOfMonths(inst), + cols = numMonths[1], + width = 17, + activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ); + + if ( activeCell.length > 0 ) { + datepicker_handleMouseover.apply( activeCell.get( 0 ) ); + } + + inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""); + if (cols > 1) { + inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em"); + } + inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") + + "Class"]("ui-datepicker-multi"); + inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") + + "Class"]("ui-datepicker-rtl"); + + if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { + inst.input.focus(); + } + + // deffered render of the years select (to avoid flashes on Firefox) + if( inst.yearshtml ){ + origyearshtml = inst.yearshtml; + setTimeout(function(){ + //assure that inst.yearshtml didn't change. + if( origyearshtml === inst.yearshtml && inst.yearshtml ){ + inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml); + } + origyearshtml = inst.yearshtml = null; + }, 0); + } + }, + + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + // Support: IE and jQuery <1.9 + _shouldFocusInput: function( inst ) { + return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); + }, + + /* Check positioning to remain on screen. */ + _checkOffset: function(inst, offset, isFixed) { + var dpWidth = inst.dpDiv.outerWidth(), + dpHeight = inst.dpDiv.outerHeight(), + inputWidth = inst.input ? inst.input.outerWidth() : 0, + inputHeight = inst.input ? inst.input.outerHeight() : 0, + viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()), + viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); + + offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0); + offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0; + offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; + + // now check if datepicker is showing outside window viewport - move to a better place if so. + offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? + Math.abs(offset.left + dpWidth - viewWidth) : 0); + offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight) : 0); + + return offset; + }, + + /* Find an object's position on the screen. */ + _findPos: function(obj) { + var position, + inst = this._getInst(obj), + isRTL = this._get(inst, "isRTL"); + + while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) { + obj = obj[isRTL ? "previousSibling" : "nextSibling"]; + } + + position = $(obj).offset(); + return [position.left, position.top]; + }, + + /* Hide the date picker from view. + * @param input element - the input field attached to the date picker + */ + _hideDatepicker: function(input) { + var showAnim, duration, postProcess, onClose, + inst = this._curInst; + + if (!inst || (input && inst !== $.data(input, "datepicker"))) { + return; + } + + if (this._datepickerShowing) { + showAnim = this._get(inst, "showAnim"); + duration = this._get(inst, "duration"); + postProcess = function() { + $.datepicker._tidyDialog(inst); + }; + + // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed + if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { + inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess); + } else { + inst.dpDiv[(showAnim === "slideDown" ? "slideUp" : + (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess); + } + + if (!showAnim) { + postProcess(); + } + this._datepickerShowing = false; + + onClose = this._get(inst, "onClose"); + if (onClose) { + onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); + } + + this._lastInput = null; + if (this._inDialog) { + this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); + if ($.blockUI) { + $.unblockUI(); + $("body").append(this.dpDiv); + } + } + this._inDialog = false; + } + }, + + /* Tidy up after a dialog display. */ + _tidyDialog: function(inst) { + inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); + }, + + /* Close date picker if clicked elsewhere. */ + _checkExternalClick: function(event) { + if (!$.datepicker._curInst) { + return; + } + + var $target = $(event.target), + inst = $.datepicker._getInst($target[0]); + + if ( ( ( $target[0].id !== $.datepicker._mainDivId && + $target.parents("#" + $.datepicker._mainDivId).length === 0 && + !$target.hasClass($.datepicker.markerClassName) && + !$target.closest("." + $.datepicker._triggerClass).length && + $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || + ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { + $.datepicker._hideDatepicker(); + } + }, + + /* Adjust one of the date sub-fields. */ + _adjustDate: function(id, offset, period) { + var target = $(id), + inst = this._getInst(target[0]); + + if (this._isDisabledDatepicker(target[0])) { + return; + } + this._adjustInstDate(inst, offset + + (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning + period); + this._updateDatepicker(inst); + }, + + /* Action for current link. */ + _gotoToday: function(id) { + var date, + target = $(id), + inst = this._getInst(target[0]); + + if (this._get(inst, "gotoCurrent") && inst.currentDay) { + inst.selectedDay = inst.currentDay; + inst.drawMonth = inst.selectedMonth = inst.currentMonth; + inst.drawYear = inst.selectedYear = inst.currentYear; + } else { + date = new Date(); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + } + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a new month/year. */ + _selectMonthYear: function(id, select, period) { + var target = $(id), + inst = this._getInst(target[0]); + + inst["selected" + (period === "M" ? "Month" : "Year")] = + inst["draw" + (period === "M" ? "Month" : "Year")] = + parseInt(select.options[select.selectedIndex].value,10); + + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a day. */ + _selectDay: function(id, month, year, td) { + var inst, + target = $(id); + + if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { + return; + } + + inst = this._getInst(target[0]); + inst.selectedDay = inst.currentDay = $("a", td).html(); + inst.selectedMonth = inst.currentMonth = month; + inst.selectedYear = inst.currentYear = year; + this._selectDate(id, this._formatDate(inst, + inst.currentDay, inst.currentMonth, inst.currentYear)); + }, + + /* Erase the input field and hide the date picker. */ + _clearDate: function(id) { + var target = $(id); + this._selectDate(target, ""); + }, + + /* Update the input field with the selected date. */ + _selectDate: function(id, dateStr) { + var onSelect, + target = $(id), + inst = this._getInst(target[0]); + + dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); + if (inst.input) { + inst.input.val(dateStr); + } + this._updateAlternate(inst); + + onSelect = this._get(inst, "onSelect"); + if (onSelect) { + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback + } else if (inst.input) { + inst.input.trigger("change"); // fire the change event + } + + if (inst.inline){ + this._updateDatepicker(inst); + } else { + this._hideDatepicker(); + this._lastInput = inst.input[0]; + if (typeof(inst.input[0]) !== "object") { + inst.input.focus(); // restore focus + } + this._lastInput = null; + } + }, + + /* Update any alternate field to synchronise with the main field. */ + _updateAlternate: function(inst) { + var altFormat, date, dateStr, + altField = this._get(inst, "altField"); + + if (altField) { // update alternate field too + altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); + date = this._getDate(inst); + dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); + $(altField).each(function() { $(this).val(dateStr); }); + } + }, + + /* Set as beforeShowDay function to prevent selection of weekends. + * @param date Date - the date to customise + * @return [boolean, string] - is this date selectable?, what is its CSS class? + */ + noWeekends: function(date) { + var day = date.getDay(); + return [(day > 0 && day < 6), ""]; + }, + + /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + * @param date Date - the date to get the week for + * @return number - the number of the week within the year that contains this date + */ + iso8601Week: function(date) { + var time, + checkDate = new Date(date.getTime()); + + // Find Thursday of this week starting on Monday + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); + + time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + }, + + /* Parse a string value into a date object. + * See formatDate below for the possible formats. + * + * @param format string - the expected format of the date + * @param value string - the date in the above format + * @param settings Object - attributes include: + * shortYearCutoff number - the cutoff year for determining the century (optional) + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return Date - the extracted date value or null if value is blank + */ + parseDate: function (format, value, settings) { + if (format == null || value == null) { + throw "Invalid arguments"; + } + + value = (typeof value === "object" ? value.toString() : value + ""); + if (value === "") { + return null; + } + + var iFormat, dim, extra, + iValue = 0, + shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, + shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : + new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + year = -1, + month = -1, + day = -1, + doy = -1, + literal = false, + date, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Extract a number from the string value + getNumber = function(match) { + var isDoubled = lookAhead(match), + size = (match === "@" ? 14 : (match === "!" ? 20 : + (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), + minSize = (match === "y" ? size : 1), + digits = new RegExp("^\\d{" + minSize + "," + size + "}"), + num = value.substring(iValue).match(digits); + if (!num) { + throw "Missing number at position " + iValue; + } + iValue += num[0].length; + return parseInt(num[0], 10); + }, + // Extract a name from the string value and convert to an index + getName = function(match, shortNames, longNames) { + var index = -1, + names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { + return [ [k, v] ]; + }).sort(function (a, b) { + return -(a[1].length - b[1].length); + }); + + $.each(names, function (i, pair) { + var name = pair[1]; + if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { + index = pair[0]; + iValue += name.length; + return false; + } + }); + if (index !== -1) { + return index + 1; + } else { + throw "Unknown name at position " + iValue; + } + }, + // Confirm that a literal character matches the string value + checkLiteral = function() { + if (value.charAt(iValue) !== format.charAt(iFormat)) { + throw "Unexpected literal at position " + iValue; + } + iValue++; + }; + + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + checkLiteral(); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + day = getNumber("d"); + break; + case "D": + getName("D", dayNamesShort, dayNames); + break; + case "o": + doy = getNumber("o"); + break; + case "m": + month = getNumber("m"); + break; + case "M": + month = getName("M", monthNamesShort, monthNames); + break; + case "y": + year = getNumber("y"); + break; + case "@": + date = new Date(getNumber("@")); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "!": + date = new Date((getNumber("!") - this._ticksTo1970) / 10000); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if (lookAhead("'")){ + checkLiteral(); + } else { + literal = true; + } + break; + default: + checkLiteral(); + } + } + } + + if (iValue < value.length){ + extra = value.substr(iValue); + if (!/^\s+/.test(extra)) { + throw "Extra/unparsed characters found in date: " + extra; + } + } + + if (year === -1) { + year = new Date().getFullYear(); + } else if (year < 100) { + year += new Date().getFullYear() - new Date().getFullYear() % 100 + + (year <= shortYearCutoff ? 0 : -100); + } + + if (doy > -1) { + month = 1; + day = doy; + do { + dim = this._getDaysInMonth(year, month - 1); + if (day <= dim) { + break; + } + month++; + day -= dim; + } while (true); + } + + date = this._daylightSavingAdjust(new Date(year, month - 1, day)); + if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { + throw "Invalid date"; // E.g. 31/02/00 + } + return date; + }, + + /* Standard date formats. */ + ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) + COOKIE: "D, dd M yy", + ISO_8601: "yy-mm-dd", + RFC_822: "D, d M y", + RFC_850: "DD, dd-M-y", + RFC_1036: "D, d M y", + RFC_1123: "D, d M yy", + RFC_2822: "D, d M yy", + RSS: "D, d M y", // RFC 822 + TICKS: "!", + TIMESTAMP: "@", + W3C: "yy-mm-dd", // ISO 8601 + + _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), + + /* Format a date object into a string value. + * The format can be combinations of the following: + * d - day of month (no leading zero) + * dd - day of month (two digit) + * o - day of year (no leading zeros) + * oo - day of year (three digit) + * D - day name short + * DD - day name long + * m - month of year (no leading zero) + * mm - month of year (two digit) + * M - month name short + * MM - month name long + * y - year (two digit) + * yy - year (four digit) + * @ - Unix timestamp (ms since 01/01/1970) + * ! - Windows ticks (100ns since 01/01/0001) + * "..." - literal text + * '' - single quote + * + * @param format string - the desired format of the date + * @param date Date - the date value to format + * @param settings Object - attributes include: + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return string - the date in the above format + */ + formatDate: function (format, date, settings) { + if (!date) { + return ""; + } + + var iFormat, + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Format a number, with leading zero if necessary + formatNumber = function(match, value, len) { + var num = "" + value; + if (lookAhead(match)) { + while (num.length < len) { + num = "0" + num; + } + } + return num; + }, + // Format a name, short or long as requested + formatName = function(match, value, shortNames, longNames) { + return (lookAhead(match) ? longNames[value] : shortNames[value]); + }, + output = "", + literal = false; + + if (date) { + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + output += format.charAt(iFormat); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + output += formatNumber("d", date.getDate(), 2); + break; + case "D": + output += formatName("D", date.getDay(), dayNamesShort, dayNames); + break; + case "o": + output += formatNumber("o", + Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3); + break; + case "m": + output += formatNumber("m", date.getMonth() + 1, 2); + break; + case "M": + output += formatName("M", date.getMonth(), monthNamesShort, monthNames); + break; + case "y": + output += (lookAhead("y") ? date.getFullYear() : + (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100); + break; + case "@": + output += date.getTime(); + break; + case "!": + output += date.getTime() * 10000 + this._ticksTo1970; + break; + case "'": + if (lookAhead("'")) { + output += "'"; + } else { + literal = true; + } + break; + default: + output += format.charAt(iFormat); + } + } + } + } + return output; + }, + + /* Extract all possible characters from the date format. */ + _possibleChars: function (format) { + var iFormat, + chars = "", + literal = false, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }; + + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + chars += format.charAt(iFormat); + } + } else { + switch (format.charAt(iFormat)) { + case "d": case "m": case "y": case "@": + chars += "0123456789"; + break; + case "D": case "M": + return null; // Accept anything + case "'": + if (lookAhead("'")) { + chars += "'"; + } else { + literal = true; + } + break; + default: + chars += format.charAt(iFormat); + } + } + } + return chars; + }, + + /* Get a setting value, defaulting if necessary. */ + _get: function(inst, name) { + return inst.settings[name] !== undefined ? + inst.settings[name] : this._defaults[name]; + }, + + /* Parse existing date and initialise date picker. */ + _setDateFromField: function(inst, noDefault) { + if (inst.input.val() === inst.lastVal) { + return; + } + + var dateFormat = this._get(inst, "dateFormat"), + dates = inst.lastVal = inst.input ? inst.input.val() : null, + defaultDate = this._getDefaultDate(inst), + date = defaultDate, + settings = this._getFormatConfig(inst); + + try { + date = this.parseDate(dateFormat, dates, settings) || defaultDate; + } catch (event) { + dates = (noDefault ? "" : dates); + } + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + inst.currentDay = (dates ? date.getDate() : 0); + inst.currentMonth = (dates ? date.getMonth() : 0); + inst.currentYear = (dates ? date.getFullYear() : 0); + this._adjustInstDate(inst); + }, + + /* Retrieve the default date shown on opening. */ + _getDefaultDate: function(inst) { + return this._restrictMinMax(inst, + this._determineDate(inst, this._get(inst, "defaultDate"), new Date())); + }, + + /* A date may be specified as an exact value or a relative one. */ + _determineDate: function(inst, date, defaultDate) { + var offsetNumeric = function(offset) { + var date = new Date(); + date.setDate(date.getDate() + offset); + return date; + }, + offsetString = function(offset) { + try { + return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + offset, $.datepicker._getFormatConfig(inst)); + } + catch (e) { + // Ignore + } + + var date = (offset.toLowerCase().match(/^c/) ? + $.datepicker._getDate(inst) : null) || new Date(), + year = date.getFullYear(), + month = date.getMonth(), + day = date.getDate(), + pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, + matches = pattern.exec(offset); + + while (matches) { + switch (matches[2] || "d") { + case "d" : case "D" : + day += parseInt(matches[1],10); break; + case "w" : case "W" : + day += parseInt(matches[1],10) * 7; break; + case "m" : case "M" : + month += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + case "y": case "Y" : + year += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + } + matches = pattern.exec(offset); + } + return new Date(year, month, day); + }, + newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) : + (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); + + newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate); + if (newDate) { + newDate.setHours(0); + newDate.setMinutes(0); + newDate.setSeconds(0); + newDate.setMilliseconds(0); + } + return this._daylightSavingAdjust(newDate); + }, + + /* Handle switch to/from daylight saving. + * Hours may be non-zero on daylight saving cut-over: + * > 12 when midnight changeover, but then cannot generate + * midnight datetime, so jump to 1AM, otherwise reset. + * @param date (Date) the date to check + * @return (Date) the corrected date + */ + _daylightSavingAdjust: function(date) { + if (!date) { + return null; + } + date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); + return date; + }, + + /* Set the date(s) directly. */ + _setDate: function(inst, date, noChange) { + var clear = !date, + origMonth = inst.selectedMonth, + origYear = inst.selectedYear, + newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); + + inst.selectedDay = inst.currentDay = newDate.getDate(); + inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); + inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); + if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) { + this._notifyChange(inst); + } + this._adjustInstDate(inst); + if (inst.input) { + inst.input.val(clear ? "" : this._formatDate(inst)); + } + }, + + /* Retrieve the date(s) directly. */ + _getDate: function(inst) { + var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null : + this._daylightSavingAdjust(new Date( + inst.currentYear, inst.currentMonth, inst.currentDay))); + return startDate; + }, + + /* Attach the onxxx handlers. These are declared statically so + * they work with static code transformers like Caja. + */ + _attachHandlers: function(inst) { + var stepMonths = this._get(inst, "stepMonths"), + id = "#" + inst.id.replace( /\\\\/g, "\\" ); + inst.dpDiv.find("[data-handler]").map(function () { + var handler = { + prev: function () { + $.datepicker._adjustDate(id, -stepMonths, "M"); + }, + next: function () { + $.datepicker._adjustDate(id, +stepMonths, "M"); + }, + hide: function () { + $.datepicker._hideDatepicker(); + }, + today: function () { + $.datepicker._gotoToday(id); + }, + selectDay: function () { + $.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this); + return false; + }, + selectMonth: function () { + $.datepicker._selectMonthYear(id, this, "M"); + return false; + }, + selectYear: function () { + $.datepicker._selectMonthYear(id, this, "Y"); + return false; + } + }; + $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]); + }); + }, + + /* Generate the HTML for the current state of the date picker. */ + _generateHTML: function(inst) { + var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, + controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, + monthNames, monthNamesShort, beforeShowDay, showOtherMonths, + selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, + cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, + printDate, dRow, tbody, daySettings, otherMonth, unselectable, + tempDate = new Date(), + today = this._daylightSavingAdjust( + new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time + isRTL = this._get(inst, "isRTL"), + showButtonPanel = this._get(inst, "showButtonPanel"), + hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"), + navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"), + numMonths = this._getNumberOfMonths(inst), + showCurrentAtPos = this._get(inst, "showCurrentAtPos"), + stepMonths = this._get(inst, "stepMonths"), + isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1), + currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : + new Date(inst.currentYear, inst.currentMonth, inst.currentDay))), + minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + drawMonth = inst.drawMonth - showCurrentAtPos, + drawYear = inst.drawYear; + + if (drawMonth < 0) { + drawMonth += 12; + drawYear--; + } + if (maxDate) { + maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), + maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); + maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); + while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { + drawMonth--; + if (drawMonth < 0) { + drawMonth = 11; + drawYear--; + } + } + } + inst.drawMonth = drawMonth; + inst.drawYear = drawYear; + + prevText = this._get(inst, "prevText"); + prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), + this._getFormatConfig(inst))); + + prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? + "" + prevText + "" : + (hideIfNoPrevNext ? "" : "" + prevText + "")); + + nextText = this._get(inst, "nextText"); + nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), + this._getFormatConfig(inst))); + + next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? + "" + nextText + "" : + (hideIfNoPrevNext ? "" : "" + nextText + "")); + + currentText = this._get(inst, "currentText"); + gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today); + currentText = (!navigationAsDateFormat ? currentText : + this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); + + controls = (!inst.inline ? "" : ""); + + buttonPanel = (showButtonPanel) ? "
      " + (isRTL ? controls : "") + + (this._isInRange(inst, gotoDate) ? "" : "") + (isRTL ? "" : controls) + "
      " : ""; + + firstDay = parseInt(this._get(inst, "firstDay"),10); + firstDay = (isNaN(firstDay) ? 0 : firstDay); + + showWeek = this._get(inst, "showWeek"); + dayNames = this._get(inst, "dayNames"); + dayNamesMin = this._get(inst, "dayNamesMin"); + monthNames = this._get(inst, "monthNames"); + monthNamesShort = this._get(inst, "monthNamesShort"); + beforeShowDay = this._get(inst, "beforeShowDay"); + showOtherMonths = this._get(inst, "showOtherMonths"); + selectOtherMonths = this._get(inst, "selectOtherMonths"); + defaultDate = this._getDefaultDate(inst); + html = ""; + dow; + for (row = 0; row < numMonths[0]; row++) { + group = ""; + this.maxRows = 4; + for (col = 0; col < numMonths[1]; col++) { + selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); + cornerClass = " ui-corner-all"; + calender = ""; + if (isMultiMonth) { + calender += "
      "; + } + calender += "
      " + + (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") + + (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") + + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, + row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers + "
      " + + ""; + thead = (showWeek ? "" : ""); + for (dow = 0; dow < 7; dow++) { // days of the week + day = (dow + firstDay) % 7; + thead += ""; + } + calender += thead + ""; + daysInMonth = this._getDaysInMonth(drawYear, drawMonth); + if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { + inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); + } + leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; + curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate + numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043) + this.maxRows = numRows; + printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); + for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows + calender += ""; + tbody = (!showWeek ? "" : ""); + for (dow = 0; dow < 7; dow++) { // create date picker days + daySettings = (beforeShowDay ? + beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]); + otherMonth = (printDate.getMonth() !== drawMonth); + unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || + (minDate && printDate < minDate) || (maxDate && printDate > maxDate); + tbody += ""; // display selectable date + printDate.setDate(printDate.getDate() + 1); + printDate = this._daylightSavingAdjust(printDate); + } + calender += tbody + ""; + } + drawMonth++; + if (drawMonth > 11) { + drawMonth = 0; + drawYear++; + } + calender += "
      " + this._get(inst, "weekHeader") + "= 5 ? " class='ui-datepicker-week-end'" : "") + ">" + + "" + dayNamesMin[day] + "
      " + + this._get(inst, "calculateWeek")(printDate) + "" + // actions + (otherMonth && !showOtherMonths ? " " : // display for other months + (unselectable ? "" + printDate.getDate() + "" : "" + printDate.getDate() + "")) + "
      " + (isMultiMonth ? "
      " + + ((numMonths[0] > 0 && col === numMonths[1]-1) ? "
      " : "") : ""); + group += calender; + } + html += group; + } + html += buttonPanel; + inst._keyEvent = false; + return html; + }, + + /* Generate the month and year header. */ + _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, + secondary, monthNames, monthNamesShort) { + + var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, + changeMonth = this._get(inst, "changeMonth"), + changeYear = this._get(inst, "changeYear"), + showMonthAfterYear = this._get(inst, "showMonthAfterYear"), + html = "
      ", + monthHtml = ""; + + // month selection + if (secondary || !changeMonth) { + monthHtml += "" + monthNames[drawMonth] + ""; + } else { + inMinYear = (minDate && minDate.getFullYear() === drawYear); + inMaxYear = (maxDate && maxDate.getFullYear() === drawYear); + monthHtml += ""; + } + + if (!showMonthAfterYear) { + html += monthHtml + (secondary || !(changeMonth && changeYear) ? " " : ""); + } + + // year selection + if ( !inst.yearshtml ) { + inst.yearshtml = ""; + if (secondary || !changeYear) { + html += "" + drawYear + ""; + } else { + // determine range of years to display + years = this._get(inst, "yearRange").split(":"); + thisYear = new Date().getFullYear(); + determineYear = function(value) { + var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) : + (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) : + parseInt(value, 10))); + return (isNaN(year) ? thisYear : year); + }; + year = determineYear(years[0]); + endYear = Math.max(year, determineYear(years[1] || "")); + year = (minDate ? Math.max(year, minDate.getFullYear()) : year); + endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); + inst.yearshtml += ""; + + html += inst.yearshtml; + inst.yearshtml = null; + } + } + + html += this._get(inst, "yearSuffix"); + if (showMonthAfterYear) { + html += (secondary || !(changeMonth && changeYear) ? " " : "") + monthHtml; + } + html += "
      "; // Close datepicker_header + return html; + }, + + /* Adjust one of the date sub-fields. */ + _adjustInstDate: function(inst, offset, period) { + var year = inst.drawYear + (period === "Y" ? offset : 0), + month = inst.drawMonth + (period === "M" ? offset : 0), + day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0), + date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); + + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + if (period === "M" || period === "Y") { + this._notifyChange(inst); + } + }, + + /* Ensure a date is within any min/max bounds. */ + _restrictMinMax: function(inst, date) { + var minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + newDate = (minDate && date < minDate ? minDate : date); + return (maxDate && newDate > maxDate ? maxDate : newDate); + }, + + /* Notify change of month/year. */ + _notifyChange: function(inst) { + var onChange = this._get(inst, "onChangeMonthYear"); + if (onChange) { + onChange.apply((inst.input ? inst.input[0] : null), + [inst.selectedYear, inst.selectedMonth + 1, inst]); + } + }, + + /* Determine the number of months to show. */ + _getNumberOfMonths: function(inst) { + var numMonths = this._get(inst, "numberOfMonths"); + return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths)); + }, + + /* Determine the current maximum date - ensure no time components are set. */ + _getMinMaxDate: function(inst, minMax) { + return this._determineDate(inst, this._get(inst, minMax + "Date"), null); + }, + + /* Find the number of days in a given month. */ + _getDaysInMonth: function(year, month) { + return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); + }, + + /* Find the day of the week of the first of a month. */ + _getFirstDayOfMonth: function(year, month) { + return new Date(year, month, 1).getDay(); + }, + + /* Determines if we should allow a "next/prev" month display change. */ + _canAdjustMonth: function(inst, offset, curYear, curMonth) { + var numMonths = this._getNumberOfMonths(inst), + date = this._daylightSavingAdjust(new Date(curYear, + curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); + + if (offset < 0) { + date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); + } + return this._isInRange(inst, date); + }, + + /* Is the given date in the accepted range? */ + _isInRange: function(inst, date) { + var yearSplit, currentYear, + minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + minYear = null, + maxYear = null, + years = this._get(inst, "yearRange"); + if (years){ + yearSplit = years.split(":"); + currentYear = new Date().getFullYear(); + minYear = parseInt(yearSplit[0], 10); + maxYear = parseInt(yearSplit[1], 10); + if ( yearSplit[0].match(/[+\-].*/) ) { + minYear += currentYear; + } + if ( yearSplit[1].match(/[+\-].*/) ) { + maxYear += currentYear; + } + } + + return ((!minDate || date.getTime() >= minDate.getTime()) && + (!maxDate || date.getTime() <= maxDate.getTime()) && + (!minYear || date.getFullYear() >= minYear) && + (!maxYear || date.getFullYear() <= maxYear)); + }, + + /* Provide the configuration settings for formatting/parsing. */ + _getFormatConfig: function(inst) { + var shortYearCutoff = this._get(inst, "shortYearCutoff"); + shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff : + new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); + return {shortYearCutoff: shortYearCutoff, + dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"), + monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")}; + }, + + /* Format the given date for display. */ + _formatDate: function(inst, day, month, year) { + if (!day) { + inst.currentDay = inst.selectedDay; + inst.currentMonth = inst.selectedMonth; + inst.currentYear = inst.selectedYear; + } + var date = (day ? (typeof day === "object" ? day : + this._daylightSavingAdjust(new Date(year, month, day))) : + this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); + return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst)); + } +}); + +/* + * Bind hover events for datepicker elements. + * Done via delegate so the binding only occurs once in the lifetime of the parent div. + * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. + */ +function datepicker_bindHover(dpDiv) { + var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; + return dpDiv.delegate(selector, "mouseout", function() { + $(this).removeClass("ui-state-hover"); + if (this.className.indexOf("ui-datepicker-prev") !== -1) { + $(this).removeClass("ui-datepicker-prev-hover"); + } + if (this.className.indexOf("ui-datepicker-next") !== -1) { + $(this).removeClass("ui-datepicker-next-hover"); + } + }) + .delegate( selector, "mouseover", datepicker_handleMouseover ); +} + +function datepicker_handleMouseover() { + if (!$.datepicker._isDisabledDatepicker( datepicker_instActive.inline? datepicker_instActive.dpDiv.parent()[0] : datepicker_instActive.input[0])) { + $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); + $(this).addClass("ui-state-hover"); + if (this.className.indexOf("ui-datepicker-prev") !== -1) { + $(this).addClass("ui-datepicker-prev-hover"); + } + if (this.className.indexOf("ui-datepicker-next") !== -1) { + $(this).addClass("ui-datepicker-next-hover"); + } + } +} + +/* jQuery extend now ignores nulls! */ +function datepicker_extendRemove(target, props) { + $.extend(target, props); + for (var name in props) { + if (props[name] == null) { + target[name] = props[name]; + } + } + return target; +} + +/* Invoke the datepicker functionality. + @param options string - a command, optionally followed by additional parameters or + Object - settings for attaching new datepicker functionality + @return jQuery object */ +$.fn.datepicker = function(options){ + + /* Verify an empty collection wasn't passed - Fixes #6976 */ + if ( !this.length ) { + return this; + } + + /* Initialise the date picker. */ + if (!$.datepicker.initialized) { + $(document).mousedown($.datepicker._checkExternalClick); + $.datepicker.initialized = true; + } + + /* Append datepicker main container to body if not exist. */ + if ($("#"+$.datepicker._mainDivId).length === 0) { + $("body").append($.datepicker.dpDiv); + } + + var otherArgs = Array.prototype.slice.call(arguments, 1); + if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) { + return $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this[0]].concat(otherArgs)); + } + if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") { + return $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this[0]].concat(otherArgs)); + } + return this.each(function() { + typeof options === "string" ? + $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this].concat(otherArgs)) : + $.datepicker._attachDatepicker(this, options); + }); +}; + +$.datepicker = new Datepicker(); // singleton instance +$.datepicker.initialized = false; +$.datepicker.uuid = new Date().getTime(); +$.datepicker.version = "1.11.4"; + +var datepicker = $.datepicker; + + +/*! + * jQuery UI Draggable 1.11.4 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/draggable/ + */ + + +$.widget("ui.draggable", $.ui.mouse, { + version: "1.11.4", + widgetEventPrefix: "drag", + options: { + addClasses: true, + appendTo: "parent", + axis: false, + connectToSortable: false, + containment: false, + cursor: "auto", + cursorAt: false, + grid: false, + handle: false, + helper: "original", + iframeFix: false, + opacity: false, + refreshPositions: false, + revert: false, + revertDuration: 500, + scope: "default", + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + snap: false, + snapMode: "both", + snapTolerance: 20, + stack: false, + zIndex: false, + + // callbacks + drag: null, + start: null, + stop: null + }, + _create: function() { + + if ( this.options.helper === "original" ) { + this._setPositionRelative(); + } + if (this.options.addClasses){ + this.element.addClass("ui-draggable"); + } + if (this.options.disabled){ + this.element.addClass("ui-draggable-disabled"); + } + this._setHandleClassName(); + + this._mouseInit(); + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "handle" ) { + this._removeHandleClassName(); + this._setHandleClassName(); + } + }, + + _destroy: function() { + if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) { + this.destroyOnClear = true; + return; + } + this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); + this._removeHandleClassName(); + this._mouseDestroy(); + }, + + _mouseCapture: function(event) { + var o = this.options; + + this._blurActiveElement( event ); + + // among others, prevent a drag on a resizable-handle + if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) { + return false; + } + + //Quit if we're not on a valid handle + this.handle = this._getHandle(event); + if (!this.handle) { + return false; + } + + this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix ); + + return true; + + }, + + _blockFrames: function( selector ) { + this.iframeBlocks = this.document.find( selector ).map(function() { + var iframe = $( this ); + + return $( "
      " ) + .css( "position", "absolute" ) + .appendTo( iframe.parent() ) + .outerWidth( iframe.outerWidth() ) + .outerHeight( iframe.outerHeight() ) + .offset( iframe.offset() )[ 0 ]; + }); + }, + + _unblockFrames: function() { + if ( this.iframeBlocks ) { + this.iframeBlocks.remove(); + delete this.iframeBlocks; + } + }, + + _blurActiveElement: function( event ) { + var document = this.document[ 0 ]; + + // Only need to blur if the event occurred on the draggable itself, see #10527 + if ( !this.handleElement.is( event.target ) ) { + return; + } + + // support: IE9 + // IE9 throws an "Unspecified error" accessing document.activeElement from an