windbarb.src.js 13 KB

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