highcharts
Version:
JavaScript charting framework
260 lines (259 loc) • 10.7 kB
JavaScript
/* *
*
* (c) 2010-2024 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
;
import AST from './AST.js';
import H from '../../Globals.js';
const { composed } = H;
import SVGElement from '../SVG/SVGElement.js';
import SVGRenderer from '../SVG/SVGRenderer.js';
import U from '../../Utilities.js';
const { attr, createElement, extend, pick, pushUnique } = U;
/* *
*
* Class
*
* */
// Extend SvgRenderer for useHTML option.
class HTMLRenderer extends SVGRenderer {
/* *
*
* Static Functions
*
* */
/** @private */
static compose(SVGRendererClass) {
if (pushUnique(composed, this.compose)) {
const htmlRendererProto = HTMLRenderer.prototype, svgRendererProto = SVGRendererClass.prototype;
svgRendererProto.html = htmlRendererProto.html;
}
return SVGRendererClass;
}
/* *
*
* Functions
*
* */
/**
* Create HTML text node. This is used by 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 element.
*/
html(str, x, y) {
const wrapper = this.createElement('span'), element = wrapper.element, renderer = wrapper.renderer, addSetters = function (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) {
const styleObject = gWrapper.div ?
gWrapper.div.style :
style;
SVGElement.prototype[prop + 'Setter']
.call(this, value, key, elem);
if (styleObject) {
styleObject[key] = value;
}
};
});
gWrapper.addedSetters = true;
};
// Text setter
wrapper.textSetter = function (value) {
if (value !== this.textStr) {
delete this.bBox;
delete this.oldTextWidth;
AST.setElementHTML(this.element, pick(value, ''));
this.textStr = value;
wrapper.doTransform = true;
}
};
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.
wrapper.alignValue = wrapper.textAlign = value;
}
else {
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 (!renderer.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;
wrapper.add = function (svgGroupWrapper) {
const container = renderer.box.parentNode, parents = [];
let htmlGroup, parentGroup;
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) {
const cls = attr(parentGroup.element, 'class'), parentProtoCss = parentGroup.css;
/**
* 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
*/
function translateSetter(value, key) {
parentGroup[key] = value;
if (key === 'translateX') {
htmlGroupStyle.left = value + 'px';
}
else {
htmlGroupStyle.top = value + 'px';
}
parentGroup.doTransform = true;
}
// Create a HTML div and append it to the parent div
// to emulate the SVG group structure
const parentGroupStyles = parentGroup.styles || {};
htmlGroup =
parentGroup.div =
parentGroup.div || createElement('div', cls ? { className: cls } : void 0, {
position: 'absolute',
left: (parentGroup.translateX || 0) + 'px',
top: (parentGroup.translateY || 0) + 'px',
display: parentGroup.display,
opacity: parentGroup.opacity,
visibility: parentGroup.visibility
// the top group is appended to container
}, htmlGroup || container);
// Shortcut
const 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)),
// Extend the parent group's css function by
// updating the shadow div counterpart with the same
// style.
css: function (styles) {
// Call the base css method. The `parentGroup`
// can be either an SVGElement or an SVGLabel,
// in which the css method is extended (#19200).
parentProtoCss.call(parentGroup, styles);
[
// #6794
'cursor',
// #5595, #18821
'pointerEvents'
].forEach((prop) => {
if (styles[prop]) {
htmlGroupStyle[prop] = styles[prop];
}
});
return parentGroup;
},
on: function () {
if (parents[0].div) { // #6418
wrapper.on.apply({
element: parents[0].div,
onEvents: parentGroup.onEvents
}, arguments);
}
return parentGroup;
},
translateXSetter: translateSetter,
translateYSetter: translateSetter
});
if (!parentGroup.addedSetters) {
addSetters(parentGroup);
}
// Apply pre-existing style
parentGroup.css(parentGroupStyles);
});
}
}
else {
htmlGroup = container;
}
htmlGroup.appendChild(element);
wrapper.added = true;
if (wrapper.alignOnAdd) {
wrapper.htmlUpdateTransform();
}
return wrapper;
};
return wrapper;
}
}
/* *
*
* Default Export
*
* */
export default HTMLRenderer;