Connection.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. /* *
  2. *
  3. * (c) 2016 Highsoft AS
  4. * Authors: Øystein Moseng, Lars A. V. Cabrera
  5. *
  6. * License: www.highcharts.com/license
  7. *
  8. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  9. *
  10. * */
  11. 'use strict';
  12. import H from '../Core/Globals.js';
  13. /**
  14. * The default pathfinder algorithm to use for a chart. It is possible to define
  15. * your own algorithms by adding them to the
  16. * `Highcharts.Pathfinder.prototype.algorithms`
  17. * object before the chart has been created.
  18. *
  19. * The default algorithms are as follows:
  20. *
  21. * `straight`: Draws a straight line between the connecting
  22. * points. Does not avoid other points when drawing.
  23. *
  24. * `simpleConnect`: Finds a path between the points using right angles
  25. * only. Takes only starting/ending points into
  26. * account, and will not avoid other points.
  27. *
  28. * `fastAvoid`: Finds a path between the points using right angles
  29. * only. Will attempt to avoid other points, but its
  30. * focus is performance over accuracy. Works well with
  31. * less dense datasets.
  32. *
  33. * @typedef {"fastAvoid"|"simpleConnect"|"straight"|string} Highcharts.PathfinderTypeValue
  34. */
  35. ''; // detach doclets above
  36. import D from '../Core/DefaultOptions.js';
  37. var defaultOptions = D.defaultOptions;
  38. import Point from '../Core/Series/Point.js';
  39. import U from '../Core/Utilities.js';
  40. var addEvent = U.addEvent, defined = U.defined, error = U.error, extend = U.extend, merge = U.merge, objectEach = U.objectEach, pick = U.pick, splat = U.splat;
  41. import '../Extensions/ArrowSymbols.js';
  42. var deg2rad = H.deg2rad, max = Math.max, min = Math.min;
  43. /*
  44. @todo:
  45. - Document how to write your own algorithms
  46. - Consider adding a Point.pathTo method that wraps creating a connection
  47. and rendering it
  48. */
  49. // Set default Pathfinder options
  50. extend(defaultOptions, {
  51. /**
  52. * The Pathfinder module allows you to define connections between any two
  53. * points, represented as lines - optionally with markers for the start
  54. * and/or end points. Multiple algorithms are available for calculating how
  55. * the connecting lines are drawn.
  56. *
  57. * Connector functionality requires Highcharts Gantt to be loaded. In Gantt
  58. * charts, the connectors are used to draw dependencies between tasks.
  59. *
  60. * @see [dependency](series.gantt.data.dependency)
  61. *
  62. * @sample gantt/pathfinder/demo
  63. * Pathfinder connections
  64. *
  65. * @declare Highcharts.ConnectorsOptions
  66. * @product gantt
  67. * @optionparent connectors
  68. */
  69. connectors: {
  70. /**
  71. * Enable connectors for this chart. Requires Highcharts Gantt.
  72. *
  73. * @type {boolean}
  74. * @default true
  75. * @since 6.2.0
  76. * @apioption connectors.enabled
  77. */
  78. /**
  79. * Set the default dash style for this chart's connecting lines.
  80. *
  81. * @type {string}
  82. * @default solid
  83. * @since 6.2.0
  84. * @apioption connectors.dashStyle
  85. */
  86. /**
  87. * Set the default color for this chart's Pathfinder connecting lines.
  88. * Defaults to the color of the point being connected.
  89. *
  90. * @type {Highcharts.ColorString}
  91. * @since 6.2.0
  92. * @apioption connectors.lineColor
  93. */
  94. /**
  95. * Set the default pathfinder margin to use, in pixels. Some Pathfinder
  96. * algorithms attempt to avoid obstacles, such as other points in the
  97. * chart. These algorithms use this margin to determine how close lines
  98. * can be to an obstacle. The default is to compute this automatically
  99. * from the size of the obstacles in the chart.
  100. *
  101. * To draw connecting lines close to existing points, set this to a low
  102. * number. For more space around existing points, set this number
  103. * higher.
  104. *
  105. * @sample gantt/pathfinder/algorithm-margin
  106. * Small algorithmMargin
  107. *
  108. * @type {number}
  109. * @since 6.2.0
  110. * @apioption connectors.algorithmMargin
  111. */
  112. /**
  113. * Set the default pathfinder algorithm to use for this chart. It is
  114. * possible to define your own algorithms by adding them to the
  115. * Highcharts.Pathfinder.prototype.algorithms object before the chart
  116. * has been created.
  117. *
  118. * The default algorithms are as follows:
  119. *
  120. * `straight`: Draws a straight line between the connecting
  121. * points. Does not avoid other points when drawing.
  122. *
  123. * `simpleConnect`: Finds a path between the points using right angles
  124. * only. Takes only starting/ending points into
  125. * account, and will not avoid other points.
  126. *
  127. * `fastAvoid`: Finds a path between the points using right angles
  128. * only. Will attempt to avoid other points, but its
  129. * focus is performance over accuracy. Works well with
  130. * less dense datasets.
  131. *
  132. * Default value: `straight` is used as default for most series types,
  133. * while `simpleConnect` is used as default for Gantt series, to show
  134. * dependencies between points.
  135. *
  136. * @sample gantt/pathfinder/demo
  137. * Different types used
  138. *
  139. * @type {Highcharts.PathfinderTypeValue}
  140. * @default undefined
  141. * @since 6.2.0
  142. */
  143. type: 'straight',
  144. /**
  145. * Set the default pixel width for this chart's Pathfinder connecting
  146. * lines.
  147. *
  148. * @since 6.2.0
  149. */
  150. lineWidth: 1,
  151. /**
  152. * Marker options for this chart's Pathfinder connectors. Note that
  153. * this option is overridden by the `startMarker` and `endMarker`
  154. * options.
  155. *
  156. * @declare Highcharts.ConnectorsMarkerOptions
  157. * @since 6.2.0
  158. */
  159. marker: {
  160. /**
  161. * Set the radius of the connector markers. The default is
  162. * automatically computed based on the algorithmMargin setting.
  163. *
  164. * Setting marker.width and marker.height will override this
  165. * setting.
  166. *
  167. * @type {number}
  168. * @since 6.2.0
  169. * @apioption connectors.marker.radius
  170. */
  171. /**
  172. * Set the width of the connector markers. If not supplied, this
  173. * is inferred from the marker radius.
  174. *
  175. * @type {number}
  176. * @since 6.2.0
  177. * @apioption connectors.marker.width
  178. */
  179. /**
  180. * Set the height of the connector markers. If not supplied, this
  181. * is inferred from the marker radius.
  182. *
  183. * @type {number}
  184. * @since 6.2.0
  185. * @apioption connectors.marker.height
  186. */
  187. /**
  188. * Set the color of the connector markers. By default this is the
  189. * same as the connector color.
  190. *
  191. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  192. * @since 6.2.0
  193. * @apioption connectors.marker.color
  194. */
  195. /**
  196. * Set the line/border color of the connector markers. By default
  197. * this is the same as the marker color.
  198. *
  199. * @type {Highcharts.ColorString}
  200. * @since 6.2.0
  201. * @apioption connectors.marker.lineColor
  202. */
  203. /**
  204. * Enable markers for the connectors.
  205. */
  206. enabled: false,
  207. /**
  208. * Horizontal alignment of the markers relative to the points.
  209. *
  210. * @type {Highcharts.AlignValue}
  211. */
  212. align: 'center',
  213. /**
  214. * Vertical alignment of the markers relative to the points.
  215. *
  216. * @type {Highcharts.VerticalAlignValue}
  217. */
  218. verticalAlign: 'middle',
  219. /**
  220. * Whether or not to draw the markers inside the points.
  221. */
  222. inside: false,
  223. /**
  224. * Set the line/border width of the pathfinder markers.
  225. */
  226. lineWidth: 1
  227. },
  228. /**
  229. * Marker options specific to the start markers for this chart's
  230. * Pathfinder connectors. Overrides the generic marker options.
  231. *
  232. * @declare Highcharts.ConnectorsStartMarkerOptions
  233. * @extends connectors.marker
  234. * @since 6.2.0
  235. */
  236. startMarker: {
  237. /**
  238. * Set the symbol of the connector start markers.
  239. */
  240. symbol: 'diamond'
  241. },
  242. /**
  243. * Marker options specific to the end markers for this chart's
  244. * Pathfinder connectors. Overrides the generic marker options.
  245. *
  246. * @declare Highcharts.ConnectorsEndMarkerOptions
  247. * @extends connectors.marker
  248. * @since 6.2.0
  249. */
  250. endMarker: {
  251. /**
  252. * Set the symbol of the connector end markers.
  253. */
  254. symbol: 'arrow-filled'
  255. }
  256. }
  257. });
  258. /**
  259. * Override Pathfinder connector options for a series. Requires Highcharts Gantt
  260. * to be loaded.
  261. *
  262. * @declare Highcharts.SeriesConnectorsOptionsObject
  263. * @extends connectors
  264. * @since 6.2.0
  265. * @excluding enabled, algorithmMargin
  266. * @product gantt
  267. * @apioption plotOptions.series.connectors
  268. */
  269. /**
  270. * Connect to a point. This option can be either a string, referring to the ID
  271. * of another point, or an object, or an array of either. If the option is an
  272. * array, each element defines a connection.
  273. *
  274. * @sample gantt/pathfinder/demo
  275. * Different connection types
  276. *
  277. * @declare Highcharts.XrangePointConnectorsOptionsObject
  278. * @type {string|Array<string|*>|*}
  279. * @extends plotOptions.series.connectors
  280. * @since 6.2.0
  281. * @excluding enabled
  282. * @product gantt
  283. * @requires highcharts-gantt
  284. * @apioption series.xrange.data.connect
  285. */
  286. /**
  287. * The ID of the point to connect to.
  288. *
  289. * @type {string}
  290. * @since 6.2.0
  291. * @product gantt
  292. * @apioption series.xrange.data.connect.to
  293. */
  294. /**
  295. * Get point bounding box using plotX/plotY and shapeArgs. If using
  296. * graphic.getBBox() directly, the bbox will be affected by animation.
  297. *
  298. * @private
  299. * @function
  300. *
  301. * @param {Highcharts.Point} point
  302. * The point to get BB of.
  303. *
  304. * @return {Highcharts.Dictionary<number>|null}
  305. * Result xMax, xMin, yMax, yMin.
  306. */
  307. function getPointBB(point) {
  308. var shapeArgs = point.shapeArgs, bb;
  309. // Prefer using shapeArgs (columns)
  310. if (shapeArgs) {
  311. return {
  312. xMin: shapeArgs.x || 0,
  313. xMax: (shapeArgs.x || 0) + (shapeArgs.width || 0),
  314. yMin: shapeArgs.y || 0,
  315. yMax: (shapeArgs.y || 0) + (shapeArgs.height || 0)
  316. };
  317. }
  318. // Otherwise use plotX/plotY and bb
  319. bb = point.graphic && point.graphic.getBBox();
  320. return bb ? {
  321. xMin: point.plotX - bb.width / 2,
  322. xMax: point.plotX + bb.width / 2,
  323. yMin: point.plotY - bb.height / 2,
  324. yMax: point.plotY + bb.height / 2
  325. } : null;
  326. }
  327. /**
  328. * Calculate margin to place around obstacles for the pathfinder in pixels.
  329. * Returns a minimum of 1 pixel margin.
  330. *
  331. * @private
  332. * @function
  333. *
  334. * @param {Array<object>} obstacles
  335. * Obstacles to calculate margin from.
  336. *
  337. * @return {number}
  338. * The calculated margin in pixels. At least 1.
  339. */
  340. function calculateObstacleMargin(obstacles) {
  341. var len = obstacles.length, i = 0, j, obstacleDistance, distances = [],
  342. // Compute smallest distance between two rectangles
  343. distance = function (a, b, bbMargin) {
  344. // Count the distance even if we are slightly off
  345. var margin = pick(bbMargin, 10), yOverlap = a.yMax + margin > b.yMin - margin &&
  346. a.yMin - margin < b.yMax + margin, xOverlap = a.xMax + margin > b.xMin - margin &&
  347. a.xMin - margin < b.xMax + margin, xDistance = yOverlap ? (a.xMin > b.xMax ? a.xMin - b.xMax : b.xMin - a.xMax) : Infinity, yDistance = xOverlap ? (a.yMin > b.yMax ? a.yMin - b.yMax : b.yMin - a.yMax) : Infinity;
  348. // If the rectangles collide, try recomputing with smaller margin.
  349. // If they collide anyway, discard the obstacle.
  350. if (xOverlap && yOverlap) {
  351. return (margin ?
  352. distance(a, b, Math.floor(margin / 2)) :
  353. Infinity);
  354. }
  355. return min(xDistance, yDistance);
  356. };
  357. // Go over all obstacles and compare them to the others.
  358. for (; i < len; ++i) {
  359. // Compare to all obstacles ahead. We will already have compared this
  360. // obstacle to the ones before.
  361. for (j = i + 1; j < len; ++j) {
  362. obstacleDistance = distance(obstacles[i], obstacles[j]);
  363. // TODO: Magic number 80
  364. if (obstacleDistance < 80) { // Ignore large distances
  365. distances.push(obstacleDistance);
  366. }
  367. }
  368. }
  369. // Ensure we always have at least one value, even in very spaceous charts
  370. distances.push(80);
  371. return max(Math.floor(distances.sort(function (a, b) {
  372. return (a - b);
  373. })[
  374. // Discard first 10% of the relevant distances, and then grab
  375. // the smallest one.
  376. Math.floor(distances.length / 10)] / 2 - 1 // Divide the distance by 2 and subtract 1.
  377. ), 1 // 1 is the minimum margin
  378. );
  379. }
  380. /* eslint-disable no-invalid-this, valid-jsdoc */
  381. /**
  382. * The Connection class. Used internally to represent a connection between two
  383. * points.
  384. *
  385. * @private
  386. * @class
  387. * @name Highcharts.Connection
  388. *
  389. * @param {Highcharts.Point} from
  390. * Connection runs from this Point.
  391. *
  392. * @param {Highcharts.Point} to
  393. * Connection runs to this Point.
  394. *
  395. * @param {Highcharts.ConnectorsOptions} [options]
  396. * Connection options.
  397. */
  398. var Connection = /** @class */ (function () {
  399. function Connection(from, to, options) {
  400. /* *
  401. *
  402. * Properties
  403. *
  404. * */
  405. this.chart = void 0;
  406. this.fromPoint = void 0;
  407. this.graphics = void 0;
  408. this.pathfinder = void 0;
  409. this.toPoint = void 0;
  410. this.init(from, to, options);
  411. }
  412. /**
  413. * Initialize the Connection object. Used as constructor only.
  414. *
  415. * @function Highcharts.Connection#init
  416. *
  417. * @param {Highcharts.Point} from
  418. * Connection runs from this Point.
  419. *
  420. * @param {Highcharts.Point} to
  421. * Connection runs to this Point.
  422. *
  423. * @param {Highcharts.ConnectorsOptions} [options]
  424. * Connection options.
  425. */
  426. Connection.prototype.init = function (from, to, options) {
  427. this.fromPoint = from;
  428. this.toPoint = to;
  429. this.options = options;
  430. this.chart = from.series.chart;
  431. this.pathfinder = this.chart.pathfinder;
  432. };
  433. /**
  434. * Add (or update) this connection's path on chart. Stores reference to the
  435. * created element on this.graphics.path.
  436. *
  437. * @function Highcharts.Connection#renderPath
  438. *
  439. * @param {Highcharts.SVGPathArray} path
  440. * Path to render, in array format. E.g. ['M', 0, 0, 'L', 10, 10]
  441. *
  442. * @param {Highcharts.SVGAttributes} [attribs]
  443. * SVG attributes for the path.
  444. *
  445. * @param {Partial<Highcharts.AnimationOptionsObject>} [animation]
  446. * Animation options for the rendering.
  447. */
  448. Connection.prototype.renderPath = function (path, attribs, animation) {
  449. var connection = this, chart = this.chart, styledMode = chart.styledMode, pathfinder = chart.pathfinder, animate = !chart.options.chart.forExport && animation !== false, pathGraphic = connection.graphics && connection.graphics.path, anim;
  450. // Add the SVG element of the pathfinder group if it doesn't exist
  451. if (!pathfinder.group) {
  452. pathfinder.group = chart.renderer.g()
  453. .addClass('highcharts-pathfinder-group')
  454. .attr({ zIndex: -1 })
  455. .add(chart.seriesGroup);
  456. }
  457. // Shift the group to compensate for plot area.
  458. // Note: Do this always (even when redrawing a path) to avoid issues
  459. // when updating chart in a way that changes plot metrics.
  460. pathfinder.group.translate(chart.plotLeft, chart.plotTop);
  461. // Create path if does not exist
  462. if (!(pathGraphic && pathGraphic.renderer)) {
  463. pathGraphic = chart.renderer.path()
  464. .add(pathfinder.group);
  465. if (!styledMode) {
  466. pathGraphic.attr({
  467. opacity: 0
  468. });
  469. }
  470. }
  471. // Set path attribs and animate to the new path
  472. pathGraphic.attr(attribs);
  473. anim = { d: path };
  474. if (!styledMode) {
  475. anim.opacity = 1;
  476. }
  477. pathGraphic[animate ? 'animate' : 'attr'](anim, animation);
  478. // Store reference on connection
  479. this.graphics = this.graphics || {};
  480. this.graphics.path = pathGraphic;
  481. };
  482. /**
  483. * Calculate and add marker graphics for connection to the chart. The
  484. * created/updated elements are stored on this.graphics.start and
  485. * this.graphics.end.
  486. *
  487. * @function Highcharts.Connection#addMarker
  488. *
  489. * @param {string} type
  490. * Marker type, either 'start' or 'end'.
  491. *
  492. * @param {Highcharts.ConnectorsMarkerOptions} options
  493. * All options for this marker. Not calculated or merged with other
  494. * options.
  495. *
  496. * @param {Highcharts.SVGPathArray} path
  497. * Connection path in array format. This is used to calculate the
  498. * rotation angle of the markers.
  499. */
  500. Connection.prototype.addMarker = function (type, options, path) {
  501. var connection = this, chart = connection.fromPoint.series.chart, pathfinder = chart.pathfinder, renderer = chart.renderer, point = (type === 'start' ?
  502. connection.fromPoint :
  503. connection.toPoint), anchor = point.getPathfinderAnchorPoint(options), markerVector, radians, rotation, box, width, height, pathVector, segment;
  504. if (!options.enabled) {
  505. return;
  506. }
  507. // Last vector before start/end of path, used to get angle
  508. if (type === 'start') {
  509. segment = path[1];
  510. }
  511. else { // 'end'
  512. segment = path[path.length - 2];
  513. }
  514. if (segment && segment[0] === 'M' || segment[0] === 'L') {
  515. pathVector = {
  516. x: segment[1],
  517. y: segment[2]
  518. };
  519. // Get angle between pathVector and anchor point and use it to
  520. // create marker position.
  521. radians = point.getRadiansToVector(pathVector, anchor);
  522. markerVector = point.getMarkerVector(radians, options.radius, anchor);
  523. // Rotation of marker is calculated from angle between pathVector
  524. // and markerVector.
  525. // (Note:
  526. // Used to recalculate radians between markerVector and pathVector,
  527. // but this should be the same as between pathVector and anchor.)
  528. rotation = -radians / deg2rad;
  529. if (options.width && options.height) {
  530. width = options.width;
  531. height = options.height;
  532. }
  533. else {
  534. width = height = options.radius * 2;
  535. }
  536. // Add graphics object if it does not exist
  537. connection.graphics = connection.graphics || {};
  538. box = {
  539. x: markerVector.x - (width / 2),
  540. y: markerVector.y - (height / 2),
  541. width: width,
  542. height: height,
  543. rotation: rotation,
  544. rotationOriginX: markerVector.x,
  545. rotationOriginY: markerVector.y
  546. };
  547. if (!connection.graphics[type]) {
  548. // Create new marker element
  549. connection.graphics[type] = renderer
  550. .symbol(options.symbol)
  551. .addClass('highcharts-point-connecting-path-' + type + '-marker')
  552. .attr(box)
  553. .add(pathfinder.group);
  554. if (!renderer.styledMode) {
  555. connection.graphics[type].attr({
  556. fill: options.color || connection.fromPoint.color,
  557. stroke: options.lineColor,
  558. 'stroke-width': options.lineWidth,
  559. opacity: 0
  560. })
  561. .animate({
  562. opacity: 1
  563. }, point.series.options.animation);
  564. }
  565. }
  566. else {
  567. connection.graphics[type].animate(box);
  568. }
  569. }
  570. };
  571. /**
  572. * Calculate and return connection path.
  573. * Note: Recalculates chart obstacles on demand if they aren't calculated.
  574. *
  575. * @function Highcharts.Connection#getPath
  576. *
  577. * @param {Highcharts.ConnectorsOptions} options
  578. * Connector options. Not calculated or merged with other options.
  579. *
  580. * @return {object|undefined}
  581. * Calculated SVG path data in array format.
  582. */
  583. Connection.prototype.getPath = function (options) {
  584. var pathfinder = this.pathfinder, chart = this.chart, algorithm = pathfinder.algorithms[options.type], chartObstacles = pathfinder.chartObstacles;
  585. if (typeof algorithm !== 'function') {
  586. error('"' + options.type + '" is not a Pathfinder algorithm.');
  587. return {
  588. path: [],
  589. obstacles: []
  590. };
  591. }
  592. // This function calculates obstacles on demand if they don't exist
  593. if (algorithm.requiresObstacles && !chartObstacles) {
  594. chartObstacles =
  595. pathfinder.chartObstacles =
  596. pathfinder.getChartObstacles(options);
  597. // If the algorithmMargin was computed, store the result in default
  598. // options.
  599. chart.options.connectors.algorithmMargin =
  600. options.algorithmMargin;
  601. // Cache some metrics too
  602. pathfinder.chartObstacleMetrics =
  603. pathfinder.getObstacleMetrics(chartObstacles);
  604. }
  605. // Get the SVG path
  606. return algorithm(
  607. // From
  608. this.fromPoint.getPathfinderAnchorPoint(options.startMarker),
  609. // To
  610. this.toPoint.getPathfinderAnchorPoint(options.endMarker), merge({
  611. chartObstacles: chartObstacles,
  612. lineObstacles: pathfinder.lineObstacles || [],
  613. obstacleMetrics: pathfinder.chartObstacleMetrics,
  614. hardBounds: {
  615. xMin: 0,
  616. xMax: chart.plotWidth,
  617. yMin: 0,
  618. yMax: chart.plotHeight
  619. },
  620. obstacleOptions: {
  621. margin: options.algorithmMargin
  622. },
  623. startDirectionX: pathfinder.getAlgorithmStartDirection(options.startMarker)
  624. }, options));
  625. };
  626. /**
  627. * (re)Calculate and (re)draw the connection.
  628. *
  629. * @function Highcharts.Connection#render
  630. */
  631. Connection.prototype.render = function () {
  632. var connection = this, fromPoint = connection.fromPoint, series = fromPoint.series, chart = series.chart, pathfinder = chart.pathfinder, pathResult, path, options = merge(chart.options.connectors, series.options.connectors, fromPoint.options.connectors, connection.options), attribs = {};
  633. // Set path attribs
  634. if (!chart.styledMode) {
  635. attribs.stroke = options.lineColor || fromPoint.color;
  636. attribs['stroke-width'] = options.lineWidth;
  637. if (options.dashStyle) {
  638. attribs.dashstyle = options.dashStyle;
  639. }
  640. }
  641. attribs['class'] = // eslint-disable-line dot-notation
  642. 'highcharts-point-connecting-path ' +
  643. 'highcharts-color-' + fromPoint.colorIndex;
  644. options = merge(attribs, options);
  645. // Set common marker options
  646. if (!defined(options.marker.radius)) {
  647. options.marker.radius = min(max(Math.ceil((options.algorithmMargin || 8) / 2) - 1, 1), 5);
  648. }
  649. // Get the path
  650. pathResult = connection.getPath(options);
  651. path = pathResult.path;
  652. // Always update obstacle storage with obstacles from this path.
  653. // We don't know if future calls will need this for their algorithm.
  654. if (pathResult.obstacles) {
  655. pathfinder.lineObstacles =
  656. pathfinder.lineObstacles || [];
  657. pathfinder.lineObstacles =
  658. pathfinder.lineObstacles.concat(pathResult.obstacles);
  659. }
  660. // Add the calculated path to the pathfinder group
  661. connection.renderPath(path, attribs, series.options.animation);
  662. // Render the markers
  663. connection.addMarker('start', merge(options.marker, options.startMarker), path);
  664. connection.addMarker('end', merge(options.marker, options.endMarker), path);
  665. };
  666. /**
  667. * Destroy connection by destroying the added graphics elements.
  668. *
  669. * @function Highcharts.Connection#destroy
  670. */
  671. Connection.prototype.destroy = function () {
  672. if (this.graphics) {
  673. objectEach(this.graphics, function (val) {
  674. val.destroy();
  675. });
  676. delete this.graphics;
  677. }
  678. };
  679. return Connection;
  680. }());
  681. // Add to Highcharts namespace
  682. H.Connection = Connection;
  683. // Add pathfinding capabilities to Points
  684. extend(Point.prototype, /** @lends Point.prototype */ {
  685. /**
  686. * Get coordinates of anchor point for pathfinder connection.
  687. *
  688. * @private
  689. * @function Highcharts.Point#getPathfinderAnchorPoint
  690. *
  691. * @param {Highcharts.ConnectorsMarkerOptions} markerOptions
  692. * Connection options for position on point.
  693. *
  694. * @return {Highcharts.PositionObject}
  695. * An object with x/y properties for the position. Coordinates are
  696. * in plot values, not relative to point.
  697. */
  698. getPathfinderAnchorPoint: function (markerOptions) {
  699. var bb = getPointBB(this), x, y;
  700. switch (markerOptions.align) { // eslint-disable-line default-case
  701. case 'right':
  702. x = 'xMax';
  703. break;
  704. case 'left':
  705. x = 'xMin';
  706. }
  707. switch (markerOptions.verticalAlign) { // eslint-disable-line default-case
  708. case 'top':
  709. y = 'yMin';
  710. break;
  711. case 'bottom':
  712. y = 'yMax';
  713. }
  714. return {
  715. x: x ? bb[x] : (bb.xMin + bb.xMax) / 2,
  716. y: y ? bb[y] : (bb.yMin + bb.yMax) / 2
  717. };
  718. },
  719. /**
  720. * Utility to get the angle from one point to another.
  721. *
  722. * @private
  723. * @function Highcharts.Point#getRadiansToVector
  724. *
  725. * @param {Highcharts.PositionObject} v1
  726. * The first vector, as an object with x/y properties.
  727. *
  728. * @param {Highcharts.PositionObject} v2
  729. * The second vector, as an object with x/y properties.
  730. *
  731. * @return {number}
  732. * The angle in degrees
  733. */
  734. getRadiansToVector: function (v1, v2) {
  735. var box;
  736. if (!defined(v2)) {
  737. box = getPointBB(this);
  738. if (box) {
  739. v2 = {
  740. x: (box.xMin + box.xMax) / 2,
  741. y: (box.yMin + box.yMax) / 2
  742. };
  743. }
  744. }
  745. return Math.atan2(v2.y - v1.y, v1.x - v2.x);
  746. },
  747. /**
  748. * Utility to get the position of the marker, based on the path angle and
  749. * the marker's radius.
  750. *
  751. * @private
  752. * @function Highcharts.Point#getMarkerVector
  753. *
  754. * @param {number} radians
  755. * The angle in radians from the point center to another vector.
  756. *
  757. * @param {number} markerRadius
  758. * The radius of the marker, to calculate the additional distance to
  759. * the center of the marker.
  760. *
  761. * @param {object} anchor
  762. * The anchor point of the path and marker as an object with x/y
  763. * properties.
  764. *
  765. * @return {object}
  766. * The marker vector as an object with x/y properties.
  767. */
  768. getMarkerVector: function (radians, markerRadius, anchor) {
  769. var twoPI = Math.PI * 2.0, theta = radians, bb = getPointBB(this), rectWidth = bb.xMax - bb.xMin, rectHeight = bb.yMax - bb.yMin, rAtan = Math.atan2(rectHeight, rectWidth), tanTheta = 1, leftOrRightRegion = false, rectHalfWidth = rectWidth / 2.0, rectHalfHeight = rectHeight / 2.0, rectHorizontalCenter = bb.xMin + rectHalfWidth, rectVerticalCenter = bb.yMin + rectHalfHeight, edgePoint = {
  770. x: rectHorizontalCenter,
  771. y: rectVerticalCenter
  772. }, xFactor = 1, yFactor = 1;
  773. while (theta < -Math.PI) {
  774. theta += twoPI;
  775. }
  776. while (theta > Math.PI) {
  777. theta -= twoPI;
  778. }
  779. tanTheta = Math.tan(theta);
  780. if ((theta > -rAtan) && (theta <= rAtan)) {
  781. // Right side
  782. yFactor = -1;
  783. leftOrRightRegion = true;
  784. }
  785. else if (theta > rAtan && theta <= (Math.PI - rAtan)) {
  786. // Top side
  787. yFactor = -1;
  788. }
  789. else if (theta > (Math.PI - rAtan) || theta <= -(Math.PI - rAtan)) {
  790. // Left side
  791. xFactor = -1;
  792. leftOrRightRegion = true;
  793. }
  794. else {
  795. // Bottom side
  796. xFactor = -1;
  797. }
  798. // Correct the edgePoint according to the placement of the marker
  799. if (leftOrRightRegion) {
  800. edgePoint.x += xFactor * (rectHalfWidth);
  801. edgePoint.y += yFactor * (rectHalfWidth) * tanTheta;
  802. }
  803. else {
  804. edgePoint.x += xFactor * (rectHeight / (2.0 * tanTheta));
  805. edgePoint.y += yFactor * (rectHalfHeight);
  806. }
  807. if (anchor.x !== rectHorizontalCenter) {
  808. edgePoint.x = anchor.x;
  809. }
  810. if (anchor.y !== rectVerticalCenter) {
  811. edgePoint.y = anchor.y;
  812. }
  813. return {
  814. x: edgePoint.x + (markerRadius * Math.cos(theta)),
  815. y: edgePoint.y - (markerRadius * Math.sin(theta))
  816. };
  817. }
  818. });
  819. /**
  820. * Warn if using legacy options. Copy the options over. Note that this will
  821. * still break if using the legacy options in chart.update, addSeries etc.
  822. * @private
  823. */
  824. function warnLegacy(chart) {
  825. if (chart.options.pathfinder ||
  826. chart.series.reduce(function (acc, series) {
  827. if (series.options) {
  828. merge(true, (series.options.connectors = series.options.connectors ||
  829. {}), series.options.pathfinder);
  830. }
  831. return acc || series.options && series.options.pathfinder;
  832. }, false)) {
  833. merge(true, (chart.options.connectors = chart.options.connectors || {}), chart.options.pathfinder);
  834. error('WARNING: Pathfinder options have been renamed. ' +
  835. 'Use "chart.connectors" or "series.connectors" instead.');
  836. }
  837. }
  838. export default Connection;