pathfinder.src.js 87 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958
  1. /**
  2. * @license Highcharts Gantt JS v8.0.0 (2019-12-10)
  3. *
  4. * Pathfinder
  5. *
  6. * (c) 2016-2019 Øystein Moseng
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/modules/pathfinder', ['highcharts'], function (Highcharts) {
  17. factory(Highcharts);
  18. factory.Highcharts = Highcharts;
  19. return factory;
  20. });
  21. } else {
  22. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  23. }
  24. }(function (Highcharts) {
  25. var _modules = Highcharts ? Highcharts._modules : {};
  26. function _registerModule(obj, path, args, fn) {
  27. if (!obj.hasOwnProperty(path)) {
  28. obj[path] = fn.apply(null, args);
  29. }
  30. }
  31. _registerModule(_modules, 'parts-gantt/PathfinderAlgorithms.js', [_modules['parts/Utilities.js']], function (U) {
  32. /* *
  33. *
  34. * (c) 2016 Highsoft AS
  35. * Author: Øystein Moseng
  36. *
  37. * License: www.highcharts.com/license
  38. *
  39. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  40. *
  41. * */
  42. var extend = U.extend, pick = U.pick;
  43. var min = Math.min, max = Math.max, abs = Math.abs;
  44. /**
  45. * Get index of last obstacle before xMin. Employs a type of binary search, and
  46. * thus requires that obstacles are sorted by xMin value.
  47. *
  48. * @private
  49. * @function findLastObstacleBefore
  50. *
  51. * @param {Array<object>} obstacles
  52. * Array of obstacles to search in.
  53. *
  54. * @param {number} xMin
  55. * The xMin threshold.
  56. *
  57. * @param {number} [startIx]
  58. * Starting index to search from. Must be within array range.
  59. *
  60. * @return {number}
  61. * The index of the last obstacle element before xMin.
  62. */
  63. function findLastObstacleBefore(obstacles, xMin, startIx) {
  64. var left = startIx || 0, // left limit
  65. right = obstacles.length - 1, // right limit
  66. min = xMin - 0.0000001, // Make sure we include all obstacles at xMin
  67. cursor, cmp;
  68. while (left <= right) {
  69. cursor = (right + left) >> 1;
  70. cmp = min - obstacles[cursor].xMin;
  71. if (cmp > 0) {
  72. left = cursor + 1;
  73. }
  74. else if (cmp < 0) {
  75. right = cursor - 1;
  76. }
  77. else {
  78. return cursor;
  79. }
  80. }
  81. return left > 0 ? left - 1 : 0;
  82. }
  83. /**
  84. * Test if a point lays within an obstacle.
  85. *
  86. * @private
  87. * @function pointWithinObstacle
  88. *
  89. * @param {object} obstacle
  90. * Obstacle to test.
  91. *
  92. * @param {Highcharts.Point} point
  93. * Point with x/y props.
  94. *
  95. * @return {boolean}
  96. * Whether point is within the obstacle or not.
  97. */
  98. function pointWithinObstacle(obstacle, point) {
  99. return (point.x <= obstacle.xMax &&
  100. point.x >= obstacle.xMin &&
  101. point.y <= obstacle.yMax &&
  102. point.y >= obstacle.yMin);
  103. }
  104. /**
  105. * Find the index of an obstacle that wraps around a point.
  106. * Returns -1 if not found.
  107. *
  108. * @private
  109. * @function findObstacleFromPoint
  110. *
  111. * @param {Array<object>} obstacles
  112. * Obstacles to test.
  113. *
  114. * @param {Highcharts.Point} point
  115. * Point with x/y props.
  116. *
  117. * @return {number}
  118. * Ix of the obstacle in the array, or -1 if not found.
  119. */
  120. function findObstacleFromPoint(obstacles, point) {
  121. var i = findLastObstacleBefore(obstacles, point.x + 1) + 1;
  122. while (i--) {
  123. if (obstacles[i].xMax >= point.x &&
  124. // optimization using lazy evaluation
  125. pointWithinObstacle(obstacles[i], point)) {
  126. return i;
  127. }
  128. }
  129. return -1;
  130. }
  131. /**
  132. * Get SVG path array from array of line segments.
  133. *
  134. * @private
  135. * @function pathFromSegments
  136. *
  137. * @param {Array<object>} segments
  138. * The segments to build the path from.
  139. *
  140. * @return {Highcharts.SVGPathArray}
  141. * SVG path array as accepted by the SVG Renderer.
  142. */
  143. function pathFromSegments(segments) {
  144. var path = [];
  145. if (segments.length) {
  146. path.push('M', segments[0].start.x, segments[0].start.y);
  147. for (var i = 0; i < segments.length; ++i) {
  148. path.push('L', segments[i].end.x, segments[i].end.y);
  149. }
  150. }
  151. return path;
  152. }
  153. /**
  154. * Limits obstacle max/mins in all directions to bounds. Modifies input
  155. * obstacle.
  156. *
  157. * @private
  158. * @function limitObstacleToBounds
  159. *
  160. * @param {object} obstacle
  161. * Obstacle to limit.
  162. *
  163. * @param {object} bounds
  164. * Bounds to use as limit.
  165. *
  166. * @return {void}
  167. */
  168. function limitObstacleToBounds(obstacle, bounds) {
  169. obstacle.yMin = max(obstacle.yMin, bounds.yMin);
  170. obstacle.yMax = min(obstacle.yMax, bounds.yMax);
  171. obstacle.xMin = max(obstacle.xMin, bounds.xMin);
  172. obstacle.xMax = min(obstacle.xMax, bounds.xMax);
  173. }
  174. // Define the available pathfinding algorithms.
  175. // Algorithms take up to 3 arguments: starting point, ending point, and an
  176. // options object.
  177. var algorithms = {
  178. /**
  179. * Get an SVG path from a starting coordinate to an ending coordinate.
  180. * Draws a straight line.
  181. *
  182. * @function Highcharts.Pathfinder.algorithms.straight
  183. *
  184. * @param {Highcharts.PositionObject} start
  185. * Starting coordinate, object with x/y props.
  186. *
  187. * @param {Highcharts.PositionObject} end
  188. * Ending coordinate, object with x/y props.
  189. *
  190. * @return {object}
  191. * An object with the SVG path in Array form as accepted by the SVG
  192. * renderer, as well as an array of new obstacles making up this
  193. * path.
  194. */
  195. straight: function (start, end) {
  196. return {
  197. path: ['M', start.x, start.y, 'L', end.x, end.y],
  198. obstacles: [{ start: start, end: end }]
  199. };
  200. },
  201. /**
  202. * Find a path from a starting coordinate to an ending coordinate, using
  203. * right angles only, and taking only starting/ending obstacle into
  204. * consideration.
  205. *
  206. * @function Highcharts.Pathfinder.algorithms.simpleConnect
  207. *
  208. * @param {Highcharts.PositionObject} start
  209. * Starting coordinate, object with x/y props.
  210. *
  211. * @param {Highcharts.PositionObject} end
  212. * Ending coordinate, object with x/y props.
  213. *
  214. * @param {object} options
  215. * Options for the algorithm:
  216. * - chartObstacles: Array of chart obstacles to avoid
  217. * - startDirectionX: Optional. True if starting in the X direction.
  218. * If not provided, the algorithm starts in the direction that is
  219. * the furthest between start/end.
  220. *
  221. * @return {object}
  222. * An object with the SVG path in Array form as accepted by the SVG
  223. * renderer, as well as an array of new obstacles making up this
  224. * path.
  225. */
  226. simpleConnect: extend(function (start, end, options) {
  227. var segments = [], endSegment, dir = pick(options.startDirectionX, abs(end.x - start.x) > abs(end.y - start.y)) ? 'x' : 'y', chartObstacles = options.chartObstacles, startObstacleIx = findObstacleFromPoint(chartObstacles, start), endObstacleIx = findObstacleFromPoint(chartObstacles, end), startObstacle, endObstacle, prevWaypoint, waypoint, waypoint2, useMax, endPoint;
  228. // eslint-disable-next-line valid-jsdoc
  229. /**
  230. * Return a clone of a point with a property set from a target object,
  231. * optionally with an offset
  232. * @private
  233. */
  234. function copyFromPoint(from, fromKey, to, toKey, offset) {
  235. var point = {
  236. x: from.x,
  237. y: from.y
  238. };
  239. point[fromKey] = to[toKey || fromKey] + (offset || 0);
  240. return point;
  241. }
  242. // eslint-disable-next-line valid-jsdoc
  243. /**
  244. * Return waypoint outside obstacle.
  245. * @private
  246. */
  247. function getMeOut(obstacle, point, direction) {
  248. var useMax = abs(point[direction] - obstacle[direction + 'Min']) >
  249. abs(point[direction] - obstacle[direction + 'Max']);
  250. return copyFromPoint(point, direction, obstacle, direction + (useMax ? 'Max' : 'Min'), useMax ? 1 : -1);
  251. }
  252. // Pull out end point
  253. if (endObstacleIx > -1) {
  254. endObstacle = chartObstacles[endObstacleIx];
  255. waypoint = getMeOut(endObstacle, end, dir);
  256. endSegment = {
  257. start: waypoint,
  258. end: end
  259. };
  260. endPoint = waypoint;
  261. }
  262. else {
  263. endPoint = end;
  264. }
  265. // If an obstacle envelops the start point, add a segment to get out,
  266. // and around it.
  267. if (startObstacleIx > -1) {
  268. startObstacle = chartObstacles[startObstacleIx];
  269. waypoint = getMeOut(startObstacle, start, dir);
  270. segments.push({
  271. start: start,
  272. end: waypoint
  273. });
  274. // If we are going back again, switch direction to get around start
  275. // obstacle.
  276. if (
  277. // Going towards max from start:
  278. waypoint[dir] >= start[dir] ===
  279. // Going towards min to end:
  280. waypoint[dir] >= endPoint[dir]) {
  281. dir = dir === 'y' ? 'x' : 'y';
  282. useMax = start[dir] < end[dir];
  283. segments.push({
  284. start: waypoint,
  285. end: copyFromPoint(waypoint, dir, startObstacle, dir + (useMax ? 'Max' : 'Min'), useMax ? 1 : -1)
  286. });
  287. // Switch direction again
  288. dir = dir === 'y' ? 'x' : 'y';
  289. }
  290. }
  291. // We are around the start obstacle. Go towards the end in one
  292. // direction.
  293. prevWaypoint = segments.length ?
  294. segments[segments.length - 1].end :
  295. start;
  296. waypoint = copyFromPoint(prevWaypoint, dir, endPoint);
  297. segments.push({
  298. start: prevWaypoint,
  299. end: waypoint
  300. });
  301. // Final run to end point in the other direction
  302. dir = dir === 'y' ? 'x' : 'y';
  303. waypoint2 = copyFromPoint(waypoint, dir, endPoint);
  304. segments.push({
  305. start: waypoint,
  306. end: waypoint2
  307. });
  308. // Finally add the endSegment
  309. segments.push(endSegment);
  310. return {
  311. path: pathFromSegments(segments),
  312. obstacles: segments
  313. };
  314. }, {
  315. requiresObstacles: true
  316. }),
  317. /**
  318. * Find a path from a starting coordinate to an ending coordinate, taking
  319. * obstacles into consideration. Might not always find the optimal path,
  320. * but is fast, and usually good enough.
  321. *
  322. * @function Highcharts.Pathfinder.algorithms.fastAvoid
  323. *
  324. * @param {Highcharts.PositionObject} start
  325. * Starting coordinate, object with x/y props.
  326. *
  327. * @param {Highcharts.PositionObject} end
  328. * Ending coordinate, object with x/y props.
  329. *
  330. * @param {object} options
  331. * Options for the algorithm.
  332. * - chartObstacles: Array of chart obstacles to avoid
  333. * - lineObstacles: Array of line obstacles to jump over
  334. * - obstacleMetrics: Object with metrics of chartObstacles cached
  335. * - hardBounds: Hard boundaries to not cross
  336. * - obstacleOptions: Options for the obstacles, including margin
  337. * - startDirectionX: Optional. True if starting in the X direction.
  338. * If not provided, the algorithm starts in the
  339. * direction that is the furthest between
  340. * start/end.
  341. *
  342. * @return {object}
  343. * An object with the SVG path in Array form as accepted by the SVG
  344. * renderer, as well as an array of new obstacles making up this
  345. * path.
  346. */
  347. fastAvoid: extend(function (start, end, options) {
  348. /*
  349. Algorithm rules/description
  350. - Find initial direction
  351. - Determine soft/hard max for each direction.
  352. - Move along initial direction until obstacle.
  353. - Change direction.
  354. - If hitting obstacle, first try to change length of previous line
  355. before changing direction again.
  356. Soft min/max x = start/destination x +/- widest obstacle + margin
  357. Soft min/max y = start/destination y +/- tallest obstacle + margin
  358. @todo:
  359. - Make retrospective, try changing prev segment to reduce
  360. corners
  361. - Fix logic for breaking out of end-points - not always picking
  362. the best direction currently
  363. - When going around the end obstacle we should not always go the
  364. shortest route, rather pick the one closer to the end point
  365. */
  366. var dirIsX = pick(options.startDirectionX, abs(end.x - start.x) > abs(end.y - start.y)), dir = dirIsX ? 'x' : 'y', segments, useMax, extractedEndPoint, endSegments = [], forceObstacleBreak = false, // Used in clearPathTo to keep track of
  367. // when to force break through an obstacle.
  368. // Boundaries to stay within. If beyond soft boundary, prefer to
  369. // change direction ASAP. If at hard max, always change immediately.
  370. metrics = options.obstacleMetrics, softMinX = min(start.x, end.x) - metrics.maxWidth - 10, softMaxX = max(start.x, end.x) + metrics.maxWidth + 10, softMinY = min(start.y, end.y) - metrics.maxHeight - 10, softMaxY = max(start.y, end.y) + metrics.maxHeight + 10,
  371. // Obstacles
  372. chartObstacles = options.chartObstacles, startObstacleIx = findLastObstacleBefore(chartObstacles, softMinX), endObstacleIx = findLastObstacleBefore(chartObstacles, softMaxX);
  373. // eslint-disable-next-line valid-jsdoc
  374. /**
  375. * How far can you go between two points before hitting an obstacle?
  376. * Does not work for diagonal lines (because it doesn't have to).
  377. * @private
  378. */
  379. function pivotPoint(fromPoint, toPoint, directionIsX) {
  380. var firstPoint, lastPoint, highestPoint, lowestPoint, i, searchDirection = fromPoint.x < toPoint.x ? 1 : -1;
  381. if (fromPoint.x < toPoint.x) {
  382. firstPoint = fromPoint;
  383. lastPoint = toPoint;
  384. }
  385. else {
  386. firstPoint = toPoint;
  387. lastPoint = fromPoint;
  388. }
  389. if (fromPoint.y < toPoint.y) {
  390. lowestPoint = fromPoint;
  391. highestPoint = toPoint;
  392. }
  393. else {
  394. lowestPoint = toPoint;
  395. highestPoint = fromPoint;
  396. }
  397. // Go through obstacle range in reverse if toPoint is before
  398. // fromPoint in the X-dimension.
  399. i = searchDirection < 0 ?
  400. // Searching backwards, start at last obstacle before last point
  401. min(findLastObstacleBefore(chartObstacles, lastPoint.x), chartObstacles.length - 1) :
  402. // Forwards. Since we're not sorted by xMax, we have to look
  403. // at all obstacles.
  404. 0;
  405. // Go through obstacles in this X range
  406. while (chartObstacles[i] && (searchDirection > 0 && chartObstacles[i].xMin <= lastPoint.x ||
  407. searchDirection < 0 && chartObstacles[i].xMax >= firstPoint.x)) {
  408. // If this obstacle is between from and to points in a straight
  409. // line, pivot at the intersection.
  410. if (chartObstacles[i].xMin <= lastPoint.x &&
  411. chartObstacles[i].xMax >= firstPoint.x &&
  412. chartObstacles[i].yMin <= highestPoint.y &&
  413. chartObstacles[i].yMax >= lowestPoint.y) {
  414. if (directionIsX) {
  415. return {
  416. y: fromPoint.y,
  417. x: fromPoint.x < toPoint.x ?
  418. chartObstacles[i].xMin - 1 :
  419. chartObstacles[i].xMax + 1,
  420. obstacle: chartObstacles[i]
  421. };
  422. }
  423. // else ...
  424. return {
  425. x: fromPoint.x,
  426. y: fromPoint.y < toPoint.y ?
  427. chartObstacles[i].yMin - 1 :
  428. chartObstacles[i].yMax + 1,
  429. obstacle: chartObstacles[i]
  430. };
  431. }
  432. i += searchDirection;
  433. }
  434. return toPoint;
  435. }
  436. /**
  437. * Decide in which direction to dodge or get out of an obstacle.
  438. * Considers desired direction, which way is shortest, soft and hard
  439. * bounds.
  440. *
  441. * (? Returns a string, either xMin, xMax, yMin or yMax.)
  442. *
  443. * @private
  444. * @function
  445. *
  446. * @param {object} obstacle
  447. * Obstacle to dodge/escape.
  448. *
  449. * @param {object} fromPoint
  450. * Point with x/y props that's dodging/escaping.
  451. *
  452. * @param {object} toPoint
  453. * Goal point.
  454. *
  455. * @param {boolean} dirIsX
  456. * Dodge in X dimension.
  457. *
  458. * @param {object} bounds
  459. * Hard and soft boundaries.
  460. *
  461. * @return {boolean}
  462. * Use max or not.
  463. */
  464. function getDodgeDirection(obstacle, fromPoint, toPoint, dirIsX, bounds) {
  465. var softBounds = bounds.soft, hardBounds = bounds.hard, dir = dirIsX ? 'x' : 'y', toPointMax = { x: fromPoint.x, y: fromPoint.y }, toPointMin = { x: fromPoint.x, y: fromPoint.y }, minPivot, maxPivot, maxOutOfSoftBounds = obstacle[dir + 'Max'] >=
  466. softBounds[dir + 'Max'], minOutOfSoftBounds = obstacle[dir + 'Min'] <=
  467. softBounds[dir + 'Min'], maxOutOfHardBounds = obstacle[dir + 'Max'] >=
  468. hardBounds[dir + 'Max'], minOutOfHardBounds = obstacle[dir + 'Min'] <=
  469. hardBounds[dir + 'Min'],
  470. // Find out if we should prefer one direction over the other if
  471. // we can choose freely
  472. minDistance = abs(obstacle[dir + 'Min'] - fromPoint[dir]), maxDistance = abs(obstacle[dir + 'Max'] - fromPoint[dir]),
  473. // If it's a small difference, pick the one leading towards dest
  474. // point. Otherwise pick the shortest distance
  475. useMax = abs(minDistance - maxDistance) < 10 ?
  476. fromPoint[dir] < toPoint[dir] :
  477. maxDistance < minDistance;
  478. // Check if we hit any obstacles trying to go around in either
  479. // direction.
  480. toPointMin[dir] = obstacle[dir + 'Min'];
  481. toPointMax[dir] = obstacle[dir + 'Max'];
  482. minPivot = pivotPoint(fromPoint, toPointMin, dirIsX)[dir] !==
  483. toPointMin[dir];
  484. maxPivot = pivotPoint(fromPoint, toPointMax, dirIsX)[dir] !==
  485. toPointMax[dir];
  486. useMax = minPivot ?
  487. (maxPivot ? useMax : true) :
  488. (maxPivot ? false : useMax);
  489. // useMax now contains our preferred choice, bounds not taken into
  490. // account. If both or neither direction is out of bounds we want to
  491. // use this.
  492. // Deal with soft bounds
  493. useMax = minOutOfSoftBounds ?
  494. (maxOutOfSoftBounds ? useMax : true) : // Out on min
  495. (maxOutOfSoftBounds ? false : useMax); // Not out on min
  496. // Deal with hard bounds
  497. useMax = minOutOfHardBounds ?
  498. (maxOutOfHardBounds ? useMax : true) : // Out on min
  499. (maxOutOfHardBounds ? false : useMax); // Not out on min
  500. return useMax;
  501. }
  502. // eslint-disable-next-line valid-jsdoc
  503. /**
  504. * Find a clear path between point.
  505. * @private
  506. */
  507. function clearPathTo(fromPoint, toPoint, dirIsX) {
  508. // Don't waste time if we've hit goal
  509. if (fromPoint.x === toPoint.x && fromPoint.y === toPoint.y) {
  510. return [];
  511. }
  512. var dir = dirIsX ? 'x' : 'y', pivot, segments, waypoint, waypointUseMax, envelopingObstacle, secondEnvelopingObstacle, envelopWaypoint, obstacleMargin = options.obstacleOptions.margin, bounds = {
  513. soft: {
  514. xMin: softMinX,
  515. xMax: softMaxX,
  516. yMin: softMinY,
  517. yMax: softMaxY
  518. },
  519. hard: options.hardBounds
  520. };
  521. // If fromPoint is inside an obstacle we have a problem. Break out
  522. // by just going to the outside of this obstacle. We prefer to go to
  523. // the nearest edge in the chosen direction.
  524. envelopingObstacle =
  525. findObstacleFromPoint(chartObstacles, fromPoint);
  526. if (envelopingObstacle > -1) {
  527. envelopingObstacle = chartObstacles[envelopingObstacle];
  528. waypointUseMax = getDodgeDirection(envelopingObstacle, fromPoint, toPoint, dirIsX, bounds);
  529. // Cut obstacle to hard bounds to make sure we stay within
  530. limitObstacleToBounds(envelopingObstacle, options.hardBounds);
  531. envelopWaypoint = dirIsX ? {
  532. y: fromPoint.y,
  533. x: envelopingObstacle[waypointUseMax ? 'xMax' : 'xMin'] +
  534. (waypointUseMax ? 1 : -1)
  535. } : {
  536. x: fromPoint.x,
  537. y: envelopingObstacle[waypointUseMax ? 'yMax' : 'yMin'] +
  538. (waypointUseMax ? 1 : -1)
  539. };
  540. // If we crashed into another obstacle doing this, we put the
  541. // waypoint between them instead
  542. secondEnvelopingObstacle = findObstacleFromPoint(chartObstacles, envelopWaypoint);
  543. if (secondEnvelopingObstacle > -1) {
  544. secondEnvelopingObstacle = chartObstacles[secondEnvelopingObstacle];
  545. // Cut obstacle to hard bounds
  546. limitObstacleToBounds(secondEnvelopingObstacle, options.hardBounds);
  547. // Modify waypoint to lay between obstacles
  548. envelopWaypoint[dir] = waypointUseMax ? max(envelopingObstacle[dir + 'Max'] - obstacleMargin + 1, (secondEnvelopingObstacle[dir + 'Min'] +
  549. envelopingObstacle[dir + 'Max']) / 2) :
  550. min((envelopingObstacle[dir + 'Min'] + obstacleMargin - 1), ((secondEnvelopingObstacle[dir + 'Max'] +
  551. envelopingObstacle[dir + 'Min']) / 2));
  552. // We are not going anywhere. If this happens for the first
  553. // time, do nothing. Otherwise, try to go to the extreme of
  554. // the obstacle pair in the current direction.
  555. if (fromPoint.x === envelopWaypoint.x &&
  556. fromPoint.y === envelopWaypoint.y) {
  557. if (forceObstacleBreak) {
  558. envelopWaypoint[dir] = waypointUseMax ?
  559. max(envelopingObstacle[dir + 'Max'], secondEnvelopingObstacle[dir + 'Max']) + 1 :
  560. min(envelopingObstacle[dir + 'Min'], secondEnvelopingObstacle[dir + 'Min']) - 1;
  561. }
  562. // Toggle on if off, and the opposite
  563. forceObstacleBreak = !forceObstacleBreak;
  564. }
  565. else {
  566. // This point is not identical to previous.
  567. // Clear break trigger.
  568. forceObstacleBreak = false;
  569. }
  570. }
  571. segments = [{
  572. start: fromPoint,
  573. end: envelopWaypoint
  574. }];
  575. }
  576. else { // If not enveloping, use standard pivot calculation
  577. pivot = pivotPoint(fromPoint, {
  578. x: dirIsX ? toPoint.x : fromPoint.x,
  579. y: dirIsX ? fromPoint.y : toPoint.y
  580. }, dirIsX);
  581. segments = [{
  582. start: fromPoint,
  583. end: {
  584. x: pivot.x,
  585. y: pivot.y
  586. }
  587. }];
  588. // Pivot before goal, use a waypoint to dodge obstacle
  589. if (pivot[dirIsX ? 'x' : 'y'] !== toPoint[dirIsX ? 'x' : 'y']) {
  590. // Find direction of waypoint
  591. waypointUseMax = getDodgeDirection(pivot.obstacle, pivot, toPoint, !dirIsX, bounds);
  592. // Cut waypoint to hard bounds
  593. limitObstacleToBounds(pivot.obstacle, options.hardBounds);
  594. waypoint = {
  595. x: dirIsX ?
  596. pivot.x :
  597. pivot.obstacle[waypointUseMax ? 'xMax' : 'xMin'] +
  598. (waypointUseMax ? 1 : -1),
  599. y: dirIsX ?
  600. pivot.obstacle[waypointUseMax ? 'yMax' : 'yMin'] +
  601. (waypointUseMax ? 1 : -1) :
  602. pivot.y
  603. };
  604. // We're changing direction here, store that to make sure we
  605. // also change direction when adding the last segment array
  606. // after handling waypoint.
  607. dirIsX = !dirIsX;
  608. segments = segments.concat(clearPathTo({
  609. x: pivot.x,
  610. y: pivot.y
  611. }, waypoint, dirIsX));
  612. }
  613. }
  614. // Get segments for the other direction too
  615. // Recursion is our friend
  616. segments = segments.concat(clearPathTo(segments[segments.length - 1].end, toPoint, !dirIsX));
  617. return segments;
  618. }
  619. // eslint-disable-next-line valid-jsdoc
  620. /**
  621. * Extract point to outside of obstacle in whichever direction is
  622. * closest. Returns new point outside obstacle.
  623. * @private
  624. */
  625. function extractFromObstacle(obstacle, point, goalPoint) {
  626. var dirIsX = min(obstacle.xMax - point.x, point.x - obstacle.xMin) <
  627. min(obstacle.yMax - point.y, point.y - obstacle.yMin), bounds = {
  628. soft: options.hardBounds,
  629. hard: options.hardBounds
  630. }, useMax = getDodgeDirection(obstacle, point, goalPoint, dirIsX, bounds);
  631. return dirIsX ? {
  632. y: point.y,
  633. x: obstacle[useMax ? 'xMax' : 'xMin'] + (useMax ? 1 : -1)
  634. } : {
  635. x: point.x,
  636. y: obstacle[useMax ? 'yMax' : 'yMin'] + (useMax ? 1 : -1)
  637. };
  638. }
  639. // Cut the obstacle array to soft bounds for optimization in large
  640. // datasets.
  641. chartObstacles =
  642. chartObstacles.slice(startObstacleIx, endObstacleIx + 1);
  643. // If an obstacle envelops the end point, move it out of there and add
  644. // a little segment to where it was.
  645. if ((endObstacleIx = findObstacleFromPoint(chartObstacles, end)) > -1) {
  646. extractedEndPoint = extractFromObstacle(chartObstacles[endObstacleIx], end, start);
  647. endSegments.push({
  648. end: end,
  649. start: extractedEndPoint
  650. });
  651. end = extractedEndPoint;
  652. }
  653. // If it's still inside one or more obstacles, get out of there by
  654. // force-moving towards the start point.
  655. while ((endObstacleIx = findObstacleFromPoint(chartObstacles, end)) > -1) {
  656. useMax = end[dir] - start[dir] < 0;
  657. extractedEndPoint = {
  658. x: end.x,
  659. y: end.y
  660. };
  661. extractedEndPoint[dir] = chartObstacles[endObstacleIx][useMax ? dir + 'Max' : dir + 'Min'] + (useMax ? 1 : -1);
  662. endSegments.push({
  663. end: end,
  664. start: extractedEndPoint
  665. });
  666. end = extractedEndPoint;
  667. }
  668. // Find the path
  669. segments = clearPathTo(start, end, dirIsX);
  670. // Add the end-point segments
  671. segments = segments.concat(endSegments.reverse());
  672. return {
  673. path: pathFromSegments(segments),
  674. obstacles: segments
  675. };
  676. }, {
  677. requiresObstacles: true
  678. })
  679. };
  680. return algorithms;
  681. });
  682. _registerModule(_modules, 'parts-gantt/ArrowSymbols.js', [_modules['parts/Globals.js']], function (H) {
  683. /* *
  684. *
  685. * (c) 2017 Highsoft AS
  686. * Authors: Lars A. V. Cabrera
  687. *
  688. * License: www.highcharts.com/license
  689. *
  690. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  691. *
  692. * */
  693. /**
  694. * Creates an arrow symbol. Like a triangle, except not filled.
  695. * ```
  696. * o
  697. * o
  698. * o
  699. * o
  700. * o
  701. * o
  702. * o
  703. * ```
  704. *
  705. * @private
  706. * @function
  707. *
  708. * @param {number} x
  709. * x position of the arrow
  710. *
  711. * @param {number} y
  712. * y position of the arrow
  713. *
  714. * @param {number} w
  715. * width of the arrow
  716. *
  717. * @param {number} h
  718. * height of the arrow
  719. *
  720. * @return {Highcharts.SVGPathArray}
  721. * Path array
  722. */
  723. H.SVGRenderer.prototype.symbols.arrow = function (x, y, w, h) {
  724. return [
  725. 'M', x, y + h / 2,
  726. 'L', x + w, y,
  727. 'L', x, y + h / 2,
  728. 'L', x + w, y + h
  729. ];
  730. };
  731. /**
  732. * Creates a half-width arrow symbol. Like a triangle, except not filled.
  733. * ```
  734. * o
  735. * o
  736. * o
  737. * o
  738. * o
  739. * ```
  740. *
  741. * @private
  742. * @function
  743. *
  744. * @param {number} x
  745. * x position of the arrow
  746. *
  747. * @param {number} y
  748. * y position of the arrow
  749. *
  750. * @param {number} w
  751. * width of the arrow
  752. *
  753. * @param {number} h
  754. * height of the arrow
  755. *
  756. * @return {Highcharts.SVGPathArray}
  757. * Path array
  758. */
  759. H.SVGRenderer.prototype.symbols['arrow-half'] = function (x, y, w, h) {
  760. return H.SVGRenderer.prototype.symbols.arrow(x, y, w / 2, h);
  761. };
  762. /**
  763. * Creates a left-oriented triangle.
  764. * ```
  765. * o
  766. * ooooooo
  767. * ooooooooooooo
  768. * ooooooo
  769. * o
  770. * ```
  771. *
  772. * @private
  773. * @function
  774. *
  775. * @param {number} x
  776. * x position of the triangle
  777. *
  778. * @param {number} y
  779. * y position of the triangle
  780. *
  781. * @param {number} w
  782. * width of the triangle
  783. *
  784. * @param {number} h
  785. * height of the triangle
  786. *
  787. * @return {Highcharts.SVGPathArray}
  788. * Path array
  789. */
  790. H.SVGRenderer.prototype.symbols['triangle-left'] = function (x, y, w, h) {
  791. return [
  792. 'M', x + w, y,
  793. 'L', x, y + h / 2,
  794. 'L', x + w, y + h,
  795. 'Z'
  796. ];
  797. };
  798. /**
  799. * Alias function for triangle-left.
  800. *
  801. * @private
  802. * @function
  803. *
  804. * @param {number} x
  805. * x position of the arrow
  806. *
  807. * @param {number} y
  808. * y position of the arrow
  809. *
  810. * @param {number} w
  811. * width of the arrow
  812. *
  813. * @param {number} h
  814. * height of the arrow
  815. *
  816. * @return {Highcharts.SVGPathArray}
  817. * Path array
  818. */
  819. H.SVGRenderer.prototype.symbols['arrow-filled'] =
  820. H.SVGRenderer.prototype.symbols['triangle-left'];
  821. /**
  822. * Creates a half-width, left-oriented triangle.
  823. * ```
  824. * o
  825. * oooo
  826. * ooooooo
  827. * oooo
  828. * o
  829. * ```
  830. *
  831. * @private
  832. * @function
  833. *
  834. * @param {number} x
  835. * x position of the triangle
  836. *
  837. * @param {number} y
  838. * y position of the triangle
  839. *
  840. * @param {number} w
  841. * width of the triangle
  842. *
  843. * @param {number} h
  844. * height of the triangle
  845. *
  846. * @return {Highcharts.SVGPathArray}
  847. * Path array
  848. */
  849. H.SVGRenderer.prototype.symbols['triangle-left-half'] = function (x, y, w, h) {
  850. return H.SVGRenderer.prototype.symbols['triangle-left'](x, y, w / 2, h);
  851. };
  852. /**
  853. * Alias function for triangle-left-half.
  854. *
  855. * @private
  856. * @function
  857. *
  858. * @param {number} x
  859. * x position of the arrow
  860. *
  861. * @param {number} y
  862. * y position of the arrow
  863. *
  864. * @param {number} w
  865. * width of the arrow
  866. *
  867. * @param {number} h
  868. * height of the arrow
  869. *
  870. * @return {Highcharts.SVGPathArray}
  871. * Path array
  872. */
  873. H.SVGRenderer.prototype.symbols['arrow-filled-half'] =
  874. H.SVGRenderer.prototype.symbols['triangle-left-half'];
  875. });
  876. _registerModule(_modules, 'parts-gantt/Pathfinder.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['parts-gantt/PathfinderAlgorithms.js']], function (H, U, pathfinderAlgorithms) {
  877. /* *
  878. *
  879. * (c) 2016 Highsoft AS
  880. * Authors: Øystein Moseng, Lars A. V. Cabrera
  881. *
  882. * License: www.highcharts.com/license
  883. *
  884. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  885. *
  886. * */
  887. /**
  888. * The default pathfinder algorithm to use for a chart. It is possible to define
  889. * your own algorithms by adding them to the
  890. * `Highcharts.Pathfinder.prototype.algorithms`
  891. * object before the chart has been created.
  892. *
  893. * The default algorithms are as follows:
  894. *
  895. * `straight`: Draws a straight line between the connecting
  896. * points. Does not avoid other points when drawing.
  897. *
  898. * `simpleConnect`: Finds a path between the points using right angles
  899. * only. Takes only starting/ending points into
  900. * account, and will not avoid other points.
  901. *
  902. * `fastAvoid`: Finds a path between the points using right angles
  903. * only. Will attempt to avoid other points, but its
  904. * focus is performance over accuracy. Works well with
  905. * less dense datasets.
  906. *
  907. * @typedef {"fastAvoid"|"simpleConnect"|"straight"|string} Highcharts.PathfinderTypeValue
  908. */
  909. var defined = U.defined, extend = U.extend, objectEach = U.objectEach, pick = U.pick, splat = U.splat;
  910. var deg2rad = H.deg2rad, addEvent = H.addEvent, merge = H.merge, max = Math.max, min = Math.min;
  911. /*
  912. @todo:
  913. - Document how to write your own algorithms
  914. - Consider adding a Point.pathTo method that wraps creating a connection
  915. and rendering it
  916. */
  917. // Set default Pathfinder options
  918. extend(H.defaultOptions, {
  919. /**
  920. * The Pathfinder module allows you to define connections between any two
  921. * points, represented as lines - optionally with markers for the start
  922. * and/or end points. Multiple algorithms are available for calculating how
  923. * the connecting lines are drawn.
  924. *
  925. * Connector functionality requires Highcharts Gantt to be loaded. In Gantt
  926. * charts, the connectors are used to draw dependencies between tasks.
  927. *
  928. * @see [dependency](series.gantt.data.dependency)
  929. *
  930. * @sample gantt/pathfinder/demo
  931. * Pathfinder connections
  932. *
  933. * @declare Highcharts.ConnectorsOptions
  934. * @product gantt
  935. * @optionparent connectors
  936. */
  937. connectors: {
  938. /**
  939. * Enable connectors for this chart. Requires Highcharts Gantt.
  940. *
  941. * @type {boolean}
  942. * @default true
  943. * @since 6.2.0
  944. * @apioption connectors.enabled
  945. */
  946. /**
  947. * Set the default dash style for this chart's connecting lines.
  948. *
  949. * @type {string}
  950. * @default solid
  951. * @since 6.2.0
  952. * @apioption connectors.dashStyle
  953. */
  954. /**
  955. * Set the default color for this chart's Pathfinder connecting lines.
  956. * Defaults to the color of the point being connected.
  957. *
  958. * @type {Highcharts.ColorString}
  959. * @since 6.2.0
  960. * @apioption connectors.lineColor
  961. */
  962. /**
  963. * Set the default pathfinder margin to use, in pixels. Some Pathfinder
  964. * algorithms attempt to avoid obstacles, such as other points in the
  965. * chart. These algorithms use this margin to determine how close lines
  966. * can be to an obstacle. The default is to compute this automatically
  967. * from the size of the obstacles in the chart.
  968. *
  969. * To draw connecting lines close to existing points, set this to a low
  970. * number. For more space around existing points, set this number
  971. * higher.
  972. *
  973. * @sample gantt/pathfinder/algorithm-margin
  974. * Small algorithmMargin
  975. *
  976. * @type {number}
  977. * @since 6.2.0
  978. * @apioption connectors.algorithmMargin
  979. */
  980. /**
  981. * Set the default pathfinder algorithm to use for this chart. It is
  982. * possible to define your own algorithms by adding them to the
  983. * Highcharts.Pathfinder.prototype.algorithms object before the chart
  984. * has been created.
  985. *
  986. * The default algorithms are as follows:
  987. *
  988. * `straight`: Draws a straight line between the connecting
  989. * points. Does not avoid other points when drawing.
  990. *
  991. * `simpleConnect`: Finds a path between the points using right angles
  992. * only. Takes only starting/ending points into
  993. * account, and will not avoid other points.
  994. *
  995. * `fastAvoid`: Finds a path between the points using right angles
  996. * only. Will attempt to avoid other points, but its
  997. * focus is performance over accuracy. Works well with
  998. * less dense datasets.
  999. *
  1000. * Default value: `straight` is used as default for most series types,
  1001. * while `simpleConnect` is used as default for Gantt series, to show
  1002. * dependencies between points.
  1003. *
  1004. * @sample gantt/pathfinder/demo
  1005. * Different types used
  1006. *
  1007. * @type {Highcharts.PathfinderTypeValue}
  1008. * @default undefined
  1009. * @since 6.2.0
  1010. */
  1011. type: 'straight',
  1012. /**
  1013. * Set the default pixel width for this chart's Pathfinder connecting
  1014. * lines.
  1015. *
  1016. * @since 6.2.0
  1017. */
  1018. lineWidth: 1,
  1019. /**
  1020. * Marker options for this chart's Pathfinder connectors. Note that
  1021. * this option is overridden by the `startMarker` and `endMarker`
  1022. * options.
  1023. *
  1024. * @declare Highcharts.ConnectorsMarkerOptions
  1025. * @since 6.2.0
  1026. */
  1027. marker: {
  1028. /**
  1029. * Set the radius of the connector markers. The default is
  1030. * automatically computed based on the algorithmMargin setting.
  1031. *
  1032. * Setting marker.width and marker.height will override this
  1033. * setting.
  1034. *
  1035. * @type {number}
  1036. * @since 6.2.0
  1037. * @apioption connectors.marker.radius
  1038. */
  1039. /**
  1040. * Set the width of the connector markers. If not supplied, this
  1041. * is inferred from the marker radius.
  1042. *
  1043. * @type {number}
  1044. * @since 6.2.0
  1045. * @apioption connectors.marker.width
  1046. */
  1047. /**
  1048. * Set the height of the connector markers. If not supplied, this
  1049. * is inferred from the marker radius.
  1050. *
  1051. * @type {number}
  1052. * @since 6.2.0
  1053. * @apioption connectors.marker.height
  1054. */
  1055. /**
  1056. * Set the color of the connector markers. By default this is the
  1057. * same as the connector color.
  1058. *
  1059. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1060. * @since 6.2.0
  1061. * @apioption connectors.marker.color
  1062. */
  1063. /**
  1064. * Set the line/border color of the connector markers. By default
  1065. * this is the same as the marker color.
  1066. *
  1067. * @type {Highcharts.ColorString}
  1068. * @since 6.2.0
  1069. * @apioption connectors.marker.lineColor
  1070. */
  1071. /**
  1072. * Enable markers for the connectors.
  1073. */
  1074. enabled: false,
  1075. /**
  1076. * Horizontal alignment of the markers relative to the points.
  1077. *
  1078. * @type {Highcharts.AlignValue}
  1079. */
  1080. align: 'center',
  1081. /**
  1082. * Vertical alignment of the markers relative to the points.
  1083. *
  1084. * @type {Highcharts.VerticalAlignValue}
  1085. */
  1086. verticalAlign: 'middle',
  1087. /**
  1088. * Whether or not to draw the markers inside the points.
  1089. */
  1090. inside: false,
  1091. /**
  1092. * Set the line/border width of the pathfinder markers.
  1093. */
  1094. lineWidth: 1
  1095. },
  1096. /**
  1097. * Marker options specific to the start markers for this chart's
  1098. * Pathfinder connectors. Overrides the generic marker options.
  1099. *
  1100. * @declare Highcharts.ConnectorsStartMarkerOptions
  1101. * @extends connectors.marker
  1102. * @since 6.2.0
  1103. */
  1104. startMarker: {
  1105. /**
  1106. * Set the symbol of the connector start markers.
  1107. */
  1108. symbol: 'diamond'
  1109. },
  1110. /**
  1111. * Marker options specific to the end markers for this chart's
  1112. * Pathfinder connectors. Overrides the generic marker options.
  1113. *
  1114. * @declare Highcharts.ConnectorsEndMarkerOptions
  1115. * @extends connectors.marker
  1116. * @since 6.2.0
  1117. */
  1118. endMarker: {
  1119. /**
  1120. * Set the symbol of the connector end markers.
  1121. */
  1122. symbol: 'arrow-filled'
  1123. }
  1124. }
  1125. });
  1126. /**
  1127. * Override Pathfinder connector options for a series. Requires Highcharts Gantt
  1128. * to be loaded.
  1129. *
  1130. * @declare Highcharts.SeriesConnectorsOptionsObject
  1131. * @extends connectors
  1132. * @since 6.2.0
  1133. * @excluding enabled, algorithmMargin
  1134. * @product gantt
  1135. * @apioption plotOptions.series.connectors
  1136. */
  1137. /**
  1138. * Connect to a point. This option can be either a string, referring to the ID
  1139. * of another point, or an object, or an array of either. If the option is an
  1140. * array, each element defines a connection.
  1141. *
  1142. * @sample gantt/pathfinder/demo
  1143. * Different connection types
  1144. *
  1145. * @declare Highcharts.XrangePointConnectorsOptionsObject
  1146. * @type {string|Array<string|*>|*}
  1147. * @extends plotOptions.series.connectors
  1148. * @since 6.2.0
  1149. * @excluding enabled
  1150. * @product gantt
  1151. * @requires highcharts-gantt
  1152. * @apioption series.xrange.data.connect
  1153. */
  1154. /**
  1155. * The ID of the point to connect to.
  1156. *
  1157. * @type {string}
  1158. * @since 6.2.0
  1159. * @product gantt
  1160. * @apioption series.xrange.data.connect.to
  1161. */
  1162. /**
  1163. * Get point bounding box using plotX/plotY and shapeArgs. If using
  1164. * graphic.getBBox() directly, the bbox will be affected by animation.
  1165. *
  1166. * @private
  1167. * @function
  1168. *
  1169. * @param {Highcharts.Point} point
  1170. * The point to get BB of.
  1171. *
  1172. * @return {Highcharts.Dictionary<number>|null}
  1173. * Result xMax, xMin, yMax, yMin.
  1174. */
  1175. function getPointBB(point) {
  1176. var shapeArgs = point.shapeArgs, bb;
  1177. // Prefer using shapeArgs (columns)
  1178. if (shapeArgs) {
  1179. return {
  1180. xMin: shapeArgs.x,
  1181. xMax: shapeArgs.x + shapeArgs.width,
  1182. yMin: shapeArgs.y,
  1183. yMax: shapeArgs.y + shapeArgs.height
  1184. };
  1185. }
  1186. // Otherwise use plotX/plotY and bb
  1187. bb = point.graphic && point.graphic.getBBox();
  1188. return bb ? {
  1189. xMin: point.plotX - bb.width / 2,
  1190. xMax: point.plotX + bb.width / 2,
  1191. yMin: point.plotY - bb.height / 2,
  1192. yMax: point.plotY + bb.height / 2
  1193. } : null;
  1194. }
  1195. /**
  1196. * Calculate margin to place around obstacles for the pathfinder in pixels.
  1197. * Returns a minimum of 1 pixel margin.
  1198. *
  1199. * @private
  1200. * @function
  1201. *
  1202. * @param {Array<object>} obstacles
  1203. * Obstacles to calculate margin from.
  1204. *
  1205. * @return {number}
  1206. * The calculated margin in pixels. At least 1.
  1207. */
  1208. function calculateObstacleMargin(obstacles) {
  1209. var len = obstacles.length, i = 0, j, obstacleDistance, distances = [],
  1210. // Compute smallest distance between two rectangles
  1211. distance = function (a, b, bbMargin) {
  1212. // Count the distance even if we are slightly off
  1213. var margin = pick(bbMargin, 10), yOverlap = a.yMax + margin > b.yMin - margin &&
  1214. a.yMin - margin < b.yMax + margin, xOverlap = a.xMax + margin > b.xMin - margin &&
  1215. 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;
  1216. // If the rectangles collide, try recomputing with smaller margin.
  1217. // If they collide anyway, discard the obstacle.
  1218. if (xOverlap && yOverlap) {
  1219. return (margin ?
  1220. distance(a, b, Math.floor(margin / 2)) :
  1221. Infinity);
  1222. }
  1223. return min(xDistance, yDistance);
  1224. };
  1225. // Go over all obstacles and compare them to the others.
  1226. for (; i < len; ++i) {
  1227. // Compare to all obstacles ahead. We will already have compared this
  1228. // obstacle to the ones before.
  1229. for (j = i + 1; j < len; ++j) {
  1230. obstacleDistance = distance(obstacles[i], obstacles[j]);
  1231. // TODO: Magic number 80
  1232. if (obstacleDistance < 80) { // Ignore large distances
  1233. distances.push(obstacleDistance);
  1234. }
  1235. }
  1236. }
  1237. // Ensure we always have at least one value, even in very spaceous charts
  1238. distances.push(80);
  1239. return max(Math.floor(distances.sort(function (a, b) {
  1240. return (a - b);
  1241. })[
  1242. // Discard first 10% of the relevant distances, and then grab
  1243. // the smallest one.
  1244. Math.floor(distances.length / 10)] / 2 - 1 // Divide the distance by 2 and subtract 1.
  1245. ), 1 // 1 is the minimum margin
  1246. );
  1247. }
  1248. /* eslint-disable no-invalid-this, valid-jsdoc */
  1249. /**
  1250. * The Connection class. Used internally to represent a connection between two
  1251. * points.
  1252. *
  1253. * @private
  1254. * @class
  1255. * @name Highcharts.Connection
  1256. *
  1257. * @param {Highcharts.Point} from
  1258. * Connection runs from this Point.
  1259. *
  1260. * @param {Highcharts.Point} to
  1261. * Connection runs to this Point.
  1262. *
  1263. * @param {Highcharts.ConnectorsOptions} [options]
  1264. * Connection options.
  1265. */
  1266. function Connection(from, to, options) {
  1267. this.init(from, to, options);
  1268. }
  1269. Connection.prototype = {
  1270. /**
  1271. * Initialize the Connection object. Used as constructor only.
  1272. *
  1273. * @function Highcharts.Connection#init
  1274. *
  1275. * @param {Highcharts.Point} from
  1276. * Connection runs from this Point.
  1277. *
  1278. * @param {Highcharts.Point} to
  1279. * Connection runs to this Point.
  1280. *
  1281. * @param {Highcharts.ConnectorsOptions} [options]
  1282. * Connection options.
  1283. */
  1284. init: function (from, to, options) {
  1285. this.fromPoint = from;
  1286. this.toPoint = to;
  1287. this.options = options;
  1288. this.chart = from.series.chart;
  1289. this.pathfinder = this.chart.pathfinder;
  1290. },
  1291. /**
  1292. * Add (or update) this connection's path on chart. Stores reference to the
  1293. * created element on this.graphics.path.
  1294. *
  1295. * @function Highcharts.Connection#renderPath
  1296. *
  1297. * @param {Highcharts.SVGPathArray} path
  1298. * Path to render, in array format. E.g. ['M', 0, 0, 'L', 10, 10]
  1299. *
  1300. * @param {Highcharts.SVGAttributes} [attribs]
  1301. * SVG attributes for the path.
  1302. *
  1303. * @param {Highcharts.AnimationOptionsObject} [animation]
  1304. * Animation options for the rendering.
  1305. */
  1306. renderPath: function (path, attribs, animation) {
  1307. 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;
  1308. // Add the SVG element of the pathfinder group if it doesn't exist
  1309. if (!pathfinder.group) {
  1310. pathfinder.group = chart.renderer.g()
  1311. .addClass('highcharts-pathfinder-group')
  1312. .attr({ zIndex: -1 })
  1313. .add(chart.seriesGroup);
  1314. }
  1315. // Shift the group to compensate for plot area.
  1316. // Note: Do this always (even when redrawing a path) to avoid issues
  1317. // when updating chart in a way that changes plot metrics.
  1318. pathfinder.group.translate(chart.plotLeft, chart.plotTop);
  1319. // Create path if does not exist
  1320. if (!(pathGraphic && pathGraphic.renderer)) {
  1321. pathGraphic = chart.renderer.path()
  1322. .add(pathfinder.group);
  1323. if (!styledMode) {
  1324. pathGraphic.attr({
  1325. opacity: 0
  1326. });
  1327. }
  1328. }
  1329. // Set path attribs and animate to the new path
  1330. pathGraphic.attr(attribs);
  1331. anim = { d: path };
  1332. if (!styledMode) {
  1333. anim.opacity = 1;
  1334. }
  1335. pathGraphic[animate ? 'animate' : 'attr'](anim, animation);
  1336. // Store reference on connection
  1337. this.graphics = this.graphics || {};
  1338. this.graphics.path = pathGraphic;
  1339. },
  1340. /**
  1341. * Calculate and add marker graphics for connection to the chart. The
  1342. * created/updated elements are stored on this.graphics.start and
  1343. * this.graphics.end.
  1344. *
  1345. * @function Highcharts.Connection#addMarker
  1346. *
  1347. * @param {string} type
  1348. * Marker type, either 'start' or 'end'.
  1349. *
  1350. * @param {Highcharts.ConnectorsMarkerOptions} options
  1351. * All options for this marker. Not calculated or merged with other
  1352. * options.
  1353. *
  1354. * @param {Highcharts.SVGPathArray} path
  1355. * Connection path in array format. This is used to calculate the
  1356. * rotation angle of the markers.
  1357. */
  1358. addMarker: function (type, options, path) {
  1359. var connection = this, chart = connection.fromPoint.series.chart, pathfinder = chart.pathfinder, renderer = chart.renderer, point = (type === 'start' ?
  1360. connection.fromPoint :
  1361. connection.toPoint), anchor = point.getPathfinderAnchorPoint(options), markerVector, radians, rotation, box, width, height, pathVector;
  1362. if (!options.enabled) {
  1363. return;
  1364. }
  1365. // Last vector before start/end of path, used to get angle
  1366. if (type === 'start') {
  1367. pathVector = {
  1368. x: path[4],
  1369. y: path[5]
  1370. };
  1371. }
  1372. else { // 'end'
  1373. pathVector = {
  1374. x: path[path.length - 5],
  1375. y: path[path.length - 4]
  1376. };
  1377. }
  1378. // Get angle between pathVector and anchor point and use it to create
  1379. // marker position.
  1380. radians = point.getRadiansToVector(pathVector, anchor);
  1381. markerVector = point.getMarkerVector(radians, options.radius, anchor);
  1382. // Rotation of marker is calculated from angle between pathVector and
  1383. // markerVector.
  1384. // (Note:
  1385. // Used to recalculate radians between markerVector and pathVector,
  1386. // but this should be the same as between pathVector and anchor.)
  1387. rotation = -radians / deg2rad;
  1388. if (options.width && options.height) {
  1389. width = options.width;
  1390. height = options.height;
  1391. }
  1392. else {
  1393. width = height = options.radius * 2;
  1394. }
  1395. // Add graphics object if it does not exist
  1396. connection.graphics = connection.graphics || {};
  1397. box = {
  1398. x: markerVector.x - (width / 2),
  1399. y: markerVector.y - (height / 2),
  1400. width: width,
  1401. height: height,
  1402. rotation: rotation,
  1403. rotationOriginX: markerVector.x,
  1404. rotationOriginY: markerVector.y
  1405. };
  1406. if (!connection.graphics[type]) {
  1407. // Create new marker element
  1408. connection.graphics[type] = renderer
  1409. .symbol(options.symbol)
  1410. .addClass('highcharts-point-connecting-path-' + type + '-marker')
  1411. .attr(box)
  1412. .add(pathfinder.group);
  1413. if (!renderer.styledMode) {
  1414. connection.graphics[type].attr({
  1415. fill: options.color || connection.fromPoint.color,
  1416. stroke: options.lineColor,
  1417. 'stroke-width': options.lineWidth,
  1418. opacity: 0
  1419. })
  1420. .animate({
  1421. opacity: 1
  1422. }, point.series.options.animation);
  1423. }
  1424. }
  1425. else {
  1426. connection.graphics[type].animate(box);
  1427. }
  1428. },
  1429. /**
  1430. * Calculate and return connection path.
  1431. * Note: Recalculates chart obstacles on demand if they aren't calculated.
  1432. *
  1433. * @function Highcharts.Connection#getPath
  1434. *
  1435. * @param {Highcharts.ConnectorsOptions} options
  1436. * Connector options. Not calculated or merged with other options.
  1437. *
  1438. * @return {object|undefined}
  1439. * Calculated SVG path data in array format.
  1440. */
  1441. getPath: function (options) {
  1442. var pathfinder = this.pathfinder, chart = this.chart, algorithm = pathfinder.algorithms[options.type], chartObstacles = pathfinder.chartObstacles;
  1443. if (typeof algorithm !== 'function') {
  1444. H.error('"' + options.type + '" is not a Pathfinder algorithm.');
  1445. return;
  1446. }
  1447. // This function calculates obstacles on demand if they don't exist
  1448. if (algorithm.requiresObstacles && !chartObstacles) {
  1449. chartObstacles =
  1450. pathfinder.chartObstacles =
  1451. pathfinder.getChartObstacles(options);
  1452. // If the algorithmMargin was computed, store the result in default
  1453. // options.
  1454. chart.options.connectors.algorithmMargin =
  1455. options.algorithmMargin;
  1456. // Cache some metrics too
  1457. pathfinder.chartObstacleMetrics =
  1458. pathfinder.getObstacleMetrics(chartObstacles);
  1459. }
  1460. // Get the SVG path
  1461. return algorithm(
  1462. // From
  1463. this.fromPoint.getPathfinderAnchorPoint(options.startMarker),
  1464. // To
  1465. this.toPoint.getPathfinderAnchorPoint(options.endMarker), merge({
  1466. chartObstacles: chartObstacles,
  1467. lineObstacles: pathfinder.lineObstacles || [],
  1468. obstacleMetrics: pathfinder.chartObstacleMetrics,
  1469. hardBounds: {
  1470. xMin: 0,
  1471. xMax: chart.plotWidth,
  1472. yMin: 0,
  1473. yMax: chart.plotHeight
  1474. },
  1475. obstacleOptions: {
  1476. margin: options.algorithmMargin
  1477. },
  1478. startDirectionX: pathfinder.getAlgorithmStartDirection(options.startMarker)
  1479. }, options));
  1480. },
  1481. /**
  1482. * (re)Calculate and (re)draw the connection.
  1483. *
  1484. * @function Highcharts.Connection#render
  1485. */
  1486. render: function () {
  1487. 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 = {};
  1488. // Set path attribs
  1489. if (!chart.styledMode) {
  1490. attribs.stroke = options.lineColor || fromPoint.color;
  1491. attribs['stroke-width'] = options.lineWidth;
  1492. if (options.dashStyle) {
  1493. attribs.dashstyle = options.dashStyle;
  1494. }
  1495. }
  1496. attribs['class'] = // eslint-disable-line dot-notation
  1497. 'highcharts-point-connecting-path ' +
  1498. 'highcharts-color-' + fromPoint.colorIndex;
  1499. options = merge(attribs, options);
  1500. // Set common marker options
  1501. if (!defined(options.marker.radius)) {
  1502. options.marker.radius = min(max(Math.ceil((options.algorithmMargin || 8) / 2) - 1, 1), 5);
  1503. }
  1504. // Get the path
  1505. pathResult = connection.getPath(options);
  1506. path = pathResult.path;
  1507. // Always update obstacle storage with obstacles from this path.
  1508. // We don't know if future calls will need this for their algorithm.
  1509. if (pathResult.obstacles) {
  1510. pathfinder.lineObstacles =
  1511. pathfinder.lineObstacles || [];
  1512. pathfinder.lineObstacles =
  1513. pathfinder.lineObstacles.concat(pathResult.obstacles);
  1514. }
  1515. // Add the calculated path to the pathfinder group
  1516. connection.renderPath(path, attribs, series.options.animation);
  1517. // Render the markers
  1518. connection.addMarker('start', merge(options.marker, options.startMarker), path);
  1519. connection.addMarker('end', merge(options.marker, options.endMarker), path);
  1520. },
  1521. /**
  1522. * Destroy connection by destroying the added graphics elements.
  1523. *
  1524. * @function Highcharts.Connection#destroy
  1525. */
  1526. destroy: function () {
  1527. if (this.graphics) {
  1528. objectEach(this.graphics, function (val) {
  1529. val.destroy();
  1530. });
  1531. delete this.graphics;
  1532. }
  1533. }
  1534. };
  1535. /**
  1536. * The Pathfinder class.
  1537. *
  1538. * @private
  1539. * @class
  1540. * @name Highcharts.Pathfinder
  1541. *
  1542. * @param {Highcharts.Chart} chart
  1543. * The chart to operate on.
  1544. */
  1545. function Pathfinder(chart) {
  1546. this.init(chart);
  1547. }
  1548. Pathfinder.prototype = {
  1549. /**
  1550. * @name Highcharts.Pathfinder#algorithms
  1551. * @type {Highcharts.Dictionary<Function>}
  1552. */
  1553. algorithms: pathfinderAlgorithms,
  1554. /**
  1555. * Initialize the Pathfinder object.
  1556. *
  1557. * @function Highcharts.Pathfinder#init
  1558. *
  1559. * @param {Highcharts.Chart} chart
  1560. * The chart context.
  1561. */
  1562. init: function (chart) {
  1563. // Initialize pathfinder with chart context
  1564. this.chart = chart;
  1565. // Init connection reference list
  1566. this.connections = [];
  1567. // Recalculate paths/obstacles on chart redraw
  1568. addEvent(chart, 'redraw', function () {
  1569. this.pathfinder.update();
  1570. });
  1571. },
  1572. /**
  1573. * Update Pathfinder connections from scratch.
  1574. *
  1575. * @function Highcharts.Pathfinder#update
  1576. *
  1577. * @param {boolean} [deferRender]
  1578. * Whether or not to defer rendering of connections until
  1579. * series.afterAnimate event has fired. Used on first render.
  1580. */
  1581. update: function (deferRender) {
  1582. var chart = this.chart, pathfinder = this, oldConnections = pathfinder.connections;
  1583. // Rebuild pathfinder connections from options
  1584. pathfinder.connections = [];
  1585. chart.series.forEach(function (series) {
  1586. if (series.visible && !series.options.isInternal) {
  1587. series.points.forEach(function (point) {
  1588. var to, connects = (point.options &&
  1589. point.options.connect &&
  1590. splat(point.options.connect));
  1591. if (point.visible && point.isInside !== false && connects) {
  1592. connects.forEach(function (connect) {
  1593. to = chart.get(typeof connect === 'string' ?
  1594. connect : connect.to);
  1595. if (to instanceof H.Point &&
  1596. to.series.visible &&
  1597. to.visible &&
  1598. to.isInside !== false) {
  1599. // Add new connection
  1600. pathfinder.connections.push(new Connection(point, // from
  1601. to, typeof connect === 'string' ?
  1602. {} :
  1603. connect));
  1604. }
  1605. });
  1606. }
  1607. });
  1608. }
  1609. });
  1610. // Clear connections that should not be updated, and move old info over
  1611. // to new connections.
  1612. for (var j = 0, k, found, lenOld = oldConnections.length, lenNew = pathfinder.connections.length; j < lenOld; ++j) {
  1613. found = false;
  1614. for (k = 0; k < lenNew; ++k) {
  1615. if (oldConnections[j].fromPoint ===
  1616. pathfinder.connections[k].fromPoint &&
  1617. oldConnections[j].toPoint ===
  1618. pathfinder.connections[k].toPoint) {
  1619. pathfinder.connections[k].graphics =
  1620. oldConnections[j].graphics;
  1621. found = true;
  1622. break;
  1623. }
  1624. }
  1625. if (!found) {
  1626. oldConnections[j].destroy();
  1627. }
  1628. }
  1629. // Clear obstacles to force recalculation. This must be done on every
  1630. // redraw in case positions have changed. Recalculation is handled in
  1631. // Connection.getPath on demand.
  1632. delete this.chartObstacles;
  1633. delete this.lineObstacles;
  1634. // Draw the pending connections
  1635. pathfinder.renderConnections(deferRender);
  1636. },
  1637. /**
  1638. * Draw the chart's connecting paths.
  1639. *
  1640. * @function Highcharts.Pathfinder#renderConnections
  1641. *
  1642. * @param {boolean} [deferRender]
  1643. * Whether or not to defer render until series animation is finished.
  1644. * Used on first render.
  1645. */
  1646. renderConnections: function (deferRender) {
  1647. if (deferRender) {
  1648. // Render after series are done animating
  1649. this.chart.series.forEach(function (series) {
  1650. var render = function () {
  1651. // Find pathfinder connections belonging to this series
  1652. // that haven't rendered, and render them now.
  1653. var pathfinder = series.chart.pathfinder, conns = pathfinder && pathfinder.connections || [];
  1654. conns.forEach(function (connection) {
  1655. if (connection.fromPoint &&
  1656. connection.fromPoint.series === series) {
  1657. connection.render();
  1658. }
  1659. });
  1660. if (series.pathfinderRemoveRenderEvent) {
  1661. series.pathfinderRemoveRenderEvent();
  1662. delete series.pathfinderRemoveRenderEvent;
  1663. }
  1664. };
  1665. if (series.options.animation === false) {
  1666. render();
  1667. }
  1668. else {
  1669. series.pathfinderRemoveRenderEvent = addEvent(series, 'afterAnimate', render);
  1670. }
  1671. });
  1672. }
  1673. else {
  1674. // Go through connections and render them
  1675. this.connections.forEach(function (connection) {
  1676. connection.render();
  1677. });
  1678. }
  1679. },
  1680. /**
  1681. * Get obstacles for the points in the chart. Does not include connecting
  1682. * lines from Pathfinder. Applies algorithmMargin to the obstacles.
  1683. *
  1684. * @function Highcharts.Pathfinder#getChartObstacles
  1685. *
  1686. * @param {object} options
  1687. * Options for the calculation. Currenlty only
  1688. * options.algorithmMargin.
  1689. *
  1690. * @return {Array<object>}
  1691. * An array of calculated obstacles. Each obstacle is defined as an
  1692. * object with xMin, xMax, yMin and yMax properties.
  1693. */
  1694. getChartObstacles: function (options) {
  1695. var obstacles = [], series = this.chart.series, margin = pick(options.algorithmMargin, 0), calculatedMargin;
  1696. for (var i = 0, sLen = series.length; i < sLen; ++i) {
  1697. if (series[i].visible && !series[i].options.isInternal) {
  1698. for (var j = 0, pLen = series[i].points.length, bb, point; j < pLen; ++j) {
  1699. point = series[i].points[j];
  1700. if (point.visible) {
  1701. bb = getPointBB(point);
  1702. if (bb) {
  1703. obstacles.push({
  1704. xMin: bb.xMin - margin,
  1705. xMax: bb.xMax + margin,
  1706. yMin: bb.yMin - margin,
  1707. yMax: bb.yMax + margin
  1708. });
  1709. }
  1710. }
  1711. }
  1712. }
  1713. }
  1714. // Sort obstacles by xMin for optimization
  1715. obstacles = obstacles.sort(function (a, b) {
  1716. return a.xMin - b.xMin;
  1717. });
  1718. // Add auto-calculated margin if the option is not defined
  1719. if (!defined(options.algorithmMargin)) {
  1720. calculatedMargin =
  1721. options.algorithmMargin =
  1722. calculateObstacleMargin(obstacles);
  1723. obstacles.forEach(function (obstacle) {
  1724. obstacle.xMin -= calculatedMargin;
  1725. obstacle.xMax += calculatedMargin;
  1726. obstacle.yMin -= calculatedMargin;
  1727. obstacle.yMax += calculatedMargin;
  1728. });
  1729. }
  1730. return obstacles;
  1731. },
  1732. /**
  1733. * Utility function to get metrics for obstacles:
  1734. * - Widest obstacle width
  1735. * - Tallest obstacle height
  1736. *
  1737. * @function Highcharts.Pathfinder#getObstacleMetrics
  1738. *
  1739. * @param {Array<object>} obstacles
  1740. * An array of obstacles to inspect.
  1741. *
  1742. * @return {object}
  1743. * The calculated metrics, as an object with maxHeight and maxWidth
  1744. * properties.
  1745. */
  1746. getObstacleMetrics: function (obstacles) {
  1747. var maxWidth = 0, maxHeight = 0, width, height, i = obstacles.length;
  1748. while (i--) {
  1749. width = obstacles[i].xMax - obstacles[i].xMin;
  1750. height = obstacles[i].yMax - obstacles[i].yMin;
  1751. if (maxWidth < width) {
  1752. maxWidth = width;
  1753. }
  1754. if (maxHeight < height) {
  1755. maxHeight = height;
  1756. }
  1757. }
  1758. return {
  1759. maxHeight: maxHeight,
  1760. maxWidth: maxWidth
  1761. };
  1762. },
  1763. /**
  1764. * Utility to get which direction to start the pathfinding algorithm
  1765. * (X vs Y), calculated from a set of marker options.
  1766. *
  1767. * @function Highcharts.Pathfinder#getAlgorithmStartDirection
  1768. *
  1769. * @param {Highcharts.ConnectorsMarkerOptions} markerOptions
  1770. * Marker options to calculate from.
  1771. *
  1772. * @return {boolean}
  1773. * Returns true for X, false for Y, and undefined for autocalculate.
  1774. */
  1775. getAlgorithmStartDirection: function (markerOptions) {
  1776. var xCenter = markerOptions.align !== 'left' &&
  1777. markerOptions.align !== 'right', yCenter = markerOptions.verticalAlign !== 'top' &&
  1778. markerOptions.verticalAlign !== 'bottom', undef;
  1779. return xCenter ?
  1780. (yCenter ? undef : false) : // x is centered
  1781. (yCenter ? true : undef); // x is off-center
  1782. }
  1783. };
  1784. // Add to Highcharts namespace
  1785. H.Connection = Connection;
  1786. H.Pathfinder = Pathfinder;
  1787. // Add pathfinding capabilities to Points
  1788. extend(H.Point.prototype, /** @lends Point.prototype */ {
  1789. /**
  1790. * Get coordinates of anchor point for pathfinder connection.
  1791. *
  1792. * @private
  1793. * @function Highcharts.Point#getPathfinderAnchorPoint
  1794. *
  1795. * @param {Highcharts.ConnectorsMarkerOptions} markerOptions
  1796. * Connection options for position on point.
  1797. *
  1798. * @return {Highcharts.PositionObject}
  1799. * An object with x/y properties for the position. Coordinates are
  1800. * in plot values, not relative to point.
  1801. */
  1802. getPathfinderAnchorPoint: function (markerOptions) {
  1803. var bb = getPointBB(this), x, y;
  1804. switch (markerOptions.align) { // eslint-disable-line default-case
  1805. case 'right':
  1806. x = 'xMax';
  1807. break;
  1808. case 'left':
  1809. x = 'xMin';
  1810. }
  1811. switch (markerOptions.verticalAlign) { // eslint-disable-line default-case
  1812. case 'top':
  1813. y = 'yMin';
  1814. break;
  1815. case 'bottom':
  1816. y = 'yMax';
  1817. }
  1818. return {
  1819. x: x ? bb[x] : (bb.xMin + bb.xMax) / 2,
  1820. y: y ? bb[y] : (bb.yMin + bb.yMax) / 2
  1821. };
  1822. },
  1823. /**
  1824. * Utility to get the angle from one point to another.
  1825. *
  1826. * @private
  1827. * @function Highcharts.Point#getRadiansToVector
  1828. *
  1829. * @param {Highcharts.PositionObject} v1
  1830. * The first vector, as an object with x/y properties.
  1831. *
  1832. * @param {Highcharts.PositionObject} v2
  1833. * The second vector, as an object with x/y properties.
  1834. *
  1835. * @return {number}
  1836. * The angle in degrees
  1837. */
  1838. getRadiansToVector: function (v1, v2) {
  1839. var box;
  1840. if (!defined(v2)) {
  1841. box = getPointBB(this);
  1842. v2 = {
  1843. x: (box.xMin + box.xMax) / 2,
  1844. y: (box.yMin + box.yMax) / 2
  1845. };
  1846. }
  1847. return Math.atan2(v2.y - v1.y, v1.x - v2.x);
  1848. },
  1849. /**
  1850. * Utility to get the position of the marker, based on the path angle and
  1851. * the marker's radius.
  1852. *
  1853. * @private
  1854. * @function Highcharts.Point#getMarkerVector
  1855. *
  1856. * @param {number} radians
  1857. * The angle in radians from the point center to another vector.
  1858. *
  1859. * @param {number} markerRadius
  1860. * The radius of the marker, to calculate the additional distance to
  1861. * the center of the marker.
  1862. *
  1863. * @param {object} anchor
  1864. * The anchor point of the path and marker as an object with x/y
  1865. * properties.
  1866. *
  1867. * @return {object}
  1868. * The marker vector as an object with x/y properties.
  1869. */
  1870. getMarkerVector: function (radians, markerRadius, anchor) {
  1871. 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 = {
  1872. x: rectHorizontalCenter,
  1873. y: rectVerticalCenter
  1874. }, markerPoint = {}, xFactor = 1, yFactor = 1;
  1875. while (theta < -Math.PI) {
  1876. theta += twoPI;
  1877. }
  1878. while (theta > Math.PI) {
  1879. theta -= twoPI;
  1880. }
  1881. tanTheta = Math.tan(theta);
  1882. if ((theta > -rAtan) && (theta <= rAtan)) {
  1883. // Right side
  1884. yFactor = -1;
  1885. leftOrRightRegion = true;
  1886. }
  1887. else if (theta > rAtan && theta <= (Math.PI - rAtan)) {
  1888. // Top side
  1889. yFactor = -1;
  1890. }
  1891. else if (theta > (Math.PI - rAtan) || theta <= -(Math.PI - rAtan)) {
  1892. // Left side
  1893. xFactor = -1;
  1894. leftOrRightRegion = true;
  1895. }
  1896. else {
  1897. // Bottom side
  1898. xFactor = -1;
  1899. }
  1900. // Correct the edgePoint according to the placement of the marker
  1901. if (leftOrRightRegion) {
  1902. edgePoint.x += xFactor * (rectHalfWidth);
  1903. edgePoint.y += yFactor * (rectHalfWidth) * tanTheta;
  1904. }
  1905. else {
  1906. edgePoint.x += xFactor * (rectHeight / (2.0 * tanTheta));
  1907. edgePoint.y += yFactor * (rectHalfHeight);
  1908. }
  1909. if (anchor.x !== rectHorizontalCenter) {
  1910. edgePoint.x = anchor.x;
  1911. }
  1912. if (anchor.y !== rectVerticalCenter) {
  1913. edgePoint.y = anchor.y;
  1914. }
  1915. markerPoint.x = edgePoint.x + (markerRadius * Math.cos(theta));
  1916. markerPoint.y = edgePoint.y - (markerRadius * Math.sin(theta));
  1917. return markerPoint;
  1918. }
  1919. });
  1920. /**
  1921. * Warn if using legacy options. Copy the options over. Note that this will
  1922. * still break if using the legacy options in chart.update, addSeries etc.
  1923. * @private
  1924. */
  1925. function warnLegacy(chart) {
  1926. if (chart.options.pathfinder ||
  1927. chart.series.reduce(function (acc, series) {
  1928. if (series.options) {
  1929. merge(true, (series.options.connectors = series.options.connectors ||
  1930. {}), series.options.pathfinder);
  1931. }
  1932. return acc || series.options && series.options.pathfinder;
  1933. }, false)) {
  1934. merge(true, (chart.options.connectors = chart.options.connectors || {}), chart.options.pathfinder);
  1935. H.error('WARNING: Pathfinder options have been renamed. ' +
  1936. 'Use "chart.connectors" or "series.connectors" instead.');
  1937. }
  1938. }
  1939. // Initialize Pathfinder for charts
  1940. H.Chart.prototype.callbacks.push(function (chart) {
  1941. var options = chart.options;
  1942. if (options.connectors.enabled !== false) {
  1943. warnLegacy(chart);
  1944. this.pathfinder = new Pathfinder(this);
  1945. this.pathfinder.update(true); // First draw, defer render
  1946. }
  1947. });
  1948. });
  1949. _registerModule(_modules, 'masters/modules/pathfinder.src.js', [], function () {
  1950. });
  1951. }));