windbarb.src.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. * Wind barb series module
  4. *
  5. * (c) 2010-2017 Torstein Honsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. var onSeriesMixin = (function (H) {
  18. /**
  19. * (c) 2010-2017 Torstein Honsi
  20. *
  21. * License: www.highcharts.com/license
  22. */
  23. var each = H.each,
  24. defined = H.defined,
  25. seriesTypes = H.seriesTypes,
  26. stableSort = H.stableSort;
  27. var onSeriesMixin = {
  28. /**
  29. * Override getPlotBox. If the onSeries option is valid, return the plot box
  30. * of the onSeries, otherwise proceed as usual.
  31. */
  32. getPlotBox: function () {
  33. return H.Series.prototype.getPlotBox.call(
  34. (
  35. this.options.onSeries &&
  36. this.chart.get(this.options.onSeries)
  37. ) || this
  38. );
  39. },
  40. /**
  41. * Extend the translate method by placing the point on the related series
  42. */
  43. translate: function () {
  44. seriesTypes.column.prototype.translate.apply(this);
  45. var series = this,
  46. options = series.options,
  47. chart = series.chart,
  48. points = series.points,
  49. cursor = points.length - 1,
  50. point,
  51. lastPoint,
  52. optionsOnSeries = options.onSeries,
  53. onSeries = optionsOnSeries && chart.get(optionsOnSeries),
  54. onKey = options.onKey || 'y',
  55. step = onSeries && onSeries.options.step,
  56. onData = onSeries && onSeries.points,
  57. i = onData && onData.length,
  58. inverted = chart.inverted,
  59. xAxis = series.xAxis,
  60. yAxis = series.yAxis,
  61. xOffset = 0,
  62. leftPoint,
  63. lastX,
  64. rightPoint,
  65. currentDataGrouping,
  66. distanceRatio;
  67. // relate to a master series
  68. if (onSeries && onSeries.visible && i) {
  69. xOffset = (onSeries.pointXOffset || 0) + (onSeries.barW || 0) / 2;
  70. currentDataGrouping = onSeries.currentDataGrouping;
  71. lastX = (
  72. onData[i - 1].x +
  73. (currentDataGrouping ? currentDataGrouping.totalRange : 0)
  74. ); // #2374
  75. // sort the data points
  76. stableSort(points, function (a, b) {
  77. return (a.x - b.x);
  78. });
  79. onKey = 'plot' + onKey[0].toUpperCase() + onKey.substr(1);
  80. while (i-- && points[cursor]) {
  81. leftPoint = onData[i];
  82. point = points[cursor];
  83. point.y = leftPoint.y;
  84. if (leftPoint.x <= point.x && leftPoint[onKey] !== undefined) {
  85. if (point.x <= lastX) { // #803
  86. point.plotY = leftPoint[onKey];
  87. // interpolate between points, #666
  88. if (leftPoint.x < point.x && !step) {
  89. rightPoint = onData[i + 1];
  90. if (rightPoint && rightPoint[onKey] !== undefined) {
  91. // the distance ratio, between 0 and 1
  92. distanceRatio = (point.x - leftPoint.x) /
  93. (rightPoint.x - leftPoint.x);
  94. point.plotY +=
  95. distanceRatio *
  96. // the plotY distance
  97. (rightPoint[onKey] - leftPoint[onKey]);
  98. point.y +=
  99. distanceRatio *
  100. (rightPoint.y - leftPoint.y);
  101. }
  102. }
  103. }
  104. cursor--;
  105. i++; // check again for points in the same x position
  106. if (cursor < 0) {
  107. break;
  108. }
  109. }
  110. }
  111. }
  112. // Add plotY position and handle stacking
  113. each(points, function (point, i) {
  114. var stackIndex;
  115. point.plotX += xOffset; // #2049
  116. // Undefined plotY means the point is either on axis, outside series
  117. // range or hidden series. If the series is outside the range of the
  118. // x axis it should fall through with an undefined plotY, but then
  119. // we must remove the shapeArgs (#847). For inverted charts, we need
  120. // to calculate position anyway, because series.invertGroups is not
  121. // defined
  122. if (point.plotY === undefined || inverted) {
  123. if (point.plotX >= 0 && point.plotX <= xAxis.len) {
  124. // We're inside xAxis range
  125. if (inverted) {
  126. point.plotY = xAxis.translate(point.x, 0, 1, 0, 1);
  127. point.plotX = defined(point.y) ?
  128. yAxis.translate(point.y, 0, 0, 0, 1) : 0;
  129. } else {
  130. point.plotY = chart.chartHeight - xAxis.bottom -
  131. (xAxis.opposite ? xAxis.height : 0) +
  132. xAxis.offset - yAxis.top; // #3517
  133. }
  134. } else {
  135. point.shapeArgs = {}; // 847
  136. }
  137. }
  138. // if multiple flags appear at the same x, order them into a stack
  139. lastPoint = points[i - 1];
  140. if (lastPoint && lastPoint.plotX === point.plotX) {
  141. if (lastPoint.stackIndex === undefined) {
  142. lastPoint.stackIndex = 0;
  143. }
  144. stackIndex = lastPoint.stackIndex + 1;
  145. }
  146. point.stackIndex = stackIndex; // #3639
  147. });
  148. this.onSeries = onSeries;
  149. }
  150. };
  151. return onSeriesMixin;
  152. }(Highcharts));
  153. (function (H, onSeriesMixin) {
  154. /**
  155. * Wind barb series module
  156. *
  157. * (c) 2010-2017 Torstein Honsi
  158. *
  159. * License: www.highcharts.com/license
  160. */
  161. var each = H.each,
  162. noop = H.noop,
  163. seriesType = H.seriesType;
  164. /**
  165. * Wind barbs are a convenient way to represent wind speed and direction in one
  166. * graphical form. Wind direction is given by the stem direction, and wind speed
  167. * by the number and shape of barbs.
  168. *
  169. * @extends {plotOptions.column}
  170. * @excluding boostThreshold,marker,connectEnds,connectNulls,cropThreshold,
  171. * dashStyle,gapSize,gapUnit,dataGrouping,linecap,shadow,stacking,
  172. * step
  173. * @product highcharts highstock
  174. * @sample {highcharts|highstock} highcharts/demo/windbarb-series/
  175. * Wind barb series
  176. * @since 6.0.0
  177. * @optionparent plotOptions.windbarb
  178. */
  179. seriesType('windbarb', 'column', {
  180. /**
  181. * The line width of the wind barb symbols.
  182. */
  183. lineWidth: 2,
  184. /**
  185. * The id of another series in the chart that the wind barbs are projected
  186. * on. When `null`, the wind symbols are drawn on the X axis, but offset
  187. * up or down by the `yOffset` setting.
  188. *
  189. * @sample {highcharts|highstock} highcharts/plotoptions/windbarb-onseries
  190. * Projected on area series
  191. * @type {String|null}
  192. */
  193. onSeries: null,
  194. states: {
  195. hover: {
  196. lineWidthPlus: 0
  197. }
  198. },
  199. tooltip: {
  200. /**
  201. * The default point format for the wind barb tooltip. Note the
  202. * `point.beaufort` property that refers to the Beaufort wind scale. The
  203. * names can be internationalized by modifying
  204. * `Highcharts.seriesTypes.windbarb.prototype.beaufortNames`.
  205. */
  206. pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.value}</b> ({point.beaufort})<br/>'
  207. },
  208. /**
  209. * Pixel length of the stems.
  210. */
  211. vectorLength: 20,
  212. /**
  213. * Vertical offset from the cartesian position, in pixels. The default value
  214. * makes sure the symbols don't overlap the X axis when `onSeries` is
  215. * `null`, and that they don't overlap the linked series when `onSeries` is
  216. * given.
  217. */
  218. yOffset: -20,
  219. /**
  220. * Horizontal offset from the cartesian position, in pixels. When the chart
  221. * is inverted, this option allows translation like
  222. * [yOffset](#plotOptions.windbarb.yOffset) in non inverted charts.
  223. *
  224. * @since 6.1.0
  225. */
  226. xOffset: 0
  227. }, {
  228. pointArrayMap: ['value', 'direction'],
  229. parallelArrays: ['x', 'value', 'direction'],
  230. beaufortName: ['Calm', 'Light air', 'Light breeze',
  231. 'Gentle breeze', 'Moderate breeze', 'Fresh breeze',
  232. 'Strong breeze', 'Near gale', 'Gale', 'Strong gale', 'Storm',
  233. 'Violent storm', 'Hurricane'],
  234. beaufortFloor: [0, 0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8,
  235. 24.5, 28.5, 32.7],
  236. trackerGroups: ['markerGroup'],
  237. /**
  238. * Get presentational attributes.
  239. */
  240. pointAttribs: function (point, state) {
  241. var options = this.options,
  242. stroke = point.color || this.color,
  243. strokeWidth = this.options.lineWidth;
  244. if (state) {
  245. stroke = options.states[state].color || stroke;
  246. strokeWidth =
  247. (options.states[state].lineWidth || strokeWidth) +
  248. (options.states[state].lineWidthPlus || 0);
  249. }
  250. return {
  251. 'stroke': stroke,
  252. 'stroke-width': strokeWidth
  253. };
  254. },
  255. markerAttribs: function () {
  256. return undefined;
  257. },
  258. getPlotBox: onSeriesMixin.getPlotBox,
  259. /**
  260. * Create a single wind arrow. It is later rotated around the zero
  261. * centerpoint.
  262. */
  263. windArrow: function (point) {
  264. var knots = point.value * 1.943844,
  265. level = point.beaufortLevel,
  266. path,
  267. barbs,
  268. u = this.options.vectorLength / 20,
  269. pos = -10;
  270. if (point.isNull) {
  271. return [];
  272. }
  273. if (level === 0) {
  274. return this.chart.renderer.symbols.circle(
  275. -10 * u,
  276. -10 * u,
  277. 20 * u,
  278. 20 * u
  279. );
  280. }
  281. // The stem and the arrow head
  282. path = [
  283. 'M', 0, 7 * u, // base of arrow
  284. 'L', -1.5 * u, 7 * u,
  285. 0, 10 * u,
  286. 1.5 * u, 7 * u,
  287. 0, 7 * u,
  288. 0, -10 * u// top
  289. ];
  290. // For each full 50 knots, add a pennant
  291. barbs = (knots - knots % 50) / 50; // pennants
  292. if (barbs > 0) {
  293. while (barbs--) {
  294. path.push(
  295. pos === -10 ? 'L' : 'M',
  296. 0,
  297. pos * u,
  298. 'L',
  299. 5 * u,
  300. pos * u + 2,
  301. 'L',
  302. 0,
  303. pos * u + 4
  304. );
  305. // Substract from the rest and move position for next
  306. knots -= 50;
  307. pos += 7;
  308. }
  309. }
  310. // For each full 10 knots, add a full barb
  311. barbs = (knots - knots % 10) / 10;
  312. if (barbs > 0) {
  313. while (barbs--) {
  314. path.push(
  315. pos === -10 ? 'L' : 'M',
  316. 0,
  317. pos * u,
  318. 'L',
  319. 7 * u,
  320. pos * u
  321. );
  322. knots -= 10;
  323. pos += 3;
  324. }
  325. }
  326. // For each full 5 knots, add a half barb
  327. barbs = (knots - knots % 5) / 5; // half barbs
  328. if (barbs > 0) {
  329. while (barbs--) {
  330. path.push(
  331. pos === -10 ? 'L' : 'M',
  332. 0,
  333. pos * u,
  334. 'L',
  335. 4 * u,
  336. pos * u
  337. );
  338. knots -= 5;
  339. pos += 3;
  340. }
  341. }
  342. return path;
  343. },
  344. translate: function () {
  345. var beaufortFloor = this.beaufortFloor,
  346. beaufortName = this.beaufortName;
  347. onSeriesMixin.translate.call(this);
  348. each(this.points, function (point) {
  349. var level = 0;
  350. // Find the beaufort level (zero based)
  351. for (; level < beaufortFloor.length; level++) {
  352. if (beaufortFloor[level] > point.value) {
  353. break;
  354. }
  355. }
  356. point.beaufortLevel = level - 1;
  357. point.beaufort = beaufortName[level - 1];
  358. });
  359. },
  360. drawPoints: function () {
  361. var chart = this.chart,
  362. yAxis = this.yAxis,
  363. inverted = chart.inverted,
  364. shapeOffset = this.options.vectorLength / 2;
  365. each(this.points, function (point) {
  366. var plotX = point.plotX,
  367. plotY = point.plotY;
  368. // Check if it's inside the plot area, but only for the X dimension.
  369. if (chart.isInsidePlot(plotX, 0, false)) {
  370. // Create the graphic the first time
  371. if (!point.graphic) {
  372. point.graphic = this.chart.renderer
  373. .path()
  374. .add(this.markerGroup);
  375. }
  376. // Position the graphic
  377. point.graphic
  378. .attr({
  379. d: this.windArrow(point),
  380. translateX: plotX + this.options.xOffset,
  381. translateY: plotY + this.options.yOffset,
  382. rotation: point.direction
  383. })
  384. .attr(this.pointAttribs(point));
  385. } else if (point.graphic) {
  386. point.graphic = point.graphic.destroy();
  387. }
  388. // Set the tooltip anchor position
  389. point.tooltipPos = [
  390. plotX + this.options.xOffset + (inverted && !this.onSeries ?
  391. shapeOffset : 0),
  392. plotY + this.options.yOffset - (inverted ? 0 :
  393. shapeOffset + yAxis.pos - chart.plotTop)
  394. ]; // #6327
  395. }, this);
  396. },
  397. /**
  398. * Fade in the arrows on initiating series.
  399. */
  400. animate: function (init) {
  401. if (init) {
  402. this.markerGroup.attr({
  403. opacity: 0.01
  404. });
  405. } else {
  406. this.markerGroup.animate({
  407. opacity: 1
  408. }, H.animObject(this.options.animation));
  409. this.animate = null;
  410. }
  411. },
  412. /**
  413. * Don't invert the marker group (#4960)
  414. */
  415. invertGroups: noop
  416. }, {
  417. isValid: function () {
  418. return H.isNumber(this.value) && this.value >= 0;
  419. }
  420. });
  421. /**
  422. * A `windbarb` series. If the [type](#series.windbarb.type) option is not
  423. * specified, it is inherited from [chart.type](#chart.type).
  424. *
  425. * @type {Object}
  426. * @extends series,plotOptions.windbarb
  427. * @excluding dataParser,dataURL
  428. * @product highcharts highstock
  429. * @apioption series.windbarb
  430. */
  431. /**
  432. * An array of data points for the series. For the `windbarb` series type,
  433. * points can be given in the following ways:
  434. *
  435. * 1. An array of arrays with 3 values. In this case, the values correspond
  436. * to `x,value,direction`. If the first value is a string, it is applied as
  437. * the name of the point, and the `x` value is inferred.
  438. *
  439. * ```js
  440. * data: [
  441. * [Date.UTC(2017, 0, 1, 0), 3.3, 90],
  442. * [Date.UTC(2017, 0, 1, 1), 12.1, 180],
  443. * [Date.UTC(2017, 0, 1, 2), 11.1, 270]
  444. * ]
  445. * ```
  446. *
  447. * 2. An array of objects with named values. The objects are point
  448. * configuration objects as seen below. If the total number of data
  449. * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
  450. * this option is not available.
  451. *
  452. * ```js
  453. * data: [{
  454. * x: Date.UTC(2017, 0, 1, 0),
  455. * value: 12.1,
  456. * direction: 90
  457. * }, {
  458. * x: Date.UTC(2017, 0, 1, 1),
  459. * value: 11.1,
  460. * direction: 270
  461. * }]
  462. * ```
  463. *
  464. * @type {Array<Object|Array|Number>}
  465. * @extends series.line.data
  466. * @sample {highcharts} highcharts/chart/reflow-true/
  467. * Numerical values
  468. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  469. * Arrays of numeric x and y
  470. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  471. * Arrays of datetime x and y
  472. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  473. * Arrays of point.name and y
  474. * @sample {highcharts} highcharts/series/data-array-of-objects/
  475. * Config objects
  476. * @product highcharts highstock
  477. * @apioption series.windbarb.data
  478. */
  479. /**
  480. * The wind speed in meters per second.
  481. *
  482. * @type {Number}
  483. * @product highcharts highstock
  484. * @apioption series.windbarb.data.value
  485. */
  486. /**
  487. * The wind direction in degrees, where 0 is north (pointing towards south).
  488. *
  489. * @type {Number}
  490. * @product highcharts highstock
  491. * @apioption series.windbarb.data.direction
  492. */
  493. }(Highcharts, onSeriesMixin));
  494. }));