| (function webpackUniversalModuleDefinition(root, factory) { |
| if(typeof exports === 'object' && typeof module === 'object') |
| module.exports = factory(require("echarts")); |
| else if(typeof define === 'function' && define.amd) |
| define(["echarts"], factory); |
| else if(typeof exports === 'object') |
| exports["echarts-graph-modularity"] = factory(require("echarts")); |
| else |
| root["echarts-graph-modularity"] = factory(root["echarts"]); |
| })(this, function(__WEBPACK_EXTERNAL_MODULE_9__) { |
| return /******/ (function(modules) { // webpackBootstrap |
| /******/ // The module cache |
| /******/ var installedModules = {}; |
| /******/ |
| /******/ // The require function |
| /******/ function __webpack_require__(moduleId) { |
| /******/ |
| /******/ // Check if module is in cache |
| /******/ if(installedModules[moduleId]) { |
| /******/ return installedModules[moduleId].exports; |
| /******/ } |
| /******/ // Create a new module (and put it into the cache) |
| /******/ var module = installedModules[moduleId] = { |
| /******/ i: moduleId, |
| /******/ l: false, |
| /******/ exports: {} |
| /******/ }; |
| /******/ |
| /******/ // Execute the module function |
| /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); |
| /******/ |
| /******/ // Flag the module as loaded |
| /******/ module.l = true; |
| /******/ |
| /******/ // Return the exports of the module |
| /******/ return module.exports; |
| /******/ } |
| /******/ |
| /******/ |
| /******/ // expose the modules object (__webpack_modules__) |
| /******/ __webpack_require__.m = modules; |
| /******/ |
| /******/ // expose the module cache |
| /******/ __webpack_require__.c = installedModules; |
| /******/ |
| /******/ // define getter function for harmony exports |
| /******/ __webpack_require__.d = function(exports, name, getter) { |
| /******/ if(!__webpack_require__.o(exports, name)) { |
| /******/ Object.defineProperty(exports, name, { |
| /******/ configurable: false, |
| /******/ enumerable: true, |
| /******/ get: getter |
| /******/ }); |
| /******/ } |
| /******/ }; |
| /******/ |
| /******/ // getDefaultExport function for compatibility with non-harmony modules |
| /******/ __webpack_require__.n = function(module) { |
| /******/ var getter = module && module.__esModule ? |
| /******/ function getDefault() { return module['default']; } : |
| /******/ function getModuleExports() { return module; }; |
| /******/ __webpack_require__.d(getter, 'a', getter); |
| /******/ return getter; |
| /******/ }; |
| /******/ |
| /******/ // Object.prototype.hasOwnProperty.call |
| /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; |
| /******/ |
| /******/ // __webpack_public_path__ |
| /******/ __webpack_require__.p = ""; |
| /******/ |
| /******/ // Load entry module and return exports |
| /******/ return __webpack_require__(__webpack_require__.s = 0); |
| /******/ }) |
| /************************************************************************/ |
| /******/ ([ |
| /* 0 */ |
| /***/ (function(module, exports, __webpack_require__) { |
| |
| module.exports = __webpack_require__(1); |
| |
| /***/ }), |
| /* 1 */ |
| /***/ (function(module, exports, __webpack_require__) { |
| |
| var Modularity = __webpack_require__(2); |
| var echarts = __webpack_require__(9); |
| var createNGraph = __webpack_require__(10); |
| |
| function createModularityVisual(chartType) { |
| return function (ecModel, api) { |
| var paletteScope = {}; |
| ecModel.eachSeriesByType(chartType, function (seriesModel) { |
| var modularityOpt = seriesModel.get('modularity'); |
| if (modularityOpt) { |
| var graph = seriesModel.getGraph(); |
| var idIndexMap = {}; |
| var ng = createNGraph(); |
| graph.data.each(function (idx) { |
| var node = graph.getNodeByIndex(idx); |
| idIndexMap[node.id] = idx; |
| ng.addNode(node.id); |
| return node.id; |
| }); |
| graph.edgeData.each('value', function (val, idx) { |
| var edge = graph.getEdgeByIndex(idx); |
| ng.addLink(edge.node1.id, edge.node2.id); |
| return { |
| source: edge.node1.id, |
| target: edge.node2.id, |
| value: val |
| }; |
| }); |
| |
| var modularity = new Modularity(seriesModel.get('modularity.resolution') || 1); |
| var result = modularity.execute(ng); |
| |
| var communities = {}; |
| for (var id in result) { |
| var comm = result[id]; |
| communities[comm] = communities[comm] || 0; |
| communities[comm]++; |
| } |
| var communitiesList = Object.keys(communities); |
| if (seriesModel.get('modularity.sort')) { |
| communitiesList.sort(function (a, b) { |
| return b - a; |
| }); |
| } |
| var colors = {}; |
| communitiesList.forEach(function (comm) { |
| colors[comm] = seriesModel.getColorFromPalette(comm, paletteScope); |
| }); |
| |
| for (var id in result) { |
| var comm = result[id]; |
| graph.data.setItemVisual(idIndexMap[id], 'color', colors[comm]); |
| } |
| |
| graph.edgeData.each(function (idx) { |
| var itemModel = graph.edgeData.getItemModel(idx); |
| var edge = graph.getEdgeByIndex(idx); |
| var color = itemModel.get('lineStyle.normal.color'); |
| |
| switch (color) { |
| case 'source': |
| color = edge.node1.getVisual('color'); |
| break; |
| case 'target': |
| color = edge.node2.getVisual('color'); |
| break; |
| } |
| |
| if (color != null) { |
| edge.setVisual('color', color); |
| } |
| }); |
| } |
| }); |
| }; |
| } |
| |
| echarts.registerVisual(echarts.PRIORITY.VISUAL.CHART + 1, createModularityVisual('graph')); |
| echarts.registerVisual(echarts.PRIORITY.VISUAL.CHART + 1, createModularityVisual('graphGL')); |
| |
| /***/ }), |
| /* 2 */ |
| /***/ (function(module, exports, __webpack_require__) { |
| |
| /* |
| Copyright 2008-2011 Gephi |
| Authors : Patick J. McSweeney <pjmcswee@syr.edu>, Sebastien Heymann <seb@gephi.org> |
| Website : http://www.gephi.org |
| |
| This file is part of Gephi. |
| |
| DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| |
| Copyright 2011 Gephi Consortium. All rights reserved. |
| |
| The contents of this file are subject to the terms of either the GNU |
| General Public License Version 3 only ("GPL") or the Common |
| Development and Distribution License("CDDL") (collectively, the |
| "License"). You may not use this file except in compliance with the |
| License. You can obtain a copy of the License at |
| http://gephi.org/about/legal/license-notice/ |
| or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the |
| specific language governing permissions and limitations under the |
| License. When distributing the software, include this License Header |
| Notice in each file and include the License files at |
| /cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the |
| License Header, with the fields enclosed by brackets [] replaced by |
| your own identifying information: |
| "Portions Copyrighted [year] [name of copyright owner]" |
| |
| If you wish your version of this file to be governed by only the CDDL |
| or only the GPL Version 3, indicate your decision by adding |
| "[Contributor] elects to include this software in this distribution |
| under the [CDDL or GPL Version 3] license." If you do not indicate a |
| single choice of license, a recipient has the option to distribute |
| your version of this file under either the CDDL, the GPL Version 3 or |
| to extend the choice of license to its licensees as provided above. |
| However, if you add GPL Version 3 code and therefore, elected the GPL |
| Version 3 license, then the option applies only if the new code is |
| made subject to such option by the copyright holder. |
| |
| Contributor(s): Thomas Aynaud <taynaud@gmail.com> |
| |
| Portions Copyrighted 2011 Gephi Consortium. |
| */ |
| var CommunityStructure = __webpack_require__(3) |
| , centrality = __webpack_require__(6) |
| ; |
| |
| /** |
| * @constructor |
| */ |
| function Modularity (resolution, useWeight) { |
| this.isRandomized = false; |
| this.useWeight = useWeight; |
| this.resolution = resolution || 1.; |
| /** |
| * @type {CommunityStructure} |
| */ |
| this.structure = null; |
| } |
| |
| /** |
| * @param {IGraph} graph |
| */ |
| Modularity.prototype.execute = function (graph/*, AttributeModel attributeModel*/) { |
| |
| |
| this.structure = new CommunityStructure(graph, this.useWeight); |
| |
| var comStructure = new Array(graph.getNodesCount()); |
| |
| var computedModularityMetrics = this.computeModularity( |
| graph |
| , this.structure |
| , comStructure |
| , this.resolution |
| , this.isRandomized |
| , this.useWeight |
| ); |
| |
| var result = {}; |
| this.structure.map.forEach(function (i, node) { |
| result[node] = comStructure[i]; |
| }); |
| |
| return result; |
| |
| }; |
| |
| |
| /** |
| * |
| * @param {IGraph} graph |
| * @param {CommunityStructure} theStructure |
| * @param {Array.<Number>} comStructure |
| * @param {Number} currentResolution |
| * @param {Boolean} randomized |
| * @param {Boolean} weighted |
| * @returns {Object.<String, Number>} |
| */ |
| Modularity.prototype.computeModularity = function(graph, theStructure, comStructure, currentResolution, randomized, weighted) { |
| |
| |
| function getRandomInt(min, max) { |
| return Math.floor(Math.random() * (max - min)) + min; |
| } |
| |
| var totalWeight = theStructure.graphWeightSum; |
| var nodeDegrees = theStructure.weights.slice(); |
| |
| |
| var /** @type {Object.<String, Number>} */ results = Object.create(null); |
| |
| |
| var someChange = true; |
| |
| while (someChange) { |
| someChange = false; |
| var localChange = true; |
| while (localChange) { |
| localChange = false; |
| var start = 0; |
| if (randomized) { |
| //start = Math.abs(rand.nextInt()) % theStructure.N; |
| start = getRandomInt(0,theStructure.N); |
| } |
| var step = 0; |
| for (var i = start; step < theStructure.N; i = (i + 1) % theStructure.N) { |
| step++; |
| var bestCommunity = this.updateBestCommunity(theStructure, i, currentResolution); |
| if ((theStructure.nodeCommunities[i] != bestCommunity) && (bestCommunity != null)) { |
| theStructure.moveNodeTo(i, bestCommunity); |
| localChange = true; |
| } |
| |
| } |
| |
| someChange = localChange || someChange; |
| |
| } |
| |
| if (someChange) { |
| theStructure.zoomOut(); |
| } |
| } |
| |
| this.fillComStructure(graph, theStructure, comStructure); |
| |
| /* |
| //TODO: uncomment when finalQ will be implemented |
| var degreeCount = this.fillDegreeCount(graph, theStructure, comStructure, nodeDegrees, weighted); |
| |
| |
| var computedModularity = this._finalQ(comStructure, degreeCount, graph, theStructure, totalWeight, 1., weighted); |
| var computedModularityResolution = this._finalQ(comStructure, degreeCount, graph, theStructure, totalWeight, currentResolution, weighted); |
| |
| results["modularity"] = computedModularity; |
| results["modularityResolution"] = computedModularityResolution; |
| */ |
| |
| return results; |
| }; |
| |
| |
| /** |
| * @param {CommunityStructure} theStructure |
| * @param {Number} i |
| * @param {Number} currentResolution |
| * @returns {Community} |
| */ |
| Modularity.prototype.updateBestCommunity = function(theStructure, i, currentResolution) { |
| var best = this.q(i, theStructure.nodeCommunities[i], theStructure, currentResolution); |
| var bestCommunity = theStructure.nodeCommunities[i]; |
| //var /*Set<Community>*/ iter = theStructure.nodeConnectionsWeight[i].keySet(); |
| theStructure.nodeConnectionsWeight[i].forEach(function (_$$val, com) { |
| |
| var qValue = this.q(i, com, theStructure, currentResolution); |
| if (qValue > best) { |
| best = qValue; |
| bestCommunity = com; |
| } |
| |
| }, this); |
| return bestCommunity; |
| }; |
| |
| /** |
| * |
| * @param {IGraph} graph |
| * @param {CommunityStructure} theStructure |
| * @param {Array.<Number>} comStructure |
| * @returns {Array.<Number>} |
| */ |
| Modularity.prototype.fillComStructure = function(graph, theStructure, comStructure) { |
| |
| var count = 0; |
| |
| theStructure.communities.forEach(function (com) { |
| |
| com.nodes.forEach(function (node) { |
| |
| var hidden = theStructure.invMap.get(node); |
| hidden.nodes.forEach( function (nodeInt){ |
| comStructure[nodeInt] = count; |
| }); |
| |
| }); |
| count++; |
| |
| }); |
| |
| |
| return comStructure; |
| }; |
| |
| /** |
| * @param {IGraph} graph |
| * @param {CommunityStructure} theStructure |
| * @param {Array.<Number>} comStructure |
| * @param {Array.<Number>} nodeDegrees |
| * @param {Boolean} weighted |
| * @returns {Array.<Number>} |
| */ |
| Modularity.prototype.fillDegreeCount = function(graph, theStructure, comStructure, nodeDegrees, weighted) { |
| |
| var degreeCount = new Array(theStructure.communities.length); |
| var degreeCentrality = centrality.degree(graph); |
| |
| graph.forEachNode(function(node){ |
| |
| var index = theStructure.map.get(node); |
| if (weighted) { |
| degreeCount[comStructure[index]] += nodeDegrees[index]; |
| } else { |
| degreeCount[comStructure[index]] += degreeCentrality[node.id]; |
| } |
| |
| }); |
| return degreeCount; |
| |
| }; |
| |
| |
| /** |
| * |
| * @param {Array.<Number>} struct |
| * @param {Array.<Number>} degrees |
| * @param {IGraph} graph |
| * @param {CommunityStructure} theStructure |
| * @param {Number} totalWeight |
| * @param {Number} usedResolution |
| * @param {Boolean} weighted |
| * @returns {Number} |
| */ |
| Modularity.prototype._finalQ = function(struct, degrees, graph, theStructure, totalWeight, usedResolution, weighted) { |
| |
| //TODO: rewrite for wighted version of algorithm |
| throw new Error("not implemented properly"); |
| var res = 0; |
| var internal = new Array(degrees.length); |
| |
| graph.forEachNode(function(n){ |
| var n_index = theStructure.map.get(n); |
| |
| graph.forEachLinkedNode(n.id, function(neighbor){ |
| if (n == neighbor) { |
| return; |
| } |
| var neigh_index = theStructure.map.get(neighbor); |
| if (struct[neigh_index] == struct[n_index]) { |
| if (weighted) { |
| //throw new Error("weighted aren't implemented"); |
| //internal[struct[neigh_index]] += graph.getEdge(n, neighbor).getWeight(); |
| } else { |
| internal[struct[neigh_index]]++; |
| } |
| } |
| }.bind(this), false); |
| |
| }.bind(this)); |
| |
| for (var i = 0; i < degrees.length; i++) { |
| internal[i] /= 2.0; |
| res += usedResolution * (internal[i] / totalWeight) - Math.pow(degrees[i] / (2 * totalWeight), 2);//HERE |
| } |
| return res; |
| }; |
| |
| |
| |
| /** |
| * |
| * @param {Number} nodeId |
| * @param {Community} community |
| * @param {CommunityStructure} theStructure |
| * @param {Number} currentResolution |
| * @returns {Number} |
| */ |
| Modularity.prototype.q = function(nodeId, community, theStructure, currentResolution) { |
| |
| var edgesToFloat = theStructure.nodeConnectionsWeight[nodeId].get(community); |
| var edgesTo = 0; |
| if (edgesToFloat != null) { |
| edgesTo = edgesToFloat; |
| } |
| var weightSum = community.weightSum; |
| var nodeWeight = theStructure.weights[nodeId]; |
| var qValue = currentResolution * edgesTo - (nodeWeight * weightSum) / (2.0 * theStructure.graphWeightSum); |
| if ((theStructure.nodeCommunities[nodeId] == community) && (theStructure.nodeCommunities[nodeId].size() > 1)) { |
| qValue = currentResolution * edgesTo - (nodeWeight * (weightSum - nodeWeight)) / (2.0 * theStructure.graphWeightSum); |
| } |
| if ((theStructure.nodeCommunities[nodeId] == community) && (theStructure.nodeCommunities[nodeId].size() == 1)) { |
| qValue = 0.; |
| } |
| return qValue; |
| |
| }; |
| |
| module.exports = Modularity; |
| |
| /***/ }), |
| /* 3 */ |
| /***/ (function(module, exports, __webpack_require__) { |
| |
| "use strict"; |
| |
| var Community = __webpack_require__(4) |
| , ModEdge = __webpack_require__(5) |
| ; |
| |
| /** |
| * |
| * @param {IGraph} graph |
| * @param useWeight |
| * @param {CommunityStructure} structure |
| * @constructor |
| */ |
| function CommunityStructure(graph, useWeight) { |
| |
| //this.graph = graph; |
| this.N = graph.getNodesCount(); |
| this.graphWeightSum = 0; |
| this.structure = this; |
| |
| /** @type {Map.<Number, Community>} */ |
| this.invMap = new Map(); |
| |
| /** @type {Array.< Map.<Community, Number> >} */ |
| this.nodeConnectionsWeight = new Array(this.N); |
| |
| /** @type {Array.< Map.<Community, Number> >} */ |
| this.nodeConnectionsCount = new Array(this.N); |
| |
| /** @type {Array.<Community>} */ |
| this.nodeCommunities = new Array(this.N); |
| |
| /** @type {Map.<Node, Number>} */ |
| this.map = new Map(); |
| |
| /** @type {Array.< Array.<ModEdge> >} */ |
| this.topology = new Array(this.N); |
| for (var i = 0; i < this.N; i++) this.topology[i] = []; |
| |
| /** @type {Array.<Community>} */ |
| this.communities = []; |
| |
| /**@type {Array.<Number>} */ |
| this.weights = new Array(this.N); |
| |
| var index = 0; |
| |
| graph.forEachNode(function (node) { |
| |
| this.map.set(node.id, index); |
| this.nodeCommunities[index] = new Community(this); |
| this.nodeConnectionsWeight[index] = new Map(); |
| this.nodeConnectionsCount[index] = new Map(); |
| this.weights[index] = 0; |
| this.nodeCommunities[index].seed(index); |
| var hidden = new Community(this); |
| hidden.nodes.add(index); |
| this.invMap.set(index, hidden); |
| this.communities.push(this.nodeCommunities[index]); |
| index++; |
| |
| }.bind(this)); |
| |
| |
| graph.forEachLink(function (link) { |
| |
| var node_index = this.map.get(link.fromId) |
| , neighbor_index = this.map.get(link.toId) |
| , weight = 1 |
| ; |
| |
| if (node_index === neighbor_index) { |
| return; |
| } |
| |
| if (useWeight) { |
| weight = link.data.weight; |
| } |
| |
| this.setUpLink(node_index, neighbor_index, weight); |
| this.setUpLink(neighbor_index, node_index, weight); |
| |
| |
| }.bind(this)); |
| |
| |
| this.graphWeightSum /= 2.0; |
| } |
| |
| |
| CommunityStructure.prototype.setUpLink = function (node_index, neighbor_index, weight) { |
| |
| this.weights[node_index] += weight; |
| var /** @type {ModEdge} */ me = new ModEdge(node_index, neighbor_index, weight); |
| this.topology[node_index].push(me); |
| var /** @type {Community} **/ adjCom = this.nodeCommunities[neighbor_index]; |
| this.nodeConnectionsWeight[node_index].set(adjCom, weight); |
| this.nodeConnectionsCount[node_index].set(adjCom, 1); |
| this.nodeCommunities[node_index].connectionsWeight.set(adjCom, weight); |
| this.nodeCommunities[node_index].connectionsCount.set(adjCom, 1); |
| this.nodeConnectionsWeight[neighbor_index].set(this.nodeCommunities[node_index], weight); |
| this.nodeConnectionsCount[neighbor_index].set(this.nodeCommunities[node_index], 1); |
| this.nodeCommunities[neighbor_index].connectionsWeight.set(this.nodeCommunities[node_index], weight); |
| this.nodeCommunities[neighbor_index].connectionsCount.set(this.nodeCommunities[node_index], 1); |
| this.graphWeightSum += weight; |
| |
| }; |
| |
| /** |
| * @param {Number} node |
| * @param {Community} to |
| */ |
| CommunityStructure.prototype.addNodeTo = function (node, to) { |
| |
| to.add(node); |
| this.nodeCommunities[node] = to; |
| |
| var nodeTopology = this.topology[node]; |
| for (var topologyKey in nodeTopology) { |
| |
| //noinspection JSUnfilteredForInLoop |
| var /** @type {ModEdge} */ e = nodeTopology[topologyKey]; |
| |
| var neighbor = e.target; |
| |
| |
| //Remove Node Connection to this community |
| var neighEdgesTo = this.nodeConnectionsWeight[neighbor].get(to); |
| if (neighEdgesTo === undefined) { |
| this.nodeConnectionsWeight[neighbor].set(to, e.weight); |
| } else { |
| this.nodeConnectionsWeight[neighbor].set(to, neighEdgesTo + e.weight); |
| } |
| |
| var neighCountEdgesTo = this.nodeConnectionsCount[neighbor].get(to); |
| if (neighCountEdgesTo === undefined) { |
| this.nodeConnectionsCount[neighbor].set(to, 1); |
| } else { |
| this.nodeConnectionsCount[neighbor].set(to, neighCountEdgesTo + 1); |
| } |
| |
| |
| var /** @type {Community} */ adjCom = this.nodeCommunities[neighbor]; |
| var wEdgesto = adjCom.connectionsWeight.get(to); |
| if (wEdgesto === undefined) { |
| adjCom.connectionsWeight.set(to, e.weight); |
| } else { |
| adjCom.connectionsWeight.set(to, wEdgesto + e.weight); |
| } |
| |
| var cEdgesto = adjCom.connectionsCount.get(to); |
| if (cEdgesto === undefined) { |
| adjCom.connectionsCount.set(to, 1); |
| } else { |
| adjCom.connectionsCount.set(to, cEdgesto + 1); |
| } |
| |
| var nodeEdgesTo = this.nodeConnectionsWeight[node].get(adjCom); |
| if (nodeEdgesTo === undefined) { |
| this.nodeConnectionsWeight[node].set(adjCom, e.weight); |
| } else { |
| this.nodeConnectionsWeight[node].set(adjCom, nodeEdgesTo + e.weight); |
| } |
| |
| var nodeCountEdgesTo = this.nodeConnectionsCount[node].get(adjCom); |
| if (nodeCountEdgesTo === undefined) { |
| this.nodeConnectionsCount[node].set(adjCom, 1); |
| } else { |
| this.nodeConnectionsCount[node].set(adjCom, nodeCountEdgesTo + 1); |
| } |
| |
| if (to != adjCom) { |
| var comEdgesto = to.connectionsWeight.get(adjCom); |
| if (comEdgesto === undefined) { |
| to.connectionsWeight.set(adjCom, e.weight); |
| } else { |
| to.connectionsWeight.set(adjCom, comEdgesto + e.weight); |
| } |
| |
| var comCountEdgesto = to.connectionsCount.get(adjCom); |
| if (comCountEdgesto === undefined) { |
| to.connectionsCount.set(adjCom, 1); |
| } else { |
| to.connectionsCount.set(adjCom, comCountEdgesto + 1); |
| } |
| |
| } |
| } |
| }; |
| |
| /** |
| * @param {Number} node |
| * @param {Community} source |
| */ |
| CommunityStructure.prototype.removeNodeFrom = function (node, source) { |
| |
| var community = this.nodeCommunities[node]; |
| |
| |
| var nodeTopology = this.topology[node]; |
| for (var topologyKey in nodeTopology) { |
| |
| //noinspection JSUnfilteredForInLoop |
| var /** @type {ModEdge} */ e = nodeTopology[topologyKey]; |
| |
| var neighbor = e.target; |
| |
| //Remove Node Connection to this community |
| var edgesTo = this.nodeConnectionsWeight[neighbor].get(community); |
| var countEdgesTo = this.nodeConnectionsCount[neighbor].get(community); |
| |
| if ((countEdgesTo - 1) == 0) { |
| this.nodeConnectionsWeight[neighbor].delete(community); |
| this.nodeConnectionsCount[neighbor].delete(community); |
| } else { |
| this.nodeConnectionsWeight[neighbor].set(community, edgesTo - e.weight); |
| this.nodeConnectionsCount[neighbor].set(community, countEdgesTo - 1); |
| } |
| |
| |
| //Remove Adjacency Community's connection to this community |
| var adjCom = this.nodeCommunities[neighbor]; |
| var oEdgesto = adjCom.connectionsWeight.get(community); |
| var oCountEdgesto = adjCom.connectionsCount.get(community); |
| if ((oCountEdgesto - 1) == 0) { |
| adjCom.connectionsWeight.delete(community); |
| adjCom.connectionsCount.delete(community); |
| } else { |
| adjCom.connectionsWeight.set(community, oEdgesto - e.weight); |
| adjCom.connectionsCount.set(community, oCountEdgesto - 1); |
| } |
| |
| if (node == neighbor) { |
| continue; |
| } |
| |
| if (adjCom != community) { |
| |
| var comEdgesto = community.connectionsWeight.get(adjCom); |
| var comCountEdgesto = community.connectionsCount.get(adjCom); |
| |
| if (comCountEdgesto - 1 == 0) { |
| community.connectionsWeight.delete(adjCom); |
| community.connectionsCount.delete(adjCom); |
| } else { |
| community.connectionsWeight.set(adjCom, comEdgesto - e.weight); |
| community.connectionsCount.set(adjCom, comCountEdgesto - 1); |
| } |
| |
| } |
| |
| var nodeEdgesTo = this.nodeConnectionsWeight[node].get(adjCom); |
| var nodeCountEdgesTo = this.nodeConnectionsCount[node].get(adjCom); |
| |
| if ((nodeCountEdgesTo - 1) == 0) { |
| this.nodeConnectionsWeight[node].delete(adjCom); |
| this.nodeConnectionsCount[node].delete(adjCom); |
| } else { |
| this.nodeConnectionsWeight[node].set(adjCom, nodeEdgesTo - e.weight); |
| this.nodeConnectionsCount[node].set(adjCom, nodeCountEdgesTo - 1); |
| } |
| |
| } |
| |
| source.remove(node); |
| }; |
| |
| /** |
| * @param {Number} node |
| * @param {Community} to |
| */ |
| CommunityStructure.prototype.moveNodeTo = function (node, to) { |
| |
| var source = this.nodeCommunities[node]; |
| this.removeNodeFrom(node, source); |
| this.addNodeTo(node, to); |
| |
| }; |
| |
| |
| CommunityStructure.prototype.zoomOut = function () { |
| var realCommunities = this.communities.reduce(function (arr, value) { |
| arr.push(value); |
| return arr; |
| }, []); |
| var M = realCommunities.length; // size |
| var /** @type Array.< Array.<ModEdge> > */ newTopology = new Array(M); |
| var index = 0; |
| |
| this.nodeCommunities = new Array(M); |
| this.nodeConnectionsWeight = new Array(M); |
| this.nodeConnectionsCount = new Array(M); |
| |
| var /** @type Map.<Number, Community>*/ newInvMap = new Map(); |
| realCommunities.forEach(function (com) { |
| |
| var weightSum = 0; |
| this.nodeConnectionsWeight[index] = new Map(); |
| this.nodeConnectionsCount[index] = new Map(); |
| newTopology[index] = []; |
| this.nodeCommunities[index] = new Community(com); |
| //var iter = com.connectionsWeight.keySet(); |
| |
| var hidden = new Community(this.structure); |
| |
| com.nodes.forEach(function (nodeInt) { |
| |
| var oldHidden = this.invMap.get(nodeInt); |
| oldHidden.nodes.forEach(hidden.nodes.add.bind(hidden.nodes)); |
| |
| }, this); |
| |
| newInvMap.set(index, hidden); |
| com.connectionsWeight.forEach(function (weight, adjCom) { |
| |
| var target = realCommunities.indexOf(adjCom); |
| if (!~target) return; |
| if (target == index) { |
| weightSum += 2. * weight; |
| } else { |
| weightSum += weight; |
| } |
| var e = new ModEdge(index, target, weight); |
| newTopology[index].push(e); |
| |
| }, this); |
| |
| this.weights[index] = weightSum; |
| this.nodeCommunities[index].seed(index); |
| |
| index++; |
| |
| }.bind(this)); |
| |
| this.communities = []; |
| |
| for (var i = 0; i < M; i++) { |
| var com = this.nodeCommunities[i]; |
| this.communities.push(com); |
| for (var ei in newTopology[i]) { |
| //noinspection JSUnfilteredForInLoop |
| var e = newTopology[i][ei]; |
| this.nodeConnectionsWeight[i].set(this.nodeCommunities[e.target], e.weight); |
| this.nodeConnectionsCount[i].set(this.nodeCommunities[e.target], 1); |
| com.connectionsWeight.set(this.nodeCommunities[e.target], e.weight); |
| com.connectionsCount.set(this.nodeCommunities[e.target], 1); |
| } |
| |
| } |
| |
| this.N = M; |
| this.topology = newTopology; |
| this.invMap = newInvMap; |
| |
| }; |
| |
| module.exports = CommunityStructure; |
| |
| /***/ }), |
| /* 4 */ |
| /***/ (function(module, exports) { |
| |
| /** |
| * @param {CommunityStructure|Community} com |
| * @constructor |
| */ |
| function Community(com) { |
| |
| /** @type {CommunityStructure} */ |
| this.structure = com.structure ? com.structure : com; |
| |
| /** @type {Map.<Community, Number>} */ |
| this.connectionsWeight = new Map(); |
| |
| /** @type {Map.<Community, Number>} */ |
| this.connectionsCount = new Map(); |
| |
| /** @type {Set.<Number>} */ |
| this.nodes = new Set; |
| |
| this.weightSum = 0; |
| |
| |
| } |
| |
| /** |
| * @public |
| * @returns {Number} |
| */ |
| Community.prototype.size = function() { |
| return this.nodes.size; |
| }; |
| |
| |
| /** |
| * @param {Number} node |
| */ |
| Community.prototype.seed = function(node) { |
| |
| this.nodes.add(node); |
| this.weightSum += this.structure.weights[node]; |
| |
| }; |
| |
| /** |
| * @param {Number} nodeId |
| * @returns {boolean} |
| */ |
| Community.prototype.add = function(nodeId) { |
| |
| this.nodes.add(nodeId); |
| this.weightSum += this.structure.weights[nodeId]; |
| return true; |
| |
| }; |
| |
| /** |
| * @param {Number} node |
| * @returns {boolean} |
| */ |
| Community.prototype.remove = function(node) { |
| |
| var result = this.nodes.delete(node); |
| |
| this.weightSum -= this.structure.weights[node]; |
| if (!this.nodes.size) { |
| var index = this.structure.communities.indexOf(this); |
| delete this.structure.communities[index]; |
| } |
| |
| return result; |
| }; |
| |
| module.exports = Community; |
| |
| |
| /***/ }), |
| /* 5 */ |
| /***/ (function(module, exports) { |
| |
| /** |
| * |
| * @param s |
| * @param t |
| * @param w |
| * @constructor |
| */ |
| function ModEdge(s, t, w) { |
| /** @type {Number} */ |
| this.source = s; |
| /** @type {Number} */ |
| this.target = t; |
| /** @type {Number} */ |
| this.weight = w; |
| } |
| |
| module.exports = ModEdge; |
| |
| |
| /***/ }), |
| /* 6 */ |
| /***/ (function(module, exports, __webpack_require__) { |
| |
| module.exports.degree = __webpack_require__(7); |
| module.exports.betweenness = __webpack_require__(8); |
| |
| |
| /***/ }), |
| /* 7 */ |
| /***/ (function(module, exports) { |
| |
| module.exports = degree; |
| |
| /** |
| * Calculates graph nodes degree centrality (in/out or both). |
| * |
| * @see http://en.wikipedia.org/wiki/Centrality#Degree_centrality |
| * |
| * @param {ngraph.graph} graph object for which we are calculating centrality. |
| * @param {string} [kind=both] What kind of degree centrality needs to be calculated: |
| * 'in' - calculate in-degree centrality |
| * 'out' - calculate out-degree centrality |
| * 'inout' - (default) generic degree centrality is calculated |
| */ |
| function degree(graph, kind) { |
| var getNodeDegree, |
| sortedDegrees = [], |
| result = Object.create(null), |
| nodeDegree; |
| |
| kind = (kind || 'both').toLowerCase(); |
| if (kind === 'both' || kind === 'inout') { |
| getNodeDegree = inoutDegreeCalculator; |
| } else if (kind === 'in') { |
| getNodeDegree = inDegreeCalculator; |
| } else if (kind === 'out') { |
| getNodeDegree = outDegreeCalculator; |
| } else { |
| throw new Error('Expected centrality degree kind is: in, out or both'); |
| } |
| |
| graph.forEachNode(calculateNodeDegree); |
| |
| return result; |
| |
| function calculateNodeDegree(node) { |
| var links = graph.getLinks(node.id); |
| result[node.id] = getNodeDegree(links, node.id); |
| } |
| } |
| |
| function inDegreeCalculator(links, nodeId) { |
| var total = 0; |
| if (!links) return total; |
| |
| for (var i = 0; i < links.length; i += 1) { |
| total += (links[i].toId === nodeId) ? 1 : 0; |
| } |
| return total; |
| } |
| |
| function outDegreeCalculator(links, nodeId) { |
| var total = 0; |
| if (!links) return total; |
| |
| for (var i = 0; i < links.length; i += 1) { |
| total += (links[i].fromId === nodeId) ? 1 : 0; |
| } |
| return total; |
| } |
| |
| function inoutDegreeCalculator(links) { |
| if (!links) return 0; |
| |
| return links.length; |
| } |
| |
| |
| /***/ }), |
| /* 8 */ |
| /***/ (function(module, exports) { |
| |
| module.exports = betweennes; |
| |
| /** |
| * I'm using http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf |
| * as a reference for this implementation |
| */ |
| function betweennes(graph, oriented) { |
| var Q = [], |
| S = []; // Queue and Stack |
| // list of predcessors on shorteest paths from source |
| var pred = Object.create(null); |
| // distance from source |
| var dist = Object.create(null); |
| // number of shortest paths from source to key |
| var sigma = Object.create(null); |
| // dependency of source on key |
| var delta = Object.create(null); |
| |
| var currentNode; |
| var centrality = Object.create(null); |
| |
| graph.forEachNode(setCentralityToZero); |
| graph.forEachNode(calculateCentrality); |
| |
| if (!oriented) { |
| // The centrality scores need to be divided by two if the graph is not oriented, |
| // since all shortest paths are considered twice |
| Object.keys(centrality).forEach(divideByTwo); |
| } |
| |
| return centrality; |
| |
| function divideByTwo(key) { |
| centrality[key] /= 2; |
| } |
| |
| function setCentralityToZero(node) { |
| centrality[node.id] = 0; |
| } |
| |
| function calculateCentrality(node) { |
| currentNode = node.id; |
| singleSourceShortestPath(currentNode); |
| accumulate(); |
| } |
| |
| function accumulate() { |
| graph.forEachNode(setDeltaToZero); |
| while (S.length) { |
| var w = S.pop(); |
| var coeff = (1 + delta[w])/sigma[w]; |
| var predcessors = pred[w]; |
| for (var idx = 0; idx < predcessors.length; ++idx) { |
| var v = predcessors[idx]; |
| delta[v] += sigma[v] * coeff; |
| } |
| if (w !== currentNode) { |
| centrality[w] += delta[w]; |
| } |
| } |
| } |
| |
| function setDeltaToZero(node) { |
| delta[node.id] = 0; |
| } |
| |
| function singleSourceShortestPath(source) { |
| graph.forEachNode(initNode); |
| dist[source] = 0; |
| sigma[source] = 1; |
| Q.push(source); |
| |
| while (Q.length) { |
| var v = Q.shift(); |
| var dedup = Object.create(null); |
| S.push(v); |
| graph.forEachLinkedNode(v, toId, oriented); |
| } |
| |
| function toId(otherNode) { |
| // NOTE: This code will also consider multi-edges, which are often |
| // ignored by popular software (Gephi/NetworkX). Depending on your use |
| // case this may not be desired and deduping needs to be performed. To |
| // save memory I'm not deduping here... |
| processNode(otherNode.id); |
| } |
| |
| function initNode(node) { |
| var nodeId = node.id; |
| pred[nodeId] = []; // empty list |
| dist[nodeId] = -1; |
| sigma[nodeId] = 0; |
| } |
| |
| function processNode(w) { |
| // path discovery |
| if (dist[w] === -1) { |
| // Node w is found for the first time |
| dist[w] = dist[v] + 1; |
| Q.push(w); |
| } |
| // path counting |
| if (dist[w] === dist[v] + 1) { |
| // edge (v, w) on a shortest path |
| sigma[w] += sigma[v]; |
| pred[w].push(v); |
| } |
| } |
| } |
| } |
| |
| |
| /***/ }), |
| /* 9 */ |
| /***/ (function(module, exports) { |
| |
| module.exports = __WEBPACK_EXTERNAL_MODULE_9__; |
| |
| /***/ }), |
| /* 10 */ |
| /***/ (function(module, exports, __webpack_require__) { |
| |
| /** |
| * @fileOverview Contains definition of the core graph object. |
| */ |
| |
| /** |
| * @example |
| * var graph = require('ngraph.graph')(); |
| * graph.addNode(1); // graph has one node. |
| * graph.addLink(2, 3); // now graph contains three nodes and one link. |
| * |
| */ |
| module.exports = createGraph; |
| |
| var eventify = __webpack_require__(11); |
| |
| /** |
| * Creates a new graph |
| */ |
| function createGraph(options) { |
| // Graph structure is maintained as dictionary of nodes |
| // and array of links. Each node has 'links' property which |
| // hold all links related to that node. And general links |
| // array is used to speed up all links enumeration. This is inefficient |
| // in terms of memory, but simplifies coding. |
| options = options || {}; |
| if (options.uniqueLinkId === undefined) { |
| // Request each link id to be unique between same nodes. This negatively |
| // impacts `addLink()` performance (O(n), where n - number of edges of each |
| // vertex), but makes operations with multigraphs more accessible. |
| options.uniqueLinkId = true; |
| } |
| |
| var nodes = typeof Object.create === 'function' ? Object.create(null) : {}, |
| links = [], |
| // Hash of multi-edges. Used to track ids of edges between same nodes |
| multiEdges = {}, |
| nodesCount = 0, |
| suspendEvents = 0, |
| |
| forEachNode = createNodeIterator(), |
| createLink = options.uniqueLinkId ? createUniqueLink : createSingleLink, |
| |
| // Our graph API provides means to listen to graph changes. Users can subscribe |
| // to be notified about changes in the graph by using `on` method. However |
| // in some cases they don't use it. To avoid unnecessary memory consumption |
| // we will not record graph changes until we have at least one subscriber. |
| // Code below supports this optimization. |
| // |
| // Accumulates all changes made during graph updates. |
| // Each change element contains: |
| // changeType - one of the strings: 'add', 'remove' or 'update'; |
| // node - if change is related to node this property is set to changed graph's node; |
| // link - if change is related to link this property is set to changed graph's link; |
| changes = [], |
| recordLinkChange = noop, |
| recordNodeChange = noop, |
| enterModification = noop, |
| exitModification = noop; |
| |
| // this is our public API: |
| var graphPart = { |
| /** |
| * Adds node to the graph. If node with given id already exists in the graph |
| * its data is extended with whatever comes in 'data' argument. |
| * |
| * @param nodeId the node's identifier. A string or number is preferred. |
| * @param [data] additional data for the node being added. If node already |
| * exists its data object is augmented with the new one. |
| * |
| * @return {node} The newly added node or node with given id if it already exists. |
| */ |
| addNode: addNode, |
| |
| /** |
| * Adds a link to the graph. The function always create a new |
| * link between two nodes. If one of the nodes does not exists |
| * a new node is created. |
| * |
| * @param fromId link start node id; |
| * @param toId link end node id; |
| * @param [data] additional data to be set on the new link; |
| * |
| * @return {link} The newly created link |
| */ |
| addLink: addLink, |
| |
| /** |
| * Removes link from the graph. If link does not exist does nothing. |
| * |
| * @param link - object returned by addLink() or getLinks() methods. |
| * |
| * @returns true if link was removed; false otherwise. |
| */ |
| removeLink: removeLink, |
| |
| /** |
| * Removes node with given id from the graph. If node does not exist in the graph |
| * does nothing. |
| * |
| * @param nodeId node's identifier passed to addNode() function. |
| * |
| * @returns true if node was removed; false otherwise. |
| */ |
| removeNode: removeNode, |
| |
| /** |
| * Gets node with given identifier. If node does not exist undefined value is returned. |
| * |
| * @param nodeId requested node identifier; |
| * |
| * @return {node} in with requested identifier or undefined if no such node exists. |
| */ |
| getNode: getNode, |
| |
| /** |
| * Gets number of nodes in this graph. |
| * |
| * @return number of nodes in the graph. |
| */ |
| getNodesCount: function() { |
| return nodesCount; |
| }, |
| |
| /** |
| * Gets total number of links in the graph. |
| */ |
| getLinksCount: function() { |
| return links.length; |
| }, |
| |
| /** |
| * Gets all links (inbound and outbound) from the node with given id. |
| * If node with given id is not found null is returned. |
| * |
| * @param nodeId requested node identifier. |
| * |
| * @return Array of links from and to requested node if such node exists; |
| * otherwise null is returned. |
| */ |
| getLinks: getLinks, |
| |
| /** |
| * Invokes callback on each node of the graph. |
| * |
| * @param {Function(node)} callback Function to be invoked. The function |
| * is passed one argument: visited node. |
| */ |
| forEachNode: forEachNode, |
| |
| /** |
| * Invokes callback on every linked (adjacent) node to the given one. |
| * |
| * @param nodeId Identifier of the requested node. |
| * @param {Function(node, link)} callback Function to be called on all linked nodes. |
| * The function is passed two parameters: adjacent node and link object itself. |
| * @param oriented if true graph treated as oriented. |
| */ |
| forEachLinkedNode: forEachLinkedNode, |
| |
| /** |
| * Enumerates all links in the graph |
| * |
| * @param {Function(link)} callback Function to be called on all links in the graph. |
| * The function is passed one parameter: graph's link object. |
| * |
| * Link object contains at least the following fields: |
| * fromId - node id where link starts; |
| * toId - node id where link ends, |
| * data - additional data passed to graph.addLink() method. |
| */ |
| forEachLink: forEachLink, |
| |
| /** |
| * Suspend all notifications about graph changes until |
| * endUpdate is called. |
| */ |
| beginUpdate: enterModification, |
| |
| /** |
| * Resumes all notifications about graph changes and fires |
| * graph 'changed' event in case there are any pending changes. |
| */ |
| endUpdate: exitModification, |
| |
| /** |
| * Removes all nodes and links from the graph. |
| */ |
| clear: clear, |
| |
| /** |
| * Detects whether there is a link between two nodes. |
| * Operation complexity is O(n) where n - number of links of a node. |
| * NOTE: this function is synonim for getLink() |
| * |
| * @returns link if there is one. null otherwise. |
| */ |
| hasLink: getLink, |
| |
| /** |
| * Gets an edge between two nodes. |
| * Operation complexity is O(n) where n - number of links of a node. |
| * |
| * @param {string} fromId link start identifier |
| * @param {string} toId link end identifier |
| * |
| * @returns link if there is one. null otherwise. |
| */ |
| getLink: getLink |
| }; |
| |
| // this will add `on()` and `fire()` methods. |
| eventify(graphPart); |
| |
| monitorSubscribers(); |
| |
| return graphPart; |
| |
| function monitorSubscribers() { |
| var realOn = graphPart.on; |
| |
| // replace real `on` with our temporary on, which will trigger change |
| // modification monitoring: |
| graphPart.on = on; |
| |
| function on() { |
| // now it's time to start tracking stuff: |
| graphPart.beginUpdate = enterModification = enterModificationReal; |
| graphPart.endUpdate = exitModification = exitModificationReal; |
| recordLinkChange = recordLinkChangeReal; |
| recordNodeChange = recordNodeChangeReal; |
| |
| // this will replace current `on` method with real pub/sub from `eventify`. |
| graphPart.on = realOn; |
| // delegate to real `on` handler: |
| return realOn.apply(graphPart, arguments); |
| } |
| } |
| |
| function recordLinkChangeReal(link, changeType) { |
| changes.push({ |
| link: link, |
| changeType: changeType |
| }); |
| } |
| |
| function recordNodeChangeReal(node, changeType) { |
| changes.push({ |
| node: node, |
| changeType: changeType |
| }); |
| } |
| |
| function addNode(nodeId, data) { |
| if (nodeId === undefined) { |
| throw new Error('Invalid node identifier'); |
| } |
| |
| enterModification(); |
| |
| var node = getNode(nodeId); |
| if (!node) { |
| node = new Node(nodeId); |
| nodesCount++; |
| recordNodeChange(node, 'add'); |
| } else { |
| recordNodeChange(node, 'update'); |
| } |
| |
| node.data = data; |
| |
| nodes[nodeId] = node; |
| |
| exitModification(); |
| return node; |
| } |
| |
| function getNode(nodeId) { |
| return nodes[nodeId]; |
| } |
| |
| function removeNode(nodeId) { |
| var node = getNode(nodeId); |
| if (!node) { |
| return false; |
| } |
| |
| enterModification(); |
| |
| if (node.links) { |
| while (node.links.length) { |
| var link = node.links[0]; |
| removeLink(link); |
| } |
| } |
| |
| delete nodes[nodeId]; |
| nodesCount--; |
| |
| recordNodeChange(node, 'remove'); |
| |
| exitModification(); |
| |
| return true; |
| } |
| |
| |
| function addLink(fromId, toId, data) { |
| enterModification(); |
| |
| var fromNode = getNode(fromId) || addNode(fromId); |
| var toNode = getNode(toId) || addNode(toId); |
| |
| var link = createLink(fromId, toId, data); |
| |
| links.push(link); |
| |
| // TODO: this is not cool. On large graphs potentially would consume more memory. |
| addLinkToNode(fromNode, link); |
| if (fromId !== toId) { |
| // make sure we are not duplicating links for self-loops |
| addLinkToNode(toNode, link); |
| } |
| |
| recordLinkChange(link, 'add'); |
| |
| exitModification(); |
| |
| return link; |
| } |
| |
| function createSingleLink(fromId, toId, data) { |
| var linkId = makeLinkId(fromId, toId); |
| return new Link(fromId, toId, data, linkId); |
| } |
| |
| function createUniqueLink(fromId, toId, data) { |
| // TODO: Get rid of this method. |
| var linkId = makeLinkId(fromId, toId); |
| var isMultiEdge = multiEdges.hasOwnProperty(linkId); |
| if (isMultiEdge || getLink(fromId, toId)) { |
| if (!isMultiEdge) { |
| multiEdges[linkId] = 0; |
| } |
| var suffix = '@' + (++multiEdges[linkId]); |
| linkId = makeLinkId(fromId + suffix, toId + suffix); |
| } |
| |
| return new Link(fromId, toId, data, linkId); |
| } |
| |
| function getLinks(nodeId) { |
| var node = getNode(nodeId); |
| return node ? node.links : null; |
| } |
| |
| function removeLink(link) { |
| if (!link) { |
| return false; |
| } |
| var idx = indexOfElementInArray(link, links); |
| if (idx < 0) { |
| return false; |
| } |
| |
| enterModification(); |
| |
| links.splice(idx, 1); |
| |
| var fromNode = getNode(link.fromId); |
| var toNode = getNode(link.toId); |
| |
| if (fromNode) { |
| idx = indexOfElementInArray(link, fromNode.links); |
| if (idx >= 0) { |
| fromNode.links.splice(idx, 1); |
| } |
| } |
| |
| if (toNode) { |
| idx = indexOfElementInArray(link, toNode.links); |
| if (idx >= 0) { |
| toNode.links.splice(idx, 1); |
| } |
| } |
| |
| recordLinkChange(link, 'remove'); |
| |
| exitModification(); |
| |
| return true; |
| } |
| |
| function getLink(fromNodeId, toNodeId) { |
| // TODO: Use sorted links to speed this up |
| var node = getNode(fromNodeId), |
| i; |
| if (!node || !node.links) { |
| return null; |
| } |
| |
| for (i = 0; i < node.links.length; ++i) { |
| var link = node.links[i]; |
| if (link.fromId === fromNodeId && link.toId === toNodeId) { |
| return link; |
| } |
| } |
| |
| return null; // no link. |
| } |
| |
| function clear() { |
| enterModification(); |
| forEachNode(function(node) { |
| removeNode(node.id); |
| }); |
| exitModification(); |
| } |
| |
| function forEachLink(callback) { |
| var i, length; |
| if (typeof callback === 'function') { |
| for (i = 0, length = links.length; i < length; ++i) { |
| callback(links[i]); |
| } |
| } |
| } |
| |
| function forEachLinkedNode(nodeId, callback, oriented) { |
| var node = getNode(nodeId); |
| |
| if (node && node.links && typeof callback === 'function') { |
| if (oriented) { |
| return forEachOrientedLink(node.links, nodeId, callback); |
| } else { |
| return forEachNonOrientedLink(node.links, nodeId, callback); |
| } |
| } |
| } |
| |
| function forEachNonOrientedLink(links, nodeId, callback) { |
| var quitFast; |
| for (var i = 0; i < links.length; ++i) { |
| var link = links[i]; |
| var linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId; |
| |
| quitFast = callback(nodes[linkedNodeId], link); |
| if (quitFast) { |
| return true; // Client does not need more iterations. Break now. |
| } |
| } |
| } |
| |
| function forEachOrientedLink(links, nodeId, callback) { |
| var quitFast; |
| for (var i = 0; i < links.length; ++i) { |
| var link = links[i]; |
| if (link.fromId === nodeId) { |
| quitFast = callback(nodes[link.toId], link); |
| if (quitFast) { |
| return true; // Client does not need more iterations. Break now. |
| } |
| } |
| } |
| } |
| |
| // we will not fire anything until users of this library explicitly call `on()` |
| // method. |
| function noop() {} |
| |
| // Enter, Exit modification allows bulk graph updates without firing events. |
| function enterModificationReal() { |
| suspendEvents += 1; |
| } |
| |
| function exitModificationReal() { |
| suspendEvents -= 1; |
| if (suspendEvents === 0 && changes.length > 0) { |
| graphPart.fire('changed', changes); |
| changes.length = 0; |
| } |
| } |
| |
| function createNodeIterator() { |
| // Object.keys iterator is 1.3x faster than `for in` loop. |
| // See `https://github.com/anvaka/ngraph.graph/tree/bench-for-in-vs-obj-keys` |
| // branch for perf test |
| return Object.keys ? objectKeysIterator : forInIterator; |
| } |
| |
| function objectKeysIterator(callback) { |
| if (typeof callback !== 'function') { |
| return; |
| } |
| |
| var keys = Object.keys(nodes); |
| for (var i = 0; i < keys.length; ++i) { |
| if (callback(nodes[keys[i]])) { |
| return true; // client doesn't want to proceed. Return. |
| } |
| } |
| } |
| |
| function forInIterator(callback) { |
| if (typeof callback !== 'function') { |
| return; |
| } |
| var node; |
| |
| for (node in nodes) { |
| if (callback(nodes[node])) { |
| return true; // client doesn't want to proceed. Return. |
| } |
| } |
| } |
| } |
| |
| // need this for old browsers. Should this be a separate module? |
| function indexOfElementInArray(element, array) { |
| if (!array) return -1; |
| |
| if (array.indexOf) { |
| return array.indexOf(element); |
| } |
| |
| var len = array.length, |
| i; |
| |
| for (i = 0; i < len; i += 1) { |
| if (array[i] === element) { |
| return i; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Internal structure to represent node; |
| */ |
| function Node(id) { |
| this.id = id; |
| this.links = null; |
| this.data = null; |
| } |
| |
| function addLinkToNode(node, link) { |
| if (node.links) { |
| node.links.push(link); |
| } else { |
| node.links = [link]; |
| } |
| } |
| |
| /** |
| * Internal structure to represent links; |
| */ |
| function Link(fromId, toId, data, id) { |
| this.fromId = fromId; |
| this.toId = toId; |
| this.data = data; |
| this.id = id; |
| } |
| |
| function hashCode(str) { |
| var hash = 0, i, chr, len; |
| if (str.length == 0) return hash; |
| for (i = 0, len = str.length; i < len; i++) { |
| chr = str.charCodeAt(i); |
| hash = ((hash << 5) - hash) + chr; |
| hash |= 0; // Convert to 32bit integer |
| } |
| return hash; |
| } |
| |
| function makeLinkId(fromId, toId) { |
| return hashCode(fromId.toString() + '👉 ' + toId.toString()); |
| } |
| |
| |
| /***/ }), |
| /* 11 */ |
| /***/ (function(module, exports) { |
| |
| module.exports = function(subject) { |
| validateSubject(subject); |
| |
| var eventsStorage = createEventsStorage(subject); |
| subject.on = eventsStorage.on; |
| subject.off = eventsStorage.off; |
| subject.fire = eventsStorage.fire; |
| return subject; |
| }; |
| |
| function createEventsStorage(subject) { |
| // Store all event listeners to this hash. Key is event name, value is array |
| // of callback records. |
| // |
| // A callback record consists of callback function and its optional context: |
| // { 'eventName' => [{callback: function, ctx: object}] } |
| var registeredEvents = Object.create(null); |
| |
| return { |
| on: function (eventName, callback, ctx) { |
| if (typeof callback !== 'function') { |
| throw new Error('callback is expected to be a function'); |
| } |
| var handlers = registeredEvents[eventName]; |
| if (!handlers) { |
| handlers = registeredEvents[eventName] = []; |
| } |
| handlers.push({callback: callback, ctx: ctx}); |
| |
| return subject; |
| }, |
| |
| off: function (eventName, callback) { |
| var wantToRemoveAll = (typeof eventName === 'undefined'); |
| if (wantToRemoveAll) { |
| // Killing old events storage should be enough in this case: |
| registeredEvents = Object.create(null); |
| return subject; |
| } |
| |
| if (registeredEvents[eventName]) { |
| var deleteAllCallbacksForEvent = (typeof callback !== 'function'); |
| if (deleteAllCallbacksForEvent) { |
| delete registeredEvents[eventName]; |
| } else { |
| var callbacks = registeredEvents[eventName]; |
| for (var i = 0; i < callbacks.length; ++i) { |
| if (callbacks[i].callback === callback) { |
| callbacks.splice(i, 1); |
| } |
| } |
| } |
| } |
| |
| return subject; |
| }, |
| |
| fire: function (eventName) { |
| var callbacks = registeredEvents[eventName]; |
| if (!callbacks) { |
| return subject; |
| } |
| |
| var fireArguments; |
| if (arguments.length > 1) { |
| fireArguments = Array.prototype.splice.call(arguments, 1); |
| } |
| for(var i = 0; i < callbacks.length; ++i) { |
| var callbackInfo = callbacks[i]; |
| callbackInfo.callback.apply(callbackInfo.ctx, fireArguments); |
| } |
| |
| return subject; |
| } |
| }; |
| } |
| |
| function validateSubject(subject) { |
| if (!subject) { |
| throw new Error('Eventify cannot use falsy object as events subject'); |
| } |
| var reservedWords = ['on', 'fire', 'off']; |
| for (var i = 0; i < reservedWords.length; ++i) { |
| if (subject.hasOwnProperty(reservedWords[i])) { |
| throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'"); |
| } |
| } |
| } |
| |
| |
| /***/ }) |
| /******/ ]); |
| }); |