| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556 | /* * * (c) 2010-2019 Torstein Honsi * * Support for old IE browsers (6, 7 and 8) in Highcharts v6+. * * License: www.highcharts.com/license */'use strict';import H from '../parts/Globals.js';import '../parts/Utilities.js';import '../parts/SvgRenderer.js';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/@product.version@/gfx/vml-radial-gradient.png';// Utilitesif (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(/<IMG /g, '<image ')            .replace(/<(\/?)TITLE>/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 = [                        '<shape isShadow="true" strokeweight="', strokeWidth,                        '" filled="false" path="', modifiedPath,                        '" coordsize="10 10" style="', element.style.cssText,                        '" />'                    ];                    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 = [                        '<stroke color="',                        shadowOptions.color || '#000000',                        '" opacity="', shadowElementOpacity * i, '"/>'];                    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(['<stroke/>']),                    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 = ['<fill colors="' + colors.join(',') +                            '" opacity="', opacity2, '" o:opacity2="',                        opacity1, '" type="', fillType, '" ', fillAttr,                        'focus="100%" method="any" />'];                        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('<', '<hcv:');            }            return markup;        },        /**         * Create rotated and aligned text         *         * @function Highcharts.VMLRenderer#text         *         * @param {string} str         *         * @param {number} x         *         * @param {number} y         */        text: SVGRenderer.prototype.html,        /**         * Create and return a path element         *         * @function Highcharts.VMLRenderer#path         *         * @param {Highcharts.SVGPathArray} path         */        path: function (path) {            var attr = {                // subpixel precision down to 0.1 (width and height = 1px)                coordsize: '10 10'            };            if (isArray(path)) {                attr.d = path;            } else if (isObject(path)) { // attributes                extend(attr, path);            }            // create the shape            return this.createElement('shape').attr(attr);        },        /**         * Create and return a circle element. In VML circles are implemented as         * shapes, which is faster than v:oval         *         * @function Highcharts.VMLRenderer#circle         *         * @param {number} x         *         * @param {number} y         *         * @param {number} r         */        circle: function (x, y, 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)            circle.r = r;            return circle.attr({ x: x, y: y });        },        /**         * 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 child HTML elements and CSS clip.         *         * @function Highcharts.VMLRenderer#g         *         * @param {string} name         *        The name of the group         */        g: function (name) {            var wrapper,                attribs;            // set the class name            if (name) {                attribs = {                    'className': 'highcharts-' + name,                    'class': 'highcharts-' + name                };            }            // the div to hold HTML and clipping            wrapper = this.createElement('div').attr(attribs);            return wrapper;        },        /**         * VML override to create a regular HTML image.         *         * @function Highcharts.VMLRenderer#image         *         * @param {string} src         *         * @param {number} x         *         * @param {number} y         *         * @param {number} width         *         * @param {number} height         */        image: function (src, x, y, width, height) {            var obj = this.createElement('img')                .attr({ src: src });            if (arguments.length > 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.            element.childNodes.forEach(function (child) {                ren.invertChild(child, element);            });        },        /**         * Symbol definitions that override the parent SVG renderer's symbols         *         * @name Highcharts.VMLRenderer#symbols         * @type {Highcharts.Dictionary<Function>}         */        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;};
 |