highcharts
Version:
JavaScript charting framework
350 lines (338 loc) • 12.9 kB
JavaScript
// SPDX-License-Identifier: LicenseRef-Highcharts
/**
* @license Highcharts JS v13.0.1 (2026-08-17)
* @module highcharts/modules/textpath
* @requires highcharts
*
* (c) 2009-2026 Highsoft AS
* Author: Torstein Hønsi
*
* A commercial license may be required depending on use,
* see www.highcharts.com/license
*/
import * as __WEBPACK_EXTERNAL_MODULE__highcharts_src_js_8202131d__ from "../highcharts.src.js";
/******/ // The require scope
/******/ const __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ const getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter/value functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ if(Array.isArray(definition)) {
/******/ var i = 0;
/******/ while(i < definition.length) {
/******/ var key = definition[i++];
/******/ var binding = definition[i++];
/******/ if(!__webpack_require__.o(exports, key)) {
/******/ if(binding === 0) {
/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] });
/******/ } else {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding });
/******/ }
/******/ } else if(binding === 0) { i++; }
/******/ }
/******/ } else {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
;// external ["../highcharts.src.js","default"]
const external_highcharts_src_js_default_namespaceObject = __WEBPACK_EXTERNAL_MODULE__highcharts_src_js_8202131d__["default"];
var external_highcharts_src_js_default_default = /*#__PURE__*/__webpack_require__.n(external_highcharts_src_js_default_namespaceObject);
;// ./code/es-modules/Extensions/TextPath.js
/* *
*
* Highcharts module with textPath functionality.
*
* (c) 2009-2026 Highsoft AS
* Author: Torstein Hønsi
*
* Integration of this software requires a license.
* - For commercial use, see www.highcharts.com/license
* - For non-commercial, see www.highcharts.com/license-eula
*
*
* */
const { composed, deg2rad } = (external_highcharts_src_js_default_default());
/* *
*
* Functions
*
* */
/**
* Set a text path for a `text` or `label` element, allowing the text to
* flow along a path.
*
* In order to unset the path for an existing element, call `setTextPath`
* with `{ enabled: false }` as the second argument.
*
* Text path support is not bundled into `highcharts.js`, and requires the
* `modules/textpath.js` file. However, it is included in the script files of
* those series types that use it by default.
*
* @sample highcharts/members/renderer-textpath/ Text path demonstrated
*
* @internal
* @function Highcharts.SVGElement#setTextPath
*
* @param {Highcharts.SVGElement|undefined} path
* Path to follow. If undefined, it allows changing options for the
* existing path.
*
* @param {Highcharts.DataLabelsTextPathOptionsObject} textPathOptions
* Options.
*
* @return {Highcharts.SVGElement} Returns the SVGElement for chaining.
*/
function setTextPath(path, textPathOptions) {
const url = this.renderer.url, textWrapper = this.text || this, textPath = textWrapper.textPath, { attributes, enabled } = (0,external_highcharts_src_js_default_namespaceObject.merge)(
// Defaults
{
enabled: true,
attributes: {
dy: -5,
startOffset: '50%',
textAnchor: 'middle'
}
}, textPathOptions);
path = path || (textPath && textPath.path);
// Remove previously added event
textPath?.undo();
if (path && enabled) {
const undo = (0,external_highcharts_src_js_default_namespaceObject.addEvent)(textWrapper, 'afterModifyTree', (e) => {
if (path && enabled) {
// Set ID for the path
let textPathId = path.attr('id');
if (!textPathId) {
path.attr('id', textPathId = (0,external_highcharts_src_js_default_namespaceObject.uniqueKey)());
}
// Set attributes for the <text>
const textAttribs = {
// `dx`/`dy` options must by set on <text> (parent), the
// rest should be set on <textPath>
x: 0,
y: 0
};
if ((0,external_highcharts_src_js_default_namespaceObject.defined)(attributes.dx)) {
textAttribs.dx = attributes.dx;
delete attributes.dx;
}
if ((0,external_highcharts_src_js_default_namespaceObject.defined)(attributes.dy)) {
textAttribs.dy = attributes.dy;
delete attributes.dy;
}
textWrapper.attr(textAttribs);
// Handle label properties
this.attr({ transform: '' });
if (this.box) {
this.box = this.box.destroy();
}
// Wrap the nodes in a textPath
const children = e.nodes.slice(0);
e.nodes.length = 0;
e.nodes[0] = {
tagName: 'textPath',
attributes: (0,external_highcharts_src_js_default_namespaceObject.extend)(attributes, {
'text-anchor': attributes.textAnchor,
href: `${url}#${textPathId}`
}),
children
};
}
});
// Set the reference
textWrapper.textPath = { path, undo };
}
else {
textWrapper.attr({ dx: 0, dy: 0 });
delete textWrapper.textPath;
}
if (this.added) {
// Rebuild text after added
textWrapper.textCache = '';
this.renderer.buildText(textWrapper);
}
return this;
}
/**
* Attach a polygon to a bounding box if the element contains a textPath.
*
* @internal
* @function Highcharts.SVGElement#setPolygon
*
* @param {any} event
* An event containing a bounding box object
*
* @return {Highcharts.BBoxObject} Returns the bounding box object.
*/
function setPolygon(event) {
const bBox = event.bBox, tp = this.element?.querySelector('textPath');
if (tp) {
const polygon = [], { b, h } = this.renderer.fontMetrics(this.element), descender = h - b, lineCleanerRegex = new RegExp('(<tspan>|' +
'<tspan(?!\\sclass="highcharts-br")[^>]*>|' +
'<\\/tspan>)', 'g'), lines = tp
.innerHTML
.replace(lineCleanerRegex, '')
.split(/<tspan class="highcharts-br"[^>]*>/), numOfLines = lines.length;
// Calculate top and bottom coordinates for
// either the start or the end of a single
// character, and append it to the polygon.
const appendTopAndBottom = (charIndex, positionOfChar) => {
const { x, y } = positionOfChar, rotation = (tp.getRotationOfChar(charIndex) - 90) * deg2rad, cosRot = Math.cos(rotation), sinRot = Math.sin(rotation);
return [
[
x - descender * cosRot,
y - descender * sinRot
],
[
x + b * cosRot,
y + b * sinRot
]
];
};
for (let i = 0, lineIndex = 0; lineIndex < numOfLines; lineIndex++) {
const line = lines[lineIndex], lineLen = line.length;
for (let lineCharIndex = 0; lineCharIndex < lineLen; lineCharIndex += 5) {
try {
const srcCharIndex = (i +
lineCharIndex +
lineIndex), [lower, upper] = appendTopAndBottom(srcCharIndex, tp.getStartPositionOfChar(srcCharIndex));
if (lineCharIndex === 0) {
polygon.push(upper);
polygon.push(lower);
}
else {
if (lineIndex === 0) {
polygon.unshift(upper);
}
if (lineIndex === numOfLines - 1) {
polygon.push(lower);
}
}
}
catch {
// Safari fails on getStartPositionOfChar even if the
// character is within the `textContent.length`
break;
}
}
i += lineLen - 1;
try {
const srcCharIndex = i + lineIndex, charPos = tp.getEndPositionOfChar(srcCharIndex), [lower, upper] = appendTopAndBottom(srcCharIndex, charPos);
polygon.unshift(upper);
polygon.unshift(lower);
}
catch {
// Safari fails on getStartPositionOfChar even if the character
// is within the `textContent.length`
break;
}
}
// Close it
if (polygon.length) {
polygon.push(polygon[0].slice());
}
bBox.polygon = polygon;
}
return bBox;
}
/**
* Draw text along a textPath for a dataLabel.
*
* @internal
* @function Highcharts.SVGElement#drawTextPath
*
* @param {any} event
* An event containing label options
*
* @return {void}
*/
function drawTextPath(event) {
const labelOptions = event.labelOptions, point = event.point, textPathOptions = (labelOptions[point.formatPrefix + 'TextPath'] ||
labelOptions.textPath);
if (textPathOptions && !labelOptions.useHTML) {
this.setTextPath(point.getDataLabelPath?.(this) || point.graphic, textPathOptions);
if (point.dataLabelPath &&
!textPathOptions.enabled) {
// Clean the DOM
point.dataLabelPath = point.dataLabelPath.destroy();
}
}
}
/** @internal */
function composeTextPath(SVGElementClass) {
if ((0,external_highcharts_src_js_default_namespaceObject.pushUnique)(composed, 'TextPath')) {
(0,external_highcharts_src_js_default_namespaceObject.addEvent)(SVGElementClass, 'afterGetBBox', setPolygon);
(0,external_highcharts_src_js_default_namespaceObject.addEvent)(SVGElementClass, 'beforeAddingDataLabel', drawTextPath);
SVGElementClass.prototype.setTextPath =
SVGElementClass.prototype.setTextPath ?? setTextPath;
}
}
/* *
*
* API Declarations
*
* */
/**
* Options for a label text which should follow marker's shape.
* Border and background are disabled for a label that follows a
* path.
*
* **Note:** Only SVG-based renderer supports this option. Setting
* `useHTML` to true will disable this option.
*
* Text path support is not bundled into `highcharts.js`, and requires the
* `modules/textpath.js` file. However, it is included in the script files of
* those series types that use it by default.
*
* @declare Highcharts.DataLabelsTextPathOptionsObject
* @since 7.1.0
* @apioption plotOptions.series.dataLabels.textPath
*/
/**
* Presentation attributes for the text path.
*
* @type {Highcharts.SVGAttributes}
* @since 7.1.0
* @default { dy:-5, startOffset:'50%', textAnchor:'middle' }
* @apioption plotOptions.series.dataLabels.textPath.attributes
*/
/**
* Enable or disable `textPath` option for link's or marker's data
* labels.
*
* @type {boolean}
* @since 7.1.0
* @default true
* @apioption plotOptions.series.dataLabels.textPath.enabled
*/
(''); // Keep doclets above in transpiled file
;// ./code/es-modules/masters/modules/textpath.src.js
const G = (external_highcharts_src_js_default_default());
composeTextPath(G.SVGElement);
/* harmony default export */ const textpath_src = ((external_highcharts_src_js_default_default()));
export { textpath_src as default };