UNPKG

highcharts

Version:
306 lines (305 loc) 10.4 kB
/* * * * (c) 2010-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 * * * */ 'use strict'; import CU from '../../Series/CenteredUtilities.js'; import PaneComposition from './PaneComposition.js'; import PaneDefaults from './PaneDefaults.js'; import { defined, extend, getAlignFactor, isNumber, merge, relativeLength, splat } from '../../Shared/Utilities.js'; /* * * * Class * * */ /** * The Pane object allows options that are common to a set of X and Y axes. * * In the future, this can be extended to basic Highcharts and Highcharts Stock. * * @internal * @class * @name Highcharts.Pane * @param {Highcharts.PaneOptions} options * @param {Highcharts.Chart} chart */ class Pane { /* * * * Constructor * * */ constructor(options, chart) { this.coll = 'pane'; // Member of chart.pane this.init(options, chart); } /* * * * Functions * * */ /** * Initialize the Pane object * * @internal * @function Highcharts.Pane#init * * @param {Highcharts.PaneOptions} options * * @param {Highcharts.Chart} chart */ init(options, chart) { this.chart = chart; this.background = []; chart.pane.push(this); this.setOptions(options); } /** * Check if the chart has a series of a specific type * @internal */ hasSeriesType(type) { return Boolean(this.chart.options?.chart?.type === type || this.chart.options?.series?.some((s) => s.type === type)); } /** * Set options override. Angular charts have a default background (#3318) * and an inner size. * * @internal */ setOptions(options) { this.options = merge(PaneDefaults.pane, this.chart.angular ? { // Add a single background element with default properties background: {}, // An inner size for plot bands and axis layout innerSize: '85%' } : {}, options); } /** * Render the pane with its backgrounds. * * @internal * @function Highcharts.Pane#render */ render() { const options = this.options, renderer = this.chart.renderer; if (!this.group) { this.group = renderer.g('pane-group') .attr({ zIndex: options.zIndex || 0 }) .add(); } this.updateCenter(); let backgroundOption = this.options.background; // Render the backgrounds if (backgroundOption) { backgroundOption = splat(backgroundOption); const len = Math.max(backgroundOption.length, this.background.length || 0); for (let i = 0; i < len; i++) { // #6641 - if axis exists, chart is circular and apply // background if (backgroundOption[i] && this.axis) { this.renderBackground(merge(PaneDefaults.background, // Defaults inherited from the `pane` option { borderRadius: this.options.borderRadius }, backgroundOption[i]), i); } else if (this.background[i]) { this.background[i] = this.background[i].destroy(); this.background.splice(i, 1); } } } } /** * Render an individual pane background. * * @internal * @function Highcharts.Pane#renderBackground * * @param {Highcharts.PaneBackgroundOptions} backgroundOptions * Background options * * @param {number} i * The index of the background in this.backgrounds */ renderBackground(backgroundOptions, i) { const attribs = { 'class': 'highcharts-pane ' + (backgroundOptions.className || '') }; let method = 'animate'; if (!this.chart.styledMode) { extend(attribs, { 'fill': backgroundOptions.backgroundColor, 'stroke': backgroundOptions.borderColor, 'stroke-width': backgroundOptions.borderWidth }); } if (!this.background[i]) { this.background[i] = this.chart.renderer .path() .add(this.group); method = 'attr'; } this.background[i][method]({ 'd': this.axis.getPlotBandPath(backgroundOptions.from, backgroundOptions.to, backgroundOptions) }).attr(attribs); } /** * Gets the center for the pane and its axis. * * @internal * @function Highcharts.Pane#updateCenter */ updateCenter() { const { axis, chart, options } = this, { plotHeight, plotWidth } = chart, centerY = options.center?.[1], m = options.margin, labels = this.axis?.options.labels, thickness = options.thickness, marginLoose = Array.isArray(m) ? m : [m, m, m, m], margin = []; let size = options.size, sizeFromAngle, appliedCenterMargin = 0, axisLabelMargin = 0; // Get the required margin in order to display the data label in or // below the center const dataLabelMargin = Math.min(chart.series .reduce((max, s) => { if (!s.is('gauge') || s.yAxis?.pane !== this) { return max; } const dl = splat(s.options.dataLabels)[0]; let dlMargin = 0; if (dl && dl.enabled !== false) { // 30 is an approximation of the default data label // height. It is not yet rendered. dlMargin = (1 - getAlignFactor(dl.verticalAlign)) * 30 + (dl.y || 0); } return Math.max(max, dlMargin); }, 0), plotHeight * 0.3); // Get the required margin to make room for the radial axis labels. if (labels?.enabled) { const fontSize = String(labels.style?.fontSize || ''), // Approximate the line height because we don't have the // actual label to measure lineHeightGuess = (/px$/.test(fontSize) ? parseFloat(fontSize) : /em$/.test(fontSize) ? parseFloat(fontSize) * 12 : 12) * 1.2, // 1.2 is a line height approximation m = Math.max(labels.distance || 0, 0) + lineHeightGuess / 2; axisLabelMargin = m; } marginLoose.forEach((m, i) => { margin[i] = m ?? Math.max(axisLabelMargin || 0); }); // Handle auto-positioning when size and center are undefined if (axis && (size === void 0 || centerY === void 0)) { const { endAngleRad, startAngleRad } = axis, deg2rad = Math.PI * 2 / 360, crossingBottom = (startAngleRad < Math.PI / 2 && endAngleRad > Math.PI / 2) || // Circle background should fill out the plot area splat(options.background).some((b) => b?.shape === 'circle'), maxAngle = crossingBottom ? Math.PI : Math.max(Math.abs(startAngleRad + Math.PI / 2), Math.abs(endAngleRad + Math.PI / 2)), sin = Math.sin(maxAngle - Math.PI / 2), // The size doesn't increase further to angles below this // minimum. For linear gauges, this means that the pivot is kept // visible. minimumAngle = 90, sizeRatio = 0.5 + 0.5 * Math.max(sin, Math.sin(deg2rad * (minimumAngle - 90))); sizeFromAngle = (plotHeight - margin[0] - margin[2]) / sizeRatio; if (size === void 0) { size = Math.max(Math.min(sizeFromAngle, plotWidth - margin[1] - margin[3]), 1); // Make sure there is space for the data label (centerMargin) const overflow = size + margin[0] + margin[2] + 2 * (dataLabelMargin - plotHeight); if (overflow > 0) { appliedCenterMargin = overflow; size = Math.max(1, size - appliedCenterMargin); } } } // Run the standard centering this.center = (axis || {}).center = CU.getCenter.call(this); // Apply the auto-positioning if (isNumber(size) && size >= 0) { this.center[2] = size; if (isNumber(thickness)) { this.center[3] = this.center[2] - thickness * 2; } else { this.center[3] = Math.min(size, relativeLength(options.innerSize || 0, size)); } } if (!defined(centerY)) { if (options.size) { this.center[1] = plotHeight / 2; } else if (isNumber(sizeFromAngle)) { this.center[1] = (sizeFromAngle + this.center[2] - appliedCenterMargin) / 4 + margin[0]; } } } /** * Destroy the pane item * * @ignore * @internal * @function Highcharts.Pane#destroy * / destroy: function () { erase(this.chart.pane, this); this.background.forEach(function (background) { background.destroy(); }); this.background.length = 0; this.group = this.group.destroy(); }, */ /** * Update the pane item with new options * * @internal * @function Highcharts.Pane#update * @param {Highcharts.PaneOptions} options * New pane options * @param {boolean} [redraw] */ update(options, redraw) { merge(true, this.options, options); this.setOptions(this.options); this.render(); this.chart.axes.forEach(function (axis) { if (axis.pane === this) { axis.pane = null; axis.update({}, redraw); } }, this); } } /* * * * Static Properties * * */ Pane.compose = PaneComposition.compose; /* * * * Default Export * * */ /** @internal */ export default Pane; /* * * * API Declarations * * */ /** * @typedef {"arc"|"circle"|"solid"} Highcharts.PaneBackgroundShapeValue */ ''; // Keeps doclets above in JS file