highcharts
Version:
JavaScript charting framework
255 lines (254 loc) • 8.53 kB
JavaScript
/* *
*
* Wind barb series module
*
* (c) 2010-2025 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import A from '../../Core/Animation/AnimationUtilities.js';
const { animObject } = A;
import ApproximationRegistry from '../../Extensions/DataGrouping/ApproximationRegistry.js';
import H from '../../Core/Globals.js';
import OnSeriesComposition from '../OnSeriesComposition.js';
import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
const { column: ColumnSeries } = SeriesRegistry.seriesTypes;
import U from '../../Core/Utilities.js';
const { extend, merge, pick } = U;
import WindbarbPoint from './WindbarbPoint.js';
import WindbarbSeriesDefaults from './WindbarbSeriesDefaults.js';
/* *
*
* Functions
*
* */
/**
* Once off, register the windbarb approximation for data grouping. This can
* be called anywhere (not necessarily in the translate function), but must
* happen after the data grouping module is loaded and before the
* wind barb series uses it.
* @private
*/
function registerApproximation() {
if (!ApproximationRegistry.windbarb) {
ApproximationRegistry.windbarb = (values, directions) => {
let vectorX = 0, vectorY = 0;
for (let i = 0, iEnd = values.length; i < iEnd; i++) {
vectorX += values[i] * Math.cos(directions[i] * H.deg2rad);
vectorY += values[i] * Math.sin(directions[i] * H.deg2rad);
}
return [
// Wind speed
values.reduce((sum, value) => (sum + value), 0) / values.length,
// Wind direction
Math.atan2(vectorY, vectorX) / H.deg2rad
];
};
}
}
/* *
*
* Class
*
* */
/**
* @private
* @class
* @name Highcharts.seriesTypes.windbarb
*
* @augments Highcharts.Series
*/
class WindbarbSeries extends ColumnSeries {
/* *
*
* Functions
*
* */
init(chart, options) {
super.init(chart, options);
}
// Get presentational attributes.
pointAttribs(point, state) {
const options = this.options;
let stroke = point.color || this.color, strokeWidth = this.options.lineWidth;
if (state) {
stroke = options.states[state].color || stroke;
strokeWidth =
(options.states[state].lineWidth || strokeWidth) +
(options.states[state].lineWidthPlus || 0);
}
return {
'stroke': stroke,
'stroke-width': strokeWidth
};
}
// Create a single wind arrow. It is later rotated around the zero
// centerpoint.
windArrow(point) {
const level = point.beaufortLevel, u = this.options.vectorLength / 20;
let knots = point.value * 1.943844, barbs, pos = -10;
if (point.isNull) {
return [];
}
if (level === 0) {
return this.chart.renderer.symbols.circle(-10 * u, -10 * u, 20 * u, 20 * u);
}
// The stem and the arrow head
const path = [
['M', 0, 7 * u], // Base of arrow
['L', -1.5 * u, 7 * u],
['L', 0, 10 * u],
['L', 1.5 * u, 7 * u],
['L', 0, 7 * u],
['L', 0, -10 * u] // Top
];
// For each full 50 knots, add a pennant
barbs = (knots - knots % 50) / 50; // Pennants
if (barbs > 0) {
while (barbs--) {
path.push(pos === -10 ? ['L', 0, pos * u] : ['M', 0, pos * u], ['L', 5 * u, pos * u + 2], ['L', 0, pos * u + 4]);
// Substract from the rest and move position for next
knots -= 50;
pos += 7;
}
}
// For each full 10 knots, add a full barb
barbs = (knots - knots % 10) / 10;
if (barbs > 0) {
while (barbs--) {
path.push(pos === -10 ? ['L', 0, pos * u] : ['M', 0, pos * u], ['L', 7 * u, pos * u]);
knots -= 10;
pos += 3;
}
}
// For each full 5 knots, add a half barb
barbs = (knots - knots % 5) / 5; // Half barbs
if (barbs > 0) {
while (barbs--) {
path.push(pos === -10 ? ['L', 0, pos * u] : ['M', 0, pos * u], ['L', 4 * u, pos * u]);
knots -= 5;
pos += 3;
}
}
return path;
}
drawPoints() {
const chart = this.chart, yAxis = this.yAxis, inverted = chart.inverted, shapeOffset = this.options.vectorLength / 2;
for (const point of this.points) {
const plotX = point.plotX, plotY = point.plotY;
// Check if it's inside the plot area, but only for the X
// dimension.
if (this.options.clip === false ||
chart.isInsidePlot(plotX, 0)) {
// Create the graphic the first time
if (!point.graphic) {
point.graphic = this.chart.renderer
.path()
.add(this.markerGroup)
.addClass('highcharts-point ' +
'highcharts-color-' +
pick(point.colorIndex, point.series.colorIndex));
}
// Position the graphic
point.graphic
.attr({
d: this.windArrow(point),
translateX: plotX + this.options.xOffset,
translateY: plotY + this.options.yOffset,
rotation: point.direction
});
if (!this.chart.styledMode) {
point.graphic
.attr(this.pointAttribs(point));
}
}
else if (point.graphic) {
point.graphic = point.graphic.destroy();
}
// Set the tooltip anchor position
point.tooltipPos = [
plotX + this.options.xOffset +
(inverted && !this.onSeries ? shapeOffset : 0),
plotY + this.options.yOffset -
(inverted ?
0 :
shapeOffset + yAxis.pos - chart.plotTop)
]; // #6327
}
}
// Fade in the arrows on initializing series.
animate(init) {
if (init) {
this.markerGroup.attr({
opacity: 0.01
});
}
else {
this.markerGroup.animate({
opacity: 1
}, animObject(this.options.animation));
}
}
markerAttribs() {
return {};
}
getExtremes() {
return {};
}
shouldShowTooltip(plotX, plotY, options = {}) {
options.ignoreX = this.chart.inverted;
options.ignoreY = !options.ignoreX;
return super.shouldShowTooltip(plotX, plotY, options);
}
}
/* *
*
* Static Properties
*
* */
WindbarbSeries.defaultOptions = merge(ColumnSeries.defaultOptions, WindbarbSeriesDefaults);
OnSeriesComposition.compose(WindbarbSeries);
extend(WindbarbSeries.prototype, {
beaufortFloor: [
0, 0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8,
24.5, 28.5, 32.7
], // @todo dictionary with names?
beaufortName: [
'Calm', 'Light air', 'Light breeze',
'Gentle breeze', 'Moderate breeze', 'Fresh breeze',
'Strong breeze', 'Near gale', 'Gale', 'Strong gale', 'Storm',
'Violent storm', 'Hurricane'
],
invertible: false,
parallelArrays: ['x', 'value', 'direction'],
pointArrayMap: ['value', 'direction'],
pointClass: WindbarbPoint,
trackerGroups: ['markerGroup'],
translate: function () {
const beaufortFloor = this.beaufortFloor, beaufortName = this.beaufortName;
OnSeriesComposition.translate.call(this);
for (const point of this.points) {
let level = 0;
// Find the beaufort level (zero based)
for (; level < beaufortFloor.length; level++) {
if (beaufortFloor[level] > point.value) {
break;
}
}
point.beaufortLevel = level - 1;
point.beaufort = beaufortName[level - 1];
}
}
});
SeriesRegistry.registerSeriesType('windbarb', WindbarbSeries);
registerApproximation();
/* *
*
* Default Export
*
* */
export default WindbarbSeries;