venn.src.js 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825
  1. /**
  2. * @license Highcharts JS v8.2.0 (2020-08-20)
  3. *
  4. * (c) 2017-2019 Highsoft AS
  5. * Authors: Jon Arild Nygard
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. factory['default'] = factory;
  13. module.exports = factory;
  14. } else if (typeof define === 'function' && define.amd) {
  15. define('highcharts/modules/venn', ['highcharts'], function (Highcharts) {
  16. factory(Highcharts);
  17. factory.Highcharts = Highcharts;
  18. return factory;
  19. });
  20. } else {
  21. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  22. }
  23. }(function (Highcharts) {
  24. var _modules = Highcharts ? Highcharts._modules : {};
  25. function _registerModule(obj, path, args, fn) {
  26. if (!obj.hasOwnProperty(path)) {
  27. obj[path] = fn.apply(null, args);
  28. }
  29. }
  30. _registerModule(_modules, 'Mixins/DrawPoint.js', [], function () {
  31. /* *
  32. *
  33. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  34. *
  35. * */
  36. var isFn = function (x) {
  37. return typeof x === 'function';
  38. };
  39. /* eslint-disable no-invalid-this, valid-jsdoc */
  40. /**
  41. * Handles the drawing of a component.
  42. * Can be used for any type of component that reserves the graphic property, and
  43. * provides a shouldDraw on its context.
  44. *
  45. * @private
  46. * @function draw
  47. * @param {DrawPointParams} params
  48. * Parameters.
  49. *
  50. * @todo add type checking.
  51. * @todo export this function to enable usage
  52. */
  53. var draw = function draw(params) {
  54. var _a;
  55. var component = this,
  56. graphic = component.graphic,
  57. animatableAttribs = params.animatableAttribs,
  58. onComplete = params.onComplete,
  59. css = params.css,
  60. renderer = params.renderer,
  61. animation = (_a = component.series) === null || _a === void 0 ? void 0 : _a.options.animation;
  62. if (component.shouldDraw()) {
  63. if (!graphic) {
  64. component.graphic = graphic =
  65. renderer[params.shapeType](params.shapeArgs)
  66. .add(params.group);
  67. }
  68. graphic
  69. .css(css)
  70. .attr(params.attribs)
  71. .animate(animatableAttribs, params.isNew ? false : animation, onComplete);
  72. }
  73. else if (graphic) {
  74. var destroy = function () {
  75. component.graphic = graphic = graphic.destroy();
  76. if (isFn(onComplete)) {
  77. onComplete();
  78. }
  79. };
  80. // animate only runs complete callback if something was animated.
  81. if (Object.keys(animatableAttribs).length) {
  82. graphic.animate(animatableAttribs, void 0, function () {
  83. destroy();
  84. });
  85. }
  86. else {
  87. destroy();
  88. }
  89. }
  90. };
  91. /**
  92. * An extended version of draw customized for points.
  93. * It calls additional methods that is expected when rendering a point.
  94. * @private
  95. * @param {Highcharts.Dictionary<any>} params Parameters
  96. */
  97. var drawPoint = function drawPoint(params) {
  98. var point = this,
  99. attribs = params.attribs = params.attribs || {};
  100. // Assigning class in dot notation does go well in IE8
  101. // eslint-disable-next-line dot-notation
  102. attribs['class'] = point.getClassName();
  103. // Call draw to render component
  104. draw.call(point, params);
  105. };
  106. var drawPointModule = {
  107. draw: draw,
  108. drawPoint: drawPoint,
  109. isFn: isFn
  110. };
  111. return drawPointModule;
  112. });
  113. _registerModule(_modules, 'Mixins/Geometry.js', [], function () {
  114. /* *
  115. *
  116. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  117. *
  118. * */
  119. /**
  120. * Calculates the center between a list of points.
  121. * @private
  122. * @param {Array<Highcharts.PositionObject>} points
  123. * A list of points to calculate the center of.
  124. * @return {Highcharts.PositionObject}
  125. * Calculated center
  126. */
  127. var getCenterOfPoints = function getCenterOfPoints(points) {
  128. var sum = points.reduce(function (sum,
  129. point) {
  130. sum.x += point.x;
  131. sum.y += point.y;
  132. return sum;
  133. }, { x: 0, y: 0 });
  134. return {
  135. x: sum.x / points.length,
  136. y: sum.y / points.length
  137. };
  138. };
  139. /**
  140. * Calculates the distance between two points based on their x and y
  141. * coordinates.
  142. * @private
  143. * @param {Highcharts.PositionObject} p1
  144. * The x and y coordinates of the first point.
  145. * @param {Highcharts.PositionObject} p2
  146. * The x and y coordinates of the second point.
  147. * @return {number}
  148. * Returns the distance between the points.
  149. */
  150. var getDistanceBetweenPoints = function getDistanceBetweenPoints(p1,
  151. p2) {
  152. return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
  153. };
  154. /**
  155. * Calculates the angle between two points.
  156. * @todo add unit tests.
  157. * @private
  158. * @param {Highcharts.PositionObject} p1 The first point.
  159. * @param {Highcharts.PositionObject} p2 The second point.
  160. * @return {number} Returns the angle in radians.
  161. */
  162. var getAngleBetweenPoints = function getAngleBetweenPoints(p1,
  163. p2) {
  164. return Math.atan2(p2.x - p1.x,
  165. p2.y - p1.y);
  166. };
  167. var geometry = {
  168. getAngleBetweenPoints: getAngleBetweenPoints,
  169. getCenterOfPoints: getCenterOfPoints,
  170. getDistanceBetweenPoints: getDistanceBetweenPoints
  171. };
  172. return geometry;
  173. });
  174. _registerModule(_modules, 'Mixins/GeometryCircles.js', [_modules['Mixins/Geometry.js']], function (Geometry) {
  175. /* *
  176. *
  177. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  178. *
  179. * */
  180. var getAngleBetweenPoints = Geometry.getAngleBetweenPoints,
  181. getCenterOfPoints = Geometry.getCenterOfPoints,
  182. getDistanceBetweenPoints = Geometry.getDistanceBetweenPoints;
  183. /**
  184. * @private
  185. * @param {number} x
  186. * Number to round
  187. * @param {number} decimals
  188. * Number of decimals to round to
  189. * @return {number}
  190. * Rounded number
  191. */
  192. function round(x, decimals) {
  193. var a = Math.pow(10,
  194. decimals);
  195. return Math.round(x * a) / a;
  196. }
  197. /**
  198. * Calculates the area of a circle based on its radius.
  199. * @private
  200. * @param {number} r
  201. * The radius of the circle.
  202. * @return {number}
  203. * Returns the area of the circle.
  204. */
  205. function getAreaOfCircle(r) {
  206. if (r <= 0) {
  207. throw new Error('radius of circle must be a positive number.');
  208. }
  209. return Math.PI * r * r;
  210. }
  211. /**
  212. * Calculates the area of a circular segment based on the radius of the circle
  213. * and the height of the segment.
  214. * See http://mathworld.wolfram.com/CircularSegment.html
  215. * @private
  216. * @param {number} r
  217. * The radius of the circle.
  218. * @param {number} h
  219. * The height of the circular segment.
  220. * @return {number}
  221. * Returns the area of the circular segment.
  222. */
  223. function getCircularSegmentArea(r, h) {
  224. return r * r * Math.acos(1 - h / r) - (r - h) * Math.sqrt(h * (2 * r - h));
  225. }
  226. /**
  227. * Calculates the area of overlap between two circles based on their radiuses
  228. * and the distance between them.
  229. * See http://mathworld.wolfram.com/Circle-CircleIntersection.html
  230. * @private
  231. * @param {number} r1
  232. * Radius of the first circle.
  233. * @param {number} r2
  234. * Radius of the second circle.
  235. * @param {number} d
  236. * The distance between the two circles.
  237. * @return {number}
  238. * Returns the area of overlap between the two circles.
  239. */
  240. function getOverlapBetweenCircles(r1, r2, d) {
  241. var overlap = 0;
  242. // If the distance is larger than the sum of the radiuses then the circles
  243. // does not overlap.
  244. if (d < r1 + r2) {
  245. if (d <= Math.abs(r2 - r1)) {
  246. // If the circles are completely overlapping, then the overlap
  247. // equals the area of the smallest circle.
  248. overlap = getAreaOfCircle(r1 < r2 ? r1 : r2);
  249. }
  250. else {
  251. // Height of first triangle segment.
  252. var d1 = (r1 * r1 - r2 * r2 + d * d) / (2 * d),
  253. // Height of second triangle segment.
  254. d2 = d - d1;
  255. overlap = (getCircularSegmentArea(r1, r1 - d1) +
  256. getCircularSegmentArea(r2, r2 - d2));
  257. }
  258. // Round the result to two decimals.
  259. overlap = round(overlap, 14);
  260. }
  261. return overlap;
  262. }
  263. /**
  264. * Calculates the intersection points of two circles.
  265. *
  266. * NOTE: does not handle floating errors well.
  267. * @private
  268. * @param {Highcharts.CircleObject} c1
  269. * The first circle.
  270. * @param {Highcharts.CircleObject} c2
  271. * The second sircle.
  272. * @return {Array<Highcharts.PositionObject>}
  273. * Returns the resulting intersection points.
  274. */
  275. function getCircleCircleIntersection(c1, c2) {
  276. var d = getDistanceBetweenPoints(c1,
  277. c2),
  278. r1 = c1.r,
  279. r2 = c2.r;
  280. var points = [];
  281. if (d < r1 + r2 && d > Math.abs(r1 - r2)) {
  282. // If the circles are overlapping, but not completely overlapping, then
  283. // it exists intersecting points.
  284. var r1Square = r1 * r1,
  285. r2Square = r2 * r2,
  286. // d^2 - r^2 + R^2 / 2d
  287. x = (r1Square - r2Square + d * d) / (2 * d),
  288. // y^2 = R^2 - x^2
  289. y = Math.sqrt(r1Square - x * x),
  290. x1 = c1.x,
  291. x2 = c2.x,
  292. y1 = c1.y,
  293. y2 = c2.y,
  294. x0 = x1 + x * (x2 - x1) / d,
  295. y0 = y1 + x * (y2 - y1) / d,
  296. rx = -(y2 - y1) * (y / d),
  297. ry = -(x2 - x1) * (y / d);
  298. points = [
  299. { x: round(x0 + rx, 14), y: round(y0 - ry, 14) },
  300. { x: round(x0 - rx, 14), y: round(y0 + ry, 14) }
  301. ];
  302. }
  303. return points;
  304. }
  305. /**
  306. * Calculates all the intersection points for between a list of circles.
  307. * @private
  308. * @param {Array<Highcharts.CircleObject>} circles
  309. * The circles to calculate the points from.
  310. * @return {Array<Highcharts.GeometryObject>}
  311. * Returns a list of intersection points.
  312. */
  313. function getCirclesIntersectionPoints(circles) {
  314. return circles.reduce(function (points, c1, i, arr) {
  315. var additional = arr.slice(i + 1)
  316. .reduce(function (points,
  317. c2,
  318. j) {
  319. var indexes = [i,
  320. j + i + 1];
  321. return points.concat(getCircleCircleIntersection(c1, c2)
  322. .map(function (p) {
  323. p.indexes = indexes;
  324. return p;
  325. }));
  326. }, []);
  327. return points.concat(additional);
  328. }, []);
  329. }
  330. /**
  331. * Tests wether the first circle is completely overlapping the second circle.
  332. *
  333. * @private
  334. * @param {Highcharts.CircleObject} circle1 The first circle.
  335. * @param {Highcharts.CircleObject} circle2 The The second circle.
  336. * @return {boolean} Returns true if circle1 is completely overlapping circle2,
  337. * false if not.
  338. */
  339. function isCircle1CompletelyOverlappingCircle2(circle1, circle2) {
  340. return getDistanceBetweenPoints(circle1, circle2) + circle2.r <
  341. circle1.r + 1e-10;
  342. }
  343. /**
  344. * Tests wether a point lies within a given circle.
  345. * @private
  346. * @param {Highcharts.PositionObject} point
  347. * The point to test for.
  348. * @param {Highcharts.CircleObject} circle
  349. * The circle to test if the point is within.
  350. * @return {boolean}
  351. * Returns true if the point is inside, false if outside.
  352. */
  353. function isPointInsideCircle(point, circle) {
  354. return getDistanceBetweenPoints(point, circle) <= circle.r + 1e-10;
  355. }
  356. /**
  357. * Tests wether a point lies within a set of circles.
  358. * @private
  359. * @param {Highcharts.PositionObject} point
  360. * The point to test.
  361. * @param {Array<Highcharts.CircleObject>} circles
  362. * The list of circles to test against.
  363. * @return {boolean}
  364. * Returns true if the point is inside all the circles, false if not.
  365. */
  366. function isPointInsideAllCircles(point, circles) {
  367. return !circles.some(function (circle) {
  368. return !isPointInsideCircle(point, circle);
  369. });
  370. }
  371. /**
  372. * Tests wether a point lies outside a set of circles.
  373. *
  374. * TODO: add unit tests.
  375. * @private
  376. * @param {Highcharts.PositionObject} point
  377. * The point to test.
  378. * @param {Array<Highcharts.CircleObject>} circles
  379. * The list of circles to test against.
  380. * @return {boolean}
  381. * Returns true if the point is outside all the circles, false if not.
  382. */
  383. function isPointOutsideAllCircles(point, circles) {
  384. return !circles.some(function (circle) {
  385. return isPointInsideCircle(point, circle);
  386. });
  387. }
  388. /**
  389. * Calculates the points for the polygon of the intersection area between a set
  390. * of circles.
  391. *
  392. * @private
  393. * @param {Array<Highcharts.CircleObject>} circles
  394. * List of circles to calculate polygon of.
  395. * @return {Array<Highcharts.GeometryObject>} Return list of points in the
  396. * intersection polygon.
  397. */
  398. function getCirclesIntersectionPolygon(circles) {
  399. return getCirclesIntersectionPoints(circles)
  400. .filter(function (p) {
  401. return isPointInsideAllCircles(p, circles);
  402. });
  403. }
  404. /**
  405. * Calculate the path for the area of overlap between a set of circles.
  406. * @todo handle cases with only 1 or 0 arcs.
  407. * @private
  408. * @param {Array<Highcharts.CircleObject>} circles
  409. * List of circles to calculate area of.
  410. * @return {Highcharts.GeometryIntersectionObject|undefined}
  411. * Returns the path for the area of overlap. Returns an empty string if
  412. * there are no intersection between all the circles.
  413. */
  414. function getAreaOfIntersectionBetweenCircles(circles) {
  415. var intersectionPoints = getCirclesIntersectionPolygon(circles),
  416. result;
  417. if (intersectionPoints.length > 1) {
  418. // Calculate the center of the intersection points.
  419. var center_1 = getCenterOfPoints(intersectionPoints);
  420. intersectionPoints = intersectionPoints
  421. // Calculate the angle between the center and the points.
  422. .map(function (p) {
  423. p.angle = getAngleBetweenPoints(center_1, p);
  424. return p;
  425. })
  426. // Sort the points by the angle to the center.
  427. .sort(function (a, b) {
  428. return b.angle - a.angle;
  429. });
  430. var startPoint = intersectionPoints[intersectionPoints.length - 1];
  431. var arcs = intersectionPoints
  432. .reduce(function (data,
  433. p1) {
  434. var startPoint = data.startPoint,
  435. midPoint = getCenterOfPoints([startPoint,
  436. p1]);
  437. // Calculate the arc from the intersection points and their
  438. // circles.
  439. var arc = p1.indexes
  440. // Filter out circles that are not included in both
  441. // intersection points.
  442. .filter(function (index) {
  443. return startPoint.indexes.indexOf(index) > -1;
  444. })
  445. // Iterate the circles of the intersection points and
  446. // calculate arcs.
  447. .reduce(function (arc, index) {
  448. var circle = circles[index],
  449. angle1 = getAngleBetweenPoints(circle,
  450. p1),
  451. angle2 = getAngleBetweenPoints(circle,
  452. startPoint),
  453. angleDiff = angle2 - angle1 +
  454. (angle2 < angle1 ? 2 * Math.PI : 0),
  455. angle = angle2 - angleDiff / 2;
  456. var width = getDistanceBetweenPoints(midPoint, {
  457. x: circle.x + circle.r * Math.sin(angle),
  458. y: circle.y + circle.r * Math.cos(angle)
  459. });
  460. var r = circle.r;
  461. // Width can sometimes become to large due to floating
  462. // point errors
  463. if (width > r * 2) {
  464. width = r * 2;
  465. }
  466. // Get the arc with the smallest width.
  467. if (!arc || arc.width > width) {
  468. arc = {
  469. r: r,
  470. largeArc: width > r ? 1 : 0,
  471. width: width,
  472. x: p1.x,
  473. y: p1.y
  474. };
  475. }
  476. // Return the chosen arc.
  477. return arc;
  478. }, null);
  479. // If we find an arc then add it to the list and update p2.
  480. if (arc) {
  481. var r = arc.r;
  482. data.arcs.push(['A', r, r, 0, arc.largeArc, 1, arc.x, arc.y]);
  483. data.startPoint = p1;
  484. }
  485. return data;
  486. }, {
  487. startPoint: startPoint,
  488. arcs: []
  489. }).arcs;
  490. if (arcs.length === 0) {
  491. // empty
  492. }
  493. else if (arcs.length === 1) {
  494. // empty
  495. }
  496. else {
  497. arcs.unshift(['M', startPoint.x, startPoint.y]);
  498. result = {
  499. center: center_1,
  500. d: arcs
  501. };
  502. }
  503. }
  504. return result;
  505. }
  506. var geometryCircles = {
  507. getAreaOfCircle: getAreaOfCircle,
  508. getAreaOfIntersectionBetweenCircles: getAreaOfIntersectionBetweenCircles,
  509. getCircleCircleIntersection: getCircleCircleIntersection,
  510. getCirclesIntersectionPoints: getCirclesIntersectionPoints,
  511. getCirclesIntersectionPolygon: getCirclesIntersectionPolygon,
  512. getCircularSegmentArea: getCircularSegmentArea,
  513. getOverlapBetweenCircles: getOverlapBetweenCircles,
  514. isCircle1CompletelyOverlappingCircle2: isCircle1CompletelyOverlappingCircle2,
  515. isPointInsideCircle: isPointInsideCircle,
  516. isPointInsideAllCircles: isPointInsideAllCircles,
  517. isPointOutsideAllCircles: isPointOutsideAllCircles,
  518. round: round
  519. };
  520. return geometryCircles;
  521. });
  522. _registerModule(_modules, 'Mixins/NelderMead.js', [], function () {
  523. /* *
  524. *
  525. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  526. *
  527. * */
  528. /* eslint-disable valid-jsdoc */
  529. var getCentroid = function (simplex) {
  530. var arr = simplex.slice(0, -1),
  531. length = arr.length,
  532. result = [],
  533. sum = function (data,
  534. point) {
  535. data.sum += point[data.i];
  536. return data;
  537. };
  538. for (var i = 0; i < length; i++) {
  539. result[i] = arr.reduce(sum, { sum: 0, i: i }).sum / length;
  540. }
  541. return result;
  542. };
  543. /**
  544. * Finds an optimal position for a given point.
  545. * @todo add unit tests.
  546. * @todo add constraints to optimize the algorithm.
  547. * @private
  548. * @param {Highcharts.NelderMeadTestFunction} fn
  549. * The function to test a point.
  550. * @param {Highcharts.NelderMeadPointArray} initial
  551. * The initial point to optimize.
  552. * @return {Highcharts.NelderMeadPointArray}
  553. * Returns the opimized position of a point.
  554. */
  555. var nelderMead = function nelderMead(fn,
  556. initial) {
  557. var maxIterations = 100,
  558. sortByFx = function (a,
  559. b) {
  560. return a.fx - b.fx;
  561. }, pRef = 1, // Reflection parameter
  562. pExp = 2, // Expansion parameter
  563. pCon = -0.5, // Contraction parameter
  564. pOCon = pCon * pRef, // Outwards contraction parameter
  565. pShrink = 0.5; // Shrink parameter
  566. /**
  567. * @private
  568. */
  569. var weightedSum = function weightedSum(weight1,
  570. v1,
  571. weight2,
  572. v2) {
  573. return v1.map(function (x,
  574. i) {
  575. return weight1 * x + weight2 * v2[i];
  576. });
  577. };
  578. /**
  579. * @private
  580. */
  581. var getSimplex = function getSimplex(initial) {
  582. var n = initial.length,
  583. simplex = new Array(n + 1);
  584. // Initial point to the simplex.
  585. simplex[0] = initial;
  586. simplex[0].fx = fn(initial);
  587. // Create a set of extra points based on the initial.
  588. for (var i = 0; i < n; ++i) {
  589. var point = initial.slice();
  590. point[i] = point[i] ? point[i] * 1.05 : 0.001;
  591. point.fx = fn(point);
  592. simplex[i + 1] = point;
  593. }
  594. return simplex;
  595. };
  596. var updateSimplex = function (simplex,
  597. point) {
  598. point.fx = fn(point);
  599. simplex[simplex.length - 1] = point;
  600. return simplex;
  601. };
  602. var shrinkSimplex = function (simplex) {
  603. var best = simplex[0];
  604. return simplex.map(function (point) {
  605. var p = weightedSum(1 - pShrink,
  606. best,
  607. pShrink,
  608. point);
  609. p.fx = fn(p);
  610. return p;
  611. });
  612. };
  613. var getPoint = function (centroid,
  614. worst,
  615. a,
  616. b) {
  617. var point = weightedSum(a,
  618. centroid,
  619. b,
  620. worst);
  621. point.fx = fn(point);
  622. return point;
  623. };
  624. // Create a simplex
  625. var simplex = getSimplex(initial);
  626. // Iterate from 0 to max iterations
  627. for (var i = 0; i < maxIterations; i++) {
  628. // Sort the simplex
  629. simplex.sort(sortByFx);
  630. // Create a centroid from the simplex
  631. var worst = simplex[simplex.length - 1];
  632. var centroid = getCentroid(simplex);
  633. // Calculate the reflected point.
  634. var reflected = getPoint(centroid,
  635. worst, 1 + pRef, -pRef);
  636. if (reflected.fx < simplex[0].fx) {
  637. // If reflected point is the best, then possibly expand.
  638. var expanded = getPoint(centroid,
  639. worst, 1 + pExp, -pExp);
  640. simplex = updateSimplex(simplex, (expanded.fx < reflected.fx) ? expanded : reflected);
  641. }
  642. else if (reflected.fx >= simplex[simplex.length - 2].fx) {
  643. // If the reflected point is worse than the second worse, then
  644. // contract.
  645. var contracted;
  646. if (reflected.fx > worst.fx) {
  647. // If the reflected is worse than the worst point, do a
  648. // contraction
  649. contracted = getPoint(centroid, worst, 1 + pCon, -pCon);
  650. if (contracted.fx < worst.fx) {
  651. simplex = updateSimplex(simplex, contracted);
  652. }
  653. else {
  654. simplex = shrinkSimplex(simplex);
  655. }
  656. }
  657. else {
  658. // Otherwise do an outwards contraction
  659. contracted = getPoint(centroid, worst, 1 - pOCon, pOCon);
  660. if (contracted.fx < reflected.fx) {
  661. simplex = updateSimplex(simplex, contracted);
  662. }
  663. else {
  664. simplex = shrinkSimplex(simplex);
  665. }
  666. }
  667. }
  668. else {
  669. simplex = updateSimplex(simplex, reflected);
  670. }
  671. }
  672. return simplex[0];
  673. };
  674. var nelderMeadMixin = {
  675. getCentroid: getCentroid,
  676. nelderMead: nelderMead
  677. };
  678. return nelderMeadMixin;
  679. });
  680. _registerModule(_modules, 'Series/VennSeries.js', [_modules['Core/Color.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Mixins/DrawPoint.js'], _modules['Mixins/Geometry.js'], _modules['Mixins/GeometryCircles.js'], _modules['Mixins/NelderMead.js']], function (Color, H, U, drawPointModule, geometry, geometryCirclesModule, nelderMeadMixin) {
  681. /* *
  682. *
  683. * Experimental Highcharts module which enables visualization of a Venn
  684. * diagram.
  685. *
  686. * (c) 2016-2020 Highsoft AS
  687. * Authors: Jon Arild Nygard
  688. *
  689. * Layout algorithm by Ben Frederickson:
  690. * https://www.benfrederickson.com/better-venn-diagrams/
  691. *
  692. * License: www.highcharts.com/license
  693. *
  694. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  695. *
  696. * */
  697. var color = Color.parse;
  698. var addEvent = U.addEvent,
  699. animObject = U.animObject,
  700. extend = U.extend,
  701. isArray = U.isArray,
  702. isNumber = U.isNumber,
  703. isObject = U.isObject,
  704. isString = U.isString,
  705. merge = U.merge,
  706. seriesType = U.seriesType;
  707. var draw = drawPointModule.draw;
  708. var getAreaOfCircle = geometryCirclesModule.getAreaOfCircle,
  709. getAreaOfIntersectionBetweenCircles = geometryCirclesModule.getAreaOfIntersectionBetweenCircles,
  710. getCircleCircleIntersection = geometryCirclesModule.getCircleCircleIntersection,
  711. getCirclesIntersectionPolygon = geometryCirclesModule.getCirclesIntersectionPolygon,
  712. getOverlapBetweenCirclesByDistance = geometryCirclesModule.getOverlapBetweenCircles,
  713. isCircle1CompletelyOverlappingCircle2 = geometryCirclesModule.isCircle1CompletelyOverlappingCircle2,
  714. isPointInsideAllCircles = geometryCirclesModule.isPointInsideAllCircles,
  715. isPointInsideCircle = geometryCirclesModule.isPointInsideCircle,
  716. isPointOutsideAllCircles = geometryCirclesModule.isPointOutsideAllCircles;
  717. var nelderMead = nelderMeadMixin.nelderMead;
  718. var getCenterOfPoints = geometry.getCenterOfPoints,
  719. getDistanceBetweenPoints = geometry.getDistanceBetweenPoints,
  720. seriesTypes = H.seriesTypes;
  721. var objectValues = function objectValues(obj) {
  722. return Object.keys(obj).map(function (x) {
  723. return obj[x];
  724. });
  725. };
  726. /**
  727. * Calculates the area of overlap between a list of circles.
  728. * @private
  729. * @todo add support for calculating overlap between more than 2 circles.
  730. * @param {Array<Highcharts.CircleObject>} circles
  731. * List of circles with their given positions.
  732. * @return {number}
  733. * Returns the area of overlap between all the circles.
  734. */
  735. var getOverlapBetweenCircles = function getOverlapBetweenCircles(circles) {
  736. var overlap = 0;
  737. // When there is only two circles we can find the overlap by using their
  738. // radiuses and the distance between them.
  739. if (circles.length === 2) {
  740. var circle1 = circles[0];
  741. var circle2 = circles[1];
  742. overlap = getOverlapBetweenCirclesByDistance(circle1.r, circle2.r, getDistanceBetweenPoints(circle1, circle2));
  743. }
  744. return overlap;
  745. };
  746. /**
  747. * Calculates the difference between the desired overlap and the actual overlap
  748. * between two circles.
  749. * @private
  750. * @param {Dictionary<Highcharts.CircleObject>} mapOfIdToCircle
  751. * Map from id to circle.
  752. * @param {Array<Highcharts.VennRelationObject>} relations
  753. * List of relations to calculate the loss of.
  754. * @return {number}
  755. * Returns the loss between positions of the circles for the given relations.
  756. */
  757. var loss = function loss(mapOfIdToCircle,
  758. relations) {
  759. var precision = 10e10;
  760. // Iterate all the relations and calculate their individual loss.
  761. return relations.reduce(function (totalLoss, relation) {
  762. var loss = 0;
  763. if (relation.sets.length > 1) {
  764. var wantedOverlap = relation.value;
  765. // Calculate the actual overlap between the sets.
  766. var actualOverlap = getOverlapBetweenCircles(
  767. // Get the circles for the given sets.
  768. relation.sets.map(function (set) {
  769. return mapOfIdToCircle[set];
  770. }));
  771. var diff = wantedOverlap - actualOverlap;
  772. loss = Math.round((diff * diff) * precision) / precision;
  773. }
  774. // Add calculated loss to the sum.
  775. return totalLoss + loss;
  776. }, 0);
  777. };
  778. /**
  779. * Finds the root of a given function. The root is the input value needed for
  780. * a function to return 0.
  781. *
  782. * See https://en.wikipedia.org/wiki/Bisection_method#Algorithm
  783. *
  784. * TODO: Add unit tests.
  785. *
  786. * @param {Function} f
  787. * The function to find the root of.
  788. * @param {number} a
  789. * The lowest number in the search range.
  790. * @param {number} b
  791. * The highest number in the search range.
  792. * @param {number} [tolerance=1e-10]
  793. * The allowed difference between the returned value and root.
  794. * @param {number} [maxIterations=100]
  795. * The maximum iterations allowed.
  796. * @return {number}
  797. * Root number.
  798. */
  799. var bisect = function bisect(f,
  800. a,
  801. b,
  802. tolerance,
  803. maxIterations) {
  804. var fA = f(a),
  805. fB = f(b),
  806. nMax = maxIterations || 100,
  807. tol = tolerance || 1e-10,
  808. delta = b - a,
  809. n = 1,
  810. x,
  811. fX;
  812. if (a >= b) {
  813. throw new Error('a must be smaller than b.');
  814. }
  815. else if (fA * fB > 0) {
  816. throw new Error('f(a) and f(b) must have opposite signs.');
  817. }
  818. if (fA === 0) {
  819. x = a;
  820. }
  821. else if (fB === 0) {
  822. x = b;
  823. }
  824. else {
  825. while (n++ <= nMax && fX !== 0 && delta > tol) {
  826. delta = (b - a) / 2;
  827. x = a + delta;
  828. fX = f(x);
  829. // Update low and high for next search interval.
  830. if (fA * fX > 0) {
  831. a = x;
  832. }
  833. else {
  834. b = x;
  835. }
  836. }
  837. }
  838. return x;
  839. };
  840. /**
  841. * Uses the bisection method to make a best guess of the ideal distance between
  842. * two circles too get the desired overlap.
  843. * Currently there is no known formula to calculate the distance from the area
  844. * of overlap, which makes the bisection method preferred.
  845. * @private
  846. * @param {number} r1
  847. * Radius of the first circle.
  848. * @param {number} r2
  849. * Radiues of the second circle.
  850. * @param {number} overlap
  851. * The wanted overlap between the two circles.
  852. * @return {number}
  853. * Returns the distance needed to get the wanted overlap between the two
  854. * circles.
  855. */
  856. var getDistanceBetweenCirclesByOverlap = function getDistanceBetweenCirclesByOverlap(r1,
  857. r2,
  858. overlap) {
  859. var maxDistance = r1 + r2,
  860. distance;
  861. if (overlap <= 0) {
  862. // If overlap is below or equal to zero, then there is no overlap.
  863. distance = maxDistance;
  864. }
  865. else if (getAreaOfCircle(r1 < r2 ? r1 : r2) <= overlap) {
  866. // When area of overlap is larger than the area of the smallest circle,
  867. // then it is completely overlapping.
  868. distance = 0;
  869. }
  870. else {
  871. distance = bisect(function (x) {
  872. var actualOverlap = getOverlapBetweenCirclesByDistance(r1,
  873. r2,
  874. x);
  875. // Return the differance between wanted and actual overlap.
  876. return overlap - actualOverlap;
  877. }, 0, maxDistance);
  878. }
  879. return distance;
  880. };
  881. var isSet = function (x) {
  882. return isArray(x.sets) && x.sets.length === 1;
  883. };
  884. /**
  885. * Calculates a margin for a point based on the iternal and external circles.
  886. * The margin describes if the point is well placed within the internal circles,
  887. * and away from the external
  888. * @private
  889. * @todo add unit tests.
  890. * @param {Highcharts.PositionObject} point
  891. * The point to evaluate.
  892. * @param {Array<Highcharts.CircleObject>} internal
  893. * The internal circles.
  894. * @param {Array<Highcharts.CircleObject>} external
  895. * The external circles.
  896. * @return {number}
  897. * Returns the margin.
  898. */
  899. var getMarginFromCircles = function getMarginFromCircles(point,
  900. internal,
  901. external) {
  902. var margin = internal.reduce(function (margin,
  903. circle) {
  904. var m = circle.r - getDistanceBetweenPoints(point,
  905. circle);
  906. return (m <= margin) ? m : margin;
  907. }, Number.MAX_VALUE);
  908. margin = external.reduce(function (margin, circle) {
  909. var m = getDistanceBetweenPoints(point,
  910. circle) - circle.r;
  911. return (m <= margin) ? m : margin;
  912. }, margin);
  913. return margin;
  914. };
  915. /**
  916. * Finds the optimal label position by looking for a position that has a low
  917. * distance from the internal circles, and as large possible distane to the
  918. * external circles.
  919. * @private
  920. * @todo Optimize the intial position.
  921. * @todo Add unit tests.
  922. * @param {Array<Highcharts.CircleObject>} internal
  923. * Internal circles.
  924. * @param {Array<Highcharts.CircleObject>} external
  925. * External circles.
  926. * @return {Highcharts.PositionObject}
  927. * Returns the found position.
  928. */
  929. var getLabelPosition = function getLabelPosition(internal,
  930. external) {
  931. // Get the best label position within the internal circles.
  932. var best = internal.reduce(function (best,
  933. circle) {
  934. var d = circle.r / 2;
  935. // Give a set of points with the circle to evaluate as the best label
  936. // position.
  937. return [
  938. { x: circle.x, y: circle.y },
  939. { x: circle.x + d, y: circle.y },
  940. { x: circle.x - d, y: circle.y },
  941. { x: circle.x, y: circle.y + d },
  942. { x: circle.x, y: circle.y - d }
  943. ]
  944. // Iterate the given points and return the one with the largest
  945. // margin.
  946. .reduce(function (best, point) {
  947. var margin = getMarginFromCircles(point,
  948. internal,
  949. external);
  950. // If the margin better than the current best, then update best.
  951. if (best.margin < margin) {
  952. best.point = point;
  953. best.margin = margin;
  954. }
  955. return best;
  956. }, best);
  957. }, {
  958. point: void 0,
  959. margin: -Number.MAX_VALUE
  960. }).point;
  961. // Use nelder mead to optimize the initial label position.
  962. var optimal = nelderMead(function (p) {
  963. return -(getMarginFromCircles({ x: p[0],
  964. y: p[1] },
  965. internal,
  966. external));
  967. }, [best.x, best.y]);
  968. // Update best to be the point which was found to have the best margin.
  969. best = {
  970. x: optimal[0],
  971. y: optimal[1]
  972. };
  973. if (!(isPointInsideAllCircles(best, internal) &&
  974. isPointOutsideAllCircles(best, external))) {
  975. // If point was either outside one of the internal, or inside one of the
  976. // external, then it was invalid and should use a fallback.
  977. if (internal.length > 1) {
  978. best = getCenterOfPoints(getCirclesIntersectionPolygon(internal));
  979. }
  980. else {
  981. best = {
  982. x: internal[0].x,
  983. y: internal[0].y
  984. };
  985. }
  986. }
  987. // Return the best point.
  988. return best;
  989. };
  990. /**
  991. * Finds the available width for a label, by taking the label position and
  992. * finding the largest distance, which is inside all internal circles, and
  993. * outside all external circles.
  994. *
  995. * @private
  996. * @param {Highcharts.PositionObject} pos
  997. * The x and y coordinate of the label.
  998. * @param {Array<Highcharts.CircleObject>} internal
  999. * Internal circles.
  1000. * @param {Array<Highcharts.CircleObject>} external
  1001. * External circles.
  1002. * @return {number}
  1003. * Returns available width for the label.
  1004. */
  1005. var getLabelWidth = function getLabelWidth(pos,
  1006. internal,
  1007. external) {
  1008. var radius = internal.reduce(function (min,
  1009. circle) {
  1010. return Math.min(circle.r,
  1011. min);
  1012. }, Infinity),
  1013. // Filter out external circles that are completely overlapping.
  1014. filteredExternals = external.filter(function (circle) {
  1015. return !isPointInsideCircle(pos, circle);
  1016. });
  1017. var findDistance = function (maxDistance,
  1018. direction) {
  1019. return bisect(function (x) {
  1020. var testPos = {
  1021. x: pos.x + (direction * x),
  1022. y: pos.y
  1023. },
  1024. isValid = (isPointInsideAllCircles(testPos,
  1025. internal) &&
  1026. isPointOutsideAllCircles(testPos,
  1027. filteredExternals));
  1028. // If the position is valid, then we want to move towards the max
  1029. // distance. If not, then we want to away from the max distance.
  1030. return -(maxDistance - x) + (isValid ? 0 : Number.MAX_VALUE);
  1031. }, 0, maxDistance);
  1032. };
  1033. // Find the smallest distance of left and right.
  1034. return Math.min(findDistance(radius, -1), findDistance(radius, 1)) * 2;
  1035. };
  1036. /**
  1037. * Calulates data label values for a given relations object.
  1038. *
  1039. * @private
  1040. * @todo add unit tests
  1041. * @param {Highcharts.VennRelationObject} relation A relations object.
  1042. * @param {Array<Highcharts.VennRelationObject>} setRelations The list of
  1043. * relations that is a set.
  1044. * @return {Highcharts.VennLabelValuesObject}
  1045. * Returns an object containing position and width of the label.
  1046. */
  1047. function getLabelValues(relation, setRelations) {
  1048. var sets = relation.sets;
  1049. // Create a list of internal and external circles.
  1050. var data = setRelations.reduce(function (data,
  1051. set) {
  1052. // If the set exists in this relation, then it is internal,
  1053. // otherwise it will be external.
  1054. var isInternal = sets.indexOf(set.sets[0]) > -1;
  1055. var property = isInternal ? 'internal' : 'external';
  1056. // Add the circle to the list.
  1057. data[property].push(set.circle);
  1058. return data;
  1059. }, {
  1060. internal: [],
  1061. external: []
  1062. });
  1063. // Filter out external circles that are completely overlapping all internal
  1064. data.external = data.external.filter(function (externalCircle) {
  1065. return data.internal.some(function (internalCircle) {
  1066. return !isCircle1CompletelyOverlappingCircle2(externalCircle, internalCircle);
  1067. });
  1068. });
  1069. // Calulate the label position.
  1070. var position = getLabelPosition(data.internal,
  1071. data.external);
  1072. // Calculate the label width
  1073. var width = getLabelWidth(position,
  1074. data.internal,
  1075. data.external);
  1076. return {
  1077. position: position,
  1078. width: width
  1079. };
  1080. }
  1081. /**
  1082. * Takes an array of relations and adds the properties `totalOverlap` and
  1083. * `overlapping` to each set. The property `totalOverlap` is the sum of value
  1084. * for each relation where this set is included. The property `overlapping` is
  1085. * a map of how much this set is overlapping another set.
  1086. * NOTE: This algorithm ignores relations consisting of more than 2 sets.
  1087. * @private
  1088. * @param {Array<Highcharts.VennRelationObject>} relations
  1089. * The list of relations that should be sorted.
  1090. * @return {Array<Highcharts.VennRelationObject>}
  1091. * Returns the modified input relations with added properties `totalOverlap` and
  1092. * `overlapping`.
  1093. */
  1094. var addOverlapToSets = function addOverlapToSets(relations) {
  1095. // Calculate the amount of overlap per set.
  1096. var mapOfIdToProps = relations
  1097. // Filter out relations consisting of 2 sets.
  1098. .filter(function (relation) {
  1099. return relation.sets.length === 2;
  1100. })
  1101. // Sum up the amount of overlap for each set.
  1102. .reduce(function (map, relation) {
  1103. var sets = relation.sets;
  1104. sets.forEach(function (set, i, arr) {
  1105. if (!isObject(map[set])) {
  1106. map[set] = {
  1107. overlapping: {},
  1108. totalOverlap: 0
  1109. };
  1110. }
  1111. map[set].totalOverlap += relation.value;
  1112. map[set].overlapping[arr[1 - i]] = relation.value;
  1113. });
  1114. return map;
  1115. }, {});
  1116. relations
  1117. // Filter out single sets
  1118. .filter(isSet)
  1119. // Extend the set with the calculated properties.
  1120. .forEach(function (set) {
  1121. var properties = mapOfIdToProps[set.sets[0]];
  1122. extend(set, properties);
  1123. });
  1124. // Returns the modified relations.
  1125. return relations;
  1126. };
  1127. /**
  1128. * Takes two sets and finds the one with the largest total overlap.
  1129. * @private
  1130. * @param {object} a The first set to compare.
  1131. * @param {object} b The second set to compare.
  1132. * @return {number} Returns 0 if a and b are equal, <0 if a is greater, >0 if b
  1133. * is greater.
  1134. */
  1135. var sortByTotalOverlap = function sortByTotalOverlap(a,
  1136. b) {
  1137. return b.totalOverlap - a.totalOverlap;
  1138. };
  1139. /**
  1140. * Uses a greedy approach to position all the sets. Works well with a small
  1141. * number of sets, and are in these cases a good choice aesthetically.
  1142. * @private
  1143. * @param {Array<object>} relations List of the overlap between two or more
  1144. * sets, or the size of a single set.
  1145. * @return {Array<object>} List of circles and their calculated positions.
  1146. */
  1147. var layoutGreedyVenn = function layoutGreedyVenn(relations) {
  1148. var positionedSets = [],
  1149. mapOfIdToCircles = {};
  1150. // Define a circle for each set.
  1151. relations
  1152. .filter(function (relation) {
  1153. return relation.sets.length === 1;
  1154. }).forEach(function (relation) {
  1155. mapOfIdToCircles[relation.sets[0]] = relation.circle = {
  1156. x: Number.MAX_VALUE,
  1157. y: Number.MAX_VALUE,
  1158. r: Math.sqrt(relation.value / Math.PI)
  1159. };
  1160. });
  1161. /**
  1162. * Takes a set and updates the position, and add the set to the list of
  1163. * positioned sets.
  1164. * @private
  1165. * @param {object} set
  1166. * The set to add to its final position.
  1167. * @param {object} coordinates
  1168. * The coordinates to position the set at.
  1169. * @return {void}
  1170. */
  1171. var positionSet = function positionSet(set,
  1172. coordinates) {
  1173. var circle = set.circle;
  1174. circle.x = coordinates.x;
  1175. circle.y = coordinates.y;
  1176. positionedSets.push(set);
  1177. };
  1178. // Find overlap between sets. Ignore relations with more then 2 sets.
  1179. addOverlapToSets(relations);
  1180. // Sort sets by the sum of their size from large to small.
  1181. var sortedByOverlap = relations
  1182. .filter(isSet)
  1183. .sort(sortByTotalOverlap);
  1184. // Position the most overlapped set at 0,0.
  1185. positionSet(sortedByOverlap.shift(), { x: 0, y: 0 });
  1186. var relationsWithTwoSets = relations.filter(function (x) {
  1187. return x.sets.length === 2;
  1188. });
  1189. // Iterate and position the remaining sets.
  1190. sortedByOverlap.forEach(function (set) {
  1191. var circle = set.circle,
  1192. radius = circle.r,
  1193. overlapping = set.overlapping;
  1194. var bestPosition = positionedSets
  1195. .reduce(function (best,
  1196. positionedSet,
  1197. i) {
  1198. var positionedCircle = positionedSet.circle,
  1199. overlap = overlapping[positionedSet.sets[0]];
  1200. // Calculate the distance between the sets to get the correct
  1201. // overlap
  1202. var distance = getDistanceBetweenCirclesByOverlap(radius,
  1203. positionedCircle.r,
  1204. overlap);
  1205. // Create a list of possible coordinates calculated from
  1206. // distance.
  1207. var possibleCoordinates = [
  1208. { x: positionedCircle.x + distance,
  1209. y: positionedCircle.y },
  1210. { x: positionedCircle.x - distance,
  1211. y: positionedCircle.y },
  1212. { x: positionedCircle.x,
  1213. y: positionedCircle.y + distance },
  1214. { x: positionedCircle.x,
  1215. y: positionedCircle.y - distance }
  1216. ];
  1217. // If there are more circles overlapping, then add the
  1218. // intersection points as possible positions.
  1219. positionedSets.slice(i + 1).forEach(function (positionedSet2) {
  1220. var positionedCircle2 = positionedSet2.circle,
  1221. overlap2 = overlapping[positionedSet2.sets[0]],
  1222. distance2 = getDistanceBetweenCirclesByOverlap(radius,
  1223. positionedCircle2.r,
  1224. overlap2);
  1225. // Add intersections to list of coordinates.
  1226. possibleCoordinates = possibleCoordinates.concat(getCircleCircleIntersection({
  1227. x: positionedCircle.x,
  1228. y: positionedCircle.y,
  1229. r: distance
  1230. }, {
  1231. x: positionedCircle2.x,
  1232. y: positionedCircle2.y,
  1233. r: distance2
  1234. }));
  1235. });
  1236. // Iterate all suggested coordinates and find the best one.
  1237. possibleCoordinates.forEach(function (coordinates) {
  1238. circle.x = coordinates.x;
  1239. circle.y = coordinates.y;
  1240. // Calculate loss for the suggested coordinates.
  1241. var currentLoss = loss(mapOfIdToCircles,
  1242. relationsWithTwoSets);
  1243. // If the loss is better, then use these new coordinates.
  1244. if (currentLoss < best.loss) {
  1245. best.loss = currentLoss;
  1246. best.coordinates = coordinates;
  1247. }
  1248. });
  1249. // Return resulting coordinates.
  1250. return best;
  1251. }, {
  1252. loss: Number.MAX_VALUE,
  1253. coordinates: void 0
  1254. });
  1255. // Add the set to its final position.
  1256. positionSet(set, bestPosition.coordinates);
  1257. });
  1258. // Return the positions of each set.
  1259. return mapOfIdToCircles;
  1260. };
  1261. /**
  1262. * Calculates the positions, and the label values of all the sets in the venn
  1263. * diagram.
  1264. *
  1265. * @private
  1266. * @todo Add support for constrained MDS.
  1267. * @param {Array<Highchats.VennRelationObject>} relations
  1268. * List of the overlap between two or more sets, or the size of a single set.
  1269. * @return {Highcharts.Dictionary<*>}
  1270. * List of circles and their calculated positions.
  1271. */
  1272. function layout(relations) {
  1273. var mapOfIdToShape = {};
  1274. var mapOfIdToLabelValues = {};
  1275. // Calculate best initial positions by using greedy layout.
  1276. if (relations.length > 0) {
  1277. var mapOfIdToCircles_1 = layoutGreedyVenn(relations);
  1278. var setRelations_1 = relations.filter(isSet);
  1279. relations
  1280. .forEach(function (relation) {
  1281. var sets = relation.sets;
  1282. var id = sets.join();
  1283. // Get shape from map of circles, or calculate intersection.
  1284. var shape = isSet(relation) ?
  1285. mapOfIdToCircles_1[id] :
  1286. getAreaOfIntersectionBetweenCircles(sets.map(function (set) {
  1287. return mapOfIdToCircles_1[set];
  1288. }));
  1289. // Calculate label values if the set has a shape
  1290. if (shape) {
  1291. mapOfIdToShape[id] = shape;
  1292. mapOfIdToLabelValues[id] = getLabelValues(relation, setRelations_1);
  1293. }
  1294. });
  1295. }
  1296. return { mapOfIdToShape: mapOfIdToShape, mapOfIdToLabelValues: mapOfIdToLabelValues };
  1297. }
  1298. var isValidRelation = function (x) {
  1299. var map = {};
  1300. return (isObject(x) &&
  1301. (isNumber(x.value) && x.value > -1) &&
  1302. (isArray(x.sets) && x.sets.length > 0) &&
  1303. !x.sets.some(function (set) {
  1304. var invalid = false;
  1305. if (!map[set] && isString(set)) {
  1306. map[set] = true;
  1307. }
  1308. else {
  1309. invalid = true;
  1310. }
  1311. return invalid;
  1312. }));
  1313. };
  1314. var isValidSet = function (x) {
  1315. return (isValidRelation(x) && isSet(x) && x.value > 0);
  1316. };
  1317. /**
  1318. * Prepares the venn data so that it is usable for the layout function. Filter
  1319. * out sets, or intersections that includes sets, that are missing in the data
  1320. * or has (value < 1). Adds missing relations between sets in the data as
  1321. * value = 0.
  1322. * @private
  1323. * @param {Array<object>} data The raw input data.
  1324. * @return {Array<object>} Returns an array of valid venn data.
  1325. */
  1326. var processVennData = function processVennData(data) {
  1327. var d = isArray(data) ? data : [];
  1328. var validSets = d
  1329. .reduce(function (arr,
  1330. x) {
  1331. // Check if x is a valid set, and that it is not an duplicate.
  1332. if (isValidSet(x) && arr.indexOf(x.sets[0]) === -1) {
  1333. arr.push(x.sets[0]);
  1334. }
  1335. return arr;
  1336. }, [])
  1337. .sort();
  1338. var mapOfIdToRelation = d.reduce(function (mapOfIdToRelation,
  1339. relation) {
  1340. if (isValidRelation(relation) &&
  1341. !relation.sets.some(function (set) {
  1342. return validSets.indexOf(set) === -1;
  1343. })) {
  1344. mapOfIdToRelation[relation.sets.sort().join()] =
  1345. relation;
  1346. }
  1347. return mapOfIdToRelation;
  1348. }, {});
  1349. validSets.reduce(function (combinations, set, i, arr) {
  1350. var remaining = arr.slice(i + 1);
  1351. remaining.forEach(function (set2) {
  1352. combinations.push(set + ',' + set2);
  1353. });
  1354. return combinations;
  1355. }, []).forEach(function (combination) {
  1356. if (!mapOfIdToRelation[combination]) {
  1357. var obj = {
  1358. sets: combination.split(','),
  1359. value: 0
  1360. };
  1361. mapOfIdToRelation[combination] = obj;
  1362. }
  1363. });
  1364. // Transform map into array.
  1365. return objectValues(mapOfIdToRelation);
  1366. };
  1367. /**
  1368. * Calculates the proper scale to fit the cloud inside the plotting area.
  1369. * @private
  1370. * @todo add unit test
  1371. * @param {number} targetWidth
  1372. * Width of target area.
  1373. * @param {number} targetHeight
  1374. * Height of target area.
  1375. * @param {Highcharts.PolygonBoxObject} field
  1376. * The playing field.
  1377. * @return {Highcharts.Dictionary<number>}
  1378. * Returns the value to scale the playing field up to the size of the target
  1379. * area, and center of x and y.
  1380. */
  1381. var getScale = function getScale(targetWidth,
  1382. targetHeight,
  1383. field) {
  1384. var height = field.bottom - field.top, // top is smaller than bottom
  1385. width = field.right - field.left,
  1386. scaleX = width > 0 ? 1 / width * targetWidth : 1,
  1387. scaleY = height > 0 ? 1 / height * targetHeight : 1,
  1388. adjustX = (field.right + field.left) / 2,
  1389. adjustY = (field.top + field.bottom) / 2,
  1390. scale = Math.min(scaleX,
  1391. scaleY);
  1392. return {
  1393. scale: scale,
  1394. centerX: targetWidth / 2 - adjustX * scale,
  1395. centerY: targetHeight / 2 - adjustY * scale
  1396. };
  1397. };
  1398. /**
  1399. * If a circle is outside a give field, then the boundaries of the field is
  1400. * adjusted accordingly. Modifies the field object which is passed as the first
  1401. * parameter.
  1402. * @private
  1403. * @todo NOTE: Copied from wordcloud, can probably be unified.
  1404. * @param {Highcharts.PolygonBoxObject} field
  1405. * The bounding box of a playing field.
  1406. * @param {Highcharts.CircleObject} circle
  1407. * The bounding box for a placed point.
  1408. * @return {Highcharts.PolygonBoxObject}
  1409. * Returns a modified field object.
  1410. */
  1411. var updateFieldBoundaries = function updateFieldBoundaries(field,
  1412. circle) {
  1413. var left = circle.x - circle.r,
  1414. right = circle.x + circle.r,
  1415. bottom = circle.y + circle.r,
  1416. top = circle.y - circle.r;
  1417. // TODO improve type checking.
  1418. if (!isNumber(field.left) || field.left > left) {
  1419. field.left = left;
  1420. }
  1421. if (!isNumber(field.right) || field.right < right) {
  1422. field.right = right;
  1423. }
  1424. if (!isNumber(field.top) || field.top > top) {
  1425. field.top = top;
  1426. }
  1427. if (!isNumber(field.bottom) || field.bottom < bottom) {
  1428. field.bottom = bottom;
  1429. }
  1430. return field;
  1431. };
  1432. /**
  1433. * A Venn diagram displays all possible logical relations between a collection
  1434. * of different sets. The sets are represented by circles, and the relation
  1435. * between the sets are displayed by the overlap or lack of overlap between
  1436. * them. The venn diagram is a special case of Euler diagrams, which can also
  1437. * be displayed by this series type.
  1438. *
  1439. * @sample {highcharts} highcharts/demo/venn-diagram/
  1440. * Venn diagram
  1441. * @sample {highcharts} highcharts/demo/euler-diagram/
  1442. * Euler diagram
  1443. *
  1444. * @extends plotOptions.scatter
  1445. * @excluding connectEnds, connectNulls, cropThreshold, dragDrop,
  1446. * findNearestPointBy, getExtremesFromAll, jitter, label, linecap,
  1447. * lineWidth, linkedTo, marker, negativeColor, pointInterval,
  1448. * pointIntervalUnit, pointPlacement, pointStart, softThreshold,
  1449. * stacking, steps, threshold, xAxis, yAxis, zoneAxis, zones,
  1450. * dataSorting, boostThreshold, boostBlending
  1451. * @product highcharts
  1452. * @requires modules/venn
  1453. * @optionparent plotOptions.venn
  1454. */
  1455. var vennOptions = {
  1456. borderColor: '#cccccc',
  1457. borderDashStyle: 'solid',
  1458. borderWidth: 1,
  1459. brighten: 0,
  1460. clip: false,
  1461. colorByPoint: true,
  1462. dataLabels: {
  1463. enabled: true,
  1464. verticalAlign: 'middle',
  1465. formatter: function () {
  1466. return this.point.name;
  1467. }
  1468. },
  1469. /**
  1470. * @ignore-option
  1471. * @private
  1472. */
  1473. inactiveOtherPoints: true,
  1474. marker: false,
  1475. opacity: 0.75,
  1476. showInLegend: false,
  1477. states: {
  1478. /**
  1479. * @excluding halo
  1480. */
  1481. hover: {
  1482. opacity: 1,
  1483. borderColor: '#333333'
  1484. },
  1485. /**
  1486. * @excluding halo
  1487. */
  1488. select: {
  1489. color: '#cccccc',
  1490. borderColor: '#000000',
  1491. animation: false
  1492. },
  1493. inactive: {
  1494. opacity: 0.075
  1495. }
  1496. },
  1497. tooltip: {
  1498. pointFormat: '{point.name}: {point.value}'
  1499. }
  1500. };
  1501. var vennSeries = {
  1502. isCartesian: false,
  1503. axisTypes: [],
  1504. directTouch: true,
  1505. pointArrayMap: ['value'],
  1506. init: function () {
  1507. seriesTypes.scatter.prototype.init.apply(this,
  1508. arguments);
  1509. // Venn's opacity is a different option from other series
  1510. delete this.opacity;
  1511. },
  1512. translate: function () {
  1513. var chart = this.chart;
  1514. this.processedXData = this.xData;
  1515. this.generatePoints();
  1516. // Process the data before passing it into the layout function.
  1517. var relations = processVennData(this.options.data);
  1518. // Calculate the positions of each circle.
  1519. var _a = layout(relations),
  1520. mapOfIdToShape = _a.mapOfIdToShape,
  1521. mapOfIdToLabelValues = _a.mapOfIdToLabelValues;
  1522. // Calculate the scale, and center of the plot area.
  1523. var field = Object.keys(mapOfIdToShape)
  1524. .filter(function (key) {
  1525. var shape = mapOfIdToShape[key];
  1526. return shape && isNumber(shape.r);
  1527. })
  1528. .reduce(function (field, key) {
  1529. return updateFieldBoundaries(field, mapOfIdToShape[key]);
  1530. }, { top: 0, bottom: 0, left: 0, right: 0 }), scaling = getScale(chart.plotWidth, chart.plotHeight, field), scale = scaling.scale, centerX = scaling.centerX, centerY = scaling.centerY;
  1531. // Iterate all points and calculate and draw their graphics.
  1532. this.points.forEach(function (point) {
  1533. var sets = isArray(point.sets) ? point.sets : [],
  1534. id = sets.join(),
  1535. shape = mapOfIdToShape[id],
  1536. shapeArgs,
  1537. dataLabelValues = mapOfIdToLabelValues[id] || {},
  1538. dataLabelWidth = dataLabelValues.width,
  1539. dataLabelPosition = dataLabelValues.position,
  1540. dlOptions = point.options && point.options.dataLabels;
  1541. if (shape) {
  1542. if (shape.r) {
  1543. shapeArgs = {
  1544. x: centerX + shape.x * scale,
  1545. y: centerY + shape.y * scale,
  1546. r: shape.r * scale
  1547. };
  1548. }
  1549. else if (shape.d) {
  1550. var d = shape.d;
  1551. d.forEach(function (seg) {
  1552. if (seg[0] === 'M') {
  1553. seg[1] = centerX + seg[1] * scale;
  1554. seg[2] = centerY + seg[2] * scale;
  1555. }
  1556. else if (seg[0] === 'A') {
  1557. seg[1] = seg[1] * scale;
  1558. seg[2] = seg[2] * scale;
  1559. seg[6] = centerX + seg[6] * scale;
  1560. seg[7] = centerY + seg[7] * scale;
  1561. }
  1562. });
  1563. shapeArgs = { d: d };
  1564. }
  1565. // Scale the position for the data label.
  1566. if (dataLabelPosition) {
  1567. dataLabelPosition.x = centerX + dataLabelPosition.x * scale;
  1568. dataLabelPosition.y = centerY + dataLabelPosition.y * scale;
  1569. }
  1570. else {
  1571. dataLabelPosition = {};
  1572. }
  1573. if (isNumber(dataLabelWidth)) {
  1574. dataLabelWidth = Math.round(dataLabelWidth * scale);
  1575. }
  1576. }
  1577. point.shapeArgs = shapeArgs;
  1578. // Placement for the data labels
  1579. if (dataLabelPosition && shapeArgs) {
  1580. point.plotX = dataLabelPosition.x;
  1581. point.plotY = dataLabelPosition.y;
  1582. }
  1583. // Add width for the data label
  1584. if (dataLabelWidth && shapeArgs) {
  1585. point.dlOptions = merge(true, {
  1586. style: {
  1587. width: dataLabelWidth
  1588. }
  1589. }, isObject(dlOptions) && dlOptions);
  1590. }
  1591. // Set name for usage in tooltip and in data label.
  1592. point.name = point.options.name || sets.join('∩');
  1593. });
  1594. },
  1595. /* eslint-disable valid-jsdoc */
  1596. /**
  1597. * Draw the graphics for each point.
  1598. * @private
  1599. */
  1600. drawPoints: function () {
  1601. var series = this,
  1602. // Series properties
  1603. chart = series.chart,
  1604. group = series.group,
  1605. points = series.points || [],
  1606. // Chart properties
  1607. renderer = chart.renderer;
  1608. // Iterate all points and calculate and draw their graphics.
  1609. points.forEach(function (point) {
  1610. var attribs = {
  1611. zIndex: isArray(point.sets) ? point.sets.length : 0
  1612. },
  1613. shapeArgs = point.shapeArgs;
  1614. // Add point attribs
  1615. if (!chart.styledMode) {
  1616. extend(attribs, series.pointAttribs(point, point.state));
  1617. }
  1618. // Draw the point graphic.
  1619. point.draw({
  1620. isNew: !point.graphic,
  1621. animatableAttribs: shapeArgs,
  1622. attribs: attribs,
  1623. group: group,
  1624. renderer: renderer,
  1625. shapeType: shapeArgs && shapeArgs.d ? 'path' : 'circle'
  1626. });
  1627. });
  1628. },
  1629. /**
  1630. * Calculates the style attributes for a point. The attributes can vary
  1631. * depending on the state of the point.
  1632. * @private
  1633. * @param {Highcharts.Point} point
  1634. * The point which will get the resulting attributes.
  1635. * @param {string} [state]
  1636. * The state of the point.
  1637. * @return {Highcharts.SVGAttributes}
  1638. * Returns the calculated attributes.
  1639. */
  1640. pointAttribs: function (point, state) {
  1641. var series = this,
  1642. seriesOptions = series.options || {},
  1643. pointOptions = point && point.options || {},
  1644. stateOptions = (state && seriesOptions.states[state]) || {},
  1645. options = merge(seriesOptions, { color: point && point.color },
  1646. pointOptions,
  1647. stateOptions);
  1648. // Return resulting values for the attributes.
  1649. return {
  1650. 'fill': color(options.color)
  1651. .setOpacity(options.opacity)
  1652. .brighten(options.brightness)
  1653. .get(),
  1654. 'stroke': options.borderColor,
  1655. 'stroke-width': options.borderWidth,
  1656. 'dashstyle': options.borderDashStyle
  1657. };
  1658. },
  1659. /* eslint-enable valid-jsdoc */
  1660. animate: function (init) {
  1661. if (!init) {
  1662. var series = this,
  1663. animOptions = animObject(series.options.animation);
  1664. series.points.forEach(function (point) {
  1665. var args = point.shapeArgs;
  1666. if (point.graphic && args) {
  1667. var attr = {},
  1668. animate = {};
  1669. if (args.d) {
  1670. // If shape is a path, then animate opacity.
  1671. attr.opacity = 0.001;
  1672. }
  1673. else {
  1674. // If shape is a circle, then animate radius.
  1675. attr.r = 0;
  1676. animate.r = args.r;
  1677. }
  1678. point.graphic
  1679. .attr(attr)
  1680. .animate(animate, animOptions);
  1681. // If shape is path, then fade it in after the circles
  1682. // animation
  1683. if (args.d) {
  1684. setTimeout(function () {
  1685. if (point && point.graphic) {
  1686. point.graphic.animate({
  1687. opacity: 1
  1688. });
  1689. }
  1690. }, animOptions.duration);
  1691. }
  1692. }
  1693. }, series);
  1694. }
  1695. },
  1696. utils: {
  1697. addOverlapToSets: addOverlapToSets,
  1698. geometry: geometry,
  1699. geometryCircles: geometryCirclesModule,
  1700. getLabelWidth: getLabelWidth,
  1701. getMarginFromCircles: getMarginFromCircles,
  1702. getDistanceBetweenCirclesByOverlap: getDistanceBetweenCirclesByOverlap,
  1703. layoutGreedyVenn: layoutGreedyVenn,
  1704. loss: loss,
  1705. nelderMead: nelderMeadMixin,
  1706. processVennData: processVennData,
  1707. sortByTotalOverlap: sortByTotalOverlap
  1708. }
  1709. };
  1710. var vennPoint = {
  1711. draw: draw,
  1712. shouldDraw: function () {
  1713. var point = this;
  1714. // Only draw points with single sets.
  1715. return !!point.shapeArgs;
  1716. },
  1717. isValid: function () {
  1718. return isNumber(this.value);
  1719. }
  1720. };
  1721. /**
  1722. * A `venn` series. If the [type](#series.venn.type) option is
  1723. * not specified, it is inherited from [chart.type](#chart.type).
  1724. *
  1725. * @extends series,plotOptions.venn
  1726. * @excluding connectEnds, connectNulls, cropThreshold, dataParser, dataURL,
  1727. * findNearestPointBy, getExtremesFromAll, label, linecap, lineWidth,
  1728. * linkedTo, marker, negativeColor, pointInterval, pointIntervalUnit,
  1729. * pointPlacement, pointStart, softThreshold, stack, stacking, steps,
  1730. * threshold, xAxis, yAxis, zoneAxis, zones, dataSorting,
  1731. * boostThreshold, boostBlending
  1732. * @product highcharts
  1733. * @requires modules/venn
  1734. * @apioption series.venn
  1735. */
  1736. /**
  1737. * @type {Array<*>}
  1738. * @extends series.scatter.data
  1739. * @excluding marker, x, y
  1740. * @product highcharts
  1741. * @apioption series.venn.data
  1742. */
  1743. /**
  1744. * The name of the point. Used in data labels and tooltip. If name is not
  1745. * defined then it will default to the joined values in
  1746. * [sets](#series.venn.sets).
  1747. *
  1748. * @sample {highcharts} highcharts/demo/venn-diagram/
  1749. * Venn diagram
  1750. * @sample {highcharts} highcharts/demo/euler-diagram/
  1751. * Euler diagram
  1752. *
  1753. * @type {number}
  1754. * @since 7.0.0
  1755. * @product highcharts
  1756. * @apioption series.venn.data.name
  1757. */
  1758. /**
  1759. * The value of the point, resulting in a relative area of the circle, or area
  1760. * of overlap between two sets in the venn or euler diagram.
  1761. *
  1762. * @sample {highcharts} highcharts/demo/venn-diagram/
  1763. * Venn diagram
  1764. * @sample {highcharts} highcharts/demo/euler-diagram/
  1765. * Euler diagram
  1766. *
  1767. * @type {number}
  1768. * @since 7.0.0
  1769. * @product highcharts
  1770. * @apioption series.venn.data.value
  1771. */
  1772. /**
  1773. * The set or sets the options will be applied to. If a single entry is defined,
  1774. * then it will create a new set. If more than one entry is defined, then it
  1775. * will define the overlap between the sets in the array.
  1776. *
  1777. * @sample {highcharts} highcharts/demo/venn-diagram/
  1778. * Venn diagram
  1779. * @sample {highcharts} highcharts/demo/euler-diagram/
  1780. * Euler diagram
  1781. *
  1782. * @type {Array<string>}
  1783. * @since 7.0.0
  1784. * @product highcharts
  1785. * @apioption series.venn.data.sets
  1786. */
  1787. /**
  1788. * @excluding halo
  1789. * @apioption series.venn.states.hover
  1790. */
  1791. /**
  1792. * @excluding halo
  1793. * @apioption series.venn.states.select
  1794. */
  1795. /**
  1796. * @private
  1797. * @class
  1798. * @name Highcharts.seriesTypes.venn
  1799. *
  1800. * @augments Highcharts.Series
  1801. */
  1802. seriesType('venn', 'scatter', vennOptions, vennSeries, vennPoint);
  1803. /* eslint-disable no-invalid-this */
  1804. // Modify final series options.
  1805. addEvent(seriesTypes.venn, 'afterSetOptions', function (e) {
  1806. var options = e.options,
  1807. states = options.states;
  1808. if (this.is('venn')) {
  1809. // Explicitly disable all halo options.
  1810. Object.keys(states).forEach(function (state) {
  1811. states[state].halo = false;
  1812. });
  1813. }
  1814. });
  1815. });
  1816. _registerModule(_modules, 'masters/modules/venn.src.js', [], function () {
  1817. });
  1818. }));