/**
* @license Highcharts JS v7.0.3 (2019-02-06)
* Old IE (v6, v7, v8) module for Highcharts v6+.
*
* (c) 2010-2019 Highsoft AS
* Author: Torstein Honsi
*
* License: www.highcharts.com/license
*/
'use strict';
(function (factory) {
if (typeof module === 'object' && module.exports) {
factory['default'] = factory;
module.exports = factory;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return factory;
});
} else {
factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
}
}(function (Highcharts) {
(function (H) {
/* *
* (c) 2010-2019 Torstein Honsi
*
* Support for old IE browsers (6, 7 and 8) in Highcharts v6+.
*
* License: www.highcharts.com/license
*/
var VMLRenderer,
VMLRendererExtension,
VMLElement,
Chart = H.Chart,
createElement = H.createElement,
css = H.css,
defined = H.defined,
deg2rad = H.deg2rad,
discardElement = H.discardElement,
doc = H.doc,
erase = H.erase,
extend = H.extend,
extendClass = H.extendClass,
isArray = H.isArray,
isNumber = H.isNumber,
isObject = H.isObject,
merge = H.merge,
noop = H.noop,
pick = H.pick,
pInt = H.pInt,
svg = H.svg,
SVGElement = H.SVGElement,
SVGRenderer = H.SVGRenderer,
win = H.win;
/**
* Path to the pattern image required by VML browsers in order to
* draw radial gradients.
*
* @type {string}
* @default http://code.highcharts.com/{version}/gfx/vml-radial-gradient.png
* @since 2.3.0
* @apioption global.VMLRadialGradientURL
*/
H.getOptions().global.VMLRadialGradientURL =
'http://code.highcharts.com/7.0.3/gfx/vml-radial-gradient.png';
// Utilites
if (doc && !doc.defaultView) {
H.getStyle = function (el, prop) {
var val,
alias = { width: 'clientWidth', height: 'clientHeight' }[prop];
if (el.style[prop]) {
return H.pInt(el.style[prop]);
}
if (prop === 'opacity') {
prop = 'filter';
}
// Getting the rendered width and height
if (alias) {
el.style.zoom = 1;
return Math.max(el[alias] - 2 * H.getStyle(el, 'padding'), 0);
}
val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b) {
return b.toUpperCase();
})];
if (prop === 'filter') {
val = val.replace(
/alpha\(opacity=([0-9]+)\)/,
function (a, b) {
return b / 100;
}
);
}
return val === '' ? 1 : H.pInt(val);
};
}
if (!svg) {
// Prevent wrapping from creating false offsetWidths in export in legacy IE.
// This applies only to charts for export, where IE runs the SVGRenderer
// instead of the VMLRenderer
// (#1079, #1063)
H.addEvent(SVGElement, 'afterInit', function () {
if (this.element.nodeName === 'text') {
this.css({
position: 'absolute'
});
}
});
/**
* Old IE override for pointer normalize, adds chartX and chartY to event
* arguments.
*
* @ignore
* @function Highcharts.Pointer#normalize
*
* @param {global.Event} e
*
* @param {boolean} [chartPosition=false]
*/
H.Pointer.prototype.normalize = function (e, chartPosition) {
e = e || win.event;
if (!e.target) {
e.target = e.srcElement;
}
// Get mouse position
if (!chartPosition) {
this.chartPosition = chartPosition = H.offset(this.chart.container);
}
return H.extend(e, {
// #2005, #2129: the second case is for IE10 quirks mode within
// framesets
chartX: Math.round(Math.max(e.x, e.clientX - chartPosition.left)),
chartY: Math.round(e.y)
});
};
/**
* Further sanitize the mock-SVG that is generated when exporting charts in
* oldIE.
*
* @private
* @function Highcharts.Chart#ieSanitizeSVG
*/
Chart.prototype.ieSanitizeSVG = function (svg) {
svg = svg
.replace(//g, '<$1title>')
.replace(/height=([^" ]+)/g, 'height="$1"')
.replace(/width=([^" ]+)/g, 'width="$1"')
.replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
.replace(/ id=([^" >]+)/g, ' id="$1"') // #4003
.replace(/class=([^" >]+)/g, 'class="$1"')
.replace(/ transform /g, ' ')
.replace(/:(path|rect)/g, '$1')
.replace(/style="([^"]+)"/g, function (s) {
return s.toLowerCase();
});
return svg;
};
/**
* VML namespaces can't be added until after complete. Listening
* for Perini's doScroll hack is not enough.
*
* @private
* @function Highcharts.Chart#isReadyToRender
*/
Chart.prototype.isReadyToRender = function () {
var chart = this;
// Note: win == win.top is required
if (
!svg &&
(
win == win.top && // eslint-disable-line eqeqeq
doc.readyState !== 'complete'
)
) {
doc.attachEvent('onreadystatechange', function () {
doc.detachEvent('onreadystatechange', chart.firstRender);
if (doc.readyState === 'complete') {
chart.firstRender();
}
});
return false;
}
return true;
};
// IE compatibility hack for generating SVG content that it doesn't really
// understand. Used by the exporting module.
if (!doc.createElementNS) {
doc.createElementNS = function (ns, tagName) {
return doc.createElement(tagName);
};
}
/**
* Old IE polyfill for addEventListener, called from inside the addEvent
* function.
*
* @private
* @function Highcharts.addEventListenerPolyfill
*
* @param {string} type
*
* @param {Function} fn
*/
H.addEventListenerPolyfill = function (type, fn) {
var el = this;
function wrappedFn(e) {
e.target = e.srcElement || win; // #2820
fn.call(el, e);
}
if (el.attachEvent) {
if (!el.hcEventsIE) {
el.hcEventsIE = {};
}
// unique function string (#6746)
if (!fn.hcKey) {
fn.hcKey = H.uniqueKey();
}
// Link wrapped fn with original fn, so we can get this in
// removeEvent
el.hcEventsIE[fn.hcKey] = wrappedFn;
el.attachEvent('on' + type, wrappedFn);
}
};
/**
* @private
* @function Highcharts.removeEventListenerPolyfill
*
* @param {string} type
*
* @param {Function} fn
*/
H.removeEventListenerPolyfill = function (type, fn) {
if (this.detachEvent) {
fn = this.hcEventsIE[fn.hcKey];
this.detachEvent('on' + type, fn);
}
};
/**
* The VML element wrapper.
*
* @private
* @class
* @name Highcharts.VMLElement
*
* @augments Highcharts.SVGElement
*/
VMLElement = {
docMode8: doc && doc.documentMode === 8,
/**
* Initialize a new VML element wrapper. It builds the markup as a
* string to minimize DOM traffic.
*
* @function Highcharts.VMLElement#init
*
* @param {object} renderer
*
* @param {object} nodeName
*/
init: function (renderer, nodeName) {
var wrapper = this,
markup = ['<', nodeName, ' filled="f" stroked="f"'],
style = ['position: ', 'absolute', ';'],
isDiv = nodeName === 'div';
// divs and shapes need size
if (nodeName === 'shape' || isDiv) {
style.push('left:0;top:0;width:1px;height:1px;');
}
style.push('visibility: ', isDiv ? 'hidden' : 'visible');
markup.push(' style="', style.join(''), '"/>');
// create element with default attributes and style
if (nodeName) {
markup = isDiv || nodeName === 'span' || nodeName === 'img' ?
markup.join('') :
renderer.prepVML(markup);
wrapper.element = createElement(markup);
}
wrapper.renderer = renderer;
},
/**
* Add the node to the given parent
*
* @function Highcharts.VMLElement
*
* @param {object} parent
*/
add: function (parent) {
var wrapper = this,
renderer = wrapper.renderer,
element = wrapper.element,
box = renderer.box,
inverted = parent && parent.inverted,
// get the parent node
parentNode = parent ?
parent.element || parent :
box;
if (parent) {
this.parentGroup = parent;
}
// if the parent group is inverted, apply inversion on all children
if (inverted) { // only on groups
renderer.invertChild(element, parentNode);
}
// append it
parentNode.appendChild(element);
// 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
if (wrapper.onAdd) {
wrapper.onAdd();
}
// IE8 Standards can't set the class name before the element is
// appended
if (this.className) {
this.attr('class', this.className);
}
return wrapper;
},
/**
* VML always uses htmlUpdateTransform
*
* @function Highcharts.VMLElement#updateTransform
*/
updateTransform: SVGElement.prototype.htmlUpdateTransform,
/**
* Set the rotation of a span with oldIE's filter
*
* @function Highcharts.VMLElement#setSpanRotation
*/
setSpanRotation: function () {
// Adjust for alignment and rotation. Rotation of useHTML content is
// not yet implemented but it can probably be implemented for
// Firefox 3.5+ on user request. FF3.5+ has support for CSS3
// transform. The getBBox method also needs to be updated to
// compensate for the rotation, like it currently does for SVG.
// Test case: https://jsfiddle.net/highcharts/Ybt44/
var rotation = this.rotation,
costheta = Math.cos(rotation * deg2rad),
sintheta = Math.sin(rotation * deg2rad);
css(this.element, {
filter: rotation ? [
'progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
', sizingMethod=\'auto expand\')'
].join('') : 'none'
});
},
/**
* Get the positioning correction for the span after rotating.
*
* @function Highcharts.VMLElement#getSpanCorrection
*/
getSpanCorrection: function (
width,
baseline,
alignCorrection,
rotation,
align
) {
var costheta = rotation ? Math.cos(rotation * deg2rad) : 1,
sintheta = rotation ? Math.sin(rotation * deg2rad) : 0,
height = pick(this.elemHeight, this.element.offsetHeight),
quad,
nonLeft = align && align !== 'left';
// correct x and y
this.xCorr = costheta < 0 && -width;
this.yCorr = sintheta < 0 && -height;
// correct for baseline and corners spilling out after rotation
quad = costheta * sintheta < 0;
this.xCorr += (
sintheta *
baseline *
(quad ? 1 - alignCorrection : alignCorrection)
);
this.yCorr -= (
costheta *
baseline *
(rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1)
);
// correct for the length/height of the text
if (nonLeft) {
this.xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
if (rotation) {
this.yCorr -= (
height *
alignCorrection *
(sintheta < 0 ? -1 : 1)
);
}
css(this.element, {
textAlign: align
});
}
},
/**
* Converts a subset of an SVG path definition to its VML counterpart.
* Takes an array as the parameter and returns a string.
*
* @function Highcharts.VMLElement#pathToVML
*/
pathToVML: function (value) {
// convert paths
var i = value.length,
path = [];
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
if (isNumber(value[i])) {
path[i] = Math.round(value[i] * 10) - 5;
} else if (value[i] === 'Z') { // close the path
path[i] = 'x';
} else {
path[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 or add 1 from the end X and Y positions.
// #186, #760, #1371, #1410.
if (
value.isArc &&
(value[i] === 'wa' || value[i] === 'at')
) {
// Start and end X
if (path[i + 5] === path[i + 7]) {
path[i + 7] += value[i + 7] > value[i + 5] ? 1 : -1;
}
// Start and end Y
if (path[i + 6] === path[i + 8]) {
path[i + 8] += value[i + 8] > value[i + 6] ? 1 : -1;
}
}
}
}
return path.join(' ') || 'x';
},
/**
* Set the element's clipping to a predefined rectangle
*
* @function Highcharts.VMLElement#clip
*
* @param {object} clipRect
*/
clip: function (clipRect) {
var wrapper = this,
clipMembers,
cssRet;
if (clipRect) {
clipMembers = clipRect.members;
// Ensure unique list of elements (#1258)
erase(clipMembers, wrapper);
clipMembers.push(wrapper);
wrapper.destroyClip = function () {
erase(clipMembers, wrapper);
};
cssRet = clipRect.getCSS(wrapper);
} else {
if (wrapper.destroyClip) {
wrapper.destroyClip();
}
cssRet = {
clip: wrapper.docMode8 ? 'inherit' : 'rect(auto)'
}; // #1214
}
return wrapper.css(cssRet);
},
/**
* Set styles for the element
*
* @function Highcharts.VMLElement#css
*
* @param {Highcharts.SVGAttributes} styles
*/
css: SVGElement.prototype.htmlCss,
/**
* Removes a child either by removeChild or move to garbageBin.
* Issue 490; in VML removeChild results in Orphaned nodes according to
* sIEve, discardElement does not.
*
* @function Highcharts.VMLElement#safeRemoveChild
*/
safeRemoveChild: function (element) {
// discardElement will detach the node from its parent before
// attaching it to the garbage bin. Therefore it is important that
// the node is attached and have parent.
if (element.parentNode) {
discardElement(element);
}
},
/**
* Extend element.destroy by removing it from the clip members array
*
* @function Highcharts.VMLElement#destroy
*/
destroy: function () {
if (this.destroyClip) {
this.destroyClip();
}
return SVGElement.prototype.destroy.apply(this);
},
/**
* Add an event listener. VML override for normalizing event parameters.
*
* @function Highcharts.VMLElement#on
*
* @param {string} eventType
*
* @param {Function} handler
*/
on: function (eventType, handler) {
// simplest possible event model for internal use
this.element['on' + eventType] = function () {
var evt = win.event;
evt.target = evt.srcElement;
handler(evt);
};
return this;
},
/**
* In stacked columns, cut off the shadows so that they don't overlap
*
* @function Highcharts.VMLElement#cutOffPath
*/
cutOffPath: function (path, length) {
var len;
// The extra comma tricks the trailing comma remover in
// "gulp scripts" task
path = path.split(/[ ,]/);
len = path.length;
if (len === 9 || len === 11) {
path[len - 4] = path[len - 2] =
pInt(path[len - 2]) - 10 * length;
}
return path.join(' ');
},
/**
* Apply a drop shadow by copying elements and giving them different
* strokes.
*
* @function Highcharts.VMLElement#shadow
*
* @param {boolean|Highcharts.ShadowOptionsObject} shadowOptions
*
* @param {boolean} group
*
* @param {boolean} cutOff
*/
shadow: function (shadowOptions, group, cutOff) {
var shadows = [],
i,
element = this.element,
renderer = this.renderer,
shadow,
elemStyle = element.style,
markup,
path = element.path,
strokeWidth,
modifiedPath,
shadowWidth,
shadowElementOpacity;
// some times empty paths are not strings
if (path && typeof path.value !== 'string') {
path = 'x';
}
modifiedPath = path;
if (shadowOptions) {
shadowWidth = pick(shadowOptions.width, 3);
shadowElementOpacity =
(shadowOptions.opacity || 0.15) / shadowWidth;
for (i = 1; i <= 3; i++) {
strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
// Cut off shadows for stacked column items
if (cutOff) {
modifiedPath = this.cutOffPath(
path.value,
strokeWidth + 0.5
);
}
markup = [
''
];
shadow = createElement(
renderer.prepVML(markup),
null, {
left: pInt(elemStyle.left) +
pick(shadowOptions.offsetX, 1),
top: pInt(elemStyle.top) +
pick(shadowOptions.offsetY, 1)
}
);
if (cutOff) {
shadow.cutOff = strokeWidth + 1;
}
// apply the opacity
markup = [
''];
createElement(renderer.prepVML(markup), null, null, shadow);
// insert it
if (group) {
group.element.appendChild(shadow);
} else {
element.parentNode.insertBefore(shadow, element);
}
// record it
shadows.push(shadow);
}
this.shadows = shadows;
}
return this;
},
updateShadows: noop, // Used in SVG only
setAttr: function (key, value) {
if (this.docMode8) { // IE8 setAttribute bug
this.element[key] = value;
} else {
this.element.setAttribute(key, value);
}
},
getAttr: function (key) {
if (this.docMode8) { // IE8 setAttribute bug
return this.element[key];
}
return this.element.getAttribute(key);
},
classSetter: function (value) {
// IE8 Standards mode has problems retrieving the className unless
// set like this. IE8 Standards can't set the class name before the
// element is appended.
(this.added ? this.element : this).className = value;
},
dashstyleSetter: function (value, key, element) {
var strokeElem = element.getElementsByTagName('stroke')[0] ||
createElement(
this.renderer.prepVML(['']),
null,
null,
element
);
strokeElem[key] = value || 'solid';
// Because changing stroke-width will change the dash length and
// cause an epileptic effect
this[key] = value;
},
dSetter: function (value, key, element) {
var i,
shadows = this.shadows;
value = value || [];
// Used in getter for animation
this.d = value.join && value.join(' ');
element.path = value = this.pathToVML(value);
// update shadows
if (shadows) {
i = shadows.length;
while (i--) {
shadows[i].path = shadows[i].cutOff ?
this.cutOffPath(value, shadows[i].cutOff) :
value;
}
}
this.setAttr(key, value);
},
fillSetter: function (value, key, element) {
var nodeName = element.nodeName;
if (nodeName === 'SPAN') { // text color
element.style.color = value;
} else if (nodeName !== 'IMG') { // #1336
element.filled = value !== 'none';
this.setAttr(
'fillcolor',
this.renderer.color(value, element, key, this)
);
}
},
'fill-opacitySetter': function (value, key, element) {
createElement(
this.renderer.prepVML(
['<', key.split('-')[0], ' opacity="', value, '"/>']
),
null,
null,
element
);
},
// Don't bother - animation is too slow and filters introduce artifacts
opacitySetter: noop,
rotationSetter: function (value, key, element) {
var style = element.style;
this[key] = style[key] = value; // style is for #1873
// Correction for the 1x1 size of the shape container. Used in gauge
// needles.
style.left = -Math.round(Math.sin(value * deg2rad) + 1) + 'px';
style.top = Math.round(Math.cos(value * deg2rad)) + 'px';
},
strokeSetter: function (value, key, element) {
this.setAttr(
'strokecolor',
this.renderer.color(value, element, key, this)
);
},
'stroke-widthSetter': function (value, key, element) {
element.stroked = !!value; // VML "stroked" attribute
this[key] = value; // used in getter, issue #113
if (isNumber(value)) {
value += 'px';
}
this.setAttr('strokeweight', value);
},
titleSetter: function (value, key) {
this.setAttr(key, value);
},
visibilitySetter: function (value, key, element) {
// Handle inherited visibility
if (value === 'inherit') {
value = 'visible';
}
// Let the shadow follow the main element
if (this.shadows) {
this.shadows.forEach(function (shadow) {
shadow.style[key] = value;
});
}
// Instead of toggling the visibility CSS property, move the div out
// of the viewport. This works around #61 and #586
if (element.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 (!this.docMode8) {
element.style[key] = value ? 'visible' : 'hidden';
}
key = 'top';
}
element.style[key] = value;
},
xSetter: function (value, key, element) {
this[key] = value; // used in getter
if (key === 'x') {
key = 'left';
} else if (key === 'y') {
key = 'top';
}
// clipping rectangle special
if (this.updateClipping) {
// the key is now 'left' or 'top' for 'x' and 'y'
this[key] = value;
this.updateClipping();
} else {
// normal
element.style[key] = value;
}
},
zIndexSetter: function (value, key, element) {
element.style[key] = value;
},
fillGetter: function () {
return this.getAttr('fillcolor') || '';
},
strokeGetter: function () {
return this.getAttr('strokecolor') || '';
},
// #7850
classGetter: function () {
return this.getAttr('className') || '';
}
};
VMLElement['stroke-opacitySetter'] = VMLElement['fill-opacitySetter'];
H.VMLElement = VMLElement = extendClass(SVGElement, VMLElement);
// Some shared setters
VMLElement.prototype.ySetter =
VMLElement.prototype.widthSetter =
VMLElement.prototype.heightSetter =
VMLElement.prototype.xSetter;
/**
* The VML renderer
*
* @private
* @class
* @name Highcharts.VMLRenderer
*
* @augments Highcharts.SVGRenderer
*/
VMLRendererExtension = { // inherit SVGRenderer
Element: VMLElement,
isIE8: win.navigator.userAgent.indexOf('MSIE 8.0') > -1,
/**
* Initialize the VMLRenderer.
*
* @function Highcharts.VMLRenderer#init
*
* @param {object} container
*
* @param {number} width
*
* @param {number} height
*/
init: function (container, width, height) {
var renderer = this,
boxWrapper,
box,
css;
renderer.alignedObjects = [];
boxWrapper = renderer.createElement('div')
.css({ position: 'relative' });
box = boxWrapper.element;
container.appendChild(boxWrapper.element);
// generate the containing box
renderer.isVML = true;
renderer.box = box;
renderer.boxWrapper = boxWrapper;
renderer.gradients = {};
renderer.cache = {}; // Cache for numerical bounding boxes
renderer.cacheKeys = [];
renderer.imgCount = 0;
renderer.setSize(width, height, false);
// The only way to make IE6 and IE7 print is to use a global
// namespace. However, with IE8 the only way to make the dynamic
// shapes visible in screen and print mode seems to be to add the
// xmlns attribute and the behaviour style inline.
if (!doc.namespaces.hcv) {
doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
// Setup default CSS (#2153, #2368, #2384)
css = 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
'{ behavior:url(#default#VML); display: inline-block; } ';
try {
doc.createStyleSheet().cssText = css;
} catch (e) {
doc.styleSheets[0].cssText += css;
}
}
},
/**
* Detect whether the renderer is hidden. This happens when one of the
* parent elements has display: none
*
* @function Highcharts.VMLRenderer#isHidden
*/
isHidden: function () {
return !this.box.offsetWidth;
},
/**
* Define a clipping rectangle. In VML it is accomplished by storing the
* values for setting the CSS style to all associated members.
*
* @function Highcharts.VMLRenderer#clipRect
*
* @param {number} x
*
* @param {number} y
*
* @param {number} width
*
* @param {number} height
*/
clipRect: function (x, y, width, height) {
// create a dummy element
var clipRect = this.createElement(),
isObj = isObject(x);
// mimic a rectangle with its style object for automatic updating in
// attr
return extend(clipRect, {
members: [],
count: 0,
left: (isObj ? x.x : x) + 1,
top: (isObj ? x.y : y) + 1,
width: (isObj ? x.width : width) - 1,
height: (isObj ? x.height : height) - 1,
getCSS: function (wrapper) {
var element = wrapper.element,
nodeName = element.nodeName,
isShape = nodeName === 'shape',
inverted = wrapper.inverted,
rect = this,
top = rect.top - (isShape ? element.offsetTop : 0),
left = rect.left,
right = left + rect.width,
bottom = top + rect.height,
ret = {
clip: 'rect(' +
Math.round(inverted ? left : top) + 'px,' +
Math.round(inverted ? bottom : right) + 'px,' +
Math.round(inverted ? right : bottom) + 'px,' +
Math.round(inverted ? top : left) + 'px)'
};
// issue 74 workaround
if (!inverted && wrapper.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 () {
clipRect.members.forEach(function (member) {
// Member.element is falsy on deleted series, like in
// stock/members/series-remove demo. Should be removed
// from members, but this will do.
if (member.element) {
member.css(clipRect.getCSS(member));
}
});
}
});
},
/**
* Take a color and return it if it's a string, make it a gradient if
* it's a gradient configuration object, and apply opacity.
*
* @function Highcharts.VMLRenderer#color
*
* @param {object} color
* The color or config object
*/
color: function (color, elem, prop, wrapper) {
var renderer = this,
colorObject,
regexRgba = /^rgba/,
markup,
fillType,
ret = 'none';
// Check for linear or radial gradient
if (color && color.linearGradient) {
fillType = 'gradient';
} else if (color && color.radialGradient) {
fillType = 'pattern';
}
if (fillType) {
var stopColor,
stopOpacity,
gradient = color.linearGradient || color.radialGradient,
x1,
y1,
x2,
y2,
opacity1,
opacity2,
color1,
color2,
fillAttr = '',
stops = color.stops,
firstStop,
lastStop,
colors = [],
addFillNode = function () {
// Add the fill subnode. When colors attribute is used,
// the meanings of opacity and o:opacity2 are reversed.
markup = [''];
createElement(
renderer.prepVML(markup),
null,
null,
elem
);
};
// Extend from 0 to 1
firstStop = stops[0];
lastStop = stops[stops.length - 1];
if (firstStop[0] > 0) {
stops.unshift([
0,
firstStop[1]
]);
}
if (lastStop[0] < 1) {
stops.push([
1,
lastStop[1]
]);
}
// Compute the stops
stops.forEach(function (stop, i) {
if (regexRgba.test(stop[1])) {
colorObject = H.color(stop[1]);
stopColor = colorObject.get('rgb');
stopOpacity = colorObject.get('a');
} else {
stopColor = stop[1];
stopOpacity = 1;
}
// Build the color attribute
colors.push((stop[0] * 100) + '% ' + stopColor);
// Only start and end opacities are allowed, so we use the
// first and the last
if (!i) {
opacity1 = stopOpacity;
color2 = stopColor;
} else {
opacity2 = stopOpacity;
color1 = stopColor;
}
});
// Apply the gradient to fills only.
if (prop === 'fill') {
// Handle linear gradient angle
if (fillType === 'gradient') {
x1 = gradient.x1 || gradient[0] || 0;
y1 = gradient.y1 || gradient[1] || 0;
x2 = gradient.x2 || gradient[2] || 0;
y2 = gradient.y2 || gradient[3] || 0;
fillAttr = 'angle="' + (90 - Math.atan(
(y2 - y1) / // y vector
(x2 - x1) // x vector
) * 180 / Math.PI) + '"';
addFillNode();
// Radial (circular) gradient
} else {
var r = gradient.r,
sizex = r * 2,
sizey = r * 2,
cx = gradient.cx,
cy = gradient.cy,
radialReference = elem.radialReference,
bBox,
applyRadialGradient = function () {
if (radialReference) {
bBox = wrapper.getBBox();
cx += (radialReference[0] - bBox.x) /
bBox.width - 0.5;
cy += (radialReference[1] - bBox.y) /
bBox.height - 0.5;
sizex *= radialReference[2] / bBox.width;
sizey *= radialReference[2] / bBox.height;
}
fillAttr = 'src="' +
H.getOptions().global.VMLRadialGradientURL +
'" ' +
'size="' + sizex + ',' + sizey + '" ' +
'origin="0.5,0.5" ' +
'position="' + cx + ',' + cy + '" ' +
'color2="' + color2 + '" ';
addFillNode();
};
// Apply radial gradient
if (wrapper.added) {
applyRadialGradient();
} else {
// We need to know the bounding box to get the size
// and position right
wrapper.onAdd = applyRadialGradient;
}
// The fill element's color attribute is broken in IE8
// standards mode, so we need to set the parent shape's
// fillcolor attribute instead.
ret = color1;
}
// Gradients are not supported for VML stroke, return the first
// color. #722.
} else {
ret = stopColor;
}
// If the color is an rgba color, split it and add a fill node
// to hold the opacity component
} else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
colorObject = H.color(color);
wrapper[prop + '-opacitySetter'](
colorObject.get('a'),
prop,
elem
);
ret = colorObject.get('rgb');
} else {
// 'stroke' or 'fill' node
var propNodes = elem.getElementsByTagName(prop);
if (propNodes.length) {
propNodes[0].opacity = 1;
propNodes[0].type = 'solid';
}
ret = color;
}
return ret;
},
/**
* Take a VML string and prepare it for either IE8 or IE6/IE7.
*
* @function Highcharts.VMLRenderer#prepVML
*
* @param {Array<*>} markup
* A string array of the VML markup to prepare
*/
prepVML: function (markup) {
var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
isIE8 = this.isIE8;
markup = markup.join('');
if (isIE8) { // add xmlns and style inline
markup = markup.replace(
'/>',
' xmlns="urn:schemas-microsoft-com:vml" />'
);
if (markup.indexOf('style="') === -1) {
markup = markup.replace(
'/>',
' style="' + vmlStyle + '" />'
);
} else {
markup = markup.replace('style="', 'style="' + vmlStyle);
}
} else { // add namespace
markup = markup.replace('<', ' 1) {
obj.attr({
x: x,
y: y,
width: width,
height: height
});
}
return obj;
},
/**
* For rectangles, VML uses a shape for rect to overcome bugs and
* rotation problems
*
* @function Highcharts.VMLRenderer#createElement
*
* @param {string} nodeName
*/
createElement: function (nodeName) {
return nodeName === 'rect' ?
this.symbol(nodeName) :
SVGRenderer.prototype.createElement.call(this, nodeName);
},
/**
* In the VML renderer, each child of an inverted div (group) is
* inverted
*
* @function Highcharts.VMLRenderer#invertChild
*
* @param {object} element
*
* @param {object} parentNode
*/
invertChild: function (element, parentNode) {
var ren = this,
parentStyle = parentNode.style,
imgStyle = element.tagName === 'IMG' && element.style; // #1111
css(element, {
flip: 'x',
left: pInt(parentStyle.width) -
(imgStyle ? pInt(imgStyle.top) : 1),
top: pInt(parentStyle.height) -
(imgStyle ? pInt(imgStyle.left) : 1),
rotation: -90
});
// Recursively invert child elements, needed for nested composite
// shapes like box plots and error bars. #1680, #1806.
[].forEach.call(element.childNodes, function (child) {
ren.invertChild(child, element);
});
},
/**
* Symbol definitions that override the parent SVG renderer's symbols
*
* @name Highcharts.VMLRenderer#symbols
* @type {Highcharts.Dictionary}
*/
symbols: {
// 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 = Math.cos(start),
sinStart = Math.sin(start),
cosEnd = Math.cos(end),
sinEnd = Math.sin(end),
ret;
if (end - start === 0) { // no angle, don't show it.
return ['x'];
}
ret = [
'wa', // clockwise arc to
x - radius, // left
y - radius, // top
x + radius, // right
y + radius, // bottom
x + radius * cosStart, // start x
y + radius * sinStart, // start y
x + radius * cosEnd, // end x
y + radius * sinEnd // end y
];
if (options.open && !innerRadius) {
ret.push(
'e',
'M',
x, // - innerRadius,
y // - innerRadius
);
}
ret.push(
'at', // anti clockwise arc to
x - innerRadius, // left
y - innerRadius, // top
x + innerRadius, // right
y + innerRadius, // bottom
x + innerRadius * cosEnd, // start x
y + innerRadius * sinEnd, // start y
x + innerRadius * cosStart, // end x
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, wrapper) {
if (wrapper && defined(wrapper.r)) {
w = h = 2 * wrapper.r;
}
// 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
y + h, // bottom
x + w, // start x
y + h / 2, // start y
x + w, // end x
y + h / 2, // end y
'e' // close
];
},
/**
* Add rectangle symbol path which eases rotation and omits arcsize
* problems compared to the built-in VML roundrect shape. When
* borders are not rounded, use the simpler square path, else use
* the callout path without the arrow.
*/
rect: function (x, y, w, h, options) {
return SVGRenderer.prototype.symbols[
!defined(options) || !options.r ? 'square' : 'callout'
].call(0, x, y, w, h, options);
}
}
};
H.VMLRenderer = VMLRenderer = function () {
this.init.apply(this, arguments);
};
VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);
// general renderer
H.Renderer = VMLRenderer;
}
SVGRenderer.prototype.getSpanWidth = function (wrapper, tspan) {
var renderer = this,
bBox = wrapper.getBBox(true),
actualWidth = bBox.width;
// Old IE cannot measure the actualWidth for SVG elements (#2314)
if (!svg && renderer.forExport) {
actualWidth = renderer.measureSpanWidth(
tspan.firstChild.data,
wrapper.styles
);
}
return actualWidth;
};
// This method is used with exporting in old IE, when emulating SVG (see #2314)
SVGRenderer.prototype.measureSpanWidth = function (text, styles) {
var measuringSpan = doc.createElement('span'),
offsetWidth,
textNode = doc.createTextNode(text);
measuringSpan.appendChild(textNode);
css(measuringSpan, styles);
this.box.appendChild(measuringSpan);
offsetWidth = measuringSpan.offsetWidth;
discardElement(measuringSpan); // #2463
return offsetWidth;
};
}(Highcharts));
return (function () {
}());
}));