test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af in contour-2.0.0.beta.9 vs test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af in contour-2.0.0.rc
- old
+ new
@@ -1,8 +1,8 @@
{I"
class:ETI"BundledAsset; FI"logical_path; TI"application.js; TI"
pathname; TI"0$root/app/assets/javascripts/application.js; FI"content_type; TI"application/javascript; TI"
-mtime; Tl+QQI"length; Ti
I"digest; TI"%baa99b268e03816f3f4b91cdda749503; FI"source; TI"
/*!
+mtime; Tl+SQI"length; Tiv&I"digest; TI"%f46777d77ba3d1a354cb94a6c76ea962; FI"source; TI"v&/*!
* jQuery JavaScript Library v1.10.2
* http://jquery.com/
*
* Includes Sizzle.js
* http://sizzlejs.com/
@@ -10190,11 +10190,11 @@
* Copyright (c) 2013 jQuery Foundation and other contributors Licensed MIT */
(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return"area"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!=="map"?!1:(o=e("img[usemap=#"+i+"]")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:"a"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return e.css(this,"visibility")==="hidden"}).length}var n=0,r=/^ui-id-\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:"1.10.0",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t=="number"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css("zIndex",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css("position");if(i==="absolute"||i==="relative"||i==="fixed"){s=parseInt(r.css("zIndex"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,"padding"+this))||0,r&&(n-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(n-=parseFloat(e.css(t,"margin"+this))||0)}),n}var i=r==="Width"?["Left","Right"]:["Top","Bottom"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+r]=function(n){return n===t?o["inner"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+"px")})},e.fn["outer"+r]=function(t,n){return typeof t!="number"?o["outer"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(n){return arguments.length?t.call(this,e.camelCase(n)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r<i.length;r++)e.options[i[r][0]]&&i[r][1].apply(e.element,n)}},hasScroll:function(t,n){if(e(t).css("overflow")==="hidden")return!1;var r=n&&n==="left"?"scrollLeft":"scrollTop",i=!1;return t[r]>0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)}})})(jQuery);(function(e,t){var n=0,r=Array.prototype.slice,i=e.cleanData;e.cleanData=function(t){for(var n=0,r;(r=t[n])!=null;n++)try{e(r).triggerHandler("remove")}catch(s){}i(t)},e.widget=function(t,n,r){var i,s,o,u,a={},f=t.split(".")[0];t=t.split(".")[1],i=f+"-"+t,r||(r=n,n=e.Widget),e.expr[":"][i.toLowerCase()]=function(t){return!!e.data(t,i)},e[f]=e[f]||{},s=e[f][t],o=e[f][t]=function(e,t){if(!this._createWidget)return new o(e,t);arguments.length&&this._createWidget(e,t)},e.extend(o,s,{version:r.version,_proto:e.extend({},r),_childConstructors:[]}),u=new n,u.options=e.widget.extend({},u.options),e.each(r,function(t,r){if(!e.isFunction(r)){a[t]=r;return}a[t]=function(){var e=function(){return n.prototype[t].apply(this,arguments)},i=function(e){return n.prototype[t].apply(this,e)};return function(){var t=this._super,n=this._superApply,s;return this._super=e,this._superApply=i,s=r.apply(this,arguments),this._super=t,this._superApply=n,s}}()}),o.prototype=e.widget.extend(u,{widgetEventPrefix:s?u.widgetEventPrefix:t},a,{constructor:o,namespace:f,widgetName:t,widgetFullName:i}),s?(e.each(s._childConstructors,function(t,n){var r=n.prototype;e.widget(r.namespace+"."+r.widgetName,o,n._proto)}),delete s._childConstructors):n._childConstructors.push(o),e.widget.bridge(t,o)},e.widget.extend=function(n){var i=r.call(arguments,1),s=0,o=i.length,u,a;for(;s<o;s++)for(u in i[s])a=i[s][u],i[s].hasOwnProperty(u)&&a!==t&&(e.isPlainObject(a)?n[u]=e.isPlainObject(n[u])?e.widget.extend({},n[u],a):e.widget.extend({},a):n[u]=a);return n},e.widget.bridge=function(n,i){var s=i.prototype.widgetFullName||n;e.fn[n]=function(o){var u=typeof o=="string",a=r.call(arguments,1),f=this;return o=!u&&a.length?e.widget.extend.apply(null,[o].concat(a)):o,u?this.each(function(){var r,i=e.data(this,s);if(!i)return e.error("cannot call methods on "+n+" prior to initialization; "+"attempted to call method '"+o+"'");if(!e.isFunction(i[o])||o.charAt(0)==="_")return e.error("no such method '"+o+"' for "+n+" widget instance");r=i[o].apply(i,a);if(r!==i&&r!==t)return f=r&&r.jquery?f.pushStack(r.get()):r,!1}):this.each(function(){var t=e.data(this,s);t?t.option(o||{})._init():e.data(this,s,new i(o,this))}),f}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n=="string"){i={},s=n.split("."),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u<s.length-1;u++)o[s[u]]=o[s[u]]||{},o=o[s[u]];n=s.pop();if(r===t)return o[n]===t?null:o[n];o[n]=r}else{if(r===t)return this.options[n]===t?null:this.options[n];i[n]=r}}return this._setOptions(i),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,e==="disabled"&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(t,n,r){var i,s=this;typeof t!="boolean"&&(r=n,n=t,t=!1),r?(n=i=e(n),this.bindings=this.bindings.add(n)):(r=n,n=this.element,i=this.widget()),e.each(r,function(r,o){function u(){if(!t&&(s.options.disabled===!0||e(this).hasClass("ui-state-disabled")))return;return(typeof o=="string"?s[o]:o).apply(s,arguments)}typeof o!="string"&&(u.guid=o.guid=o.guid||u.guid||e.guid++);var a=r.match(/^(\w+)\s*(.*)$/),f=a[1]+s.eventNamespace,l=a[2];l?i.delegate(l,f,u):n.bind(f,u)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function n(){return(typeof e=="string"?r[e]:e).apply(r,arguments)}var r=this;return setTimeout(n,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,n,r){var i,s,o=this.options[t];r=r||{},n=e.Event(n),n.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),n.target=this.element[0],s=n.originalEvent;if(s)for(i in s)i in n||(n[i]=s[i]);return this.element.trigger(n,r),!(e.isFunction(o)&&o.apply(this.element[0],[n].concat(r))===!1||n.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,n){e.Widget.prototype["_"+t]=function(r,i,s){typeof i=="string"&&(i={effect:i});var o,u=i?i===!0||typeof i=="number"?n:i.effect||n:t;i=i||{},typeof i=="number"&&(i={duration:i}),o=!e.isEmptyObject(i),i.complete=s,i.delay&&r.delay(i.delay),o&&e.effects&&e.effects.effect[u]?r[t](i):u!==t&&r[u]?r[u](i.duration,i.easing,s):r.queue(function(n){e(this)[t](),s&&s.call(r[0]),n()})}})})(jQuery);(function(e,t){var n=!1;e(document).mouseup(function(){n=!1}),e.widget("ui.mouse",{version:"1.10.0",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(n){if(!0===e.data(n.target,t.widgetName+".preventClickEvent"))return e.removeData(n.target,t.widgetName+".preventClickEvent"),n.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(n)return;this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var r=this,i=t.which===1,s=typeof this.options.cancel=="string"&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;if(!i||s||!this._mouseCapture(t))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){r.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)){this._mouseStarted=this._mouseStart(t)!==!1;if(!this._mouseStarted)return t.preventDefault(),!0}return!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return r._mouseMove(e)},this._mouseUpDelegate=function(e){return r._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),n=!0,!0},_mouseMove:function(t){return e.ui.ie&&(!document.documentMode||document.documentMode<9)&&!t.button?this._mouseUp(t):this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(e,t){e.widget("ui.draggable",e.ui.mouse,{version:"1.10.0",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){this.options.helper==="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(t){var n=this.options;return this.helper||n.disabled||e(t.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(t),this.handle?(e(n.iframeFix===!0?"iframe":n.iframeFix).each(function(){e("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(e(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(t){var n=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,n.cursorAt&&this._adjustOffsetFromHelper(n.cursorAt),n.containment&&this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_mouseDrag:function(t,n){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute");if(!n){var r=this._uiHash();if(this._trigger("drag",t,r)===!1)return this._mouseUp({}),!1;this.position=r.position}if(!this.options.axis||this.options.axis!=="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!=="x")this.helper[0].style.top=this.position.top+"px";return e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var n,r=this,i=!1,s=!1;e.ui.ddmanager&&!this.options.dropBehaviour&&(s=e.ui.ddmanager.drop(this,t)),this.dropped&&(s=this.dropped,this.dropped=!1),n=this.element[0];while(n&&(n=n.parentNode))n===document&&(i=!0);return!i&&this.options.helper==="original"?!1:(this.options.revert==="invalid"&&!s||this.options.revert==="valid"&&s||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){r._trigger("stop",t)!==!1&&r._clear()}):this._trigger("stop",t)!==!1&&this._clear(),!1)},_mouseUp:function(t){return e("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){var n=!this.options.handle||!e(this.options.handle,this.element).length?!0:!1;return e(this.options.handle,this.element).find("*").addBack().each(function(){this===t.target&&(n=!0)}),n},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t])):n.helper==="clone"?this.element.clone().removeAttr("id"):this.element;return r.parents("body").length||r.appendTo(n.appendTo==="parent"?this.element[0].parentNode:n.appendTo),r[0]!==this.element[0]&&!/(fixed|absolute)/.test(r.css("position"))&&r.css("position","absolute"),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition==="absolute"&&this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()==="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition==="relative"){var e=this.element.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,n,r,i=this.options;i.containment==="parent"&&(i.containment=this.helper[0].parentNode);if(i.containment==="document"||i.containment==="window")this.containment=[i.containment==="document"?0:e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,i.containment==="document"?0:e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(i.containment==="document"?0:e(window).scrollLeft())+e(i.containment==="document"?document:window).width()-this.helperProportions.width-this.margins.left,(i.containment==="document"?0:e(window).scrollTop())+(e(i.containment==="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(i.containment)&&i.containment.constructor!==Array){n=e(i.containment),r=n[0];if(!r)return;t=e(r).css("overflow")!=="hidden",this.containment=[(parseInt(e(r).css("borderLeftWidth"),10)||0)+(parseInt(e(r).css("paddingLeft"),10)||0),(parseInt(e(r).css("borderTopWidth"),10)||0)+(parseInt(e(r).css("paddingTop"),10)||0),(t?Math.max(r.scrollWidth,r.offsetWidth):r.offsetWidth)-(parseInt(e(r).css("borderLeftWidth"),10)||0)-(parseInt(e(r).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(t?Math.max(r.scrollHeight,r.offsetHeight):r.offsetHeight)-(parseInt(e(r).css("borderTopWidth"),10)||0)-(parseInt(e(r).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=n}else i.containment.constructor===Array&&(this.containment=i.containment)},_convertPositionTo:function(t,n){n||(n=this.position);var r=t==="absolute"?1:-1,i=this.cssPosition!=="absolute"||this.scrollParent[0]!==document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,s=/(html|body)/i.test(i[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition==="fixed"?-this.scrollParent.scrollTop():s?0:i.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():s?0:i.scrollLeft())*r}},_generatePosition:function(t){var n,r,i,s,o=this.options,u=this.cssPosition!=="absolute"||this.scrollParent[0]!==document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,a=/(html|body)/i.test(u[0].tagName),f=t.pageX,l=t.pageY;return this.originalPosition&&(this.containment&&(this.relative_container?(r=this.relative_container.offset(),n=[this.containment[0]+r.left,this.containment[1]+r.top,this.containment[2]+r.left,this.containment[3]+r.top]):n=this.containment,t.pageX-this.offset.click.left<n[0]&&(f=n[0]+this.offset.click.left),t.pageY-this.offset.click.top<n[1]&&(l=n[1]+this.offset.click.top),t.pageX-this.offset.click.left>n[2]&&(f=n[2]+this.offset.click.left),t.pageY-this.offset.click.top>n[3]&&(l=n[3]+this.offset.click.top)),o.grid&&(i=o.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,l=n?i-this.offset.click.top>=n[1]||i-this.offset.click.top>n[3]?i:i-this.offset.click.top>=n[1]?i-o.grid[1]:i+o.grid[1]:i,s=o.grid[0]?this.originalPageX+Math.round((f-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,f=n?s-this.offset.click.left>=n[0]||s-this.offset.click.left>n[2]?s:s-this.offset.click.left>=n[0]?s-o.grid[0]:s+o.grid[0]:s)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(this.cssPosition==="fixed"?-this.scrollParent.scrollTop():a?0:u.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():a?0:u.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]!==this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(t,n,r){return r=r||this._uiHash(),e.ui.plugin.call(this,t,[n,r]),t==="drag"&&(this.positionAbs=this._convertPositionTo("absolute")),e.Widget.prototype._trigger.call(this,t,n,r)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,n){var r=e(this).data("ui-draggable"),i=r.options,s=e.extend({},n,{item:r.element});r.sortables=[],e(i.connectToSortable).each(function(){var n=e.data(this,"ui-sortable");n&&!n.options.disabled&&(r.sortables.push({instance:n,shouldRevert:n.options.revert}),n.refreshPositions(),n._trigger("activate",t,s))})},stop:function(t,n){var r=e(this).data("ui-draggable"),i=e.extend({},n,{item:r.element});e.each(r.sortables,function(){this.instance.isOver?(this.instance.isOver=0,r.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=!0),this.instance._mouseStop(t),this.instance.options.helper=this.instance.options._helper,r.options.helper==="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",t,i))})},drag:function(t,n){var r=e(this).data("ui-draggable"),i=this;e.each(r.sortables,function(){var s=!1,o=this;this.instance.positionAbs=r.positionAbs,this.instance.helperProportions=r.helperProportions,this.instance.offset.click=r.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(s=!0,e.each(r.sortables,function(){return this.instance.positionAbs=r.positionAbs,this.instance.helperProportions=r.helperProportions,this.instance.offset.click=r.offset.click,this!==o&&this.instance._intersectsWith(this.instance.containerCache)&&e.ui.contains(o.instance.element[0],this.instance.element[0])&&(s=!1),s})),s?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=e(i).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return n.helper[0]},t.target=this.instance.currentItem[0],this.instance._mouseCapture(t,!0),this.instance._mouseStart(t,!0,!0),this.instance.offset.click.top=r.offset.click.top,this.instance.offset.click.left=r.offset.click.left,this.instance.offset.parent.left-=r.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=r.offset.parent.top-this.instance.offset.parent.top,r._trigger("toSortable",t),r.dropped=this.instance.element,r.currentItem=r.element,this.instance.fromOutside=r),this.instance.currentItem&&this.instance._mouseDrag(t)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",t,this.instance._uiHash(this.instance)),this.instance._mouseStop(t,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),r._trigger("fromSortable",t),r.dropped=!1)})}}),e.ui.plugin.add("draggable","cursor",{start:function(){var t=e("body"),n=e(this).data("ui-draggable").options;t.css("cursor")&&(n._cursor=t.css("cursor")),t.css("cursor",n.cursor)},stop:function(){var t=e(this).data("ui-draggable").options;t._cursor&&e("body").css("cursor",t._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,n){var r=e(n.helper),i=e(this).data("ui-draggable").options;r.css("opacity")&&(i._opacity=r.css("opacity")),r.css("opacity",i.opacity)},stop:function(t,n){var r=e(this).data("ui-draggable").options;r._opacity&&e(n.helper).css("opacity",r._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(){var t=e(this).data("ui-draggable");t.scrollParent[0]!==document&&t.scrollParent[0].tagName!=="HTML"&&(t.overflowOffset=t.scrollParent.offset())},drag:function(t){var n=e(this).data("ui-draggable"),r=n.options,i=!1;if(n.scrollParent[0]!==document&&n.scrollParent[0].tagName!=="HTML"){if(!r.axis||r.axis!=="x")n.overflowOffset.top+n.scrollParent[0].offsetHeight-t.pageY<r.scrollSensitivity?n.scrollParent[0].scrollTop=i=n.scrollParent[0].scrollTop+r.scrollSpeed:t.pageY-n.overflowOffset.top<r.scrollSensitivity&&(n.scrollParent[0].scrollTop=i=n.scrollParent[0].scrollTop-r.scrollSpeed);if(!r.axis||r.axis!=="y")n.overflowOffset.left+n.scrollParent[0].offsetWidth-t.pageX<r.scrollSensitivity?n.scrollParent[0].scrollLeft=i=n.scrollParent[0].scrollLeft+r.scrollSpeed:t.pageX-n.overflowOffset.left<r.scrollSensitivity&&(n.scrollParent[0].scrollLeft=i=n.scrollParent[0].scrollLeft-r.scrollSpeed)}else{if(!r.axis||r.axis!=="x")t.pageY-e(document).scrollTop()<r.scrollSensitivity?i=e(document).scrollTop(e(document).scrollTop()-r.scrollSpeed):e(window).height()-(t.pageY-e(document).scrollTop())<r.scrollSensitivity&&(i=e(document).scrollTop(e(document).scrollTop()+r.scrollSpeed));if(!r.axis||r.axis!=="y")t.pageX-e(document).scrollLeft()<r.scrollSensitivity?i=e(document).scrollLeft(e(document).scrollLeft()-r.scrollSpeed):e(window).width()-(t.pageX-e(document).scrollLeft())<r.scrollSensitivity&&(i=e(document).scrollLeft(e(document).scrollLeft()+r.scrollSpeed))}i!==!1&&e.ui.ddmanager&&!r.dropBehaviour&&e.ui.ddmanager.prepareOffsets(n,t)}}),e.ui.plugin.add("draggable","snap",{start:function(){var t=e(this).data("ui-draggable"),n=t.options;t.snapElements=[],e(n.snap.constructor!==String?n.snap.items||":data(ui-draggable)":n.snap).each(function(){var n=e(this),r=n.offset();this!==t.element[0]&&t.snapElements.push({item:this,width:n.outerWidth(),height:n.outerHeight(),top:r.top,left:r.left})})},drag:function(t,n){var r,i,s,o,u,a,f,l,c,h,p=e(this).data("ui-draggable"),d=p.options,v=d.snapTolerance,m=n.offset.left,g=m+p.helperProportions.width,y=n.offset.top,b=y+p.helperProportions.height;for(c=p.snapElements.length-1;c>=0;c--){u=p.snapElements[c].left,a=u+p.snapElements[c].width,f=p.snapElements[c].top,l=f+p.snapElements[c].height;if(!(u-v<m&&m<a+v&&f-v<y&&y<l+v||u-v<m&&m<a+v&&f-v<b&&b<l+v||u-v<g&&g<a+v&&f-v<y&&y<l+v||u-v<g&&g<a+v&&f-v<b&&b<l+v)){p.snapElements[c].snapping&&p.options.snap.release&&p.options.snap.release.call(p.element,t,e.extend(p._uiHash(),{snapItem:p.snapElements[c].item})),p.snapElements[c].snapping=!1;continue}d.snapMode!=="inner"&&(r=Math.abs(f-b)<=v,i=Math.abs(l-y)<=v,s=Math.abs(u-g)<=v,o=Math.abs(a-m)<=v,r&&(n.position.top=p._convertPositionTo("relative",{top:f-p.helperProportions.height,left:0}).top-p.margins.top),i&&(n.position.top=p._convertPositionTo("relative",{top:l,left:0}).top-p.margins.top),s&&(n.position.left=p._convertPositionTo("relative",{top:0,left:u-p.helperProportions.width}).left-p.margins.left),o&&(n.position.left=p._convertPositionTo("relative",{top:0,left:a}).left-p.margins.left)),h=r||i||s||o,d.snapMode!=="outer"&&(r=Math.abs(f-y)<=v,i=Math.abs(l-b)<=v,s=Math.abs(u-m)<=v,o=Math.abs(a-g)<=v,r&&(n.position.top=p._convertPositionTo("relative",{top:f,left:0}).top-p.margins.top),i&&(n.position.top=p._convertPositionTo("relative",{top:l-p.helperProportions.height,left:0}).top-p.margins.top),s&&(n.position.left=p._convertPositionTo("relative",{top:0,left:u}).left-p.margins.left),o&&(n.position.left=p._convertPositionTo("relative",{top:0,left:a-p.helperProportions.width}).left-p.margins.left)),!p.snapElements[c].snapping&&(r||i||s||o||h)&&p.options.snap.snap&&p.options.snap.snap.call(p.element,t,e.extend(p._uiHash(),{snapItem:p.snapElements[c].item})),p.snapElements[c].snapping=r||i||s||o||h}}}),e.ui.plugin.add("draggable","stack",{start:function(){var t,n=e(this).data("ui-draggable").options,r=e.makeArray(e(n.stack)).sort(function(t,n){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(n).css("zIndex"),10)||0)});if(!r.length)return;t=parseInt(r[0].style.zIndex,10)||0,e(r).each(function(e){this.style.zIndex=t+e}),this[0].style.zIndex=t+r.length}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,n){var r=e(n.helper),i=e(this).data("ui-draggable").options;r.css("zIndex")&&(i._zIndex=r.css("zIndex")),r.css("zIndex",i.zIndex)},stop:function(t,n){var r=e(this).data("ui-draggable").options;r._zIndex&&e(n.helper).css("zIndex",r._zIndex)}})})(jQuery);(function(e,t){function n(e,t,n){return e>t&&e<t+n}e.widget("ui.droppable",{version:"1.10.0",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var t=this.options,n=t.accept;this.isover=!1,this.isout=!0,this.accept=e.isFunction(n)?n:function(e){return e.is(n)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},e.ui.ddmanager.droppables[t.scope]=e.ui.ddmanager.droppables[t.scope]||[],e.ui.ddmanager.droppables[t.scope].push(this),t.addClasses&&this.element.addClass("ui-droppable")},_destroy:function(){var t=0,n=e.ui.ddmanager.droppables[this.options.scope];for(;t<n.length;t++)n[t]===this&&n.splice(t,1);this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(t,n){t==="accept"&&(this.accept=e.isFunction(n)?n:function(e){return e.is(n)}),e.Widget.prototype._setOption.apply(this,arguments)},_activate:function(t){var n=e.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),n&&this._trigger("activate",t,this.ui(n))},_deactivate:function(t){var n=e.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),n&&this._trigger("deactivate",t,this.ui(n))},_over:function(t){var n=e.ui.ddmanager.current;if(!n||(n.currentItem||n.element)[0]===this.element[0])return;this.accept.call(this.element[0],n.currentItem||n.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",t,this.ui(n)))},_out:function(t){var n=e.ui.ddmanager.current;if(!n||(n.currentItem||n.element)[0]===this.element[0])return;this.accept.call(this.element[0],n.currentItem||n.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",t,this.ui(n)))},_drop:function(t,n){var r=n||e.ui.ddmanager.current,i=!1;return!r||(r.currentItem||r.element)[0]===this.element[0]?!1:(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var t=e.data(this,"ui-droppable");if(t.options.greedy&&!t.options.disabled&&t.options.scope===r.options.scope&&t.accept.call(t.element[0],r.currentItem||r.element)&&e.ui.intersect(r,e.extend(t,{offset:t.element.offset()}),t.options.tolerance))return i=!0,!1}),i?!1:this.accept.call(this.element[0],r.currentItem||r.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",t,this.ui(r)),this.element):!1)},ui:function(e){return{draggable:e.currentItem||e.element,helper:e.helper,position:e.position,offset:e.positionAbs}}}),e.ui.intersect=function(e,t,r){if(!t.offset)return!1;var i,s,o=(e.positionAbs||e.position.absolute).left,u=o+e.helperProportions.width,a=(e.positionAbs||e.position.absolute).top,f=a+e.helperProportions.height,l=t.offset.left,c=l+t.proportions.width,h=t.offset.top,p=h+t.proportions.height;switch(r){case"fit":return l<=o&&u<=c&&h<=a&&f<=p;case"intersect":return l<o+e.helperProportions.width/2&&u-e.helperProportions.width/2<c&&h<a+e.helperProportions.height/2&&f-e.helperProportions.height/2<p;case"pointer":return i=(e.positionAbs||e.position.absolute).left+(e.clickOffset||e.offset.click).left,s=(e.positionAbs||e.position.absolute).top+(e.clickOffset||e.offset.click).top,n(s,h,t.proportions.height)&&n(i,l,t.proportions.width);case"touch":return(a>=h&&a<=p||f>=h&&f<=p||a<h&&f>p)&&(o>=l&&o<=c||u>=l&&u<=c||o<l&&u>c);default:return!1}},e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,n){var r,i,s=e.ui.ddmanager.droppables[t.options.scope]||[],o=n?n.type:null,u=(t.currentItem||t.element).find(":data(ui-droppable)").addBack();e:for(r=0;r<s.length;r++){if(s[r].options.disabled||t&&!s[r].accept.call(s[r].element[0],t.currentItem||t.element))continue;for(i=0;i<u.length;i++)if(u[i]===s[r].element[0]){s[r].proportions.height=0;continue e}s[r].visible=s[r].element.css("display")!=="none";if(!s[r].visible)continue;o==="mousedown"&&s[r]._activate.call(s[r],n),s[r].offset=s[r].element.offset(),s[r].proportions={width:s[r].element[0].offsetWidth,height:s[r].element[0].offsetHeight}}},drop:function(t,n){var r=!1;return e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(!this.options)return;!this.options.disabled&&this.visible&&e.ui.intersect(t,this,this.options.tolerance)&&(r=this._drop.call(this,n)||r),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],t.currentItem||t.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,n))}),r},dragStart:function(t,n){t.element.parentsUntil("body").bind("scroll.droppable",function(){t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,n)})},drag:function(t,n){t.options.refreshPositions&&e.ui.ddmanager.prepareOffsets(t,n),e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(this.options.disabled||this.greedyChild||!this.visible)return;var r,i,s,o=e.ui.intersect(t,this,this.options.tolerance),u=!o&&this.isover?"isout":o&&!this.isover?"isover":null;if(!u)return;this.options.greedy&&(i=this.options.scope,s=this.element.parents(":data(ui-droppable)").filter(function(){return e.data(this,"ui-droppable").options.scope===i}),s.length&&(r=e.data(s[0],"ui-droppable"),r.greedyChild=u==="isover")),r&&u==="isover"&&(r.isover=!1,r.isout=!0,r._out.call(r,n)),this[u]=!0,this[u==="isout"?"isover":"isout"]=!1,this[u==="isover"?"_over":"_out"].call(this,n),r&&u==="isout"&&(r.isout=!1,r.isover=!0,r._over.call(r,n))})},dragStop:function(t,n){t.element.parentsUntil("body").unbind("scroll.droppable"),t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,n)}}})(jQuery);(function(e,t){function n(e,t,n){return e>t&&e<t+n}e.widget("ui.sortable",e.ui.mouse,{version:"1.10.0",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var e=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?e.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_setOption:function(t,n){t==="disabled"?(this.options[t]=n,this.widget().toggleClass("ui-sortable-disabled",!!n)):e.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(t,n){var r=null,i=!1,s=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type==="static")return!1;this._refreshItems(t),e(t.target).parents().each(function(){if(e.data(this,s.widgetName+"-item")===s)return r=e(this),!1}),e.data(t.target,s.widgetName+"-item")===s&&(r=e(t.target));if(!r)return!1;if(this.options.handle&&!n){e(this.options.handle,r).find("*").addBack().each(function(){this===t.target&&(i=!0)});if(!i)return!1}return this.currentItem=r,this._removeCurrentsFromItems(),!0},_mouseStart:function(t,n,r){var i,s=this.options;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,s.cursorAt&&this._adjustOffsetFromHelper(s.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),s.containment&&this._setContainment(),s.cursor&&(e("body").css("cursor")&&(this._storedCursor=e("body").css("cursor")),e("body").css("cursor",s.cursor)),s.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",s.opacity)),s.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",s.zIndex)),this.scrollParent[0]!==document&&this.scrollParent[0].tagName!=="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!r)for(i=this.containers.length-1;i>=0;i--)this.containers[i]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!s.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){var n,r,i,s,o=this.options,u=!1;this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&this.scrollParent[0].tagName!=="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY<o.scrollSensitivity?this.scrollParent[0].scrollTop=u=this.scrollParent[0].scrollTop+o.scrollSpeed:t.pageY-this.overflowOffset.top<o.scrollSensitivity&&(this.scrollParent[0].scrollTop=u=this.scrollParent[0].scrollTop-o.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-t.pageX<o.scrollSensitivity?this.scrollParent[0].scrollLeft=u=this.scrollParent[0].scrollLeft+o.scrollSpeed:t.pageX-this.overflowOffset.left<o.scrollSensitivity&&(this.scrollParent[0].scrollLeft=u=this.scrollParent[0].scrollLeft-o.scrollSpeed)):(t.pageY-e(document).scrollTop()<o.scrollSensitivity?u=e(document).scrollTop(e(document).scrollTop()-o.scrollSpeed):e(window).height()-(t.pageY-e(document).scrollTop())<o.scrollSensitivity&&(u=e(document).scrollTop(e(document).scrollTop()+o.scrollSpeed)),t.pageX-e(document).scrollLeft()<o.scrollSensitivity?u=e(document).scrollLeft(e(document).scrollLeft()-o.scrollSpeed):e(window).width()-(t.pageX-e(document).scrollLeft())<o.scrollSensitivity&&(u=e(document).scrollLeft(e(document).scrollLeft()+o.scrollSpeed))),u!==!1&&e.ui.ddmanager&&!o.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t)),this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!=="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!=="x")this.helper[0].style.top=this.position.top+"px";for(n=this.items.length-1;n>=0;n--){r=this.items[n],i=r.item[0],s=this._intersectsWithPointer(r);if(!s)continue;if(r.instance!==this.currentContainer)continue;if(i!==this.currentItem[0]&&this.placeholder[s===1?"next":"prev"]()[0]!==i&&!e.contains(this.placeholder[0],i)&&(this.options.type==="semi-dynamic"?!e.contains(this.element[0],i):!0)){this.direction=s===1?"down":"up";if(this.options.tolerance!=="pointer"&&!this._intersectsWithSides(r))break;this._rearrange(t,r),this._trigger("change",t,this._uiHash());break}}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,n){if(!t)return;e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t);if(this.options.revert){var r=this,i=this.placeholder.offset();this.reverting=!0,e(this.helper).animate({left:i.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft),top:i.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){r._clear(t)})}else this._clear(t,n);return!1},cancel:function(){if(this.dragging){this._mouseUp({target:null}),this.options.helper==="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!=="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},e(n).each(function(){var n=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[\-=_](.+)/);n&&r.push((t.key||n[1]+"[]")+"="+(t.key&&t.expression?n[1]:n[2]))}),!r.length&&t.key&&r.push(t.key+"="),r.join("&")},toArray:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},n.each(function(){r.push(e(t.item||this).attr(t.attribute||"id")||"")}),r},_intersectsWith:function(e){var t=this.positionAbs.left,n=t+this.helperProportions.width,r=this.positionAbs.top,i=r+this.helperProportions.height,s=e.left,o=s+e.width,u=e.top,a=u+e.height,f=this.offset.click.top,l=this.offset.click.left,c=r+f>u&&r+f<a&&t+l>s&&t+l<o;return this.options.tolerance==="pointer"||this.options.forcePointerForContainers||this.options.tolerance!=="pointer"&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?c:s<t+this.helperProportions.width/2&&n-this.helperProportions.width/2<o&&u<r+this.helperProportions.height/2&&i-this.helperProportions.height/2<a},_intersectsWithPointer:function(e){var t=this.options.axis==="x"||n(this.positionAbs.top+this.offset.click.top,e.top,e.height),r=this.options.axis==="y"||n(this.positionAbs.left+this.offset.click.left,e.left,e.width),i=t&&r,s=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return i?this.floating?o&&o==="right"||s==="down"?2:1:s&&(s==="down"?2:1):!1},_intersectsWithSides:function(e){var t=n(this.positionAbs.top+this.offset.click.top,e.top+e.height/2,e.height),r=n(this.positionAbs.left+this.offset.click.left,e.left+e.width/2,e.width),i=this._getDragVerticalDirection(),s=this._getDragHorizontalDirection();return this.floating&&s?s==="right"&&r||s==="left"&&!r:i&&(i==="down"&&t||i==="up"&&!t)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return e!==0&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return e!==0&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor===String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){var n,r,i,s,o=[],u=[],a=this._connectWith();if(a&&t)for(n=a.length-1;n>=0;n--){i=e(a[n]);for(r=i.length-1;r>=0;r--)s=e.data(i[r],this.widgetFullName),s&&s!==this&&!s.options.disabled&&u.push([e.isFunction(s.options.items)?s.options.items.call(s.element):e(s.options.items,s.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),s])}u.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(n=u.length-1;n>=0;n--)u[n][0].each(function(){o.push(this)});return e(o)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var n=0;n<t.length;n++)if(t[n]===e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var n,r,i,s,o,u,a,f,l=this.items,c=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],h=this._connectWith();if(h&&this.ready)for(n=h.length-1;n>=0;n--){i=e(h[n]);for(r=i.length-1;r>=0;r--)s=e.data(i[r],this.widgetFullName),s&&s!==this&&!s.options.disabled&&(c.push([e.isFunction(s.options.items)?s.options.items.call(s.element[0],t,{item:this.currentItem}):e(s.options.items,s.element),s]),this.containers.push(s))}for(n=c.length-1;n>=0;n--){o=c[n][1],u=c[n][0];for(r=0,f=u.length;r<f;r++)a=e(u[r]),a.data(this.widgetName+"-item",o),l.push({item:a,instance:o,width:0,height:0,left:0,top:0})}},refreshPositions:function(t){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var n,r,i,s;for(n=this.items.length-1;n>=0;n--){r=this.items[n];if(r.instance!==this.currentContainer&&this.currentContainer&&r.item[0]!==this.currentItem[0])continue;i=this.options.toleranceElement?e(this.options.toleranceElement,r.item):r.item,t||(r.width=i.outerWidth(),r.height=i.outerHeight()),s=i.offset(),r.left=s.left,r.top=s.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(n=this.containers.length-1;n>=0;n--)s=this.containers[n].element.offset(),this.containers[n].containerCache.left=s.left,this.containers[n].containerCache.top=s.top,this.containers[n].containerCache.width=this.containers[n].element.outerWidth(),this.containers[n].containerCache.height=this.containers[n].element.outerHeight();return this},_createPlaceholder:function(t){t=t||this;var n,r=t.options;if(!r.placeholder||r.placeholder.constructor===String)n=r.placeholder,r.placeholder={element:function(){var r=e(document.createElement(t.currentItem[0].nodeName)).addClass(n||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return n||(r.style.visibility="hidden"),r},update:function(e,i){if(n&&!r.forcePlaceholderSize)return;i.height()||i.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),i.width()||i.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10))}};t.placeholder=e(r.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),r.placeholder.update(t,t.placeholder)},_contactContainers:function(t){var n,r,i,s,o,u,a,f,l,c=null,h=null;for(n=this.containers.length-1;n>=0;n--){if(e.contains(this.currentItem[0],this.containers[n].element[0]))continue;if(this._intersectsWith(this.containers[n].containerCache)){if(c&&e.contains(this.containers[n].element[0],c.element[0]))continue;c=this.containers[n],h=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger("out",t,this._uiHash(this)),this.containers[n].containerCache.over=0)}if(!c)return;if(this.containers.length===1)this.containers[h]._trigger("over",t,this._uiHash(this)),this.containers[h].containerCache.over=1;else{i=1e4,s=null,o=this.containers[h].floating?"left":"top",u=this.containers[h].floating?"width":"height",a=this.positionAbs[o]+this.offset.click[o];for(r=this.items.length-1;r>=0;r--){if(!e.contains(this.containers[h].element[0],this.items[r].item[0]))continue;if(this.items[r].item[0]===this.currentItem[0])continue;f=this.items[r].item.offset()[o],l=!1,Math.abs(f-a)>Math.abs(f+this.items[r][u]-a)&&(l=!0,f+=this.items[r][u]),Math.abs(f-a)<i&&(i=Math.abs(f-a),s=this.items[r],this.direction=l?"up":"down")}if(!s&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[h],s?this._rearrange(t,s,null,!0):this._rearrange(t,null,this.containers[h].element,!0),this._trigger("change",t,this._uiHash()),this.containers[h]._trigger("change",t,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[h]._trigger("over",t,this._uiHash(this)),this.containers[h].containerCache.over=1}},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t,this.currentItem])):n.helper==="clone"?this.currentItem.clone():this.currentItem;return r.parents("body").length||e(n.appendTo!=="parent"?n.appendTo:this.currentItem[0].parentNode)[0].appendChild(r[0]),r[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!r[0].style.width||n.forceHelperSize)&&r.width(this.currentItem.width()),(!r[0].style.height||n.forceHelperSize)&&r.height(this.currentItem.height()),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition==="absolute"&&this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()==="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition==="relative"){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,n,r,i=this.options;i.containment==="parent"&&(i.containment=this.helper[0].parentNode);if(i.containment==="document"||i.containment==="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,e(i.containment==="document"?document:window).width()-this.helperProportions.width-this.margins.left,(e(i.containment==="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];/^(document|window|parent)$/.test(i.containment)||(t=e(i.containment)[0],n=e(i.containment).offset(),r=e(t).css("overflow")!=="hidden",this.containment=[n.left+(parseInt(e(t).css("borderLeftWidth"),10)||0)+(parseInt(e(t).css("paddingLeft"),10)||0)-this.margins.left,n.top+(parseInt(e(t).css("borderTopWidth"),10)||0)+(parseInt(e(t).css("paddingTop"),10)||0)-this.margins.top,n.left+(r?Math.max(t.scrollWidth,t.offsetWidth):t.offsetWidth)-(parseInt(e(t).css("borderLeftWidth"),10)||0)-(parseInt(e(t).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,n.top+(r?Math.max(t.scrollHeight,t.offsetHeight):t.offsetHeight)-(parseInt(e(t).css("borderTopWidth"),10)||0)-(parseInt(e(t).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(t,n){n||(n=this.position);var r=t==="absolute"?1:-1,i=this.cssPosition!=="absolute"||this.scrollParent[0]!==document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,s=/(html|body)/i.test(i[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition==="fixed"?-this.scrollParent.scrollTop():s?0:i.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():s?0:i.scrollLeft())*r}},_generatePosition:function(t){var n,r,i=this.options,s=t.pageX,o=t.pageY,u=this.cssPosition!=="absolute"||this.scrollParent[0]!==document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,a=/(html|body)/i.test(u[0].tagName);return this.cssPosition==="relative"&&(this.scrollParent[0]===document||this.scrollParent[0]===this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(t.pageX-this.offset.click.left<this.containment[0]&&(s=this.containment[0]+this.offset.click.left),t.pageY-this.offset.click.top<this.containment[1]&&(o=this.containment[1]+this.offset.click.top),t.pageX-this.offset.click.left>this.containment[2]&&(s=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top)),i.grid&&(n=this.originalPageY+Math.round((o-this.originalPageY)/i.grid[1])*i.grid[1],o=this.containment?n-this.offset.click.top>=this.containment[1]&&n-this.offset.click.top<=this.containment[3]?n:n-this.offset.click.top>=this.containment[1]?n-i.grid[1]:n+i.grid[1]:n,r=this.originalPageX+Math.round((s-this.originalPageX)/i.grid[0])*i.grid[0],s=this.containment?r-this.offset.click.left>=this.containment[0]&&r-this.offset.click.left<=this.containment[2]?r:r-this.offset.click.left>=this.containment[0]?r-i.grid[0]:r+i.grid[0]:r)),{top:o-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(this.cssPosition==="fixed"?-this.scrollParent.scrollTop():a?0:u.scrollTop()),left:s-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():a?0:u.scrollLeft())}},_rearrange:function(e,t,n,r){n?n[0].appendChild(this.placeholder[0]):t.item[0].parentNode.insertBefore(this.placeholder[0],this.direction==="down"?t.item[0]:t.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var i=this.counter;this._delay(function(){i===this.counter&&this.refreshPositions(!r)})},_clear:function(t,n){this.reverting=!1;var r,i=[];!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null;if(this.helper[0]===this.currentItem[0]){for(r in this._storedCSS)if(this._storedCSS[r]==="auto"||this._storedCSS[r]==="static")this._storedCSS[r]="";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!n&&i.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))}),(this.fromOutside||this.domPosition.prev!==this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!==this.currentItem.parent()[0])&&!n&&i.push(function(e){this._trigger("update",e,this._uiHash())}),this!==this.currentContainer&&(n||(i.push(function(e){this._trigger("remove",e,this._uiHash())}),i.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),i.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer))));for(r=this.containers.length-1;r>=0;r--)n||i.push(function(e){return function(t){e._trigger("deactivate",t,this._uiHash(this))}}.call(this,this.containers[r])),this.containers[r].containerCache.over&&(i.push(function(e){return function(t){e._trigger("out",t,this._uiHash(this))}}.call(this,this.containers[r])),this.containers[r].containerCache.over=0);this._storedCursor&&e("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex==="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!n){this._trigger("beforeStop",t,this._uiHash());for(r=0;r<i.length;r++)i[r].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}n||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null;if(!n){for(r=0;r<i.length;r++)i[r].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){e.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(t){var n=t||this;return{helper:n.helper,placeholder:n.placeholder||e([]),position:n.position,originalPosition:n.originalPosition,offset:n.positionAbs,item:n.currentItem,sender:t?t.element:null}}})})(jQuery);jQuery.effects||function(e,t){var n="ui-effects-";e.effects={effect:{}},function(e,t){function h(e,t,n){var r=u[t.type]||{};return e==null?n||!t.def?null:t.def:(e=r.floor?~~e:parseFloat(e),isNaN(e)?t.def:r.mod?(e+r.mod)%r.mod:0>e?0:r.max<e?r.max:e)}function p(t){var n=s(),r=n._rgba=[];return t=t.toLowerCase(),c(i,function(e,i){var s,u=i.re.exec(t),a=u&&i.parse(u),f=i.space||"rgba";if(a)return s=n[f](a),n[o[f].cache]=s[o[f].cache],r=n._rgba=s._rgba,!1}),r.length?(r.join()==="0,0,0,0"&&e.extend(r,l.transparent),n):l[t]}function d(e,t,n){return n=(n+1)%1,n*6<1?e+(t-e)*n*6:n*2<1?t:n*3<2?e+(t-e)*(2/3-n)*6:e}var n="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,i=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1],e[2],e[3],e[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1]*2.55,e[2]*2.55,e[3]*2.55,e[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(e){return[e[1],e[2]/100,e[3]/100,e[4]]}}],s=e.Color=function(t,n,r,i){return new e.Color.fn.parse(t,n,r,i)},o={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},a=s.support={},f=e("<p>")[0],l,c=e.each;f.style.cssText="background-color:rgba(1,1,1,.5)",a.rgba=f.style.backgroundColor.indexOf("rgba")>-1,c(o,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),s.fn=e.extend(s.prototype,{parse:function(n,r,i,u){if(n===t)return this._rgba=[null,null,null,null],this;if(n.jquery||n.nodeType)n=e(n).css(r),r=t;var a=this,f=e.type(n),d=this._rgba=[];r!==t&&(n=[n,r,i,u],f="array");if(f==="string")return this.parse(p(n)||l._default);if(f==="array")return c(o.rgba.props,function(e,t){d[t.idx]=h(n[t.idx],t)}),this;if(f==="object")return n instanceof s?c(o,function(e,t){n[t.cache]&&(a[t.cache]=n[t.cache].slice())}):c(o,function(t,r){var i=r.cache;c(r.props,function(e,t){if(!a[i]&&r.to){if(e==="alpha"||n[e]==null)return;a[i]=r.to(a._rgba)}a[i][t.idx]=h(n[e],t,!0)}),a[i]&&e.inArray(null,a[i].slice(0,3))<0&&(a[i][3]=1,r.from&&(a._rgba=r.from(a[i])))}),this},is:function(e){var t=s(e),n=!0,r=this;return c(o,function(e,i){var s,o=t[i.cache];return o&&(s=r[i.cache]||i.to&&i.to(r._rgba)||[],c(i.props,function(e,t){if(o[t.idx]!=null)return n=o[t.idx]===s[t.idx],n})),n}),n},_space:function(){var e=[],t=this;return c(o,function(n,r){t[r.cache]&&e.push(n)}),e.pop()},transition:function(e,t){var n=s(e),r=n._space(),i=o[r],a=this.alpha()===0?s("transparent"):this,f=a[i.cache]||i.to(a._rgba),l=f.slice();return n=n[i.cache],c(i.props,function(e,r){var i=r.idx,s=f[i],o=n[i],a=u[r.type]||{};if(o===null)return;s===null?l[i]=o:(a.mod&&(o-s>a.mod/2?s+=a.mod:s-o>a.mod/2&&(s-=a.mod)),l[i]=h((o-s)*t+s,r))}),this[r](l)},blend:function(t){if(this._rgba[3]===1)return this;var n=this._rgba.slice(),r=n.pop(),i=s(t)._rgba;return s(e.map(n,function(e,t){return(1-r)*i[t]+r*e}))},toRgbaString:function(){var t="rgba(",n=e.map(this._rgba,function(e,t){return e==null?t>2?1:0:e});return n[3]===1&&(n.pop(),t="rgb("),t+n.join()+")"},toHslaString:function(){var t="hsla(",n=e.map(this.hsla(),function(e,t){return e==null&&(e=t>2?1:0),t&&t<3&&(e=Math.round(e*100)+"%"),e});return n[3]===1&&(n.pop(),t="hsl("),t+n.join()+")"},toHexString:function(t){var n=this._rgba.slice(),r=n.pop();return t&&n.push(~~(r*255)),"#"+e.map(n,function(e){return e=(e||0).toString(16),e.length===1?"0"+e:e}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),s.fn.parse.prototype=s.fn,o.hsla.to=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/255,n=e[1]/255,r=e[2]/255,i=e[3],s=Math.max(t,n,r),o=Math.min(t,n,r),u=s-o,a=s+o,f=a*.5,l,c;return o===s?l=0:t===s?l=60*(n-r)/u+360:n===s?l=60*(r-t)/u+120:l=60*(t-n)/u+240,u===0?c=0:f<=.5?c=u/a:c=u/(2-a),[Math.round(l)%360,c,f,i==null?1:i]},o.hsla.from=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/360,n=e[1],r=e[2],i=e[3],s=r<=.5?r*(1+n):r+n-r*n,o=2*r-s;return[Math.round(d(o,s,t+1/3)*255),Math.round(d(o,s,t)*255),Math.round(d(o,s,t-1/3)*255),i]},c(o,function(n,i){var o=i.props,u=i.cache,a=i.to,f=i.from;s.fn[n]=function(n){a&&!this[u]&&(this[u]=a(this._rgba));if(n===t)return this[u].slice();var r,i=e.type(n),l=i==="array"||i==="object"?n:arguments,p=this[u].slice();return c(o,function(e,t){var n=l[i==="object"?e:t.idx];n==null&&(n=p[t.idx]),p[t.idx]=h(n,t)}),f?(r=s(f(p)),r[u]=p,r):s(p)},c(o,function(t,i){if(s.fn[t])return;s.fn[t]=function(s){var o=e.type(s),u=t==="alpha"?this._hsla?"hsla":"rgba":n,a=this[u](),f=a[i.idx],l;return o==="undefined"?f:(o==="function"&&(s=s.call(this,f),o=e.type(s)),s==null&&i.empty?this:(o==="string"&&(l=r.exec(s),l&&(s=f+parseFloat(l[2])*(l[1]==="+"?1:-1))),a[i.idx]=s,this[u](a)))}})}),s.hook=function(t){var n=t.split(" ");c(n,function(t,n){e.cssHooks[n]={set:function(t,r){var i,o,u="";if(r!=="transparent"&&(e.type(r)!=="string"||(i=p(r)))){r=s(i||r);if(!a.rgba&&r._rgba[3]!==1){o=n==="backgroundColor"?t.parentNode:t;while((u===""||u==="transparent")&&o&&o.style)try{u=e.css(o,"backgroundColor"),o=o.parentNode}catch(f){}r=r.blend(u&&u!=="transparent"?u:"_default")}r=r.toRgbaString()}try{t.style[n]=r}catch(f){}}},e.fx.step[n]=function(t){t.colorInit||(t.start=s(t.elem,n),t.end=s(t.end),t.colorInit=!0),e.cssHooks[n].set(t.elem,t.start.transition(t.end,t.pos))}})},s.hook(n),e.cssHooks.borderColor={expand:function(e){var t={};return c(["Top","Right","Bottom","Left"],function(n,r){t["border"+r+"Color"]=e}),t}},l=e.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(t){var n,r,i=t.ownerDocument.defaultView?t.ownerDocument.defaultView.getComputedStyle(t,null):t.currentStyle,s={};if(i&&i.length&&i[0]&&i[i[0]]){r=i.length;while(r--)n=i[r],typeof i[n]=="string"&&(s[e.camelCase(n)]=i[n])}else for(n in i)typeof i[n]=="string"&&(s[n]=i[n]);return s}function s(t,n){var i={},s,o;for(s in n)o=n[s],t[s]!==o&&!r[s]&&(e.fx.step[s]||!isNaN(parseFloat(o)))&&(i[s]=o);return i}var n=["add","remove","toggle"],r={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,n){e.fx.step[n]=function(e){if(e.end!=="none"&&!e.setAttr||e.pos===1&&!e.setAttr)jQuery.style(e.elem,n,e.end),e.setAttr=!0}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}),e.effects.animateClass=function(t,r,o,u){var a=e.speed(r,o,u);return this.queue(function(){var r=e(this),o=r.attr("class")||"",u,f=a.children?r.find("*").addBack():r;f=f.map(function(){var t=e(this);return{el:t,start:i(this)}}),u=function(){e.each(n,function(e,n){t[n]&&r[n+"Class"](t[n])})},u(),f=f.map(function(){return this.end=i(this.el[0]),this.diff=s(this.start,this.end),this}),r.attr("class",o),f=f.map(function(){var t=this,n=e.Deferred(),r=e.extend({},a,{queue:!1,complete:function(){n.resolve(t)}});return this.el.animate(this.diff,r),n.promise()}),e.when.apply(e,f.get()).done(function(){u(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),a.complete.call(r[0])})})},e.fn.extend({_addClass:e.fn.addClass,addClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{add:t},n,r,i):this._addClass(t)},_removeClass:e.fn.removeClass,removeClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{remove:t},n,r,i):this._removeClass(t)},_toggleClass:e.fn.toggleClass,toggleClass:function(n,r,i,s,o){return typeof r=="boolean"||r===t?i?e.effects.animateClass.call(this,r?{add:n}:{remove:n},i,s,o):this._toggleClass(n,r):e.effects.animateClass.call(this,{toggle:n},r,i,s)},switchClass:function(t,n,r,i,s){return e.effects.animateClass.call(this,{add:n,remove:t},r,i,s)}})}(),function(){function r(t,n,r,i){e.isPlainObject(t)&&(n=t,t=t.effect),t={effect:t},n==null&&(n={}),e.isFunction(n)&&(i=n,r=null,n={});if(typeof n=="number"||e.fx.speeds[n])i=r,r=n,n={};return e.isFunction(r)&&(i=r,r=null),n&&e.extend(t,n),r=r||n.duration,t.duration=e.fx.off?0:typeof r=="number"?r:r in e.fx.speeds?e.fx.speeds[r]:e.fx.speeds._default,t.complete=i||n.complete,t}function i(t){return!t||typeof t=="number"||e.fx.speeds[t]?!0:typeof t=="string"&&!e.effects.effect[t]}e.extend(e.effects,{version:"1.10.0",save:function(e,t){for(var r=0;r<t.length;r++)t[r]!==null&&e.data(n+t[r],e[0].style[t[r]])},restore:function(e,r){var i,s;for(s=0;s<r.length;s++)r[s]!==null&&(i=e.data(n+r[s]),i===t&&(i=""),e.css(r[s],i))},setMode:function(e,t){return t==="toggle"&&(t=e.is(":hidden")?"show":"hide"),t},getBaseline:function(e,t){var n,r;switch(e[0]){case"top":n=0;break;case"middle":n=.5;break;case"bottom":n=1;break;default:n=e[0]/t.height}switch(e[1]){case"left":r=0;break;case"center":r=.5;break;case"right":r=1;break;default:r=e[1]/t.width}return{x:r,y:n}},createWrapper:function(t){if(t.parent().is(".ui-effects-wrapper"))return t.parent();var n={width:t.outerWidth(!0),height:t.outerHeight(!0),"float":t.css("float")},r=e("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),i={width:t.width(),height:t.height()},s=document.activeElement;try{s.id}catch(o){s=document.body}return t.wrap(r),(t[0]===s||e.contains(t[0],s))&&e(s).focus(),r=t.parent(),t.css("position")==="static"?(r.css({position:"relative"}),t.css({position:"relative"})):(e.extend(n,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,r){n[r]=t.css(r),isNaN(parseInt(n[r],10))&&(n[r]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(i),r.css(n).show()},removeWrapper:function(t){var n=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===n||e.contains(t[0],n))&&e(n).focus()),t},setTransition:function(t,n,r,i){return i=i||{},e.each(n,function(e,n){var s=t.cssUnit(n);s[0]>0&&(i[n]=s[0]*r+s[1])}),i}}),e.fn.extend({effect:function(){function o(n){function u(){e.isFunction(i)&&i.call(r[0]),e.isFunction(n)&&n()}var r=e(this),i=t.complete,o=t.mode;(r.is(":hidden")?o==="hide":o==="show")?u():s.call(r[0],t,u)}var t=r.apply(this,arguments),n=t.mode,i=t.queue,s=e.effects.effect[t.effect];return e.fx.off||!s?n?this[n](t.duration,t.complete):this.each(function(){t.complete&&t.complete.call(this)}):i===!1?this.each(o):this.queue(i||"fx",o)},_show:e.fn.show,show:function(e){if(i(e))return this._show.apply(this,arguments);var t=r.apply(this,arguments);return t.mode="show",this.effect.call(this,t)},_hide:e.fn.hide,hide:function(e){if(i(e))return this._hide.apply(this,arguments);var t=r.apply(this,arguments);return t.mode="hide",this.effect.call(this,t)},__toggle:e.fn.toggle,toggle:function(t){if(i(t)||typeof t=="boolean"||e.isFunction(t))return this.__toggle.apply(this,arguments);var n=r.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)},cssUnit:function(t){var n=this.css(t),r=[];return e.each(["em","px","%","pt"],function(e,t){n.indexOf(t)>0&&(r=[parseFloat(n),t])}),r}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,n){t[n]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return e===0||e===1?e:-Math.pow(2,8*(e-1))*Math.sin(((e-1)*80-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){var t,n=4;while(e<((t=Math.pow(2,--n))-1)/11);return 1/Math.pow(4,3-n)-7.5625*Math.pow((t*3-2)/22-e,2)}}),e.each(t,function(t,n){e.easing["easeIn"+t]=n,e.easing["easeOut"+t]=function(e){return 1-n(1-e)},e.easing["easeInOut"+t]=function(e){return e<.5?n(e*2)/2:1-n(e*-2+2)/2}})}()}(jQuery);(function(e,t){e.effects.effect.fade=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"toggle");r.animate({opacity:i},{queue:!1,duration:t.duration,easing:t.easing,complete:n})}})(jQuery);(function(e,t){e.effects.effect.highlight=function(t,n){var r=e(this),i=["backgroundImage","backgroundColor","opacity"],s=e.effects.setMode(r,t.mode||"show"),o={backgroundColor:r.css("backgroundColor")};s==="hide"&&(o.opacity=0),e.effects.save(r,i),r.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),n()}})}})(jQuery);
/* ===================================================
- * bootstrap-transition.js v2.3.0
+ * bootstrap-transition.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#transitions
* ===================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -10250,11 +10250,11 @@
})()
})
}(window.jQuery);/* ==========================================================
- * bootstrap-alert.js v2.3.0
+ * bootstrap-alert.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#alerts
* ==========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -10348,11 +10348,11 @@
* ============== */
$(document).on('click.alert.data-api', dismiss, Alert.prototype.close)
}(window.jQuery);/* ============================================================
- * bootstrap-button.js v2.3.0
+ * bootstrap-button.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#buttons
* ============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -10452,11 +10452,11 @@
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
$btn.button('toggle')
})
}(window.jQuery);/* ==========================================================
- * bootstrap-carousel.js v2.3.0
+ * bootstrap-carousel.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#carousel
* ==========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -10528,11 +10528,11 @@
, pause: function (e) {
if (!e) this.paused = true
if (this.$element.find('.next, .prev').length && $.support.transition.end) {
this.$element.trigger($.support.transition.end)
- this.cycle()
+ this.cycle(true)
}
clearInterval(this.interval)
this.interval = null
return this
}
@@ -10658,11 +10658,11 @@
e.preventDefault()
})
}(window.jQuery);/* =============================================================
- * bootstrap-collapse.js v2.3.0
+ * bootstrap-collapse.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#collapse
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -10824,11 +10824,11 @@
$this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
$(target).collapse(option)
})
}(window.jQuery);/* ============================================================
- * bootstrap-dropdown.js v2.3.0
+ * bootstrap-dropdown.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
* ============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -10877,10 +10877,14 @@
isActive = $parent.hasClass('open')
clearMenus()
if (!isActive) {
+ if ('ontouchstart' in document.documentElement) {
+ // if mobile we we use a backdrop because click events don't delegate
+ $('<div class="dropdown-backdrop"/>').insertBefore($(this)).on('click', clearMenus)
+ }
$parent.toggleClass('open')
}
$this.focus()
@@ -10929,10 +10933,11 @@
}
}
function clearMenus() {
+ $('.dropdown-backdrop').remove()
$(toggle).each(function () {
getParent($(this)).removeClass('open')
})
}
@@ -10983,17 +10988,16 @@
* =================================== */
$(document)
.on('click.dropdown.data-api', clearMenus)
.on('click.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
- .on('.dropdown-menu', function (e) { e.stopPropagation() })
.on('click.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
.on('keydown.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
}(window.jQuery);
/* =========================================================
- * bootstrap-modal.js v2.3.0
+ * bootstrap-modal.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#modals
* =========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -11138,11 +11142,11 @@
that.$element.trigger('hidden')
})
}
, removeBackdrop: function () {
- this.$backdrop.remove()
+ this.$backdrop && this.$backdrop.remove()
this.$backdrop = null
}
, backdrop: function (callback) {
var that = this
@@ -11236,11 +11240,11 @@
})
})
}(window.jQuery);
/* ===========================================================
- * bootstrap-tooltip.js v2.3.0
+ * bootstrap-tooltip.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#tooltips
* Inspired by the original jQuery.tipsy by Jason Frame
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
@@ -11317,20 +11321,20 @@
return options
}
, enter: function (e) {
- // Added by Contour
- // https://github.com/twitter/bootstrap/issues/6232#issuecomment-13458685
- if (this.options.trigger == 'hover' && 'ontouchstart' in document.documentElement) {
- // Block tooltip event on touch devices since it swallows click events
- return;
- }
- // End of Added by Contour
+ var defaults = $.fn[this.type].defaults
+ , options = {}
+ , self
- var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+ this._options && $.each(this._options, function (key, value) {
+ if (defaults[key] != value) options[key] = value
+ }, this)
+ self = $(e.currentTarget)[this.type](options).data(this.type)
+
if (!self.options.delay || !self.options.delay.show) return self.show()
clearTimeout(this.timeout)
self.hoverState = 'in'
this.timeout = setTimeout(function() {
@@ -11597,11 +11601,11 @@
return this
}
}(window.jQuery);
/* ===========================================================
- * bootstrap-popover.js v2.3.0
+ * bootstrap-popover.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#popovers
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -11711,11 +11715,11 @@
return this
}
}(window.jQuery);
/* =============================================================
- * bootstrap-scrollspy.js v2.3.0
+ * bootstrap-scrollspy.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#scrollspy
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -11872,11 +11876,11 @@
$spy.scrollspy($spy.data())
})
})
}(window.jQuery);/* ========================================================
- * bootstrap-tab.js v2.3.0
+ * bootstrap-tab.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#tabs
* ========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -12015,11 +12019,11 @@
e.preventDefault()
$(this).tab('show')
})
}(window.jQuery);/* =============================================================
- * bootstrap-typeahead.js v2.3.0
+ * bootstrap-typeahead.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#typeahead
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -12350,11 +12354,11 @@
$this.typeahead($this.data())
})
}(window.jQuery);
/* ==========================================================
- * bootstrap-affix.js v2.3.0
+ * bootstrap-affix.js v2.3.2
* http://twitter.github.com/bootstrap/javascript.html#affix
* ==========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -12756,19 +12760,19 @@
}).call(this);
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
/**
- * @license Highcharts JS v2.3.3 (2012-10-04)
+ * @license Highcharts JS v3.0.2 (2013-06-05)
*
- * (c) 2009-2011 Torstein Hønsi
+ * (c) 2009-2013 Torstein Hønsi
*
* License: www.highcharts.com/license
*/
// JSLint options:
-/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */
+/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */
(function () {
// encapsulated variables
var UNDEFINED,
@@ -12792,10 +12796,11 @@
isOpera = win.opera,
isIE = /msie/i.test(userAgent) && !isOpera,
docMode8 = doc.documentMode === 8,
isWebKit = /AppleWebKit/.test(userAgent),
isFirefox = /Firefox/.test(userAgent),
+ isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),
SVG_NS = 'http://www.w3.org/2000/svg',
hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,
Renderer,
@@ -12807,10 +12812,13 @@
dateFormat, // function
globalAnimation,
pathAnim,
timeUnits,
noop = function () {},
+ charts = [],
+ PRODUCT = 'Highcharts',
+ VERSION = '3.0.2',
// some constants for frequently used strings
DIV = 'div',
ABSOLUTE = 'absolute',
RELATIVE = 'relative',
@@ -12825,16 +12833,17 @@
* Empirical lowest possible opacities for TRACKER_FILL
* IE6: 0.002
* IE7: 0.002
* IE8: 0.002
* IE9: 0.00000000001 (unlimited)
+ * IE10: 0.0001 (exporting only)
* FF: 0.00000000001 (unlimited)
* Chrome: 0.000001
* Safari: 0.000001
* Opera: 0.00000000001 (unlimited)
*/
- TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable
+ TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable
//TRACKER_FILL = 'rgba(192,192,192,0.5)',
NORMAL_STATE = '',
HOVER_STATE = 'hover',
SELECT_STATE = 'select',
MILLISECOND = 'millisecond',
@@ -12845,14 +12854,12 @@
WEEK = 'week',
MONTH = 'month',
YEAR = 'year',
// constants for attributes
- FILL = 'fill',
LINEAR_GRADIENT = 'linearGradient',
STOPS = 'stops',
- STROKE = 'stroke',
STROKE_WIDTH = 'stroke-width',
// time methods, changed based on whether or not UTC is used
makeTime,
getMinutes,
@@ -12870,11 +12877,11 @@
// lookup over the types and the associated classes
seriesTypes = {};
// The Highcharts namespace
-win.Highcharts = {};
+win.Highcharts = win.Highcharts ? error(16, true) : {};
/**
* Extend an object with the members of another
* @param {Object} a The object to be extended
* @param {Object} b The object to add to the first one
@@ -12887,11 +12894,55 @@
for (n in b) {
a[n] = b[n];
}
return a;
}
+
+/**
+ * Deep merge two or more objects and return a third object.
+ * Previously this function redirected to jQuery.extend(true), but this had two limitations.
+ * First, it deep merged arrays, which lead to workarounds in Highcharts. Second,
+ * it copied properties from extended prototypes.
+ */
+function merge() {
+ var i,
+ len = arguments.length,
+ ret = {},
+ doCopy = function (copy, original) {
+ var value, key;
+ for (key in original) {
+ if (original.hasOwnProperty(key)) {
+ value = original[key];
+
+ // An object is replacing a primitive
+ if (typeof copy !== 'object') {
+ copy = {};
+ }
+
+ // Copy the contents of objects, but not arrays or DOM nodes
+ if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]'
+ && typeof value.nodeType !== 'number') {
+ copy[key] = doCopy(copy[key] || {}, value);
+
+ // Primitives and arrays are copied over directly
+ } else {
+ copy[key] = original[key];
+ }
+ }
+ }
+ return copy;
+ };
+
+ // For each argument, extend the return
+ for (i = 0; i < len; i++) {
+ ret = doCopy(ret, arguments[i]);
+ }
+
+ return ret;
+}
+
/**
* Take an array and turn into a hash with even number arguments as keys and odd numbers as
* values. Allows creating constants for commonly used style properties, attributes etc.
* Avoid it in performance critical situations like looping
*/
@@ -13086,22 +13137,10 @@
extend(object.prototype, members);
return object;
}
/**
- * How many decimals are there in a number
- */
-function getDecimals(number) {
-
- number = (number || 0).toString();
-
- return number.indexOf('.') > -1 ?
- number.split('.')[1].length :
- 0;
-}
-
-/**
* Format a number and return a string based on input settings
* @param {Number} number The input number to format
* @param {Number} decimals The amount of decimals
* @param {String} decPoint The decimal point, defaults to the one given in the lang options
* @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
@@ -13109,11 +13148,11 @@
function numberFormat(number, decimals, decPoint, thousandsSep) {
var lang = defaultOptions.lang,
// http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
n = number,
c = decimals === -1 ?
- getDecimals(number) :
+ ((n || 0).toString().split('.')[1] || '').length : // preserve decimals
(isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),
d = decPoint === undefined ? lang.decimalPoint : decPoint,
t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
s = n < 0 ? "-" : "",
i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
@@ -13170,23 +13209,13 @@
dayOfMonth = date[getDate](),
month = date[getMonth](),
fullYear = date[getFullYear](),
lang = defaultOptions.lang,
langWeekdays = lang.weekdays,
- /* // uncomment this and the 'W' format key below to enable week numbers
- weekNumber = function () {
- var clone = new Date(date.valueOf()),
- day = clone[getDay]() == 0 ? 7 : clone[getDay](),
- dayNumber;
- clone.setDate(clone[getDate]() + 4 - day);
- dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
- return 1 + mathFloor(dayNumber / 7);
- },
- */
- // list all format keys
- replacements = {
+ // List all format keys. Custom formats can be added from the outside.
+ replacements = extend({
// Day
'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
'A': langWeekdays[day], // Long weekday, like 'Monday'
'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
@@ -13211,23 +13240,99 @@
'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59
'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
- };
+ }, Highcharts.dateFormats);
// do the replaces
for (key in replacements) {
- format = format.replace('%' + key, replacements[key]);
+ while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster
+ format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]);
+ }
}
// Optionally capitalize the string and return
return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
};
+/**
+ * Format a single variable. Similar to sprintf, without the % prefix.
+ */
+function formatSingle(format, val) {
+ var floatRegex = /f$/,
+ decRegex = /\.([0-9])/,
+ lang = defaultOptions.lang,
+ decimals;
+
+ if (floatRegex.test(format)) { // float
+ decimals = format.match(decRegex);
+ decimals = decimals ? decimals[1] : -1;
+ val = numberFormat(
+ val,
+ decimals,
+ lang.decimalPoint,
+ format.indexOf(',') > -1 ? lang.thousandsSep : ''
+ );
+ } else {
+ val = dateFormat(format, val);
+ }
+ return val;
+}
+
/**
+ * Format a string according to a subset of the rules of Python's String.format method.
+ */
+function format(str, ctx) {
+ var splitter = '{',
+ isInside = false,
+ segment,
+ valueAndFormat,
+ path,
+ i,
+ len,
+ ret = [],
+ val,
+ index;
+
+ while ((index = str.indexOf(splitter)) !== -1) {
+
+ segment = str.slice(0, index);
+ if (isInside) { // we're on the closing bracket looking back
+
+ valueAndFormat = segment.split(':');
+ path = valueAndFormat.shift().split('.'); // get first and leave format
+ len = path.length;
+ val = ctx;
+
+ // Assign deeper paths
+ for (i = 0; i < len; i++) {
+ val = val[path[i]];
+ }
+
+ // Format the replacement
+ if (valueAndFormat.length) {
+ val = formatSingle(valueAndFormat.join(':'), val);
+ }
+
+ // Push the result and advance the cursor
+ ret.push(val);
+
+ } else {
+ ret.push(segment);
+
+ }
+ str = str.slice(index + 1); // the rest
+ isInside = !isInside; // toggle
+ splitter = isInside ? '}' : '{'; // now look for next matching bracket
+ }
+ ret.push(str);
+ return ret.join('');
+}
+
+/**
* Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
* @param {Number} interval
* @param {Array} multiples
* @param {Number} magnitude
* @param {Object} options
@@ -13365,96 +13470,100 @@
minYear, // used in months and years as a basis for Date.UTC()
minDate = new Date(min),
interval = normalizedInterval.unitRange,
count = normalizedInterval.count;
+ if (defined(min)) { // #1300
+ if (interval >= timeUnits[SECOND]) { // second
+ minDate.setMilliseconds(0);
+ minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
+ count * mathFloor(minDate.getSeconds() / count));
+ }
-
- if (interval >= timeUnits[SECOND]) { // second
- minDate.setMilliseconds(0);
- minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
- count * mathFloor(minDate.getSeconds() / count));
- }
-
- if (interval >= timeUnits[MINUTE]) { // minute
- minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
- count * mathFloor(minDate[getMinutes]() / count));
- }
-
- if (interval >= timeUnits[HOUR]) { // hour
- minDate[setHours](interval >= timeUnits[DAY] ? 0 :
- count * mathFloor(minDate[getHours]() / count));
- }
-
- if (interval >= timeUnits[DAY]) { // day
- minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
- count * mathFloor(minDate[getDate]() / count));
- }
-
- if (interval >= timeUnits[MONTH]) { // month
- minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
- count * mathFloor(minDate[getMonth]() / count));
+ if (interval >= timeUnits[MINUTE]) { // minute
+ minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
+ count * mathFloor(minDate[getMinutes]() / count));
+ }
+
+ if (interval >= timeUnits[HOUR]) { // hour
+ minDate[setHours](interval >= timeUnits[DAY] ? 0 :
+ count * mathFloor(minDate[getHours]() / count));
+ }
+
+ if (interval >= timeUnits[DAY]) { // day
+ minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
+ count * mathFloor(minDate[getDate]() / count));
+ }
+
+ if (interval >= timeUnits[MONTH]) { // month
+ minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
+ count * mathFloor(minDate[getMonth]() / count));
+ minYear = minDate[getFullYear]();
+ }
+
+ if (interval >= timeUnits[YEAR]) { // year
+ minYear -= minYear % count;
+ minDate[setFullYear](minYear);
+ }
+
+ // week is a special case that runs outside the hierarchy
+ if (interval === timeUnits[WEEK]) {
+ // get start of current week, independent of count
+ minDate[setDate](minDate[getDate]() - minDate[getDay]() +
+ pick(startOfWeek, 1));
+ }
+
+
+ // get tick positions
+ i = 1;
minYear = minDate[getFullYear]();
- }
-
- if (interval >= timeUnits[YEAR]) { // year
- minYear -= minYear % count;
- minDate[setFullYear](minYear);
- }
-
- // week is a special case that runs outside the hierarchy
- if (interval === timeUnits[WEEK]) {
- // get start of current week, independent of count
- minDate[setDate](minDate[getDate]() - minDate[getDay]() +
- pick(startOfWeek, 1));
- }
-
-
- // get tick positions
- i = 1;
- minYear = minDate[getFullYear]();
- var time = minDate.getTime(),
- minMonth = minDate[getMonth](),
- minDateDate = minDate[getDate](),
- timezoneOffset = useUTC ?
- 0 :
- (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
-
- // iterate and add tick positions at appropriate values
- while (time < max) {
- tickPositions.push(time);
-
- // if the interval is years, use Date.UTC to increase years
- if (interval === timeUnits[YEAR]) {
- time = makeTime(minYear + i * count, 0);
-
- // if the interval is months, use Date.UTC to increase months
- } else if (interval === timeUnits[MONTH]) {
- time = makeTime(minYear, minMonth + i * count);
-
- // if we're using global time, the interval is not fixed as it jumps
- // one hour at the DST crossover
- } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
- time = makeTime(minYear, minMonth, minDateDate +
- i * count * (interval === timeUnits[DAY] ? 1 : 7));
-
- // else, the interval is fixed and we use simple addition
- } else {
- time += interval * count;
-
- // mark new days if the time is dividable by day
- if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset) {
- higherRanks[time] = DAY;
+ var time = minDate.getTime(),
+ minMonth = minDate[getMonth](),
+ minDateDate = minDate[getDate](),
+ timezoneOffset = useUTC ?
+ 0 :
+ (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
+
+ // iterate and add tick positions at appropriate values
+ while (time < max) {
+ tickPositions.push(time);
+
+ // if the interval is years, use Date.UTC to increase years
+ if (interval === timeUnits[YEAR]) {
+ time = makeTime(minYear + i * count, 0);
+
+ // if the interval is months, use Date.UTC to increase months
+ } else if (interval === timeUnits[MONTH]) {
+ time = makeTime(minYear, minMonth + i * count);
+
+ // if we're using global time, the interval is not fixed as it jumps
+ // one hour at the DST crossover
+ } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
+ time = makeTime(minYear, minMonth, minDateDate +
+ i * count * (interval === timeUnits[DAY] ? 1 : 7));
+
+ // else, the interval is fixed and we use simple addition
+ } else {
+ time += interval * count;
}
+
+ i++;
}
+
+ // push the last time
+ tickPositions.push(time);
- i++;
+
+ // mark new days if the time is dividible by day (#1649, #1760)
+ each(grep(tickPositions, function (time) {
+ return interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset;
+ }), function (time) {
+ higherRanks[time] = DAY;
+ });
}
-
- // push the last time
- tickPositions.push(time);
+
// record information on the chosen unit - for dynamic label formatter
tickPositions.info = extend(normalizedInterval, {
higherRanks: higherRanks,
totalRange: interval * count
});
@@ -13630,11 +13739,11 @@
SECOND, 1000,
MINUTE, 60000,
HOUR, 3600000,
DAY, 24 * 3600000,
WEEK, 7 * 24 * 3600000,
- MONTH, 30 * 24 * 3600000,
+ MONTH, 31 * 24 * 3600000,
YEAR, 31556952000
);
/*jslint white: false*/
/**
* Path interpolation algorithm used across adapters
@@ -13746,23 +13855,23 @@
// extend the animate function to allow SVG animations
var Fx = $.fx,
Step = Fx.step,
dSetter,
Tween = $.Tween,
- propHooks = Tween && Tween.propHooks;
+ propHooks = Tween && Tween.propHooks,
+ opacityHook = $.cssHooks.opacity;
/*jslint unparam: true*//* allow unused param x in this function */
$.extend($.easing, {
easeOutQuad: function (x, t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
}
});
/*jslint unparam: false*/
-
// extend some methods to check for elem.attr, which means it is a Highcharts SVG object
- $.each(['cur', '_default', 'width', 'height'], function (i, fn) {
+ $.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) {
var obj = Step,
base,
elem;
// Handle different parent objects
@@ -13793,10 +13902,15 @@
elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
base.apply(this, arguments); // use jQuery's built-in method
};
}
});
+
+ // Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+
+ wrap(opacityHook, 'get', function (proceed, elem, computed) {
+ return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed);
+ });
// Define the setter function for d (path definitions)
dSetter = function (fx) {
var elem = fx.elem,
@@ -13846,20 +13960,47 @@
return i;
}
}
};
- // Register Highcharts as a jQuery plugin
- // TODO: MooTools and prototype as well?
- // TODO: StockChart
- /*$.fn.highcharts = function(options, callback) {
- options.chart = merge(options.chart, { renderTo: this[0] });
- this.chart = new Chart(options, callback);
- return this;
- };*/
+ /**
+ * Register Highcharts as a plugin in the respective framework
+ */
+ $.fn.highcharts = function () {
+ var constr = 'Chart', // default constructor
+ args = arguments,
+ options,
+ ret,
+ chart;
+
+ if (isString(args[0])) {
+ constr = args[0];
+ args = Array.prototype.slice.call(args, 1);
+ }
+ options = args[0];
+
+ // Create the chart
+ if (options !== UNDEFINED) {
+ /*jslint unused:false*/
+ options.chart = options.chart || {};
+ options.chart.renderTo = this[0];
+ chart = new Highcharts[constr](options, args[1]);
+ ret = this;
+ /*jslint unused:true*/
+ }
+
+ // When called without parameters or with the return argument, get a predefined chart
+ if (options === UNDEFINED) {
+ ret = charts[attr(this[0], 'data-highcharts-chart')];
+ }
+
+ return ret;
+ };
+
},
-
+
+
/**
* Downloads a script and executes a callback when done.
* @param {String} scriptLocation
* @param {Function} callback
*/
@@ -13900,18 +14041,10 @@
return results;
},
/**
- * Deep merge two objects and return a third object
- */
- merge: function () {
- var args = arguments;
- return $.extend(true, null, args[0], args[1], args[2], args[3]);
- },
-
- /**
* Get the position of an element relative to the top left of the page
*/
offset: function (el) {
return $(el).offset();
},
@@ -13934,11 +14067,11 @@
*/
removeEvent: function (el, eventType, handler) {
// workaround for jQuery issue with unbinding custom events:
// http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2
var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
- if (doc[func] && !el[func]) {
+ if (doc[func] && el && !el[func]) {
el[func] = function () {};
}
$(el).unbind(eventType, handler);
},
@@ -14029,10 +14162,13 @@
* @param {Object} params
* @param {Object} options jQuery-like animation options: duration, easing, callback
*/
animate: function (el, params, options) {
var $el = $(el);
+ if (!el.style) {
+ el.style = {}; // #1881
+ }
if (params.d) {
el.toD = params.d; // keep the array form for paths, used in $.fx.step.d
params.d = 1; // because in jQuery, animating to an array has a different meaning
}
@@ -14058,21 +14194,20 @@
if (globalAdapter) {
globalAdapter.init.call(globalAdapter, pathAnim);
}
- // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
- // and all the utility functions will be null. In that case they are populated by the
- // default adapters below.
+// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
+// and all the utility functions will be null. In that case they are populated by the
+// default adapters below.
var adapterRun = adapter.adapterRun,
getScript = adapter.getScript,
inArray = adapter.inArray,
each = adapter.each,
grep = adapter.grep,
offset = adapter.offset,
map = adapter.map,
- merge = adapter.merge,
addEvent = adapter.addEvent,
removeEvent = adapter.removeEvent,
fireEvent = adapter.fireEvent,
washMouseEvent = adapter.washMouseEvent,
animate = adapter.animate,
@@ -14094,18 +14229,19 @@
/*formatter: function () {
return this.value;
},*/
style: {
color: '#666',
+ cursor: 'default',
fontSize: '11px',
lineHeight: '14px'
}
};
defaultOptions = {
- colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
- '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
+ colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970',
+ '#f28f43', '#77a1e5', '#c42525', '#a6c96a'],
symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
lang: {
loading: 'Loading...',
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December'],
@@ -14117,12 +14253,12 @@
resetZoomTitle: 'Reset zoom level 1:1',
thousandsSep: ','
},
global: {
useUTC: true,
- canvasToolsURL: 'http://code.highcharts.com/2.3.3/modules/canvas-tools.js',
- VMLRadialGradientURL: 'http://code.highcharts.com/2.3.3/gfx/vml-radial-gradient.png'
+ canvasToolsURL: 'http://code.highcharts.com/3.0.2/modules/canvas-tools.js',
+ VMLRadialGradientURL: 'http://code.highcharts.com/3.0.2/gfx/vml-radial-gradient.png'
},
chart: {
//animation: true,
//alignTicks: false,
//reflow: true,
@@ -14174,11 +14310,11 @@
// margin: 15,
// x: 0,
// verticalAlign: 'top',
y: 15,
style: {
- color: '#3E576F',
+ color: '#274b6d',//#3E576F',
fontSize: '16px'
}
},
subtitle: {
@@ -14187,11 +14323,11 @@
// floating: false
// x: 0,
// verticalAlign: 'top',
y: 30,
style: {
- color: '#6D869F'
+ color: '#4d759e'
}
},
plotOptions: {
line: { // base series options
@@ -14206,11 +14342,11 @@
//dashStyle: null,
//enableMouseTracking: true,
events: {},
//legendIndex: 0,
lineWidth: 2,
- shadow: true,
+ //shadow: false,
// stacking: null,
marker: {
enabled: true,
//symbol: null,
lineWidth: 0,
@@ -14233,11 +14369,11 @@
events: {}
},
dataLabels: merge(defaultLabelOptions, {
enabled: false,
formatter: function () {
- return this.y;
+ return numberFormat(this.y, -1);
},
verticalAlign: 'bottom', // above singular point
y: 0
// backgroundColor: undefined,
// borderColor: undefined,
@@ -14295,11 +14431,11 @@
borderWidth: 1,
borderColor: '#909090',
borderRadius: 5,
navigation: {
// animation: true,
- activeColor: '#3E576F',
+ activeColor: '#274b6d',
// arrowSize: 12
inactiveColor: '#CCC'
// style: {} // text styles
},
// margin: 10,
@@ -14309,11 +14445,11 @@
/*style: {
padding: '5px'
},*/
itemStyle: {
cursor: 'pointer',
- color: '#3E576F',
+ color: '#274b6d',
fontSize: '12px'
},
itemHoverStyle: {
//cursor: 'pointer', removed as of #601
color: '#000'
@@ -14330,11 +14466,17 @@
symbolWidth: 16,
symbolPadding: 5,
verticalAlign: 'bottom',
// width: undefined,
x: 0,
- y: 0
+ y: 0,
+ title: {
+ //text: null,
+ style: {
+ fontWeight: 'bold'
+ }
+ }
},
loading: {
// hideDuration: 100,
labelStyle: {
@@ -14351,14 +14493,15 @@
}
},
tooltip: {
enabled: true,
+ animation: hasSVG,
//crosshairs: null,
backgroundColor: 'rgba(255, 255, 255, .85)',
- borderWidth: 2,
- borderRadius: 5,
+ borderWidth: 1,
+ borderRadius: 3,
dateTimeLabelFormats: {
millisecond: '%A, %b %e, %H:%M:%S.%L',
second: '%A, %b %e, %H:%M:%S',
minute: '%A, %b %e, %H:%M',
hour: '%A, %b %e, %H:%M',
@@ -14369,16 +14512,17 @@
},
//formatter: defaultFormatter,
headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
shadow: true,
- shared: useCanVG,
- snap: hasTouch ? 25 : 10,
+ //shared: false,
+ snap: isTouchDevice ? 25 : 10,
style: {
color: '#333333',
+ cursor: 'default',
fontSize: '12px',
- padding: '5px',
+ padding: '8px',
whiteSpace: 'nowrap'
}
//xDateFormat: '%A, %b %e, %Y',
//valueDecimals: null,
//valuePrefix: '',
@@ -14396,11 +14540,11 @@
y: -5
},
style: {
cursor: 'pointer',
color: '#909090',
- fontSize: '10px'
+ fontSize: '9px'
}
}
};
@@ -14475,46 +14619,68 @@
function getOptions() {
return defaultOptions;
}
-
/**
* Handle color operations. The object methods are chainable.
* @param {String} input The input color in either rbga or hex format
*/
var Color = function (input) {
// declare variables
- var rgba = [], result;
+ var rgba = [], result, stops;
/**
* Parse the input color to rgba array
* @param {String} input
*/
function init(input) {
- // rgba
- result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input);
- if (result) {
- rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
- } else { // hex
- result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
+ // Gradients
+ if (input && input.stops) {
+ stops = map(input.stops, function (stop) {
+ return Color(stop[1]);
+ });
+
+ // Solid colors
+ } else {
+ // rgba
+ result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input);
if (result) {
- rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
+ rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
+ } else {
+ // hex
+ result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
+ if (result) {
+ rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
+ } else {
+ // rgb
+ result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(input);
+ if (result) {
+ rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
+ }
+ }
}
- }
+ }
}
/**
* Return the color a specified format
* @param {String} format
*/
function get(format) {
var ret;
+ if (stops) {
+ ret = merge(input);
+ ret.stops = [].concat(ret.stops);
+ each(stops, function (stop, i) {
+ ret.stops[i] = [ret.stops[i][0], stop.get(format)];
+ });
+
// it's NaN if gradient colors on a column chart
- if (rgba && !isNaN(rgba[0])) {
+ } else if (rgba && !isNaN(rgba[0])) {
if (format === 'rgb') {
ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
} else if (format === 'a') {
ret = rgba[3];
} else {
@@ -14529,11 +14695,16 @@
/**
* Brighten the color
* @param {Number} alpha
*/
function brighten(alpha) {
- if (isNumber(alpha) && alpha !== 0) {
+ if (stops) {
+ each(stops, function (stop) {
+ stop.brighten(alpha);
+ });
+
+ } else if (isNumber(alpha) && alpha !== 0) {
var i;
for (i = 0; i < 3; i++) {
rgba[i] += pInt(alpha * 255);
if (rgba[i] < 0) {
@@ -14560,10 +14731,11 @@
// public methods
return {
get: get,
brighten: brighten,
+ rgba: rgba,
setOpacity: setOpacity
};
};
@@ -14591,10 +14763,14 @@
* Renderer.label.
*/
wrapper.attrSetters = {};
},
/**
+ * Default base for animation
+ */
+ opacity: 1,
+ /**
* Animate a given attribute
* @param {Object} params
* @param {Number} options The same options as in jQuery animation
* @param {Function} complete Function to perform at the end of animation
*/
@@ -14651,11 +14827,10 @@
key = { x: 'cx', y: 'cy' }[key] || key;
} else if (key === 'strokeWidth') {
key = 'stroke-width';
}
ret = attr(element, key) || wrapper[key] || 0;
-
if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
ret = parseFloat(ret);
}
// setter
@@ -14671,10 +14846,11 @@
if (result !== false) {
if (result !== UNDEFINED) {
value = result; // the attribute setter has returned a new value to set
}
+
// paths
if (key === 'd') {
if (value && value.join) { // join path
value = value.join(' ');
}
@@ -14692,14 +14868,12 @@
//child.setAttribute('x', value);
attr(child, 'x', value);
}
}
- if (wrapper.rotation) {
- attr(element, 'transform', 'rotate(' + wrapper.rotation + ' ' + value + ' ' +
- pInt(hash.y || attr(element, 'y')) + ')');
- }
+ } else if (wrapper.rotation && (key === 'x' || key === 'y')) {
+ doTransform = true;
// apply gradients
} else if (key === 'fill') {
value = renderer.color(value, element, key);
@@ -14714,11 +14888,12 @@
ry: value
});
skipAttr = true;
// translation and text rotation
- } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
+ } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' ||
+ key === 'verticalAlign' || key === 'scaleX' || key === 'scaleY') {
doTransform = true;
skipAttr = true;
// apply opacity as subnode (required by legacy WebKit and Batik)
} else if (key === 'stroke') {
@@ -14747,14 +14922,10 @@
value[i] = pInt(value[i]) * hash['stroke-width'];
}
value = value.join(',');
}
- // special
- } else if (key === 'isTracker') {
- wrapper[key] = value;
-
// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
// is unable to cast them. Test again with final IE9.
} else if (key === 'width') {
value = pInt(value);
@@ -14776,13 +14947,24 @@
// jQuery animate changes case
if (key === 'strokeWidth') {
key = 'stroke-width';
}
- // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
- if (isWebKit && key === 'stroke-width' && value === 0) {
- value = 0.000001;
+ // In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-
+ // width is 0. #1369
+ if (key === 'stroke-width' || key === 'stroke') {
+ wrapper[key] = value;
+ // Only apply the stroke attribute if the stroke width is defined and larger than 0
+ if (wrapper.stroke && wrapper['stroke-width']) {
+ attr(element, 'stroke', wrapper.stroke);
+ attr(element, 'stroke-width', wrapper['stroke-width']);
+ wrapper.hasStroke = true;
+ } else if (key === 'stroke-width' && value === 0 && wrapper.hasStroke) {
+ element.removeAttribute('stroke');
+ wrapper.hasStroke = false;
+ }
+ skipAttr = true;
}
// symbols
if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
@@ -14814,16 +14996,11 @@
}
// Record for animation and quick access without polling the DOM
wrapper[key] = value;
- // Update transform
- if (doTransform) {
- wrapper.updateTransform();
- }
-
-
+
if (key === 'text') {
// Delete bBox memo when the text changes
if (value !== wrapper.textStr) {
delete wrapper.bBox;
}
@@ -14837,16 +15014,40 @@
}
}
+ // Update transform. Do this outside the loop to prevent redundant updating for batch setting
+ // of attributes.
+ if (doTransform) {
+ wrapper.updateTransform();
+ }
+
}
return ret;
},
+
/**
+ * Add a class name to an element
+ */
+ addClass: function (className) {
+ attr(this.element, 'class', attr(this.element, 'class') + ' ' + className);
+ return this;
+ },
+ /* hasClass and removeClass are not (yet) needed
+ hasClass: function (className) {
+ return attr(this.element, 'class').indexOf(className) !== -1;
+ },
+ removeClass: function (className) {
+ attr(this.element, 'class', attr(this.element, 'class').replace(className, ''));
+ return this;
+ },
+ */
+
+ /**
* If one of the symbol size affecting parameters are changed,
* check all the others only once for each call to an element's
* .attr() method
* @param {Object} hash
*/
@@ -14856,11 +15057,17 @@
each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
wrapper[key] = pick(hash[key], wrapper[key]);
});
wrapper.attr({
- d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper)
+ d: wrapper.renderer.symbols[wrapper.symbolName](
+ wrapper.x,
+ wrapper.y,
+ wrapper.width,
+ wrapper.height,
+ wrapper
+ )
});
},
/**
* Apply a clipping path to this object
@@ -14948,13 +15155,11 @@
css(elemWrapper.element, styles);
} else {
for (n in styles) {
serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
}
- elemWrapper.attr({
- style: serializedCss
- });
+ attr(elem, 'style', serializedCss); // #1881
}
// re-build text
if (textWidth && elemWrapper.added) {
@@ -14968,21 +15173,19 @@
* Add an event listener
* @param {String} eventType
* @param {Function} handler
*/
on: function (eventType, handler) {
- var fn = handler;
// touch
if (hasTouch && eventType === 'click') {
- eventType = 'touchstart';
- fn = function (e) {
+ this.element.ontouchstart = function (e) {
e.preventDefault();
handler();
};
}
// simplest possible event model for internal use
- this.element['on' + eventType] = fn;
+ this.element['on' + eventType] = handler;
return this;
},
/**
* Set the coordinates needed to draw a consistent radial gradient across
@@ -15092,23 +15295,21 @@
alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
nonLeft = align && align !== 'left',
shadows = wrapper.shadows;
// apply translate
- if (translateX || translateY) {
- css(elem, {
- marginLeft: translateX,
- marginTop: translateY
- });
- if (shadows) { // used in labels/tooltip
- each(shadows, function (shadow) {
- css(shadow, {
- marginLeft: translateX + 1,
- marginTop: translateY + 1
- });
+ css(elem, {
+ marginLeft: translateX,
+ marginTop: translateY
+ });
+ if (shadows) { // used in labels/tooltip
+ each(shadows, function (shadow) {
+ css(shadow, {
+ marginLeft: translateX + 1,
+ marginTop: translateY + 1
});
- }
+ });
}
// apply inversion
if (wrapper.inverted) { // wrapper is a group
each(elem.childNodes, function (child) {
@@ -15159,11 +15360,11 @@
width = pick(wrapper.elemWidth, elem.offsetWidth);
height = pick(wrapper.elemHeight, elem.offsetHeight);
// update textWidth
- if (width > textWidth && /[ \-]/.test(elem.innerText)) { // #983
+ if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
css(elem, {
width: textWidth + PX,
display: 'block',
whiteSpace: 'normal'
});
@@ -15199,10 +15400,15 @@
// apply position with correction
css(elem, {
left: (x + xCorr) + PX,
top: (y + yCorr) + PX
});
+
+ // force reflow in webkit to apply the left and top on useHTML element (#1249)
+ if (isWebKit) {
+ height = elem.offsetHeight; // assigned to height for JSLint purpose
+ }
// record current text transform
wrapper.cTT = currentTextTransform;
}
},
@@ -15213,32 +15419,38 @@
*/
updateTransform: function () {
var wrapper = this,
translateX = wrapper.translateX || 0,
translateY = wrapper.translateY || 0,
+ scaleX = wrapper.scaleX,
+ scaleY = wrapper.scaleY,
inverted = wrapper.inverted,
rotation = wrapper.rotation,
- transform = [];
+ transform;
// flipping affects translate as adjustment for flipping around the group's axis
if (inverted) {
translateX += wrapper.attr('width');
translateY += wrapper.attr('height');
}
- // apply translate
- if (translateX || translateY) {
- transform.push('translate(' + translateX + ',' + translateY + ')');
- }
+ // Apply translate. Nearly all transformed elements have translation, so instead
+ // of checking for translate = 0, do it always (#1767, #1846).
+ transform = ['translate(' + translateX + ',' + translateY + ')'];
// apply rotation
if (inverted) {
transform.push('rotate(90) scale(-1,1)');
} else if (rotation) { // text rotation
transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');
}
+ // apply scale
+ if (defined(scaleX) || defined(scaleY)) {
+ transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')');
+ }
+
if (transform.length) {
attr(wrapper.element, 'transform', transform.join(' '));
}
},
/**
@@ -15255,58 +15467,74 @@
* Break down alignment options like align, verticalAlign, x and y
* to x and y relative to the chart.
*
* @param {Object} alignOptions
* @param {Boolean} alignByTranslate
- * @param {Object} box The box to align to, needs a width and height
+ * @param {String[Object} box The box to align to, needs a width and height. When the
+ * box is a string, it refers to an object in the Renderer. For example, when
+ * box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height
+ * x and y properties.
*
*/
align: function (alignOptions, alignByTranslate, box) {
- var elemWrapper = this;
+ var align,
+ vAlign,
+ x,
+ y,
+ attribs = {},
+ alignTo,
+ renderer = this.renderer,
+ alignedObjects = renderer.alignedObjects;
- if (!alignOptions) { // called on resize
- alignOptions = elemWrapper.alignOptions;
- alignByTranslate = elemWrapper.alignByTranslate;
- } else { // first call on instanciate
- elemWrapper.alignOptions = alignOptions;
- elemWrapper.alignByTranslate = alignByTranslate;
- if (!box) { // boxes other than renderer handle this internally
- elemWrapper.renderer.alignedObjects.push(elemWrapper);
+ // First call on instanciate
+ if (alignOptions) {
+ this.alignOptions = alignOptions;
+ this.alignByTranslate = alignByTranslate;
+ if (!box || isString(box)) { // boxes other than renderer handle this internally
+ this.alignTo = alignTo = box || 'renderer';
+ erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize
+ alignedObjects.push(this);
+ box = null; // reassign it below
}
+
+ // When called on resize, no arguments are supplied
+ } else {
+ alignOptions = this.alignOptions;
+ alignByTranslate = this.alignByTranslate;
+ alignTo = this.alignTo;
}
- box = pick(box, elemWrapper.renderer);
+ box = pick(box, renderer[alignTo], renderer);
- var align = alignOptions.align,
- vAlign = alignOptions.verticalAlign,
- x = (box.x || 0) + (alignOptions.x || 0), // default: left align
- y = (box.y || 0) + (alignOptions.y || 0), // default: top align
- attribs = {};
+ // Assign variables
+ align = alignOptions.align;
+ vAlign = alignOptions.verticalAlign;
+ x = (box.x || 0) + (alignOptions.x || 0); // default: left align
+ y = (box.y || 0) + (alignOptions.y || 0); // default: top align
-
- // align
+ // Align
if (align === 'right' || align === 'center') {
x += (box.width - (alignOptions.width || 0)) /
{ right: 1, center: 2 }[align];
}
attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
- // vertical align
+ // Vertical align
if (vAlign === 'bottom' || vAlign === 'middle') {
y += (box.height - (alignOptions.height || 0)) /
({ bottom: 1, middle: 2 }[vAlign] || 1);
}
attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
- // animate only if already placed
- elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
- elemWrapper.placed = true;
- elemWrapper.alignAttr = attribs;
+ // Animate only if already placed
+ this[this.placed ? 'animate' : 'attr'](attribs);
+ this.placed = true;
+ this.alignAttr = attribs;
- return elemWrapper;
+ return this;
},
/**
* Get the bounding box (width, height, x and y) for the element
*/
@@ -15318,11 +15546,11 @@
height,
rotation = wrapper.rotation,
element = wrapper.element,
styles = wrapper.styles,
rad = rotation * deg2rad;
-
+
if (!bBox) {
// SVG elements
if (element.namespaceURI === SVG_NS || renderer.forExport) {
try { // Fails in Firefox if the container has display: none.
@@ -15354,23 +15582,23 @@
// True SVG elements as well as HTML elements in modern browsers using the .useHTML option
// need to compensated for rotation
if (renderer.isSVG) {
width = bBox.width;
height = bBox.height;
-
+
+ // Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669)
+ if (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '22.7') {
+ bBox.height = height = 14;
+ }
+
// Adjust for rotated text
if (rotation) {
bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
}
}
- // Workaround for wrong bounding box in IE9 and IE10 (#1101)
- if (isIE && styles && styles.fontSize === '11px' && height === 22.700000762939453) {
- bBox.height = 14;
- }
-
wrapper.bBox = bBox;
}
return bBox;
},
@@ -15386,10 +15614,22 @@
*/
hide: function () {
return this.attr({ visibility: HIDDEN });
},
+ fadeOut: function (duration) {
+ var elemWrapper = this;
+ elemWrapper.animate({
+ opacity: 0
+ }, {
+ duration: duration || 150,
+ complete: function () {
+ elemWrapper.hide();
+ }
+ });
+ },
+
/**
* Add the element
* @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
* to append the element to the renderer.box.
*/
@@ -15477,11 +15717,11 @@
shadows = wrapper.shadows,
key,
i;
// remove events
- element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
+ element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null;
stop(wrapper); // stop running animations
if (wrapper.clipPath) {
wrapper.clipPath = wrapper.clipPath.destroy();
}
@@ -15503,33 +15743,22 @@
wrapper.safeRemoveChild(shadow);
});
}
// remove from alignObjects
- erase(wrapper.renderer.alignedObjects, wrapper);
+ if (wrapper.alignTo) {
+ erase(wrapper.renderer.alignedObjects, wrapper);
+ }
for (key in wrapper) {
delete wrapper[key];
}
return null;
},
/**
- * Empty a group element
- */
- empty: function () {
- var element = this.element,
- childNodes = element.childNodes,
- i = childNodes.length;
-
- while (i--) {
- element.removeChild(childNodes[i]);
- }
- },
-
- /**
* Add a shadow to the element. Must be done after the element is added to the DOM
* @param {Boolean|Object} shadowOptions
*/
shadow: function (shadowOptions, group, cutOff) {
var shadows = [],
@@ -15600,11 +15829,12 @@
* @param {Boolean} forExport
*/
init: function (container, width, height, forExport) {
var renderer = this,
loc = location,
- boxWrapper;
+ boxWrapper,
+ desc;
boxWrapper = renderer.createElement('svg')
.attr({
xmlns: SVG_NS,
version: '1.1'
@@ -15623,10 +15853,15 @@
.replace(/#.*?$/, '') // remove the hash
.replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
.replace(/ /g, '%20') : // replace spaces (needed for Safari only)
'';
+ // Add description
+ desc = this.createElement('desc').add();
+ desc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION));
+
+
renderer.defs = this.createElement('defs').add();
renderer.forExport = forExport;
renderer.gradients = {}; // Object where gradient SvgElements are stored
renderer.setSize(width, height, false);
@@ -15717,10 +15952,12 @@
*
* @param {Object} textNode The parent text SVG node
*/
buildText: function (wrapper) {
var textNode = wrapper.element,
+ renderer = this,
+ forExport = renderer.forExport,
lines = pick(wrapper.textStr, '').toString()
.replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
.replace(/<(i|em)>/g, '<span style="font-style:italic">')
.replace(/<a/g, '<span')
.replace(/<\/(b|strong|i|em|a)>/g, '</span>')
@@ -15730,24 +15967,13 @@
hrefRegex = /href="([^"]+)"/,
parentX = attr(textNode, 'x'),
textStyles = wrapper.styles,
width = textStyles && textStyles.width && pInt(textStyles.width),
textLineHeight = textStyles && textStyles.lineHeight,
- lastLine,
- GET_COMPUTED_STYLE = 'getComputedStyle',
- i = childNodes.length,
- linePositions = [];
-
- // Needed in IE9 because it doesn't report tspan's offsetHeight (#893)
- function getLineHeightByBBox(lineNo) {
- linePositions[lineNo] = textNode.getBBox ?
- textNode.getBBox().height :
- wrapper.renderer.fontMetrics(textNode.style.fontSize).h; // #990
- return mathRound(linePositions[lineNo] - (linePositions[lineNo - 1] || 0));
- }
+ i = childNodes.length;
- // remove old text
+ /// remove old text
while (i--) {
textNode.removeChild(childNodes[i]);
}
if (width && !wrapper.added) {
@@ -15759,11 +15985,11 @@
lines.pop();
}
// build the lines
each(lines, function (line, lineNo) {
- var spans, spanNo = 0, lineHeight;
+ var spans, spanNo = 0;
line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
spans = line.split('|||');
each(spans, function (span) {
@@ -15773,65 +15999,56 @@
spanStyle; // #390
if (styleRegex.test(span)) {
spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
attr(tspan, 'style', spanStyle);
}
- if (hrefRegex.test(span)) {
+ if (hrefRegex.test(span) && !forExport) { // Not for export - #1529
attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
css(tspan, { cursor: 'pointer' });
}
span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
.replace(/</g, '<')
.replace(/>/g, '>');
- // issue #38 workaround.
- /*if (reverse) {
- arr = [];
- i = span.length;
- while (i--) {
- arr.push(span.charAt(i));
- }
- span = arr.join('');
- }*/
-
// add the text node
tspan.appendChild(doc.createTextNode(span));
if (!spanNo) { // first span in a line, align it to the left
attributes.x = parentX;
} else {
- // Firefox ignores spaces at the front or end of the tspan
- attributes.dx = 3; // space
+ attributes.dx = 0; // #16
}
+ // add attributes
+ attr(tspan, attributes);
+
// first span on subsequent line, add the line height
- if (!spanNo) {
- if (lineNo) {
+ if (!spanNo && lineNo) {
- // allow getting the right offset height in exporting in IE
- if (!hasSVG && wrapper.renderer.forExport) {
- css(tspan, { display: 'block' });
- }
-
- // Webkit and opera sometimes return 'normal' as the line height. In that
- // case, webkit uses offsetHeight, while Opera falls back to 18
- lineHeight = win[GET_COMPUTED_STYLE] &&
- pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height'));
-
- if (!lineHeight || isNaN(lineHeight)) {
- lineHeight = textLineHeight || lastLine.offsetHeight || getLineHeightByBBox(lineNo) || 18;
- }
- attr(tspan, 'dy', lineHeight);
+ // allow getting the right offset height in exporting in IE
+ if (!hasSVG && forExport) {
+ css(tspan, { display: 'block' });
}
- lastLine = tspan; // record for use in next line
+
+ // Set the line height based on the font size of either
+ // the text element or the tspan element
+ attr(
+ tspan,
+ 'dy',
+ textLineHeight || renderer.fontMetrics(
+ /px$/.test(tspan.style.fontSize) ?
+ tspan.style.fontSize :
+ textStyles.fontSize
+ ).h,
+ // Safari 6.0.2 - too optimized for its own good (#1539)
+ // TODO: revisit this with future versions of Safari
+ isWebKit && tspan.offsetHeight
+ );
}
- // add attributes
- attr(tspan, attributes);
-
- // append it
+ // Append it
textNode.appendChild(tspan);
spanNo++;
// check width and apply soft breaks
@@ -15886,69 +16103,65 @@
* @param {Object} normalState
* @param {Object} hoverState
* @param {Object} pressedState
*/
button: function (text, x, y, callback, normalState, hoverState, pressedState) {
- var label = this.label(text, x, y),
+ var label = this.label(text, x, y, null, null, null, null, null, 'button'),
curState = 0,
stateOptions,
stateStyle,
normalStyle,
hoverStyle,
pressedStyle,
STYLE = 'style',
verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
- // prepare the attributes
- /*jslint white: true*/
- normalState = merge(hash(
- STROKE_WIDTH, 1,
- STROKE, '#999',
- FILL, hash(
- LINEAR_GRADIENT, verticalGradient,
- STOPS, [
- [0, '#FFF'],
- [1, '#DDD']
+ // Normal state - prepare the attributes
+ normalState = merge({
+ 'stroke-width': 1,
+ stroke: '#CCCCCC',
+ fill: {
+ linearGradient: verticalGradient,
+ stops: [
+ [0, '#FEFEFE'],
+ [1, '#F6F6F6']
]
- ),
- 'r', 3,
- 'padding', 3,
- STYLE, hash(
- 'color', 'black'
- )
- ), normalState);
- /*jslint white: false*/
+ },
+ r: 2,
+ padding: 5,
+ style: {
+ color: 'black'
+ }
+ }, normalState);
normalStyle = normalState[STYLE];
delete normalState[STYLE];
- /*jslint white: true*/
- hoverState = merge(normalState, hash(
- STROKE, '#68A',
- FILL, hash(
- LINEAR_GRADIENT, verticalGradient,
- STOPS, [
+ // Hover state
+ hoverState = merge(normalState, {
+ stroke: '#68A',
+ fill: {
+ linearGradient: verticalGradient,
+ stops: [
[0, '#FFF'],
[1, '#ACF']
]
- )
- ), hoverState);
- /*jslint white: false*/
+ }
+ }, hoverState);
hoverStyle = hoverState[STYLE];
delete hoverState[STYLE];
- /*jslint white: true*/
- pressedState = merge(normalState, hash(
- STROKE, '#68A',
- FILL, hash(
- LINEAR_GRADIENT, verticalGradient,
- STOPS, [
+ // Pressed state
+ pressedState = merge(normalState, {
+ stroke: '#68A',
+ fill: {
+ linearGradient: verticalGradient,
+ stops: [
[0, '#9BD'],
[1, '#CDF']
]
- )
- ), pressedState);
- /*jslint white: false*/
+ }
+ }, pressedState);
pressedStyle = pressedState[STYLE];
delete pressedState[STYLE];
// add the events
addEvent(label.element, 'mouseenter', function () {
@@ -16185,10 +16398,11 @@
width,
height,
options
),
+ imageElement,
imageRegex = /^url\((.*?)\)$/,
imageSrc,
imageSize,
centerImage;
@@ -16211,45 +16425,48 @@
// image symbols
} else if (imageRegex.test(symbol)) {
// On image load, set the size and position
centerImage = function (img, size) {
- img.attr({
- width: size[0],
- height: size[1]
- });
+ if (img.element) { // it may be destroyed in the meantime (#1390)
+ img.attr({
+ width: size[0],
+ height: size[1]
+ });
- if (!img.alignByTranslate) { // #185
- img.translate(
- -mathRound(size[0] / 2),
- -mathRound(size[1] / 2)
- );
+ if (!img.alignByTranslate) { // #185
+ img.translate(
+ mathRound((width - size[0]) / 2), // #1378
+ mathRound((height - size[1]) / 2)
+ );
+ }
}
};
imageSrc = symbol.match(imageRegex)[1];
imageSize = symbolSizes[imageSrc];
- // create the image synchronously, add attribs async
+ // Ireate the image synchronously, add attribs async
obj = this.image(imageSrc)
.attr({
x: x,
y: y
});
+ obj.isImg = true;
if (imageSize) {
centerImage(obj, imageSize);
} else {
- // initialize image to be 0 size so export will still function if there's no cached sizes
+ // Initialize image to be 0 size so export will still function if there's no cached sizes.
+ //
obj.attr({ width: 0, height: 0 });
- // create a dummy JavaScript image to get the width and height
- createElement('img', {
+ // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8,
+ // the created element must be assigned to a variable in order to load (#292).
+ imageElement = createElement('img', {
onload: function () {
- var img = this;
-
- centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
+ centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]);
},
src: imageSrc
});
}
}
@@ -16308,11 +16525,11 @@
];
},
'arc': function (x, y, w, h, options) {
var start = options.start,
radius = options.r || w || h,
- end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
+ end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561)
innerRadius = options.innerR,
open = options.open,
cosStart = mathCos(start),
sinStart = mathSin(start),
cosEnd = mathCos(end),
@@ -16384,61 +16601,83 @@
*/
color: function (color, elem, prop) {
var renderer = this,
colorObject,
regexRgba = /^rgba/,
- gradName;
+ gradName,
+ gradAttr,
+ gradients,
+ gradientObject,
+ stops,
+ stopColor,
+ stopOpacity,
+ radialReference,
+ n,
+ id,
+ key = [];
// Apply linear or radial gradients
if (color && color.linearGradient) {
gradName = 'linearGradient';
} else if (color && color.radialGradient) {
gradName = 'radialGradient';
}
if (gradName) {
- var gradAttr = color[gradName],
- gradients = renderer.gradients,
- gradientObject,
- stopColor,
- stopOpacity,
- radialReference = elem.radialReference;
+ gradAttr = color[gradName];
+ gradients = renderer.gradients;
+ stops = color.stops;
+ radialReference = elem.radialReference;
+ // Keep < 2.2 kompatibility
+ if (isArray(gradAttr)) {
+ color[gradName] = gradAttr = {
+ x1: gradAttr[0],
+ y1: gradAttr[1],
+ x2: gradAttr[2],
+ y2: gradAttr[3],
+ gradientUnits: 'userSpaceOnUse'
+ };
+ }
+
+ // Correct the radial gradient for the radial reference system
+ if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
+ gradAttr = merge(gradAttr, {
+ cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
+ cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
+ r: gradAttr.r * radialReference[2],
+ gradientUnits: 'userSpaceOnUse'
+ });
+ }
+
+ // Build the unique key to detect whether we need to create a new element (#1282)
+ for (n in gradAttr) {
+ if (n !== 'id') {
+ key.push(n, gradAttr[n]);
+ }
+ }
+ for (n in stops) {
+ key.push(stops[n]);
+ }
+ key = key.join(',');
+
// Check if a gradient object with the same config object is created within this renderer
- if (!gradAttr.id || !gradients[gradAttr.id]) {
+ if (gradients[key]) {
+ id = gradients[key].id;
- // Keep < 2.2 kompatibility
- if (isArray(gradAttr)) {
- color[gradName] = gradAttr = {
- x1: gradAttr[0],
- y1: gradAttr[1],
- x2: gradAttr[2],
- y2: gradAttr[3],
- gradientUnits: 'userSpaceOnUse'
- };
- }
-
- // Correct the radial gradient for the radial reference system
- if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
- extend(gradAttr, {
- cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
- cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
- r: gradAttr.r * radialReference[2],
- gradientUnits: 'userSpaceOnUse'
- });
- }
+ } else {
// Set the id and create the element
- gradAttr.id = PREFIX + idCounter++;
- gradients[gradAttr.id] = gradientObject = renderer.createElement(gradName)
+ gradAttr.id = id = PREFIX + idCounter++;
+ gradients[key] = gradientObject = renderer.createElement(gradName)
.attr(gradAttr)
.add(renderer.defs);
// The gradient needs to keep a list of stops to be able to destroy them
gradientObject.stops = [];
- each(color.stops, function (stop) {
+ each(stops, function (stop) {
var stopObject;
if (regexRgba.test(stop[1])) {
colorObject = Color(stop[1]);
stopColor = colorObject.get('rgb');
stopOpacity = colorObject.get('a');
@@ -16456,11 +16695,11 @@
gradientObject.stops.push(stopObject);
});
}
// Return the reference to the gradient object
- return 'url(' + renderer.url + '#' + gradAttr.id + ')';
+ return 'url(' + renderer.url + '#' + id + ')';
// Webkit and Batik can't show rgba.
} else if (regexRgba.test(color)) {
colorObject = Color(color);
attr(elem, prop + '-opacity', colorObject.get('a'));
@@ -16691,62 +16930,70 @@
//.add(wrapper),
box,
bBox,
alignFactor = 0,
padding = 3,
+ paddingLeft = 0,
width,
height,
wrapperX,
wrapperY,
crispAdjust = 0,
deferredAttr = {},
baselineOffset,
- attrSetters = wrapper.attrSetters;
+ attrSetters = wrapper.attrSetters,
+ needsBox;
/**
* This function runs after the label is added to the DOM (when the bounding box is
* available), and after the text of the label is updated to detect the new bounding
* box and reflect it in the border box.
*/
function updateBoxSize() {
- var boxY,
+ var boxX,
+ boxY,
style = text.element.style;
bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
text.getBBox();
- wrapper.width = (width || bBox.width || 0) + 2 * padding;
+ wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft;
wrapper.height = (height || bBox.height || 0) + 2 * padding;
// update the label-scoped y offset
baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
-
-
- // create the border box if it is not already present
- if (!box) {
- boxY = baseline ? -baselineOffset : 0;
-
- wrapper.box = box = shape ?
- renderer.symbol(shape, -alignFactor * padding, boxY, wrapper.width, wrapper.height) :
- renderer.rect(-alignFactor * padding, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
- box.add(wrapper);
+
+ if (needsBox) {
+
+ // create the border box if it is not already present
+ if (!box) {
+ boxX = mathRound(-alignFactor * padding);
+ boxY = baseline ? -baselineOffset : 0;
+
+ wrapper.box = box = shape ?
+ renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height) :
+ renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
+ box.add(wrapper);
+ }
+
+ // apply the box attributes
+ if (!box.isImg) { // #1630
+ box.attr(merge({
+ width: wrapper.width,
+ height: wrapper.height
+ }, deferredAttr));
+ }
+ deferredAttr = null;
}
-
- // apply the box attributes
- box.attr(merge({
- width: wrapper.width,
- height: wrapper.height
- }, deferredAttr));
- deferredAttr = null;
}
/**
* This function runs after setting text or padding, but only if padding is changed
*/
function updateTextPadding() {
var styles = wrapper.styles,
textAlign = styles && styles.textAlign,
- x = padding * (1 - alignFactor),
+ x = paddingLeft + padding * (1 - alignFactor),
y;
// determin y based on the baseline
y = baseline ? 0 : baselineOffset;
@@ -16787,11 +17034,11 @@
text: str, // alignment is available now
x: x,
y: y
});
- if (defined(anchorX)) {
+ if (box && defined(anchorX)) {
wrapper.attr({
anchorX: anchorX,
anchorY: anchorY
});
}
@@ -16814,18 +17061,25 @@
};
attrSetters.height = function (value) {
height = value;
return false;
};
- attrSetters.padding = function (value) {
+ attrSetters.padding = function (value) {
if (defined(value) && value !== padding) {
padding = value;
updateTextPadding();
}
-
return false;
};
+ attrSetters.paddingLeft = function (value) {
+ if (defined(value) && value !== paddingLeft) {
+ paddingLeft = value;
+ updateTextPadding();
+ }
+ return false;
+ };
+
// change local variable and set attribue as well
attrSetters.align = function (value) {
alignFactor = { left: 0, center: 0.5, right: 1 }[value];
return false; // prevent setting text-anchor on the group
@@ -16839,15 +17093,19 @@
return false;
};
// apply these to the box but not to the text
attrSetters[STROKE_WIDTH] = function (value, key) {
+ needsBox = true;
crispAdjust = value % 2 / 2;
boxAttr(key, value);
return false;
};
attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
+ if (key === 'fill') {
+ needsBox = true;
+ }
boxAttr(key, value);
return false;
};
attrSetters.anchorX = function (value, key) {
anchorX = value;
@@ -16869,11 +17127,11 @@
wrapper.attr('translateX', wrapperX);
return false;
};
attrSetters.y = function (value) {
wrapperY = wrapper.y = mathRound(value);
- wrapper.attr('translateY', value);
+ wrapper.attr('translateY', wrapperY);
return false;
};
// Redirect certain methods to either the box or the text
var baseCss = wrapper.css;
@@ -16882,12 +17140,12 @@
* Pick up some properties and apply them to the text instead of the wrapper
*/
css: function (styles) {
if (styles) {
var textStyles = {};
- styles = merge({}, styles); // create a copy to avoid altering the original object (#537)
- each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width'], function (prop) {
+ styles = merge(styles); // create a copy to avoid altering the original object (#537)
+ each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration'], function (prop) {
if (styles[prop] !== UNDEFINED) {
textStyles[prop] = styles[prop];
delete styles[prop];
}
});
@@ -16897,17 +17155,24 @@
},
/**
* Return the bounding box of the box, not the group
*/
getBBox: function () {
- return box.getBBox();
+ return {
+ width: bBox.width + 2 * padding,
+ height: bBox.height + 2 * padding,
+ x: bBox.x - padding,
+ y: bBox.y - padding
+ };
},
/**
* Apply the shadow to the box
*/
shadow: function (b) {
- box.shadow(b);
+ if (box) {
+ box.shadow(b);
+ }
return wrapper;
},
/**
* Destroy and release memory.
*/
@@ -16924,10 +17189,13 @@
if (box) {
box = box.destroy();
}
// Call base implementation to destroy the rest
SVGElement.prototype.destroy.call(wrapper);
+
+ // Release local pointers (#1298)
+ wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = getSizeAfterAdd = null;
}
});
}
}; // end SVGRenderer
@@ -16946,42 +17214,41 @@
*****************************************************************************/
/**
* @constructor
*/
-var VMLRenderer;
+var VMLRenderer, VMLElement;
if (!hasSVG && !useCanVG) {
/**
* The VML element wrapper.
*/
-var VMLElement = {
+Highcharts.VMLElement = VMLElement = {
/**
* Initialize a new VML element wrapper. It builds the markup as a string
* to minimize DOM traffic.
* @param {Object} renderer
* @param {Object} nodeName
*/
init: function (renderer, nodeName) {
var wrapper = this,
markup = ['<', nodeName, ' filled="f" stroked="f"'],
- style = ['position: ', ABSOLUTE, ';'];
+ style = ['position: ', ABSOLUTE, ';'],
+ isDiv = nodeName === DIV;
// divs and shapes need size
- if (nodeName === 'shape' || nodeName === DIV) {
+ if (nodeName === 'shape' || isDiv) {
style.push('left:0;top:0;width:1px;height:1px;');
}
- if (docMode8) {
- style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
- }
-
+ style.push('visibility: ', isDiv ? HIDDEN : VISIBLE);
+
markup.push(' style="', style.join(''), '"/>');
// create element with default attributes and style
if (nodeName) {
- markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ?
+ markup = isDiv || nodeName === 'span' || nodeName === 'img' ?
markup.join('')
: renderer.prepVML(markup);
wrapper.element = createElement(markup);
}
@@ -17017,11 +17284,11 @@
// align text after adding to be able to read offset
wrapper.added = true;
if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
wrapper.updateTransform();
}
-
+
// fire an event for internal hooks
fireEvent(wrapper, 'add');
return wrapper;
},
@@ -17100,11 +17367,12 @@
value = value || [];
wrapper.d = value.join(' '); // used in getter for animation
// convert paths
i = value.length;
- var convertedPath = [];
+ var convertedPath = [],
+ clockwise;
while (i--) {
// Multiply by 10 to allow subpixel precision.
// Substracting half a pixel seems to make the coordinates
// align with SVG, but this hasn't been tested thoroughly
@@ -17112,10 +17380,24 @@
convertedPath[i] = mathRound(value[i] * 10) - 5;
} else if (value[i] === 'Z') { // close the path
convertedPath[i] = 'x';
} else {
convertedPath[i] = value[i];
+
+ // When the start X and end X coordinates of an arc are too close,
+ // they are rounded to the same value above. In this case, substract 1 from the end X
+ // position. #760, #1371.
+ if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {
+ clockwise = value[i] === 'wa' ? 1 : -1; // #1642
+ if (convertedPath[i + 5] === convertedPath[i + 7]) {
+ convertedPath[i + 7] -= clockwise;
+ }
+ // Start and end Y (#1410)
+ if (convertedPath[i + 6] === convertedPath[i + 8]) {
+ convertedPath[i + 8] -= clockwise;
+ }
+ }
}
}
value = convertedPath.join(' ') || 'x';
element.path = value;
@@ -17142,13 +17424,19 @@
// Instead of toggling the visibility CSS property, move the div out of the viewport.
// This works around #61 and #586
if (nodeName === 'DIV') {
value = value === HIDDEN ? '-999em' : 0;
+
+ // In order to redraw, IE7 needs the div to be visible when tucked away
+ // outside the viewport. So the visibility is actually opposite of
+ // the expected value. This applies to the tooltip only.
+ if (!docMode8) {
+ elemStyle[key] = value ? VISIBLE : HIDDEN;
+ }
key = 'top';
}
-
elemStyle[key] = value;
skipAttr = true;
// directly mapped to css
} else if (key === 'zIndex') {
@@ -17156,37 +17444,36 @@
if (value) {
elemStyle[key] = value;
}
skipAttr = true;
- // width and height
- } else if (key === 'width' || key === 'height') {
+ // x, y, width, height
+ } else if (inArray(key, ['x', 'y', 'width', 'height']) !== -1) {
- value = mathMax(0, value); // don't set width or height below zero (#311)
+ wrapper[key] = value; // used in getter
- this[key] = value; // used in getter
-
+ if (key === 'x' || key === 'y') {
+ key = { x: 'left', y: 'top' }[key];
+ } else {
+ value = mathMax(0, value); // don't set width or height below zero (#311)
+ }
+
// clipping rectangle special
if (wrapper.updateClipping) {
- wrapper[key] = value;
+ wrapper[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'
wrapper.updateClipping();
} else {
// normal
elemStyle[key] = value;
}
- skipAttr = true;
+ skipAttr = true;
- // x and y
- } else if (key === 'x' || key === 'y') {
- wrapper[key] = value; // used in getter
- elemStyle[{ x: 'left', y: 'top' }[key]] = value;
-
// class name
- } else if (key === 'class') {
+ } else if (key === 'class' && nodeName === 'DIV') {
// IE8 Standards mode has problems retrieving the className
- element.className = value;
+ element.className = value;
// stroke
} else if (key === 'stroke') {
value = renderer.color(value, element, key);
@@ -17214,17 +17501,24 @@
// fill
} else if (key === 'fill') {
if (nodeName === 'SPAN') { // text color
elemStyle.color = value;
- } else {
+ } else if (nodeName !== 'IMG') { // #1336
element.filled = value !== NONE ? true : false;
value = renderer.color(value, element, key, wrapper);
key = 'fillcolor';
}
+
+ // opacity: don't bother - animation is too slow and filters introduce artifacts
+ } else if (key === 'opacity') {
+ /*css(element, {
+ opacity: value
+ });*/
+ skipAttr = true;
// rotation on VML elements
} else if (nodeName === 'shape' && key === 'rotation') {
wrapper[key] = value;
// Correction for the 1x1 size of the shape container. Used in gauge needles.
@@ -17241,12 +17535,13 @@
// text for rotated and non-rotated elements
} else if (key === 'text') {
this.bBox = null;
element.innerHTML = value;
skipAttr = true;
- }
+ }
+
if (!skipAttr) {
if (docMode8) { // IE8 setAttribute bug
element[key] = value;
} else {
attr(element, key, value);
@@ -17265,24 +17560,19 @@
* @param {String} id The id of the clip rectangle
*/
clip: function (clipRect) {
var wrapper = this,
clipMembers,
- element = wrapper.element,
- parentNode = element.parentNode,
cssRet;
if (clipRect) {
clipMembers = clipRect.members;
+ erase(clipMembers, wrapper); // Ensure unique list of elements (#1258)
clipMembers.push(wrapper);
wrapper.destroyClip = function () {
erase(clipMembers, wrapper);
};
- // Issue #863 workaround - related to #140, #61, #74
- if (parentNode && parentNode.className === 'highcharts-tracker' && !docMode8) {
- css(element, { visibility: HIDDEN });
- }
cssRet = clipRect.getCSS(wrapper);
} else {
if (wrapper.destroyClip) {
wrapper.destroyClip();
@@ -17322,25 +17612,10 @@
return SVGElement.prototype.destroy.apply(this);
},
/**
- * Remove all child nodes of a group, except the v:group element
- */
- empty: function () {
- var element = this.element,
- childNodes = element.childNodes,
- i = childNodes.length,
- node;
-
- while (i--) {
- node = childNodes[i];
- node.parentNode.removeChild(node);
- }
- },
-
- /**
* Add an event listener. VML override for normalizing event parameters.
* @param {String} eventType
* @param {Function} handler
*/
on: function (eventType, handler) {
@@ -17471,10 +17746,11 @@
box.style.position = RELATIVE; // for freeform drawing using renderer directly
container.appendChild(boxWrapper.element);
// generate the containing box
+ renderer.isVML = true;
renderer.box = box;
renderer.boxWrapper = boxWrapper;
renderer.setSize(width, height, false);
@@ -17524,13 +17800,16 @@
left: isObj ? x.x : x,
top: isObj ? x.y : y,
width: isObj ? x.width : width,
height: isObj ? x.height : height,
getCSS: function (wrapper) {
- var inverted = wrapper.inverted,
+ var element = wrapper.element,
+ nodeName = element.nodeName,
+ isShape = nodeName === 'shape',
+ inverted = wrapper.inverted,
rect = this,
- top = rect.top,
+ top = rect.top - (isShape ? element.offsetTop : 0),
left = rect.left,
right = left + rect.width,
bottom = top + rect.height,
ret = {
clip: 'rect(' +
@@ -17539,17 +17818,16 @@
mathRound(inverted ? right : bottom) + 'px,' +
mathRound(inverted ? top : left) + 'px)'
};
// issue 74 workaround
- if (!inverted && docMode8 && wrapper.element.nodeName !== 'IMG') {
+ if (!inverted && docMode8 && nodeName === 'DIV') {
extend(ret, {
width: right + PX,
height: bottom + PX
});
}
-
return ret;
},
// used in attr and animation to update the clipping of all members
updateClipping: function () {
@@ -17722,13 +18000,14 @@
ret = colorObject.get('rgb');
} else {
- var strokeNodes = elem.getElementsByTagName(prop);
- if (strokeNodes.length) {
- strokeNodes[0].opacity = 1;
+ var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node
+ if (propNodes.length) {
+ propNodes[0].opacity = 1;
+ propNodes[0].type = 'solid';
}
ret = color;
}
return ret;
@@ -17791,11 +18070,18 @@
* @param {Number} x
* @param {Number} y
* @param {Number} r
*/
circle: function (x, y, r) {
- return this.symbol('circle').attr({ x: x - r, y: y - r, width: 2 * r, height: 2 * r });
+ var circle = this.symbol('circle');
+ if (isObject(x)) {
+ r = x.r;
+ y = x.y;
+ x = x.x;
+ }
+ circle.isCircle = true; // Causes x and y to mean center (#1682)
+ return circle.attr({ x: x, y: y, width: 2 * r, height: 2 * r });
},
/**
* Create a group using an outer div and an inner v:group to allow rotating
* and flipping. A simple v:group would have problems with positioning
@@ -17882,27 +18168,19 @@
// VML specific arc function
arc: function (x, y, w, h, options) {
var start = options.start,
end = options.end,
radius = options.r || w || h,
+ innerRadius = options.innerR,
cosStart = mathCos(start),
sinStart = mathSin(start),
cosEnd = mathCos(end),
sinEnd = mathSin(end),
- innerRadius = options.innerR,
- circleCorrection = 0.08 / radius, // #760
- innerCorrection = (innerRadius && 0.1 / innerRadius) || 0,
ret;
if (end - start === 0) { // no angle, don't show it.
return ['x'];
-
- } else if (2 * mathPI - end + start < circleCorrection) { // full circle
- // empirical correction found by trying out the limits for different radii
- cosEnd = -circleCorrection;
- } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
- cosEnd = mathCos(start + innerCorrection);
}
ret = [
'wa', // clockwise arc to
x - radius, // left
@@ -17936,16 +18214,23 @@
y + innerRadius * sinStart, // end y
'x', // finish path
'e' // close
);
+ ret.isArc = true;
return ret;
},
// Add circle symbol path. This performs significantly faster than v:oval.
- circle: function (x, y, w, h) {
+ circle: function (x, y, w, h, wrapper) {
+ // Center correction, #1682
+ if (wrapper && wrapper.isCircle) {
+ x -= w / 2;
+ y -= h / 2;
+ }
+ // Return the path
return [
'wa', // clockwisearcto
x, // left
y, // top
x + w, // right
@@ -18026,11 +18311,11 @@
}
return ret;
}
}
};
-VMLRenderer = function () {
+Highcharts.VMLRenderer = VMLRenderer = function () {
this.init.apply(this, arguments);
};
VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);
// general renderer
@@ -18055,11 +18340,11 @@
/**
* The CanVGRenderer is empty from start to keep the source footprint small.
* When requested, the CanVGController downloads the rest of the source packaged
* together with the canvg library.
*/
- CanVGRenderer = function () {
+ Highcharts.CanVGRenderer = CanVGRenderer = function () {
// Override the global SVG namespace to fake SVG/HTML that accepts CSS
SVG_NS = 'http://www.w3.org/1999/xhtml';
};
/**
@@ -18099,32 +18384,30 @@
// Register render call
deferredRenderCalls.push(func);
}
};
}());
+
+ Renderer = CanVGRenderer;
} // end CanVGRenderer
/* ****************************************************************************
* *
* END OF ANDROID < 3 SPECIFIC CODE *
* *
*****************************************************************************/
/**
- * General renderer
- */
-Renderer = VMLRenderer || CanVGRenderer || SVGRenderer;
-/**
* The Tick class
*/
-function Tick(axis, pos, type) {
+function Tick(axis, pos, type, noLabel) {
this.axis = axis;
this.pos = pos;
this.type = type || '';
this.isNew = true;
- if (!type) {
+ if (!type && !noLabel) {
this.addLabel();
}
}
Tick.prototype = {
@@ -18136,24 +18419,27 @@
axis = tick.axis,
options = axis.options,
chart = axis.chart,
horiz = axis.horiz,
categories = axis.categories,
+ names = axis.series[0] && axis.series[0].names,
pos = tick.pos,
labelOptions = options.labels,
str,
tickPositions = axis.tickPositions,
- width = (categories && horiz && categories.length &&
+ width = (horiz && categories &&
!labelOptions.step && !labelOptions.staggerLines &&
!labelOptions.rotation &&
chart.plotWidth / tickPositions.length) ||
- (!horiz && chart.plotWidth / 2),
+ (!horiz && (chart.optionsMarginLeft || chart.plotWidth / 2)), // #1580
isFirst = pos === tickPositions[0],
isLast = pos === tickPositions[tickPositions.length - 1],
css,
attr,
- value = categories && defined(categories[pos]) ? categories[pos] : pos,
+ value = categories ?
+ pick(categories[pos], names && names[pos], pos) :
+ pos,
label = tick.label,
tickPositionInfo = tickPositions.info,
dateTimeLabelFormat;
// Set the datetime label format. If a higher rank is set for this position, use that. If not,
@@ -18363,11 +18649,11 @@
* Put everything in place
*
* @param index {Number}
* @param old {Boolean} Use old coordinates to prepare an animation into new position
*/
- render: function (index, old) {
+ render: function (index, old, opacity) {
var tick = this,
axis = tick.axis,
options = axis.options,
chart = axis.chart,
renderer = chart.renderer,
@@ -18394,15 +18680,18 @@
show = true,
tickmarkOffset = axis.tickmarkOffset,
xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
x = xy.x,
y = xy.y,
+ reverseCrisp = ((horiz && x === axis.pos) || (!horiz && y === axis.pos + axis.len)) ? -1 : 1, // #1480
staggerLines = axis.staggerLines;
+
+ this.isActive = true;
// create the grid line
if (gridLineWidth) {
- gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);
+ gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true);
if (gridLine === UNDEFINED) {
attribs = {
stroke: gridLineColor,
'stroke-width': gridLineWidth
@@ -18411,10 +18700,13 @@
attribs.dashstyle = dashStyle;
}
if (!type) {
attribs.zIndex = 1;
}
+ if (old) {
+ attribs.opacity = 0;
+ }
tick.gridLine = gridLine =
gridLineWidth ?
renderer.path(gridLinePath)
.attr(attribs).add(axis.gridGroup) :
null;
@@ -18422,11 +18714,12 @@
// If the parameter 'old' is set, the current call will be followed
// by another call, therefore do not do any animations this time
if (!old && gridLine && gridLinePath) {
gridLine[tick.isNew ? 'attr' : 'animate']({
- d: gridLinePath
+ d: gridLinePath,
+ opacity: opacity
});
}
}
// create the tick mark
@@ -18438,22 +18731,24 @@
}
if (axis.opposite) {
tickLength = -tickLength;
}
- markPath = tick.getMarkPath(x, y, tickLength, tickWidth, horiz, renderer);
+ markPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer);
if (mark) { // updating
mark.animate({
- d: markPath
+ d: markPath,
+ opacity: opacity
});
} else { // first time
tick.mark = renderer.path(
markPath
).attr({
stroke: tickColor,
- 'stroke-width': tickWidth
+ 'stroke-width': tickWidth,
+ opacity: opacity
}).add(axis.axisGroup);
}
}
// the label is created on init - now move it into place
@@ -18475,16 +18770,16 @@
// show those indices dividable by step
show = false;
}
// Set the new position, and show or hide
- if (show) {
+ if (show && !isNaN(xy.y)) {
+ xy.opacity = opacity;
label[tick.isNew ? 'attr' : 'animate'](xy);
- label.show();
tick.isNew = false;
} else {
- label.hide();
+ label.attr('y', -9999); // #1338
}
}
},
/**
@@ -18504,13 +18799,10 @@
if (options) {
this.options = options;
this.id = options.id;
}
-
- //plotLine.render()
- return this;
}
PlotLineOrBand.prototype = {
/**
@@ -18726,23 +19018,27 @@
/**
* Renders the stack total label and adds it to the stack label group.
*/
render: function (group) {
- var str = this.options.formatter.call(this); // format the text in the label
+ var options = this.options,
+ formatOption = options.format, // docs: added stackLabel.format option
+ str = formatOption ?
+ format(formatOption, this) :
+ options.formatter.call(this); // format the text in the label
// Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
if (this.label) {
this.label.attr({text: str, visibility: HIDDEN});
// Create new label
} else {
this.label =
- this.axis.chart.renderer.text(str, 0, 0) // dummy positions, actual position updated with setOffset method in columnseries
- .css(this.options.style) // apply style
+ this.axis.chart.renderer.text(str, 0, 0, options.useHTML) // dummy positions, actual position updated with setOffset method in columnseries
+ .css(options.style) // apply style
.attr({
align: this.textAlign, // fix the text-anchor
- rotation: this.options.rotation, // rotation
+ rotation: options.rotation, // rotation
visibility: HIDDEN // hidden until setOffset is called
})
.add(group); // add to the labels-group
}
},
@@ -18865,11 +19161,11 @@
align: 'middle', // low, middle or high
//margin: 0 for horizontal, 10 for vertical axes,
//rotation: 0,
//side: 'outside',
style: {
- color: '#6D869F',
+ color: '#4d759e',
//font: defaultFont.replace('normal', 'bold')
fontWeight: 'bold'
}
//x: 0,
//y: 0
@@ -18895,22 +19191,22 @@
minPadding: 0.05,
startOnTick: true,
tickWidth: 0,
title: {
rotation: 270,
- text: 'Y-values'
+ text: 'Values'
},
stackLabels: {
enabled: false,
//align: dynamic,
//y: dynamic,
//x: dynamic,
//verticalAlign: dynamic,
//textAlign: dynamic,
//rotation: 0,
formatter: function () {
- return this.total;
+ return numberFormat(this.total, -1);
},
style: defaultLabelOptions.style
}
},
@@ -19014,30 +19310,30 @@
//axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series
//axis.ignoreMaxPadding = UNDEFINED;
axis.chart = chart;
axis.reversed = options.reversed;
+ axis.zoomEnabled = options.zoomEnabled !== false;
// Initial categories
- axis.categories = options.categories;
+ axis.categories = options.categories || type === 'category';
// Elements
//axis.axisGroup = UNDEFINED;
//axis.gridGroup = UNDEFINED;
//axis.axisTitle = UNDEFINED;
//axis.axisLine = UNDEFINED;
- // Flag if type === logarithmic
+ // Shorthand types
axis.isLog = type === 'logarithmic';
+ axis.isDatetimeAxis = isDatetimeAxis;
// Flag, if axis is linked to another axis
axis.isLinked = defined(options.linkedTo);
// Linked axis.
//axis.linkedParent = UNDEFINED;
- // Flag if type === datetime
- axis.isDatetimeAxis = isDatetimeAxis;
// Flag if percentage mode
//axis.usePercentage = UNDEFINED;
@@ -19045,11 +19341,11 @@
//axis.tickPositions = UNDEFINED; // array containing predefined positions
// Tick intervals
//axis.tickInterval = UNDEFINED;
//axis.minorTickInterval = UNDEFINED;
- axis.tickmarkOffset = (options.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
+ axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
// Major ticks
axis.ticks = {};
// Minor ticks
axis.minorTicks = {};
@@ -19082,10 +19378,11 @@
axis.offset = options.offset || 0;
// Dictionary for stacks
axis.stacks = {};
+ axis._stacksTouched = 0;
// Min and max in the data
//axis.dataMin = UNDEFINED,
//axis.dataMax = UNDEFINED,
@@ -19101,24 +19398,24 @@
var eventType,
events = axis.options.events;
// Register
- chart.axes.push(axis);
- chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);
+ if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update()
+ chart.axes.push(axis);
+ chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);
+ }
- axis.series = []; // populated by Series
+ axis.series = axis.series || []; // populated by Series
// inverted charts have reversed xAxes as default
if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) {
axis.reversed = true;
}
axis.removePlotBand = axis.removePlotBandOrLine;
axis.removePlotLine = axis.removePlotBandOrLine;
- axis.addPlotBand = axis.addPlotBandOrLine;
- axis.addPlotLine = axis.addPlotBandOrLine;
// register event listeners
for (eventType in events) {
addEvent(axis, eventType, events[eventType]);
@@ -19144,11 +19441,56 @@
defaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053)
userOptions
)
);
},
+
+ /**
+ * Update the axis with a new options structure
+ */
+ update: function (newOptions, redraw) {
+ var chart = this.chart;
+
+ newOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions);
+
+ this.destroy();
+ this._addedPlotLB = false; // #1611
+
+ this.init(chart, newOptions);
+
+ chart.isDirtyBox = true;
+ if (pick(redraw, true)) {
+ chart.redraw();
+ }
+ },
+ /**
+ * Remove the axis from the chart
+ */
+ remove: function (redraw) {
+ var chart = this.chart,
+ key = this.xOrY + 'Axis'; // xAxis or yAxis
+
+ // Remove associated series
+ each(this.series, function (series) {
+ series.remove(false);
+ });
+
+ // Remove the axis
+ erase(chart.axes, this);
+ erase(chart[key], this);
+ chart.options[key].splice(this.options.index, 1);
+ each(chart[key], function (axis, i) { // Re-index, #1706
+ axis.options.index = i;
+ });
+ this.destroy();
+ chart.isDirtyBox = true;
+
+ if (pick(redraw, true)) {
+ chart.redraw();
+ }
+ },
/**
* The default label formatter. The context is a special config object for the label.
*/
defaultLabelFormatter: function () {
@@ -19158,15 +19500,19 @@
dateTimeLabelFormat = this.dateTimeLabelFormat,
numericSymbols = defaultOptions.lang.numericSymbols,
i = numericSymbols && numericSymbols.length,
multi,
ret,
+ formatOption = axis.options.labels.format,
// make sure the same symbol is added for all labels on a linear axis
numericSymbolDetector = axis.isLog ? value : axis.tickInterval;
- if (categories) {
+ if (formatOption) {
+ ret = format(formatOption, this);
+
+ } else if (categories) {
ret = value;
} else if (dateTimeLabelFormat) { // datetime axis
ret = dateFormat(dateTimeLabelFormat, value);
@@ -19201,10 +19547,12 @@
var axis = this,
chart = axis.chart,
stacks = axis.stacks,
posStack = [],
negStack = [],
+ stacksTouched = axis._stacksTouched = axis._stacksTouched + 1,
+ type,
i;
axis.hasVisibleSeries = false;
// reset dataMin and dataMax in case we're redrawing
@@ -19227,10 +19575,12 @@
x,
y,
threshold = seriesOptions.threshold,
yDataLength,
activeYData = [],
+ seriesDataMin,
+ seriesDataMax,
activeCounter = 0;
axis.hasVisibleSeries = true;
// Validate threshold in logarithmic axes
@@ -19256,11 +19606,10 @@
//findPointRange,
//pointRange,
j,
hasModifyValue = !!series.modifyValue;
-
// Handle stacking
stacking = seriesOptions.stacking;
axis.usePercentage = stacking === 'percent';
// create a stack for this particular series type
@@ -19298,15 +19647,18 @@
if (stacking) {
isNegative = y < threshold;
pointStack = isNegative ? negPointStack : posPointStack;
key = isNegative ? negKey : stackKey;
- y = pointStack[x] =
- defined(pointStack[x]) ?
- correctFloat(pointStack[x] + y) :
- y;
+ // Set the stack value and y for extremes
+ if (defined(pointStack[x])) { // we're adding to the stack
+ pointStack[x] = correctFloat(pointStack[x] + y);
+ y = [y, pointStack[x]]; // consider both the actual value and the stack (#1376)
+ } else { // it's the first point in the stack
+ pointStack[x] = y;
+ }
// add the series
if (!stacks[key]) {
stacks[key] = {};
}
@@ -19314,24 +19666,26 @@
// If the StackItem is there, just update the values,
// if not, create one first
if (!stacks[key][x]) {
stacks[key][x] = new StackItem(axis, axis.options.stackLabels, isNegative, x, stackOption, stacking);
}
- stacks[key][x].setTotal(y);
+ stacks[key][x].setTotal(pointStack[x]);
+ stacks[key][x].touched = stacksTouched;
}
// Handle non null values
- if (y !== null && y !== UNDEFINED) {
+ if (y !== null && y !== UNDEFINED && (!axis.isLog || (y.length || y > 0))) {
// general hook, used for Highstock compare values feature
if (hasModifyValue) {
y = series.modifyValue(y);
}
- // for points within the visible range, including the first point outside the
+ // For points within the visible range, including the first point outside the
// visible range, consider y extremes
- if (cropped || ((xData[i + 1] || x) >= xExtremes.min && (xData[i - 1] || x) <= xExtremes.max)) {
+ if (series.getExtremesFromAll || cropped || ((xData[i + 1] || x) >= xExtremes.min &&
+ (xData[i - 1] || x) <= xExtremes.max)) {
j = y.length;
if (j) { // array, like ohlc or range data
while (j--) {
if (y[j] !== null) {
@@ -19343,21 +19697,17 @@
}
}
}
}
- // record the least unit distance
- /*if (findPointRange) {
- series.pointRange = pointRange || 1;
- }
- series.closestPointRange = pointRange;*/
-
// Get the dataMin and dataMax so far. If percentage is used, the min and max are
// always 0 and 100. If the length of activeYData is 0, continue with null values.
if (!axis.usePercentage && activeYData.length) {
- axis.dataMin = mathMin(pick(axis.dataMin, activeYData[0]), arrayMin(activeYData));
- axis.dataMax = mathMax(pick(axis.dataMax, activeYData[0]), arrayMax(activeYData));
+ series.dataMin = seriesDataMin = arrayMin(activeYData);
+ series.dataMax = seriesDataMax = arrayMax(activeYData);
+ axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin);
+ axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax);
}
// Adjust to threshold
if (defined(threshold)) {
if (axis.dataMin >= threshold) {
@@ -19369,10 +19719,20 @@
}
}
}
}
});
+
+ // Destroy unused stacks (#1044)
+ for (type in stacks) {
+ for (i in stacks[type]) {
+ if (stacks[type][i].touched < stacksTouched) {
+ stacks[type][i].destroy();
+ delete stacks[type][i];
+ }
+ }
+ }
},
/**
* Translate from axis value to pixel position on the chart, or back
@@ -19384,54 +19744,81 @@
sign = 1,
cvsOffset = 0,
localA = old ? axis.oldTransA : axis.transA,
localMin = old ? axis.oldMin : axis.min,
returnValue,
- postTranslate = axis.options.ordinal || (axis.isLog && handleLog);
+ minPixelPadding = axis.minPixelPadding,
+ postTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val;
if (!localA) {
localA = axis.transA;
}
+ // In vertical axes, the canvas coordinates start from 0 at the top like in
+ // SVG.
if (cvsCoord) {
sign *= -1; // canvas coordinates inverts the value
cvsOffset = axisLength;
}
- if (axis.reversed) { // reversed axis
+
+ // Handle reversed axis
+ if (axis.reversed) {
sign *= -1;
cvsOffset -= sign * axisLength;
}
+ // From pixels to value
if (backwards) { // reverse translation
- if (axis.reversed) {
- val = axisLength - val;
- }
+
+ val = val * sign + cvsOffset;
+ val -= minPixelPadding;
returnValue = val / localA + localMin; // from chart pixel to value
if (postTranslate) { // log and ordinal axes
returnValue = axis.lin2val(returnValue);
}
- } else { // normal translation, from axis value to pixel, relative to plot
+ // From value to pixels
+ } else {
if (postTranslate) { // log and ordinal axes
val = axis.val2lin(val);
}
- returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * axis.minPixelPadding) +
+ returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) +
(pointPlacementBetween ? localA * axis.pointRange / 2 : 0);
}
return returnValue;
},
/**
+ * Utility method to translate an axis value to pixel position.
+ * @param {Number} value A value in terms of axis units
+ * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart
+ * or just the axis/pane itself.
+ */
+ toPixels: function (value, paneCoordinates) {
+ return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos);
+ },
+
+ /*
+ * Utility method to translate a pixel position in to an axis value
+ * @param {Number} pixel The pixel value coordinate
+ * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the
+ * axis/pane itself.
+ */
+ toValue: function (pixel, paneCoordinates) {
+ return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true);
+ },
+
+ /**
* Create the path for a plot line that goes from the given value on
* this axis, across the plot to the opposite side
* @param {Number} value
* @param {Number} lineWidth Used for calculation crisp line
* @param {Number] old Use old coordinates (for resizing and rescaling)
*/
- getPlotLinePath: function (value, lineWidth, old) {
+ getPlotLinePath: function (value, lineWidth, old, force) {
var axis = this,
chart = axis.chart,
axisLeft = axis.left,
axisTop = axis.top,
x1,
@@ -19462,11 +19849,11 @@
if (y1 < axisTop || y1 > axisTop + axis.height) {
skip = true;
}
}
- return skip ?
+ return skip && !force ?
null :
chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
},
/**
@@ -19527,15 +19914,14 @@
* Set the tick positions of a logarithmic axis
*/
getLogTickPositions: function (interval, min, max, minor) {
var axis = this,
options = axis.options,
- axisLength = axis.len;
-
- // Since we use this method for both major and minor ticks,
- // use a local variable and return the result
- var positions = [];
+ axisLength = axis.len,
+ // Since we use this method for both major and minor ticks,
+ // use a local variable and return the result
+ positions = [];
// Reset
if (!minor) {
axis._minorAutoInterval = null;
}
@@ -19568,11 +19954,11 @@
for (i = roundedMin; i < max + 1 && !break2; i++) {
len = intermediate.length;
for (j = 0; j < len && !break2; j++) {
pos = log2lin(lin2log(i) * intermediate[j]);
- if (pos > min) {
+ if (pos > min && (!minor || lastPos <= max)) { // #1670
positions.push(lastPos);
}
if (lastPos > max) {
break2 = true;
@@ -19626,14 +20012,14 @@
* Return the minor tick positions. For logarithmic axes, reuse the same logic
* as for major ticks.
*/
getMinorTickPositions: function () {
var axis = this,
+ options = axis.options,
tickPositions = axis.tickPositions,
- minorTickInterval = axis.minorTickInterval;
-
- var minorTickPositions = [],
+ minorTickInterval = axis.minorTickInterval,
+ minorTickPositions = [],
pos,
i,
len;
if (axis.isLog) {
@@ -19641,17 +20027,27 @@
for (i = 1; i < len; i++) {
minorTickPositions = minorTickPositions.concat(
axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)
);
}
-
+ } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314
+ minorTickPositions = minorTickPositions.concat(
+ getTimeTicks(
+ normalizeTimeTickInterval(minorTickInterval),
+ axis.min,
+ axis.max,
+ options.startOfWeek
+ )
+ );
+ if (minorTickPositions[0] < axis.min) {
+ minorTickPositions.shift();
+ }
} else {
for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {
- minorTickPositions.push(pos);
+ minorTickPositions.push(pos);
}
}
-
return minorTickPositions;
},
/**
* Adjust the min and max for the minimum range. Keep in mind that the series data is
@@ -19731,18 +20127,19 @@
},
/**
* Update translation information
*/
- setAxisTranslation: function () {
+ setAxisTranslation: function (saveOld) {
var axis = this,
range = axis.max - axis.min,
pointRange = 0,
closestPointRange,
minPointOffset = 0,
pointRangePadding = 0,
linkedParent = axis.linkedParent,
+ ordinalCorrection,
transA = axis.transA;
// adjust translation for padding
if (axis.isXAxis) {
if (linkedParent) {
@@ -19753,10 +20150,13 @@
each(axis.series, function (series) {
var seriesPointRange = series.pointRange,
pointPlacement = series.options.pointPlacement,
seriesClosestPointRange = series.closestPointRange;
+ if (seriesPointRange > range) { // #1446
+ seriesPointRange = 0;
+ }
pointRange = mathMax(pointRange, seriesPointRange);
// minPointOffset is the value padding to the left of the axis in order to make
// room for points with a pointRange, typically columns. When the pointPlacement option
// is 'between' or 'on', this padding does not apply.
@@ -19780,25 +20180,27 @@
}
});
}
// Record minPointOffset and pointRangePadding
- axis.minPointOffset = minPointOffset;
- axis.pointRangePadding = pointRangePadding;
+ ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853
+ axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection;
+ axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection;
// pointRange means the width reserved for each point, like in a column chart
- axis.pointRange = pointRange;
+ axis.pointRange = mathMin(pointRange, range);
// closestPointRange means the closest distance between points. In columns
// it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
// is some other value
axis.closestPointRange = closestPointRange;
}
- // secondary values
- axis.oldTransA = transA;
- //axis.translationSlope = axis.transA = transA = axis.len / ((range + (2 * minPointOffset)) || 1);
+ // Secondary values
+ if (saveOld) {
+ axis.oldTransA = transA;
+ }
axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1);
axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend
axis.minPixelPadding = transA * minPointOffset;
},
@@ -19854,23 +20256,31 @@
axis.userMax = axis.max;
if (secondPass) {
axis.range = null; // don't use it when running setExtremes
}
}
+
+ // Hook for adjusting this.min and this.max. Used by bubble series.
+ if (axis.beforePadding) {
+ axis.beforePadding();
+ }
// adjust min and max for the minimum range
axis.adjustForMinRange();
-
- // pad the values to get clear of the chart's edges
+
+ // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding
+ // into account, we do this after computing tick interval (#1337).
if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
- length = (axis.max - axis.min) || 1;
- if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
- axis.min -= length * minPadding;
+ length = axis.max - axis.min;
+ if (length) {
+ if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
+ axis.min -= length * minPadding;
+ }
+ if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
+ axis.max += length * maxPadding;
+ }
}
- if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
- axis.max += length * maxPadding;
- }
}
// get tickInterval
if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
axis.tickInterval = 1;
@@ -19893,11 +20303,11 @@
series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);
});
}
// set the translation factor used in translate function
- axis.setAxisTranslation(secondPass);
+ axis.setAxisTranslation(true);
// hook for ordinal axes and radial axes
if (axis.beforeSetTickPositions) {
axis.beforeSetTickPositions();
}
@@ -19923,11 +20333,13 @@
// get minorTickInterval
axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ?
axis.tickInterval / 5 : options.minorTickInterval;
// find the tick positions
- axis.tickPositions = tickPositions = options.tickPositions || (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));
+ axis.tickPositions = tickPositions = options.tickPositions ?
+ [].concat(options.tickPositions) : // Work on a copy (#1565)
+ (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));
if (!tickPositions) {
if (isDatetimeAxis) {
tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
normalizeTimeTickInterval(axis.tickInterval, options.units),
axis.min,
@@ -19948,11 +20360,12 @@
if (!isLinked) {
// reset min/max or remove extremes based on start/end on tick
var roundedMin = tickPositions[0],
roundedMax = tickPositions[tickPositions.length - 1],
- minPointOffset = axis.minPointOffset || 0;
+ minPointOffset = axis.minPointOffset || 0,
+ singlePad;
if (options.startOnTick) {
axis.min = roundedMin;
} else if (axis.min - minPointOffset > roundedMin) {
tickPositions.shift();
@@ -19962,54 +20375,55 @@
axis.max = roundedMax;
} else if (axis.max + minPointOffset < roundedMax) {
tickPositions.pop();
}
+ // When there is only one point, or all points have the same value on this axis, then min
+ // and max are equal and tickPositions.length is 1. In this case, add some padding
+ // in order to center the point, but leave it with one tick. #1337.
+ if (tickPositions.length === 1) {
+ singlePad = 0.001; // The lowest possible number to avoid extra padding on columns
+ axis.min -= singlePad;
+ axis.max += singlePad;
+ }
}
},
/**
* Set the max ticks of either the x and y axis collection
*/
setMaxTicks: function () {
var chart = this.chart,
- maxTicks = chart.maxTicks,
+ maxTicks = chart.maxTicks || {},
tickPositions = this.tickPositions,
- xOrY = this.xOrY;
+ key = this._maxTicksKey = [this.xOrY, this.pos, this.len].join('-');
- if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
- maxTicks = {
- x: 0,
- y: 0
- };
+ if (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) {
+ maxTicks[key] = tickPositions.length;
}
-
- if (!this.isLinked && !this.isDatetimeAxis && tickPositions.length > maxTicks[xOrY] && this.options.alignTicks !== false) {
- maxTicks[xOrY] = tickPositions.length;
- }
chart.maxTicks = maxTicks;
},
/**
* When using multiple axes, adjust the number of ticks to match the highest
* number of ticks in that group
*/
adjustTickAmount: function () {
var axis = this,
chart = axis.chart,
- xOrY = axis.xOrY,
+ key = axis._maxTicksKey,
tickPositions = axis.tickPositions,
maxTicks = chart.maxTicks;
- if (maxTicks && maxTicks[xOrY] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale
+ if (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale
var oldTickAmount = axis.tickAmount,
calculatedTickAmount = tickPositions.length,
tickAmount;
// set the axis-level tickAmount to use below
- axis.tickAmount = tickAmount = maxTicks[xOrY];
+ axis.tickAmount = tickAmount = maxTicks[key];
if (calculatedTickAmount < tickAmount) {
while (tickPositions.length < tickAmount) {
tickPositions.push(correctFloat(
tickPositions[tickPositions.length - 1] + axis.tickInterval
@@ -20053,13 +20467,15 @@
isDirtyData = true;
}
});
// do we really need to go through all this?
- if (isDirtyAxisLength || isDirtyData || axis.isLinked ||
+ if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||
axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {
+ axis.forceRedraw = false;
+
// get data extremes if needed
axis.getSeriesExtremes();
// get fixed positions based on tickInterval
axis.setTickPositions();
@@ -20129,34 +20545,60 @@
/**
* Overridable method for zooming chart. Pulled out in a separate method to allow overriding
* in stock charts.
*/
zoom: function (newMin, newMax) {
- this.setExtremes(newMin, newMax, false, UNDEFINED, { trigger: 'zoom' });
+
+ // Prevent pinch zooming out of range
+ if (!this.allowZoomOutside) {
+ if (newMin <= this.dataMin) {
+ newMin = UNDEFINED;
+ }
+ if (newMax >= this.dataMax) {
+ newMax = UNDEFINED;
+ }
+ }
+
+ // In full view, displaying the reset zoom button is not required
+ this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED;
+
+ // Do it
+ this.setExtremes(
+ newMin,
+ newMax,
+ false,
+ UNDEFINED,
+ { trigger: 'zoom' }
+ );
return true;
},
/**
* Update the axis metrics
*/
setAxisSize: function () {
- var axis = this,
- chart = axis.chart,
- options = axis.options;
+ var chart = this.chart,
+ options = this.options,
+ offsetLeft = options.offsetLeft || 0,
+ offsetRight = options.offsetRight || 0,
+ horiz = this.horiz,
+ width,
+ height,
+ top,
+ left;
- var offsetLeft = options.offsetLeft || 0,
- offsetRight = options.offsetRight || 0;
+ // Expose basic values to use in Series object and navigator
+ this.left = left = pick(options.left, chart.plotLeft + offsetLeft);
+ this.top = top = pick(options.top, chart.plotTop);
+ this.width = width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);
+ this.height = height = pick(options.height, chart.plotHeight);
+ this.bottom = chart.chartHeight - height - top;
+ this.right = chart.chartWidth - width - left;
- // basic values
- // expose to use in Series object and navigator
- axis.left = pick(options.left, chart.plotLeft + offsetLeft);
- axis.top = pick(options.top, chart.plotTop);
- axis.width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);
- axis.height = pick(options.height, chart.plotHeight);
- axis.bottom = chart.chartHeight - axis.height - axis.top;
- axis.right = chart.chartWidth - axis.width - axis.left;
- axis.len = mathMax(axis.horiz ? axis.width : axis.height, 0); // mathMax fixes #905
+ // Direction agnostic properties
+ this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905
+ this.pos = horiz ? left : top; // distance from SVG origin
},
/**
* Get the actual axis extremes
*/
@@ -20192,18 +20634,35 @@
}
return axis.translate(threshold, 0, 1, 0, 1);
},
+ addPlotBand: function (options) {
+ this.addPlotBandOrLine(options, 'plotBands');
+ },
+
+ addPlotLine: function (options) {
+ this.addPlotBandOrLine(options, 'plotLines');
+ },
+
/**
* Add a plot band or plot line after render time
*
* @param options {Object} The plotBand or plotLine configuration object
*/
- addPlotBandOrLine: function (options) {
- var obj = new PlotLineOrBand(this, options).render();
- this.plotLinesAndBands.push(obj);
+ addPlotBandOrLine: function (options, coll) {
+ var obj = new PlotLineOrBand(this, options).render(),
+ userOptions = this.userOptions;
+
+ // Add it to the user options for exporting and Axis.update
+ if (coll) {
+ userOptions[coll] = userOptions[coll] || [];
+ userOptions[coll].push(options);
+ }
+
+ this.plotLinesAndBands.push(obj);
+
return obj;
},
/**
* Render the tick labels to a preliminary position to get their sizes
@@ -20215,28 +20674,28 @@
options = axis.options,
tickPositions = axis.tickPositions,
ticks = axis.ticks,
horiz = axis.horiz,
side = axis.side,
+ invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side,
hasData,
showAxis,
titleOffset = 0,
titleOffsetOption,
titleMargin = 0,
axisTitleOptions = options.title,
labelOptions = options.labels,
labelOffset = 0, // reset
axisOffset = chart.axisOffset,
+ clipOffset = chart.clipOffset,
directionFactor = [-1, 1, 1, -1][side],
n;
-
// For reuse in Axis.render
axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
-
// Create the axisGroup and gridGroup elements on first iteration
if (!axis.axisGroup) {
axis.gridGroup = renderer.g('grid')
.attr({ zIndex: options.gridZIndex || 1 })
.add();
@@ -20280,11 +20739,11 @@
ticks[n].destroy();
delete ticks[n];
}
}
- if (axisTitleOptions && axisTitleOptions.text) {
+ if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) {
if (!axis.axisTitle) {
axis.axisTitle = renderer.text(
axisTitleOptions.text,
0,
0,
@@ -20313,21 +20772,21 @@
}
// handle automatic or user set offset
axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
-
axis.axisTitleMargin =
pick(titleOffsetOption,
labelOffset + titleMargin +
(side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
);
axisOffset[side] = mathMax(
axisOffset[side],
axis.axisTitleMargin + titleOffset + directionFactor * axis.offset
);
+ clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], options.lineWidth);
},
/**
* Get the path for the axis line
@@ -20339,10 +20798,13 @@
horiz = this.horiz,
lineLeft = this.left + (opposite ? this.width : 0) + offset,
lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;
this.lineTop = lineTop; // used by flag series
+ if (!opposite) {
+ lineWidth *= -1; // crispify the other way - #1480
+ }
return chart.renderer.crispLine([
M,
horiz ?
this.left :
@@ -20426,10 +20888,18 @@
hasData = axis.hasData,
showAxis = axis.showAxis,
from,
to;
+ // Mark all elements inActive before we go over and mark the active ones
+ each([ticks, minorTicks, alternateBands], function (coll) {
+ var pos;
+ for (pos in coll) {
+ coll[pos].isActive = false;
+ }
+ });
+
// If the series has data draw the ticks. Else only the line and title
if (hasData || isLinked) {
// minor ticks
if (axis.minorTickInterval && !axis.categories) {
@@ -20441,41 +20911,49 @@
// render new ticks in old position
if (slideInTicks && minorTicks[pos].isNew) {
minorTicks[pos].render(null, true);
}
-
- minorTicks[pos].isActive = true;
- minorTicks[pos].render();
+ minorTicks[pos].render(null, false, 1);
});
}
// Major ticks. Pull out the first item and render it last so that
// we can get the position of the neighbour label. #808.
- each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
-
- // Reorganize the indices
- i = (i === tickPositions.length - 1) ? 0 : i + 1;
-
- // linked axes need an extra check to find out if
- if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
-
- if (!ticks[pos]) {
- ticks[pos] = new Tick(axis, pos);
+ if (tickPositions.length) { // #1300
+ each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
+
+ // Reorganize the indices
+ i = (i === tickPositions.length - 1) ? 0 : i + 1;
+
+ // linked axes need an extra check to find out if
+ if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
+
+ if (!ticks[pos]) {
+ ticks[pos] = new Tick(axis, pos);
+ }
+
+ // render new ticks in old position
+ if (slideInTicks && ticks[pos].isNew) {
+ ticks[pos].render(i, true);
+ }
+
+ ticks[pos].render(i, false, 1);
}
-
- // render new ticks in old position
- if (slideInTicks && ticks[pos].isNew) {
- ticks[pos].render(i, true);
+
+ });
+ // In a categorized axis, the tick marks are displayed between labels. So
+ // we need to add a tick mark and grid line at the left edge of the X axis.
+ if (tickmarkOffset && axis.min === 0) {
+ if (!ticks[-1]) {
+ ticks[-1] = new Tick(axis, -1, null, true);
}
-
- ticks[pos].isActive = true;
- ticks[pos].render(i);
+ ticks[-1].render(-1);
}
+
+ }
- });
-
// alternate grid color
if (alternateGridColor) {
each(tickPositions, function (pos, i) {
if (i % 2 === 0 && pos < axis.max) {
if (!alternateBands[pos]) {
@@ -20495,29 +20973,52 @@
}
// custom plot lines and bands
if (!axis._addedPlotLB) { // only first time
each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
- //plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
axis.addPlotBandOrLine(plotLineOptions);
});
axis._addedPlotLB = true;
}
} // end if hasData
- // remove inactive ticks
+ // Remove inactive ticks
each([ticks, minorTicks, alternateBands], function (coll) {
- var pos;
+ var pos,
+ i,
+ forDestruction = [],
+ delay = globalAnimation ? globalAnimation.duration || 500 : 0,
+ destroyInactiveItems = function () {
+ i = forDestruction.length;
+ while (i--) {
+ // When resizing rapidly, the same items may be destroyed in different timeouts,
+ // or the may be reactivated
+ if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) {
+ coll[forDestruction[i]].destroy();
+ delete coll[forDestruction[i]];
+ }
+ }
+
+ };
+
for (pos in coll) {
+
if (!coll[pos].isActive) {
- coll[pos].destroy();
- delete coll[pos];
- } else {
- coll[pos].isActive = false; // reset
+ // Render to zero opacity
+ coll[pos].render(pos, false, 0);
+ coll[pos].isActive = false;
+ forDestruction.push(pos);
}
}
+
+ // When the objects are finished fading out, destroy them
+ if (coll === alternateBands || !chart.hasRendered || !delay) {
+ destroyInactiveItems();
+ } else if (delay) {
+ setTimeout(destroyInactiveItems, delay);
+ }
});
// Static items. As the axis group is cleared on subsequent calls
// to render, these items are added outside the group.
// axis line
@@ -20596,34 +21097,24 @@
/**
* Update the axis title by options
*/
setTitle: function (newTitleOptions, redraw) {
- var chart = this.chart,
- options = this.options,
- axisTitle = this.axisTitle;
-
- options.title = merge(options.title, newTitleOptions);
-
- this.axisTitle = axisTitle && axisTitle.destroy(); // #922
- this.isDirty = true;
-
- if (pick(redraw, true)) {
- chart.redraw();
- }
+ this.update({ title: newTitleOptions }, redraw);
},
/**
* Redraw the axis to reflect changes in the data or axis extremes
*/
redraw: function () {
var axis = this,
- chart = axis.chart;
+ chart = axis.chart,
+ pointer = chart.pointer;
// hide tooltip and hover states
- if (chart.tracker.resetTracker) {
- chart.tracker.resetTracker(true);
+ if (pointer.reset) {
+ pointer.reset(true);
}
// render the axis
axis.render();
@@ -20639,33 +21130,15 @@
},
/**
* Set new axis categories and optionally redraw
- * @param {Array} newCategories
- * @param {Boolean} doRedraw
+ * @param {Array} categories
+ * @param {Boolean} redraw
*/
- setCategories: function (newCategories, doRedraw) {
- var axis = this,
- chart = axis.chart;
-
- // set the categories
- axis.categories = axis.userOptions.categories = newCategories;
-
- // force reindexing tooltips
- each(axis.series, function (series) {
- series.translate();
- series.setTooltipPoints(true);
- });
-
-
- // optionally redraw
- axis.isDirty = true;
-
- if (pick(doRedraw, true)) {
- chart.redraw();
- }
+ setCategories: function (categories, redraw) {
+ this.update({ categories: categories }, redraw);
},
/**
* Destroys an Axis instance.
*/
@@ -20703,56 +21176,63 @@
/**
* The tooltip object
* @param {Object} chart The chart instance
* @param {Object} options Tooltip options
*/
-function Tooltip(chart, options) {
- var borderWidth = options.borderWidth,
- style = options.style,
- padding = pInt(style.padding);
+function Tooltip() {
+ this.init.apply(this, arguments);
+}
- // Save the chart and options
- this.chart = chart;
- this.options = options;
+Tooltip.prototype = {
- // Keep track of the current series
- //this.currentSeries = UNDEFINED;
+ init: function (chart, options) {
- // List of crosshairs
- this.crosshairs = [];
+ var borderWidth = options.borderWidth,
+ style = options.style,
+ padding = pInt(style.padding);
- // Current values of x and y when animating
- this.now = { x: 0, y: 0 };
+ // Save the chart and options
+ this.chart = chart;
+ this.options = options;
- // The tooltip is initially hidden
- this.isHidden = true;
+ // Keep track of the current series
+ //this.currentSeries = UNDEFINED;
- // create the label
- this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')
- .attr({
- padding: padding,
- fill: options.backgroundColor,
- 'stroke-width': borderWidth,
- r: options.borderRadius,
- zIndex: 8
- })
- .css(style)
- .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)
- .hide()
- .add();
+ // List of crosshairs
+ this.crosshairs = [];
- // When using canVG the shadow shows up as a gray circle
- // even if the tooltip is hidden.
- if (!useCanVG) {
- this.label.shadow(options.shadow);
- }
+ // Current values of x and y when animating
+ this.now = { x: 0, y: 0 };
- // Public property for getting the shared state.
- this.shared = options.shared;
-}
+ // The tooltip is initially hidden
+ this.isHidden = true;
-Tooltip.prototype = {
+
+ // create the label
+ this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')
+ .attr({
+ padding: padding,
+ fill: options.backgroundColor,
+ 'stroke-width': borderWidth,
+ r: options.borderRadius,
+ zIndex: 8
+ })
+ .css(style)
+ .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)
+ .hide()
+ .add();
+
+ // When using canVG the shadow shows up as a gray circle
+ // even if the tooltip is hidden.
+ if (!useCanVG) {
+ this.label.shadow(options.shadow);
+ }
+
+ // Public property for getting the shared state.
+ this.shared = options.shared;
+ },
+
/**
* Destroy the tooltip and its elements.
*/
destroy: function () {
each(this.crosshairs, function (crosshair) {
@@ -20763,10 +21243,12 @@
// Destroy and clear local variables
if (this.label) {
this.label = this.label.destroy();
}
+ clearTimeout(this.hideTimer);
+ clearTimeout(this.tooltipTimeout);
},
/**
* Provide a soft movement for the tooltip
*
@@ -20810,24 +21292,30 @@
/**
* Hide the tooltip
*/
hide: function () {
+ var tooltip = this,
+ hoverPoints;
+
+ clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766)
if (!this.isHidden) {
- var hoverPoints = this.chart.hoverPoints;
+ hoverPoints = this.chart.hoverPoints;
- this.label.hide();
+ this.hideTimer = setTimeout(function () {
+ tooltip.label.fadeOut();
+ tooltip.isHidden = true;
+ }, pick(this.options.hideDelay, 500));
// hide previous hoverPoints and set new
if (hoverPoints) {
each(hoverPoints, function (point) {
point.setState();
});
}
this.chart.hoverPoints = null;
- this.isHidden = true;
}
},
/**
* Hide the crosshairs
@@ -20846,35 +21334,46 @@
*/
getAnchor: function (points, mouseEvent) {
var ret,
chart = this.chart,
inverted = chart.inverted,
+ plotTop = chart.plotTop,
plotX = 0,
plotY = 0,
yAxis;
points = splat(points);
// Pie uses a special tooltipPos
ret = points[0].tooltipPos;
+ // When tooltip follows mouse, relate the position to the mouse
+ if (this.followPointer && mouseEvent) {
+ if (mouseEvent.chartX === UNDEFINED) {
+ mouseEvent = chart.pointer.normalize(mouseEvent);
+ }
+ ret = [
+ mouseEvent.chartX - chart.plotLeft,
+ mouseEvent.chartY - plotTop
+ ];
+ }
// When shared, use the average position
if (!ret) {
each(points, function (point) {
yAxis = point.series.yAxis;
plotX += point.plotX;
plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
- (!inverted && yAxis ? yAxis.top - chart.plotTop : 0); // #1151
+ (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
});
plotX /= points.length;
plotY /= points.length;
ret = [
inverted ? chart.plotWidth - plotY : plotX,
this.shared && !inverted && points.length > 1 && mouseEvent ?
- mouseEvent.chartY - chart.plotTop : // place shared tooltip next to the mouse (#424)
+ mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)
inverted ? chart.plotHeight - plotX : plotY
];
}
return map(ret, mathRound);
@@ -20930,60 +21429,61 @@
return {x: x, y: y};
},
/**
+ * In case no user defined formatter is given, this will be used. Note that the context
+ * here is an object holding point, series, x, y etc.
+ */
+ defaultFormatter: function (tooltip) {
+ var items = this.points || splat(this),
+ series = items[0].series,
+ s;
+
+ // build the header
+ s = [series.tooltipHeaderFormatter(items[0])];
+
+ // build the values
+ each(items, function (item) {
+ series = item.series;
+ s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
+ item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
+ });
+
+ // footer
+ s.push(tooltip.options.footerFormat || '');
+
+ return s.join('');
+ },
+
+ /**
* Refresh the tooltip's text and position.
* @param {Object} point
*/
refresh: function (point, mouseEvent) {
var tooltip = this,
chart = tooltip.chart,
label = tooltip.label,
- options = tooltip.options;
-
- /**
- * In case no user defined formatter is given, this will be used
- */
- function defaultFormatter() {
- var pThis = this,
- items = pThis.points || splat(pThis),
- series = items[0].series,
- s;
-
- // build the header
- s = [series.tooltipHeaderFormatter(items[0].key)];
-
- // build the values
- each(items, function (item) {
- series = item.series;
- s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
- item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
- });
-
- // footer
- s.push(options.footerFormat || '');
-
- return s.join('');
- }
-
- var x,
+ options = tooltip.options,
+ x,
y,
show,
anchor,
textConfig = {},
text,
pointConfig = [],
- formatter = options.formatter || defaultFormatter,
+ formatter = options.formatter || tooltip.defaultFormatter,
hoverPoints = chart.hoverPoints,
- placedTooltipPoint,
borderColor,
crosshairsOptions = options.crosshairs,
shared = tooltip.shared,
currentSeries;
+ clearTimeout(this.hideTimer);
+
// get the reference point coordinates (pie charts use tooltipPos)
+ tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer;
anchor = tooltip.getAnchor(point, mouseEvent);
x = anchor[0];
y = anchor[1];
// shared tooltip, array is sent over
@@ -21013,11 +21513,11 @@
// single point tooltip
} else {
textConfig = point.getLabelConfig();
}
- text = formatter.call(textConfig);
+ text = formatter.call(textConfig, tooltip);
// register the current series
currentSeries = point.series;
@@ -21029,11 +21529,12 @@
this.hide();
} else {
// show it
if (tooltip.isHidden) {
- label.show();
+ stop(label);
+ label.attr('opacity', 1).show();
}
// update text
label.attr({
text: text
@@ -21042,45 +21543,36 @@
// set the stroke color of the box
borderColor = options.borderColor || point.color || currentSeries.color || '#606060';
label.attr({
stroke: borderColor
});
-
- placedTooltipPoint = (options.positioner || tooltip.getPosition).call(
- tooltip,
- label.width,
- label.height,
- { plotX: x, plotY: y }
- );
-
- // do the move
- tooltip.move(
- mathRound(placedTooltipPoint.x),
- mathRound(placedTooltipPoint.y),
- x + chart.plotLeft,
- y + chart.plotTop
- );
-
- tooltip.isHidden = false;
+ tooltip.updatePosition({ plotX: x, plotY: y });
+
+ this.isHidden = false;
}
// crosshairs
if (crosshairsOptions) {
crosshairsOptions = splat(crosshairsOptions); // [x, y]
var path,
i = crosshairsOptions.length,
attribs,
- axis;
+ axis,
+ val;
while (i--) {
axis = point.series[i ? 'yAxis' : 'xAxis'];
if (crosshairsOptions[i] && axis) {
+ val = i ? pick(point.stackY, point.y) : point.x; // #814
+ if (axis.isLog) { // #1671
+ val = log2lin(val);
+ }
path = axis.getPlotLinePath(
- i ? pick(point.stackY, point.y) : point.x, // #814
+ val,
1
);
if (tooltip.crosshairs[i]) {
tooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE });
@@ -21104,54 +21596,80 @@
text: text,
x: x + chart.plotLeft,
y: y + chart.plotTop,
borderColor: borderColor
});
+ },
+
+ /**
+ * Find the new position and perform the move
+ */
+ updatePosition: function (point) {
+ var chart = this.chart,
+ label = this.label,
+ pos = (this.options.positioner || this.getPosition).call(
+ this,
+ label.width,
+ label.height,
+ point
+ );
+
+ // do the move
+ this.move(
+ mathRound(pos.x),
+ mathRound(pos.y),
+ point.plotX + chart.plotLeft,
+ point.plotY + chart.plotTop
+ );
}
};
/**
- * The mouse tracker object
+ * The mouse tracker object. All methods starting with "on" are primary DOM event handlers.
+ * Subsequent methods should be named differently from what they are doing.
* @param {Object} chart The Chart instance
* @param {Object} options The root options object
*/
-function MouseTracker(chart, options) {
- var zoomType = useCanVG ? '' : options.chart.zoomType;
+function Pointer(chart, options) {
+ this.init(chart, options);
+}
- // Zoom status
- this.zoomX = /x/.test(zoomType);
- this.zoomY = /y/.test(zoomType);
+Pointer.prototype = {
+ /**
+ * Initialize Pointer
+ */
+ init: function (chart, options) {
+
+ var zoomType = useCanVG ? '' : options.chart.zoomType,
+ inverted = chart.inverted,
+ zoomX,
+ zoomY;
- // Store reference to options
- this.options = options;
+ // Store references
+ this.options = options;
+ this.chart = chart;
+
+ // Zoom status
+ this.zoomX = zoomX = /x/.test(zoomType);
+ this.zoomY = zoomY = /y/.test(zoomType);
+ this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
+ this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
- // Reference to the chart
- this.chart = chart;
+ this.pinchDown = [];
+ this.lastValidTouch = {};
- // The interval id
- //this.tooltipTimeout = UNDEFINED;
+ if (options.tooltip.enabled) {
+ chart.tooltip = new Tooltip(chart, options.tooltip);
+ }
- // The cached x hover position
- //this.hoverX = UNDEFINED;
+ this.setDOMEvents();
+ },
- // The chart position
- //this.chartPosition = UNDEFINED;
-
- // The selection marker element
- //this.selectionMarker = UNDEFINED;
-
- // False or a value > 0 if a dragging operation
- //this.mouseDownX = UNDEFINED;
- //this.mouseDownY = UNDEFINED;
- this.init(chart, options.tooltip);
-}
-
-MouseTracker.prototype = {
/**
* Add crossbrowser support for chartX and chartY
* @param {Object} e The event object in standard browsers
*/
- normalizeMouseEvent: function (e) {
+ normalize: function (e) {
var chartPosition,
chartX,
chartY,
ePos;
@@ -21186,31 +21704,22 @@
},
/**
* Get the click position in terms of axis values.
*
- * @param {Object} e A mouse event
+ * @param {Object} e A pointer event
*/
- getMouseCoordinates: function (e) {
+ getCoordinates: function (e) {
var coordinates = {
xAxis: [],
yAxis: []
- },
- chart = this.chart;
+ };
- each(chart.axes, function (axis) {
- var isXAxis = axis.isXAxis,
- isHorizontal = chart.inverted ? !isXAxis : isXAxis;
-
- coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
+ each(this.chart.axes, function (axis) {
+ coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
axis: axis,
- value: axis.translate(
- (isHorizontal ?
- e.chartX - chart.plotLeft :
- axis.top + axis.len - e.chartY) - axis.minPixelPadding, // #1051
- true
- )
+ value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
});
});
return coordinates;
},
@@ -21224,53 +21733,57 @@
chart.plotHeight + chart.plotTop - e.chartY :
e.chartX - chart.plotLeft;
},
/**
- * With line type charts with a single tracker, get the point closest to the mouse
+ * With line type charts with a single tracker, get the point closest to the mouse.
+ * Run Point.onMouseOver and display tooltip for the point or points.
*/
- onmousemove: function (e) {
- var mouseTracker = this,
- chart = mouseTracker.chart,
+ runPointActions: function (e) {
+ var pointer = this,
+ chart = pointer.chart,
series = chart.series,
tooltip = chart.tooltip,
point,
points,
hoverPoint = chart.hoverPoint,
hoverSeries = chart.hoverSeries,
i,
j,
distance = chart.chartWidth,
- index = mouseTracker.getIndex(e);
+ index = pointer.getIndex(e),
+ anchor;
// shared tooltip
- if (tooltip && mouseTracker.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
+ if (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
points = [];
// loop over all series and find the ones with points closest to the mouse
i = series.length;
for (j = 0; j < i; j++) {
if (series[j].visible &&
series[j].options.enableMouseTracking !== false &&
!series[j].noSharedTooltip && series[j].tooltipPoints.length) {
point = series[j].tooltipPoints[index];
- point._dist = mathAbs(index - point[series[j].xAxis.tooltipPosName || 'plotX']);
- distance = mathMin(distance, point._dist);
- points.push(point);
+ if (point.series) { // not a dummy point, #1544
+ point._dist = mathAbs(index - point.clientX);
+ distance = mathMin(distance, point._dist);
+ points.push(point);
+ }
}
}
// remove furthest points
i = points.length;
while (i--) {
if (points[i]._dist > distance) {
points.splice(i, 1);
}
}
// refresh the tooltip if necessary
- if (points.length && (points[0].plotX !== mouseTracker.hoverX)) {
+ if (points.length && (points[0].clientX !== pointer.hoverX)) {
tooltip.refresh(points, e);
- mouseTracker.hoverX = points[0].plotX;
+ pointer.hoverX = points[0].clientX;
}
}
// separate tooltip and general mouse events
if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
@@ -21280,26 +21793,30 @@
// a new point is hovered, refresh the tooltip
if (point && point !== hoverPoint) {
// trigger the events
- point.onMouseOver();
+ point.onMouseOver(e);
}
+
+ } else if (tooltip && tooltip.followPointer && !tooltip.isHidden) {
+ anchor = tooltip.getAnchor([{}], e);
+ tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
}
},
/**
* Reset the tracking by hiding the tooltip, the hover series state and the hover point
*
* @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible
*/
- resetTracker: function (allowMove) {
- var mouseTracker = this,
- chart = mouseTracker.chart,
+ reset: function (allowMove) {
+ var pointer = this,
+ chart = pointer.chart,
hoverSeries = chart.hoverSeries,
hoverPoint = chart.hoverPoint,
tooltip = chart.tooltip,
tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint;
@@ -21329,445 +21846,663 @@
if (tooltip) {
tooltip.hide();
tooltip.hideCrosshairs();
}
- mouseTracker.hoverX = null;
+ pointer.hoverX = null;
}
},
/**
- * Set the JS events on the container element
+ * Scale series groups to a certain scale and translation
*/
- setDOMEvents: function () {
- var lastWasOutsidePlot = true,
- mouseTracker = this,
- chart = mouseTracker.chart,
- container = chart.container,
- hasDragged,
- zoomHor = (mouseTracker.zoomX && !chart.inverted) || (mouseTracker.zoomY && chart.inverted),
- zoomVert = (mouseTracker.zoomY && !chart.inverted) || (mouseTracker.zoomX && chart.inverted);
+ scaleGroups: function (attribs, clip) {
- /**
- * Mouse up or outside the plot area
- */
- function drop() {
- if (mouseTracker.selectionMarker) {
- var selectionData = {
- xAxis: [],
- yAxis: []
- },
- selectionBox = mouseTracker.selectionMarker.getBBox(),
- selectionLeft = selectionBox.x - chart.plotLeft,
- selectionTop = selectionBox.y - chart.plotTop,
- runZoom;
+ var chart = this.chart;
- // a selection has been made
- if (hasDragged) {
-
- // record each axis' min and max
- each(chart.axes, function (axis) {
- if (axis.options.zoomEnabled !== false) {
- var isXAxis = axis.isXAxis,
- isHorizontal = chart.inverted ? !isXAxis : isXAxis,
- selectionMin = axis.translate(
- isHorizontal ?
- selectionLeft :
- chart.plotHeight - selectionTop - selectionBox.height,
- true,
- 0,
- 0,
- 1
- ),
- selectionMax = axis.translate(
- (isHorizontal ?
- selectionLeft + selectionBox.width :
- chart.plotHeight - selectionTop) -
- 2 * axis.minPixelPadding, // #875
- true,
- 0,
- 0,
- 1
- );
-
- if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
- selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
- axis: axis,
- min: mathMin(selectionMin, selectionMax), // for reversed axes,
- max: mathMax(selectionMin, selectionMax)
- });
- runZoom = true;
- }
- }
- });
- if (runZoom) {
- fireEvent(chart, 'selection', selectionData, function (args) { chart.zoom(args); });
- }
-
+ // Scale each series
+ each(chart.series, function (series) {
+ if (series.xAxis && series.xAxis.zoomEnabled) {
+ series.group.attr(attribs);
+ if (series.markerGroup) {
+ series.markerGroup.attr(attribs);
+ series.markerGroup.clip(clip ? chart.clipRect : null);
}
- mouseTracker.selectionMarker = mouseTracker.selectionMarker.destroy();
+ if (series.dataLabelsGroup) {
+ series.dataLabelsGroup.attr(attribs);
+ }
}
+ });
+
+ // Clip
+ chart.clipRect.attr(clip || chart.clipBox);
+ },
- if (chart) { // it may be destroyed on mouse up - #877
- css(container, { cursor: 'auto' });
- chart.cancelClick = hasDragged; // #370
- chart.mouseIsDown = hasDragged = false;
- }
+ /**
+ * Run translation operations for each direction (horizontal and vertical) independently
+ */
+ pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
+ var chart = this.chart,
+ xy = horiz ? 'x' : 'y',
+ XY = horiz ? 'X' : 'Y',
+ sChartXY = 'chart' + XY,
+ wh = horiz ? 'width' : 'height',
+ plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
+ selectionWH,
+ selectionXY,
+ clipXY,
+ scale = 1,
+ inverted = chart.inverted,
+ bounds = chart.bounds[horiz ? 'h' : 'v'],
+ singleTouch = pinchDown.length === 1,
+ touch0Start = pinchDown[0][sChartXY],
+ touch0Now = touches[0][sChartXY],
+ touch1Start = !singleTouch && pinchDown[1][sChartXY],
+ touch1Now = !singleTouch && touches[1][sChartXY],
+ outOfBounds,
+ transformScale,
+ scaleKey,
+ setScale = function () {
+ if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis
+ scale = mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start);
+ }
+
+ clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
+ selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale;
+ };
- removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
- }
+ // Set the scale, first pass
+ setScale();
- /**
- * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
- */
- mouseTracker.hideTooltipOnMouseMove = function (e) {
+ selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not
- // Get e.pageX and e.pageY back in MooTools
- e = washMouseEvent(e);
+ // Out of bounds
+ if (selectionXY < bounds.min) {
+ selectionXY = bounds.min;
+ outOfBounds = true;
+ } else if (selectionXY + selectionWH > bounds.max) {
+ selectionXY = bounds.max - selectionWH;
+ outOfBounds = true;
+ }
+
+ // Is the chart dragged off its bounds, determined by dataMin and dataMax?
+ if (outOfBounds) {
- // If we're outside, hide the tooltip
- if (mouseTracker.chartPosition && chart.hoverSeries && chart.hoverSeries.isCartesian &&
- !chart.isInsidePlot(e.pageX - mouseTracker.chartPosition.left - chart.plotLeft,
- e.pageY - mouseTracker.chartPosition.top - chart.plotTop)) {
- mouseTracker.resetTracker();
+ // Modify the touchNow position in order to create an elastic drag movement. This indicates
+ // to the user that the chart is responsive but can't be dragged further.
+ touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
+ if (!singleTouch) {
+ touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
}
- };
- /**
- * When mouse leaves the container, hide the tooltip.
- */
- mouseTracker.hideTooltipOnMouseLeave = function () {
- mouseTracker.resetTracker();
- mouseTracker.chartPosition = null; // also reset the chart position, used in #149 fix
- };
+ // Set the scale, second pass to adapt to the modified touchNow positions
+ setScale();
+ } else {
+ lastValidTouch[xy] = [touch0Now, touch1Now];
+ }
- /*
- * Record the starting position of a dragoperation
- */
- container.onmousedown = function (e) {
- e = mouseTracker.normalizeMouseEvent(e);
+
+ // Set geometry for clipping, selection and transformation
+ if (!inverted) { // TODO: implement clipping for inverted charts
+ clip[xy] = clipXY - plotLeftTop;
+ clip[wh] = selectionWH;
+ }
+ scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
+ transformScale = inverted ? 1 / scale : scale;
- // issue #295, dragging not always working in Firefox
- if (!hasTouch && e.preventDefault) {
+ selectionMarker[wh] = selectionWH;
+ selectionMarker[xy] = selectionXY;
+ transform[scaleKey] = scale;
+ transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start));
+ },
+
+ /**
+ * Handle touch events with two touches
+ */
+ pinch: function (e) {
+
+ var self = this,
+ chart = self.chart,
+ pinchDown = self.pinchDown,
+ followTouchMove = chart.tooltip && chart.tooltip.options.followTouchMove,
+ touches = e.touches,
+ touchesLength = touches.length,
+ lastValidTouch = self.lastValidTouch,
+ zoomHor = self.zoomHor || self.pinchHor,
+ zoomVert = self.zoomVert || self.pinchVert,
+ hasZoom = zoomHor || zoomVert,
+ selectionMarker = self.selectionMarker,
+ transform = {},
+ clip = {};
+
+ // On touch devices, only proceed to trigger click if a handler is defined
+ if (e.type === 'touchstart') {
+ if (followTouchMove || hasZoom) {
e.preventDefault();
}
+ }
+
+ // Normalize each touch
+ map(touches, function (e) {
+ return self.normalize(e);
+ });
+
+ // Register the touch start position
+ if (e.type === 'touchstart') {
+ each(touches, function (e, i) {
+ pinchDown[i] = { chartX: e.chartX, chartY: e.chartY };
+ });
+ lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX];
+ lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY];
- // record the start position
- chart.mouseIsDown = true;
- chart.cancelClick = false;
- chart.mouseDownX = mouseTracker.mouseDownX = e.chartX;
- mouseTracker.mouseDownY = e.chartY;
+ // Identify the data bounds in pixels
+ each(chart.axes, function (axis) {
+ if (axis.zoomEnabled) {
+ var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
+ minPixelPadding = axis.minPixelPadding,
+ min = axis.toPixels(axis.dataMin),
+ max = axis.toPixels(axis.dataMax),
+ absMin = mathMin(min, max),
+ absMax = mathMax(min, max);
- addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
- };
+ // Store the bounds for use in the touchmove handler
+ bounds.min = mathMin(axis.pos, absMin - minPixelPadding);
+ bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding);
+ }
+ });
+
+ // Event type is touchmove, handle panning and pinching
+ } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first
+
- // The mousemove, touchmove and touchstart event handler
- var mouseMove = function (e) {
- // let the system handle multitouch operations like two finger scroll
- // and pinching
- if (e && e.touches && e.touches.length > 1) {
- return;
+ // Set the marker
+ if (!selectionMarker) {
+ self.selectionMarker = selectionMarker = extend({
+ destroy: noop
+ }, chart.plotBox);
}
- // normalize
- e = mouseTracker.normalizeMouseEvent(e);
- if (!hasTouch) { // not for touch devices
- e.returnValue = false;
+
+
+ if (zoomHor) {
+ self.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
}
+ if (zoomVert) {
+ self.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
+ }
- var chartX = e.chartX,
- chartY = e.chartY,
- isOutsidePlot = !chart.isInsidePlot(chartX - chart.plotLeft, chartY - chart.plotTop);
+ self.hasPinched = hasZoom;
- // on touch devices, only trigger click if a handler is defined
- if (hasTouch && e.type === 'touchstart') {
- if (attr(e.target, 'isTracker')) {
- if (!chart.runTrackerClick) {
- e.preventDefault();
- }
- } else if (!chart.runChartClick && !isOutsidePlot) {
- e.preventDefault();
- }
+ // Scale and translate the groups to provide visual feedback during pinching
+ self.scaleGroups(transform, clip);
+
+ // Optionally move the tooltip on touchmove
+ if (!hasZoom && followTouchMove && touchesLength === 1) {
+ this.runPointActions(self.normalize(e));
}
+ }
+ },
- // cancel on mouse outside
- if (isOutsidePlot) {
+ /**
+ * Start a drag operation
+ */
+ dragStart: function (e) {
+ var chart = this.chart;
- /*if (!lastWasOutsidePlot) {
- // reset the tracker
- resetTracker();
- }*/
+ // Record the start position
+ chart.mouseIsDown = e.type;
+ chart.cancelClick = false;
+ chart.mouseDownX = this.mouseDownX = e.chartX;
+ this.mouseDownY = e.chartY;
+ },
- // drop the selection if any and reset mouseIsDown and hasDragged
- //drop();
- if (chartX < chart.plotLeft) {
- chartX = chart.plotLeft;
- } else if (chartX > chart.plotLeft + chart.plotWidth) {
- chartX = chart.plotLeft + chart.plotWidth;
- }
+ /**
+ * Perform a drag operation in response to a mousemove event while the mouse is down
+ */
+ drag: function (e) {
- if (chartY < chart.plotTop) {
- chartY = chart.plotTop;
- } else if (chartY > chart.plotTop + chart.plotHeight) {
- chartY = chart.plotTop + chart.plotHeight;
+ var chart = this.chart,
+ chartOptions = chart.options.chart,
+ chartX = e.chartX,
+ chartY = e.chartY,
+ zoomHor = this.zoomHor,
+ zoomVert = this.zoomVert,
+ plotLeft = chart.plotLeft,
+ plotTop = chart.plotTop,
+ plotWidth = chart.plotWidth,
+ plotHeight = chart.plotHeight,
+ clickedInside,
+ size,
+ mouseDownX = this.mouseDownX,
+ mouseDownY = this.mouseDownY;
+
+ // If the mouse is outside the plot area, adjust to cooordinates
+ // inside to prevent the selection marker from going outside
+ if (chartX < plotLeft) {
+ chartX = plotLeft;
+ } else if (chartX > plotLeft + plotWidth) {
+ chartX = plotLeft + plotWidth;
+ }
+
+ if (chartY < plotTop) {
+ chartY = plotTop;
+ } else if (chartY > plotTop + plotHeight) {
+ chartY = plotTop + plotHeight;
+ }
+
+ // determine if the mouse has moved more than 10px
+ this.hasDragged = Math.sqrt(
+ Math.pow(mouseDownX - chartX, 2) +
+ Math.pow(mouseDownY - chartY, 2)
+ );
+ if (this.hasDragged > 10) {
+ clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
+
+ // make a selection
+ if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) {
+ if (!this.selectionMarker) {
+ this.selectionMarker = chart.renderer.rect(
+ plotLeft,
+ plotTop,
+ zoomHor ? 1 : plotWidth,
+ zoomVert ? 1 : plotHeight,
+ 0
+ )
+ .attr({
+ fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)',
+ zIndex: 7
+ })
+ .add();
}
}
- if (chart.mouseIsDown && e.type !== 'touchstart') { // make selection
+ // adjust the width of the selection marker
+ if (this.selectionMarker && zoomHor) {
+ size = chartX - mouseDownX;
+ this.selectionMarker.attr({
+ width: mathAbs(size),
+ x: (size > 0 ? 0 : size) + mouseDownX
+ });
+ }
+ // adjust the height of the selection marker
+ if (this.selectionMarker && zoomVert) {
+ size = chartY - mouseDownY;
+ this.selectionMarker.attr({
+ height: mathAbs(size),
+ y: (size > 0 ? 0 : size) + mouseDownY
+ });
+ }
- // determine if the mouse has moved more than 10px
- hasDragged = Math.sqrt(
- Math.pow(mouseTracker.mouseDownX - chartX, 2) +
- Math.pow(mouseTracker.mouseDownY - chartY, 2)
- );
- if (hasDragged > 10) {
- var clickedInside = chart.isInsidePlot(mouseTracker.mouseDownX - chart.plotLeft, mouseTracker.mouseDownY - chart.plotTop);
+ // panning
+ if (clickedInside && !this.selectionMarker && chartOptions.panning) {
+ chart.pan(chartX);
+ }
+ }
+ },
- // make a selection
- if (chart.hasCartesianSeries && (mouseTracker.zoomX || mouseTracker.zoomY) && clickedInside) {
- if (!mouseTracker.selectionMarker) {
- mouseTracker.selectionMarker = chart.renderer.rect(
- chart.plotLeft,
- chart.plotTop,
- zoomHor ? 1 : chart.plotWidth,
- zoomVert ? 1 : chart.plotHeight,
- 0
- )
- .attr({
- fill: mouseTracker.options.chart.selectionMarkerFill || 'rgba(69,114,167,0.25)',
- zIndex: 7
- })
- .add();
- }
- }
+ /**
+ * On mouse up or touch end across the entire document, drop the selection.
+ */
+ drop: function (e) {
+ var chart = this.chart,
+ hasPinched = this.hasPinched;
- // adjust the width of the selection marker
- if (mouseTracker.selectionMarker && zoomHor) {
- var xSize = chartX - mouseTracker.mouseDownX;
- mouseTracker.selectionMarker.attr({
- width: mathAbs(xSize),
- x: (xSize > 0 ? 0 : xSize) + mouseTracker.mouseDownX
- });
- }
- // adjust the height of the selection marker
- if (mouseTracker.selectionMarker && zoomVert) {
- var ySize = chartY - mouseTracker.mouseDownY;
- mouseTracker.selectionMarker.attr({
- height: mathAbs(ySize),
- y: (ySize > 0 ? 0 : ySize) + mouseTracker.mouseDownY
- });
- }
+ if (this.selectionMarker) {
+ var selectionData = {
+ xAxis: [],
+ yAxis: [],
+ originalEvent: e.originalEvent || e
+ },
+ selectionBox = this.selectionMarker,
+ selectionLeft = selectionBox.x,
+ selectionTop = selectionBox.y,
+ runZoom;
+ // a selection has been made
+ if (this.hasDragged || hasPinched) {
- // panning
- if (clickedInside && !mouseTracker.selectionMarker && mouseTracker.options.chart.panning) {
- chart.pan(chartX);
+ // record each axis' min and max
+ each(chart.axes, function (axis) {
+ if (axis.zoomEnabled) {
+ var horiz = axis.horiz,
+ selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)),
+ selectionMax = axis.toValue((horiz ? selectionLeft + selectionBox.width : selectionTop + selectionBox.height));
+
+ if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
+ selectionData[axis.xOrY + 'Axis'].push({
+ axis: axis,
+ min: mathMin(selectionMin, selectionMax), // for reversed axes,
+ max: mathMax(selectionMin, selectionMax)
+ });
+ runZoom = true;
+ }
}
+ });
+ if (runZoom) {
+ fireEvent(chart, 'selection', selectionData, function (args) {
+ chart.zoom(extend(args, hasPinched ? { animation: false } : null));
+ });
}
- }
-
- // Show the tooltip and run mouse over events (#977)
- if (!isOutsidePlot) {
- mouseTracker.onmousemove(e);
}
+ this.selectionMarker = this.selectionMarker.destroy();
- lastWasOutsidePlot = isOutsidePlot;
+ // Reset scaling preview
+ if (hasPinched) {
+ this.scaleGroups({
+ translateX: chart.plotLeft,
+ translateY: chart.plotTop,
+ scaleX: 1,
+ scaleY: 1
+ });
+ }
+ }
- // when outside plot, allow touch-drag by returning true
- return isOutsidePlot || !chart.hasCartesianSeries;
- };
+ // Reset all
+ if (chart) { // it may be destroyed on mouse up - #877
+ css(chart.container, { cursor: chart._cursor });
+ chart.cancelClick = this.hasDragged > 10; // #370
+ chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
+ this.pinchDown = [];
+ }
+ },
- /*
- * When the mouse enters the container, run mouseMove
- */
- container.onmousemove = mouseMove;
+ onContainerMouseDown: function (e) {
- /*
- * When the mouse leaves the container, hide the tracking (tooltip).
- */
- addEvent(container, 'mouseleave', mouseTracker.hideTooltipOnMouseLeave);
+ e = this.normalize(e);
- // issue #149 workaround
- // The mouseleave event above does not always fire. Whenever the mouse is moving
- // outside the plotarea, hide the tooltip
- addEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove);
+ // issue #295, dragging not always working in Firefox
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+
+ this.dragStart(e);
+ },
- container.ontouchstart = function (e) {
- // For touch devices, use touchmove to zoom
- if (mouseTracker.zoomX || mouseTracker.zoomY) {
- container.onmousedown(e);
- }
- // Show tooltip and prevent the lower mouse pseudo event
- mouseMove(e);
- };
+
- /*
- * Allow dragging the finger over the chart to read the values on touch
- * devices
- */
- container.ontouchmove = mouseMove;
+ onDocumentMouseUp: function (e) {
+ this.drop(e);
+ },
- /*
- * Allow dragging the finger over the chart to read the values on touch
- * devices
- */
- container.ontouchend = function () {
- if (hasDragged) {
- mouseTracker.resetTracker();
- }
- };
+ /**
+ * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
+ * Issue #149 workaround. The mouseleave event does not always fire.
+ */
+ onDocumentMouseMove: function (e) {
+ var chart = this.chart,
+ chartPosition = this.chartPosition,
+ hoverSeries = chart.hoverSeries;
+ // Get e.pageX and e.pageY back in MooTools
+ e = washMouseEvent(e);
- // MooTools 1.2.3 doesn't fire this in IE when using addEvent
- container.onclick = function (e) {
- var hoverPoint = chart.hoverPoint,
- plotX,
- plotY;
- e = mouseTracker.normalizeMouseEvent(e);
+ // If we're outside, hide the tooltip
+ if (chartPosition && hoverSeries && hoverSeries.isCartesian &&
+ !chart.isInsidePlot(e.pageX - chartPosition.left - chart.plotLeft,
+ e.pageY - chartPosition.top - chart.plotTop)) {
+ this.reset();
+ }
+ },
- e.cancelBubble = true; // IE specific
+ /**
+ * When mouse leaves the container, hide the tooltip.
+ */
+ onContainerMouseLeave: function () {
+ this.reset();
+ this.chartPosition = null; // also reset the chart position, used in #149 fix
+ },
+ // The mousemove, touchmove and touchstart event handler
+ onContainerMouseMove: function (e) {
- if (!chart.cancelClick) {
- // Detect clicks on trackers or tracker groups, #783
- if (hoverPoint && (attr(e.target, 'isTracker') || attr(e.target.parentNode, 'isTracker'))) {
- plotX = hoverPoint.plotX;
- plotY = hoverPoint.plotY;
+ var chart = this.chart;
- // add page position info
- extend(hoverPoint, {
- pageX: mouseTracker.chartPosition.left + chart.plotLeft +
- (chart.inverted ? chart.plotWidth - plotY : plotX),
- pageY: mouseTracker.chartPosition.top + chart.plotTop +
- (chart.inverted ? chart.plotHeight - plotX : plotY)
- });
+ // normalize
+ e = this.normalize(e);
- // the series click event
- fireEvent(hoverPoint.series, 'click', extend(e, {
- point: hoverPoint
- }));
+ // #295
+ e.returnValue = false;
+
+
+ if (chart.mouseIsDown === 'mousedown') {
+ this.drag(e);
+ }
+
+ // Show the tooltip and run mouse over events (#977)
+ if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && !chart.openMenu) {
+ this.runPointActions(e);
+ }
+ },
- // the point click event
+ /**
+ * Utility to detect whether an element has, or has a parent with, a specific
+ * class name. Used on detection of tracker objects and on deciding whether
+ * hovering the tooltip should cause the active series to mouse out.
+ */
+ inClass: function (element, className) {
+ var elemClassName;
+ while (element) {
+ elemClassName = attr(element, 'class');
+ if (elemClassName) {
+ if (elemClassName.indexOf(className) !== -1) {
+ return true;
+ } else if (elemClassName.indexOf(PREFIX + 'container') !== -1) {
+ return false;
+ }
+ }
+ element = element.parentNode;
+ }
+ },
+
+ onTrackerMouseOut: function (e) {
+ var series = this.chart.hoverSeries;
+ if (series && !series.options.stickyTracking && !this.inClass(e.toElement || e.relatedTarget, PREFIX + 'tooltip')) {
+ series.onMouseOut();
+ }
+ },
+
+ onContainerClick: function (e) {
+ var chart = this.chart,
+ hoverPoint = chart.hoverPoint,
+ plotLeft = chart.plotLeft,
+ plotTop = chart.plotTop,
+ inverted = chart.inverted,
+ chartPosition,
+ plotX,
+ plotY;
+
+ e = this.normalize(e);
+ e.cancelBubble = true; // IE specific
+
+ if (!chart.cancelClick) {
+
+ // On tracker click, fire the series and point events. #783, #1583
+ if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) {
+ chartPosition = this.chartPosition;
+ plotX = hoverPoint.plotX;
+ plotY = hoverPoint.plotY;
+
+ // add page position info
+ extend(hoverPoint, {
+ pageX: chartPosition.left + plotLeft +
+ (inverted ? chart.plotWidth - plotY : plotX),
+ pageY: chartPosition.top + plotTop +
+ (inverted ? chart.plotHeight - plotX : plotY)
+ });
+
+ // the series click event
+ fireEvent(hoverPoint.series, 'click', extend(e, {
+ point: hoverPoint
+ }));
+
+ // the point click event
+ if (chart.hoverPoint) { // it may be destroyed (#1844)
hoverPoint.firePointEvent('click', e);
+ }
- } else {
- extend(e, mouseTracker.getMouseCoordinates(e));
+ // When clicking outside a tracker, fire a chart event
+ } else {
+ extend(e, this.getCoordinates(e));
- // fire a click event in the chart
- if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
- fireEvent(chart, 'click', e);
- }
+ // fire a click event in the chart
+ if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
+ fireEvent(chart, 'click', e);
}
+ }
+ }
+ },
+
+ onContainerTouchStart: function (e) {
+ var chart = this.chart;
+
+ if (e.touches.length === 1) {
+
+ e = this.normalize(e);
+
+ if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
+
+ // Prevent the click pseudo event from firing unless it is set in the options
+ /*if (!chart.runChartClick) {
+ e.preventDefault();
+ }*/
+
+ // Run mouse events and display tooltip etc
+ this.runPointActions(e);
+
+ this.pinch(e);
}
- };
+ } else if (e.touches.length === 2) {
+ this.pinch(e);
+ }
},
+ onContainerTouchMove: function (e) {
+ if (e.touches.length === 1 || e.touches.length === 2) {
+ this.pinch(e);
+ }
+ },
+
+ onDocumentTouchEnd: function (e) {
+ this.drop(e);
+ },
+
/**
- * Destroys the MouseTracker object and disconnects DOM events.
+ * Set the JS DOM events on the container and document. This method should contain
+ * a one-to-one assignment between methods and their handlers. Any advanced logic should
+ * be moved to the handler reflecting the event's name.
*/
- destroy: function () {
- var mouseTracker = this,
- chart = mouseTracker.chart,
- container = chart.container;
+ setDOMEvents: function () {
- // Destroy the tracker group element
- if (chart.trackerGroup) {
- chart.trackerGroup = chart.trackerGroup.destroy();
+ var pointer = this,
+ container = pointer.chart.container,
+ events;
+
+ this._events = events = [
+ [container, 'onmousedown', 'onContainerMouseDown'],
+ [container, 'onmousemove', 'onContainerMouseMove'],
+ [container, 'onclick', 'onContainerClick'],
+ [container, 'mouseleave', 'onContainerMouseLeave'],
+ [doc, 'mousemove', 'onDocumentMouseMove'],
+ [doc, 'mouseup', 'onDocumentMouseUp']
+ ];
+
+ if (hasTouch) {
+ events.push(
+ [container, 'ontouchstart', 'onContainerTouchStart'],
+ [container, 'ontouchmove', 'onContainerTouchMove'],
+ [doc, 'touchend', 'onDocumentTouchEnd']
+ );
}
- removeEvent(container, 'mouseleave', mouseTracker.hideTooltipOnMouseLeave);
- removeEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove);
- container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null;
+ each(events, function (eventConfig) {
- // memory and CPU leak
- clearInterval(this.tooltipTimeout);
+ // First, create the callback function that in turn calls the method on Pointer
+ pointer['_' + eventConfig[2]] = function (e) {
+ pointer[eventConfig[2]](e);
+ };
+
+ // Now attach the function, either as a direct property or through addEvent
+ if (eventConfig[1].indexOf('on') === 0) {
+ eventConfig[0][eventConfig[1]] = pointer['_' + eventConfig[2]];
+ } else {
+ addEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);
+ }
+ });
+
+
},
- // Run MouseTracker
- init: function (chart, options) {
- if (!chart.trackerGroup) {
- chart.trackerGroup = chart.renderer.g('tracker')
- .attr({ zIndex: 9 })
- .add();
- }
+ /**
+ * Destroys the Pointer object and disconnects DOM events.
+ */
+ destroy: function () {
+ var pointer = this;
- if (options.enabled) {
- chart.tooltip = new Tooltip(chart, options);
- }
+ // Release all DOM events
+ each(pointer._events, function (eventConfig) {
+ if (eventConfig[1].indexOf('on') === 0) {
+ eventConfig[0][eventConfig[1]] = null; // delete breaks oldIE
+ } else {
+ removeEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);
+ }
+ });
+ delete pointer._events;
- this.setDOMEvents();
+ // memory and CPU leak
+ clearInterval(pointer.tooltipTimeout);
}
};
/**
* The overview of the chart's series
*/
-function Legend(chart) {
-
- this.init(chart);
+function Legend(chart, options) {
+ this.init(chart, options);
}
Legend.prototype = {
/**
* Initialize the legend
*/
- init: function (chart) {
+ init: function (chart, options) {
+
var legend = this,
- options = legend.options = chart.options.legend;
+ itemStyle = options.itemStyle,
+ padding = pick(options.padding, 8),
+ itemMarginTop = options.itemMarginTop || 0;
+ this.options = options;
+
if (!options.enabled) {
return;
}
- var //style = options.style || {}, // deprecated
- itemStyle = options.itemStyle,
- padding = pick(options.padding, 8),
- itemMarginTop = options.itemMarginTop || 0;
-
legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype
legend.itemStyle = itemStyle;
legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);
legend.itemMarginTop = itemMarginTop;
legend.padding = padding;
legend.initialItemX = padding;
legend.initialItemY = padding - 5; // 5 is the number of pixels above the text
legend.maxItemWidth = 0;
legend.chart = chart;
- //legend.allItems = UNDEFINED;
- //legend.legendWidth = UNDEFINED;
- //legend.legendHeight = UNDEFINED;
- //legend.offsetWidth = UNDEFINED;
legend.itemHeight = 0;
legend.lastLineHeight = 0;
- //legend.itemX = UNDEFINED;
- //legend.itemY = UNDEFINED;
- //legend.lastItemY = UNDEFINED;
-
- // Elements
- //legend.group = UNDEFINED;
- //legend.box = UNDEFINED;
- // run legend
+ // Render it
legend.render();
// move checkboxes
- addEvent(legend.chart, 'endResize', function () { legend.positionCheckboxes(); });
+ addEvent(legend.chart, 'endResize', function () {
+ legend.positionCheckboxes();
+ });
-/* // expose
- return {
- colorizeItem: colorizeItem,
- destroyItem: destroyItem,
- render: render,
- destroy: destroy,
- getLegendWidth: getLegendWidth,
- getLegendHeight: getLegendHeight
- };*/
},
/**
* Set the colors for the legend item
* @param {Object} item A Series or Point instance
@@ -21790,11 +22525,11 @@
key,
val;
if (legendItem) {
- legendItem.css({ fill: textColor });
+ legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE
}
if (legendLine) {
legendLine.attr({ stroke: symbolColor });
}
@@ -21879,23 +22614,53 @@
},
/**
* Position the checkboxes after the width is determined
*/
- positionCheckboxes: function () {
- var legend = this;
+ positionCheckboxes: function (scrollOffset) {
+ var alignAttr = this.group.alignAttr,
+ translateY,
+ clipHeight = this.clipHeight || this.legendHeight;
- each(legend.allItems, function (item) {
- var checkbox = item.checkbox,
- alignAttr = legend.group.alignAttr;
- if (checkbox) {
- css(checkbox, {
- left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
- top: (alignAttr.translateY + checkbox.y + 3) + PX
- });
+ if (alignAttr) {
+ translateY = alignAttr.translateY;
+ each(this.allItems, function (item) {
+ var checkbox = item.checkbox,
+ top;
+
+ if (checkbox) {
+ top = (translateY + checkbox.y + (scrollOffset || 0) + 3);
+ css(checkbox, {
+ left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
+ top: top + PX,
+ display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE
+ });
+ }
+ });
+ }
+ },
+
+ /**
+ * Render the legend title on top of the legend
+ */
+ renderTitle: function () {
+ var options = this.options,
+ padding = this.padding,
+ titleOptions = options.title,
+ titleHeight = 0;
+
+ if (titleOptions.text) {
+ if (!this.title) {
+ this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title')
+ .attr({ zIndex: 1 })
+ .css(titleOptions.style)
+ .add(this.group);
}
- });
+ titleHeight = this.title.getBBox().height;
+ this.contentGroup.attr({ translateY: titleHeight });
+ }
+ this.titleHeight = titleHeight;
},
/**
* Render a single specific legend item
* @param {Object} item A series or point
@@ -21920,11 +22685,12 @@
bBox,
itemWidth,
li = item.legendItem,
series = item.series || item,
itemOptions = series.options,
- showCheckbox = itemOptions.showCheckbox;
+ showCheckbox = itemOptions.showCheckbox,
+ useHTML = options.useHTML;
if (!li) { // generate it once, later move it
// Generate the group box
// A group to hold the symbol and text. Text is to be appended in Legend class.
@@ -21935,24 +22701,24 @@
// Draw the legend symbol inside the group box
series.drawLegendSymbol(legend, item);
// Generate the list item text and add it to the group
item.legendItem = li = renderer.text(
- options.labelFormatter.call(item),
+ options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item),
ltr ? symbolWidth + symbolPadding : -symbolPadding,
legend.baseline,
- options.useHTML
+ useHTML
)
.css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)
.attr({
align: ltr ? 'left' : 'right',
zIndex: 2
})
.add(item.legendGroup);
- // Set the events on the item group
- item.legendGroup.on('mouseover', function () {
+ // Set the events on the item group, or in case of useHTML, the item itself (#1249)
+ (useHTML ? li : item.legendGroup).on('mouseover', function () {
item.setState(HOVER_STATE);
li.css(legend.options.itemHoverStyle);
})
.on('mouseout', function () {
li.css(item.visible ? itemStyle : itemHiddenStyle);
@@ -22073,30 +22839,27 @@
legend.offsetWidth = 0;
legend.lastItemY = 0;
if (!legendGroup) {
legend.group = legendGroup = renderer.g('legend')
- // #414, #759. Trackers will be drawn above the legend, but we have
- // to sacrifice that because tooltips need to be above the legend
- // and trackers above tooltips
.attr({ zIndex: 7 })
.add();
legend.contentGroup = renderer.g()
.attr({ zIndex: 1 }) // above background
.add(legendGroup);
legend.scrollGroup = renderer.g()
.add(legend.contentGroup);
- legend.clipRect = renderer.clipRect(0, 0, 9999, chart.chartHeight);
- legend.contentGroup.clip(legend.clipRect);
}
+
+ legend.renderTitle();
// add each series or point
allItems = [];
each(chart.series, function (serie) {
var seriesOptions = serie.options;
- if (!seriesOptions.showInLegend) {
+ if (!seriesOptions.showInLegend || defined(seriesOptions.linkedTo)) {
return;
}
// use points or series for the legend item depending on legendType
allItems = allItems.concat(
@@ -22125,11 +22888,11 @@
legend.renderItem(item);
});
// Draw the border
legendWidth = options.width || legend.offsetWidth;
- legendHeight = legend.lastItemY + legend.lastLineHeight;
+ legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight;
legendHeight = legend.handleOverflow(legendHeight);
if (legendBorderWidth || legendBackgroundColor) {
@@ -22187,11 +22950,11 @@
if (display) {
legendGroup.align(extend({
width: legendWidth,
height: legendHeight
- }, options), true, chart.spacingBox);
+ }, options), true, 'spacingBox');
}
if (!chart.isResizing) {
this.positionCheckboxes();
}
@@ -22225,17 +22988,22 @@
if (maxHeight) {
spaceHeight = mathMin(spaceHeight, maxHeight);
}
// Reset the legend height and adjust the clipping rectangle
- if (legendHeight > spaceHeight) {
-
- this.clipHeight = clipHeight = spaceHeight - 20;
+ if (legendHeight > spaceHeight && !options.useHTML) {
+
+ this.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight;
this.pageCount = pageCount = mathCeil(legendHeight / clipHeight);
this.currentPage = pick(this.currentPage, 1);
this.fullHeight = legendHeight;
+ // Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787)
+ if (!clipRect) {
+ clipRect = legend.clipRect = renderer.clipRect(0, 0, 9999, 0);
+ legend.contentGroup.clip(clipRect);
+ }
clipRect.attr({
height: clipHeight
});
// Add navigation elements
@@ -22267,10 +23035,11 @@
});
nav.hide();
this.scrollGroup.attr({
translateY: 1
});
+ this.clipHeight = 0; // #1379
}
return legendHeight;
},
@@ -22285,11 +23054,12 @@
clipHeight = this.clipHeight,
navOptions = this.options.navigation,
activeColor = navOptions.activeColor,
inactiveColor = navOptions.inactiveColor,
pager = this.pager,
- padding = this.padding;
+ padding = this.padding,
+ scrollOffset;
// When resizing while looking at the last page
if (currentPage > pageCount) {
currentPage = pageCount;
}
@@ -22300,11 +23070,11 @@
setAnimation(animation, this.chart);
}
this.nav.attr({
translateX: padding,
- translateY: clipHeight + 7,
+ translateY: clipHeight + 7 + this.titleHeight,
visibility: VISIBLE
});
this.up.attr({
fill: currentPage === 1 ? inactiveColor : activeColor
})
@@ -22320,106 +23090,153 @@
})
.css({
cursor: currentPage === pageCount ? 'default' : 'pointer'
});
+ scrollOffset = -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1;
this.scrollGroup.animate({
- translateY: -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1
+ translateY: scrollOffset
});
pager.attr({
text: currentPage + '/' + pageCount
});
this.currentPage = currentPage;
+ this.positionCheckboxes(scrollOffset);
}
}
};
-
/**
* The chart class
* @param {Object} options
* @param {Function} callback Function to run when the chart has loaded
*/
-function Chart(userOptions, callback) {
- // Handle regular options
- var options,
- seriesOptions = userOptions.series; // skip merging data points to increase performance
- userOptions.series = null;
- options = merge(defaultOptions, userOptions); // do the merge
- options.series = userOptions.series = seriesOptions; // set back the series data
+function Chart() {
+ this.init.apply(this, arguments);
+}
- var optionsChart = options.chart,
- optionsMargin = optionsChart.margin,
- margin = isObject(optionsMargin) ?
- optionsMargin :
- [optionsMargin, optionsMargin, optionsMargin, optionsMargin];
+Chart.prototype = {
- this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]);
- this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]);
- this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]);
- this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]);
+ /**
+ * Initialize the chart
+ */
+ init: function (userOptions, callback) {
- var chartEvents = optionsChart.events;
+ // Handle regular options
+ var options,
+ seriesOptions = userOptions.series; // skip merging data points to increase performance
- this.runChartClick = chartEvents && !!chartEvents.click;
- this.callback = callback;
- this.isResizing = 0;
- this.options = options;
- //chartTitleOptions = UNDEFINED;
- //chartSubtitleOptions = UNDEFINED;
+ userOptions.series = null;
+ options = merge(defaultOptions, userOptions); // do the merge
+ options.series = userOptions.series = seriesOptions; // set back the series data
- this.axes = [];
- this.series = [];
- this.hasCartesianSeries = optionsChart.showAxes;
- //this.axisOffset = UNDEFINED;
- //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
- //this.inverted = UNDEFINED;
- //this.loadingShown = UNDEFINED;
- //this.container = UNDEFINED;
- //this.chartWidth = UNDEFINED;
- //this.chartHeight = UNDEFINED;
- //this.marginRight = UNDEFINED;
- //this.marginBottom = UNDEFINED;
- //this.containerWidth = UNDEFINED;
- //this.containerHeight = UNDEFINED;
- //this.oldChartWidth = UNDEFINED;
- //this.oldChartHeight = UNDEFINED;
+ var optionsChart = options.chart,
+ optionsMargin = optionsChart.margin,
+ margin = isObject(optionsMargin) ?
+ optionsMargin :
+ [optionsMargin, optionsMargin, optionsMargin, optionsMargin];
- //this.renderTo = UNDEFINED;
- //this.renderToClone = UNDEFINED;
- //this.tracker = UNDEFINED;
+ this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]);
+ this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]);
+ this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]);
+ this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]);
- //this.spacingBox = UNDEFINED
+ var chartEvents = optionsChart.events;
- //this.legend = UNDEFINED;
+ this.runChartClick = chartEvents && !!chartEvents.click;
+ this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom
- // Elements
- //this.chartBackground = UNDEFINED;
- //this.plotBackground = UNDEFINED;
- //this.plotBGImage = UNDEFINED;
- //this.plotBorder = UNDEFINED;
- //this.loadingDiv = UNDEFINED;
- //this.loadingSpan = UNDEFINED;
+ this.callback = callback;
+ this.isResizing = 0;
+ this.options = options;
+ //chartTitleOptions = UNDEFINED;
+ //chartSubtitleOptions = UNDEFINED;
- this.init(chartEvents);
-}
+ this.axes = [];
+ this.series = [];
+ this.hasCartesianSeries = optionsChart.showAxes;
+ //this.axisOffset = UNDEFINED;
+ //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
+ //this.inverted = UNDEFINED;
+ //this.loadingShown = UNDEFINED;
+ //this.container = UNDEFINED;
+ //this.chartWidth = UNDEFINED;
+ //this.chartHeight = UNDEFINED;
+ //this.marginRight = UNDEFINED;
+ //this.marginBottom = UNDEFINED;
+ //this.containerWidth = UNDEFINED;
+ //this.containerHeight = UNDEFINED;
+ //this.oldChartWidth = UNDEFINED;
+ //this.oldChartHeight = UNDEFINED;
-Chart.prototype = {
+ //this.renderTo = UNDEFINED;
+ //this.renderToClone = UNDEFINED;
+ //this.spacingBox = UNDEFINED
+
+ //this.legend = UNDEFINED;
+
+ // Elements
+ //this.chartBackground = UNDEFINED;
+ //this.plotBackground = UNDEFINED;
+ //this.plotBGImage = UNDEFINED;
+ //this.plotBorder = UNDEFINED;
+ //this.loadingDiv = UNDEFINED;
+ //this.loadingSpan = UNDEFINED;
+
+ var chart = this,
+ eventType;
+
+ // Add the chart to the global lookup
+ chart.index = charts.length;
+ charts.push(chart);
+
+ // Set up auto resize
+ if (optionsChart.reflow !== false) {
+ addEvent(chart, 'load', function () {
+ chart.initReflow();
+ });
+ }
+
+ // Chart event handlers
+ if (chartEvents) {
+ for (eventType in chartEvents) {
+ addEvent(chart, eventType, chartEvents[eventType]);
+ }
+ }
+
+ chart.xAxis = [];
+ chart.yAxis = [];
+
+ // Expose methods and variables
+ chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
+ chart.pointCount = 0;
+ chart.counters = new ChartCounters();
+
+ chart.firstRender();
+ },
+
/**
* Initialize an individual series, called internally before render time
*/
initSeries: function (options) {
var chart = this,
optionsChart = chart.options.chart,
type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
- series = new seriesTypes[type]();
+ series,
+ constr = seriesTypes[type];
+ // No such series type
+ if (!constr) {
+ error(17, true);
+ }
+
+ series = new constr();
series.init(this, options);
return series;
},
/**
@@ -22435,27 +23252,51 @@
addSeries: function (options, redraw, animation) {
var series,
chart = this;
if (options) {
- setAnimation(animation, chart);
redraw = pick(redraw, true); // defaults to true
fireEvent(chart, 'addSeries', { options: options }, function () {
series = chart.initSeries(options);
chart.isDirtyLegend = true; // the series array is out of sync with the display
if (redraw) {
- chart.redraw();
+ chart.redraw(animation);
}
});
}
return series;
},
/**
+ * Add an axis to the chart
+ * @param {Object} options The axis option
+ * @param {Boolean} isX Whether it is an X axis or a value axis
+ */
+ addAxis: function (options, isX, redraw, animation) {
+ var key = isX ? 'xAxis' : 'yAxis',
+ chartOptions = this.options,
+ axis;
+
+ /*jslint unused: false*/
+ axis = new Axis(this, merge(options, {
+ index: this[key].length
+ }));
+ /*jslint unused: true*/
+
+ // Push the new axis options to the chart options
+ chartOptions[key] = splat(chartOptions[key] || {});
+ chartOptions[key].push(options);
+
+ if (pick(redraw, true)) {
+ this.redraw(animation);
+ }
+ },
+
+ /**
* Check whether a given point is within the plot area
*
* @param {Number} plotX Pixel x relative to the plot area
* @param {Number} plotY Pixel y relative to the plot area
* @param {Boolean} inverted Whether the chart is inverted
@@ -22490,11 +23331,11 @@
*/
redraw: function (animation) {
var chart = this,
axes = chart.axes,
series = chart.series,
- tracker = chart.tracker,
+ pointer = chart.pointer,
legend = chart.legend,
redrawLegend = chart.isDirtyLegend,
hasStackedSeries,
isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
seriesLength = series.length,
@@ -22577,29 +23418,28 @@
}
});
}
-
// the plot areas size has changed
if (isDirtyBox) {
chart.drawChartBox();
}
+
// redraw affected series
each(series, function (serie) {
if (serie.isDirty && serie.visible &&
(!serie.isCartesian || serie.xAxis)) { // issue #153
serie.redraw();
}
});
-
// move tooltip or reset
- if (tracker && tracker.resetTracker) {
- tracker.resetTracker(true);
+ if (pointer && pointer.reset) {
+ pointer.reset(true);
}
// redraw if canvas
renderer.draw();
@@ -22632,14 +23472,10 @@
// create the layer at the first call
if (!loadingDiv) {
chart.loadingDiv = loadingDiv = createElement(DIV, {
className: PREFIX + 'loading'
}, extend(loadingOptions.style, {
- left: chart.plotLeft + PX,
- top: chart.plotTop + PX,
- width: chart.plotWidth + PX,
- height: chart.plotHeight + PX,
zIndex: 10,
display: NONE
}), chart.container);
chart.loadingSpan = createElement(
@@ -22654,11 +23490,18 @@
// update text
chart.loadingSpan.innerHTML = str || options.lang.loading;
// show it
if (!chart.loadingShown) {
- css(loadingDiv, { opacity: 0, display: '' });
+ css(loadingDiv, {
+ opacity: 0,
+ display: '',
+ left: chart.plotLeft + PX,
+ top: chart.plotTop + PX,
+ width: chart.plotWidth + PX,
+ height: chart.plotHeight + PX
+ });
animate(loadingDiv, {
opacity: loadingOptions.style.opacity
}, {
duration: loadingOptions.showDuration || 0
});
@@ -22728,25 +23571,22 @@
/**
* Create the Axis instances based on the config options
*/
getAxes: function () {
var chart = this,
- options = this.options;
-
- var xAxisOptions = options.xAxis || {},
- yAxisOptions = options.yAxis || {},
+ options = this.options,
+ xAxisOptions = options.xAxis = splat(options.xAxis || {}),
+ yAxisOptions = options.yAxis = splat(options.yAxis || {}),
optionsArray,
axis;
// make sure the options are arrays and add some members
- xAxisOptions = splat(xAxisOptions);
each(xAxisOptions, function (axis, i) {
axis.index = i;
axis.isX = true;
});
- yAxisOptions = splat(yAxisOptions);
each(yAxisOptions, function (axis, i) {
axis.index = i;
});
// concatenate all axis options into one array
@@ -22764,11 +23604,11 @@
* Get the currently selected points from all series
*/
getSelectedPoints: function () {
var points = [];
each(this.series, function (serie) {
- points = points.concat(grep(serie.points, function (point) {
+ points = points.concat(grep(serie.points || [], function (point) {
return point.selected;
}));
});
return points;
},
@@ -22797,75 +23637,80 @@
.attr({
align: btnOptions.position.align,
title: lang.resetZoomTitle
})
.add()
- .align(btnOptions.position, false, chart[alignTo]);
- this.resetZoomButton.alignTo = alignTo;
+ .align(btnOptions.position, false, alignTo);
},
/**
* Zoom out to 1:1
*/
zoomOut: function () {
- var chart = this,
- resetZoomButton = chart.resetZoomButton;
-
- fireEvent(chart, 'selection', { resetSelection: true }, function () { chart.zoom(); });
- if (resetZoomButton) {
- chart.resetZoomButton = resetZoomButton.destroy();
- }
+ var chart = this;
+ fireEvent(chart, 'selection', { resetSelection: true }, function () {
+ chart.zoom();
+ });
},
/**
* Zoom into a given portion of the chart given by axis coordinates
* @param {Object} event
*/
zoom: function (event) {
var chart = this,
- hasZoomed;
+ hasZoomed,
+ pointer = chart.pointer,
+ displayButton = false,
+ resetZoomButton;
- // if zoom is called with no arguments, reset the axes
+ // If zoom is called with no arguments, reset the axes
if (!event || event.resetSelection) {
each(chart.axes, function (axis) {
hasZoomed = axis.zoom();
});
} else { // else, zoom in on all axes
each(event.xAxis.concat(event.yAxis), function (axisData) {
- var axis = axisData.axis;
+ var axis = axisData.axis,
+ isXAxis = axis.isXAxis;
// don't zoom more than minRange
- if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
+ if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) {
hasZoomed = axis.zoom(axisData.min, axisData.max);
+ if (axis.displayBtn) {
+ displayButton = true;
+ }
}
});
}
- // Show the Reset zoom button
- if (!chart.resetZoomButton) {
+ // Show or hide the Reset zoom button
+ resetZoomButton = chart.resetZoomButton;
+ if (displayButton && !resetZoomButton) {
chart.showResetZoom();
+ } else if (!displayButton && isObject(resetZoomButton)) {
+ chart.resetZoomButton = resetZoomButton.destroy();
}
// Redraw
if (hasZoomed) {
chart.redraw(
- pick(chart.options.chart.animation, chart.pointCount < 100) // animation
+ pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
);
}
},
/**
* Pan the chart by dragging the mouse across the pane. This function is called
* on mouse move, and the distance to pan is computed from chartX compared to
* the first chartX position in the dragging operation.
*/
pan: function (chartX) {
- var chart = this;
-
- var xAxis = chart.xAxis[0],
+ var chart = this,
+ xAxis = chart.xAxis[0],
mouseDownX = chart.mouseDownX,
halfPointRange = xAxis.pointRange / 2,
extremes = xAxis.getExtremes(),
newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange,
newMax = xAxis.translate(mouseDownX + chart.plotWidth - chartX, true) - halfPointRange,
@@ -22897,12 +23742,12 @@
var chart = this,
options = chart.options,
chartTitleOptions,
chartSubtitleOptions;
- chart.chartTitleOptions = chartTitleOptions = merge(options.title, titleOptions);
- chart.chartSubtitleOptions = chartSubtitleOptions = merge(options.subtitle, subtitleOptions);
+ chartTitleOptions = options.title = merge(options.title, titleOptions);
+ chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions);
// add title and subtitle
each([
['title', titleOptions, chartTitleOptions],
['subtitle', subtitleOptions, chartSubtitleOptions]
@@ -22928,11 +23773,11 @@
'class': PREFIX + name,
zIndex: chartTitleOptions.zIndex || 4
})
.css(chartTitleOptions.style)
.add()
- .align(chartTitleOptions, false, chart.spacingBox);
+ .align(chartTitleOptions, false, 'spacingBox');
}
});
},
@@ -22946,14 +23791,14 @@
// get inner width and height from jQuery (#824)
chart.containerWidth = adapterRun(renderTo, 'width');
chart.containerHeight = adapterRun(renderTo, 'height');
- chart.chartWidth = optionsChart.width || chart.containerWidth || 600;
- chart.chartHeight = optionsChart.height ||
+ chart.chartWidth = mathMax(0, optionsChart.width || chart.containerWidth || 600); // #1393, 1460
+ chart.chartHeight = mathMax(0, pick(optionsChart.height,
// the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
- (chart.containerHeight > 19 ? chart.containerHeight : 400);
+ chart.containerHeight > 19 ? chart.containerHeight : 400));
},
/**
* Create a clone of the chart's renderTo div and place it outside the viewport to allow
* size computation on chart.render and chart.redraw
@@ -22997,10 +23842,12 @@
container,
optionsChart = chart.options.chart,
chartWidth,
chartHeight,
renderTo,
+ indexAttrName = 'data-highcharts-chart',
+ oldChartIndex,
containerId;
chart.renderTo = renderTo = optionsChart.renderTo;
containerId = PREFIX + idCounter++;
@@ -23010,10 +23857,19 @@
// Display an error if the renderTo is wrong
if (!renderTo) {
error(13, true);
}
+
+ // If the container already holds a chart, destroy it
+ oldChartIndex = pInt(attr(renderTo, indexAttrName));
+ if (!isNaN(oldChartIndex) && charts[oldChartIndex]) {
+ charts[oldChartIndex].destroy();
+ }
+
+ // Make a reference to the chart from the div
+ attr(renderTo, indexAttrName, chart.index);
// remove previous chart
renderTo.innerHTML = '';
// If the container doesn't have an offsetWidth, it has or is a child of a node
@@ -23040,15 +23896,19 @@
// content overflow in IE
width: chartWidth + PX,
height: chartHeight + PX,
textAlign: 'left',
lineHeight: 'normal', // #427
- zIndex: 0 // #1072
+ zIndex: 0, // #1072
+ '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
}, optionsChart.style),
chart.renderToClone || renderTo
);
+ // cache the cursor (#1650)
+ chart._cursor = container.style.cursor;
+
chart.renderer =
optionsChart.forExport ? // force SVG, used for SVG export
new SVGRenderer(container, chartWidth, chartHeight, true) :
new Renderer(container, chartWidth, chartHeight);
@@ -23075,12 +23935,12 @@
legend = chart.legend,
optionsMarginTop = chart.optionsMarginTop,
optionsMarginLeft = chart.optionsMarginLeft,
optionsMarginRight = chart.optionsMarginRight,
optionsMarginBottom = chart.optionsMarginBottom,
- chartTitleOptions = chart.chartTitleOptions,
- chartSubtitleOptions = chart.chartSubtitleOptions,
+ chartTitleOptions = chart.options.title,
+ chartSubtitleOptions = chart.options.subtitle,
legendOptions = chart.options.legend,
legendMargin = pick(legendOptions.margin, 10),
legendX = legendOptions.x,
legendY = legendOptions.y,
align = legendOptions.align,
@@ -23182,17 +24042,18 @@
height = optionsChart.height || adapterRun(renderTo, 'height'),
target = e ? e.target : win; // #805 - MooTools doesn't supply e
// Width and height checks for display:none. Target is doc in IE8 and Opera,
// win in Firefox, Chrome and IE9.
- if (width && height && (target === win || target === doc)) {
+ if (!chart.hasUserSize && width && height && (target === win || target === doc)) {
if (width !== chart.containerWidth || height !== chart.containerHeight) {
clearTimeout(reflowTimeout);
chart.reflowTimeout = reflowTimeout = setTimeout(function () {
if (chart.container) { // It may have been destroyed in the meantime (#1257)
- chart.resize(width, height, false);
+ chart.setSize(width, height, false);
+ chart.hasUserSize = null;
}
}, 100);
}
chart.containerWidth = width;
chart.containerHeight = height;
@@ -23208,19 +24069,14 @@
* Resize the chart to a given width and height
* @param {Number} width
* @param {Number} height
* @param {Object|Boolean} animation
*/
- // TODO: This method is called setSize in the api
- resize: function (width, height, animation) {
+ setSize: function (width, height, animation) {
var chart = this,
chartWidth,
chartHeight,
- spacingBox,
- resetZoomButton = chart.resetZoomButton,
- chartTitle = chart.title,
- chartSubtitle = chart.subtitle,
fireEndResize;
// Handle the isResizing counter
chart.isResizing += 1;
fireEndResize = function () {
@@ -23235,26 +24091,24 @@
setAnimation(animation, chart);
chart.oldChartHeight = chart.chartHeight;
chart.oldChartWidth = chart.chartWidth;
if (defined(width)) {
- chart.chartWidth = chartWidth = mathRound(width);
+ chart.chartWidth = chartWidth = mathMax(0, mathRound(width));
+ chart.hasUserSize = !!chartWidth;
}
if (defined(height)) {
- chart.chartHeight = chartHeight = mathRound(height);
+ chart.chartHeight = chartHeight = mathMax(0, mathRound(height));
}
css(chart.container, {
width: chartWidth + PX,
height: chartHeight + PX
});
+ chart.setChartSize(true);
chart.renderer.setSize(chartWidth, chartHeight, animation);
- // update axis lengths for more correct tick intervals:
- chart.plotWidth = chartWidth - chart.plotLeft - chart.marginRight;
- chart.plotHeight = chartHeight - chart.plotTop - chart.marginBottom;
-
// handle axes
chart.maxTicks = null;
each(chart.axes, function (axis) {
axis.isDirty = true;
axis.setScale();
@@ -23268,24 +24122,10 @@
chart.isDirtyLegend = true; // force legend redraw
chart.isDirtyBox = true; // force redraw of plot and chart border
chart.getMargins();
- // move titles
- spacingBox = chart.spacingBox;
- if (chartTitle) {
- chartTitle.align(null, null, spacingBox);
- }
- if (chartSubtitle) {
- chartSubtitle.align(null, null, spacingBox);
- }
-
- // Move resize button (#1115)
- if (resetZoomButton && resetZoomButton.align) {
- resetZoomButton.align(null, null, chart[resetZoomButton.alignTo]);
- }
-
chart.redraw(animation);
chart.oldChartHeight = null;
fireEvent(chart, 'resize');
@@ -23301,60 +24141,68 @@
/**
* Set the public chart properties. This is done before and after the pre-render
* to determine margin sizes
*/
- setChartSize: function () {
+ setChartSize: function (skipAxes) {
var chart = this,
inverted = chart.inverted,
+ renderer = chart.renderer,
chartWidth = chart.chartWidth,
chartHeight = chart.chartHeight,
optionsChart = chart.options.chart,
spacingTop = optionsChart.spacingTop,
spacingRight = optionsChart.spacingRight,
spacingBottom = optionsChart.spacingBottom,
spacingLeft = optionsChart.spacingLeft,
+ clipOffset = chart.clipOffset,
+ clipX,
+ clipY,
plotLeft,
plotTop,
plotWidth,
plotHeight,
plotBorderWidth;
chart.plotLeft = plotLeft = mathRound(chart.plotLeft);
chart.plotTop = plotTop = mathRound(chart.plotTop);
- chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - chart.marginRight);
- chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - chart.marginBottom);
+ chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight));
+ chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom));
chart.plotSizeX = inverted ? plotHeight : plotWidth;
chart.plotSizeY = inverted ? plotWidth : plotHeight;
chart.plotBorderWidth = plotBorderWidth = optionsChart.plotBorderWidth || 0;
// Set boxes used for alignment
- chart.spacingBox = {
+ chart.spacingBox = renderer.spacingBox = {
x: spacingLeft,
y: spacingTop,
width: chartWidth - spacingLeft - spacingRight,
height: chartHeight - spacingTop - spacingBottom
};
- chart.plotBox = {
+ chart.plotBox = renderer.plotBox = {
x: plotLeft,
y: plotTop,
width: plotWidth,
height: plotHeight
};
+ clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2);
+ clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2);
chart.clipBox = {
- x: plotBorderWidth / 2,
- y: plotBorderWidth / 2,
- width: chart.plotSizeX - plotBorderWidth,
- height: chart.plotSizeY - plotBorderWidth
+ x: clipX,
+ y: clipY,
+ width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX),
+ height: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)
};
- each(chart.axes, function (axis) {
- axis.setAxisSize();
- axis.setAxisTranslation();
- });
+ if (!skipAxes) {
+ each(chart.axes, function (axis) {
+ axis.setAxisSize();
+ axis.setAxisTranslation();
+ });
+ }
},
/**
* Initial margins before auto size margins are applied
*/
@@ -23369,10 +24217,11 @@
chart.plotTop = pick(chart.optionsMarginTop, spacingTop);
chart.marginRight = pick(chart.optionsMarginRight, spacingRight);
chart.marginBottom = pick(chart.optionsMarginBottom, spacingBottom);
chart.plotLeft = pick(chart.optionsMarginLeft, spacingLeft);
chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
+ chart.clipOffset = [0, 0, 0, 0];
},
/**
* Draw the borders and backgrounds for chart and plot area
*/
@@ -23538,11 +24387,11 @@
// Title
chart.setTitle();
// Legend
- chart.legend = new Legend(chart);
+ chart.legend = new Legend(chart, options.legend);
// Get margins by pre-rendering axes
// set axes scales
each(axes, function (axis) {
axis.setScale();
@@ -23639,13 +24488,17 @@
axes = chart.axes,
series = chart.series,
container = chart.container,
i,
parentNode = container && container.parentNode;
-
+
// fire the chart.destoy event
fireEvent(chart, 'destroy');
+
+ // Delete the chart from charts lookup array
+ charts[chart.index] = UNDEFINED;
+ chart.renderTo.removeAttribute('data-highcharts-chart');
// remove events
removeEvent(chart);
// ==== Destroy collections:
@@ -23660,11 +24513,13 @@
while (i--) {
series[i] = series[i].destroy();
}
// ==== Destroy chart properties:
- each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
+ each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage',
+ 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller',
+ 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
var prop = chart[name];
if (prop && prop.destroy) {
chart[name] = prop.destroy();
}
@@ -23685,51 +24540,58 @@
delete chart[i];
}
},
+
/**
- * Prepare for first rendering after all data are loaded
+ * VML namespaces can't be added until after complete. Listening
+ * for Perini's doScroll hack is not enough.
*/
- firstRender: function () {
- var chart = this,
- options = chart.options,
- callback = chart.callback;
+ isReadyToRender: function () {
+ var chart = this;
- // VML namespaces can't be added until after complete. Listening
- // for Perini's doScroll hack is not enough.
- var ONREADYSTATECHANGE = 'onreadystatechange',
- COMPLETE = 'complete';
// Note: in spite of JSLint's complaints, win == win.top is required
/*jslint eqeq: true*/
- if ((!hasSVG && (win == win.top && doc.readyState !== COMPLETE)) || (useCanVG && !win.canvg)) {
+ if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) {
/*jslint eqeq: false*/
if (useCanVG) {
// Delay rendering until canvg library is downloaded and ready
- CanVGController.push(function () { chart.firstRender(); }, options.global.canvasToolsURL);
+ CanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL);
} else {
- doc.attachEvent(ONREADYSTATECHANGE, function () {
- doc.detachEvent(ONREADYSTATECHANGE, chart.firstRender);
- if (doc.readyState === COMPLETE) {
+ doc.attachEvent('onreadystatechange', function () {
+ doc.detachEvent('onreadystatechange', chart.firstRender);
+ if (doc.readyState === 'complete') {
chart.firstRender();
}
});
}
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Prepare for first rendering after all data are loaded
+ */
+ firstRender: function () {
+ var chart = this,
+ options = chart.options,
+ callback = chart.callback;
+
+ // Check whether the chart is ready to render
+ if (!chart.isReadyToRender()) {
return;
}
- // create the container
+ // Create the container
chart.getContainer();
// Run an early event after the container and renderer are established
fireEvent(chart, 'init');
- // Initialize range selector for stock charts
- if (Highcharts.RangeSelector && options.rangeSelector.enabled) {
- chart.rangeSelector = new Highcharts.RangeSelector(chart);
- }
-
+
chart.resetMargins();
chart.setChartSize();
// Set the common chart properties (mainly invert) from the given series
chart.propFromSeries();
@@ -23740,20 +24602,17 @@
// Initialize the series
each(options.series || [], function (serieOptions) {
chart.initSeries(serieOptions);
});
- // Run an event where series and axes can be added
- //fireEvent(chart, 'beforeRender');
+ // Run an event after axes and series are initialized, but before render. At this stage,
+ // the series data is indexed and cached in the xData and yData arrays, so we can access
+ // those before rendering. Used in Highstock.
+ fireEvent(chart, 'beforeRender');
- // Initialize scroller for stock charts
- if (Highcharts.Scroller && (options.navigator.enabled || options.scrollbar.enabled)) {
- chart.scroller = new Highcharts.Scroller(chart);
- }
-
// depends on inverted and on margins being set
- chart.tracker = new MouseTracker(chart, options);
+ chart.pointer = new Pointer(chart, options);
chart.render();
// add canvas
chart.renderer.draw();
@@ -23769,77 +24628,10 @@
// If the chart was rendered outside the top container, put it back in
chart.cloneRenderTo(true);
fireEvent(chart, 'load');
- },
-
- init: function (chartEvents) {
- var chart = this,
- optionsChart = chart.options.chart,
- eventType;
-
- // Run chart
-
- // Set up auto resize
- if (optionsChart.reflow !== false) {
- addEvent(chart, 'load', chart.initReflow);
- }
-
- // Chart event handlers
- if (chartEvents) {
- for (eventType in chartEvents) {
- addEvent(chart, eventType, chartEvents[eventType]);
- }
- }
-
- chart.xAxis = [];
- chart.yAxis = [];
-
- // Expose methods and variables
- chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
- chart.setSize = chart.resize;
- chart.pointCount = 0;
- chart.counters = new ChartCounters();
- /*
- if ($) $(function () {
- $container = $('#container');
- var origChartWidth,
- origChartHeight;
- if ($container) {
- $('<button>+</button>')
- .insertBefore($container)
- .click(function () {
- if (origChartWidth === UNDEFINED) {
- origChartWidth = chartWidth;
- origChartHeight = chartHeight;
- }
- chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
- });
- $('<button>-</button>')
- .insertBefore($container)
- .click(function () {
- if (origChartWidth === UNDEFINED) {
- origChartWidth = chartWidth;
- origChartHeight = chartHeight;
- }
- chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
- });
- $('<button>1:1</button>')
- .insertBefore($container)
- .click(function () {
- if (origChartWidth === UNDEFINED) {
- origChartWidth = chartWidth;
- origChartHeight = chartHeight;
- }
- chart.resize(origChartWidth, origChartHeight);
- });
- }
- })
- */
-
- chart.firstRender();
}
}; // end Chart
// Hook for exporting module
Chart.prototype.callbacks = [];
@@ -23853,23 +24645,24 @@
* Initialize the point
* @param {Object} series The series object containing this point
* @param {Object} options The data in either number, array or object format
*/
init: function (series, options, x) {
+
var point = this,
- counters = series.chart.counters,
- defaultColors;
+ colors;
point.series = series;
point.applyOptions(options, x);
point.pointAttr = {};
if (series.options.colorByPoint) {
- defaultColors = series.chart.options.colors;
- point.color = point.color || defaultColors[counters.color++];
-
+ colors = series.options.colors || series.chart.options.colors;
+ point.color = point.color || colors[series.colorCounter++];
// loop back to zero
- counters.wrapColor(defaultColors.length);
+ if (series.colorCounter === colors.length) {
+ series.colorCounter = 0;
+ }
}
series.chart.pointCount++;
return point;
},
@@ -23880,49 +24673,77 @@
* @param {Object} options
*/
applyOptions: function (options, x) {
var point = this,
series = point.series,
- optionsType = typeof options;
+ pointValKey = series.pointValKey;
- point.config = options;
+ options = Point.prototype.optionsToObject.call(this, options);
- // onedimensional array input
- if (optionsType === 'number' || options === null) {
- point.y = options;
- } else if (typeof options[0] === 'number') { // two-dimentional array
- point.x = options[0];
- point.y = options[1];
- } else if (optionsType === 'object' && typeof options.length !== 'number') { // object input
- // copy options directly to point
- extend(point, options);
- point.options = options;
+ // copy options directly to point
+ extend(point, options);
+ point.options = point.options ? extend(point.options, options) : options;
+ // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
+ if (pointValKey) {
+ point.y = point[pointValKey];
+ }
+
+ // If no x is set by now, get auto incremented value. All points must have an
+ // x value, however the y value can be null to create a gap in the series
+ if (point.x === UNDEFINED && series) {
+ point.x = x === UNDEFINED ? series.autoIncrement() : x;
+ }
+
+ return point;
+ },
+
+ /**
+ * Transform number or array configs into objects
+ */
+ optionsToObject: function (options) {
+ var ret,
+ series = this.series,
+ pointArrayMap = series.pointArrayMap || ['y'],
+ valueCount = pointArrayMap.length,
+ firstItemType,
+ i = 0,
+ j = 0;
+
+ if (typeof options === 'number' || options === null) {
+ ret = { y: options };
+
+ } else if (isArray(options)) {
+ ret = {};
+ // with leading x value
+ if (options.length > valueCount) {
+ firstItemType = typeof options[0];
+ if (firstItemType === 'string') {
+ ret.name = options[0];
+ } else if (firstItemType === 'number') {
+ ret.x = options[0];
+ }
+ i++;
+ }
+ while (j < valueCount) {
+ ret[pointArrayMap[j++]] = options[i++];
+ }
+ } else if (typeof options === 'object') {
+ ret = options;
+
// This is the fastest way to detect if there are individual point dataLabels that need
// to be considered in drawDataLabels. These can only occur in object configs.
if (options.dataLabels) {
series._hasPointLabels = true;
}
-
+
// Same approach as above for markers
if (options.marker) {
series._hasPointMarkers = true;
}
- } else if (typeof options[0] === 'string') { // categorized data with name in first position
- point.name = options[0];
- point.y = options[1];
}
-
- /*
- * If no x is set by now, get auto incremented value. All points must have an
- * x value, however the y value can be null to create a gap in the series
- */
- // todo: skip this? It is only used in applyOptions, in translate it should not be used
- if (point.x === UNDEFINED) {
- point.x = x === UNDEFINED ? series.autoIncrement() : x;
- }
-
+ return ret;
},
/**
* Destroy a point to clear memory. Its reference still stays in series.data.
*/
@@ -23967,11 +24788,11 @@
/**
* Destroy SVG elements associated with the point
*/
destroyElements: function () {
var point = this,
- props = ['graphic', 'tracker', 'dataLabel', 'group', 'connector', 'shadowGroup'],
+ props = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'],
prop,
i = 6;
while (i--) {
prop = props[i];
if (point[prop]) {
@@ -24009,27 +24830,33 @@
selected = pick(selected, !point.selected);
// fire the event with the defalut handler
point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
- point.selected = selected;
+ point.selected = point.options.selected = selected;
+ series.options.data[inArray(point, series.data)] = point.options;
+
point.setState(selected && SELECT_STATE);
// unselect all other points unless Ctrl or Cmd + click
if (!accumulate) {
each(chart.getSelectedPoints(), function (loopPoint) {
if (loopPoint.selected && loopPoint !== point) {
- loopPoint.selected = false;
+ loopPoint.selected = loopPoint.options.selected = false;
+ series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
loopPoint.setState(NORMAL_STATE);
loopPoint.firePointEvent('unselect');
}
});
}
});
},
- onMouseOver: function () {
+ /**
+ * Runs on mouse over the point
+ */
+ onMouseOver: function (e) {
var point = this,
series = point.series,
chart = series.chart,
tooltip = chart.tooltip,
hoverPoint = chart.hoverPoint;
@@ -24042,18 +24869,21 @@
// trigger the event
point.firePointEvent('mouseOver');
// update the tooltip
if (tooltip && (!tooltip.shared || series.noSharedTooltip)) {
- tooltip.refresh(point);
+ tooltip.refresh(point, e);
}
// hover this
point.setState(HOVER_STATE);
chart.hoverPoint = point;
},
-
+
+ /**
+ * Runs on mouse out from the point
+ */
onMouseOut: function () {
var chart = this.series.chart,
hoverPoints = chart.hoverPoints;
if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887
@@ -24068,64 +24898,31 @@
* Extendable method for formatting each point's tooltip line
*
* @return {String} A string to be concatenated in to the common tooltip text
*/
tooltipFormatter: function (pointFormat) {
- var point = this,
- series = point.series,
- seriesTooltipOptions = series.tooltipOptions,
- match = pointFormat.match(/\{(series|point)\.[a-zA-Z]+\}/g),
- splitter = /[{\.}]/,
- obj,
- key,
- replacement,
- repOptionKey,
- parts,
- prop,
- i,
- cfg = {
- y: 0, // 0: use 'value' for repOptionKey
- open: 0,
- high: 0,
- low: 0,
- close: 0,
- percentage: 1, // 1: use the self name for repOptionKey
- total: 1
- };
- // Backwards compatibility to y naming in early Highstock
- seriesTooltipOptions.valuePrefix = seriesTooltipOptions.valuePrefix || seriesTooltipOptions.yPrefix;
- seriesTooltipOptions.valueDecimals = seriesTooltipOptions.valueDecimals || seriesTooltipOptions.yDecimals;
- seriesTooltipOptions.valueSuffix = seriesTooltipOptions.valueSuffix || seriesTooltipOptions.ySuffix;
-
- // loop over the variables defined on the form {series.name}, {point.y} etc
- for (i in match) {
- key = match[i];
- if (isString(key) && key !== pointFormat) { // IE matches more than just the variables
-
- // Split it further into parts
- parts = (' ' + key).split(splitter); // add empty string because IE and the rest handles it differently
- obj = { 'point': point, 'series': series }[parts[1]];
- prop = parts[2];
-
- // Add some preformatting
- if (obj === point && cfg.hasOwnProperty(prop)) {
- repOptionKey = cfg[prop] ? prop : 'value';
- replacement = (seriesTooltipOptions[repOptionKey + 'Prefix'] || '') +
- numberFormat(point[prop], pick(seriesTooltipOptions[repOptionKey + 'Decimals'], -1)) +
- (seriesTooltipOptions[repOptionKey + 'Suffix'] || '');
-
- // Automatic replacement
- } else {
- replacement = obj[prop];
- }
-
- pointFormat = pointFormat.replace(key, replacement);
+ // Insert options for valueDecimals, valuePrefix, and valueSuffix
+ var series = this.series,
+ seriesTooltipOptions = series.tooltipOptions,
+ valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
+ valuePrefix = seriesTooltipOptions.valuePrefix || '',
+ valueSuffix = seriesTooltipOptions.valueSuffix || '';
+
+ // Loop over the point array map and replace unformatted values with sprintf formatting markup
+ each(series.pointArrayMap || ['y'], function (key) {
+ key = '{point.' + key; // without the closing bracket
+ if (valuePrefix || valueSuffix) {
+ pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
}
- }
+ pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
+ });
- return pointFormat;
+ return format(pointFormat, {
+ point: this,
+ series: this.series
+ });
},
/**
* Update the point with new options (typically x/y data) and optionally redraw the series.
*
@@ -24139,11 +24936,10 @@
var point = this,
series = point.series,
graphic = point.graphic,
i,
data = series.data,
- dataLength = data.length,
chart = series.chart;
redraw = pick(redraw, true);
// fire the event with a default handler of doing the update
@@ -24158,18 +24954,15 @@
graphic.attr(point.pointAttr[series.state]);
}
}
// record changes in the parallel arrays
- for (i = 0; i < dataLength; i++) {
- if (data[i] === point) {
- series.xData[i] = point.x;
- series.yData[i] = point.y;
- series.options.data[i] = options;
- break;
- }
- }
+ i = inArray(point, data);
+ series.xData[i] = point.x;
+ series.yData[i] = series.toYData ? series.toYData(point) : point.y;
+ series.zData[i] = point.z;
+ series.options.data[i] = point.options;
// redraw
series.isDirty = true;
series.isDirtyData = true;
if (redraw) {
@@ -24187,33 +24980,26 @@
remove: function (redraw, animation) {
var point = this,
series = point.series,
chart = series.chart,
i,
- data = series.data,
- dataLength = data.length;
+ data = series.data;
setAnimation(animation, chart);
redraw = pick(redraw, true);
// fire the event with a default handler of removing the point
point.firePointEvent('remove', null, function () {
- //erase(series.data, point);
+ // splice all the parallel arrays
+ i = inArray(point, data);
+ data.splice(i, 1);
+ series.options.data.splice(i, 1);
+ series.xData.splice(i, 1);
+ series.yData.splice(i, 1);
+ series.zData.splice(i, 1);
- for (i = 0; i < dataLength; i++) {
- if (data[i] === point) {
-
- // splice all the parallel arrays
- data.splice(i, 1);
- series.options.data.splice(i, 1);
- series.xData.splice(i, 1);
- series.yData.splice(i, 1);
- break;
- }
- }
-
point.destroy();
// redraw
series.isDirty = true;
@@ -24287,12 +25073,14 @@
markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
normalDisabled = markerOptions && !markerOptions.enabled,
markerStateOptions = markerOptions && markerOptions.states[state],
stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
stateMarkerGraphic = series.stateMarkerGraphic,
+ pointMarker = point.marker || {},
chart = series.chart,
radius,
+ newSymbol,
pointAttr = point.pointAttr;
state = state || NORMAL_STATE; // empty string
if (
@@ -24324,22 +25112,33 @@
} else {
// if a graphic is not applied to each point in the normal state, create a shared
// graphic for the hover state
if (state && markerStateOptions) {
radius = markerStateOptions.radius;
- if (!stateMarkerGraphic) { // add
+ newSymbol = pointMarker.symbol || series.symbol;
+
+ // If the point has another symbol than the previous one, throw away the
+ // state marker graphic and force a new one (#1459)
+ if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {
+ stateMarkerGraphic = stateMarkerGraphic.destroy();
+ }
+
+ // Add a new state marker graphic
+ if (!stateMarkerGraphic) {
series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
- series.symbol,
+ newSymbol,
plotX - radius,
plotY - radius,
2 * radius,
2 * radius
)
.attr(pointAttr[state])
.add(series.markerGroup);
+ stateMarkerGraphic.currentSymbol = newSymbol;
- } else { // update
+ // Move the existing graphic
+ } else {
stateMarkerGraphic.attr({ // #1054
x: plotX - radius,
y: plotY - radius
});
}
@@ -24379,24 +25178,28 @@
isCartesian: true,
type: 'line',
pointClass: Point,
sorted: true, // requires the data to be sorted
+ requireSorting: true,
pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
stroke: 'lineColor',
'stroke-width': 'lineWidth',
fill: 'fillColor',
r: 'radius'
},
+ colorCounter: 0,
init: function (chart, options) {
var series = this,
eventType,
- events;
+ events,
+ linkedTo,
+ chartSeries = chart.series;
series.chart = chart;
series.options = options = series.setOptions(options); // merge with plotOptions
-
+
// bind the axes
series.bindAxes();
// set some variables
extend(series, {
@@ -24435,20 +25238,36 @@
if (series.isCartesian) {
chart.hasCartesianSeries = true;
}
// Register it in the chart
- chart.series.push(series);
+ chartSeries.push(series);
+ series._i = chartSeries.length - 1;
// Sort series according to index option (#248, #1123)
- stableSort(chart.series, function (a, b) {
- return (a.options.index || 0) - (b.options.index || 0);
+ stableSort(chartSeries, function (a, b) {
+ return pick(a.options.index, a._i) - pick(b.options.index, a._i);
});
- each(chart.series, function (series, i) {
+ each(chartSeries, function (series, i) {
series.index = i;
series.name = series.name || 'Series ' + (i + 1);
});
+
+ // Linked series
+ linkedTo = options.linkedTo;
+ series.linkedSeries = [];
+ if (isString(linkedTo)) {
+ if (linkedTo === ':previous') {
+ linkedTo = chartSeries[series.index - 1];
+ } else {
+ linkedTo = chart.get(linkedTo);
+ }
+ if (linkedTo) {
+ linkedTo.linkedSeries.push(series);
+ series.linkedParent = linkedTo;
+ }
+ }
},
/**
* Set the xAxis and yAxis properties of cartesian series, and register the series
* in the axis.series array
@@ -24468,10 +25287,11 @@
axisOptions = axis.options;
// apply if the series xAxis or yAxis option mathches the number of the
// axis, or if undefined, use the first axis
if ((seriesOptions[AXIS] === axisOptions.index) ||
+ (seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) ||
(seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {
// register this series in the axis.series lookup
axis.series.push(series);
@@ -24480,11 +25300,16 @@
// mark dirty for redraw
axis.isDirty = true;
}
});
-
+
+ // The series needs an X and an Y axis
+ if (!series[AXIS]) {
+ error(18, true);
+ }
+
});
}
},
@@ -24555,24 +25380,20 @@
setOptions: function (itemOptions) {
var chart = this.chart,
chartOptions = chart.options,
plotOptions = chartOptions.plotOptions,
typeOptions = plotOptions[this.type],
- data = itemOptions.data,
options;
- itemOptions.data = null; // remove from merge to prevent looping over the data set
+ this.userOptions = itemOptions;
options = merge(
typeOptions,
plotOptions.series,
itemOptions
);
- // Re-insert the data array to the options and the original config (#717)
- options.data = itemOptions.data = data;
-
// the tooltip options are merged between global and series specific options
this.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
// Delte marker object if not allowed (#1125)
if (typeOptions.marker === null) {
@@ -24585,27 +25406,54 @@
/**
* Get the series' color
*/
getColor: function () {
var options = this.options,
+ userOptions = this.userOptions,
defaultColors = this.chart.options.colors,
- counters = this.chart.counters;
- this.color = options.color ||
- (!options.colorByPoint && defaultColors[counters.color++]) || 'gray';
+ counters = this.chart.counters,
+ color,
+ colorIndex;
+
+ color = options.color || defaultPlotOptions[this.type].color;
+
+ if (!color && !options.colorByPoint) {
+ if (defined(userOptions._colorIndex)) { // after Series.update()
+ colorIndex = userOptions._colorIndex;
+ } else {
+ userOptions._colorIndex = counters.color;
+ colorIndex = counters.color++;
+ }
+ color = defaultColors[colorIndex];
+ }
+
+ this.color = color;
counters.wrapColor(defaultColors.length);
},
/**
* Get the series' symbol
*/
getSymbol: function () {
var series = this,
+ userOptions = series.userOptions,
seriesMarkerOption = series.options.marker,
chart = series.chart,
defaultSymbols = chart.options.symbols,
- counters = chart.counters;
- series.symbol = seriesMarkerOption.symbol || defaultSymbols[counters.symbol++];
-
+ counters = chart.counters,
+ symbolIndex;
+
+ series.symbol = seriesMarkerOption.symbol;
+ if (!series.symbol) {
+ if (defined(userOptions._symbolIndex)) { // after Series.update()
+ symbolIndex = userOptions._symbolIndex;
+ } else {
+ userOptions._symbolIndex = counters.symbol;
+ symbolIndex = counters.symbol++;
+ }
+ series.symbol = defaultSymbols[symbolIndex];
+ }
+
// don't substract radius in image symbols (#604)
if (/^url/.test(series.symbol)) {
seriesMarkerOption.radius = 0;
}
counters.wrapSymbol(defaultSymbols.length);
@@ -24673,20 +25521,22 @@
* @param {Boolean|Object} animation Whether to apply animation, and optionally animation
* configuration
*/
addPoint: function (options, redraw, shift, animation) {
var series = this,
+ seriesOptions = series.options,
data = series.data,
graph = series.graph,
area = series.area,
chart = series.chart,
xData = series.xData,
yData = series.yData,
+ zData = series.zData,
+ names = series.names,
currentShift = (graph && graph.shift) || 0,
- dataOptions = series.options.data,
- point,
- proto = series.pointClass.prototype;
+ dataOptions = seriesOptions.data,
+ point;
setAnimation(animation, chart);
// Make graph animate sideways
if (graph && shift) {
@@ -24703,25 +25553,34 @@
redraw = pick(redraw, true);
// Get options and push the point to xData, yData and series.options. In series.generatePoints
// the Point instance will be created on demand and pushed to the series.data array.
point = { series: series };
- proto.applyOptions.apply(point, [options]);
+ series.pointClass.prototype.applyOptions.apply(point, [options]);
xData.push(point.x);
- yData.push(proto.toYData ? proto.toYData.call(point) : point.y);
+ yData.push(series.toYData ? series.toYData(point) : point.y);
+ zData.push(point.z);
+ if (names) {
+ names[point.x] = point.name;
+ }
dataOptions.push(options);
+ // Generate points to be added to the legend (#1329)
+ if (seriesOptions.legendType === 'point') {
+ series.generatePoints();
+ }
// Shift the first point off the parallel arrays
// todo: consider series.removePoint(i) method
if (shift) {
if (data[0] && data[0].remove) {
data[0].remove(false);
} else {
data.shift();
xData.shift();
yData.shift();
+ zData.shift();
dataOptions.shift();
}
}
series.getAttribs();
@@ -24740,33 +25599,32 @@
*/
setData: function (data, redraw) {
var series = this,
oldData = series.points,
options = series.options,
- initialColor = series.initialColor,
chart = series.chart,
firstPoint = null,
xAxis = series.xAxis,
- i,
- pointProto = series.pointClass.prototype;
+ names = xAxis && xAxis.categories && !xAxis.categories.length ? [] : null,
+ i;
// reset properties
series.xIncrement = null;
series.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange;
- if (defined(initialColor)) { // reset colors for pie
- chart.counters.color = initialColor;
- }
+ series.colorCounter = 0; // for series with colorByPoint (#1547)
// parallel arrays
var xData = [],
yData = [],
+ zData = [],
dataLength = data ? data.length : [],
turboThreshold = options.turboThreshold || 1000,
pt,
pointArrayMap = series.pointArrayMap,
- valueCount = pointArrayMap && pointArrayMap.length;
+ valueCount = pointArrayMap && pointArrayMap.length,
+ hasToYData = !!series.toYData;
// In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
// first value is tested, and we assume that all the rest are defined the same
// way. Although the 'for' loops are similar, they are repeated inside each
// if-else conditional for max performance.
@@ -24807,26 +25665,40 @@
} /* else {
error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
}*/
} else {
for (i = 0; i < dataLength; i++) {
- pt = { series: series };
- pointProto.applyOptions.apply(pt, [data[i]]);
- xData[i] = pt.x;
- yData[i] = pointProto.toYData ? pointProto.toYData.call(pt) : pt.y;
+ if (data[i] !== UNDEFINED) { // stray commas in oldIE
+ pt = { series: series };
+ series.pointClass.prototype.applyOptions.apply(pt, [data[i]]);
+ xData[i] = pt.x;
+ yData[i] = hasToYData ? series.toYData(pt) : pt.y;
+ zData[i] = pt.z;
+ if (names && pt.name) {
+ names[i] = pt.name;
+ }
+ }
}
}
+
+ // Unsorted data is not supported by the line tooltip as well as data grouping and
+ // navigation in Stock charts (#725)
+ if (series.requireSorting && xData.length > 1 && xData[1] < xData[0]) {
+ error(15);
+ }
// Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON
if (isString(yData[0])) {
error(14, true);
}
series.data = [];
series.options.data = data;
series.xData = xData;
series.yData = yData;
+ series.zData = zData;
+ series.names = names;
// destroy old points
i = (oldData && oldData.length) || 0;
while (i--) {
if (oldData[i] && oldData[i].destroy) {
@@ -25033,26 +25905,31 @@
if (!this.processedXData) { // hidden series
this.processData();
}
this.generatePoints();
var series = this,
- chart = series.chart,
options = series.options,
stacking = options.stacking,
xAxis = series.xAxis,
categories = xAxis.categories,
yAxis = series.yAxis,
points = series.points,
dataLength = points.length,
hasModifyValue = !!series.modifyValue,
isBottomSeries,
- allStackSeries = yAxis.series,
- i = allStackSeries.length,
- placeBetween = options.pointPlacement === 'between';
+ allStackSeries,
+ i,
+ placeBetween = options.pointPlacement === 'between',
+ threshold = options.threshold;
//nextSeriesDown;
- // Is it the last visible series?
+ // Is it the last visible series? (#809, #1722).
+ // TODO: After merging in the 'stacking' branch, this logic should probably be moved to Chart.getStacks
+ allStackSeries = yAxis.series.sort(function (a, b) {
+ return a.index - b.index;
+ });
+ i = allStackSeries.length;
while (i--) {
if (allStackSeries[i].visible) {
if (allStackSeries[i] === series) { // #809
isBottomSeries = true;
}
@@ -25064,27 +25941,31 @@
for (i = 0; i < dataLength; i++) {
var point = points[i],
xValue = point.x,
yValue = point.y,
yBottom = point.low,
- stack = yAxis.stacks[(yValue < options.threshold ? '-' : '') + series.stackKey],
+ stack = yAxis.stacks[(yValue < threshold ? '-' : '') + series.stackKey],
pointStack,
pointStackTotal;
-
- // get the plotX translation
- //point.plotX = mathRound(xAxis.translate(xValue, 0, 0, 0, 1) * 10) / 10; // Math.round fixes #591
+
+ // Discard disallowed y values for log axes
+ if (yAxis.isLog && yValue <= 0) {
+ point.y = yValue = null;
+ }
+
+ // Get the plotX translation
point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, placeBetween); // Math.round fixes #591
- // calculate the bottom y value for stacked series
+ // Calculate the bottom y value for stacked series
if (stacking && series.visible && stack && stack[xValue]) {
pointStack = stack[xValue];
pointStackTotal = pointStack.total;
pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
yValue = yBottom + yValue;
if (isBottomSeries) {
- yBottom = pick(options.threshold, yAxis.min);
+ yBottom = pick(threshold, yAxis.min);
}
if (yAxis.isLog && yBottom <= 0) { // #1200, #1232
yBottom = null;
}
@@ -25108,18 +25989,18 @@
if (hasModifyValue) {
yValue = series.modifyValue(yValue, point);
}
// Set the the plotY value, reset it for redraws
- point.plotY = (typeof yValue === 'number') ?
+ point.plotY = (typeof yValue === 'number' && yValue !== Infinity) ?
mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
UNDEFINED;
- // set client related positions for mouse tracking
- point.clientX = chart.inverted ?
- chart.plotHeight - point.plotX :
- point.plotX; // for mouse tracking
+ // Set client related positions for mouse tracking
+ point.clientX = placeBetween ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514
+
+ point.negative = point.y < (threshold || 0);
// some API data
point.category = categories && categories[point.x] !== UNDEFINED ?
categories[point.x] : point.x;
@@ -25138,11 +26019,10 @@
pointsLength,
low,
high,
xAxis = series.xAxis,
axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
- plotX = (xAxis && xAxis.tooltipPosName) || 'plotX',
point,
i,
tooltipPoints = []; // a lookup array for each pixel in the x dimension
// don't waste resources if tracker is disabled
@@ -25171,11 +26051,11 @@
point = points[i];
// Set this range's low to the last range's high plus one
low = points[i - 1] ? high + 1 : 0;
// Now find the new high
high = points[i + 1] ?
- mathMax(0, mathFloor((point[plotX] + (points[i + 1] ? points[i + 1][plotX] : axisLength)) / 2)) :
+ mathMax(0, mathFloor((point.clientX + (points[i + 1] ? points[i + 1].clientX : axisLength)) / 2)) :
axisLength;
while (low >= 0 && low <= high) {
tooltipPoints[low++] = point;
}
@@ -25184,46 +26064,54 @@
},
/**
* Format the header of the tooltip
*/
- tooltipHeaderFormatter: function (key) {
+ tooltipHeaderFormatter: function (point) {
var series = this,
tooltipOptions = series.tooltipOptions,
xDateFormat = tooltipOptions.xDateFormat,
+ dateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats,
xAxis = series.xAxis,
isDateTime = xAxis && xAxis.options.type === 'datetime',
+ headerFormat = tooltipOptions.headerFormat,
+ closestPointRange = xAxis && xAxis.closestPointRange,
n;
// Guess the best date format based on the closest point distance (#568)
if (isDateTime && !xDateFormat) {
- for (n in timeUnits) {
- if (timeUnits[n] >= xAxis.closestPointRange) {
- xDateFormat = tooltipOptions.dateTimeLabelFormats[n];
- break;
- }
- }
+ if (closestPointRange) {
+ for (n in timeUnits) {
+ if (timeUnits[n] >= closestPointRange) {
+ xDateFormat = dateTimeLabelFormats[n];
+ break;
+ }
+ }
+ } else {
+ xDateFormat = dateTimeLabelFormats.day;
+ }
}
- return tooltipOptions.headerFormat
- .replace('{point.key}', isDateTime && isNumber(key) ? dateFormat(xDateFormat, key) : key)
- .replace('{series.name}', series.name)
- .replace('{series.color}', series.color);
+ // Insert the header date format if any
+ if (isDateTime && xDateFormat && isNumber(point.key)) {
+ headerFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}');
+ }
+
+ return format(headerFormat, {
+ point: point,
+ series: series
+ });
},
/**
* Series mouse over handler
*/
onMouseOver: function () {
var series = this,
chart = series.chart,
hoverSeries = chart.hoverSeries;
- /*if (!hasTouch && chart.mouseIsDown) {
- return;
- }*/
-
// set normal state to previous series
if (hoverSeries && hoverSeries !== series) {
hoverSeries.onMouseOut();
}
@@ -25259,11 +26147,11 @@
fireEvent(series, 'mouseOut');
}
// hide the tooltip
- if (tooltip && !options.stickyTracking && !tooltip.shared) {
+ if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
tooltip.hide();
}
// set normal state
series.setState();
@@ -25388,14 +26276,14 @@
plotX = point.plotX;
plotY = point.plotY;
graphic = point.graphic;
pointMarkerOptions = point.marker || {};
enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;
- isInside = chart.isInsidePlot(plotX, plotY, chart.inverted);
+ isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858
// only draw the point if y is defined
- if (enabled && plotY !== UNDEFINED && !isNaN(plotY)) {
+ if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
// shortcuts
pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
radius = pointAttr.r;
symbol = pick(pointMarkerOptions.symbol, series.symbol);
@@ -25465,11 +26353,12 @@
* points with individual marker options. If such options are not defined for the point,
* a reference to the series wide attributes is stored in point.pointAttr.
*/
getAttribs: function () {
var series = this,
- normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
+ seriesOptions = series.options,
+ normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions,
stateOptions = normalOptions.states,
stateOptionsHover = stateOptions[HOVER_STATE],
pointStateOptionsHover,
seriesColor = series.color,
normalDefaults = {
@@ -25481,19 +26370,20 @@
point,
seriesPointAttr = [],
pointAttr,
pointAttrToOptions = series.pointAttrToOptions,
hasPointSpecificOptions,
+ negativeColor = seriesOptions.negativeColor,
key;
// series type specific modifications
- if (series.options.marker) { // line, spline, area, areaspline, scatter
+ if (seriesOptions.marker) { // line, spline, area, areaspline, scatter
// if no hover radius is given, default to normal radius + 2
stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
-
+
} else { // column, bar, pie
// if no hover color is given, brighten the normal color
stateOptionsHover.color = stateOptionsHover.color ||
Color(stateOptionsHover.color || seriesColor)
@@ -25521,33 +26411,36 @@
point = points[i];
normalOptions = (point.options && point.options.marker) || point.options;
if (normalOptions && normalOptions.enabled === false) {
normalOptions.radius = 0;
}
- hasPointSpecificOptions = series.options.colorByPoint; // #868
+ if (point.negative && negativeColor) {
+ point.color = point.fillColor = negativeColor;
+ }
+
+ hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868
+
// check if the point has specific visual options
if (point.options) {
for (key in pointAttrToOptions) {
if (defined(normalOptions[pointAttrToOptions[key]])) {
hasPointSpecificOptions = true;
}
}
}
-
-
// a specific marker config object is defined for the individual point:
// create it's own attribute collection
if (hasPointSpecificOptions) {
normalOptions = normalOptions || {};
pointAttr = [];
stateOptions = normalOptions.states || {}; // reassign for individual point
pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
// Handle colors for column and pies
- if (!series.options.marker) { // column, bar, point
+ if (!seriesOptions.marker) { // column, bar, point
// if no hover color is given, brighten the normal color
pointStateOptionsHover.color =
Color(pointStateOptionsHover.color || point.color)
.brighten(pointStateOptionsHover.brightness ||
stateOptionsHover.brightness).get();
@@ -25563,17 +26456,23 @@
pointAttr[HOVER_STATE] = series.convertAttribs(
stateOptions[HOVER_STATE],
seriesPointAttr[HOVER_STATE],
pointAttr[NORMAL_STATE]
);
+
// inherit from point normal and series hover
pointAttr[SELECT_STATE] = series.convertAttribs(
stateOptions[SELECT_STATE],
seriesPointAttr[SELECT_STATE],
pointAttr[NORMAL_STATE]
);
+ // Force the fill to negativeColor on markers
+ if (point.negative && seriesOptions.marker && negativeColor) {
+ pointAttr[NORMAL_STATE].fill = pointAttr[HOVER_STATE].fill = pointAttr[SELECT_STATE].fill =
+ series.convertAttribs({ fillColor: negativeColor }).fill;
+ }
// no marker config object is created: copy a reference to the series-wide
// attribute collection
} else {
@@ -25583,12 +26482,38 @@
point.pointAttr = pointAttr;
}
},
+ /**
+ * Update the series with a new set of options
+ */
+ update: function (newOptions, redraw) {
+ var chart = this.chart,
+ // must use user options when changing type because this.options is merged
+ // in with type specific plotOptions
+ oldOptions = this.userOptions,
+ oldType = this.type;
+ // Do the merge, with some forced options
+ newOptions = merge(oldOptions, {
+ animation: false,
+ index: this.index,
+ pointStart: this.xData[0] // when updating after addPoint
+ }, newOptions);
+ // Destroy the series and reinsert methods from the type prototype
+ this.remove(false);
+ extend(this, seriesTypes[newOptions.type || oldType].prototype);
+
+
+ this.init(chart, newOptions);
+ if (pick(redraw, true)) {
+ chart.redraw(false);
+ }
+ },
+
/**
* Clear DOM objects and free up memory
*/
destroy: function () {
var series = this,
@@ -25610,11 +26535,11 @@
// erase from axes
each(['xAxis', 'yAxis'], function (AXIS) {
axis = series[AXIS];
if (axis) {
erase(axis.series, series);
- axis.isDirty = true;
+ axis.isDirty = axis.forceRedraw = true;
}
});
// remove legend items
if (series.legendItem) {
@@ -25633,11 +26558,12 @@
// Clear the animation timeout if we are destroying the series during initial animation
clearTimeout(series.animationTimeout);
// destroy all SVGElements associated to the series
- each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker', 'trackerGroup'], function (prop) {
+ each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker',
+ 'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) {
if (series[prop]) {
// issue 134 workaround
destroy = issue134 && prop === 'group' ?
'hide' :
@@ -25683,22 +26609,24 @@
// Create a separate group for the data labels to avoid rotation
dataLabelsGroup = series.plotGroup(
'dataLabelsGroup',
'data-labels',
series.visible ? VISIBLE : HIDDEN,
- 6
+ options.zIndex || 6
);
// Make the labels for each point
generalOptions = options;
each(points, function (point) {
var enabled,
dataLabel = point.dataLabel,
+ labelConfig,
attr,
name,
rotation,
+ connector = point.connector,
isNew = true;
// Determine if each data label is enabled
pointOptions = point.options && point.options.dataLabels;
enabled = generalOptions.enabled || (pointOptions && pointOptions.enabled);
@@ -25717,24 +26645,36 @@
// Create individual options structure that can be extended without
// affecting others
options = merge(generalOptions, pointOptions);
// Get the string
- str = options.formatter.call(point.getLabelConfig(), options);
+ labelConfig = point.getLabelConfig();
+ str = options.format ?
+ format(options.format, labelConfig) :
+ options.formatter.call(labelConfig, options);
// Determine the color
options.style.color = pick(options.color, options.style.color, series.color, 'black');
// update existing label
if (dataLabel) {
- // vertically centered
- dataLabel
- .attr({
- text: str
- });
- isNew = false;
+
+ if (defined(str)) {
+ dataLabel
+ .attr({
+ text: str
+ });
+ isNew = false;
+
+ } else { // #1437 - the label is shown conditionally
+ point.dataLabel = dataLabel = dataLabel.destroy();
+ if (connector) {
+ point.connector = connector.destroy();
+ }
+ }
+
// create new label
} else if (defined(str)) {
attr = {
//align: align,
fill: options.backgroundColor,
@@ -25766,12 +26706,12 @@
.add(dataLabelsGroup)
.shadow(options.shadow);
}
- // Now the data label is created and placed at 0,0, so we need to align it
if (dataLabel) {
+ // Now the data label is created and placed at 0,0, so we need to align it
series.alignDataLabel(point, dataLabel, options, null, isNew);
}
}
});
}
@@ -25815,42 +26755,63 @@
alignAttr = dataLabel.alignAttr;
}
// Show or hide based on the final aligned position
dataLabel.attr({
- visibility: options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) || chart.isInsidePlot(plotX, plotY, inverted) ?
- (hasSVG ? 'inherit' : VISIBLE) :
+ visibility: options.crop === false || /*chart.isInsidePlot(alignAttr.x, alignAttr.y) || */chart.isInsidePlot(plotX, plotY, inverted) ?
+ (chart.renderer.isSVG ? 'inherit' : VISIBLE) :
HIDDEN
});
},
/**
* Return the graph path of a segment
*/
getSegmentPath: function (segment) {
var series = this,
- segmentPath = [];
-
+ segmentPath = [],
+ step = series.options.step;
+
// build the segment line
each(segment, function (point, i) {
+
+ var plotX = point.plotX,
+ plotY = point.plotY,
+ lastPoint;
if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
} else {
// moveTo or lineTo
segmentPath.push(i ? L : M);
// step line?
- if (i && series.options.step) {
- var lastPoint = segment[i - 1];
- segmentPath.push(
- point.plotX,
- lastPoint.plotY
- );
+ if (step && i) {
+ lastPoint = segment[i - 1];
+ if (step === 'right') {
+ segmentPath.push(
+ lastPoint.plotX,
+ plotY
+ );
+
+ } else if (step === 'center') {
+ segmentPath.push(
+ (lastPoint.plotX + plotX) / 2,
+ lastPoint.plotY,
+ (lastPoint.plotX + plotX) / 2,
+ plotY
+ );
+
+ } else {
+ segmentPath.push(
+ plotX,
+ lastPoint.plotY
+ );
+ }
}
// normal line to next point
segmentPath.push(
point.plotX,
@@ -25893,58 +26854,151 @@
},
/**
* Draw the actual graph
*/
- drawGraph: function () {
- var options = this.options,
- graph = this.graph,
- group = this.group,
- color = options.lineColor || this.color,
+ drawGraph: function () {
+ var series = this,
+ options = this.options,
+ props = [['graph', options.lineColor || this.color]],
lineWidth = options.lineWidth,
dashStyle = options.dashStyle,
- attribs,
- graphPath = this.getGraphPath();
+ graphPath = this.getGraphPath(),
+ negativeColor = options.negativeColor;
-
+ if (negativeColor) {
+ props.push(['graphNeg', negativeColor]);
+ }
+
// draw the graph
- if (graph) {
- stop(graph); // cancel running animations, #459
- graph.animate({ d: graphPath });
-
- } else {
- if (lineWidth) {
+ each(props, function (prop, i) {
+ var graphKey = prop[0],
+ graph = series[graphKey],
+ attribs;
+
+ if (graph) {
+ stop(graph); // cancel running animations, #459
+ graph.animate({ d: graphPath });
+
+ } else if (lineWidth && graphPath.length) { // #1487
attribs = {
- stroke: color,
+ stroke: prop[1],
'stroke-width': lineWidth,
zIndex: 1 // #1069
};
if (dashStyle) {
attribs.dashstyle = dashStyle;
}
- this.graph = this.chart.renderer.path(graphPath)
- .attr(attribs).add(group).shadow(options.shadow);
+ series[graphKey] = series.chart.renderer.path(graphPath)
+ .attr(attribs)
+ .add(series.group)
+ .shadow(!i && options.shadow);
}
- }
+ });
},
+
+ /**
+ * Clip the graphs into the positive and negative coloured graphs
+ */
+ clipNeg: function () {
+ var options = this.options,
+ chart = this.chart,
+ renderer = chart.renderer,
+ negativeColor = options.negativeColor,
+ translatedThreshold,
+ posAttr,
+ negAttr,
+ graph = this.graph,
+ area = this.area,
+ posClip = this.posClip,
+ negClip = this.negClip,
+ chartWidth = chart.chartWidth,
+ chartHeight = chart.chartHeight,
+ chartSizeMax = mathMax(chartWidth, chartHeight),
+ above,
+ below;
+
+ if (negativeColor && (graph || area)) {
+ translatedThreshold = mathCeil(this.yAxis.len - this.yAxis.translate(options.threshold || 0));
+ above = {
+ x: 0,
+ y: 0,
+ width: chartSizeMax,
+ height: translatedThreshold
+ };
+ below = {
+ x: 0,
+ y: translatedThreshold,
+ width: chartSizeMax,
+ height: chartSizeMax - translatedThreshold
+ };
+
+ if (chart.inverted && renderer.isVML) {
+ above = {
+ x: chart.plotWidth - translatedThreshold - chart.plotLeft,
+ y: 0,
+ width: chartWidth,
+ height: chartHeight
+ };
+ below = {
+ x: translatedThreshold + chart.plotLeft - chartWidth,
+ y: 0,
+ width: chart.plotLeft + translatedThreshold,
+ height: chartWidth
+ };
+ }
+
+ if (this.yAxis.reversed) {
+ posAttr = below;
+ negAttr = above;
+ } else {
+ posAttr = above;
+ negAttr = below;
+ }
+
+ if (posClip) { // update
+ posClip.animate(posAttr);
+ negClip.animate(negAttr);
+ } else {
+
+ this.posClip = posClip = renderer.clipRect(posAttr);
+ this.negClip = negClip = renderer.clipRect(negAttr);
+
+ if (graph) {
+ graph.clip(posClip);
+ this.graphNeg.clip(negClip);
+ }
+
+ if (area) {
+ area.clip(posClip);
+ this.areaNeg.clip(negClip);
+ }
+ }
+ }
+ },
/**
- * Initialize and perform group inversion on series.group and series.trackerGroup
+ * Initialize and perform group inversion on series.group and series.markerGroup
*/
invertGroups: function () {
var series = this,
chart = series.chart;
+
+ // Pie, go away (#1736)
+ if (!series.xAxis) {
+ return;
+ }
// A fixed size is needed for inversion to work
function setInvert() {
var size = {
width: series.yAxis.len,
height: series.xAxis.len
};
- each(['group', 'trackerGroup', 'markerGroup'], function (groupName) {
+ each(['group', 'markerGroup'], function (groupName) {
if (series[groupName]) {
series[groupName].attr(size).invert();
}
});
}
@@ -25960,34 +27014,36 @@
// On subsequent render and redraw, just do setInvert without setting up events again
series.invertGroups = setInvert;
},
/**
- * General abstraction for creating plot groups like series.group, series.trackerGroup, series.dataLabelsGroup and
+ * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and
* series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size.
*/
plotGroup: function (prop, name, visibility, zIndex, parent) {
var group = this[prop],
+ isNew = !group,
chart = this.chart,
xAxis = this.xAxis,
yAxis = this.yAxis;
// Generate it on first call
- if (!group) {
+ if (isNew) {
this[prop] = group = chart.renderer.g(name)
.attr({
visibility: visibility,
zIndex: zIndex || 0.1 // IE8 needs this
})
.add(parent);
}
// Place it on first and subsequent (redraw) calls
- group.translate(
- xAxis ? xAxis.left : chart.plotLeft,
- yAxis ? yAxis.top : chart.plotTop
- );
-
+ group[isNew ? 'attr' : 'animate']({
+ translateX: xAxis ? xAxis.left : chart.plotLeft,
+ translateY: yAxis ? yAxis.top : chart.plotTop,
+ scaleX: 1, // #1623
+ scaleY: 1
+ });
return group;
},
/**
@@ -25997,11 +27053,13 @@
var series = this,
chart = series.chart,
group,
options = series.options,
animation = options.animation,
- doAnimation = animation && !!series.animate,
+ doAnimation = animation && !!series.animate &&
+ chart.renderer.isSVG, // this animation doesn't work in IE8 quirks when the group div is hidden,
+ // and looks bad in other oldIE
visibility = series.visible ? VISIBLE : HIDDEN,
zIndex = options.zIndex,
hasRendered = series.hasRendered,
chartSeriesGroup = chart.seriesGroup;
@@ -26028,23 +27086,24 @@
}
// cache attributes for shapes
series.getAttribs();
- // SVGRenderer needs to know this before drawing elements (#1089)
- group.inverted = chart.inverted;
+ // SVGRenderer needs to know this before drawing elements (#1089, #1795)
+ group.inverted = series.isCartesian ? chart.inverted : false;
// draw the graph if any
if (series.drawGraph) {
series.drawGraph();
+ series.clipNeg();
}
+ // draw the data labels (inn pies they go before the points)
+ series.drawDataLabels();
+
// draw the points
series.drawPoints();
-
- // draw the data labels
- series.drawDataLabels();
// draw the mouse tracking area
if (series.options.enableMouseTracking !== false) {
series.drawTracker();
@@ -26056,13 +27115,10 @@
}
// Initial clipping, must be defined after inverting groups for VML
if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
group.clip(chart.clipRect);
- if (this.trackerGroup) {
- this.trackerGroup.clip(chart.clipRect);
- }
}
// Run the animation
if (doAnimation) {
series.animate();
@@ -26080,11 +27136,13 @@
*/
redraw: function () {
var series = this,
chart = series.chart,
wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after
- group = series.group;
+ group = series.group,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis;
// reposition on resize
if (group) {
if (chart.inverted) {
group.attr({
@@ -26092,12 +27150,12 @@
height: chart.plotHeight
});
}
group.animate({
- translateX: series.xAxis.left,
- translateY: series.yAxis.top
+ translateX: pick(xAxis && xAxis.left, chart.plotLeft),
+ translateY: pick(yAxis && yAxis.top, chart.plotTop)
});
}
series.translate();
series.setTooltipPoints(true);
@@ -26113,12 +27171,14 @@
*/
setState: function (state) {
var series = this,
options = series.options,
graph = series.graph,
+ graphNeg = series.graphNeg,
stateOptions = options.states,
- lineWidth = options.lineWidth;
+ lineWidth = options.lineWidth,
+ attribs;
state = state || NORMAL_STATE;
if (series.state !== state) {
series.state = state;
@@ -26130,13 +27190,18 @@
if (state) {
lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
}
if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
- graph.attr({ // use attr because animate will cause any other animation on the graph to stop
+ attribs = {
'stroke-width': lineWidth
- }, state ? 0 : 500);
+ };
+ // use attr because animate will cause any other animation on the graph to stop
+ graph.attr(attribs);
+ if (graphNeg) {
+ graphNeg.attr(attribs);
+ }
}
}
},
/**
@@ -26147,51 +27212,32 @@
*/
setVisible: function (vis, redraw) {
var series = this,
chart = series.chart,
legendItem = series.legendItem,
- seriesGroup = series.group,
- seriesTracker = series.tracker,
- dataLabelsGroup = series.dataLabelsGroup,
- markerGroup = series.markerGroup,
showOrHide,
- i,
- points = series.points,
- point,
ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
oldVisibility = series.visible;
// if called without an argument, toggle visibility
- series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
+ series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis;
showOrHide = vis ? 'show' : 'hide';
- // show or hide series
- if (seriesGroup) { // pies don't have one
- seriesGroup[showOrHide]();
- }
- if (markerGroup) {
- markerGroup[showOrHide]();
- }
-
- // show or hide trackers
- if (seriesTracker) {
- seriesTracker[showOrHide]();
- } else if (points) {
- i = points.length;
- while (i--) {
- point = points[i];
- if (point.tracker) {
- point.tracker[showOrHide]();
- }
+ // show or hide elements
+ each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) {
+ if (series[key]) {
+ series[key][showOrHide]();
}
- }
+ });
-
- if (dataLabelsGroup) {
- dataLabelsGroup[showOrHide]();
+
+ // hide tooltip (#1361)
+ if (chart.hoverSeries === series) {
+ series.onMouseOut();
}
+
if (legendItem) {
chart.legend.colorizeItem(series, vis);
}
@@ -26204,10 +27250,15 @@
otherSeries.isDirty = true;
}
});
}
+ // show or hide linked series
+ each(series.linkedSeries, function (otherSeries) {
+ otherSeries.setVisible(vis, false);
+ });
+
if (ignoreHiddenSeries) {
chart.isDirtyBox = true;
}
if (redraw !== false) {
chart.redraw();
@@ -26260,19 +27311,24 @@
options = series.options,
trackByArea = options.trackByArea,
trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
trackerPathLength = trackerPath.length,
chart = series.chart,
+ pointer = chart.pointer,
renderer = chart.renderer,
snap = chart.options.tooltip.snap,
tracker = series.tracker,
cursor = options.cursor,
css = cursor && { cursor: cursor },
singlePoints = series.singlePoints,
- trackerGroup = this.isCartesian && this.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup),
singlePoint,
- i;
+ i,
+ onMouseOver = function () {
+ if (chart.hoverSeries !== series) {
+ series.onMouseOver();
+ }
+ };
// Extend end points. A better way would be to use round linecaps,
// but those are not clickable in VML.
if (trackerPathLength && !trackByArea) {
i = trackerPathLength + 1;
@@ -26299,31 +27355,29 @@
if (tracker) {
tracker.attr({ d: trackerPath });
} else { // create
- series.tracker = renderer.path(trackerPath)
+ series.tracker = tracker = renderer.path(trackerPath)
.attr({
- isTracker: true,
- 'stroke-linejoin': 'bevel',
+ 'class': PREFIX + 'tracker',
+ 'stroke-linejoin': 'round', // #1225
visibility: series.visible ? VISIBLE : HIDDEN,
stroke: TRACKER_FILL,
fill: trackByArea ? TRACKER_FILL : NONE,
- 'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap)
+ 'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap),
+ zIndex: 2
})
- .on(hasTouch ? 'touchstart' : 'mouseover', function () {
- if (chart.hoverSeries !== series) {
- series.onMouseOver();
- }
- })
- .on('mouseout', function () {
- if (!options.stickyTracking) {
- series.onMouseOut();
- }
- })
+ .addClass(PREFIX + 'tracker')
+ .on('mouseover', onMouseOver)
+ .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
.css(css)
- .add(trackerGroup);
+ .add(series.markerGroup);
+
+ if (hasTouch) {
+ tracker.on('touchstart', onMouseOver);
+ }
}
}
}; // end Series prototype
@@ -26351,10 +27405,77 @@
*/
var AreaSeries = extendClass(Series, {
type: 'area',
/**
+ * For stacks, don't split segments on null values. Instead, draw null values with
+ * no marker. Also insert dummy points for any X position that exists in other series
+ * in the stack.
+ */
+ getSegments: function () {
+ var segments = [],
+ segment = [],
+ keys = [],
+ xAxis = this.xAxis,
+ yAxis = this.yAxis,
+ stack = yAxis.stacks[this.stackKey],
+ pointMap = {},
+ plotX,
+ plotY,
+ points = this.points,
+ i,
+ x;
+
+ if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue
+ // Create a map where we can quickly look up the points by their X value.
+ for (i = 0; i < points.length; i++) {
+ pointMap[points[i].x] = points[i];
+ }
+
+ // Sort the keys (#1651)
+ for (x in stack) {
+ keys.push(+x);
+ }
+ keys.sort(function (a, b) {
+ return a - b;
+ });
+
+ each(keys, function (x) {
+ // The point exists, push it to the segment
+ if (pointMap[x]) {
+ segment.push(pointMap[x]);
+
+ // There is no point for this X value in this series, so we
+ // insert a dummy point in order for the areas to be drawn
+ // correctly.
+ } else {
+ plotX = xAxis.translate(x);
+ plotY = yAxis.toPixels(stack[x].cum, true);
+ segment.push({
+ y: null,
+ plotX: plotX,
+ clientX: plotX,
+ plotY: plotY,
+ yBottom: plotY,
+ onMouseOver: noop
+ });
+ }
+ });
+
+ if (segment.length) {
+ segments.push(segment);
+ }
+
+ } else {
+ Series.prototype.getSegments.call(this);
+ segments = this.segments;
+ }
+
+ this.segments = segments;
+ },
+
+ /**
* Extend the base Series getSegmentPath method by adding the path for the area.
* This path is pushed to the series.areaPath property.
*/
getSegmentPath: function (segment) {
@@ -26418,28 +27539,39 @@
// Call the base method
Series.prototype.drawGraph.apply(this);
// Define local variables
- var areaPath = this.areaPath,
+ var series = this,
+ areaPath = this.areaPath,
options = this.options,
- area = this.area;
+ negativeColor = options.negativeColor,
+ props = [['area', this.color, options.fillColor]]; // area name, main color, fill color
- // Create or update the area
- if (area) { // update
- area.animate({ d: areaPath });
-
- } else { // create
- this.area = this.chart.renderer.path(areaPath)
- .attr({
- fill: pick(
- options.fillColor,
- Color(this.color).setOpacity(options.fillOpacity || 0.75).get()
- ),
- zIndex: 0 // #1069
- }).add(this.group);
+ if (negativeColor) {
+ props.push(['areaNeg', options.negativeColor, options.negativeFillColor]);
}
+
+ each(props, function (prop) {
+ var areaKey = prop[0],
+ area = series[areaKey];
+
+ // Create or update the area
+ if (area) { // update
+ area.animate({ d: areaPath });
+
+ } else { // create
+ series[areaKey] = series.chart.renderer.path(areaPath)
+ .attr({
+ fill: pick(
+ prop[2],
+ Color(prop[1]).setOpacity(options.fillOpacity || 0.75).get()
+ ),
+ zIndex: 0 // #1069
+ }).add(series.group);
+ }
+ });
},
/**
* Get the series' symbol in the legend
*
@@ -26562,11 +27694,11 @@
stroke: 'green',
'stroke-width': 1
})
.add();
}
- // */
+ */
// moveTo or lineTo
if (!i) {
ret = [M, plotX, plotY];
} else { // curve from last point to this
@@ -26636,25 +27768,28 @@
dataLabels: {
align: null, // auto
verticalAlign: null, // auto
y: null
},
+ stickyTracking: false,
threshold: 0
});
/**
* ColumnSeries object
*/
var ColumnSeries = extendClass(Series, {
type: 'column',
tooltipOutsidePlot: true,
+ requireSorting: false,
pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
stroke: 'borderColor',
'stroke-width': 'borderWidth',
fill: 'color',
r: 'borderRadius'
},
+ trackerGroups: ['group', 'dataLabelsGroup'],
/**
* Initialize the series
*/
init: function () {
@@ -26673,33 +27808,30 @@
});
}
},
/**
- * Translate each point to the plot area coordinate system and find shape positions
+ * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding,
+ * pointWidth etc.
*/
- translate: function () {
+ getColumnMetrics: function () {
+
var series = this,
chart = series.chart,
options = series.options,
- stacking = options.stacking,
- borderWidth = options.borderWidth,
- columnCount = 0,
- xAxis = series.xAxis,
+ xAxis = this.xAxis,
reversedXAxis = xAxis.reversed,
- stackGroups = {},
stackKey,
- columnIndex;
+ stackGroups = {},
+ columnIndex,
+ columnCount = 0;
- Series.prototype.translate.apply(series);
-
// Get the total number of column type series.
// This is called on every series. Consider moving this logic to a
// chart.orderStacks() function and call it on init, addSeries and removeSeries
if (options.grouping === false) {
columnCount = 1;
-
} else {
each(chart.series, function (otherSeries) {
var otherOptions = otherSeries.options;
if (otherSeries.type === series.type && otherSeries.visible &&
series.options.group === otherOptions.group) { // used in Stock charts navigator series
@@ -26715,40 +27847,64 @@
otherSeries.columnIndex = columnIndex;
}
});
}
- // calculate the width and position of each column based on
- // the number of column series in the plot, the groupPadding
- // and the pointPadding options
- var points = series.points,
- categoryWidth = mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1),
+ var categoryWidth = mathMin(
+ mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1),
+ xAxis.len // #1535
+ ),
groupPadding = categoryWidth * options.groupPadding,
groupWidth = categoryWidth - 2 * groupPadding,
pointOffsetWidth = groupWidth / columnCount,
optionPointWidth = options.pointWidth,
pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
pointOffsetWidth * options.pointPadding,
pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts
- barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
- colIndex = (reversedXAxis ? columnCount -
- series.columnIndex : series.columnIndex) || 0,
+ colIndex = (reversedXAxis ?
+ columnCount - (series.columnIndex || 0) : // #1251
+ series.columnIndex) || 0,
pointXOffset = pointPadding + (groupPadding + colIndex *
pointOffsetWidth - (categoryWidth / 2)) *
- (reversedXAxis ? -1 : 1),
+ (reversedXAxis ? -1 : 1);
+
+ // Save it for reading in linked series (Error bars particularly)
+ return (series.columnMetrics = {
+ width: pointWidth,
+ offset: pointXOffset
+ });
+
+ },
+
+ /**
+ * Translate each point to the plot area coordinate system and find shape positions
+ */
+ translate: function () {
+ var series = this,
+ chart = series.chart,
+ options = series.options,
+ stacking = options.stacking,
+ borderWidth = options.borderWidth,
+ yAxis = series.yAxis,
threshold = options.threshold,
- translatedThreshold = series.translatedThreshold = series.yAxis.getThreshold(threshold),
- minPointLength = pick(options.minPointLength, 5);
+ translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
+ minPointLength = pick(options.minPointLength, 5),
+ metrics = series.getColumnMetrics(),
+ pointWidth = metrics.width,
+ barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
+ pointXOffset = metrics.offset;
+ Series.prototype.translate.apply(series);
+
// record the new values
- each(points, function (point) {
- var plotY = point.plotY,
+ each(series.points, function (point) {
+ var plotY = mathMin(mathMax(-999, point.plotY), yAxis.len + 999), // Don't draw too far outside plot area (#1303)
yBottom = pick(point.yBottom, translatedThreshold),
barX = point.plotX + pointXOffset,
barY = mathCeil(mathMin(plotY, yBottom)),
barH = mathCeil(mathMax(plotY, yBottom) - barY),
- stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
+ stack = yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
shapeArgs;
// Record the offset'ed position and width of the bar to be able to align the stacking total correctly
if (stacking && series.visible && stack && stack[point.x]) {
stack[point.x].setOffset(pointXOffset, barW);
@@ -26759,11 +27915,11 @@
if (minPointLength) {
barH = minPointLength;
barY =
mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
yBottom - minPointLength : // keep position
- translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
+ translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0); // use exact yAxis.translation (#1485)
}
}
point.barX = barX;
point.pointWidth = pointWidth;
@@ -26775,15 +27931,10 @@
if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
shapeArgs.y -= 1;
shapeArgs.height += 1;
}
- // make small columns responsive to mouse
- point.trackerArgs = mathAbs(barH) < 3 && merge(point.shapeArgs, {
- height: 6,
- y: barY - 3
- });
});
},
getSymbol: noop,
@@ -26813,12 +27964,14 @@
// draw the columns
each(series.points, function (point) {
var plotY = point.plotY,
graphic = point.graphic;
+
if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
shapeArgs = point.shapeArgs;
+
if (graphic) { // update
stop(graphic);
graphic.animate(merge(shapeArgs));
} else {
@@ -26831,82 +27984,78 @@
} else if (graphic) {
point.graphic = graphic.destroy(); // #1269
}
});
},
+
/**
- * Draw the individual tracker elements.
- * This method is inherited by scatter and pie charts too.
+ * Add tracking event listener to the series group, so the point graphics
+ * themselves act as trackers
*/
drawTracker: function () {
var series = this,
- chart = series.chart,
- renderer = chart.renderer,
- shapeArgs,
- tracker,
- trackerLabel = +new Date(),
- options = series.options,
- cursor = options.cursor,
+ pointer = series.chart.pointer,
+ cursor = series.options.cursor,
css = cursor && { cursor: cursor },
- trackerGroup = series.isCartesian && series.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup),
- rel,
- plotY,
- validPlotY;
-
- each(series.points, function (point) {
- tracker = point.tracker;
- shapeArgs = point.trackerArgs || point.shapeArgs;
- plotY = point.plotY;
- validPlotY = !series.isCartesian || (plotY !== UNDEFINED && !isNaN(plotY));
- delete shapeArgs.strokeWidth;
- if (point.y !== null && validPlotY) {
- if (tracker) {// update
- tracker.attr(shapeArgs);
+ onMouseOver = function (e) {
+ var target = e.target,
+ point;
- } else {
- point.tracker =
- renderer[point.shapeType](shapeArgs)
- .attr({
- isTracker: trackerLabel,
- fill: TRACKER_FILL,
- visibility: series.visible ? VISIBLE : HIDDEN
- })
- .on(hasTouch ? 'touchstart' : 'mouseover', function (event) {
- rel = event.relatedTarget || event.fromElement;
- if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
- series.onMouseOver();
- }
- point.onMouseOver();
+ series.onMouseOver();
- })
- .on('mouseout', function (event) {
- if (!options.stickyTracking) {
- rel = event.relatedTarget || event.toElement;
- if (attr(rel, 'isTracker') !== trackerLabel) {
- series.onMouseOut();
- }
- }
- })
- .css(css)
- .add(point.group || trackerGroup); // pies have point group - see issue #118
+ while (target && !point) {
+ point = target.point;
+ target = target.parentNode;
}
+ if (point !== UNDEFINED) { // undefined on graph in scatterchart
+ point.onMouseOver(e);
+ }
+ };
+
+ // Add reference to the point
+ each(series.points, function (point) {
+ if (point.graphic) {
+ point.graphic.element.point = point;
}
+ if (point.dataLabel) {
+ point.dataLabel.element.point = point;
+ }
});
+
+ // Add the event listeners, we need to do this only once
+ if (!series._hasTracking) {
+ each(series.trackerGroups, function (key) {
+ if (series[key]) { // we don't always have dataLabelsGroup
+ series[key]
+ .addClass(PREFIX + 'tracker')
+ .on('mouseover', onMouseOver)
+ .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
+ .css(css);
+ if (hasTouch) {
+ series[key].on('touchstart', onMouseOver);
+ }
+ }
+ });
+
+ } else {
+ series._hasTracking = true;
+ }
},
/**
* Override the basic data label alignment by adjusting for the position of the column
*/
alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
var chart = this.chart,
inverted = chart.inverted,
- below = point.below || (point.plotY > (this.translatedThreshold || chart.plotSizeY)),
- inside = (this.options.stacking || options.inside); // draw it inside the box?
+ dlBox = point.dlBox || point.shapeArgs, // data label box for alignment
+ below = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)),
+ inside = pick(options.inside, !!this.options.stacking); // draw it inside the box?
// Align to the column itself, or the top of it
- if (point.shapeArgs) { // Area range uses this method but not alignTo
- alignTo = merge(point.shapeArgs);
+ if (dlBox) { // Area range uses this method but not alignTo
+ alignTo = merge(dlBox);
if (inverted) {
alignTo = {
x: chart.plotWidth - alignTo.y - alignTo.height,
y: chart.plotHeight - alignTo.x - alignTo.width,
width: alignTo.height,
@@ -26946,51 +28095,39 @@
* Animate the column heights one by one from zero
* @param {Boolean} init Whether to initialize the animation or run it
*/
animate: function (init) {
var series = this,
- points = series.points,
- options = series.options;
+ yAxis = this.yAxis,
+ options = series.options,
+ inverted = this.chart.inverted,
+ attr = {},
+ translatedThreshold;
- if (!init) { // run the animation
- /*
- * Note: Ideally the animation should be initialized by calling
- * series.group.hide(), and then calling series.group.show()
- * after the animation was started. But this rendered the shadows
- * invisible in IE8 standards mode. If the columns flicker on large
- * datasets, this is the cause.
- */
-
- each(points, function (point) {
- var graphic = point.graphic,
- shapeArgs = point.shapeArgs,
- yAxis = series.yAxis,
- threshold = options.threshold;
-
- if (graphic) {
- // start values
- graphic.attr({
- height: 0,
- y: defined(threshold) ?
- yAxis.getThreshold(threshold) :
- yAxis.translate(yAxis.getExtremes().min, 0, 1, 0, 1)
- });
-
- // animate
- graphic.animate({
- height: shapeArgs.height,
- y: shapeArgs.y
- }, options.animation);
+ if (hasSVG) { // VML is too slow anyway
+ if (init) {
+ attr.scaleY = 0.001;
+ translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold)));
+ if (inverted) {
+ attr.translateX = translatedThreshold - yAxis.len;
+ } else {
+ attr.translateY = translatedThreshold;
}
- });
+ series.group.attr(attr);
+ } else { // run the animation
+
+ attr.scaleY = 1;
+ attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos;
+ series.group.animate(attr, series.options.animation);
- // delete this function to allow it only once
- series.animate = null;
+ // delete this function to allow it only once
+ series.animate = null;
+ }
}
-
},
+
/**
* Remove this series from the chart
*/
remove: function () {
var series = this,
@@ -27026,98 +28163,42 @@
/**
* Set the default options for scatter
*/
defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
lineWidth: 0,
- states: {
- hover: {
- lineWidth: 0
- }
- },
tooltip: {
headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
- pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
- }
+ pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>',
+ followPointer: true
+ },
+ stickyTracking: false
});
/**
* The scatter series class
*/
var ScatterSeries = extendClass(Series, {
type: 'scatter',
sorted: false,
- /**
- * Extend the base Series' translate method by adding shape type and
- * arguments for the point trackers
- */
- translate: function () {
- var series = this;
+ requireSorting: false,
+ noSharedTooltip: true,
+ trackerGroups: ['markerGroup'],
- Series.prototype.translate.apply(series);
-
- each(series.points, function (point) {
- point.shapeType = 'circle';
- point.shapeArgs = {
- x: point.plotX,
- y: point.plotY,
- r: series.chart.options.tooltip.snap
- };
- });
- },
-
- /**
- * Add tracking event listener to the series group, so the point graphics
- * themselves act as trackers
- */
- drawTracker: function () {
- var series = this,
- cursor = series.options.cursor,
- css = cursor && { cursor: cursor },
- points = series.points,
- i = points.length,
- graphic;
-
- // Set an expando property for the point index, used below
- while (i--) {
- graphic = points[i].graphic;
- if (graphic) { // doesn't exist for null points
- graphic.element._i = i;
- }
- }
-
- // Add the event listeners, we need to do this only once
- if (!series._hasTracking) {
- series.markerGroup
- .attr({
- isTracker: true
- })
- .on(hasTouch ? 'touchstart' : 'mouseover', function (e) {
- series.onMouseOver();
- if (e.target._i !== UNDEFINED) { // undefined on graph in scatterchart
- points[e.target._i].onMouseOver();
- }
- })
- .on('mouseout', function () {
- if (!series.options.stickyTracking) {
- series.onMouseOut();
- }
- })
- .css(css);
- } else {
- series._hasTracking = true;
- }
- }
+ drawTracker: ColumnSeries.prototype.drawTracker,
+
+ setTooltipPoints: noop
});
seriesTypes.scatter = ScatterSeries;
/**
* Set the default options for pie
*/
defaultPlotOptions.pie = merge(defaultSeriesOptions, {
borderColor: '#FFFFFF',
borderWidth: 1,
- center: ['50%', '50%'],
+ center: [null, null],
+ clip: false,
colorByPoint: true, // always true for pies
dataLabels: {
// align: null,
// connectorWidth: 1,
// connectorColor: point.color,
@@ -27128,21 +28209,26 @@
return this.point.name;
}
// softConnector: true,
//y: 0
},
+ ignoreHiddenPoint: true,
//innerSize: 0,
legendType: 'point',
marker: null, // point options are specified in the base options
- size: '75%',
+ size: null,
showInLegend: false,
slicedOffset: 10,
states: {
hover: {
brightness: 0.1,
shadow: false
}
+ },
+ stickyTracking: false,
+ tooltip: {
+ followPointer: true
}
});
/**
* Extended point object for pies
@@ -27156,10 +28242,15 @@
Point.prototype.init.apply(this, arguments);
var point = this,
toggleSlice;
+ // Disallow negative values (#1530)
+ if (point.y < 0) {
+ point.y = null;
+ }
+
//visible: options.visible !== false,
extend(point, {
visible: point.visible !== false,
name: pick(point.name, 'Slice')
});
@@ -27181,34 +28272,25 @@
*/
setVisible: function (vis) {
var point = this,
series = point.series,
chart = series.chart,
- tracker = point.tracker,
- dataLabel = point.dataLabel,
- connector = point.connector,
- shadowGroup = point.shadowGroup,
method;
// if called without an argument, toggle visibility
- point.visible = vis = vis === UNDEFINED ? !point.visible : vis;
-
+ point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis;
+ series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
+
method = vis ? 'show' : 'hide';
- point.group[method]();
- if (tracker) {
- tracker[method]();
- }
- if (dataLabel) {
- dataLabel[method]();
- }
- if (connector) {
- connector[method]();
- }
- if (shadowGroup) {
- shadowGroup[method]();
- }
+ // Show and hide associated elements
+ each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) {
+ if (point[key]) {
+ point[key][method]();
+ }
+ });
+
if (point.legendItem) {
chart.legend.colorizeItem(point, vis);
}
// Handle ignore hidden slices
@@ -27225,26 +28307,28 @@
*/
slice: function (sliced, redraw, animation) {
var point = this,
series = point.series,
chart = series.chart,
- slicedTranslation = point.slicedTranslation,
translation;
setAnimation(animation, chart);
// redraw is true by default
redraw = pick(redraw, true);
// if called without an argument, toggle
- sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;
+ point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
+ series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
- translation = {
- translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
- translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
+ translation = sliced ? point.slicedTranslation : {
+ translateX: 0,
+ translateY: 0
};
- point.group.animate(translation);
+
+ point.graphic.animate(translation);
+
if (point.shadowGroup) {
point.shadowGroup.animate(translation);
}
}
@@ -27255,56 +28339,57 @@
*/
var PieSeries = {
type: 'pie',
isCartesian: false,
pointClass: PiePoint,
+ requireSorting: false,
+ noSharedTooltip: true,
+ trackerGroups: ['group', 'dataLabelsGroup'],
pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
stroke: 'borderColor',
'stroke-width': 'borderWidth',
fill: 'color'
},
/**
* Pies have one color each point
*/
- getColor: function () {
- // record first color for use in setData
- this.initialColor = this.chart.counters.color;
- },
+ getColor: noop,
/**
* Animate the pies in
*/
- animate: function () {
+ animate: function (init) {
var series = this,
- points = series.points;
+ points = series.points,
+ startAngleRad = series.startAngleRad;
- each(points, function (point) {
- var graphic = point.graphic,
- args = point.shapeArgs,
- up = -mathPI / 2;
+ if (!init) {
+ each(points, function (point) {
+ var graphic = point.graphic,
+ args = point.shapeArgs;
- if (graphic) {
- // start values
- graphic.attr({
- r: 0,
- start: up,
- end: up
- });
+ if (graphic) {
+ // start values
+ graphic.attr({
+ r: series.center[3] / 2, // animate from inner radius (#779)
+ start: startAngleRad,
+ end: startAngleRad
+ });
- // animate
- graphic.animate({
- r: args.r,
- start: args.start,
- end: args.end
- }, series.options.animation);
- }
- });
+ // animate
+ graphic.animate({
+ r: args.r,
+ start: args.start,
+ end: args.end
+ }, series.options.animation);
+ }
+ });
- // delete this function to allow it only once
- series.animate = null;
-
+ // delete this function to allow it only once
+ series.animate = null;
+ }
},
/**
* Extend the basic setData method by running processData and generatePoints immediately,
* in order to access the points from the legend.
@@ -27324,48 +28409,50 @@
*/
getCenter: function () {
var options = this.options,
chart = this.chart,
- plotWidth = chart.plotWidth,
- plotHeight = chart.plotHeight,
- positions = options.center.concat([options.size, options.innerSize || 0]),
+ slicingRoom = 2 * (options.slicedOffset || 0),
+ handleSlicingRoom,
+ plotWidth = chart.plotWidth - 2 * slicingRoom,
+ plotHeight = chart.plotHeight - 2 * slicingRoom,
+ centerOption = options.center,
+ positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
smallestSize = mathMin(plotWidth, plotHeight),
- isPercent;
+ isPercent;
return map(positions, function (length, i) {
-
isPercent = /%$/.test(length);
- return isPercent ?
+ handleSlicingRoom = i < 2 || (i === 2 && isPercent);
+ return (isPercent ?
// i == 0: centerX, relative to width
// i == 1: centerY, relative to height
// i == 2: size, relative to smallestSize
// i == 4: innerSize, relative to smallestSize
[plotWidth, plotHeight, smallestSize, smallestSize][i] *
pInt(length) / 100 :
- length;
+ length) + (handleSlicingRoom ? slicingRoom : 0);
});
},
/**
* Do translation for pie slices
*/
- translate: function () {
+ translate: function (positions) {
this.generatePoints();
var total = 0,
series = this,
- cumulative = -0.25, // start at top
+ cumulative = 0,
precision = 1000, // issue #172
options = series.options,
slicedOffset = options.slicedOffset,
connectorOffset = slicedOffset + options.borderWidth,
- positions,
- chart = series.chart,
start,
end,
angle,
+ startAngleRad = series.startAngleRad = mathPI / 180 * ((options.startAngle || 0) % 360 - 90),
points = series.points,
circ = 2 * mathPI,
fraction,
radiusX, // the x component of the radius vector for a given point
radiusY,
@@ -27373,12 +28460,16 @@
ignoreHiddenPoint = options.ignoreHiddenPoint,
i,
len = points.length,
point;
- // get positions - either an integer or a percentage string must be given
- series.center = positions = series.getCenter();
+ // Get positions - either an integer or a percentage string must be given.
+ // If positions are passed as a parameter, we're in a recursive loop for adjusting
+ // space for data labels.
+ if (!positions) {
+ series.center = positions = series.getCenter();
+ }
// utility for getting the x value from a given y, used for anticollision logic in data labels
series.getX = function (y, left) {
angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
@@ -27399,15 +28490,15 @@
point = points[i];
// set start and end angle
fraction = total ? point.y / total : 0;
- start = mathRound(cumulative * circ * precision) / precision;
+ start = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
if (!ignoreHiddenPoint || point.visible) {
cumulative += fraction;
}
- end = mathRound(cumulative * circ * precision) / precision;
+ end = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
// set the shape
point.shapeType = 'arc';
point.shapeArgs = {
x: positions[0],
@@ -27418,34 +28509,41 @@
end: end
};
// center for the sliced out slice
angle = (end + start) / 2;
- point.slicedTranslation = map([
- mathCos(angle) * slicedOffset + chart.plotLeft,
- mathSin(angle) * slicedOffset + chart.plotTop
- ], mathRound);
+ if (angle > 0.75 * circ) {
+ angle -= 2 * mathPI;
+ }
+ point.slicedTranslation = {
+ translateX: mathRound(mathCos(angle) * slicedOffset),
+ translateY: mathRound(mathSin(angle) * slicedOffset)
+ };
// set the anchor point for tooltips
radiusX = mathCos(angle) * positions[2] / 2;
radiusY = mathSin(angle) * positions[2] / 2;
point.tooltipPos = [
positions[0] + radiusX * 0.7,
positions[1] + radiusY * 0.7
];
+
+ point.half = angle < circ / 4 ? 0 : 1;
+ point.angle = angle;
// set the anchor point for data labels
+ connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678
point.labelPos = [
positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
positions[0] + radiusX, // landing point for connector
positions[1] + radiusY, // a/a
labelDistance < 0 ? // alignment
'center' :
- angle < circ / 4 ? 'left' : 'right', // alignment
+ point.half ? 'right' : 'left', // alignment
angle // center angle
];
// API properties
point.percentage = fraction * 100;
@@ -27455,92 +28553,68 @@
this.setTooltipPoints();
},
- /**
- * Render the slices
- */
- render: function () {
- var series = this;
+ drawGraph: null,
- // cache attributes for shapes
- series.getAttribs();
-
- this.drawPoints();
-
- // draw the mouse tracking area
- if (series.options.enableMouseTracking !== false) {
- series.drawTracker();
- }
-
- this.drawDataLabels();
-
- if (series.options.animation && series.animate) {
- series.animate();
- }
-
- // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
- series.isDirty = false; // means data is in accordance with what you see
- },
-
/**
* Draw the data points
*/
drawPoints: function () {
var series = this,
chart = series.chart,
renderer = chart.renderer,
groupTranslation,
//center,
graphic,
- group,
+ //group,
shadow = series.options.shadow,
shadowGroup,
shapeArgs;
+ if (shadow && !series.shadowGroup) {
+ series.shadowGroup = renderer.g('shadow')
+ .add(series.group);
+ }
+
// draw the slices
each(series.points, function (point) {
graphic = point.graphic;
shapeArgs = point.shapeArgs;
- group = point.group;
shadowGroup = point.shadowGroup;
// put the shadow behind all points
if (shadow && !shadowGroup) {
shadowGroup = point.shadowGroup = renderer.g('shadow')
- .attr({ zIndex: 4 })
- .add();
+ .add(series.shadowGroup);
}
- // create the group the first time
- if (!group) {
- group = point.group = renderer.g('point')
- .attr({ zIndex: 5 })
- .add();
- }
-
// if the point is sliced, use special translation, else use plot area traslation
- groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
- group.translate(groupTranslation[0], groupTranslation[1]);
+ groupTranslation = point.sliced ? point.slicedTranslation : {
+ translateX: 0,
+ translateY: 0
+ };
+
+ //group.translate(groupTranslation[0], groupTranslation[1]);
if (shadowGroup) {
- shadowGroup.translate(groupTranslation[0], groupTranslation[1]);
+ shadowGroup.attr(groupTranslation);
}
// draw the slice
if (graphic) {
- graphic.animate(shapeArgs);
+ graphic.animate(extend(shapeArgs, groupTranslation));
} else {
point.graphic = graphic = renderer.arc(shapeArgs)
.setRadialReference(series.center)
- .attr(extend(
- point.pointAttr[NORMAL_STATE],
- { 'stroke-linejoin': 'round' }
- ))
- .add(point.group)
- .shadow(shadow, shadowGroup);
-
+ .attr(
+ point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]
+ )
+ .attr({ 'stroke-linejoin': 'round' })
+ .attr(groupTranslation)
+ .add(series.group)
+ .shadow(shadow, shadowGroup);
}
// detect point specific visibility
if (point.visible === false) {
point.setVisible(false);
@@ -27559,32 +28633,43 @@
point,
chart = series.chart,
options = series.options.dataLabels,
connectorPadding = pick(options.connectorPadding, 10),
connectorWidth = pick(options.connectorWidth, 1),
+ plotWidth = chart.plotWidth,
+ plotHeight = chart.plotHeight,
connector,
connectorPath,
softConnector = pick(options.softConnector, true),
distanceOption = options.distance,
seriesCenter = series.center,
radius = seriesCenter[2] / 2,
centerY = seriesCenter[1],
outside = distanceOption > 0,
dataLabel,
+ dataLabelWidth,
labelPos,
labelHeight,
halves = [// divide the points into right and left halves for anti collision
[], // right
[] // left
],
x,
y,
visibility,
rankArr,
- sort,
- i = 2,
- j;
+ i,
+ j,
+ overflow = [0, 0, 0, 0], // top, right, bottom, left
+ sort = function (a, b) {
+ return b.y - a.y;
+ },
+ sortByAngle = function (points, sign) {
+ points.sort(function (a, b) {
+ return a.angle !== undefined && (b.angle - a.angle) * sign;
+ });
+ };
// get out if not enabled
if (!options.enabled && !series._hasPointLabels) {
return;
}
@@ -27593,61 +28678,62 @@
Series.prototype.drawDataLabels.apply(series);
// arrange points for detection collision
each(data, function (point) {
if (point.dataLabel) { // it may have been cancelled in the base method (#407)
- halves[
- point.labelPos[7] < mathPI / 2 ? 0 : 1
- ].push(point);
+ halves[point.half].push(point);
}
});
- halves[1].reverse();
- // define the sorting algorithm
- sort = function (a, b) {
- return b.y - a.y;
- };
-
// assume equal label heights
- labelHeight = halves[0][0] && halves[0][0].dataLabel && (halves[0][0].dataLabel.getBBox().height || 21); // 21 is for #968
+ i = 0;
+ while (!labelHeight && data[i]) { // #1569
+ labelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968
+ i++;
+ }
/* Loop over the points in each half, starting from the top and bottom
* of the pie to detect overlapping labels.
*/
+ i = 2;
while (i--) {
var slots = [],
slotsLength,
usedSlots = [],
points = halves[i],
pos,
length = points.length,
slotIndex;
+
+ // Sort by angle
+ sortByAngle(points, i - 0.5);
// Only do anti-collision when we are outside the pie and have connectors (#856)
if (distanceOption > 0) {
// build the slots
for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
slots.push(pos);
+
// visualize the slot
/*
var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
slotY = pos + chart.plotTop;
if (!isNaN(slotX)) {
- chart.renderer.rect(slotX, slotY - 7, 100, labelHeight)
+ chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1)
.attr({
'stroke-width': 1,
stroke: 'silver'
})
.add();
chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
.attr({
fill: 'silver'
}).add();
}
- // */
+ */
}
slotsLength = slots.length;
// if there are more values than available slots, remove lowest values
if (length > slotsLength) {
@@ -27742,63 +28828,186 @@
// and botton slice connectors from touching each other on either side
x = options.justify ?
seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :
series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);
- // move or place the data label
- dataLabel
- .attr({
- visibility: visibility,
- align: labelPos[6]
- })[dataLabel.moved ? 'animate' : 'attr']({
- x: x + options.x +
- ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
- y: y + options.y - 10 // 10 is for the baseline (label vs text)
- });
- dataLabel.moved = true;
-
- // draw the connector
- if (outside && connectorWidth) {
+
+ // Record the placement and visibility
+ dataLabel._attr = {
+ visibility: visibility,
+ align: labelPos[6]
+ };
+ dataLabel._pos = {
+ x: x + options.x +
+ ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
+ y: y + options.y - 10 // 10 is for the baseline (label vs text)
+ };
+ dataLabel.connX = x;
+ dataLabel.connY = y;
+
+
+ // Detect overflowing data labels
+ if (this.options.size === null) {
+ dataLabelWidth = dataLabel.width;
+ // Overflow left
+ if (x - dataLabelWidth < connectorPadding) {
+ overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]);
+
+ // Overflow right
+ } else if (x + dataLabelWidth > plotWidth - connectorPadding) {
+ overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]);
+ }
+
+ // Overflow top
+ if (y - labelHeight / 2 < 0) {
+ overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]);
+
+ // Overflow left
+ } else if (y + labelHeight / 2 > plotHeight) {
+ overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]);
+ }
+ }
+ } // for each point
+ } // for each half
+
+ // Do not apply the final placement and draw the connectors until we have verified
+ // that labels are not spilling over.
+ if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) {
+
+ // Place the labels in the final position
+ this.placeDataLabels();
+
+ // Draw the connectors
+ if (outside && connectorWidth) {
+ each(this.points, function (point) {
connector = point.connector;
-
- connectorPath = softConnector ? [
- M,
- x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
- 'C',
- x, y, // first break, next to the label
- 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
- labelPos[2], labelPos[3], // second break
- L,
- labelPos[4], labelPos[5] // base
- ] : [
- M,
- x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
- L,
- labelPos[2], labelPos[3], // second break
- L,
- labelPos[4], labelPos[5] // base
- ];
-
- if (connector) {
- connector.animate({ d: connectorPath });
- connector.attr('visibility', visibility);
-
- } else {
- point.connector = connector = series.chart.renderer.path(connectorPath).attr({
- 'stroke-width': connectorWidth,
- stroke: options.connectorColor || point.color || '#606060',
- visibility: visibility,
- zIndex: 3
- })
- .translate(chart.plotLeft, chart.plotTop)
- .add();
+ labelPos = point.labelPos;
+ dataLabel = point.dataLabel;
+
+ if (dataLabel && dataLabel._pos) {
+ visibility = dataLabel._attr.visibility;
+ x = dataLabel.connX;
+ y = dataLabel.connY;
+ connectorPath = softConnector ? [
+ M,
+ x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
+ 'C',
+ x, y, // first break, next to the label
+ 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
+ labelPos[2], labelPos[3], // second break
+ L,
+ labelPos[4], labelPos[5] // base
+ ] : [
+ M,
+ x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
+ L,
+ labelPos[2], labelPos[3], // second break
+ L,
+ labelPos[4], labelPos[5] // base
+ ];
+
+ if (connector) {
+ connector.animate({ d: connectorPath });
+ connector.attr('visibility', visibility);
+
+ } else {
+ point.connector = connector = series.chart.renderer.path(connectorPath).attr({
+ 'stroke-width': connectorWidth,
+ stroke: options.connectorColor || point.color || '#606060',
+ visibility: visibility
+ })
+ .add(series.group);
+ }
+ } else if (connector) {
+ point.connector = connector.destroy();
}
+ });
+ }
+ }
+ },
+
+ /**
+ * Verify whether the data labels are allowed to draw, or we should run more translation and data
+ * label positioning to keep them inside the plot area. Returns true when data labels are ready
+ * to draw.
+ */
+ verifyDataLabelOverflow: function (overflow) {
+
+ var center = this.center,
+ options = this.options,
+ centerOption = options.center,
+ minSize = options.minSize || 80,
+ newSize = minSize,
+ ret;
+
+ // Handle horizontal size and center
+ if (centerOption[0] !== null) { // Fixed center
+ newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize);
+
+ } else { // Auto center
+ newSize = mathMax(
+ center[2] - overflow[1] - overflow[3], // horizontal overflow
+ minSize
+ );
+ center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center
+ }
+
+ // Handle vertical size and center
+ if (centerOption[1] !== null) { // Fixed center
+ newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize);
+
+ } else { // Auto center
+ newSize = mathMax(
+ mathMin(
+ newSize,
+ center[2] - overflow[0] - overflow[2] // vertical overflow
+ ),
+ minSize
+ );
+ center[1] += (overflow[0] - overflow[2]) / 2; // vertical center
+ }
+
+ // If the size must be decreased, we need to run translate and drawDataLabels again
+ if (newSize < center[2]) {
+ center[2] = newSize;
+ this.translate(center);
+ each(this.points, function (point) {
+ if (point.dataLabel) {
+ point.dataLabel._pos = null; // reset
}
- }
+ });
+ this.drawDataLabels();
+
+ // Else, return true to indicate that the pie and its labels is within the plot area
+ } else {
+ ret = true;
}
+ return ret;
},
+ /**
+ * Perform the final placement of the data labels after we have verified that they
+ * fall within the plot area.
+ */
+ placeDataLabels: function () {
+ each(this.points, function (point) {
+ var dataLabel = point.dataLabel,
+ _pos;
+
+ if (dataLabel) {
+ _pos = dataLabel._pos;
+ if (_pos) {
+ dataLabel.attr(dataLabel._attr);
+ dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
+ dataLabel.moved = true;
+ } else if (dataLabel) {
+ dataLabel.attr({ y: -999 });
+ }
+ }
+ });
+ },
+
alignDataLabel: noop,
/**
* Draw point specific tracker objects. Inherit directly from column series.
*/
@@ -27810,11 +29019,11 @@
drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
/**
* Pies don't have point marker symbols
*/
- getSymbol: function () {}
+ getSymbol: noop
};
PieSeries = extendClass(Series, PieSeries);
seriesTypes.pie = PieSeries;
@@ -27822,28 +29031,32 @@
// global variables
extend(Highcharts, {
// Constructors
Axis: Axis,
- CanVGRenderer: CanVGRenderer,
Chart: Chart,
Color: Color,
Legend: Legend,
- MouseTracker: MouseTracker,
+ Pointer: Pointer,
Point: Point,
Tick: Tick,
Tooltip: Tooltip,
Renderer: Renderer,
Series: Series,
+ SVGElement: SVGElement,
SVGRenderer: SVGRenderer,
- VMLRenderer: VMLRenderer,
// Various
+ arrayMin: arrayMin,
+ arrayMax: arrayMax,
+ charts: charts,
dateFormat: dateFormat,
+ format: format,
pathAnim: pathAnim,
getOptions: getOptions,
hasBidiBug: hasBidiBug,
+ isTouchDevice: isTouchDevice,
numberFormat: numberFormat,
seriesTypes: seriesTypes,
setOptions: setOptions,
addEvent: addEvent,
removeEvent: removeEvent,
@@ -27860,751 +29073,14 @@
pInt: pInt,
wrap: wrap,
svg: hasSVG,
canvas: useCanVG,
vml: !hasSVG && !useCanVG,
- product: 'Highcharts',
- version: '2.3.3'
+ product: PRODUCT,
+ version: VERSION
});
}());
-/**
- * @license Highcharts JS v2.3.3 (2012-10-04)
- * Exporting module
- *
- * (c) 2010-2011 Torstein Hønsi
- *
- * License: www.highcharts.com/license
- */
-
-// JSLint options:
-/*global Highcharts, document, window, Math, setTimeout */
-
-
-(function () { // encapsulate
-
-// create shortcuts
-var HC = Highcharts,
- Chart = HC.Chart,
- addEvent = HC.addEvent,
- removeEvent = HC.removeEvent,
- createElement = HC.createElement,
- discardElement = HC.discardElement,
- css = HC.css,
- merge = HC.merge,
- each = HC.each,
- extend = HC.extend,
- math = Math,
- mathMax = math.max,
- doc = document,
- win = window,
- hasTouch = doc.documentElement.ontouchstart !== undefined,
- M = 'M',
- L = 'L',
- DIV = 'div',
- HIDDEN = 'hidden',
- NONE = 'none',
- PREFIX = 'highcharts-',
- ABSOLUTE = 'absolute',
- PX = 'px',
- UNDEFINED,
- defaultOptions = HC.getOptions();
-
- // Add language
- extend(defaultOptions.lang, {
- downloadPNG: 'Download PNG image',
- downloadJPEG: 'Download JPEG image',
- downloadPDF: 'Download PDF document',
- downloadSVG: 'Download SVG vector image',
- exportButtonTitle: 'Export to raster or vector image',
- printButtonTitle: 'Print the chart'
- });
-
-// Buttons and menus are collected in a separate config option set called 'navigation'.
-// This can be extended later to add control buttons like zoom and pan right click menus.
-defaultOptions.navigation = {
- menuStyle: {
- border: '1px solid #A0A0A0',
- background: '#FFFFFF'
- },
- menuItemStyle: {
- padding: '0 5px',
- background: NONE,
- color: '#303030',
- fontSize: hasTouch ? '14px' : '11px'
- },
- menuItemHoverStyle: {
- background: '#4572A5',
- color: '#FFFFFF'
- },
-
- buttonOptions: {
- align: 'right',
- backgroundColor: {
- linearGradient: [0, 0, 0, 20],
- stops: [
- [0.4, '#F7F7F7'],
- [0.6, '#E3E3E3']
- ]
- },
- borderColor: '#B0B0B0',
- borderRadius: 3,
- borderWidth: 1,
- //enabled: true,
- height: 20,
- hoverBorderColor: '#909090',
- hoverSymbolFill: '#81A7CF',
- hoverSymbolStroke: '#4572A5',
- symbolFill: '#E0E0E0',
- //symbolSize: 12,
- symbolStroke: '#A0A0A0',
- //symbolStrokeWidth: 1,
- symbolX: 11.5,
- symbolY: 10.5,
- verticalAlign: 'top',
- width: 24,
- y: 10
- }
-};
-
-
-
-// Add the export related options
-defaultOptions.exporting = {
- //enabled: true,
- //filename: 'chart',
- type: 'image/png',
- url: 'http://export.highcharts.com/',
- width: 800,
- buttons: {
- exportButton: {
- //enabled: true,
- symbol: 'exportIcon',
- x: -10,
- symbolFill: '#A8BF77',
- hoverSymbolFill: '#768F3E',
- _id: 'exportButton',
- _titleKey: 'exportButtonTitle',
- menuItems: [{
- textKey: 'downloadPNG',
- onclick: function () {
- this.exportChart();
- }
- }, {
- textKey: 'downloadJPEG',
- onclick: function () {
- this.exportChart({
- type: 'image/jpeg'
- });
- }
- }, {
- textKey: 'downloadPDF',
- onclick: function () {
- this.exportChart({
- type: 'application/pdf'
- });
- }
- }, {
- textKey: 'downloadSVG',
- onclick: function () {
- this.exportChart({
- type: 'image/svg+xml'
- });
- }
- }
- // Enable this block to add "View SVG" to the dropdown menu
- /*
- ,{
-
- text: 'View SVG',
- onclick: function () {
- var svg = this.getSVG()
- .replace(/</g, '\n<')
- .replace(/>/g, '>');
-
- doc.body.innerHTML = '<pre>' + svg + '</pre>';
- }
- } // */
- ]
-
- },
- printButton: {
- //enabled: true,
- symbol: 'printIcon',
- x: -36,
- symbolFill: '#B5C9DF',
- hoverSymbolFill: '#779ABF',
- _id: 'printButton',
- _titleKey: 'printButtonTitle',
- onclick: function () {
- this.print();
- }
- }
- }
-};
-
-
-
-extend(Chart.prototype, {
- /**
- * Return an SVG representation of the chart
- *
- * @param additionalOptions {Object} Additional chart options for the generated SVG representation
- */
- getSVG: function (additionalOptions) {
- var chart = this,
- chartCopy,
- sandbox,
- svg,
- seriesOptions,
- options = merge(chart.options, additionalOptions); // copy the options and add extra options
-
- // IE compatibility hack for generating SVG content that it doesn't really understand
- if (!doc.createElementNS) {
- /*jslint unparam: true*//* allow unused parameter ns in function below */
- doc.createElementNS = function (ns, tagName) {
- return doc.createElement(tagName);
- };
- /*jslint unparam: false*/
- }
-
- // create a sandbox where a new chart will be generated
- sandbox = createElement(DIV, null, {
- position: ABSOLUTE,
- top: '-9999em',
- width: chart.chartWidth + PX,
- height: chart.chartHeight + PX
- }, doc.body);
-
- // override some options
- extend(options.chart, {
- renderTo: sandbox,
- forExport: true
- });
- options.exporting.enabled = false; // hide buttons in print
- options.chart.plotBackgroundImage = null; // the converter doesn't handle images
-
- // prepare for replicating the chart
- options.series = [];
- each(chart.series, function (serie) {
- seriesOptions = merge(serie.options, {
- animation: false, // turn off animation
- showCheckbox: false,
- visible: serie.visible
- });
-
- if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
-
- // remove image markers
- if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) {
- seriesOptions.marker.symbol = 'circle';
- }
-
- options.series.push(seriesOptions);
- }
- });
-
- // generate the chart copy
- chartCopy = new Highcharts.Chart(options);
-
- // reflect axis extremes in the export
- each(['xAxis', 'yAxis'], function (axisType) {
- each(chart[axisType], function (axis, i) {
- var axisCopy = chartCopy[axisType][i],
- extremes = axis.getExtremes(),
- userMin = extremes.userMin,
- userMax = extremes.userMax;
-
- if (userMin !== UNDEFINED || userMax !== UNDEFINED) {
- axisCopy.setExtremes(userMin, userMax, true, false);
- }
- });
- });
-
- // get the SVG from the container's innerHTML
- svg = chartCopy.container.innerHTML;
-
- // free up memory
- options = null;
- chartCopy.destroy();
- discardElement(sandbox);
-
- // sanitize
- svg = svg
- .replace(/zIndex="[^"]+"/g, '')
- .replace(/isShadow="[^"]+"/g, '')
- .replace(/symbolName="[^"]+"/g, '')
- .replace(/jQuery[0-9]+="[^"]+"/g, '')
- .replace(/isTracker="[^"]+"/g, '')
- .replace(/url\([^#]+#/g, 'url(#')
- .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
- .replace(/ href=/g, ' xlink:href=')
- .replace(/\n/, ' ')
- .replace(/<\/svg>.*?$/, '</svg>') // any HTML added to the container after the SVG (#894)
- /* This fails in IE < 8
- .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
- return s2 +'.'+ s3[0];
- })*/
-
- // Replace HTML entities, issue #347
- .replace(/ /g, '\u00A0') // no-break space
- .replace(/­/g, '\u00AD') // soft hyphen
-
- // IE specific
- .replace(/<IMG /g, '<image ')
- .replace(/height=([^" ]+)/g, 'height="$1"')
- .replace(/width=([^" ]+)/g, 'width="$1"')
- .replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
- .replace(/id=([^" >]+)/g, 'id="$1"')
- .replace(/class=([^" ]+)/g, 'class="$1"')
- .replace(/ transform /g, ' ')
- .replace(/:(path|rect)/g, '$1')
- .replace(/style="([^"]+)"/g, function (s) {
- return s.toLowerCase();
- });
-
- // IE9 beta bugs with innerHTML. Test again with final IE9.
- svg = svg.replace(/(url\(#highcharts-[0-9]+)"/g, '$1')
- .replace(/"/g, "'");
- if (svg.match(/ xmlns="/g).length === 2) {
- svg = svg.replace(/xmlns="[^"]+"/, '');
- }
-
- return svg;
- },
-
- /**
- * Submit the SVG representation of the chart to the server
- * @param {Object} options Exporting options. Possible members are url, type and width.
- * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
- */
- exportChart: function (options, chartOptions) {
- var form,
- chart = this,
- svg = chart.getSVG(merge(chart.options.exporting.chartOptions, chartOptions)); // docs
-
- // merge the options
- options = merge(chart.options.exporting, options);
-
- // create the form
- form = createElement('form', {
- method: 'post',
- action: options.url,
- enctype: 'multipart/form-data'
- }, {
- display: NONE
- }, doc.body);
-
- // add the values
- each(['filename', 'type', 'width', 'svg'], function (name) {
- createElement('input', {
- type: HIDDEN,
- name: name,
- value: {
- filename: options.filename || 'chart',
- type: options.type,
- width: options.width,
- svg: svg
- }[name]
- }, null, form);
- });
-
- // submit
- form.submit();
-
- // clean up
- discardElement(form);
- },
-
- /**
- * Print the chart
- */
- print: function () {
-
- var chart = this,
- container = chart.container,
- origDisplay = [],
- origParent = container.parentNode,
- body = doc.body,
- childNodes = body.childNodes;
-
- if (chart.isPrinting) { // block the button while in printing mode
- return;
- }
-
- chart.isPrinting = true;
-
- // hide all body content
- each(childNodes, function (node, i) {
- if (node.nodeType === 1) {
- origDisplay[i] = node.style.display;
- node.style.display = NONE;
- }
- });
-
- // pull out the chart
- body.appendChild(container);
-
- // print
- win.print();
-
- // allow the browser to prepare before reverting
- setTimeout(function () {
-
- // put the chart back in
- origParent.appendChild(container);
-
- // restore all body content
- each(childNodes, function (node, i) {
- if (node.nodeType === 1) {
- node.style.display = origDisplay[i];
- }
- });
-
- chart.isPrinting = false;
-
- }, 1000);
-
- },
-
- /**
- * Display a popup menu for choosing the export type
- *
- * @param {String} name An identifier for the menu
- * @param {Array} items A collection with text and onclicks for the items
- * @param {Number} x The x position of the opener button
- * @param {Number} y The y position of the opener button
- * @param {Number} width The width of the opener button
- * @param {Number} height The height of the opener button
- */
- contextMenu: function (name, items, x, y, width, height) {
- var chart = this,
- navOptions = chart.options.navigation,
- menuItemStyle = navOptions.menuItemStyle,
- chartWidth = chart.chartWidth,
- chartHeight = chart.chartHeight,
- cacheName = 'cache-' + name,
- menu = chart[cacheName],
- menuPadding = mathMax(width, height), // for mouse leave detection
- boxShadow = '3px 3px 10px #888',
- innerMenu,
- hide,
- menuStyle;
-
- // create the menu only the first time
- if (!menu) {
-
- // create a HTML element above the SVG
- chart[cacheName] = menu = createElement(DIV, {
- className: PREFIX + name
- }, {
- position: ABSOLUTE,
- zIndex: 1000,
- padding: menuPadding + PX
- }, chart.container);
-
- innerMenu = createElement(DIV, null,
- extend({
- MozBoxShadow: boxShadow,
- WebkitBoxShadow: boxShadow,
- boxShadow: boxShadow
- }, navOptions.menuStyle), menu);
-
- // hide on mouse out
- hide = function () {
- css(menu, { display: NONE });
- };
-
- addEvent(menu, 'mouseleave', hide);
-
-
- // create the items
- each(items, function (item) {
- if (item) {
- var div = createElement(DIV, {
- onmouseover: function () {
- css(this, navOptions.menuItemHoverStyle);
- },
- onmouseout: function () {
- css(this, menuItemStyle);
- },
- innerHTML: item.text || chart.options.lang[item.textKey]
- }, extend({
- cursor: 'pointer'
- }, menuItemStyle), innerMenu);
-
- div[hasTouch ? 'ontouchstart' : 'onclick'] = function () {
- hide();
- item.onclick.apply(chart, arguments);
- };
-
- // Keep references to menu divs to be able to destroy them
- chart.exportDivElements.push(div);
- }
- });
-
- // Keep references to menu and innerMenu div to be able to destroy them
- chart.exportDivElements.push(innerMenu, menu);
-
- chart.exportMenuWidth = menu.offsetWidth;
- chart.exportMenuHeight = menu.offsetHeight;
- }
-
- menuStyle = { display: 'block' };
-
- // if outside right, right align it
- if (x + chart.exportMenuWidth > chartWidth) {
- menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
- } else {
- menuStyle.left = (x - menuPadding) + PX;
- }
- // if outside bottom, bottom align it
- if (y + height + chart.exportMenuHeight > chartHeight) {
- menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
- } else {
- menuStyle.top = (y + height - menuPadding) + PX;
- }
-
- css(menu, menuStyle);
- },
-
- /**
- * Add the export button to the chart
- */
- addButton: function (options) {
- var chart = this,
- renderer = chart.renderer,
- btnOptions = merge(chart.options.navigation.buttonOptions, options),
- onclick = btnOptions.onclick,
- menuItems = btnOptions.menuItems,
- buttonWidth = btnOptions.width,
- buttonHeight = btnOptions.height,
- box,
- symbol,
- button,
- borderWidth = btnOptions.borderWidth,
- boxAttr = {
- stroke: btnOptions.borderColor
-
- },
- symbolAttr = {
- stroke: btnOptions.symbolStroke,
- fill: btnOptions.symbolFill
- },
- symbolSize = btnOptions.symbolSize || 12;
-
- // Keeps references to the button elements
- if (!chart.exportDivElements) {
- chart.exportDivElements = [];
- chart.exportSVGElements = [];
- }
-
- if (btnOptions.enabled === false) {
- return;
- }
-
- // element to capture the click
- function revert() {
- symbol.attr(symbolAttr);
- box.attr(boxAttr);
- }
-
- // the box border
- box = renderer.rect(
- 0,
- 0,
- buttonWidth,
- buttonHeight,
- btnOptions.borderRadius,
- borderWidth
- )
- //.translate(buttonLeft, buttonTop) // to allow gradients
- .align(btnOptions, true)
- .attr(extend({
- fill: btnOptions.backgroundColor,
- 'stroke-width': borderWidth,
- zIndex: 19
- }, boxAttr)).add();
-
- // the invisible element to track the clicks
- button = renderer.rect(
- 0,
- 0,
- buttonWidth,
- buttonHeight,
- 0
- )
- .align(btnOptions)
- .attr({
- id: btnOptions._id,
- fill: 'rgba(255, 255, 255, 0.001)',
- title: chart.options.lang[btnOptions._titleKey],
- zIndex: 21
- }).css({
- cursor: 'pointer'
- })
- .on('mouseover', function () {
- symbol.attr({
- stroke: btnOptions.hoverSymbolStroke,
- fill: btnOptions.hoverSymbolFill
- });
- box.attr({
- stroke: btnOptions.hoverBorderColor
- });
- })
- .on('mouseout', revert)
- .on('click', revert)
- .add();
-
- // add the click event
- if (menuItems) {
- onclick = function () {
- revert();
- var bBox = button.getBBox();
- chart.contextMenu('export-menu', menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
- };
- }
- /*addEvent(button.element, 'click', function() {
- onclick.apply(chart, arguments);
- });*/
- button.on('click', function () {
- onclick.apply(chart, arguments);
- });
-
- // the icon
- symbol = renderer.symbol(
- btnOptions.symbol,
- btnOptions.symbolX - (symbolSize / 2),
- btnOptions.symbolY - (symbolSize / 2),
- symbolSize,
- symbolSize
- )
- .align(btnOptions, true)
- .attr(extend(symbolAttr, {
- 'stroke-width': btnOptions.symbolStrokeWidth || 1,
- zIndex: 20
- })).add();
-
- // Keep references to the renderer element so to be able to destroy them later.
- chart.exportSVGElements.push(box, button, symbol);
- },
-
- /**
- * Destroy the buttons.
- */
- destroyExport: function () {
- var i,
- chart = this,
- elem;
-
- // Destroy the extra buttons added
- for (i = 0; i < chart.exportSVGElements.length; i++) {
- elem = chart.exportSVGElements[i];
- // Destroy and null the svg/vml elements
- elem.onclick = elem.ontouchstart = null;
- chart.exportSVGElements[i] = elem.destroy();
- }
-
- // Destroy the divs for the menu
- for (i = 0; i < chart.exportDivElements.length; i++) {
- elem = chart.exportDivElements[i];
-
- // Remove the event handler
- removeEvent(elem, 'mouseleave');
-
- // Remove inline events
- chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;
-
- // Destroy the div by moving to garbage bin
- discardElement(elem);
- }
- }
-});
-
-/**
- * Crisp for 1px stroke width, which is default. In the future, consider a smarter,
- * global function.
- */
-function crisp(arr) {
- var i = arr.length;
- while (i--) {
- if (typeof arr[i] === 'number') {
- arr[i] = Math.round(arr[i]) - 0.5;
- }
- }
- return arr;
-}
-
-// Create the export icon
-HC.Renderer.prototype.symbols.exportIcon = function (x, y, width, height) {
- return crisp([
- M, // the disk
- x, y + width,
- L,
- x + width, y + height,
- x + width, y + height * 0.8,
- x, y + height * 0.8,
- 'Z',
- M, // the arrow
- x + width * 0.5, y + height * 0.8,
- L,
- x + width * 0.8, y + height * 0.4,
- x + width * 0.4, y + height * 0.4,
- x + width * 0.4, y,
- x + width * 0.6, y,
- x + width * 0.6, y + height * 0.4,
- x + width * 0.2, y + height * 0.4,
- 'Z'
- ]);
-};
-// Create the print icon
-HC.Renderer.prototype.symbols.printIcon = function (x, y, width, height) {
- return crisp([
- M, // the printer
- x, y + height * 0.7,
- L,
- x + width, y + height * 0.7,
- x + width, y + height * 0.4,
- x, y + height * 0.4,
- 'Z',
- M, // the upper sheet
- x + width * 0.2, y + height * 0.4,
- L,
- x + width * 0.2, y,
- x + width * 0.8, y,
- x + width * 0.8, y + height * 0.4,
- 'Z',
- M, // the lower sheet
- x + width * 0.2, y + height * 0.7,
- L,
- x, y + height,
- x + width, y + height,
- x + width * 0.8, y + height * 0.7,
- 'Z'
- ]);
-};
-
-
-// Add the buttons on chart load
-Chart.prototype.callbacks.push(function (chart) {
- var n,
- exportingOptions = chart.options.exporting,
- buttons = exportingOptions.buttons;
-
- if (exportingOptions.enabled !== false) {
-
- for (n in buttons) {
- chart.addButton(buttons[n]);
- }
-
- // Destroy the export elements at chart destroy
- addEvent(chart, 'destroy', chart.destroyExport);
- }
-
-});
-
-
-}());
(function() {
this.showWaiting = function(element_id, text, centered) {
var element;
element = $(element_id);
if (element && centered) {
@@ -30509,11 +30985,10 @@
//
//
-
//
;
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
@@ -30522,6 +30997,6 @@
// the compiled file.
//
;
-; TI"required_assets_digest; TI"%6c62dc9f5d15d51a7b16c78353d6f035; FI"
_version; TI"%ac1cd7cf9811f9938e2b8937c60a24e6; F
+; TI"required_assets_digest; TI"%a84f09da6977aaf09ef5ef8e3fa8c3e6; FI"
_version; TI"%ac1cd7cf9811f9938e2b8937c60a24e6; F
\ No newline at end of file