| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348 | /** * @license  Highcharts JS v7.0.2 (2019-01-17) * Force directed graph module * * (c) 2010-2019 Torstein Honsi * * License: www.highcharts.com/license */'use strict';(function (factory) {	if (typeof module === 'object' && module.exports) {		factory['default'] = factory;		module.exports = factory;	} else if (typeof define === 'function' && define.amd) {		define(function () {			return factory;		});	} else {		factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);	}}(function (Highcharts) {	(function (H) {		H.NodesMixin = {		    // Create a single node that holds information on incoming and outgoing		    // links.		    createNode: function (id) {		        function findById(nodes, id) {		            return H.find(nodes, function (node) {		                return node.id === id;		            });		        }		        var node = findById(this.nodes, id),		            PointClass = this.pointClass,		            options;		        if (!node) {		            options = this.options.nodes && findById(this.options.nodes, id);		            node = (new PointClass()).init(		                this,		                H.extend({		                    className: 'highcharts-node',		                    isNode: true,		                    id: id,		                    y: 1 // Pass isNull test		                }, options)		            );		            node.linksTo = [];		            node.linksFrom = [];		            node.formatPrefix = 'node';		            node.name = node.name || node.options.id; // for use in formats		            // Return the largest sum of either the incoming or outgoing links.		            node.getSum = function () {		                var sumTo = 0,		                    sumFrom = 0;		                node.linksTo.forEach(function (link) {		                    sumTo += link.weight;		                });		                node.linksFrom.forEach(function (link) {		                    sumFrom += link.weight;		                });		                return Math.max(sumTo, sumFrom);		            };		            // Get the offset in weight values of a point/link.		            node.offset = function (point, coll) {		                var offset = 0;		                for (var i = 0; i < node[coll].length; i++) {		                    if (node[coll][i] === point) {		                        return offset;		                    }		                    offset += node[coll][i].weight;		                }		            };		            // Return true if the node has a shape, otherwise all links are		            // outgoing.		            node.hasShape = function () {		                var outgoing = 0;		                node.linksTo.forEach(function (link) {		                    if (link.outgoing) {		                        outgoing++;		                    }		                });		                return !node.linksTo.length || outgoing !== node.linksTo.length;		            };		            this.nodes.push(node);		        }		        return node;		    }		};	}(Highcharts));	(function (H) {		/**		 * Networkgraph series		 *		 * (c) 2010-2019 Paweł Fus		 *		 * License: www.highcharts.com/license		 */		var pick = H.pick;		H.layouts = {		    'reingold-fruchterman': function (options) {		        this.options = options;		        this.nodes = [];		        this.links = [];		        this.series = [];		        this.box = {		            x: 0,		            y: 0,		            width: 0,		            height: 0		        };		        this.setInitialRendering(true);		    }		};		H.extend(		    /**		    * Reingold-Fruchterman algorithm from		    * "Graph Drawing by Force-directed Placement" paper.		    */		    H.layouts['reingold-fruchterman'].prototype,		    {		        run: function () {		            var layout = this,		                series = this.series,		                options = this.options;		            if (layout.initialRendering) {		                layout.initPositions();		                // Render elements in initial positions:		                series.forEach(function (s) {		                    s.render();		                });		            }		            // Algorithm:		            function localLayout() {		                // Barycenter forces:		                layout.applyBarycenterForces();		                // Repulsive forces:		                layout.applyRepulsiveForces();		                // Attractive forces:		                layout.applyAttractiveForces();		                // Limit to the plotting area and cool down:		                layout.applyLimits(layout.temperature);		                // Cool down:		                layout.temperature -= layout.diffTemperature;		                layout.prevSystemTemperature = layout.systemTemperature;		                layout.systemTemperature = layout.getSystemTemperature();		                if (options.enableSimulation) {		                    series.forEach(function (s) {		                        s.render();		                    });		                    if (		                        layout.maxIterations-- &&		                        !layout.isStable()		                    ) {		                        layout.simulation = H.win.requestAnimationFrame(		                            localLayout		                        );		                    } else {		                        layout.simulation = false;		                    }		                }		            }		            layout.setK();		            layout.resetSimulation(options);		            if (options.enableSimulation) {		                // Animate it:		                layout.simulation = H.win.requestAnimationFrame(localLayout);		            } else {		                // Synchronous rendering:		                while (		                    layout.maxIterations-- &&		                    !layout.isStable()		                ) {		                    localLayout();		                }		                series.forEach(function (s) {		                    s.render();		                });		            }		        },		        stop: function () {		            if (this.simulation) {		                H.win.cancelAnimationFrame(this.simulation);		            }		        },		        setArea: function (x, y, w, h) {		            this.box = {		                left: x,		                top: y,		                width: w,		                height: h		            };		        },		        setK: function () {		            // Optimal distance between nodes,		            // available space around the node:		            this.k = this.options.linkLength ||		                Math.pow(		                    this.box.width * this.box.height / this.nodes.length,		                    0.4		                );		        },		        addNodes: function (nodes) {		            nodes.forEach(function (node) {		                if (this.nodes.indexOf(node) === -1) {		                    this.nodes.push(node);		                }		            }, this);		        },		        removeNode: function (node) {		            var index = this.nodes.indexOf(node);		            if (index !== -1) {		                this.nodes.splice(index, 1);		            }		        },		        removeLink: function (link) {		            var index = this.links.indexOf(link);		            if (index !== -1) {		                this.links.splice(index, 1);		            }		        },		        addLinks: function (links) {		            links.forEach(function (link) {		                if (this.links.indexOf(link) === -1) {		                    this.links.push(link);		                }		            }, this);		        },		        addSeries: function (series) {		            if (this.series.indexOf(series) === -1) {		                this.series.push(series);		            }		        },		        clear: function () {		            this.nodes.length = 0;		            this.links.length = 0;		            this.series.length = 0;		            this.resetSimulation();		        },		        resetSimulation: function () {		            this.forcedStop = false;		            this.systemTemperature = 0;		            this.setMaxIterations();		            this.setTemperature();		            this.setDiffTemperature();		        },		        setMaxIterations: function (maxIterations) {		            this.maxIterations = pick(		                maxIterations,		                this.options.maxIterations		            );		        },		        setTemperature: function () {		            this.temperature = Math.sqrt(this.nodes.length);		        },		        setDiffTemperature: function () {		            this.diffTemperature = this.temperature /		                (this.options.maxIterations + 1);		        },		        setInitialRendering: function (enable) {		            this.initialRendering = enable;		        },		        initPositions: function () {		            var initialPositions = this.options.initialPositions;		            if (H.isFunction(initialPositions)) {		                initialPositions.call(this);		            } else if (initialPositions === 'circle') {		                this.setCircularPositions();		            } else {		                this.setRandomPositions();		            }		        },		        setCircularPositions: function () {		            var box = this.box,		                nodes = this.nodes,		                nodesLength = nodes.length + 1,		                angle = 2 * Math.PI / nodesLength,		                rootNodes = nodes.filter(function (node) {		                    return node.linksTo.length === 0;		                }),		                sortedNodes = [],		                visitedNodes = {};		            function addToNodes(node) {		                node.linksFrom.forEach(function (link) {		                    if (!visitedNodes[link.toNode.id]) {		                        visitedNodes[link.toNode.id] = true;		                        sortedNodes.push(link.toNode);		                        addToNodes(link.toNode);		                    }		                });		            }		            // Start with identified root nodes an sort the nodes by their		            // hierarchy. In trees, this ensures that branches don't cross		            // eachother.		            rootNodes.forEach(function (rootNode) {		                sortedNodes.push(rootNode);		                addToNodes(rootNode);		            });		            // Cyclic tree, no root node found		            if (!sortedNodes.length) {		                sortedNodes = nodes;		            // Dangling, cyclic trees		            } else {		                nodes.forEach(function (node) {		                    if (sortedNodes.indexOf(node) === -1) {		                        sortedNodes.push(node);		                    }		                });		            }		            // Initial positions are laid out along a small circle, appearing		            // as a cluster in the middle		            sortedNodes.forEach(function (node, index) {		                node.plotX = pick(		                    node.plotX,		                    box.width / 2 + Math.cos(index * angle)		                );		                node.plotY = pick(		                    node.plotY,		                    box.height / 2 + Math.sin(index * angle)		                );		                node.dispX = 0;		                node.dispY = 0;		            });		        },		        setRandomPositions: function () {		            var box = this.box,		                nodes = this.nodes,		                nodesLength = nodes.length + 1;		            // Return a repeatable, quasi-random number based on an integer		            // input. For the initial positions		            function unrandom(n) {		                var rand = n * n / Math.PI;		                rand = rand - Math.floor(rand);		                return rand;		            }		            // Initial positions:		            nodes.forEach(		                function (node, index) {		                    node.plotX = pick(		                        node.plotX,		                        box.width * unrandom(index)		                    );		                    node.plotY = pick(		                        node.plotY,		                        box.height * unrandom(nodesLength + index)		                    );		                    node.dispX = 0;		                    node.dispY = 0;		                }		            );		        },		        applyBarycenterForces: function () {		            var nodesLength = this.nodes.length,		                gravitationalConstant = this.options.gravitationalConstant,		                cx = 0,		                cy = 0;		            // Calculate center:		            this.nodes.forEach(function (node) {		                cx += node.plotX;		                cy += node.plotY;		            });		            this.barycenter = {		                x: cx,		                y: cy		            };		            // Apply forces:		            this.nodes.forEach(function (node) {		                var degree = node.getDegree(),		                    phi = degree * (1 + degree / 2);		                node.dispX = (cx / nodesLength - node.plotX) *		                    gravitationalConstant * phi;		                node.dispY = (cy / nodesLength - node.plotY) *		                    gravitationalConstant * phi;		            });		        },		        applyRepulsiveForces: function () {		            var layout = this,		                nodes = layout.nodes,		                options = layout.options,		                k = this.k;		            nodes.forEach(function (node) {		                nodes.forEach(function (repNode) {		                    var force,		                        distanceR,		                        distanceXY;		                    if (		                        // Node can not repulse itself:		                        node !== repNode &&		                        // Only close nodes affect each other:		                        /* layout.getDistR(node, repNode) < 2 * k && */		                        // Not dragged:		                        !node.fixedPosition		                    ) {		                        distanceXY = layout.getDistXY(node, repNode);		                        distanceR = layout.vectorLength(distanceXY);		                        if (distanceR !== 0) {		                            force = options.repulsiveForce.call(		                                layout, distanceR, k		                            );		                            node.dispX += (distanceXY.x / distanceR) * force;		                            node.dispY += (distanceXY.y / distanceR) * force;		                        }		                    }		                });		            });		        },		        applyAttractiveForces: function () {		            var layout = this,		                links = layout.links,		                options = this.options,		                k = this.k;		            links.forEach(function (link) {		                if (link.fromNode && link.toNode) {		                    var distanceXY = layout.getDistXY(		                            link.fromNode,		                            link.toNode		                        ),		                        distanceR = layout.vectorLength(distanceXY),		                        force = options.attractiveForce.call(		                            layout, distanceR, k		                        );		                    if (distanceR !== 0) {		                        if (!link.fromNode.fixedPosition) {		                            link.fromNode.dispX -= (distanceXY.x / distanceR) *		                                force;		                            link.fromNode.dispY -= (distanceXY.y / distanceR) *		                                force;		                        }		                        if (!link.toNode.fixedPosition) {		                            link.toNode.dispX += (distanceXY.x / distanceR) *		                                force;		                            link.toNode.dispY += (distanceXY.y / distanceR) *		                                force;		                        }		                    }		                }		            });		        },		        applyLimits: function (temperature) {		            var layout = this,		                options = layout.options,		                nodes = layout.nodes,		                box = layout.box,		                distanceR;		            nodes.forEach(function (node) {		                if (node.fixedPosition) {		                    return;		                }		                // Friction:		                node.dispX += options.friction * node.dispX;		                node.dispY += options.friction * node.dispY;		                distanceR = node.temperature = layout.vectorLength({		                    x: node.dispX,		                    y: node.dispY		                });		                // Place nodes:		                if (distanceR !== 0) {		                    node.plotX += node.dispX / distanceR *		                        Math.min(Math.abs(node.dispX), temperature);		                    node.plotY += node.dispY / distanceR *		                        Math.min(Math.abs(node.dispY), temperature);		                }		                /*		                TO DO: Consider elastic collision instead of stopping.		                o' means end position when hitting plotting area edge:		                - "inealstic":		                o		                 \		                ______		                |  o'		                |   \		                |    \		                - "elastic"/"bounced":		                o		                 \		                ______		                |  ^		                | / \		                |o'  \		                */		                // Limit X-coordinates:		                node.plotX = Math.round(		                    Math.max(		                        Math.min(		                            node.plotX,		                            box.width		                        ),		                        box.left		                    )		                );		                // Limit Y-coordinates:		                node.plotY = Math.round(		                    Math.max(		                        Math.min(		                            node.plotY,		                            box.height		                        ),		                        box.top		                    )		                );		                // Reset displacement:		                node.dispX = 0;		                node.dispY = 0;		            });		        },		        isStable: function () {		            return Math.abs(		                this.systemTemperature -		                this.prevSystemTemperature		            ) === 0;		        },		        getSystemTemperature: function () {		            return this.nodes.reduce(function (value, node) {		                return value + node.temperature;		            }, 0);		        },		        vectorLength: function (vector) {		            return Math.sqrt(vector.x * vector.x + vector.y * vector.y);		        },		        getDistR: function (nodeA, nodeB) {		            var distance = this.getDistXY(nodeA, nodeB);		            return Math.sqrt(		                distance.x * distance.x +		                distance.y * distance.y		            );		        },		        getDistXY: function (nodeA, nodeB) {		            var xDist = nodeA.plotX - nodeB.plotX,		                yDist = nodeA.plotY - nodeB.plotY;		            return {		                x: xDist,		                y: yDist,		                absX: Math.abs(xDist),		                absY: Math.abs(yDist)		            };		        }		    }		);	}(Highcharts));	(function (H) {		/**		 * Networkgraph series		 *		 * (c) 2010-2019 Paweł Fus		 *		 * License: www.highcharts.com/license		 */		var addEvent = H.addEvent,		    defined = H.defined,		    seriesType = H.seriesType,		    seriesTypes = H.seriesTypes,		    pick = H.pick,		    Chart = H.Chart,		    Point = H.Point,		    Series = H.Series;		/**		 * A networkgraph is a type of relationship chart, where connnections		 * (links) attracts nodes (points) and other nodes repulse each other.		 *		 * @extends      plotOptions.line		 * @product      highcharts		 * @sample       highcharts/demo/network-graph/		 *               Networkgraph		 * @since        7.0.0		 * @excluding    boostThreshold, animation, animationLimit, connectEnds,		 *               connectNulls, dragDrop, getExtremesFromAll, label, linecap,		 *               negativeColor, pointInterval, pointIntervalUnit,		 *               pointPlacement, pointStart, softThreshold, stack, stacking,		 *               step, threshold, xAxis, yAxis, zoneAxis		 * @optionparent plotOptions.networkgraph		 */		seriesType('networkgraph', 'line', {		    marker: {		        enabled: true		    },		    dataLabels: {		        format: '{key}'		    },		    /**		     * Link style options		     */		    link: {		        /**		         * A name for the dash style to use for links.		         *		         * @type      {String}		         * @apioption plotOptions.networkgraph.link.dashStyle		         * @defaults  undefined		         */		        /**		         * Color of the link between two nodes.		         */		        color: 'rgba(100, 100, 100, 0.5)',		        /**		         * Width (px) of the link between two nodes.		         */		        width: 1		    },		    /**		     * Flag to determine if nodes are draggable or not.		     */		    draggable: true,		    layoutAlgorithm: {		        /**		         * Ideal length (px) of the link between two nodes. When not defined,		         * length is calculated as:		         * `Math.pow(availableWidth * availableHeight / nodesLength, 0.4);`		         *		         * Note: Because of the algorithm specification, length of each link		         * might be not exactly as specified.		         *		         * @type      {number}		         * @apioption series.networkgraph.layoutAlgorithm.linkLength		         * @sample    highcharts/series-networkgraph/styled-links/		         *            Numerical values		         * @defaults  undefined		         */		        /**		         * Initial layout algorithm for positioning nodes. Can be one of		         * built-in options ("circle", "random") or a function where positions		         * should be set on each node (`this.nodes`) as `node.plotX` and		         * `node.plotY`		         *		         * @sample      highcharts/series-networkgraph/initial-positions/		         *              Initial positions with callback		         * @type        {String|Function}		         * @validvalue  ["circle", "random"]		         */		        initialPositions: 'circle',		        /**		         * Experimental. Enables live simulation of the algorithm		         * implementation. All nodes are animated as the forces applies on		         * them.		         *		         * @sample       highcharts/demo/network-graph/		         *               Live simulation enabled		         */		        enableSimulation: false,		        /**		         * Type of the algorithm used when positioning nodes.		         *		         * @validvalue  ["reingold-fruchterman"]		         */		        type: 'reingold-fruchterman',		        /**		         * Max number of iterations before algorithm will stop. In general,		         * algorithm should find positions sooner, but when rendering huge		         * number of nodes, it is recommended to increase this value as		         * finding perfect graph positions can require more time.		         */		        maxIterations: 1000,		        /**		         * Gravitational const used in the barycenter force of the algorithm.		         *		         * @sample      highcharts/series-networkgraph/forces/		         *              Custom forces		         */		        gravitationalConstant: 0.0625,		        /**		         * Friction applied on forces to prevent nodes rushing to fast to the		         * desired positions.		         */		        friction: -0.981,		        /**		         * Repulsive force applied on a node. Passed are two arguments:		         * - `d` - which is current distance between two nodes		         * - `k` - which is desired distance between two nodes		         *		         * @sample      highcharts/series-networkgraph/forces/		         *              Custom forces		         * @type        {Function}		         * @default function (d, k) { return k * k / d; }		         */		        repulsiveForce: function (d, k) {		            /*		            basic, not recommended:		            return k / d;		            */		            /*		            standard:		            return k * k / d;		            */		            /*		            grid-variant:		            return k * k / d * (2 * k - d > 0 ? 1 : 0);		            */		            return k * k / d;		        },		        /**		         * Attraction force applied on a node which is conected to another node		         * by a link. Passed are two arguments:		         * - `d` - which is current distance between two nodes		         * - `k` - which is desired distance between two nodes		         *		         * @sample      highcharts/series-networkgraph/forces/		         *              Custom forces		         * @type        {Function}		         * @default function (d, k) { return k * k / d; }		         */		        attractiveForce: function (d, k) {		            /*		            basic, not recommended:		            return d / k;		            */		            return d * d / k;		        }		    },		    showInLegend: false		}, {		    isNetworkgraph: true,		    drawGraph: null,		    isCartesian: false,		    requireSorting: false,		    directTouch: true,		    noSharedTooltip: true,		    trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],		    drawTracker: H.TrackerMixin.drawTrackerPoint,		    // Animation is run in `series.simulation`.		    animate: null,		    /**		     * Create a single node that holds information on incoming and outgoing		     * links.		     */		    createNode: H.NodesMixin.createNode,		    /**		     * Extend generatePoints by adding the nodes, which are Point objects		     * but pushed to the this.nodes array.		     */		    generatePoints: function () {		        var nodeLookup = {},		            chart = this.chart;		        H.Series.prototype.generatePoints.call(this);		        if (!this.nodes) {		            this.nodes = []; // List of Point-like node items		        }		        this.colorCounter = 0;		        // Reset links from previous run		        this.nodes.forEach(function (node) {		            node.linksFrom.length = 0;		            node.linksTo.length = 0;		        });		        // Create the node list and set up links		        this.points.forEach(function (point) {		            if (defined(point.from)) {		                if (!nodeLookup[point.from]) {		                    nodeLookup[point.from] = this.createNode(point.from);		                }		                nodeLookup[point.from].linksFrom.push(point);		                point.fromNode = nodeLookup[point.from];		                // Point color defaults to the fromNode's color		                if (chart.styledMode) {		                    point.colorIndex = pick(		                        point.options.colorIndex,		                        nodeLookup[point.from].colorIndex		                    );		                } else {		                    point.color =		                        point.options.color || nodeLookup[point.from].color;		                }		            }		            if (defined(point.to)) {		                if (!nodeLookup[point.to]) {		                    nodeLookup[point.to] = this.createNode(point.to);		                }		                nodeLookup[point.to].linksTo.push(point);		                point.toNode = nodeLookup[point.to];		            }		            point.name = point.name || point.id; // for use in formats		        }, this);		        if (this.options.nodes) {		            this.options.nodes.forEach(		                function (nodeOptions) {		                    if (!nodeLookup[nodeOptions.id]) {		                        nodeLookup[nodeOptions.id] = this		                            .createNode(nodeOptions.id);		                    }		                },		                this		            );		        }		    },		    /**		     * Run pre-translation by generating the nodeColumns.		     */		    translate: function () {		        if (!this.processedXData) {		            this.processData();		        }		        this.generatePoints();		        this.deferLayout();		        this.nodes.forEach(function (node) {		            // Draw the links from this node		            node.isInside = true;		            node.linksFrom.forEach(function (point) {		                point.shapeType = 'path';		                // Pass test in drawPoints		                point.y = 1;		            });		        });		    },		    deferLayout: function () {		        var layoutOptions = this.options.layoutAlgorithm,		            graphLayoutsStorage = this.chart.graphLayoutsStorage,		            chartOptions = this.chart.options.chart,		            layout;		        if (!this.visible) {		            return;		        }		        if (!graphLayoutsStorage) {		            this.chart.graphLayoutsStorage = graphLayoutsStorage = {};		        }		        layout = graphLayoutsStorage[layoutOptions.type];		        if (!layout) {		            layoutOptions.enableSimulation = !defined(chartOptions.forExport) ?		                layoutOptions.enableSimulation :		                !chartOptions.forExport;		            graphLayoutsStorage[layoutOptions.type] = layout =		                new H.layouts[layoutOptions.type](layoutOptions);		        }		        this.layout = layout;		        layout.setArea(0, 0, this.chart.plotWidth, this.chart.plotHeight);		        layout.addSeries(this);		        layout.addNodes(this.nodes);		        layout.addLinks(this.points);		    },		    /**		     * Extend the render function to also render this.nodes together with		     * the points.		     */		    render: function () {		        var points = this.points,		            hoverPoint = this.chart.hoverPoint,		            dataLabels = [];		        // Render markers:		        this.points = this.nodes;		        seriesTypes.line.prototype.render.call(this);		        this.points = points;		        points.forEach(function (point) {		            if (point.fromNode && point.toNode) {		                point.renderLink();		                point.redrawLink();		            }		        });		        if (hoverPoint && hoverPoint.series === this) {		            this.redrawHalo(hoverPoint);		        }		        this.nodes.forEach(function (node) {		            if (node.dataLabel) {		                dataLabels.push(node.dataLabel);		            }		        });		        H.Chart.prototype.hideOverlappingLabels(dataLabels);		    },		    /*		     * Draggable mode:		     */		    redrawHalo: function (point) {		        if (point && this.halo) {		            this.halo.attr({		                d: point.haloPath(		                    this.options.states.hover.halo.size		                )		            });		        }		    },		    onMouseDown: function (point, event) {		        var normalizedEvent = this.chart.pointer.normalize(event);		        point.fixedPosition = {		            chartX: normalizedEvent.chartX,		            chartY: normalizedEvent.chartY,		            plotX: point.plotX,		            plotY: point.plotY		        };		    },		    onMouseMove: function (point, event) {		        if (point.fixedPosition) {		            var series = this,		                chart = series.chart,		                normalizedEvent = chart.pointer.normalize(event),		                diffX = point.fixedPosition.chartX - normalizedEvent.chartX,		                diffY = point.fixedPosition.chartY - normalizedEvent.chartY,		                newPlotX,		                newPlotY;		            // At least 5px to apply change (avoids simple click):		            if (Math.abs(diffX) > 5 || Math.abs(diffY) > 5) {		                newPlotX = point.fixedPosition.plotX - diffX;		                newPlotY = point.fixedPosition.plotY - diffY;		                if (chart.isInsidePlot(newPlotX, newPlotY)) {		                    point.plotX = newPlotX;		                    point.plotY = newPlotY;		                    series.redrawHalo();		                    if (!series.layout.simulation) {		                        // Start new simulation:		                        if (!series.layout.enableSimulation) {		                            // Run only one iteration to speed things up:		                            series.layout.setMaxIterations(1);		                        }		                        // When dragging nodes, we don't need to calculate		                        // initial positions and rendering nodes:		                        series.layout.setInitialRendering(false);		                        series.layout.run();		                        // Restore defaults:		                        series.layout.setInitialRendering(true);		                    } else {		                        // Extend current simulation:		                        series.layout.resetSimulation();		                    }		                }		            }		        }		    },		    onMouseUp: function (point) {		        if (point.fixedPosition) {		            this.layout.run();		            delete point.fixedPosition;		        }		    },		    destroy: function () {		        this.nodes.forEach(function (node) {		            node.destroy();		        });		        return Series.prototype.destroy.apply(this, arguments);		    }		}, {		    getDegree: function () {		        var deg = this.isNode ? this.linksFrom.length + this.linksTo.length : 0;		        return deg === 0 ? 1 : deg;		    },		    // Links:		    getLinkAttribues: function () {		        var linkOptions = this.series.options.link,		            pointOptions = this.options;		        return {		            'stroke-width': pick(pointOptions.width, linkOptions.width),		            stroke: pointOptions.color || linkOptions.color,		            dashstyle: pointOptions.dashStyle || linkOptions.dashStyle		        };		    },		    renderLink: function () {		        if (!this.graphic) {		            this.graphic = this.series.chart.renderer		                .path(		                    this.getLinkPath(this.fromNode, this.toNode)		                )		                .attr(this.getLinkAttribues())		                .add(this.series.group);		        }		    },		    redrawLink: function () {		        if (this.graphic) {		            this.graphic.animate({		                d: this.getLinkPath(this.fromNode, this.toNode)		            });		        }		    },		    getLinkPath: function (from, to) {		        return [		            'M',		            from.plotX,		            from.plotY,		            'L',		            to.plotX,		            to.plotY		        ];		        /*		        IDEA: different link shapes?		        return [		            'M',		            from.plotX,		            from.plotY,		            'Q',		            (to.plotX + from.plotX) / 2,		            (to.plotY + from.plotY) / 2 + 15,		            to.plotX,		            to.plotY		        ];*/		    },		    // Default utils:		    destroy: function () {		        if (this.isNode) {		            this.linksFrom.forEach(		                function (linkFrom) {		                    if (linkFrom.graphic) {		                        linkFrom.graphic = linkFrom.graphic.destroy();		                    }		                }		            );		        }		        return Point.prototype.destroy.apply(this, arguments);		    }		});		addEvent(seriesTypes.networkgraph, 'updatedData', function () {		    if (this.layout) {		        this.layout.stop();		    }		});		addEvent(seriesTypes.networkgraph.prototype.pointClass, 'remove', function () {		    if (this.series.layout) {		        if (this.isNode) {		            this.series.layout.removeNode(this);		        } else {		            this.series.layout.removeLink(this);		        }		    }		});		/*		 * Multiple series support:		 */		// Clear previous layouts		addEvent(Chart, 'predraw', function () {		    if (this.graphLayoutsStorage) {		        H.objectEach(		            this.graphLayoutsStorage,		            function (layout) {		                layout.stop();		            }		        );		    }		});		addEvent(Chart, 'render', function () {		    if (this.graphLayoutsStorage) {		        H.setAnimation(false, this);		        H.objectEach(		            this.graphLayoutsStorage,		            function (layout) {		                layout.run();		            }		        );		    }		});		/*		 * Draggable mode:		 */		addEvent(		    seriesTypes.networkgraph.prototype.pointClass,		    'mouseOver',		    function () {		        H.css(this.series.chart.container, { cursor: 'move' });		    }		);		addEvent(		    seriesTypes.networkgraph.prototype.pointClass,		    'mouseOut',		    function () {		        H.css(this.series.chart.container, { cursor: 'default' });		    }		);		addEvent(		    Chart,		    'load',		    function () {		        var chart = this,		            unbinders = [];		        if (chart.container) {		            unbinders.push(		                addEvent(		                    chart.container,		                    'mousedown',		                    function (event) {		                        var point = chart.hoverPoint;		                        if (		                            point &&		                            point.series &&		                            point.series.isNetworkgraph &&		                            point.series.options.draggable		                        ) {		                            point.series.onMouseDown(point, event);		                            unbinders.push(addEvent(		                                chart.container,		                                'mousemove',		                                function (e) {		                                    return point.series.onMouseMove(point, e);		                                }		                            ));		                            unbinders.push(addEvent(		                                chart.container.ownerDocument,		                                'mouseup',		                                function (e) {		                                    return point.series.onMouseUp(point, e);		                                }		                            ));		                        }		                    }		                )		            );		        }		        addEvent(chart, 'destroy', function () {		            unbinders.forEach(function (unbind) {		                unbind();		            });		        });		    }		);		/**		 * A `networkgraph` series. If the [type](#series.networkgraph.type) option is		 * not specified, it is inherited from [chart.type](#chart.type).		 *		 * @type      {Object}		 * @extends   series,plotOptions.networkgraph		 * @excluding boostThreshold, animation, animationLimit, connectEnds,		 *            connectNulls, dragDrop, getExtremesFromAll, label, linecap,		 *            negativeColor, pointInterval, pointIntervalUnit,		 *            pointPlacement, pointStart, softThreshold, stack, stacking,		 *            step, threshold, xAxis, yAxis, zoneAxis		 * @product   highcharts		 * @apioption series.networkgraph		 */		/**		 * An array of data points for the series. For the `networkgraph` series type,		 * points can be given in the following way:		 *		 * An array of objects with named values. The following snippet shows only a		 * few settings, see the complete options set below. If the total number of		 * data points exceeds the series'		 * [turboThreshold](#series.area.turboThreshold), this option is not available.		 *		 *  ```js		 *     data: [{		 *         from: 'Category1',		 *         to: 'Category2'		 *     }, {		 *         from: 'Category1',		 *         to: 'Category3'		 *     }]		 *  ```		 *		 * @type      {Array<Object|Array|Number>}		 * @extends   series.line.data		 * @excluding drilldown,marker,x,y,draDrop		 * @sample    {highcharts} highcharts/chart/reflow-true/		 *            Numerical values		 * @sample    {highcharts} highcharts/series/data-array-of-arrays/		 *            Arrays of numeric x and y		 * @sample    {highcharts} highcharts/series/data-array-of-arrays-datetime/		 *            Arrays of datetime x and y		 * @sample    {highcharts} highcharts/series/data-array-of-name-value/		 *            Arrays of point.name and y		 * @sample    {highcharts} highcharts/series/data-array-of-objects/		 *            Config objects		 * @product   highcharts		 * @apioption series.networkgraph.data		 */		/**		 * The node that the link runs from.		 *		 * @type      {String}		 * @product   highcharts		 * @apioption series.networkgraph.data.from		 */		/**		 * The node that the link runs to.		 *		 * @type      {String}		 * @product   highcharts		 * @apioption series.networkgraph.data.to		 */		/**		 * The weight of the link.		 *		 * @type      {Number}		 * @product   highcharts		 * @apioption series.networkgraph.data.weight		 */		/**		  * A collection of options for the individual nodes. The nodes in a		  * networkgraph diagram are auto-generated instances of `Highcharts.Point`,		  * but options can be applied here and linked by the `id`.		  *		  * @sample highcharts/series-networkgraph/data-options/		  *         Networkgraph diagram with node options		  *		  * @type      {Array<*>}		  * @product   highcharts		  * @apioption series.networkgraph.nodes		  */		/**		  * The id of the auto-generated node, refering to the `from` or `to` setting of		  * the link.		  *		  * @type      {string}		  * @product   highcharts		  * @apioption series.networkgraph.nodes.id		  */		/**		  * The color of the auto generated node.		  *		  * @type      {Highcharts.ColorString}		  * @product   highcharts		  * @apioption series.networkgraph.nodes.color		  */		/**		  * The color index of the auto generated node, especially for use in styled		  * mode.		  *		  * @type      {number}		  * @product   highcharts		  * @apioption series.networkgraph.nodes.colorIndex		  */		/**		  * The name to display for the node in data labels and tooltips. Use this when		  * the name is different from the `id`. Where the id must be unique for each		  * node, this is not necessary for the name.		  *		  * @sample highcharts/series-networkgraph/data-options/		  *         Networkgraph diagram with node options		  *		  * @type      {string}		  * @product   highcharts		  * @apioption series.networkgraph.nodes.name		  */	}(Highcharts));	return (function () {	}());}));
 |