highcharts
Version:
JavaScript charting framework
535 lines (534 loc) • 13.2 kB
JavaScript
/* *
*
* (c) 2010-2026 Highsoft AS
* Author: Torstein Hønsi
*
* A commercial license may be required depending on use.
* See www.highcharts.com/license
*
*
* */
'use strict';
import { defined, isNumber, pick } from '../../../Shared/Utilities.js';
/* *
*
* Functions
*
* */
/* eslint-disable require-jsdoc, valid-jsdoc */
/**
* Arc symbol path.
*
* @param {number} cx
* Center X
* @param {number} cy
* Center Y
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
function arc(cx, cy, w, h, options) {
const arc = [];
if (options) {
let start = options.start || 0, end = options.end || 0;
const rx = pick(options.r, w), ry = pick(options.r, h || w),
// Subtract a small number to prevent cos and sin of start and end
// from becoming equal on 360 arcs (#1561). See "Arc proximity"
// tests at samples/unit-tests/svgrenderer/symbol/demo.js
proximity = 0.0001, fullCircle = (Math.abs(end - start - 2 * Math.PI) <
proximity);
if (fullCircle) {
start = Math.PI / 2;
end = Math.PI * 2.5 - proximity;
}
const innerRadius = options.innerR, open = pick(options.open, fullCircle), cosStart = fullCircle ? 0 : Math.cos(start), sinStart = fullCircle ? 1 : Math.sin(start), cosEnd = fullCircle ? 0 : Math.cos(end), sinEnd = fullCircle ? 1 : Math.sin(end),
// Proximity takes care of rounding errors around PI (#6971)
longArc = pick(options.longArc, end - start - Math.PI < proximity ? 0 : 1);
let arcSegment = [
'A', // ArcTo
rx, // X radius
ry, // Y radius
0, // Slanting
longArc, // Long or short arc
pick(options.clockwise, 1), // Clockwise
// Use a static pixel offset for full circle (#21701)
cx + (fullCircle ? 0.001 : rx * cosEnd),
cy + ry * sinEnd
];
arcSegment.params = { start, end, cx, cy }; // Memo for border radius
arc.push([
'M',
cx + rx * cosStart,
cy + ry * sinStart
], arcSegment);
if (defined(innerRadius)) {
arcSegment = [
'A', // ArcTo
innerRadius, // X radius
innerRadius, // Y radius
0, // Slanting
longArc, // Long or short arc
// Clockwise - opposite to the outer arc clockwise
defined(options.clockwise) ? 1 - options.clockwise : 0,
cx + (fullCircle ? -0.001 : innerRadius * cosStart),
cy + innerRadius * sinStart
];
// Memo for border radius
arcSegment.params = {
start: end,
end: start,
cx,
cy
};
arc.push(open ?
[
'M',
cx + innerRadius * cosEnd,
cy + innerRadius * sinEnd
] : [
'L',
cx + innerRadius * cosEnd,
cy + innerRadius * sinEnd
], arcSegment);
}
if (!open) {
arc.push(['Z']);
}
}
return arc;
}
/**
* Callout shape used for default tooltips.
*
* @param {number} x
* Center X
* @param {number} y
* Center Y
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
function callout(x, y, w, h, options) {
const arrowLength = 6, halfDistance = 6, r = Math.min((options?.r) || 0, w, h), safeDistance = r + halfDistance, anchorX = options?.anchorX, anchorY = options?.anchorY || 0;
const path = roundedRect(x, y, w, h, { r });
if (!isNumber(anchorX)) {
return path;
}
// Do not render a connector, if anchor starts inside the label
if (anchorX < w && anchorX > 0 && anchorY < h && anchorY > 0) {
return path;
}
// Anchor on right side
if (x + anchorX > w - safeDistance) {
// Chevron
if (anchorY > y + safeDistance &&
anchorY < y + h - safeDistance) {
path.splice(3, 1, ['L', x + w, anchorY - halfDistance], ['L', x + w + arrowLength, anchorY], ['L', x + w, anchorY + halfDistance], ['L', x + w, y + h - r]);
// Simple connector
}
else {
if (anchorX < w) { // Corner connector
const isTopCorner = anchorY < y + safeDistance, cornerY = isTopCorner ? y : y + h, sliceStart = isTopCorner ? 2 : 5;
path.splice(sliceStart, 0, ['L', anchorX, anchorY], ['L', x + w - r, cornerY]);
}
else { // Side connector
path.splice(3, 1, ['L', x + w, h / 2], ['L', anchorX, anchorY], ['L', x + w, h / 2], ['L', x + w, y + h - r]);
}
}
// Anchor on left side
}
else if (x + anchorX < safeDistance) {
// Chevron
if (anchorY > y + safeDistance &&
anchorY < y + h - safeDistance) {
path.splice(7, 1, ['L', x, anchorY + halfDistance], ['L', x - arrowLength, anchorY], ['L', x, anchorY - halfDistance], ['L', x, y + r]);
// Simple connector
}
else {
if (anchorX > 0) { // Corner connector
const isTopCorner = anchorY < y + safeDistance, cornerY = isTopCorner ? y : y + h, sliceStart = isTopCorner ? 1 : 6;
path.splice(sliceStart, 0, ['L', anchorX, anchorY], ['L', x + r, cornerY]);
}
else { // Side connector
path.splice(7, 1, ['L', x, h / 2], ['L', anchorX, anchorY], ['L', x, h / 2], ['L', x, y + r]);
}
}
}
else if ( // Replace bottom
anchorY > h &&
anchorX < w - safeDistance) {
path.splice(5, 1, ['L', anchorX + halfDistance, y + h], ['L', anchorX, y + h + arrowLength], ['L', anchorX - halfDistance, y + h], ['L', x + r, y + h]);
}
else if ( // Replace top
anchorY < 0 &&
anchorX > safeDistance) {
path.splice(1, 1, ['L', anchorX - halfDistance, y], ['L', anchorX, y - arrowLength], ['L', anchorX + halfDistance, y], ['L', w - r, y]);
}
return path;
}
/**
* Circle symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @return {Highcharts.SVGPathArray}
* Path
*/
function circle(x, y, w, h) {
// Return a full arc
return arc(x + w / 2, y + h / 2, w / 2, h / 2, {
start: Math.PI * 0.5,
end: Math.PI * 2.5,
open: false
});
}
/**
* Diamond symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @return {Highcharts.SVGPathArray}
* Path
*/
function diamond(x, y, w, h) {
return [
['M', x + w / 2, y],
['L', x + w, y + h / 2],
['L', x + w / 2, y + h],
['L', x, y + h / 2],
['Z']
];
}
// #15291
/**
* Rect symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
function rect(x, y, w, h, options) {
if (options?.r) {
return roundedRect(x, y, w, h, options);
}
return [
['M', x, y],
['L', x + w, y],
['L', x + w, y + h],
['L', x, y + h],
['Z']
];
}
/**
* Rounded rectangle symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
function roundedRect(x, y, w, h, options) {
const r = options?.r || 0;
return [
['M', x + r, y],
['L', x + w - r, y], // Top side
['A', r, r, 0, 0, 1, x + w, y + r], // Top-right corner
['L', x + w, y + h - r], // Right side
['A', r, r, 0, 0, 1, x + w - r, y + h], // Bottom-right corner
['L', x + r, y + h], // Bottom side
['A', r, r, 0, 0, 1, x, y + h - r], // Bottom-left corner
['L', x, y + r], // Left side
['A', r, r, 0, 0, 1, x + r, y],
['Z'] // Top-left corner
];
}
/**
* Triangle symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @return {Highcharts.SVGPathArray}
* Path
*/
function triangle(x, y, w, h) {
return [
['M', x + w / 2, y],
['L', x + w, y + h],
['L', x, y + h],
['Z']
];
}
/**
* Inverted triangle symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @return {Highcharts.SVGPathArray}
* Path
*/
function triangleDown(x, y, w, h) {
return [
['M', x, y],
['L', x + w, y],
['L', x + w / 2, y + h],
['Z']
];
}
const Symbols = {
/**
* Arc symbol path.
*
* @param {number} cx
* Center X
* @param {number} cy
* Center Y
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
arc,
/**
* Callout shape used for default tooltips.
*
* @param {number} cx
* Center X
* @param {number} cy
* Center Y
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
callout,
/**
* Circle symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @return {Highcharts.SVGPathArray}
* Path
*/
circle,
/**
* Diamond symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @return {Highcharts.SVGPathArray}
* Path
*/
diamond,
/**
* Rect symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
rect,
/**
* Rounded rectangle symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
roundedRect,
/**
* Rect symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @param {Highcharts.SymbolOptions} [options]
* Options
* @return {Highcharts.SVGPathArray}
* Path
*/
square: rect,
/**
* Triangle symbol path.
*
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @return {Highcharts.SVGPathArray}
* Path
*/
triangle,
/**
* Inverted triangle symbol path.
*
* @param {number} x
* X coordinate
* @param {number} number
* Y coordinate
* @param {number} w
* Width
* @param {number} h
* Height
* @return {Highcharts.SVGPathArray}
* Path
*/
'triangle-down': triangleDown
};
/* *
*
* Default Export
*
* */
export default Symbols;
/* *
*
* API Declarations
*
* */
/**
* @interface Highcharts.SymbolOptions
*/ /**
* @name anchorX
* @type {number|undefined}
*/ /**
* @name anchorY
* @type {number|undefined}
*/ /**
* @name backgroundSize
* @type {"contain"|"cover"|"within"}
*/ /**
* @name clockwise
* @type {0|1|undefined}
*/ /**
* @name context
* @type {string|undefined}
*/ /**
* @name end
* @type {number|undefined}
*/ /**
* @name height
* @type {number|undefined}
*/ /**
* @name innerR
* @type {number|undefined}
*/ /**
* @name longArc
* @type {0|1|undefined}
*/ /**
* @name open
* @type {boolean|undefined}
*/ /**
* @name r
* @type {number|undefined}
*/ /**
* @name start
* @type {number|undefined}
*/ /**
* @name width
* @type {number|undefined}
*/ /**
* @name x
* @type {number|undefined}
*/ /**
* @name y
* @type {number|undefined}
*/
''; // Keeps doclets above in file