highcharts
Version:
JavaScript charting framework
308 lines (307 loc) • 11 kB
JavaScript
/* *
*
* (c) 2010-2024 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
;
import FlagsPoint from './FlagsPoint.js';
import FlagsSeriesDefaults from './FlagsSeriesDefaults.js';
import FlagsSymbols from './FlagsSymbols.js';
import H from '../../Core/Globals.js';
const { noop } = H;
import OnSeriesComposition from '../OnSeriesComposition.js';
import R from '../../Core/Renderer/RendererUtilities.js';
const { distribute } = R;
import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
const { series: Series, seriesTypes: { column: ColumnSeries } } = SeriesRegistry;
import SVGElement from '../../Core/Renderer/SVG/SVGElement.js';
import U from '../../Core/Utilities.js';
const { addEvent, defined, extend, isNumber, merge, objectEach, wrap } = U;
/* *
*
* Classes
*
* */
/**
* The Flags series.
*
* @private
* @class
* @name Highcharts.seriesTypes.flags
*
* @augments Highcharts.Series
*/
class FlagsSeries extends ColumnSeries {
/* *
*
* Functions
*
* */
/**
* Disable animation, but keep clipping (#8546).
* @private
*/
animate(init) {
if (init) {
this.setClip();
}
}
/**
* Draw the markers.
* @private
*/
drawPoints() {
const series = this, points = series.points, chart = series.chart, renderer = chart.renderer, inverted = chart.inverted, options = series.options, optionsY = options.y, yAxis = series.yAxis, boxesMap = {}, boxes = [], borderRadius = isNumber(options.borderRadius) ?
options.borderRadius : 0;
let plotX, plotY, shape, i, point, graphic, stackIndex, anchorY, attribs, outsideRight, centered;
i = points.length;
while (i--) {
point = points[i];
outsideRight =
(inverted ? point.plotY : point.plotX) >
series.xAxis.len;
plotX = point.plotX;
stackIndex = point.stackIndex;
shape = point.options.shape || options.shape;
plotY = point.plotY;
if (typeof plotY !== 'undefined') {
plotY = point.plotY + optionsY -
(typeof stackIndex !== 'undefined' &&
(stackIndex * options.stackDistance));
}
// Skip connectors for higher level stacked points
point.anchorX = stackIndex ? void 0 : point.plotX;
anchorY = stackIndex ? void 0 : point.plotY;
centered = shape !== 'flag';
graphic = point.graphic;
// Only draw the point if y is defined and the flag is within
// the visible area
if (typeof plotY !== 'undefined' &&
plotX >= 0 &&
!outsideRight) {
// #15384
if (graphic && point.hasNewShapeType()) {
graphic = graphic.destroy();
}
// Create the flag
if (!graphic) {
graphic = point.graphic = renderer.label('', 0, void 0, shape, void 0, void 0, options.useHTML)
.addClass('highcharts-point')
.add(series.markerGroup);
// Add reference to the point for tracker (#6303)
if (point.graphic.div) {
point.graphic.div.point = point;
}
graphic.isNew = true;
}
graphic.attr({
align: centered ? 'center' : 'left',
width: options.width,
height: options.height,
'text-align': options.textAlign,
r: borderRadius
});
if (!chart.styledMode) {
graphic
.attr(series.pointAttribs(point))
.css(merge(options.style, point.style))
.shadow(options.shadow);
}
if (plotX > 0) { // #3119
plotX -= graphic.strokeWidth() % 2; // #4285
}
// Plant the flag
attribs = {
y: plotY,
anchorY: anchorY
};
if (options.allowOverlapX) {
attribs.x = plotX;
attribs.anchorX = point.anchorX;
}
graphic.attr({
// Allow empty string as a flag title (#20549)
text: point.options.title ?? options.title ?? 'A'
})[graphic.isNew ? 'attr' : 'animate'](attribs);
// Rig for the distribute function
if (!options.allowOverlapX) {
if (!boxesMap[point.plotX]) {
boxesMap[point.plotX] = {
align: centered ? 0.5 : 0,
size: graphic.width || 0,
target: plotX,
anchorX: plotX
};
}
else {
boxesMap[point.plotX].size = Math.max(boxesMap[point.plotX].size, graphic.width || 0);
}
}
// Set the tooltip anchor position
point.tooltipPos = [
plotX,
plotY + yAxis.pos - chart.plotTop
]; // #6327
}
else if (graphic) {
point.graphic = graphic.destroy();
}
}
// Handle X-dimension overlapping
if (!options.allowOverlapX) {
let maxDistance = 100;
objectEach(boxesMap, function (box) {
box.plotX = box.anchorX;
boxes.push(box);
maxDistance = Math.max(box.size, maxDistance);
});
// If necessary (for overlapping or long labels) distribute it
// depending on the label width or a hardcoded value, #16041.
distribute(boxes, inverted ? yAxis.len : this.xAxis.len, maxDistance);
for (const point of points) {
const plotX = point.plotX, graphic = point.graphic, box = graphic && boxesMap[plotX];
if (box && graphic) {
// Hide flag when its box position is not specified
// (#8573, #9299)
if (!defined(box.pos)) {
graphic.hide().isNew = true;
}
else {
graphic[graphic.isNew ? 'attr' : 'animate']({
x: box.pos + (box.align || 0) * box.size,
anchorX: point.anchorX
}).show().isNew = false;
}
}
}
}
// Can be a mix of SVG and HTML and we need events for both (#6303)
if (options.useHTML && series.markerGroup) {
wrap(series.markerGroup, 'on', function (proceed) {
return SVGElement.prototype.on.apply(
// For HTML
// eslint-disable-next-line no-invalid-this
proceed.apply(this, [].slice.call(arguments, 1)),
// And for SVG
[].slice.call(arguments, 1));
});
}
}
/**
* Extend the column trackers with listeners to expand and contract
* stacks.
* @private
*/
drawTracker() {
const series = this, points = series.points;
super.drawTracker();
/* *
* Bring each stacked flag up on mouse over, this allows readability
* of vertically stacked elements as well as tight points on the x
* axis. #1924.
*/
for (const point of points) {
const graphic = point.graphic;
if (graphic) {
if (point.unbindMouseOver) {
point.unbindMouseOver();
}
point.unbindMouseOver = addEvent(graphic.element, 'mouseover', function () {
// Raise this point
if (point.stackIndex > 0 &&
!point.raised) {
point._y = graphic.y;
graphic.attr({
y: point._y - 8
});
point.raised = true;
}
// Revert other raised points
for (const otherPoint of points) {
if (otherPoint !== point &&
otherPoint.raised &&
otherPoint.graphic) {
otherPoint.graphic.attr({
y: otherPoint._y
});
otherPoint.raised = false;
}
}
});
}
}
}
/**
* Get presentational attributes
* @private
*/
pointAttribs(point, state) {
const options = this.options, color = (point && point.color) || this.color;
let lineColor = options.lineColor, lineWidth = (point && point.lineWidth), fill = (point && point.fillColor) || options.fillColor;
if (state) {
fill = options.states[state].fillColor;
lineColor = options.states[state].lineColor;
lineWidth = options.states[state].lineWidth;
}
return {
fill: fill || color,
stroke: lineColor || color,
'stroke-width': lineWidth || options.lineWidth || 0
};
}
/**
* @private
*/
setClip() {
Series.prototype.setClip.apply(this, arguments);
if (this.options.clip !== false &&
this.sharedClipKey &&
this.markerGroup) {
this.markerGroup.clip(this.chart.sharedClips[this.sharedClipKey]);
}
}
}
/* *
*
* Static Properties
*
* */
FlagsSeries.compose = FlagsSymbols.compose;
FlagsSeries.defaultOptions = merge(ColumnSeries.defaultOptions, FlagsSeriesDefaults);
OnSeriesComposition.compose(FlagsSeries);
extend(FlagsSeries.prototype, {
allowDG: false,
forceCrop: true,
invertible: false, // Flags series group should not be invertible (#14063).
noSharedTooltip: true,
pointClass: FlagsPoint,
sorted: false,
takeOrdinalPosition: false, // #1074
trackerGroups: ['markerGroup'],
buildKDTree: noop,
/**
* Inherit the initialization from base Series.
* @private
*/
init: Series.prototype.init
});
SeriesRegistry.registerSeriesType('flags', FlagsSeries);
/* *
*
* Default Export
*
* */
export default FlagsSeries;
/* *
*
* API Declarations
*
* */
/**
* @typedef {"circlepin"|"flag"|"squarepin"} Highcharts.FlagsShapeValue
*/
''; // Detach doclets above