/* *
|
*
|
* (c) 2010-2019 Torstein Honsi
|
*
|
* License: www.highcharts.com/license
|
*
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
*
|
* */
|
'use strict';
|
|
var _Globals = require('./Globals.js');
|
|
var _Globals2 = _interopRequireDefault(_Globals);
|
|
var _Utilities = require('./Utilities.js');
|
|
var _Utilities2 = _interopRequireDefault(_Utilities);
|
|
require('./SvgRenderer.js');
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
var defined = _Utilities2.default.defined,
|
pInt = _Utilities2.default.pInt;
|
|
var attr = _Globals2.default.attr,
|
createElement = _Globals2.default.createElement,
|
css = _Globals2.default.css,
|
extend = _Globals2.default.extend,
|
isFirefox = _Globals2.default.isFirefox,
|
isMS = _Globals2.default.isMS,
|
isWebKit = _Globals2.default.isWebKit,
|
pick = _Globals2.default.pick,
|
SVGElement = _Globals2.default.SVGElement,
|
SVGRenderer = _Globals2.default.SVGRenderer,
|
win = _Globals2.default.win;
|
/* eslint-disable valid-jsdoc */
|
// Extend SvgElement for useHTML option.
|
extend(SVGElement.prototype, /** @lends SVGElement.prototype */{
|
/**
|
* Apply CSS to HTML elements. This is used in text within SVG rendering and
|
* by the VML renderer
|
*
|
* @private
|
* @function Highcharts.SVGElement#htmlCss
|
*
|
* @param {Highcharts.CSSObject} styles
|
*
|
* @return {Highcharts.SVGElement}
|
*/
|
htmlCss: function htmlCss(styles) {
|
var wrapper = this,
|
element = wrapper.element,
|
|
// When setting or unsetting the width style, we need to update
|
// transform (#8809)
|
isSettingWidth = element.tagName === 'SPAN' && styles && 'width' in styles,
|
textWidth = pick(isSettingWidth && styles.width, undefined),
|
doTransform;
|
if (isSettingWidth) {
|
delete styles.width;
|
wrapper.textWidth = textWidth;
|
doTransform = true;
|
}
|
if (styles && styles.textOverflow === 'ellipsis') {
|
styles.whiteSpace = 'nowrap';
|
styles.overflow = 'hidden';
|
}
|
wrapper.styles = extend(wrapper.styles, styles);
|
css(wrapper.element, styles);
|
// Now that all styles are applied, to the transform
|
if (doTransform) {
|
wrapper.htmlUpdateTransform();
|
}
|
return wrapper;
|
},
|
/**
|
* VML and useHTML method for calculating the bounding box based on offsets.
|
*
|
* @private
|
* @function Highcharts.SVGElement#htmlGetBBox
|
*
|
* @param {boolean} refresh
|
* Whether to force a fresh value from the DOM or to use the cached
|
* value.
|
*
|
* @return {Highcharts.BBoxObject}
|
* A hash containing values for x, y, width and height.
|
*/
|
htmlGetBBox: function htmlGetBBox() {
|
var wrapper = this,
|
element = wrapper.element;
|
return {
|
x: element.offsetLeft,
|
y: element.offsetTop,
|
width: element.offsetWidth,
|
height: element.offsetHeight
|
};
|
},
|
/**
|
* VML override private method to update elements based on internal
|
* properties based on SVG transform.
|
*
|
* @private
|
* @function Highcharts.SVGElement#htmlUpdateTransform
|
* @return {void}
|
*/
|
htmlUpdateTransform: function htmlUpdateTransform() {
|
// aligning non added elements is expensive
|
if (!this.added) {
|
this.alignOnAdd = true;
|
return;
|
}
|
var wrapper = this,
|
renderer = wrapper.renderer,
|
elem = wrapper.element,
|
translateX = wrapper.translateX || 0,
|
translateY = wrapper.translateY || 0,
|
x = wrapper.x || 0,
|
y = wrapper.y || 0,
|
align = wrapper.textAlign || 'left',
|
alignCorrection = {
|
left: 0, center: 0.5, right: 1
|
}[align],
|
styles = wrapper.styles,
|
whiteSpace = styles && styles.whiteSpace;
|
/**
|
* @private
|
* @return {number}
|
*/
|
function getTextPxLength() {
|
// Reset multiline/ellipsis in order to read width (#4928,
|
// #5417)
|
css(elem, {
|
width: '',
|
whiteSpace: whiteSpace || 'nowrap'
|
});
|
return elem.offsetWidth;
|
}
|
// apply translate
|
css(elem, {
|
marginLeft: translateX,
|
marginTop: translateY
|
});
|
if (!renderer.styledMode && wrapper.shadows) {
|
// used in labels/tooltip
|
wrapper.shadows.forEach(function (shadow) {
|
css(shadow, {
|
marginLeft: translateX + 1,
|
marginTop: translateY + 1
|
});
|
});
|
}
|
// apply inversion
|
if (wrapper.inverted) {
|
// wrapper is a group
|
[].forEach.call(elem.childNodes, function (child) {
|
renderer.invertChild(child, elem);
|
});
|
}
|
if (elem.tagName === 'SPAN') {
|
var rotation = wrapper.rotation,
|
baseline,
|
textWidth = wrapper.textWidth && pInt(wrapper.textWidth),
|
currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth, wrapper.textAlign].join(',');
|
// Update textWidth. Use the memoized textPxLength if possible, to
|
// avoid the getTextPxLength function using elem.offsetWidth.
|
// Calling offsetWidth affects rendering time as it forces layout
|
// (#7656).
|
if (textWidth !== wrapper.oldTextWidth && (textWidth > wrapper.oldTextWidth || (wrapper.textPxLength || getTextPxLength()) > textWidth) && (
|
// Only set the width if the text is able to word-wrap, or
|
// text-overflow is ellipsis (#9537)
|
/[ \-]/.test(elem.textContent || elem.innerText) || elem.style.textOverflow === 'ellipsis')) {
|
// #983, #1254
|
css(elem, {
|
width: textWidth + 'px',
|
display: 'block',
|
whiteSpace: whiteSpace || 'normal' // #3331
|
});
|
wrapper.oldTextWidth = textWidth;
|
wrapper.hasBoxWidthChanged = true; // #8159
|
} else {
|
wrapper.hasBoxWidthChanged = false; // #8159
|
}
|
// Do the calculations and DOM access only if properties changed
|
if (currentTextTransform !== wrapper.cTT) {
|
baseline = renderer.fontMetrics(elem.style.fontSize, elem).b;
|
// Renderer specific handling of span rotation, but only if we
|
// have something to update.
|
if (defined(rotation) && (rotation !== (wrapper.oldRotation || 0) || align !== wrapper.oldAlign)) {
|
wrapper.setSpanRotation(rotation, alignCorrection, baseline);
|
}
|
wrapper.getSpanCorrection(
|
// Avoid elem.offsetWidth if we can, it affects rendering
|
// time heavily (#7656)
|
!defined(rotation) && wrapper.textPxLength || // #7920
|
elem.offsetWidth, baseline, alignCorrection, rotation, align);
|
}
|
// apply position with correction
|
css(elem, {
|
left: x + (wrapper.xCorr || 0) + 'px',
|
top: y + (wrapper.yCorr || 0) + 'px'
|
});
|
// record current text transform
|
wrapper.cTT = currentTextTransform;
|
wrapper.oldRotation = rotation;
|
wrapper.oldAlign = align;
|
}
|
},
|
/**
|
* Set the rotation of an individual HTML span.
|
*
|
* @private
|
* @function Highcharts.SVGElement#setSpanRotation
|
* @param {number} rotation
|
* @param {number} alignCorrection
|
* @param {number} baseline
|
* @return {void}
|
*/
|
setSpanRotation: function setSpanRotation(rotation, alignCorrection, baseline) {
|
var rotationStyle = {},
|
cssTransformKey = this.renderer.getTransformKey();
|
rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
|
rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] = rotationStyle.transformOrigin = alignCorrection * 100 + '% ' + baseline + 'px';
|
css(this.element, rotationStyle);
|
},
|
/**
|
* Get the correction in X and Y positioning as the element is rotated.
|
*
|
* @private
|
* @function Highcharts.SVGElement#getSpanCorrection
|
* @param {number} width
|
* @param {number} baseline
|
* @param {number} alignCorrection
|
* @return {void}
|
*/
|
getSpanCorrection: function getSpanCorrection(width, baseline, alignCorrection) {
|
this.xCorr = -width * alignCorrection;
|
this.yCorr = -baseline;
|
}
|
});
|
// Extend SvgRenderer for useHTML option.
|
extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */{
|
/**
|
* @private
|
* @function Highcharts.SVGRenderer#getTransformKey
|
*
|
* @return {string}
|
*/
|
getTransformKey: function getTransformKey() {
|
return isMS && !/Edge/.test(win.navigator.userAgent) ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : win.opera ? '-o-transform' : '';
|
},
|
/**
|
* Create HTML text node. This is used by the VML renderer as well as the
|
* SVG renderer through the useHTML option.
|
*
|
* @private
|
* @function Highcharts.SVGRenderer#html
|
*
|
* @param {string} str
|
* The text of (subset) HTML to draw.
|
*
|
* @param {number} x
|
* The x position of the text's lower left corner.
|
*
|
* @param {number} y
|
* The y position of the text's lower left corner.
|
*
|
* @return {Highcharts.HTMLDOMElement}
|
*/
|
html: function html(str, x, y) {
|
var wrapper = this.createElement('span'),
|
element = wrapper.element,
|
renderer = wrapper.renderer,
|
isSVG = renderer.isSVG,
|
addSetters = function addSetters(gWrapper, style) {
|
// These properties are set as attributes on the SVG group, and
|
// as identical CSS properties on the div. (#3542)
|
['opacity', 'visibility'].forEach(function (prop) {
|
gWrapper[prop + 'Setter'] = function (value, key, elem) {
|
var styleObject = gWrapper.div ? gWrapper.div.style : style;
|
SVGElement.prototype[prop + 'Setter'].call(this, value, key, elem);
|
if (styleObject) {
|
styleObject[key] = value;
|
}
|
};
|
});
|
gWrapper.addedSetters = true;
|
},
|
chart = _Globals2.default.charts[renderer.chartIndex],
|
styledMode = chart && chart.styledMode;
|
// Text setter
|
wrapper.textSetter = function (value) {
|
if (value !== element.innerHTML) {
|
delete this.bBox;
|
delete this.oldTextWidth;
|
}
|
this.textStr = value;
|
element.innerHTML = pick(value, '');
|
wrapper.doTransform = true;
|
};
|
// Add setters for the element itself (#4938)
|
if (isSVG) {
|
// #4938, only for HTML within SVG
|
addSetters(wrapper, wrapper.element.style);
|
}
|
// Various setters which rely on update transform
|
wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function (value, key) {
|
if (key === 'align') {
|
// Do not overwrite the SVGElement.align method. Same as VML.
|
key = 'textAlign';
|
}
|
wrapper[key] = value;
|
wrapper.doTransform = true;
|
};
|
// Runs at the end of .attr()
|
wrapper.afterSetters = function () {
|
// Update transform. Do this outside the loop to prevent redundant
|
// updating for batch setting of attributes.
|
if (this.doTransform) {
|
this.htmlUpdateTransform();
|
this.doTransform = false;
|
}
|
};
|
// Set the default attributes
|
wrapper.attr({
|
text: str,
|
x: Math.round(x),
|
y: Math.round(y)
|
}).css({
|
position: 'absolute'
|
});
|
if (!styledMode) {
|
wrapper.css({
|
fontFamily: this.style.fontFamily,
|
fontSize: this.style.fontSize
|
});
|
}
|
// Keep the whiteSpace style outside the wrapper.styles collection
|
element.style.whiteSpace = 'nowrap';
|
// Use the HTML specific .css method
|
wrapper.css = wrapper.htmlCss;
|
// This is specific for HTML within SVG
|
if (isSVG) {
|
wrapper.add = function (svgGroupWrapper) {
|
var htmlGroup,
|
container = renderer.box.parentNode,
|
parentGroup,
|
parents = [];
|
this.parentGroup = svgGroupWrapper;
|
// Create a mock group to hold the HTML elements
|
if (svgGroupWrapper) {
|
htmlGroup = svgGroupWrapper.div;
|
if (!htmlGroup) {
|
// Read the parent chain into an array and read from top
|
// down
|
parentGroup = svgGroupWrapper;
|
while (parentGroup) {
|
parents.push(parentGroup);
|
// Move up to the next parent group
|
parentGroup = parentGroup.parentGroup;
|
}
|
// Ensure dynamically updating position when any parent
|
// is translated
|
parents.reverse().forEach(function (parentGroup) {
|
var htmlGroupStyle,
|
cls = attr(parentGroup.element, 'class');
|
/**
|
* Common translate setter for X and Y on the HTML
|
* group. Reverted the fix for #6957 du to
|
* positioning problems and offline export (#7254,
|
* #7280, #7529)
|
* @private
|
* @param {*} value
|
* @param {string} key
|
* @return {void}
|
*/
|
function translateSetter(value, key) {
|
parentGroup[key] = value;
|
if (key === 'translateX') {
|
htmlGroupStyle.left = value + 'px';
|
} else {
|
htmlGroupStyle.top = value + 'px';
|
}
|
parentGroup.doTransform = true;
|
}
|
if (cls) {
|
cls = { className: cls };
|
} // else null
|
// Create a HTML div and append it to the parent div
|
// to emulate the SVG group structure
|
htmlGroup = parentGroup.div = parentGroup.div || createElement('div', cls, {
|
position: 'absolute',
|
left: (parentGroup.translateX || 0) + 'px',
|
top: (parentGroup.translateY || 0) + 'px',
|
display: parentGroup.display,
|
opacity: parentGroup.opacity,
|
pointerEvents: parentGroup.styles && parentGroup.styles.pointerEvents // #5595
|
// the top group is appended to container
|
}, htmlGroup || container);
|
// Shortcut
|
htmlGroupStyle = htmlGroup.style;
|
// Set listeners to update the HTML div's position
|
// whenever the SVG group position is changed.
|
extend(parentGroup, {
|
// (#7287) Pass htmlGroup to use
|
// the related group
|
classSetter: function (htmlGroup) {
|
return function (value) {
|
this.element.setAttribute('class', value);
|
htmlGroup.className = value;
|
};
|
}(htmlGroup),
|
on: function on() {
|
if (parents[0].div) {
|
// #6418
|
wrapper.on.apply({ element: parents[0].div }, arguments);
|
}
|
return parentGroup;
|
},
|
translateXSetter: translateSetter,
|
translateYSetter: translateSetter
|
});
|
if (!parentGroup.addedSetters) {
|
addSetters(parentGroup);
|
}
|
});
|
}
|
} else {
|
htmlGroup = container;
|
}
|
htmlGroup.appendChild(element);
|
// Shared with VML:
|
wrapper.added = true;
|
if (wrapper.alignOnAdd) {
|
wrapper.htmlUpdateTransform();
|
}
|
return wrapper;
|
};
|
}
|
return wrapper;
|
}
|
});
|