WindbarbSeries.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /* *
  2. *
  3. * Wind barb series module
  4. *
  5. * (c) 2010-2020 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import H from '../Core/Globals.js';
  14. import U from '../Core/Utilities.js';
  15. var animObject = U.animObject, isNumber = U.isNumber, pick = U.pick, seriesType = U.seriesType;
  16. import onSeriesMixin from '../Mixins/OnSeries.js';
  17. import './ColumnSeries.js';
  18. var noop = H.noop;
  19. // eslint-disable-next-line valid-jsdoc
  20. /**
  21. * Once off, register the windbarb approximation for data grouping. This can be
  22. * called anywhere (not necessarily in the translate function), but must happen
  23. * after the data grouping module is loaded and before the wind barb series uses
  24. * it.
  25. * @private
  26. */
  27. function registerApproximation() {
  28. if (H.approximations && !H.approximations.windbarb) {
  29. H.approximations.windbarb = function (values, directions) {
  30. var vectorX = 0, vectorY = 0, i, len = values.length;
  31. for (i = 0; i < len; i++) {
  32. vectorX += values[i] * Math.cos(directions[i] * H.deg2rad);
  33. vectorY += values[i] * Math.sin(directions[i] * H.deg2rad);
  34. }
  35. return [
  36. // Wind speed
  37. values.reduce(function (sum, value) {
  38. return sum + value;
  39. }, 0) / values.length,
  40. // Wind direction
  41. Math.atan2(vectorY, vectorX) / H.deg2rad
  42. ];
  43. };
  44. }
  45. }
  46. registerApproximation();
  47. /**
  48. * @private
  49. * @class
  50. * @name Highcharts.seriesTypes.windbarb
  51. *
  52. * @augments Highcharts.Series
  53. */
  54. seriesType('windbarb', 'column'
  55. /**
  56. * Wind barbs are a convenient way to represent wind speed and direction in
  57. * one graphical form. Wind direction is given by the stem direction, and
  58. * wind speed by the number and shape of barbs.
  59. *
  60. * @sample {highcharts|highstock} highcharts/demo/windbarb-series/
  61. * Wind barb series
  62. *
  63. * @extends plotOptions.column
  64. * @excluding boostThreshold, marker, connectEnds, connectNulls,
  65. * cropThreshold, dashStyle, dragDrop, gapSize, gapUnit,
  66. * linecap, shadow, stacking, step, boostBlending
  67. * @since 6.0.0
  68. * @product highcharts highstock
  69. * @requires modules/windbarb
  70. * @optionparent plotOptions.windbarb
  71. */
  72. , {
  73. /**
  74. * Data grouping options for the wind barbs. In Highcharts, this
  75. * requires the `modules/datagrouping.js` module to be loaded. In
  76. * Highstock, data grouping is included.
  77. *
  78. * @sample highcharts/plotoptions/windbarb-datagrouping
  79. * Wind barb with data grouping
  80. *
  81. * @since 7.1.0
  82. * @product highcharts highstock
  83. */
  84. dataGrouping: {
  85. /**
  86. * Whether to enable data grouping.
  87. *
  88. * @product highcharts highstock
  89. */
  90. enabled: true,
  91. /**
  92. * Approximation function for the data grouping. The default
  93. * returns an average of wind speed and a vector average direction
  94. * weighted by wind speed.
  95. *
  96. * @product highcharts highstock
  97. *
  98. * @type {string|Function}
  99. */
  100. approximation: 'windbarb',
  101. /**
  102. * The approximate data group width.
  103. *
  104. * @product highcharts highstock
  105. */
  106. groupPixelWidth: 30
  107. },
  108. /**
  109. * The line width of the wind barb symbols.
  110. */
  111. lineWidth: 2,
  112. /**
  113. * The id of another series in the chart that the wind barbs are
  114. * projected on. When `null`, the wind symbols are drawn on the X axis,
  115. * but offset up or down by the `yOffset` setting.
  116. *
  117. * @sample {highcharts|highstock} highcharts/plotoptions/windbarb-onseries
  118. * Projected on area series
  119. *
  120. * @type {string|null}
  121. */
  122. onSeries: null,
  123. states: {
  124. hover: {
  125. lineWidthPlus: 0
  126. }
  127. },
  128. tooltip: {
  129. /**
  130. * The default point format for the wind barb tooltip. Note the
  131. * `point.beaufort` property that refers to the Beaufort wind scale.
  132. * The names can be internationalized by modifying
  133. * `Highcharts.seriesTypes.windbarb.prototype.beaufortNames`.
  134. */
  135. pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.value}</b> ({point.beaufort})<br/>'
  136. },
  137. /**
  138. * Pixel length of the stems.
  139. */
  140. vectorLength: 20,
  141. /**
  142. * @default value
  143. */
  144. colorKey: 'value',
  145. /**
  146. * Vertical offset from the cartesian position, in pixels. The default
  147. * value makes sure the symbols don't overlap the X axis when `onSeries`
  148. * is `null`, and that they don't overlap the linked series when
  149. * `onSeries` is given.
  150. */
  151. yOffset: -20,
  152. /**
  153. * Horizontal offset from the cartesian position, in pixels. When the
  154. * chart is inverted, this option allows translation like
  155. * [yOffset](#plotOptions.windbarb.yOffset) in non inverted charts.
  156. *
  157. * @since 6.1.0
  158. */
  159. xOffset: 0
  160. }, {
  161. pointArrayMap: ['value', 'direction'],
  162. parallelArrays: ['x', 'value', 'direction'],
  163. beaufortName: ['Calm', 'Light air', 'Light breeze',
  164. 'Gentle breeze', 'Moderate breeze', 'Fresh breeze',
  165. 'Strong breeze', 'Near gale', 'Gale', 'Strong gale', 'Storm',
  166. 'Violent storm', 'Hurricane'],
  167. beaufortFloor: [0, 0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8,
  168. 24.5, 28.5, 32.7],
  169. trackerGroups: ['markerGroup'],
  170. init: function (chart, options) {
  171. registerApproximation();
  172. H.Series.prototype.init.call(this, chart, options);
  173. },
  174. // Get presentational attributes.
  175. pointAttribs: function (point, state) {
  176. var options = this.options, stroke = point.color || this.color, strokeWidth = this.options.lineWidth;
  177. if (state) {
  178. stroke = options.states[state].color || stroke;
  179. strokeWidth =
  180. (options.states[state].lineWidth || strokeWidth) +
  181. (options.states[state].lineWidthPlus || 0);
  182. }
  183. return {
  184. 'stroke': stroke,
  185. 'stroke-width': strokeWidth
  186. };
  187. },
  188. markerAttribs: function () {
  189. return;
  190. },
  191. getPlotBox: onSeriesMixin.getPlotBox,
  192. // Create a single wind arrow. It is later rotated around the zero
  193. // centerpoint.
  194. windArrow: function (point) {
  195. var knots = point.value * 1.943844, level = point.beaufortLevel, path, barbs, u = this.options.vectorLength / 20, pos = -10;
  196. if (point.isNull) {
  197. return [];
  198. }
  199. if (level === 0) {
  200. return this.chart.renderer.symbols.circle(-10 * u, -10 * u, 20 * u, 20 * u);
  201. }
  202. // The stem and the arrow head
  203. path = [
  204. ['M', 0, 7 * u],
  205. ['L', -1.5 * u, 7 * u],
  206. ['L', 0, 10 * u],
  207. ['L', 1.5 * u, 7 * u],
  208. ['L', 0, 7 * u],
  209. ['L', 0, -10 * u] // top
  210. ];
  211. // For each full 50 knots, add a pennant
  212. barbs = (knots - knots % 50) / 50; // pennants
  213. if (barbs > 0) {
  214. while (barbs--) {
  215. path.push(pos === -10 ? ['L', 0, pos * u] : ['M', 0, pos * u], ['L', 5 * u, pos * u + 2], ['L', 0, pos * u + 4]);
  216. // Substract from the rest and move position for next
  217. knots -= 50;
  218. pos += 7;
  219. }
  220. }
  221. // For each full 10 knots, add a full barb
  222. barbs = (knots - knots % 10) / 10;
  223. if (barbs > 0) {
  224. while (barbs--) {
  225. path.push(pos === -10 ? ['L', 0, pos * u] : ['M', 0, pos * u], ['L', 7 * u, pos * u]);
  226. knots -= 10;
  227. pos += 3;
  228. }
  229. }
  230. // For each full 5 knots, add a half barb
  231. barbs = (knots - knots % 5) / 5; // half barbs
  232. if (barbs > 0) {
  233. while (barbs--) {
  234. path.push(pos === -10 ? ['L', 0, pos * u] : ['M', 0, pos * u], ['L', 4 * u, pos * u]);
  235. knots -= 5;
  236. pos += 3;
  237. }
  238. }
  239. return path;
  240. },
  241. translate: function () {
  242. var beaufortFloor = this.beaufortFloor, beaufortName = this.beaufortName;
  243. onSeriesMixin.translate.call(this);
  244. this.points.forEach(function (point) {
  245. var level = 0;
  246. // Find the beaufort level (zero based)
  247. for (; level < beaufortFloor.length; level++) {
  248. if (beaufortFloor[level] > point.value) {
  249. break;
  250. }
  251. }
  252. point.beaufortLevel = level - 1;
  253. point.beaufort = beaufortName[level - 1];
  254. });
  255. },
  256. drawPoints: function () {
  257. var chart = this.chart, yAxis = this.yAxis, inverted = chart.inverted, shapeOffset = this.options.vectorLength / 2;
  258. this.points.forEach(function (point) {
  259. var plotX = point.plotX, plotY = point.plotY;
  260. // Check if it's inside the plot area, but only for the X
  261. // dimension.
  262. if (this.options.clip === false ||
  263. chart.isInsidePlot(plotX, 0, false)) {
  264. // Create the graphic the first time
  265. if (!point.graphic) {
  266. point.graphic = this.chart.renderer
  267. .path()
  268. .add(this.markerGroup)
  269. .addClass('highcharts-point ' +
  270. 'highcharts-color-' +
  271. pick(point.colorIndex, point.series.colorIndex));
  272. }
  273. // Position the graphic
  274. point.graphic
  275. .attr({
  276. d: this.windArrow(point),
  277. translateX: plotX + this.options.xOffset,
  278. translateY: plotY + this.options.yOffset,
  279. rotation: point.direction
  280. });
  281. if (!this.chart.styledMode) {
  282. point.graphic
  283. .attr(this.pointAttribs(point));
  284. }
  285. }
  286. else if (point.graphic) {
  287. point.graphic = point.graphic.destroy();
  288. }
  289. // Set the tooltip anchor position
  290. point.tooltipPos = [
  291. plotX + this.options.xOffset +
  292. (inverted && !this.onSeries ? shapeOffset : 0),
  293. plotY + this.options.yOffset -
  294. (inverted ?
  295. 0 :
  296. shapeOffset + yAxis.pos - chart.plotTop)
  297. ]; // #6327
  298. }, this);
  299. },
  300. // Fade in the arrows on initializing series.
  301. animate: function (init) {
  302. if (init) {
  303. this.markerGroup.attr({
  304. opacity: 0.01
  305. });
  306. }
  307. else {
  308. this.markerGroup.animate({
  309. opacity: 1
  310. }, animObject(this.options.animation));
  311. }
  312. },
  313. // Don't invert the marker group (#4960)
  314. invertGroups: noop,
  315. // No data extremes for the Y axis
  316. getExtremes: function () { return ({}); }
  317. }, {
  318. isValid: function () {
  319. return isNumber(this.value) && this.value >= 0;
  320. }
  321. });
  322. /**
  323. * A `windbarb` series. If the [type](#series.windbarb.type) option is not
  324. * specified, it is inherited from [chart.type](#chart.type).
  325. *
  326. * @extends series,plotOptions.windbarb
  327. * @excluding dataParser, dataURL, boostThreshold, boostBlending
  328. * @product highcharts highstock
  329. * @requires modules/windbarb
  330. * @apioption series.windbarb
  331. */
  332. /**
  333. * An array of data points for the series. For the `windbarb` series type,
  334. * points can be given in the following ways:
  335. *
  336. * 1. An array of arrays with 3 values. In this case, the values correspond to
  337. * `x,value,direction`. If the first value is a string, it is applied as the
  338. * name of the point, and the `x` value is inferred.
  339. * ```js
  340. * data: [
  341. * [Date.UTC(2017, 0, 1, 0), 3.3, 90],
  342. * [Date.UTC(2017, 0, 1, 1), 12.1, 180],
  343. * [Date.UTC(2017, 0, 1, 2), 11.1, 270]
  344. * ]
  345. * ```
  346. *
  347. * 2. An array of objects with named values. The following snippet shows only a
  348. * few settings, see the complete options set below. If the total number of
  349. * data points exceeds the series'
  350. * [turboThreshold](#series.area.turboThreshold), this option is not
  351. * available.
  352. * ```js
  353. * data: [{
  354. * x: Date.UTC(2017, 0, 1, 0),
  355. * value: 12.1,
  356. * direction: 90
  357. * }, {
  358. * x: Date.UTC(2017, 0, 1, 1),
  359. * value: 11.1,
  360. * direction: 270
  361. * }]
  362. * ```
  363. *
  364. * @sample {highcharts} highcharts/chart/reflow-true/
  365. * Numerical values
  366. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  367. * Arrays of numeric x and y
  368. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  369. * Arrays of datetime x and y
  370. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  371. * Arrays of point.name and y
  372. * @sample {highcharts} highcharts/series/data-array-of-objects/
  373. * Config objects
  374. *
  375. * @type {Array<Array<(number|string),number,number>|*>}
  376. * @extends series.line.data
  377. * @product highcharts highstock
  378. * @apioption series.windbarb.data
  379. */
  380. /**
  381. * The wind speed in meters per second.
  382. *
  383. * @type {number|null}
  384. * @product highcharts highstock
  385. * @apioption series.windbarb.data.value
  386. */
  387. /**
  388. * The wind direction in degrees, where 0 is north (pointing towards south).
  389. *
  390. * @type {number}
  391. * @product highcharts highstock
  392. * @apioption series.windbarb.data.direction
  393. */
  394. ''; // adds doclets above to transpiled file