| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191 | 
							- /* *
 
-  * Experimental Highcharts module which enables visualization of a Venn Diagram.
 
-  *
 
-  * (c) 2016-2019 Highsoft AS
 
-  *
 
-  * Authors: Jon Arild Nygard
 
-  *
 
-  * Layout algorithm by Ben Frederickson:
 
-  * https://www.benfrederickson.com/better-venn-diagrams/
 
-  *
 
-  * License: www.highcharts.com/license
 
-  */
 
- 'use strict';
 
- import draw from '../mixins/draw-point.js';
 
- import geometry from '../mixins/geometry.js';
 
- import geometryCircles from '../mixins/geometry-circles.js';
 
- import H from '../parts/Globals.js';
 
- import '../parts/Series.js';
 
- var color = H.Color,
 
-     extend = H.extend,
 
-     getAreaOfIntersectionBetweenCircles =
 
-         geometryCircles.getAreaOfIntersectionBetweenCircles,
 
-     getCircleCircleIntersection = geometryCircles.getCircleCircleIntersection,
 
-     getCenterOfPoints = geometry.getCenterOfPoints,
 
-     getDistanceBetweenPoints = geometry.getDistanceBetweenPoints,
 
-     getOverlapBetweenCirclesByDistance =
 
-         geometryCircles.getOverlapBetweenCircles,
 
-     isArray = H.isArray,
 
-     isNumber = H.isNumber,
 
-     isObject = H.isObject,
 
-     isPointInsideAllCircles = geometryCircles.isPointInsideAllCircles,
 
-     isPointOutsideAllCircles = geometryCircles.isPointOutsideAllCircles,
 
-     isString = H.isString,
 
-     merge = H.merge,
 
-     seriesType = H.seriesType;
 
- var objectValues = function objectValues(obj) {
 
-     return Object.keys(obj).map(function (x) {
 
-         return obj[x];
 
-     });
 
- };
 
- /**
 
-  * Calculates the area of overlap between a list of circles.
 
-  * @private
 
-  * @todo add support for calculating overlap between more than 2 circles.
 
-  * @param {Array<object>} circles List of circles with their given positions.
 
-  * @return {number} Returns the area of overlap between all the circles.
 
-  */
 
- var getOverlapBetweenCircles = function getOverlapBetweenCircles(circles) {
 
-     var overlap = 0;
 
-     // When there is only two circles we can find the overlap by using their
 
-     // radiuses and the distance between them.
 
-     if (circles.length === 2) {
 
-         var circle1 = circles[0];
 
-         var circle2 = circles[1];
 
-         overlap = getOverlapBetweenCirclesByDistance(
 
-             circle1.r,
 
-             circle2.r,
 
-             getDistanceBetweenPoints(circle1, circle2)
 
-         );
 
-     }
 
-     return overlap;
 
- };
 
- /**
 
-  * Calculates the difference between the desired overlap and the actual overlap
 
-  * between two circles.
 
-  * @private
 
-  * @param {object} mapOfIdToCircle Map from id to circle.
 
-  * @param {Array<object>} relations List of relations to calculate the loss of.
 
-  * @return {number} Returns the loss between positions of the circles for the
 
-  * given relations.
 
-  */
 
- var loss = function loss(mapOfIdToCircle, relations) {
 
-     var precision = 10e10;
 
-     // Iterate all the relations and calculate their individual loss.
 
-     return relations.reduce(function (totalLoss, relation) {
 
-         var loss = 0;
 
-         if (relation.sets.length > 1) {
 
-             var wantedOverlap = relation.value;
 
-             // Calculate the actual overlap between the sets.
 
-             var actualOverlap = getOverlapBetweenCircles(
 
-                 // Get the circles for the given sets.
 
-                 relation.sets.map(function (set) {
 
-                     return mapOfIdToCircle[set];
 
-                 })
 
-             );
 
-             var diff = wantedOverlap - actualOverlap;
 
-             loss = Math.round((diff * diff) * precision) / precision;
 
-         }
 
-         // Add calculated loss to the sum.
 
-         return totalLoss + loss;
 
-     }, 0);
 
- };
 
- /**
 
-  * Finds the root of a given function. The root is the input value needed for
 
-  * a function to return 0.
 
-  *
 
-  * See https://en.wikipedia.org/wiki/Bisection_method#Algorithm
 
-  *
 
-  * TODO: Add unit tests.
 
-  *
 
-  * @param {function} f The function to find the root of.
 
-  * @param {number} a The lowest number in the search range.
 
-  * @param {number} b The highest number in the search range.
 
-  * @param {number} [tolerance=1e-10] The allowed difference between the returned
 
-  * value and root.
 
-  * @param {number} [maxIterations=100] The maximum iterations allowed.
 
-  */
 
- var bisect = function bisect(f, a, b, tolerance, maxIterations) {
 
-     var fA = f(a),
 
-         fB = f(b),
 
-         nMax = maxIterations || 100,
 
-         tol = tolerance || 1e-10,
 
-         delta = b - a,
 
-         n = 1,
 
-         x, fX;
 
-     if (a >= b) {
 
-         throw new Error('a must be smaller than b.');
 
-     } else if (fA * fB > 0) {
 
-         throw new Error('f(a) and f(b) must have opposite signs.');
 
-     }
 
-     if (fA === 0) {
 
-         x = a;
 
-     } else if (fB === 0) {
 
-         x = b;
 
-     } else {
 
-         while (n++ <= nMax && fX !== 0 && delta > tol) {
 
-             delta = (b - a) / 2;
 
-             x = a + delta;
 
-             fX = f(x);
 
-             // Update low and high for next search interval.
 
-             if (fA * fX > 0) {
 
-                 a = x;
 
-             } else {
 
-                 b = x;
 
-             }
 
-         }
 
-     }
 
-     return x;
 
- };
 
- /**
 
-  * Uses the bisection method to make a best guess of the ideal distance between
 
-  * two circles too get the desired overlap.
 
-  * Currently there is no known formula to calculate the distance from the area
 
-  * of overlap, which makes the bisection method preferred.
 
-  * @private
 
-  * @param {number} r1 Radius of the first circle.
 
-  * @param {number} r2 Radiues of the second circle.
 
-  * @param {number} overlap The wanted overlap between the two circles.
 
-  * @return {number} Returns the distance needed to get the wanted overlap
 
-  * between the two circles.
 
-  */
 
- var getDistanceBetweenCirclesByOverlap =
 
- function getDistanceBetweenCirclesByOverlap(r1, r2, overlap) {
 
-     var maxDistance = r1 + r2,
 
-         distance = maxDistance;
 
-     if (overlap > 0) {
 
-         distance = bisect(function (x) {
 
-             var actualOverlap = getOverlapBetweenCirclesByDistance(r1, r2, x);
 
-             // Return the differance between wanted and actual overlap.
 
-             return overlap - actualOverlap;
 
-         }, 0, maxDistance);
 
-     }
 
-     return distance;
 
- };
 
- var isSet = function (x) {
 
-     return isArray(x.sets) && x.sets.length === 1;
 
- };
 
- /**
 
-  * Finds an optimal position for a given point.
 
-  * @private
 
-  * @todo add unit tests.
 
-  * @todo add constraints to optimize the algorithm.
 
-  * @param {Function} fn The function to test a point.
 
-  * @param {Array<*>} initial The initial point to optimize.
 
-  * @return {Array<*>} Returns the opimized position of a point.
 
-  */
 
- var nelderMead = function nelderMead(fn, initial) {
 
-     var maxIterations = 100,
 
-         sortByFx = function (a, b) {
 
-             return a.fx - b.fx;
 
-         },
 
-         pRef = 1, // Reflection parameter
 
-         pExp = 2, // Expansion parameter
 
-         pCon = -0.5, // Contraction parameter
 
-         pOCon = pCon * pRef, // Outwards contraction parameter
 
-         pShrink = 0.5; // Shrink parameter
 
-     var weightedSum = function weightedSum(weight1, v1, weight2, v2) {
 
-         return v1.map(function (x, i) {
 
-             return weight1 * x + weight2 * v2[i];
 
-         });
 
-     };
 
-     var getSimplex = function getSimplex(initial) {
 
-         var n = initial.length,
 
-             simplex = new Array(n + 1);
 
-         // Initial point to the simplex.
 
-         simplex[0] = initial;
 
-         simplex[0].fx = fn(initial);
 
-         // Create a set of extra points based on the initial.
 
-         for (var i = 0; i < n; ++i) {
 
-             var point = initial.slice();
 
-             point[i] = point[i] ? point[i] * 1.05 : 0.001;
 
-             point.fx = fn(point);
 
-             simplex[i + 1] = point;
 
-         }
 
-         return simplex;
 
-     };
 
-     var updateSimplex = function (simplex, point) {
 
-         point.fx = fn(point);
 
-         simplex[simplex.length - 1] = point;
 
-         return simplex;
 
-     };
 
-     var shrinkSimplex = function (simplex) {
 
-         var best = simplex[0];
 
-         return simplex.map(function (point) {
 
-             var p = weightedSum(1 - pShrink, best, pShrink, point);
 
-             p.fx = fn(p);
 
-             return p;
 
-         });
 
-     };
 
-     var getCentroid = function (simplex) {
 
-         var arr = simplex.slice(0, -1),
 
-             length = arr.length,
 
-             result = [],
 
-             sum = function (data, point) {
 
-                 data.sum += point[data.i];
 
-                 return data;
 
-             };
 
-         for (var i = 0; i < length; i++) {
 
-             result[i] = simplex.reduce(sum, { sum: 0, i: i }).sum / length;
 
-         }
 
-         return result;
 
-     };
 
-     var getPoint = function (centroid, worst, a, b) {
 
-         var point = weightedSum(a, centroid, b, worst);
 
-         point.fx = fn(point);
 
-         return point;
 
-     };
 
-     // Create a simplex
 
-     var simplex = getSimplex(initial);
 
-     // Iterate from 0 to max iterations
 
-     for (var i = 0; i < maxIterations; i++) {
 
-         // Sort the simplex
 
-         simplex.sort(sortByFx);
 
-         // Create a centroid from the simplex
 
-         var worst = simplex[simplex.length - 1];
 
-         var centroid = getCentroid(simplex);
 
-         // Calculate the reflected point.
 
-         var reflected = getPoint(centroid, worst, 1 + pRef, -pRef);
 
-         if (reflected.fx < simplex[0].fx) {
 
-             // If reflected point is the best, then possibly expand.
 
-             var expanded = getPoint(centroid, worst, 1 + pExp, -pExp);
 
-             simplex = updateSimplex(
 
-                 simplex,
 
-                 (expanded.fx < reflected.fx) ? expanded : reflected
 
-             );
 
-         } else if (reflected.fx >= simplex[simplex.length - 2].fx) {
 
-             // If the reflected point is worse than the second worse, then
 
-             // contract.
 
-             var contracted;
 
-             if (reflected.fx > worst.fx) {
 
-                 // If the reflected is worse than the worst point, do a
 
-                 // contraction
 
-                 contracted = getPoint(centroid, worst, 1 + pCon, -pCon);
 
-                 if (contracted.fx < worst.fx) {
 
-                     simplex = updateSimplex(simplex, contracted);
 
-                 } else {
 
-                     simplex = shrinkSimplex(simplex);
 
-                 }
 
-             } else {
 
-                 // Otherwise do an outwards contraction
 
-                 contracted = getPoint(centroid, worst, 1 - pOCon, pOCon);
 
-                 if (contracted.fx < reflected.fx) {
 
-                     simplex = updateSimplex(simplex, contracted);
 
-                 } else {
 
-                     simplex = shrinkSimplex(simplex);
 
-                 }
 
-             }
 
-         } else {
 
-             simplex = updateSimplex(simplex, reflected);
 
-         }
 
-     }
 
-     return simplex[0];
 
- };
 
- /**
 
-  * Calculates a margin for a point based on the iternal and external circles.
 
-  * The margin describes if the point is well placed within the internal circles,
 
-  * and away from the external
 
-  * @private
 
-  * @todo add unit tests.
 
-  * @param {object} point The point to evaluate.
 
-  * @param {Array<object>} internal The internal circles.
 
-  * @param {Array<object>} external The external circles.
 
-  * @return {number} Returns the margin.
 
-  */
 
- var getMarginFromCircles =
 
- function getMarginFromCircles(point, internal, external) {
 
-     var margin = internal.reduce(function (margin, circle) {
 
-         var m = circle.r - getDistanceBetweenPoints(point, circle);
 
-         return (m <= margin) ? m : margin;
 
-     }, Number.MAX_VALUE);
 
-     margin = external.reduce(function (margin, circle) {
 
-         var m = getDistanceBetweenPoints(point, circle) - circle.r;
 
-         return (m <= margin) ? m : margin;
 
-     }, margin);
 
-     return margin;
 
- };
 
- /**
 
-  * Finds the optimal label position by looking for a position that has a low
 
-  * distance from the internal circles, and as large possible distane to the
 
-  * external circles.
 
-  * @private
 
-  * @todo Optimize the intial position.
 
-  * @todo Add unit tests.
 
-  * @param {Array<object>} internal Internal circles.
 
-  * @param {Array<object>} external External circles.
 
-  * @return {object} Returns the found position.
 
-  */
 
- var getLabelPosition = function getLabelPosition(internal, external) {
 
-     // Get the best label position within the internal circles.
 
-     var best = internal.reduce(function (best, circle) {
 
-         var d = circle.r / 2;
 
-         // Give a set of points with the circle to evaluate as the best label
 
-         // position.
 
-         return [
 
-             { x: circle.x, y: circle.y },
 
-             { x: circle.x + d, y: circle.y },
 
-             { x: circle.x - d, y: circle.y },
 
-             { x: circle.x, y: circle.y + d },
 
-             { x: circle.x, y: circle.y - d }
 
-         ]
 
-         // Iterate the given points and return the one with the largest margin.
 
-             .reduce(function (best, point) {
 
-                 var margin = getMarginFromCircles(point, internal, external);
 
-                 // If the margin better than the current best, then update best.
 
-                 if (best.margin < margin) {
 
-                     best.point = point;
 
-                     best.margin = margin;
 
-                 }
 
-                 return best;
 
-             }, best);
 
-     }, {
 
-         point: undefined,
 
-         margin: -Number.MAX_VALUE
 
-     }).point;
 
-     // Use nelder mead to optimize the initial label position.
 
-     var optimal = nelderMead(
 
-         function (p) {
 
-             return -(
 
-                 getMarginFromCircles({ x: p[0], y: p[1] }, internal, external)
 
-             );
 
-         },
 
-         [best.x, best.y]
 
-     );
 
-     // Update best to be the point which was found to have the best margin.
 
-     best = {
 
-         x: optimal[0],
 
-         y: optimal[1]
 
-     };
 
-     if (!(
 
-         isPointInsideAllCircles(best, internal) &&
 
-         isPointOutsideAllCircles(best, external)
 
-     )) {
 
-         // If point was either outside one of the internal, or inside one of the
 
-         // external, then it was invalid and should use a fallback.
 
-         best = getCenterOfPoints(internal);
 
-     }
 
-     // Return the best point.
 
-     return best;
 
- };
 
- /**
 
-  * Calulates data label positions for a list of relations.
 
-  * @private
 
-  * @todo add unit tests
 
-  * @todo NOTE: may be better suited as a part of the layout function.
 
-  * @param {Array<object>} relations The list of relations.
 
-  * @return {object} Returns a map from id to the data label position.
 
-  */
 
- var getLabelPositions = function getLabelPositions(relations) {
 
-     var singleSets = relations.filter(isSet);
 
-     return relations.reduce(function (map, relation) {
 
-         if (relation.value) {
 
-             var sets = relation.sets,
 
-                 id = sets.join(),
 
-                 // Create a list of internal and external circles.
 
-                 data = singleSets.reduce(function (data, set) {
 
-                     // If the set exists in this relation, then it is internal,
 
-                     // otherwise it will be external.
 
-                     var isInternal = sets.indexOf(set.sets[0]) > -1,
 
-                         property = isInternal ? 'internal' : 'external';
 
-                     // Add the circle to the list.
 
-                     data[property].push(set.circle);
 
-                     return data;
 
-                 }, {
 
-                     internal: [],
 
-                     external: []
 
-                 });
 
-             // Calulate the label position.
 
-             map[id] = getLabelPosition(
 
-                 data.internal,
 
-                 data.external
 
-             );
 
-         }
 
-         return map;
 
-     }, {});
 
- };
 
- /**
 
-  * Takes an array of relations and adds the properties `totalOverlap` and
 
-  * `overlapping` to each set. The property `totalOverlap` is the sum of value
 
-  * for each relation where this set is included. The property `overlapping` is
 
-  * a map of how much this set is overlapping another set.
 
-  * NOTE: This algorithm ignores relations consisting of more than 2 sets.
 
-  * @private
 
-  * @param {Array<object>} relations The list of relations that should be sorted.
 
-  * @return {Array<object>} Returns the modified input relations with added
 
-  * properties `totalOverlap` and `overlapping`.
 
-  */
 
- var addOverlapToSets = function addOverlapToSets(relations) {
 
-     // Calculate the amount of overlap per set.
 
-     var mapOfIdToProps = relations
 
-         // Filter out relations consisting of 2 sets.
 
-         .filter(function (relation) {
 
-             return relation.sets.length === 2;
 
-         })
 
-         // Sum up the amount of overlap for each set.
 
-         .reduce(function (map, relation) {
 
-             var sets = relation.sets;
 
-             sets.forEach(function (set, i, arr) {
 
-                 if (!isObject(map[set])) {
 
-                     map[set] = {
 
-                         overlapping: {},
 
-                         totalOverlap: 0
 
-                     };
 
-                 }
 
-                 map[set].totalOverlap += relation.value;
 
-                 map[set].overlapping[arr[1 - i]] = relation.value;
 
-             });
 
-             return map;
 
-         }, {});
 
-     relations
 
-         // Filter out single sets
 
-         .filter(isSet)
 
-         // Extend the set with the calculated properties.
 
-         .forEach(function (set) {
 
-             var properties = mapOfIdToProps[set.sets[0]];
 
-             extend(set, properties);
 
-         });
 
-     // Returns the modified relations.
 
-     return relations;
 
- };
 
- /**
 
-  * Takes two sets and finds the one with the largest total overlap.
 
-  * @private
 
-  * @param {object} a The first set to compare.
 
-  * @param {object} b The second set to compare.
 
-  * @return {number} Returns 0 if a and b are equal, <0 if a is greater, >0 if b
 
-  * is greater.
 
-  */
 
- var sortByTotalOverlap = function sortByTotalOverlap(a, b) {
 
-     return b.totalOverlap - a.totalOverlap;
 
- };
 
- /**
 
-  * Uses a greedy approach to position all the sets. Works well with a small
 
-  * number of sets, and are in these cases a good choice aesthetically.
 
-  * @private
 
-  * @param {Array<object>} relations List of the overlap between two or more
 
-  * sets, or the size of a single set.
 
-  * @return {Array<object>} List of circles and their calculated positions.
 
-  */
 
- var layoutGreedyVenn = function layoutGreedyVenn(relations) {
 
-     var positionedSets = [],
 
-         mapOfIdToCircles = {};
 
-     // Define a circle for each set.
 
-     relations
 
-         .filter(function (relation) {
 
-             return relation.sets.length === 1;
 
-         }).forEach(function (relation) {
 
-             mapOfIdToCircles[relation.sets[0]] = relation.circle = {
 
-                 x: Number.MAX_VALUE,
 
-                 y: Number.MAX_VALUE,
 
-                 r: Math.sqrt(relation.value / Math.PI)
 
-             };
 
-         });
 
-     /**
 
-      * Takes a set and updates the position, and add the set to the list of
 
-      * positioned sets.
 
-      * @private
 
-      * @param {object} set The set to add to its final position.
 
-      * @param {object} coordinates The coordinates to position the set at.
 
-      */
 
-     var positionSet = function positionSet(set, coordinates) {
 
-         var circle = set.circle;
 
-         circle.x = coordinates.x;
 
-         circle.y = coordinates.y;
 
-         positionedSets.push(set);
 
-     };
 
-     // Find overlap between sets. Ignore relations with more then 2 sets.
 
-     addOverlapToSets(relations);
 
-     // Sort sets by the sum of their size from large to small.
 
-     var sortedByOverlap = relations
 
-         .filter(isSet)
 
-         .sort(sortByTotalOverlap);
 
-     // Position the most overlapped set at 0,0.
 
-     positionSet(sortedByOverlap.pop(), { x: 0, y: 0 });
 
-     var relationsWithTwoSets = relations.filter(function (x) {
 
-         return x.sets.length === 2;
 
-     });
 
-     // Iterate and position the remaining sets.
 
-     sortedByOverlap.forEach(function (set) {
 
-         var circle = set.circle,
 
-             radius = circle.r,
 
-             overlapping = set.overlapping;
 
-         var bestPosition = positionedSets
 
-             .reduce(function (best, positionedSet, i) {
 
-                 var positionedCircle = positionedSet.circle,
 
-                     overlap = overlapping[positionedSet.sets[0]];
 
-                 // Calculate the distance between the sets to get the correct
 
-                 // overlap
 
-                 var distance = getDistanceBetweenCirclesByOverlap(
 
-                     radius,
 
-                     positionedCircle.r,
 
-                     overlap
 
-                 );
 
-                 // Create a list of possible coordinates calculated from
 
-                 // distance.
 
-                 var possibleCoordinates = [
 
-                     { x: positionedCircle.x + distance, y: positionedCircle.y },
 
-                     { x: positionedCircle.x - distance, y: positionedCircle.y },
 
-                     { x: positionedCircle.x, y: positionedCircle.y + distance },
 
-                     { x: positionedCircle.x, y: positionedCircle.y - distance }
 
-                 ];
 
-                 // If there are more circles overlapping, then add the
 
-                 // intersection points as possible positions.
 
-                 positionedSets.slice(i + 1).forEach(function (positionedSet2) {
 
-                     var positionedCircle2 = positionedSet2.circle,
 
-                         overlap2 = overlapping[positionedSet2.sets[0]],
 
-                         distance2 = getDistanceBetweenCirclesByOverlap(
 
-                             radius,
 
-                             positionedCircle2.r,
 
-                             overlap2
 
-                         );
 
-                     // Add intersections to list of coordinates.
 
-                     possibleCoordinates = possibleCoordinates.concat(
 
-                         getCircleCircleIntersection({
 
-                             x: positionedCircle.x,
 
-                             y: positionedCircle.y,
 
-                             r: distance2
 
-                         }, {
 
-                             x: positionedCircle2.x,
 
-                             y: positionedCircle2.y,
 
-                             r: distance2
 
-                         })
 
-                     );
 
-                 });
 
-                 // Iterate all suggested coordinates and find the best one.
 
-                 possibleCoordinates.forEach(function (coordinates) {
 
-                     circle.x = coordinates.x;
 
-                     circle.y = coordinates.y;
 
-                     // Calculate loss for the suggested coordinates.
 
-                     var currentLoss = loss(
 
-                         mapOfIdToCircles, relationsWithTwoSets
 
-                     );
 
-                     // If the loss is better, then use these new coordinates.
 
-                     if (currentLoss < best.loss) {
 
-                         best.loss = currentLoss;
 
-                         best.coordinates = coordinates;
 
-                     }
 
-                 });
 
-                 // Return resulting coordinates.
 
-                 return best;
 
-             }, {
 
-                 loss: Number.MAX_VALUE,
 
-                 coordinates: undefined
 
-             });
 
-         // Add the set to its final position.
 
-         positionSet(set, bestPosition.coordinates);
 
-     });
 
-     // Return the positions of each set.
 
-     return mapOfIdToCircles;
 
- };
 
- /**
 
-  * Calculates the positions of all the sets in the venn diagram.
 
-  * @private
 
-  * @todo Add support for constrained MDS.
 
-  * @param {Array<object>} relations List of the overlap between two or more sets, or the
 
-  * size of a single set.
 
-  * @return {Arrat<object>} List of circles and their calculated positions.
 
-  */
 
- var layout = function (relations) {
 
-     var mapOfIdToShape = {};
 
-     // Calculate best initial positions by using greedy layout.
 
-     if (relations.length > 0) {
 
-         mapOfIdToShape = layoutGreedyVenn(relations);
 
-         relations
 
-             .filter(function (x) {
 
-                 return !isSet(x);
 
-             })
 
-             .forEach(function (relation) {
 
-                 var sets = relation.sets,
 
-                     id = sets.join(),
 
-                     circles = sets.map(function (set) {
 
-                         return mapOfIdToShape[set];
 
-                     });
 
-                 // Add intersection shape to map
 
-                 mapOfIdToShape[id] =
 
-                     getAreaOfIntersectionBetweenCircles(circles);
 
-             });
 
-     }
 
-     return mapOfIdToShape;
 
- };
 
- var isValidRelation = function (x) {
 
-     var map = {};
 
-     return (
 
-         isObject(x) &&
 
-         (isNumber(x.value) && x.value > -1) &&
 
-         (isArray(x.sets) && x.sets.length > 0) &&
 
-         !x.sets.some(function (set) {
 
-             var invalid = false;
 
-             if (!map[set] && isString(set)) {
 
-                 map[set] = true;
 
-             } else {
 
-                 invalid = true;
 
-             }
 
-             return invalid;
 
-         })
 
-     );
 
- };
 
- var isValidSet = function (x) {
 
-     return (isValidRelation(x) && isSet(x) && x.value > 0);
 
- };
 
- /**
 
-  * Prepares the venn data so that it is usable for the layout function. Filter
 
-  * out sets, or intersections that includes sets, that are missing in the data
 
-  * or has (value < 1). Adds missing relations between sets in the data as
 
-  * value = 0.
 
-  * @private
 
-  * @param {Array<object>} data The raw input data.
 
-  * @return {Array<object>} Returns an array of valid venn data.
 
-  */
 
- var processVennData = function processVennData(data) {
 
-     var d = isArray(data) ? data : [];
 
-     var validSets = d
 
-         .reduce(function (arr, x) {
 
-             // Check if x is a valid set, and that it is not an duplicate.
 
-             if (isValidSet(x) && arr.indexOf(x.sets[0]) === -1) {
 
-                 arr.push(x.sets[0]);
 
-             }
 
-             return arr;
 
-         }, [])
 
-         .sort();
 
-     var mapOfIdToRelation = d.reduce(function (mapOfIdToRelation, relation) {
 
-         if (isValidRelation(relation) && !relation.sets.some(function (set) {
 
-             return validSets.indexOf(set) === -1;
 
-         })) {
 
-             mapOfIdToRelation[relation.sets.sort().join()] = relation;
 
-         }
 
-         return mapOfIdToRelation;
 
-     }, {});
 
-     validSets.reduce(function (combinations, set, i, arr) {
 
-         var remaining = arr.slice(i + 1);
 
-         remaining.forEach(function (set2) {
 
-             combinations.push(set + ',' + set2);
 
-         });
 
-         return combinations;
 
-     }, []).forEach(function (combination) {
 
-         if (!mapOfIdToRelation[combination]) {
 
-             var obj = {
 
-                 sets: combination.split(','),
 
-                 value: 0
 
-             };
 
-             mapOfIdToRelation[combination] = obj;
 
-         }
 
-     });
 
-     // Transform map into array.
 
-     return objectValues(mapOfIdToRelation);
 
- };
 
- /**
 
-  * Calculates the proper scale to fit the cloud inside the plotting area.
 
-  * @private
 
-  * @todo add unit test
 
-  * @param {number} targetWidth  Width of target area.
 
-  * @param {number} targetHeight Height of target area.
 
-  * @param {object} field The playing field.
 
-  * @param {Highcharts.Series} series Series object.
 
-  * @return {object} Returns the value to scale the playing field up to the size
 
-  * of the target area, and center of x and y.
 
-  */
 
- var getScale = function getScale(targetWidth, targetHeight, field) {
 
-     var height = field.bottom - field.top, // top is smaller than bottom
 
-         width = field.right - field.left,
 
-         scaleX = width > 0 ? 1 / width * targetWidth : 1,
 
-         scaleY = height > 0 ? 1 / height * targetHeight : 1,
 
-         adjustX = (field.right + field.left) / 2,
 
-         adjustY = (field.top + field.bottom) / 2,
 
-         scale = Math.min(scaleX, scaleY);
 
-     return {
 
-         scale: scale,
 
-         centerX: targetWidth / 2 - adjustX * scale,
 
-         centerY: targetHeight / 2 - adjustY * scale
 
-     };
 
- };
 
- /**
 
-  * If a circle is outside a give field, then the boundaries of the field is
 
-  * adjusted accordingly. Modifies the field object which is passed as the first
 
-  * parameter.
 
-  * @private
 
-  * @todo NOTE: Copied from wordcloud, can probably be unified.
 
-  * @param {object} field The bounding box of a playing field.
 
-  * @param {object} placement The bounding box for a placed point.
 
-  * @return {object} Returns a modified field object.
 
-  */
 
- var updateFieldBoundaries = function updateFieldBoundaries(field, circle) {
 
-     var left = circle.x - circle.r,
 
-         right = circle.x + circle.r,
 
-         bottom = circle.y + circle.r,
 
-         top = circle.y - circle.r;
 
-     // TODO improve type checking.
 
-     if (!isNumber(field.left) || field.left > left) {
 
-         field.left = left;
 
-     }
 
-     if (!isNumber(field.right) || field.right < right) {
 
-         field.right = right;
 
-     }
 
-     if (!isNumber(field.top) || field.top > top) {
 
-         field.top = top;
 
-     }
 
-     if (!isNumber(field.bottom) || field.bottom < bottom) {
 
-         field.bottom = bottom;
 
-     }
 
-     return field;
 
- };
 
- /**
 
-  * A Venn diagram displays all possible logical relations between a collection
 
-  * of different sets. The sets are represented by circles, and the relation
 
-  * between the sets are displayed by the overlap or lack of overlap between
 
-  * them. The venn diagram is a special case of Euler diagrams, which can also
 
-  * be displayed by this series type.
 
-  *
 
-  * @sample {highcharts} highcharts/demo/venn-diagram/
 
-  *         Venn diagram
 
-  * @sample {highcharts} highcharts/demo/euler-diagram/
 
-  *         Euler diagram
 
-  *
 
-  * @extends      plotOptions.scatter
 
-  * @excluding    connectEnds, connectNulls, cropThreshold, findNearestPointBy,
 
-  *               getExtremesFromAll, jitter, label, linecap, lineWidth,
 
-  *               linkedTo, marker, negativeColor, pointInterval,
 
-  *               pointIntervalUnit, pointPlacement, pointStart, softThreshold,
 
-  *               stacking, steps, threshold, xAxis, yAxis, zoneAxis, zones
 
-  * @product      highcharts
 
-  * @optionparent plotOptions.venn
 
-  */
 
- var vennOptions = {
 
-     borderColor: '#cccccc',
 
-     borderDashStyle: 'solid',
 
-     borderWidth: 1,
 
-     brighten: 0,
 
-     clip: false,
 
-     colorByPoint: true,
 
-     dataLabels: {
 
-         enabled: true,
 
-         formatter: function () {
 
-             return this.point.name;
 
-         }
 
-     },
 
-     marker: false,
 
-     opacity: 0.75,
 
-     showInLegend: false,
 
-     states: {
 
-         hover: {
 
-             opacity: 1,
 
-             halo: false,
 
-             borderColor: '#333333'
 
-         },
 
-         select: {
 
-             color: '#cccccc',
 
-             borderColor: '#000000',
 
-             animation: false
 
-         }
 
-     },
 
-     tooltip: {
 
-         pointFormat: '{point.name}: {point.value}'
 
-     }
 
- };
 
- var vennSeries = {
 
-     isCartesian: false,
 
-     axisTypes: [],
 
-     directTouch: true,
 
-     pointArrayMap: ['value'],
 
-     translate: function () {
 
-         var chart = this.chart;
 
-         this.processedXData = this.xData;
 
-         this.generatePoints();
 
-         // Process the data before passing it into the layout function.
 
-         var relations = processVennData(this.options.data);
 
-         // Calculate the positions of each circle.
 
-         var mapOfIdToShape = layout(relations);
 
-         // Calculate positions of each data label
 
-         var mapOfIdToLabelPosition = getLabelPositions(relations);
 
-         // Calculate the scale, and center of the plot area.
 
-         var field = Object.keys(mapOfIdToShape)
 
-                 .filter(function (key) {
 
-                     var shape = mapOfIdToShape[key];
 
-                     return shape && isNumber(shape.r);
 
-                 })
 
-                 .reduce(function (field, key) {
 
-                     return updateFieldBoundaries(field, mapOfIdToShape[key]);
 
-                 }, { top: 0, bottom: 0, left: 0, right: 0 }),
 
-             scaling = getScale(chart.plotWidth, chart.plotHeight, field),
 
-             scale = scaling.scale,
 
-             centerX = scaling.centerX,
 
-             centerY = scaling.centerY;
 
-         // Iterate all points and calculate and draw their graphics.
 
-         this.points.forEach(function (point) {
 
-             var sets = isArray(point.sets) ? point.sets : [],
 
-                 id = sets.join(),
 
-                 shape = mapOfIdToShape[id],
 
-                 shapeArgs,
 
-                 dataLabelPosition = mapOfIdToLabelPosition[id];
 
-             if (shape) {
 
-                 if (shape.r) {
 
-                     shapeArgs = {
 
-                         x: centerX + shape.x * scale,
 
-                         y: centerY + shape.y * scale,
 
-                         r: shape.r * scale
 
-                     };
 
-                 } else if (shape.d) {
 
-                     // TODO: find a better way to handle scaling of a path.
 
-                     var d = shape.d.reduce(function (path, arr) {
 
-                         if (arr[0] === 'M') {
 
-                             arr[1] = centerX + arr[1] * scale;
 
-                             arr[2] = centerY + arr[2] * scale;
 
-                         } else if (arr[0] === 'A') {
 
-                             arr[1] = arr[1] * scale;
 
-                             arr[2] = arr[2] * scale;
 
-                             arr[6] = centerX + arr[6] * scale;
 
-                             arr[7] = centerY + arr[7] * scale;
 
-                         }
 
-                         return path.concat(arr);
 
-                     }, [])
 
-                         .join(' ');
 
-                     shapeArgs = {
 
-                         d: d
 
-                     };
 
-                 }
 
-                 // Scale the position for the data label.
 
-                 if (dataLabelPosition) {
 
-                     dataLabelPosition.x = centerX + dataLabelPosition.x * scale;
 
-                     dataLabelPosition.y = centerY + dataLabelPosition.y * scale;
 
-                 } else {
 
-                     dataLabelPosition = {};
 
-                 }
 
-             }
 
-             point.shapeArgs = shapeArgs;
 
-             // Placement for the data labels
 
-             if (dataLabelPosition && shapeArgs) {
 
-                 point.plotX = dataLabelPosition.x;
 
-                 point.plotY = dataLabelPosition.y;
 
-             }
 
-             // Set name for usage in tooltip and in data label.
 
-             point.name = point.options.name || sets.join('∩');
 
-         });
 
-     },
 
-     /**
 
-      * Draw the graphics for each point.
 
-      * @private
 
-      */
 
-     drawPoints: function () {
 
-         var series = this,
 
-             // Series properties
 
-             chart = series.chart,
 
-             group = series.group,
 
-             points = series.points || [],
 
-             // Chart properties
 
-             renderer = chart.renderer;
 
-         // Iterate all points and calculate and draw their graphics.
 
-         points.forEach(function (point) {
 
-             var attribs,
 
-                 shapeArgs = point.shapeArgs;
 
-             // Add point attribs
 
-             if (!chart.styledMode) {
 
-                 attribs = series.pointAttribs(point, point.state);
 
-             }
 
-             // Draw the point graphic.
 
-             point.draw({
 
-                 isNew: !point.graphic,
 
-                 animatableAttribs: shapeArgs,
 
-                 attribs: attribs,
 
-                 group: group,
 
-                 renderer: renderer,
 
-                 shapeType: shapeArgs && shapeArgs.d ? 'path' : 'circle'
 
-             });
 
-         });
 
-     },
 
-     /**
 
-      * Calculates the style attributes for a point. The attributes can vary
 
-      * depending on the state of the point.
 
-      * @private
 
-      * @param {object} point The point which will get the resulting attributes.
 
-      * @param {string} state The state of the point.
 
-      * @return {object} Returns the calculated attributes.
 
-      */
 
-     pointAttribs: function (point, state) {
 
-         var series = this,
 
-             seriesOptions = series.options || {},
 
-             pointOptions = point && point.options || {},
 
-             stateOptions = (state && seriesOptions.states[state]) || {},
 
-             options = merge(
 
-                 seriesOptions,
 
-                 { color: point && point.color },
 
-                 pointOptions,
 
-                 stateOptions
 
-             );
 
-         // Return resulting values for the attributes.
 
-         return {
 
-             'fill': color(options.color)
 
-                 .setOpacity(options.opacity)
 
-                 .brighten(options.brightness)
 
-                 .get(),
 
-             'stroke': options.borderColor,
 
-             'stroke-width': options.borderWidth,
 
-             'dashstyle': options.borderDashStyle
 
-         };
 
-     },
 
-     animate: function (init) {
 
-         if (!init) {
 
-             var series = this,
 
-                 animOptions = H.animObject(series.options.animation);
 
-             series.points.forEach(function (point) {
 
-                 var args = point.shapeArgs;
 
-                 if (point.graphic && args) {
 
-                     var attr = {},
 
-                         animate = {};
 
-                     if (args.d) {
 
-                         // If shape is a path, then animate opacity.
 
-                         attr.opacity = 0.001;
 
-                     } else {
 
-                         // If shape is a circle, then animate radius.
 
-                         attr.r = 0;
 
-                         animate.r = args.r;
 
-                     }
 
-                     point.graphic
 
-                         .attr(attr)
 
-                         .animate(animate, animOptions);
 
-                     // If shape is path, then fade it in after the circles
 
-                     // animation
 
-                     if (args.d) {
 
-                         setTimeout(function () {
 
-                             if (point && point.graphic) {
 
-                                 point.graphic.animate({
 
-                                     opacity: 1
 
-                                 });
 
-                             }
 
-                         }, animOptions.duration);
 
-                     }
 
-                 }
 
-             }, series);
 
-             series.animate = null;
 
-         }
 
-     },
 
-     utils: {
 
-         addOverlapToSets: addOverlapToSets,
 
-         geometry: geometry,
 
-         geometryCircles: geometryCircles,
 
-         getDistanceBetweenCirclesByOverlap: getDistanceBetweenCirclesByOverlap,
 
-         loss: loss,
 
-         processVennData: processVennData,
 
-         sortByTotalOverlap: sortByTotalOverlap
 
-     }
 
- };
 
- var vennPoint = {
 
-     draw: draw,
 
-     shouldDraw: function () {
 
-         var point = this;
 
-         // Only draw points with single sets.
 
-         return !!point.shapeArgs;
 
-     },
 
-     isValid: function () {
 
-         return isNumber(this.value);
 
-     }
 
- };
 
- /**
 
-  * A `venn` series. If the [type](#series.venn.type) option is
 
-  * not specified, it is inherited from [chart.type](#chart.type).
 
-  *
 
-  * @extends   series,plotOptions.venn
 
-  * @excluding connectEnds, connectNulls, cropThreshold, dataParser, dataURL,
 
-  *            findNearestPointBy, getExtremesFromAll, label, linecap, lineWidth,
 
-  *            linkedTo, marker, negativeColor, pointInterval, pointIntervalUnit,
 
-  *            pointPlacement, pointStart, softThreshold, stack, stacking, steps,
 
-  *            threshold, xAxis, yAxis, zoneAxis, zones
 
-  * @product   highcharts
 
-  * @apioption series.venn
 
-  */
 
- /**
 
-  * @type      {Array<*>}
 
-  * @extends   series.scatter.data
 
-  * @excluding marker, x, y
 
-  * @product   highcharts
 
-  * @apioption series.venn.data
 
-  */
 
- /**
 
-  * The name of the point. Used in data labels and tooltip. If name is not
 
-  * defined then it will default to the joined values in
 
-  * [sets](#series.venn.sets).
 
-  *
 
-  * @sample {highcharts} highcharts/demo/venn-diagram/
 
-  *         Venn diagram
 
-  * @sample {highcharts} highcharts/demo/euler-diagram/
 
-  *         Euler diagram
 
-  *
 
-  * @type      {number}
 
-  * @since     7.0.0
 
-  * @product   highcharts
 
-  * @apioption series.venn.data.name
 
-  */
 
- /**
 
-  * The value of the point, resulting in a relative area of the circle, or area
 
-  * of overlap between two sets in the venn or euler diagram.
 
-  *
 
-  * @sample {highcharts} highcharts/demo/venn-diagram/
 
-  *         Venn diagram
 
-  * @sample {highcharts} highcharts/demo/euler-diagram/
 
-  *         Euler diagram
 
-  *
 
-  * @type      {number}
 
-  * @since     7.0.0
 
-  * @product   highcharts
 
-  * @apioption series.venn.data.value
 
-  */
 
- /**
 
-  * The set or sets the options will be applied to. If a single entry is defined,
 
-  * then it will create a new set. If more than one entry is defined, then it
 
-  * will define the overlap between the sets in the array.
 
-  *
 
-  * @sample {highcharts} highcharts/demo/venn-diagram/
 
-  *         Venn diagram
 
-  * @sample {highcharts} highcharts/demo/euler-diagram/
 
-  *         Euler diagram
 
-  *
 
-  * @type      {Array<string>}
 
-  * @since     7.0.0
 
-  * @product   highcharts
 
-  * @apioption series.venn.data.sets
 
-  */
 
- /**
 
-  * @private
 
-  * @class
 
-  * @name Highcharts.seriesTypes.venn
 
-  *
 
-  * @augments Highcharts.Series
 
-  */
 
- seriesType('venn', 'scatter', vennOptions, vennSeries, vennPoint);
 
 
  |