All Downloads are FREE. Search and download functionalities are using the official Maven repository.

resources.alchemy.js Maven / Gradle / Ivy

(function() {
  "Alchemy.js is a graph drawing application for the web.\nCopyright (C) 2014  GraphAlchemist, Inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU Affero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with this program.  If not, see .\nlets";
  var API, Alchemy, Clustering, DrawEdge, DrawEdges, DrawNode, DrawNodes, Editor, EditorInteractions, EditorUtils, Layout, Remove, root, warnings,
    __slice = [].slice,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  Alchemy = (function() {
    function Alchemy(userConf) {
      if (userConf == null) {
        userConf = null;
      }
      this.a = this;
      this.version = "0.4.2";
      this.api = new API(this);
      this.get = this.api.get;
      this.create = this.api.create;
      this.set = this.api.set;
      this.filter = this.api.filter;
      this.forceLayout = this.api.force;
      this.drawing = {
        DrawEdge: DrawEdge(this),
        DrawEdges: DrawEdges(this),
        DrawNode: DrawNode(this),
        DrawNodes: DrawNodes(this),
        NodeUtils: this.NodeUtils(this)
      };
      this.controlDash = this.controlDash(this);
      this.stats = this.stats(this);
      this.search = this.search(this);
      this.layout = Layout;
      this.clustering = Clustering;
      this.models = {
        Node: this.Node(this),
        Edge: this.Edge(this)
      };
      this.utils = {
        warnings: new warnings(this)
      };
      this.filters = this.filters(this);
      this.exports = this.exports(this);
      this.visControls = {};
      this.styles = {};
      this.editor = {};
      this.log = {};
      this.state = {
        "interactions": "default",
        "layout": "default"
      };
      this.startGraph = this.startGraph(this);
      this.updateGraph = this.updateGraph(this);
      this.generateLayout = this.generateLayout(this);
      this.svgStyles = this.svgStyles(this);
      this.interactions = this.interactions(this);
      this.plugins = this.plugins(this);
      this._nodes = {};
      this._edges = {};
      this.getNodes = this.get.getNodes;
      this.getEdges = this.get.getEdges;
      this.allNodes = this.get.allNodes;
      this.allEdges = this.get.allEdges;
      if (userConf) {
        this.begin(userConf);
      }
    }

    Alchemy.prototype.begin = function(userConf) {
      var conf;
      conf = this.setConf(userConf);
	  
	  Alchemy.prototype.instances.push(this);
	  
      switch (typeof this.conf.dataSource) {
        case 'string':
          d3.json(this.a.conf.dataSource, this.a.startGraph);
          break;
        case 'object':
          this.a.startGraph(this.a.conf.dataSource);
      }
      this.plugins.init();
      
      return this;
    };

    Alchemy.prototype.setConf = function(userConf) {
      var key, val;
      if (userConf.theme != null) {
        userConf = _.merge(_.cloneDeep(this.defaults), this.a.themes["" + userConf.theme]);
      }
      for (key in userConf) {
        val = userConf[key];
        switch (key) {
          case "clusterColors":
            userConf["clusterColours"] = val;
            break;
          case "backgroundColor":
            userConf["backgroundColour"] = val;
            break;
          case "nodeColor":
            userConf[nodeColour] = val;
        }
      }
      return this.a.conf = _.merge(_.cloneDeep(this.defaults), userConf);
    };

    Alchemy.prototype.instances = [];

    Alchemy.prototype.getInst = function(svg) {
      var instNumber;
      instNumber = parseInt(d3.select(svg).attr("alchInst"));
      return Alchemy.prototype.instances[instNumber];
    };

    return Alchemy;

  })();

  root = typeof exports !== "undefined" && exports !== null ? exports : this;

  root.Alchemy = Alchemy;

  root.alchemy = {
    begin: function(config) {
      return root.alchemy = new Alchemy(config);
    }
  };

  API = (function() {
    function API(instance) {
      this.a = instance;
      this.get = this.Get(instance, this);
      this.create = this.Create(instance, this);
      this.set = this.Set(instance, this);
      this.remove = Remove.remove;
      this.force = this.Force(instance, this);
      this.filter = this.Filter(instance, this);
      this.search = this.Search(instance, this);
      this._el = [];
      this._elType = null;
      this._makeChain = function(inp, endpoint) {
        var e, _i, _len;
        this.__proto__ = [].__proto__;
        endpoint.__proto__ = [].__proto__;
        while (this.length) {
          this.pop();
        }
        while (endpoint.length) {
          endpoint.pop();
        }
        for (_i = 0, _len = inp.length; _i < _len; _i++) {
          e = inp[_i];
          this.push(e);
        }
        Array.prototype.push.apply(endpoint, this._el);
        return _.extend(endpoint, this);
      };
    }

    return API;

  })();

  API.prototype.Create = function(instance, api) {
    return {
      a: instance,
      api: api,
      nodes: function() {
        var a, n, nodeMap, nodeMaps, registerNode, _i, _len;
        nodeMap = arguments[0], nodeMaps = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
        a = this.a;
        registerNode = function(node) {
          var aNode;
          if (!a._nodes[node.id]) {
            aNode = new a.models.Node(node);
            a._nodes[node.id] = aNode;
            return [aNode];
          } else {
            return console.warn("A node with the id " + node.id + " already exists.\nConsider using the @a.get.nodes() method to \nretrieve the node and then using the Node methods.");
          }
        };
        nodeMaps = _.uniq(_.flatten(arguments));
        for (_i = 0, _len = nodeMaps.length; _i < _len; _i++) {
          n = nodeMaps[_i];
          registerNode(n);
        }
        if (this.a.initial) {
          this.a.index = Alchemy.prototype.Index(this.a);
          return this.a.updateGraph();
        }
      },
      edges: function() {
        var a, allEdges, edgeMap, edgeMaps, registerEdge;
        edgeMap = arguments[0], edgeMaps = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
        a = this.a;
        registerEdge = function(edge) {
          var aEdge, edgeArray;
          if (edge.id && !a._edges[edge.id]) {
            aEdge = new a.models.Edge(edge);
            a._edges[edge.id] = [aEdge];
            return [aEdge];
          } else if (edge.id && a._edges[edge.id]) {
            return console.warn("An edge with that id " + someEdgeMap.id + " already exists.\nConsider using the @a.get.edge() method to \nretrieve the edge and then using the Edge methods.\nNote: id's are not required for edges.  Alchemy will create\nan unlimited number of edges for the same source and target node.\nSimply omit 'id' when creating the edge.");
          } else {
            edgeArray = a._edges["" + edge.source + "-" + edge.target];
            if (edgeArray) {
              aEdge = new a.models.Edge(edge, edgeArray.length);
              edgeArray.push(aEdge);
              return [aEdge];
            } else {
              aEdge = new a.models.Edge(edge, 0);
              a._edges["" + edge.source + "-" + edge.target] = [aEdge];
              return [aEdge];
            }
          }
        };
        allEdges = _.uniq(_.flatten(arguments));
        _.each(allEdges, function(e) {
          return registerEdge(e);
        });
        if (this.a.initial) {
          this.a.index = Alchemy.prototype.Index(this.a);
          return this.a.updateGraph();
        }
      }
    };
  };

  API.prototype.Filter = function(instance, api) {
    var a, filter;
    a = instance;
    filter = function(type) {
      return _.each(api._el, function(el) {
        var key;
        key = (function() {
          if (api._elType === "edge") {
            "_edgeType";
          }
          if (api._elType === "node") {
            return "_nodeType";
          }
        })();
        if (el[key] === type) {
          return el.toggleHidden();
        }
      });
    };
    filter.nodes = function(type) {
      var nodes;
      nodes = a.elements.nodes.val;
      return _.each(nodes, function(n) {
        if (n._nodeType === type) {
          return n.toggleHidden();
        }
      });
    };
    filter.edges = function(type) {
      var edges;
      edges = a.elements.edges.flat;
      return _.each(edges, function(e) {
        if (e._edgeType === type) {
          return e.toggleHidden();
        }
      });
    };
    return filter;
  };

  API.prototype.Force = function(instance, api) {
    var a;
    a = instance;
    return {
      toggle: function() {
        if (a.force.alpha() > 0) {
          return a.force.stop();
        } else {
          return a.force.resume();
        }
      },
      start: function() {
        return a.force.start();
      },
      stop: function() {
        return a.force.stop();
      }
    };
  };

  API.prototype.Get = function(instance, api) {
    return {
      a: instance,
      api: api,
      nodes: function() {
        var a, allIDs, args, id, ids, nodeList;
        id = arguments[0], ids = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
        nodeList = [];
        if (id != null) {
          args = _.flatten(arguments);
          allIDs = _.map(args, function(arg) {
            return String(arg);
          });
          a = this.a;
          nodeList = (function(a) {
            return _.filter(a._nodes, function(val, key) {
              if (_.contains(allIDs, key)) {
                return val;
              }
            });
          })(a);
        }
        this.api._elType = "node";
        this.api._el = nodeList;
        return this.api._makeChain(nodeList, this);
      },
      edges: function() {
        var a, allIDs, edgeList, id, ids;
        id = arguments[0], ids = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
        edgeList = [];
        if (id != null) {
          allIDs = _.map(arguments, function(arg) {
            return String(arg);
          });
          a = this.a;
          edgeList = (function(a) {
            return _.flatten(_.filter(a._edges, function(val, key) {
              if (_.contains(allIDs, key)) {
                return val;
              }
            }));
          })(a);
        }
        this.api._elType = "edge";
        this.api._el = edgeList;
        return this.api._makeChain(edgeList, this);
      },
      all: function() {
        var a, elType;
        a = this.a;
        elType = this.api._elType;
        this.api._el = (function(elType) {
          switch (elType) {
            case "node":
              return a.elements.nodes.val;
            case "edge":
              return a.elements.edges.flat;
          }
        })(elType);
        return this.api._makeChain(this.api._el, this);
      },
      elState: function(state) {
        var elList;
        elList = _.filter(this.api._el, function(e) {
          return e._state === state;
        });
        this.api._el = elList;
        return this.api._makeChain(elList, this);
      },
      state: function(key) {
        if (this.a.state.key != null) {
          return this.a.state.key;
        }
      },
      type: function(type) {
        var elList;
        elList = _.filter(this.api._el, function(e) {
          return e._nodeType === type || e._edgeType === type;
        });
        this.api._el = elList;
        return this.api._makeChain(elList, this);
      },
      activeNodes: function() {
        return _.filter(this.a._nodes, function(node) {
          if (node._state === "active") {
            return node;
          }
        });
      },
      activeEdges: function() {
        return _.filter(this.a.get.allEdges(), function(edge) {
          if (edge._state === "active") {
            return edge;
          }
        });
      },
      state: function(key) {
        if (this.a.state.key != null) {
          return this.a.state.key;
        }
      },
      clusters: function() {
        var clusterMap, nodesByCluster;
        clusterMap = this.a.layout._clustering.clusterMap;
        nodesByCluster = {};
        _.each(clusterMap, function(key, value) {
          return nodesByCluster[value] = _.select(this.a.get.allNodes(), function(node) {
            return node.getProperties()[this.a.conf.clusterKey] === value;
          });
        });
        return nodesByCluster;
      },
      clusterColours: function() {
        var clusterColoursObject, clusterMap;
        clusterMap = this.a.layout._clustering.clusterMap;
        clusterColoursObject = {};
        _.each(clusterMap, function(key, value) {
          return clusterColoursObject[value] = this.a.conf.clusterColours[key % this.a.conf.clusterColours.length];
        });
        return clusterColoursObject;
      },
      allEdges: function() {
        return this.a.elements.nodes.flat;
      },
      allNodes: function(type) {
        if (type != null) {
          return _.filter(this.a._nodes, function(n) {
            if (n._nodeType === type) {
              return n;
            }
          });
        } else {
          return this.a.elements.nodes.val;
        }
      },
      getNodes: function() {
        var a, id, ids;
        id = arguments[0], ids = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
        a = this.a;
        ids.push(id);
        return _.map(ids, function(id) {
          return a._nodes[id];
        });
      },
      getEdges: function(id, target) {
        var a, edge_id;
        if (id == null) {
          id = null;
        }
        if (target == null) {
          target = null;
        }
        a = this.a;
        if ((id != null) && (target != null)) {
          edge_id = "" + id + "-" + target;
          return this.a._edges[edge_id];
        } else if ((id != null) && (target == null)) {
          return this.a._nodes[id]._adjacentEdges;
        }
      }
    };
  };

  Remove = {
    remove: function() {
      return _.each(this._el, function(e) {
        return e.remove();
      });
    }
  };

  API.prototype.Search = function(instance, api) {
    var a, search;
    a = instance;
    search = function(query) {
      var regex;
      query = (function() {
        if (a.conf.searchMethod === "contains") {
          return query;
        }
        if (a.conf.searchMethod === "begins") {
          return "^" + query;
        }
      })();
      regex = new RegExp(query, "i");
      return _.filter(api._el, function(el) {
        return regex.test(el._properties.caption);
      });
    };
    search.nodes = function(query) {
      var regex;
      query = (function() {
        if (a.conf.searchMethod === "contains") {
          return query;
        }
        if (a.conf.searchMethod === "begins") {
          return "^" + query;
        }
      })();
      regex = new RegExp(query, "i");
      return _.filter(a._nodes, function(node) {
        return regex.test(node._properties.caption);
      });
    };
    search.edges = function(query) {
      var regex;
      query = (function() {
        if (a.conf.searchMethod === "contains") {
          return query;
        }
        if (a.conf.searchMethod === "begins") {
          return "^" + query;
        }
      })();
      regex = new RegExp(query, "i");
      return _.filter(a.elements.edges.flat, function(edge) {
        return regex.test(edge._properties.caption);
      });
    };
    return search;
  };

  API.prototype.Set = function(instance, api) {
    return {
      a: instance,
      api: api,
      state: function(key, value) {
        return this.a.state.key = value;
      }
    };
  };

  Clustering = (function() {
    function Clustering(instance) {
      var clustering, conf, nodes, _charge, _friction, _gravity, _linkDistancefn, _linkStrength;
      this.a = instance;
      nodes = this.a._nodes;
      conf = this.a.conf;
      clustering = this;
      this.clusterKey = conf.clusterKey;
      this.identifyClusters(this.a);
      _charge = -500;
      _linkStrength = function(edge) {
        var sourceCluster, targetCluster;
        sourceCluster = nodes[edge.source.id]._properties[this.clusterKey];
        targetCluster = nodes[edge.target.id]._properties[this.clusterKey];
        if (sourceCluster === targetCluster) {
          return 0.15;
        } else {
          return 0;
        }
      };
      _friction = function() {
        return 0.7;
      };
      _linkDistancefn = function(edge) {
        nodes = edge.self.a._nodes;
        if (nodes[edge.source.id]._properties.root || nodes[edge.target.id]._properties.root) {
          return 300;
        } else if (nodes[edge.source.id]._properties[this.clusterKey] === nodes[edge.target.id]._properties[this.clusterKey]) {
          return 10;
        } else {
          return 600;
        }
      };
      _gravity = function(k) {
        return 8 * k;
      };
      this.layout = {
        charge: _charge,
        linkStrength: function(edge) {
          return _linkStrength(edge);
        },
        friction: function() {
          return _friction();
        },
        linkDistancefn: function(edge) {
          return _linkDistancefn(edge);
        },
        gravity: function(k) {
          return _gravity(k);
        }
      };
    }

    Clustering.prototype.identifyClusters = function(a) {
      var clusters, nodes, _i, _ref, _results;
      nodes = a.elements.nodes.val;
      clusters = _.uniq(_.map(nodes, function(node) {
        return node.getProperties()[a.conf.clusterKey];
      }));
      return this.clusterMap = _.zipObject(clusters, (function() {
        _results = [];
        for (var _i = 0, _ref = clusters.length; 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); }
        return _results;
      }).apply(this));
    };

    Clustering.prototype.getClusterColour = function(clusterValue) {
      var index;
      index = this.clusterMap[clusterValue] % this.a.conf.clusterColours.length;
      return this.a.conf.clusterColours[index];
    };

    Clustering.prototype.edgeGradient = function(edges) {
      var Q, defs, edge, endColour, gradient, gradient_id, id, ids, nodes, startColour, _i, _len, _ref, _results;
      defs = this.a.vis.select("" + this.a.conf.divSelector + " svg");
      Q = {};
      nodes = this.a._nodes;
      _ref = _.map(edges, function(edge) {
        return edge._d3;
      });
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        edge = _ref[_i];
        if (nodes[edge.source.id]._properties.root || nodes[edge.target.id]._properties.root) {
          continue;
        }
        if (nodes[edge.source.id]._properties[this.clusterKey] === nodes[edge.target.id]._properties[this.clusterKey]) {
          continue;
        }
        if (nodes[edge.target.id]._properties[this.clusterKey] !== nodes[edge.source.id]._properties[this.clusterKey]) {
          id = nodes[edge.source.id]._properties[this.clusterKey] + "-" + nodes[edge.target.id]._properties[this.clusterKey];
          if (id in Q) {
            continue;
          } else if (!(id in Q)) {
            startColour = this.getClusterColour(nodes[edge.target.id]._properties[this.clusterKey]);
            endColour = this.getClusterColour(nodes[edge.source.id]._properties[this.clusterKey]);
            Q[id] = {
              'startColour': startColour,
              'endColour': endColour
            };
          }
        }
      }
      _results = [];
      for (ids in Q) {
        gradient_id = "cluster-gradient-" + ids;
        gradient = defs.append("svg:linearGradient").attr("id", gradient_id);
        gradient.append("svg:stop").attr("offset", "0%").attr("stop-color", Q[ids]['startColour']);
        _results.push(gradient.append("svg:stop").attr("offset", "100%").attr("stop-color", Q[ids]['endColour']));
      }
      return _results;
    };

    return Clustering;

  })();

  Alchemy.prototype.clusterControls = {
    init: function() {
      var changeClusterHTML;
      changeClusterHTML = "";
      this.a.dash.select("#clustering-container").append("div").attr("id", "cluster-key-container").attr('class', 'property form-inline form-group').html(changeClusterHTML).style("display", "none");
      this.a.dash.select("#cluster_control_header").on("click", function() {
        var display, element;
        element = this.a.dash.select("#cluster-key-container");
        return display = element.style("display");
      });
      element.style("display", function(e) {
        if (display === "block") {
          return "none";
        } else {
          return "block";
        }
      });
      if (this.a.dash.select("#cluster-key-container").style("display") === "none") {
        this.a.dash.select("#cluster-arrow").attr("class", "fa fa-2x fa-caret-right");
      } else {
        this.a.dash.select("#cluster-arrow").attr("class", "fa fa-2x fa-caret-down");
      }
      return this.a.dash.select("#cluster-key").on("keydown", function() {
        if (d3.event.keyIdentifier === "Enter") {
          this.a.conf.cluster = true;
          this.a.conf.clusterKey = this.value;
          return this.a.generateLayout();
        }
      });
    }
  };

  Alchemy.prototype.controlDash = function(instance) {
    var a;
    a = instance;
    return {
      init: function() {
        var divSelector;
        if (this.dashIsShown()) {
          divSelector = a.conf.divSelector;
          a.dash = d3.select("" + divSelector).append("div").attr("id", "control-dash-wrapper").attr("class", "col-md-4 initial");
          a.dash.append("i").attr("id", "dash-toggle").attr("class", "fa fa-flask col-md-offset-12");
          a.dash.append("div").attr("id", "control-dash").attr("class", "col-md-12");
          a.dash.select('#dash-toggle').on('click', a.interactions.toggleControlDash);
          a.controlDash.zoomCtrl();
          a.controlDash.search();
          a.controlDash.filters();
          a.controlDash.stats();
          a.controlDash.clustering();
          return a.controlDash.exports();
        }
      },
      search: function() {
        if (a.conf.search) {
          a.dash.select("#control-dash").append("div").attr("id", "search").html("
\n \n \n
"); return a.search.init(); } }, zoomCtrl: function() { if (a.conf.zoomControls) { a.dash.select("#control-dash-wrapper").append("div").attr("id", "zoom-controls").attr("class", "col-md-offset-12").html(" "); a.dash.select('#zoom-in').on("click", function() { return a.interactions.clickZoom('in'); }); a.dash.select('#zoom-out').on("click", function() { return a.interactions.clickZoom('out'); }); return a.dash.select('#zoom-reset').on("click", function() { return a.interactions.clickZoom('reset'); }); } }, filters: function() { if (a.conf.nodeFilters || a.conf.edgeFilters) { a.dash.select("#control-dash").append("div").attr("id", "filters"); return a.filters.init(); } }, stats: function() { var stats_html; if (a.conf.nodeStats || a.conf.edgeStats) { stats_html = "
\n

\n Statistics\n

\n\n
\n
\n
    \n
      \n
      "; a.dash.select("#control-dash").append("div").attr("id", "stats").html(stats_html).select('#stats-header').on('click', function() { if (a.dash.select('#all-stats').classed("in")) { return a.dash.select("#stats-header>span").attr("class", "fa fa-2x fa-caret-right"); } else { return a.dash.select("#stats-header>span").attr("class", "fa fa-2x fa-caret-down"); } }); return a.stats.init(); } }, exports: function() { var exports_html; if (a.conf.exportSVG) { exports_html = "
      \n

      \n Exports\n

      \n \n
      \n
      "; a.dash.select("#control-dash").append("div").attr("id", "exports").attr("style", "padding: 0.5em 1em; border-bottom: thin dashed #E89619; color: white;").html(exports_html).select("#exports-header"); return a.exports.init(); } }, clustering: function() { var clusterControl_html; if (a.conf.clusterControl) { clusterControl_html = "
      \n
      \n

      Clustering

      \n \n
      \n
      "; a.dash.select("#control-dash").append("div").attr("id", "clustering").html(clusterControl_html).select('#cluster_control_header'); return a.clusterControls.init(); } }, dashIsShown: function() { var conf; conf = a.conf; return conf.showEditor || conf.captionToggle || conf.toggleRootNodes || conf.removeElement || conf.clusterControl || conf.nodeStats || conf.edgeStats || conf.edgeFilters || conf.nodeFilters || conf.edgesToggle || conf.nodesToggle || conf.search || conf.exportSVG; } }; }; Alchemy.prototype.filters = (function(_this) { return function(instance) { var a; a = instance; return { init: function() { var caption, edgeType, edgeTypes, nodeKey, nodeType, nodeTypes, types, _i, _j, _len, _len1, _ref; a.filters.show(); if (a.conf.edgeFilters) { a.filters.showEdgeFilters(); } if (a.conf.nodeFilters) { a.filters.showNodeFilters(); } if (a.conf.nodeTypes) { nodeKey = Object.keys(a.conf.nodeTypes); nodeTypes = ''; _ref = a.conf.nodeTypes[nodeKey]; for (_i = 0, _len = _ref.length; _i < _len; _i++) { nodeType = _ref[_i]; caption = nodeType.replace('_', ' '); nodeTypes += ""; } a.dash.select('#node-dropdown').html(nodeTypes); } if (a.conf.edgeTypes) { if (_.isPlainObject(a.conf.edgeTypes)) { types = _.values(a.conf.edgeTypes)[0]; } else { types = a.conf.edgeTypes; } edgeTypes = ''; for (_j = 0, _len1 = types.length; _j < _len1; _j++) { edgeType = types[_j]; caption = edgeType.replace('_', ' '); edgeTypes += ""; } a.dash.select('#rel-dropdown').html(edgeTypes); } if (a.conf.captionsToggle) { a.filters.captionsToggle(); } if (a.conf.edgesToggle) { a.filters.edgesToggle(); } if (a.conf.nodesToggle) { a.filters.nodesToggle(); } return a.filters.update(); }, show: function() { var filter_html; filter_html = "
      \n

      Filters

      \n \n
      \n
      \n
      "; a.dash.select('#control-dash #filters').html(filter_html); a.dash.selectAll('#filter-header').on('click', function() { if (a.dash.select('#filters>form').classed("in")) { return a.dash.select("#filter-header>span").attr("class", "fa fa-2x fa-caret-right"); } else { return a.dash.select("#filter-header>span").attr("class", "fa fa-2x fa-caret-down"); } }); return a.dash.select('#filters form'); }, showEdgeFilters: function() { var rel_filter_html; rel_filter_html = "
      \n

      \n Edge Types\n

      \n \n
      \n
        \n
      "; a.dash.select('#filters form').append("div").attr("id", "filter-relationships").html(rel_filter_html); return a.dash.select("#filter-rel-header").on('click', function() { if (a.dash.select('#rel-dropdown').classed("in")) { return a.dash.select("#filter-rel-header>span").attr("class", "fa fa-lg fa-caret-right"); } else { return a.dash.select("#filter-rel-header>span").attr("class", "fa fa-lg fa-caret-down"); } }); }, showNodeFilters: function() { var node_filter_html; node_filter_html = "
      \n

      \n Node Types\n

      \n \n
      \n
        \n
      "; a.dash.select('#filters form').append("div").attr("id", "filter-nodes").html(node_filter_html); return a.dash.select("#filter-node-header").on('click', function() { if (a.dash.select('#node-dropdown').classed("in")) { return a.dash.select("#filter-node-header>span").attr("class", "fa fa-lg fa-caret-right"); } else { return a.dash.select("#filter-node-header>span").attr("class", "fa fa-lg fa-caret-down"); } }); }, captionsToggle: function() { return a.dash.select("#filters form").append("li").attr({ "id": "toggle-captions", "class": "list-group-item active-label toggle" }).html("Show Captions").on("click", function() { var isDisplayed; isDisplayed = a.dash.select("g text").attr("style"); if (isDisplayed === "display: block" || null) { return a.dash.selectAll("g text").attr("style", "display: none"); } else { return a.dash.selectAll("g text").attr("style", "display: block"); } }); }, edgesToggle: function() { return a.dash.select("#filters form").append("li").attr({ "id": "toggle-edges", "class": "list-group-item active-label toggle" }).html("Toggle Edges").on("click", function() { if (_.contains(_.pluck(_.flatten(_.values(a._edges)), "_state"), "active")) { return _.each(_.values(a._edges), function(edges) { return _.each(edges, function(e) { if (e._state === "active") { return e.toggleHidden(); } }); }); } else { return _.each(_.values(a._edges), function(edges) { return _.each(edges, function(e) { var source, target; source = a._nodes[e._properties.source]; target = a._nodes[e._properties.target]; if (source._state === "active" && target._state === "active") { return e.toggleHidden(); } }); }); } }); }, nodesToggle: function() { return a.dash.select("#filters form").append("li").attr({ "id": "toggle-nodes", "class": "list-group-item active-label toggle" }).html("Toggle Nodes").on("click", function() { var nodes; nodes = _.values(a._nodes); if (_.contains(_.pluck(nodes, "_state"), "active")) { return _.each(nodes, function(n) { if (a.conf.toggleRootNodes && n._d3.root) { return; } if (n._state === "active") { return n.toggleHidden(); } }); } else { return _.each(_.values(a._nodes), function(n) { if (a.conf.toggleRootNodes && n._d3.root) { return; } return n.toggleHidden(); }); } }); }, update: function() { return a.dash.selectAll(".nodeType, .edgeType").on("click", function() { var element, tag; element = d3.select(this); tag = element.attr("name"); a.vis.selectAll("." + tag).each(function(d) { var edge, node, source, target; if (a._nodes[d.id] != null) { node = a._nodes[d.id]; return node.toggleHidden(); } else { edge = a._edges[d.id][0]; source = a._nodes[edge._properties.source]; target = a._nodes[edge._properties.target]; if (source._state === "active" && target._state === "active") { return edge.toggleHidden(); } } }); return a.stats.nodeStats(); }); } }; }; })(this); Alchemy.prototype.Index = function(instance) { var a, edges, elements, nodes; a = instance; elements = { nodes: { val: (function() { return _.values(a._nodes); })() }, edges: { val: (function() { return _.values(a._edges); })() } }; nodes = elements.nodes; edges = elements.edges; elements.edges.flat = (function() { return _.flatten(edges.val); })(); elements.nodes.d3 = (function() { return _.map(nodes.val, function(n) { return n._d3; }); })(); elements.edges.d3 = (function() { return _.map(edges.flat, function(e) { return e._d3; }); })(); if (a.initial) { elements.nodes.svg = (function() { return a.vis.selectAll('g.node'); })(); elements.edges.svg = (function() { return a.vis.selectAll('g.edge'); })(); } a.elements = elements; return function() { a.elements.nodes.svg = (function() { return a.vis.selectAll('g.node'); })(); return a.elements.edges.svg = (function() { return a.vis.selectAll('g.edge'); })(); }; }; Alchemy.prototype.interactions = function(instance) { var a; a = instance; return { edgeClick: function(d) { var edge; if (d3.event.defaultPrevented) { return; } d3.event.stopPropagation(); edge = d.self; if (typeof a.conf.edgeClick === 'function') { a.conf.edgeClick(edge, d3.event); } if (edge._state !== "hidden") { edge._state = edge._state === "highlighted" ? "selected" : "active"; return edge.setStyles(); } }, edgeMouseOver: function(d) { var edge; edge = d.self; if (edge._state !== "hidden") { if (edge._state !== "selected") { edge._state = "highlighted"; } return edge.setStyles(); } }, edgeMouseOut: function(d) { var edge; edge = d.self; if (edge._state !== "hidden") { if (edge._state !== "selected") { edge._state = "active"; } return edge.setStyles(); } }, nodeMouseOver: function(n) { var node; node = n.self; if (node._state !== "hidden") { if (node._state !== "selected") { node._state = "highlighted"; node.setStyles(); } if (typeof a.conf.nodeMouseOver === 'function') { return a.conf.nodeMouseOver(node); } else if (typeof a.conf.nodeMouseOver === ('number' || 'string')) { return node.properties[a.conf.nodeMouseOver]; } } }, nodeMouseOut: function(n) { var node; node = n.self; a = node.a; if (node._state !== "hidden") { if (node._state !== "selected") { node._state = "active"; node.setStyles(); } if ((a.conf.nodeMouseOut != null) && typeof a.conf.nodeMouseOut === 'function') { return a.conf.nodeMouseOut(n); } } }, nodeClick: function(n) { var node; if (d3.event.defaultPrevented) { return; } d3.event.stopPropagation(); node = n.self; if (typeof a.conf.nodeClick === 'function') { a.conf.nodeClick(node, d3.event); } if (node._state !== "hidden") { node._state = (function() { if (node._state === "selected") { return "active"; } return "selected"; })(); return node.setStyles(); } }, zoom: function(extent) { if (this._zoomBehavior == null) { this._zoomBehavior = d3.behavior.zoom(); } return this._zoomBehavior.scaleExtent(extent).on("zoom", function(d) { a = Alchemy.prototype.getInst(this); return a.vis.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")"); }); }, clickZoom: function(direction) { var scale, x, y, _ref; _ref = a.vis.attr("transform").match(/(-*\d+\.*\d*)/g).map(function(a) { return parseFloat(a); }), x = _ref[0], y = _ref[1], scale = _ref[2]; a.vis.attr("transform", function() { if (direction === "in") { if (scale < a.conf.scaleExtent[1]) { scale += 0.2; } return "translate(" + x + "," + y + ") scale(" + scale + ")"; } else if (direction === "out") { if (scale > a.conf.scaleExtent[0]) { scale -= 0.2; } return "translate(" + x + "," + y + ") scale(" + scale + ")"; } else if (direction === "reset") { return "translate(0,0) scale(1)"; } else { return console.log('error'); } }); if (this._zoomBehavior == null) { this._zoomBehavior = d3.behavior.zoom(); } return this._zoomBehavior.scale(scale).translate([x, y]); }, toggleControlDash: function() { var offCanvas; offCanvas = a.dash.classed("off-canvas") || a.dash.classed("initial"); return a.dash.classed({ "off-canvas": !offCanvas, "initial": false, "on-canvas": offCanvas }); }, nodeDragStarted: function(d, i) { d3.event.preventDefault; d3.event.sourceEvent.stopPropagation(); d3.select(this).classed("dragging", true); return d.fixed = true; }, nodeDragged: function(d, i) { var edges, node; a = d.self.a; d.x += d3.event.dx; d.y += d3.event.dy; d.px += d3.event.dx; d.py += d3.event.dy; node = d3.select(this); node.attr("transform", "translate(" + d.x + ", " + d.y + ")"); edges = d.self._adjacentEdges; return _.each(edges, function(edge) { var selection; selection = a.vis.select("#edge-" + edge.id + "-" + edge._index); return a._drawEdges.updateEdge(selection.data()[0]); }); }, nodeDragended: function(d, i) { a = d.self.a; d3.select(this).classed({ "dragging": false }); if (!a.conf.forceLocked) { return a.force.start(); } }, nodeDoubleClick: function(d) { return null; }, deselectAll: function() { var _ref; a = Alchemy.prototype.getInst(this); if ((_ref = d3.event) != null ? _ref.defaultPrevented : void 0) { return; } if (a.conf.showEditor === true) { a.modifyElements.nodeEditorClear(); } _.each(a._nodes, function(n) { n._state = "active"; return n.setStyles(); }); _.each(a._edges, function(edge) { return _.each(edge, function(e) { e._state = "active"; return e.setStyles(); }); }); if (a.conf.deselectAll && typeof (a.conf.deselectAll === 'function')) { return a.conf.deselectAll(); } } }; }; Layout = (function() { function Layout(instance) { this.tick = __bind(this.tick, this); this.linkStrength = __bind(this.linkStrength, this); this.gravity = __bind(this.gravity, this); var a, conf, nodes; this.a = a = instance; conf = this.a.conf; nodes = this.a._nodes; this.k = Math.sqrt(Math.log(_.size(this.a._nodes)) / (conf.graphWidth() * conf.graphHeight())); this._clustering = new this.a.clustering(this.a); this.d3NodeInternals = a.elements.nodes.d3; if (conf.cluster) { this._charge = function() { return this._clustering.layout.charge; }; this._linkStrength = function(edge) { return this._clustering.layout.linkStrength(edge); }; } else { this._charge = function() { return -10 / this.k; }; this._linkStrength = function(edge) { if (nodes[edge.source.id].getProperties('root') || nodes[edge.target.id].getProperties('root')) { return 1; } else { return 0.9; } }; } if (conf.cluster) { this._linkDistancefn = function(edge) { return this._clustering.layout.linkDistancefn(edge); }; } else if (conf.linkDistancefn === 'default') { this._linkDistancefn = function(edge) { return 1 / (this.k * 50); }; } else if (typeof conf.linkDistancefn === 'number') { this._linkDistancefn = function(edge) { return conf.linkDistancefn; }; } else if (typeof conf.linkDistancefn === 'function') { this._linkDistancefn = function(edge) { return conf.linkDistancefn(edge); }; } } Layout.prototype.gravity = function() { if (this.a.conf.cluster) { return this._clustering.layout.gravity(this.k); } else { return 50 * this.k; } }; Layout.prototype.linkStrength = function(edge) { return this._linkStrength(edge); }; Layout.prototype.friction = function() { return 0.9; }; Layout.prototype.collide = function(node) { var conf, nx1, nx2, ny1, ny2, r; conf = this.a.conf; r = 2 * (node.radius + node['stroke-width']) + conf.nodeOverlap; nx1 = node.x - r; nx2 = node.x + r; ny1 = node.y - r; ny2 = node.y + r; return function(quad, x1, y1, x2, y2) { var l, x, y; if (quad.point && (quad.point !== node)) { x = node.x - Math.abs(quad.point.x); y = node.y - quad.point.y; l = Math.sqrt(x * x + y * y); r = r; if (l < r) { l = (l - r) / l * conf.alpha; node.x -= x *= l; node.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }; }; Layout.prototype.tick = function(draw) { var a, edges, node, nodes, q, _i, _len, _ref; a = this.a; nodes = a.elements.nodes.svg; edges = a.elements.edges.svg; if (a.conf.collisionDetection) { q = d3.geom.quadtree(this.d3NodeInternals); _ref = this.d3NodeInternals; for (_i = 0, _len = _ref.length; _i < _len; _i++) { node = _ref[_i]; q.visit(this.collide(node)); } } nodes.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); this.drawEdge = a.drawing.DrawEdge; this.drawEdge.styleText(edges); return this.drawEdge.styleLink(edges); }; Layout.prototype.positionRootNodes = function() { var conf, container, i, n, rootNodes, _i, _len, _ref, _ref1, _results; conf = this.a.conf; container = { width: conf.graphWidth(), height: conf.graphHeight() }; rootNodes = _.filter(this.a.elements.nodes.val, function(node) { return node.getProperties('root'); }); if (rootNodes.length === 1) { n = rootNodes[0]; _ref = [container.width / 2, container.width / 2], n._d3.x = _ref[0], n._d3.px = _ref[1]; _ref1 = [container.height / 2, container.height / 2], n._d3.y = _ref1[0], n._d3.py = _ref1[1]; n._d3.fixed = true; } else { _results = []; for (i = _i = 0, _len = rootNodes.length; _i < _len; i = ++_i) { n = rootNodes[i]; n._d3.x = container.width / Math.sqrt(rootNodes.length * (i + 1)); n._d3.y = container.height / 2; _results.push(n._d3.fixed = true); } return _results; } }; Layout.prototype.chargeDistance = function() { return 500; }; Layout.prototype.linkDistancefn = function(edge) { return this._linkDistancefn(edge); }; Layout.prototype.charge = function() { return this._charge(); }; return Layout; })(); Alchemy.prototype.generateLayout = function(instance) { var a; a = instance; return function(start) { var conf; if (start == null) { start = false; } conf = a.conf; a.layout = new Layout(a); return a.force = d3.layout.force().size([conf.graphWidth(), conf.graphHeight()]).theta(1.0).gravity(a.layout.gravity()).friction(a.layout.friction()).nodes(a.elements.nodes.d3).links(a.elements.edges.d3).linkDistance(function(link) { return a.layout.linkDistancefn(link); }).linkStrength(function(link) { return a.layout.linkStrength(link); }).charge(a.layout.charge()).chargeDistance(a.layout.chargeDistance()); }; }; Alchemy.prototype.search = function(instance) { var a; a = instance; return { init: function() { var searchBox; searchBox = a.dash.select("#search input"); return searchBox.on("keyup", function() { var input; input = searchBox[0][0].value.toLowerCase(); a.vis.selectAll(".node").classed("inactive", false); a.vis.selectAll("text").attr("style", function() { if (input !== "") { return "display: inline;"; } }); return a.vis.selectAll(".node").classed("inactive", function(node) { var DOMtext, hidden; DOMtext = d3.select(this).text(); switch (a.conf.searchMethod) { case 'contains': hidden = DOMtext.toLowerCase().indexOf(input) < 0; break; case 'begins': hidden = DOMtext.toLowerCase().indexOf(input) !== 0; } if (hidden) { a.vis.selectAll("[source-target*='" + node.id + "']").classed("inactive", hidden); } else { a.vis.selectAll("[source-target*='" + node.id + "']").classed("inactive", function(edge) { var nodeIDs, sourceHidden, targetHidden; nodeIDs = [edge.source.id, edge.target.id]; sourceHidden = a.vis.select("#node-" + nodeIDs[0]).classed("inactive"); targetHidden = a.vis.select("#node-" + nodeIDs[1]).classed("inactive"); return targetHidden || sourceHidden; }); } return hidden; }); }); } }; }; Alchemy.prototype.startGraph = function(instance) { var a; a = instance; return function(data) { var conf, d3Edges, d3Nodes, defs, editor, editorInteractions; conf = a.conf; if (d3.select(conf.divSelector).empty()) { console.warn(a.utils.warnings.divWarning()); } if (!data) { data = { nodes: [], edges: [] }; a.utils.warnings.dataWarning(); } if (data.edges == null) { data.edges = []; } a.create.nodes(data.nodes); data.edges.forEach(function(e) { return a.create.edges(e); }); a.vis = d3.select(conf.divSelector).attr("style", "width:" + (conf.graphWidth()) + "px; height:" + (conf.graphHeight()) + "px; background:" + conf.backgroundColour + ";").append("svg").attr("xmlns", "http://www.w3.org/2000/svg").attr("xlink", "http://www.w3.org/1999/xlink").attr("pointer-events", "all").attr("style", "background:" + conf.backgroundColour + ";").attr("alchInst", Alchemy.prototype.instances.length - 1).on('click', a.interactions.deselectAll).call(a.interactions.zoom(conf.scaleExtent)).on("dblclick.zoom", null).append('g').attr("transform", "translate(" + conf.initialTranslate + ") scale(" + conf.initialScale + ")"); a.interactions.zoom().scale(conf.initialScale); a.interactions.zoom().translate(conf.initialTranslate); a.index = Alchemy.prototype.Index(a); a.generateLayout(); a.controlDash.init(); d3Edges = a.elements.edges.d3; d3Nodes = a.elements.nodes.d3; a.layout.positionRootNodes(); a.force.start(); if (conf.forceLocked) { while (a.force.alpha() > 0.005) { a.force.tick(); } } a._drawEdges = a.drawing.DrawEdges; a._drawNodes = a.drawing.DrawNodes; a._drawEdges.createEdge(d3Edges); a._drawNodes.createNode(d3Nodes); a.index(); a.elements.nodes.svg.attr("transform", function(id, i) { return "translate(" + id.x + ", " + id.y + ")"; }); console.log(Date() + ' completed initial computation'); if (!conf.forceLocked) { a.force.on("tick", a.layout.tick).start(); } if (conf.afterLoad != null) { if (typeof conf.afterLoad === 'function') { conf.afterLoad(); } else if (typeof conf.afterLoad === 'string') { a[conf.afterLoad] = true; } } if (conf.cluster) { defs = d3.select("" + a.conf.divSelector + " svg").append("svg:defs"); } if (conf.nodeStats) { a.stats.nodeStats(); } if (conf.showEditor) { editor = new a.editor.Editor; editorInteractions = new a.editor.Interactions; d3.select("body").on('keydown', editorInteractions.deleteSelected); editor.startEditor(); } return a.initial = true; }; }; Alchemy.prototype.stats = function(instance) { var a; a = instance; return { init: function() { return a.stats.update(); }, nodeStats: function() { var activeNodes, allNodes, caption, inactiveNodes, nodeData, nodeGraph, nodeKeys, nodeNum, nodeStats, nodeType, nodeTypes, _i, _len, _ref; nodeData = []; allNodes = a.get.allNodes().length; activeNodes = a.get.activeNodes().length; inactiveNodes = allNodes - activeNodes; nodeStats = "
    • Number of nodes: " + allNodes + "
    • Number of active nodes: " + activeNodes + "
    • Number of inactive nodes: " + inactiveNodes + "

    • "; if (a.conf.nodeTypes) { nodeKeys = Object.keys(a.conf.nodeTypes); nodeTypes = ''; _ref = a.conf.nodeTypes[nodeKeys]; for (_i = 0, _len = _ref.length; _i < _len; _i++) { nodeType = _ref[_i]; caption = nodeType.replace('_', ' '); nodeNum = a.vis.selectAll("g.node." + nodeType)[0].length; nodeTypes += "
    • Number of " + caption + " nodes: " + nodeNum + "
    • "; nodeData.push(["" + nodeType, nodeNum]); } nodeStats += nodeTypes; } nodeGraph = "
    • "; nodeStats += nodeGraph; a.dash.select('#node-stats').html(nodeStats); return this.insertSVG("node", nodeData); }, edgeStats: function() { var activeEdges, allEdges, caption, edgeData, edgeGraph, edgeKeys, edgeNum, edgeStats, edgeType, edgeTypes, inactiveEdges, _i, _len; edgeData = []; allEdges = a.get.allEdges().length; activeEdges = a.get.activeEdges().length; inactiveEdges = allEdges - activeEdges; edgeStats = "
    • Number of relationships: " + allEdges + "
    • Number of active relationships: " + activeEdges + "
    • Number of inactive relationships: " + inactiveEdges + "

    • "; if (a.conf.edgeTypes) { edgeKeys = _.values(alchemy.conf.edgeTypes)[0]; edgeTypes = ''; for (_i = 0, _len = edgeKeys.length; _i < _len; _i++) { edgeType = edgeKeys[_i]; if (!edgeType) { continue; } caption = edgeType.replace('_', ' '); edgeNum = _.filter(a.get.allEdges(), function(edge) { if (edge._edgeType === edgeType) { return edge; } }).length; edgeTypes += "
    • Number of " + caption + " relationships: " + edgeNum + "
    • "; edgeData.push(["" + caption, edgeNum]); } edgeStats += edgeTypes; } edgeGraph = "
    • "; edgeStats += edgeGraph; a.dash.select('#rel-stats').html(edgeStats); return this.insertSVG("edge", edgeData); }, insertSVG: function(element, data) { var arc, arcs, color, height, pie, radius, svg, width; if (data === null) { return a.dash.select("#" + element + "-stats-graph").html("

      There are no " + element + "Types listed in your conf.

      "); } else { width = a.conf.graphWidth() * .25; height = 250; radius = width / 4; color = d3.scale.category20(); arc = d3.svg.arc().outerRadius(radius - 10).innerRadius(radius / 2); pie = d3.layout.pie().sort(null).value(function(d) { return d[1]; }); svg = a.dash.select("#" + element + "-stats-graph").append("svg").append("g").style({ "width": width, "height": height }).attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); arcs = svg.selectAll(".arc").data(pie(data)).enter().append("g").classed("arc", true).on("mouseover", function(d, i) { return a.dash.select("#" + data[i][0] + "-stat").classed("hidden", false); }).on("mouseout", function(d, i) { return a.dash.select("#" + data[i][0] + "-stat").classed("hidden", true); }); arcs.append("path").attr("d", arc).attr("stroke", function(d, i) { return color(i); }).attr("stroke-width", 2).attr("fill-opacity", "0.3"); return arcs.append("text").attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; }).attr("id", function(d, i) { return "" + data[i][0] + "-stat"; }).attr("dy", ".35em").classed("hidden", true).text(function(d, i) { return data[i][0]; }); } }, update: function() { if (a.conf.nodeStats) { this.nodeStats(); } if (a.conf.edgeStats) { return this.edgeStats(); } } }; }; Alchemy.prototype.updateGraph = function(instance) { var a; a = instance; return function() { a.generateLayout(); a._drawEdges.createEdge(a.elements.edges.d3); a._drawNodes.createNode(a.elements.nodes.d3); a.index(); a.layout.positionRootNodes(); a.force.start(); while (a.force.alpha() > 0.005) { a.force.tick(); } a.force.on("tick", a.layout.tick).start(); return a.elements.nodes.svg.attr('transform', function(id, i) { return "translate(" + id.x + ", " + id.y + ")"; }); }; }; Alchemy.prototype.defaults = { plugins: null, renderer: "svg", graphWidth: function() { return d3.select(this.divSelector).node().parentElement.clientWidth; }, graphHeight: function() { if (d3.select(this.divSelector).node().parentElement.nodeName === "BODY") { return window.innerHeight; } else { return d3.select(this.divSelector).node().parentElement.clientHeight; } }, alpha: 0.5, collisionDetection: true, nodeOverlap: 25, fixNodes: false, fixRootNodes: false, forceLocked: true, linkDistancefn: 'default', nodePositions: null, showEditor: false, captionToggle: false, toggleRootNodes: false, removeElement: false, cluster: false, clusterKey: "cluster", clusterColours: d3.shuffle(["#DD79FF", "#FFFC00", "#00FF30", "#5168FF", "#00C0FF", "#FF004B", "#00CDCD", "#f83f00", "#f800df", "#ff8d8f", "#ffcd00", "#184fff", "#ff7e00"]), clusterControl: false, nodeStats: false, edgeStats: false, edgeFilters: false, nodeFilters: false, edgesToggle: false, nodesToggle: false, zoomControls: false, nodeCaption: 'caption', nodeCaptionsOnByDefault: false, nodeStyle: { "all": { "radius": 10, "color": "#68B9FE", "borderColor": "#127DC1", "borderWidth": function(d, radius) { return radius / 3; }, "captionColor": "#FFFFFF", "captionBackground": null, "captionSize": 12, "selected": { "color": "#FFFFFF", "borderColor": "#349FE3" }, "highlighted": { "color": "#EEEEFF" }, "hidden": { "color": "none", "borderColor": "none" } } }, nodeColour: null, nodeMouseOver: 'caption', nodeRadius: 10, nodeTypes: null, rootNodes: 'root', rootNodeRadius: 15, nodeClick: null, nodePadding: 0, edgeCaption: 'caption', edgeCaptionsOnByDefault: false, edgeStyle: { "all": { "width": 4, "color": "#CCC", "opacity": 0.2, "directed": true, "curved": true, "selected": { "opacity": 1 }, "highlighted": { "opacity": 1 }, "hidden": { "opacity": 0 } } }, edgeTypes: null, curvedEdges: false, edgeWidth: function() { return 4; }, edgeOverlayWidth: 20, directedEdges: false, edgeArrowSize: 5, edgeClick: null, search: false, searchMethod: "contains", backgroundColour: "#000000", theme: null, afterLoad: 'afterLoad', divSelector: '#alchemy', dataSource: null, initialScale: 1, initialTranslate: [0, 0], scaleExtent: [0.5, 2.4], exportSVG: false, dataWarning: "default", warningMessage: "There be no data! What's going on?" }; DrawEdge = function(instance) { return { a: instance, createLink: function(edge) { var conf; conf = this.a.conf; edge.append('path').attr('class', 'edge-line').attr('id', function(d) { return "path-" + d.id; }); return edge.filter(function(d) { return d.caption != null; }).append('text').append('textPath').classed("textpath", true); }, triangle: function(edge) { var height, hyp, width; width = edge.target.x - edge.source.x; height = edge.target.y - edge.source.y; hyp = Math.sqrt(height * height + width * width); return [width, height, hyp]; }, edgeData: function(edge) { var curveOffset, edgeLength, edgeWidth, height, hyp, startPathX, width, _ref; _ref = this.triangle(edge), width = _ref[0], height = _ref[1], hyp = _ref[2]; edgeWidth = edge['stroke-width']; curveOffset = 2; startPathX = edge.source.radius + edge.source['stroke-width'] - (edgeWidth / 2) + curveOffset; edgeLength = hyp - startPathX - curveOffset * 1.5; return { edgeAngle: Math.atan2(height, width) / Math.PI * 180, edgeLength: edgeLength }; }, edgeAngle: function(edge) { var height, width; width = edge.target.x - edge.source.x; height = edge.target.y - edge.source.y; return Math.atan2(height, width) / Math.PI * 180; }, edgeStyle: function(d) { var clustering, conf, edge, nodes, styles; conf = this.a.conf; edge = this.a._edges[d.id][d.pos]; styles = this.a.svgStyles.edge.populate(edge); nodes = this.a._nodes; if (this.a.conf.cluster) { clustering = this.a.layout._clustering; styles.stroke = (function(d) { var clusterKey, gid, id, index, source, target; clusterKey = conf.clusterKey; source = nodes[d.source.id]._properties; target = nodes[d.target.id]._properties; if (source.root || target.root) { index = source.root ? target[clusterKey] : source[clusterKey]; return "" + (clustering.getClusterColour(index)); } else if (source[clusterKey] === target[clusterKey]) { index = source[clusterKey]; return "" + (clustering.getClusterColour(index)); } else if (source[clusterKey] !== target[clusterKey]) { id = "" + source[clusterKey] + "-" + target[clusterKey]; gid = "cluster-gradient-" + id; return "url(#" + gid + ")"; } })(d); } return styles; }, edgeWalk: function(edge) { var A, B, C, a, angle, arcDegree, arcRadius, arrowWidth, c1, c2, cx, cy, d, distance, edgeLength, endAttachX, endAttachY, endNormal, endR, endTangent, endX, endY, g1, g2, gradient, hc, headLength, headRadius, homotheticCenter, p, padding, radiusRatio, shaftRadius, source, sourcePadding, square, startAttachX, startAttachY, startR, startTangent, startX, startY, target, targetPadding, xDist, yDist; a = this.a; square = function(num) { return num * num; }; source = edge.source; target = edge.target; if (!a.conf.curvedEdges) { padding = a.conf.nodePadding; sourcePadding = source.radius + padding + (source["stroke-width"] / 2); targetPadding = target.radius + padding + (target["stroke-width"] / 2); xDist = edge.source.x - edge.target.x; yDist = edge.source.y - edge.target.y; distance = Math.sqrt(square(xDist) + square(yDist)); if (!a.conf.directedEdges) { return "M " + sourcePadding + " 0 L " + (distance - targetPadding) + " 0"; } else { headLength = a.conf.edgeWidth() * 2.4; return "M " + sourcePadding + " 0 L " + (distance - targetPadding - headLength) + " 0 l 0 2 l " + (headLength / 2) + " -2 l " + (-headLength / 2) + " -2 L " + (distance - targetPadding - headLength) + " 0"; } } else { padding = a.conf.edgeWidth() * 1.7; arrowWidth = a.conf.edgeWidth() * 0.6; shaftRadius = arrowWidth / 2; headRadius = shaftRadius * 2.4; headLength = a.conf.directedEdges ? headRadius * 2 : 0.0001; xDist = edge.source.x - edge.target.x; yDist = edge.source.y - edge.target.y; edgeLength = Math.sqrt(square(xDist) + square(yDist)); startX = 0; startY = 0; startR = edge.source.radius; endX = edgeLength; endY = 0; endR = edge.target.radius; d = endX - startX; radiusRatio = (startR + padding) / (endR + headLength + padding); homotheticCenter = -d * radiusRatio / (1 - radiusRatio); arcDegree = 1.5; angle = arcDegree * headRadius * 2 / startR; startAttachX = Math.cos(angle) * (startR + padding); startAttachY = Math.sin(angle) * (startR + padding); gradient = startAttachY / (startAttachX - homotheticCenter); hc = startAttachY - gradient * startAttachX; p = endX; A = 1 + square(gradient); B = 2 * (gradient * hc - p); C = square(hc) + square(p) - square(endR + headLength + padding); endAttachX = (-B - Math.sqrt(square(B) - 4 * A * C)) / (2 * A); endAttachY = (endAttachX - homotheticCenter) * gradient; g1 = -startAttachX / startAttachY; c1 = startAttachY + (square(startAttachX) / startAttachY); g2 = -(endAttachX - endX) / endAttachY; c2 = endAttachY + (endAttachX - endX) * endAttachX / endAttachY; cx = (c1 - c2) / (g2 - g1); cy = g1 * cx + c1; arcRadius = Math.sqrt(square(cx - startAttachX) + square(cy - startAttachY)); startTangent = function(dr) { var dx, dy, num; if (dr < 0) { num = -1; } else { num = 1; } dx = num * Math.sqrt(square(dr) / (1 + square(g1))); dy = g1 * dx; return "" + (startAttachX + dx) + ", " + (startAttachY + dy); }; endTangent = function(dr) { var dx, dy, num; if (dr < 0) { num = -1; } else { num = 1; } dx = num * Math.sqrt(square(dr) / (1 + square(g2))); dy = g2 * dx; return "" + (endAttachX + dx) + ", " + (endAttachY + dy); }; endNormal = function(dc) { var dx, dy, num; if (dc < 0) { num = -1; } else { num = 1; } dx = num * Math.sqrt(square(dc) / (1 + square(1 / g2))); dy = dx / g2; return "" + (endAttachX + dx) + ", " + (endAttachY - dy); }; if (!a.conf.directedEdges) { return "M " + (startTangent(-shaftRadius)) + " A " + (arcRadius - shaftRadius) + ", " + (arcRadius - shaftRadius) + " 0 0 0 " + (endTangent(-shaftRadius)); } else { return "M " + (startTangent(-shaftRadius)) + " L " + (startTangent(shaftRadius)) + " A " + (arcRadius - shaftRadius) + ", " + (arcRadius - shaftRadius) + " 0 0 0 " + (endTangent(-shaftRadius)) + " L " + (endTangent(-headRadius)) + " L " + (endNormal(headLength)) + " L " + (endTangent(headRadius)) + " L " + (endTangent(shaftRadius)) + " A " + (arcRadius + shaftRadius) + ", " + (arcRadius + shaftRadius) + " 0 0 1 " + (startTangent(-shaftRadius)) + " Z"; } } }, styleLink: function(edges) { var a, conf, utils; a = this.a; conf = a.conf; utils = a.drawing.DrawEdge; return edges.each(function(edge) { var edgeData, g; g = d3.select(this); edgeData = utils.edgeData(edge); g.attr('transform', "translate(" + edge.source.x + ", " + edge.source.y + ") rotate(" + (utils.edgeAngle(edge)) + ")"); return g.select('.edge-line').attr('d', (function() { return utils.edgeWalk(edge); })()).attr('stroke-width', (function() { return a.conf.edgeWidth; })()).style(utils.edgeStyle(edge)); }); }, classEdge: function(edge) { return edge.classed('active', true); }, styleText: function(edge) { var conf; conf = this.a.conf; return edge.select('text').each(function(d) { var dx, dy, edgeLength, xDist, yDist; xDist = d.source.x - d.target.x; yDist = d.source.y - d.target.y; edgeLength = Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2)); ({ captionAngle: function(angle) { if (angle < -90 || angle > 90) { return 180; } else { return 0; } } }); dx = edgeLength / 2; dy = -d['stroke-width'] * 2; return d3.select(this).attr('dx', "" + dx).attr("dy", "" + dy).select(".textpath").text(d.caption).attr("xlink:xlink:href", "#path-" + d.source.id + "-" + d.target.id).style("display", function(d) { if (conf.edgeCaptionsOnByDefault) { return "block"; } }); }); }, setInteractions: function(edges) { var interactions; interactions = this.a.interactions; return edges.on('click', interactions.edgeClick).on('mouseover', function(d) { return interactions.edgeMouseOver(d); }).on('mouseout', function(d) { return interactions.edgeMouseOut(d); }); } }; }; DrawEdges = function(instance) { return { a: instance, createEdge: function(d3Edges) { var d3edges, drawEdge; drawEdge = this.a.drawing.DrawEdge; d3edges = this.a.vis.selectAll("g.edge").data(d3Edges); d3edges.enter().append('g').attr("id", function(d) { return "edge-" + d.id + "-" + d.pos; }).attr('class', function(d) { return "edge " + d.edgeType; }).attr('source-target', function(d) { return "" + d.source.id + "-" + d.target.id; }); drawEdge.createLink(d3edges); drawEdge.classEdge(d3edges); drawEdge.styleLink(d3edges); drawEdge.styleText(d3edges); drawEdge.setInteractions(d3edges); return d3edges.exit().remove(); }, updateEdge: function(d3Edge) { var drawEdge, edge; drawEdge = this.a.drawing.DrawEdge; edge = this.a.vis.select("#edge-" + d3Edge.id + "-" + d3Edge.pos); drawEdge.classEdge(edge); drawEdge.styleLink(edge); drawEdge.styleText(edge); return drawEdge.setInteractions(edge); } }; }; DrawNode = function(instance) { return { a: instance, styleText: function(node) { var conf, nodes, utils; conf = this.a.conf; utils = this.a.drawing.NodeUtils; nodes = this.a._nodes; return node.selectAll("text").attr('dy', function(d) { if (nodes[d.id].getProperties().root) { return conf.rootNodeRadius / 2 + (typeof(conf.nodeStyle.all.captionOffset)!='undefined'?conf.nodeStyle.all.captionOffset:0); } else { return conf.nodeRadius / 2 + (typeof(conf.nodeStyle.all.captionOffset)!='undefined'?conf.nodeStyle.all.captionOffset:0); } }).attr('visibility', function(d) { if (nodes[d.id]._state === "hidden") { return "hidden"; } else { return "visible"; } }).text(function(d) { return utils.nodeText(d); }).style("display", function(d) { if (conf.nodeCaptionsOnByDefault) { return "block"; } }); }, createNode: function(node) { node = _.difference(node, node.select("circle").data()); node.__proto__ = d3.select().__proto__; node.append('circle').attr('id', function(d) { return "circle-" + d.id; }); return node.append('svg:text').attr('id', function(d) { return "text-" + d.id; }); }, styleNode: function(node) { var utils; utils = this.a.drawing.NodeUtils; return node.selectAll('circle').attr('r', function(d) { if (typeof d.radius === "function") { return d.radius(); } else { return d.radius; } }).each(function(d) { return d3.select(this).style(utils.nodeStyle(d)); }); }, setInteractions: function(node) { var conf, coreInteractions, drag, editorEnabled, editorInteractions, nonRootNodes, rootNodes; conf = this.a.conf; coreInteractions = this.a.interactions; editorEnabled = this.a.get.state("interactions") === "editor"; drag = d3.behavior.drag().origin(Object).on("dragstart", null).on("drag", null).on("dragend", null); if (editorEnabled) { editorInteractions = new this.a.editor.Interactions; return node.on('mouseup', function(d) { return editorInteractions.nodeMouseUp(d); }).on('mouseover', function(d) { return editorInteractions.nodeMouseOver(d); }).on('mouseout', function(d) { return editorInteractions.nodeMouseOut(d); }).on('dblclick', function(d) { return coreInteractions.nodeDoubleClick(d); }).on('click', function(d) { return editorInteractions.nodeClick(d); }); } else { node.on('mouseup', null).on('mouseover', function(d) { return coreInteractions.nodeMouseOver(d); }).on('mouseout', function(d) { return coreInteractions.nodeMouseOut(d); }).on('dblclick', function(d) { return coreInteractions.nodeDoubleClick(d); }).on('click', function(d) { return coreInteractions.nodeClick(d); }); drag = d3.behavior.drag().origin(Object).on("dragstart", coreInteractions.nodeDragStarted).on("drag", coreInteractions.nodeDragged).on("dragend", coreInteractions.nodeDragended); if (!conf.fixNodes) { nonRootNodes = node.filter(function(d) { return d.root !== true; }); nonRootNodes.call(drag); } if (!conf.fixRootNodes) { rootNodes = node.filter(function(d) { return d.root === true; }); return rootNodes.call(drag); } } } }; }; DrawNodes = function(instance) { return { a: instance, createNode: function(d3Nodes) { var drawNode, node; drawNode = this.a.drawing.DrawNode; node = this.a.vis.selectAll("g.node").data(d3Nodes, function(n) { return n.id; }); node.enter().append("g").attr("class", function(d) { var nodeType; nodeType = d.self._nodeType; return "node " + nodeType + " active"; }).attr('id', function(d) { return "node-" + d.id; }).classed('root', function(d) { return d.root; }); drawNode.createNode(node); drawNode.styleNode(node); drawNode.styleText(node); drawNode.setInteractions(node); return node.exit().remove(); }, updateNode: function(alchemyNode) { var drawNode, node; drawNode = this.a.drawing.DrawNode; node = this.a.vis.select("#node-" + alchemyNode.id); drawNode.styleNode(node); drawNode.styleText(node); return drawNode.setInteractions(node); } }; }; Alchemy.prototype.NodeUtils = function(instance) { var a; a = instance; return { nodeStyle: function(d) { var conf, node; conf = a.conf; node = d.self; if (conf.cluster && (node._state !== "hidden")) { d.fill = (function(d) { var clusterMap, clustering, colour, colourIndex, colours, key, nodeProp; clustering = a.layout._clustering; nodeProp = node.getProperties(); clusterMap = clustering.clusterMap; key = conf.clusterKey; colours = conf.clusterColours; colourIndex = clusterMap[nodeProp[key]] % colours.length; colour = colours[colourIndex]; return "" + colour; })(d); d.stroke = d.fill; } return d; }, nodeText: function(d) { var caption, conf, nodeProps; conf = a.conf; nodeProps = a._nodes[d.id]._properties; if (conf.nodeCaption && typeof conf.nodeCaption === 'string') { if (nodeProps[conf.nodeCaption] != null) { return nodeProps[conf.nodeCaption]; } else { return ''; } } else if (conf.nodeCaption && typeof conf.nodeCaption === 'function') { caption = conf.nodeCaption(nodeProps); if (caption === void 0 || String(caption) === 'undefined') { a.log["caption"] = "At least one caption returned undefined"; conf.caption = false; } return caption; } } }; }; Alchemy.prototype.svgStyles = function(instance) { return { a: instance, node: { a: this.a, populate: function(node) { var conf, d, defaultStyle, fill, nodeType, nodeTypeKey, radius, stroke, strokeWidth, style, svgStyles, toFunc, typedStyle; conf = this.a.conf; defaultStyle = _.omit(conf.nodeStyle.all, "selected", "highlighted", "hidden"); d = node; toFunc = function(inp) { if (typeof inp === "function") { return inp; } return function() { return inp; }; }; nodeTypeKey = _.keys(conf.nodeTypes)[0]; nodeType = node.getProperties()[nodeTypeKey]; if (conf.nodeStyle[nodeType] === void 0) { nodeType = "all"; } typedStyle = _.assign(_.cloneDeep(defaultStyle), conf.nodeStyle[nodeType]); style = _.assign(typedStyle, conf.nodeStyle[nodeType][node._state]); radius = toFunc(style.radius); fill = toFunc(style.color); stroke = toFunc(style.borderColor); strokeWidth = toFunc(style.borderWidth); svgStyles = {}; svgStyles["radius"] = radius(d); svgStyles["fill"] = fill(d); svgStyles["stroke"] = stroke(d); svgStyles["stroke-width"] = strokeWidth(d, radius(d)); return svgStyles; } }, edge: { a: this.a, populate: function(edge) { var color, conf, defaultStyle, edgeType, opacity, style, svgStyles, toFunc, typedStyle, width; conf = this.a.conf; defaultStyle = _.omit(conf.edgeStyle.all, "selected", "highlighted", "hidden"); toFunc = function(inp) { if (typeof inp === "function") { return inp; } return function() { return inp; }; }; edgeType = edge._edgeType; if (conf.edgeStyle[edgeType] === void 0) { edgeType = "all"; } typedStyle = _.assign(_.cloneDeep(defaultStyle), conf.edgeStyle[edgeType]); style = _.assign(typedStyle, conf.edgeStyle[edgeType][edge._state]); width = toFunc(style.width); color = toFunc(style.color); opacity = toFunc(style.opacity); svgStyles = { "stroke": color(edge), "stroke-width": width(edge), "opacity": opacity(edge), "fill": "none" }; return svgStyles; }, update: function(edge) { var color, conf, opacity, style, svgStyles, toFunc, width; conf = this.a.conf; style = edge._style; toFunc = function(inp) { if (typeof inp === "function") { return inp; } return function() { return inp; }; }; width = toFunc(style.width); color = toFunc(style.color); opacity = toFunc(style.opacity); svgStyles = { "stroke": color(edge), "stroke-width": width(edge), "opacity": opacity(edge), "fill": "none" }; return svgStyles; } } }; }; Editor = (function() { function Editor() { this.nodeEditor = __bind(this.nodeEditor, this); this.startEditor = __bind(this.startEditor, this); this.utils = new alchemy.editor.Utils; } Editor.prototype.editorContainerHTML = "
      \n

      Editor

      \n
      \n
      \n
        \n
      • Remove Selected
      • \n
      • Editor mode enabled, click to disable editor interactions
      • \n
      \n
      "; Editor.prototype.elementEditorHTML = function(type) { return "

      " + type + " Editor

      \n
      \n
      \n \n \n
      \n \n
      \n
      \n \n
      "; }; Editor.prototype.startEditor = function() { var divSelector, editor, editor_interactions, html, utils; divSelector = alchemy.conf.divSelector; html = this.editorContainerHTML; editor = alchemy.dash.select("#control-dash").append("div").attr("id", "editor").html(html); editor.select('#editor-header').on('click', function() { if (alchemy.dash.select('#element-options').classed("in")) { return alchemy.dash.select("#editor-header>span").attr("class", "fa fa-2x fa-caret-right"); } else { return alchemy.dash.select("#editor-header>span").attr("class", "fa fa-2x fa-caret-down"); } }); editor_interactions = editor.select('#element-options ul #editor-interactions').on('click', function() { return d3.select(this).attr("class", function() { if (alchemy.get.state() === 'editor') { alchemy.set.state('interactions', 'default'); return "inactive list-group-item"; } else { alchemy.set.state('interactions', 'editor'); return "active list-group-item"; } }).html(function() { if (alchemy.get.state() === 'editor') { return "Disable Editor Interactions"; } else { return "Enable Editor Interactions"; } }); }); editor.select("#element-options ul #remove").on("click", function() { return alchemy.editor.remove(); }); utils = this.utils; return editor_interactions.on("click", function() { if (!alchemy.dash.select("#editor-interactions").classed("active")) { utils.enableEditor(); return alchemy.dash.select("#editor-interactions").classed({ "active": true, "inactive": false }).html("Editor mode enabled, click to disable editor interactions"); } else { utils.disableEditor(); return alchemy.dash.select("#editor-interactions").classed({ "active": false, "inactive": true }).html("Editor mode disabled, click to enable editor interactions"); } }); }; Editor.prototype.nodeEditor = function(n) { var add_property, divSelector, editor, elementEditor, html, nodeProperties, node_property, options, property, property_list, updateProperty, val; divSelector = alchemy.conf.divSelector; editor = alchemy.dash.select("#control-dash #editor"); options = editor.select('#element-options'); html = this.elementEditorHTML("Node"); elementEditor = options.append('div').attr('id', 'node-editor').html(html); elementEditor.attr("class", function() { var active; active = alchemy.dash.select("#editor-interactions").classed("active"); if (active) { return "enabled"; } return "hidden"; }); add_property = editor.select("#node-editor form #add-property"); add_property.select("#node-add-prop-key").attr("placeholder", "New Property Name").attr("value", null); add_property.select("#node-add-prop-value").attr("placeholder", "New Property Value").attr("value", null); alchemy.dash.select("#add-property-form").on("submit", function() { var key, value; event.preventDefault(); key = alchemy.dash.select('#add-prop-key').property('value'); key = key.replace(/\s/g, "_"); value = alchemy.dash.select('#add-prop-value').property('value'); updateProperty(key, value, true); alchemy.dash.selectAll("#add-property .edited-property").classed({ "edited-property": false }); return this.reset(); }); nodeProperties = alchemy._nodes[n.id].getProperties(); alchemy.vis.select("#node-" + n.id).classed({ "editing": true }); property_list = editor.select("#node-editor #properties-list"); for (property in nodeProperties) { val = nodeProperties[property]; node_property = property_list.append("div").attr("id", "node-" + property).attr("class", "property form-inline form-group"); node_property.append("label").attr("for", "node-" + property + "-input").attr("class", "form-control property-name").text("" + property); node_property.append("input").attr("id", "node-" + property + "-input").attr("class", "form-control property-value").attr("value", "" + val); } alchemy.dash.select("#properties-list").on("submit", function() { var key, properties, selection, value, _i, _len, _ref; event.preventDefault(); properties = alchemy.dash.selectAll(".edited-property"); _ref = properties[0]; for (_i = 0, _len = _ref.length; _i < _len; _i++) { property = _ref[_i]; selection = alchemy.dash.select(property); key = selection.select("label").text(); value = selection.select("input").attr('value'); updateProperty(key, value, false); } alchemy.dash.selectAll("#node-properties-list .edited-property").classed({ "edited-property": false }); return this.reset(); }); d3.selectAll("#add-prop-key, #add-prop-value, .property").on("keydown", function() { if (d3.event.keyCode === 13) { event.preventDefault(); } return d3.select(this).classed({ "edited-property": true }); }); return updateProperty = function(key, value, newProperty) { var drawNodes, nodeID; nodeID = n.id; if ((key !== "") && (value !== "")) { alchemy._nodes[nodeID].setProperty("" + key, "" + value); drawNodes = alchemy._drawNodes; drawNodes.updateNode(alchemy.viz.select("#node-" + nodeID)); if (newProperty === true) { alchemy.dash.select("#node-add-prop-key").attr("value", "property added/updated to key: " + key); return alchemy.dash.select("#node-add-prop-value").attr("value", "property at " + key + " updated to: " + value); } else { return alchemy.dash.select("#node-" + key + "-input").attr("value", "property at " + key + " updated to: " + value); } } else { if (newProperty === true) { alchemy.dash.select("#node-add-prop-key").attr("value", "null or invalid input"); return alchemy.dash.select("#node-add-prop-value").attr("value", "null or invlid input"); } else { return alchemy.dash.select("#node-" + key + "-input").attr("value", "null or invalid input"); } } }; }; Editor.prototype.editorClear = function() { alchemy.dash.selectAll(".node").classed({ "editing": false }); alchemy.dash.selectAll(".edge").classed({ "editing": false }); alchemy.dash.select("#node-editor").remove(); alchemy.dash.select("#edge-editor").remove(); return alchemy.dash.select("#node-add-prop-submit").attr("placeholder", function() { if (alchemy.vis.selectAll(".selected").empty()) { return "select a node or edge to edit properties"; } return "add a property to this element"; }); }; Editor.prototype.edgeEditor = function(e) { var add_property, divSelector, edgeProperties, edge_property, editor, elementEditor, html, options, property, property_list, updateProperty, val; divSelector = alchemy.conf.divSelector; editor = alchemy.dash("#control-dash #editor"); options = editor.select('#element-options'); html = this.elementEditorHTML("Edge"); elementEditor = options.append('div').attr('id', 'edge-editor').html(html); elementEditor.attr("class", function() { if (alchemy.dash.select("#editor-interactions").classed("active")) { return "enabled"; } return "hidden"; }); add_property = editor.select("#edge-editor form #add-property"); add_property.select("#add-prop-key").attr("placeholder", "New Property Name").attr("value", null); add_property.select("#add-prop-value").attr("placeholder", "New Property Value").attr("value", null); edgeProperties = alchemy._edges[e.id].getProperties(); alchemy.vis.select("#edge-" + e.id).classed({ "editing": true }); property_list = editor.select("#edge-editor #properties-list"); for (property in edgeProperties) { val = edgeProperties[property]; edge_property = property_list.append("div").attr("id", "edge-" + property).attr("class", "property form-inline form-group"); edge_property.append("label").attr("for", "edge-" + property + "-input").attr("class", "form-control property-name").text("" + property); edge_property.append("input").attr("id", "edge-" + property + "-input").attr("class", "form-control property-value").attr("value", "" + val); } alchemy.dash.selectAll("#add-prop-key, #add-prop-value, .property").on("keydown", function() { if (d3.event.keyCode === 13) { event.preventDefault(); } return d3.select(this).classed({ "edited-property": true }); }); alchemy.dash.select("#add-property-form").on("submit", function() { var key, value; event.preventDefault(); key = alchemy.dash.select("#add-prop-key").property('value'); key = key.replace(/\s/g, "_"); value = alchemy.dash.select("#add-prop-value").property('value'); updateProperty(key, value, true); alchemy.dash.selectAll("#add-property .edited-property").classed({ "edited-property": false }); return this.reset(); }); d3.select("#properties-list").on("submit", function() { var key, properties, selection, value, _i, _len, _ref; event.preventDefault(); properties = alchemy.dash.selectAll(".edited-property"); _ref = properties[0]; for (_i = 0, _len = _ref.length; _i < _len; _i++) { property = _ref[_i]; selection = alchemy.dash.select(property); key = selection.select("label").text(); value = selection.select("input").property('value'); updateProperty(key, value, false); } alchemy.dash.selectAll("#properties-list .edited-property").classed({ "edited-property": false }); return this.reset(); }); return updateProperty = function(key, value, newProperty) { var drawEdges, edgeID, edgeSelection; edgeID = e.id; if ((key !== "") && (value !== "")) { alchemy._edges[edgeID].setProperty("" + key, "" + value); edgeSelection = alchemy.vis.select("#edge-" + edgeID); drawEdges = new alchemy.drawing.DrawEdges; drawEdges.updateEdge(alchemy.vis.select("#edge-" + edgeID)); if (newProperty === true) { alchemy.dash.select("#add-prop-key").attr("value", "property added/updated to key: " + key); return alchemy.dash.select("#add-prop-value").attr("value", "property at " + key + " updated to: " + value); } else { return alchemy.dash.select("#edge-" + key + "-input").attr("value", "property at " + key + " updated to: " + value); } } else { if (newProperty === true) { alchemy.dash.select("#add-prop-key").attr("value", "null or invalid input"); return alchemy.dash.select("#add-prop-value").attr("value", "null or invlid input"); } else { return alchemy.dash.select("#edge-" + key + "-input").attr("value", "null or invalid input"); } } }; }; return Editor; })(); EditorInteractions = (function() { function EditorInteractions() { this.reset = __bind(this.reset, this); this.deleteSelected = __bind(this.deleteSelected, this); this.addNodeDragended = __bind(this.addNodeDragended, this); this.addNodeDragging = __bind(this.addNodeDragging, this); this.addNodeStart = __bind(this.addNodeStart, this); this.edgeClick = __bind(this.edgeClick, this); this.nodeClick = __bind(this.nodeClick, this); this.nodeMouseUp = __bind(this.nodeMouseUp, this); this.editor = new alchemy.editor.Editor; } EditorInteractions.prototype.nodeMouseOver = function(n) { var radius; if (!d3.select(this).select("circle").empty()) { radius = d3.select(this).select("circle").attr("r"); d3.select(this).select("circle").attr("r", radius * 3); } return this; }; EditorInteractions.prototype.nodeMouseUp = function(n) { if (this.sourceNode !== n) { this.mouseUpNode = true; this.targetNode = n; this.click = false; } else { this.click = true; } return this; }; EditorInteractions.prototype.nodeMouseOut = function(n) { var radius; if (!d3.select(this).select("circle").empty()) { radius = d3.select(this).select("circle").attr("r"); d3.select(this).select("circle").attr("r", radius / 3); } return this; }; EditorInteractions.prototype.nodeClick = function(c) { var selected; d3.event.stopPropagation(); if (!alchemy.vis.select("#node-" + c.id).empty()) { selected = alchemy.vis.select("#node-" + c.id).classed('selected'); alchemy.vis.select("#node-" + c.id).classed('selected', !selected); } this.editor.editorClear(); return this.editor.nodeEditor(c); }; EditorInteractions.prototype.edgeClick = function(e) { d3.event.stopPropagation(); this.editor.editorClear(); return this.editor.edgeEditor(e); }; EditorInteractions.prototype.addNodeStart = function(d, i) { d3.event.sourceEvent.stopPropagation(); this.sourceNode = d; alchemy.vis.select('#dragline').classed({ "hidden": false }); return this; }; EditorInteractions.prototype.addNodeDragging = function(d, i) { var x2coord, y2coord; x2coord = d3.event.x; y2coord = d3.event.y; alchemy.vis.select('#dragline').attr("x1", this.sourceNode.x).attr("y1", this.sourceNode.y).attr("x2", x2coord).attr("y2", y2coord).attr("style", "stroke: #FFF"); return this; }; EditorInteractions.prototype.addNodeDragended = function(d, i) { var dragline, targetX, targetY; if (!this.click) { if (!this.mouseUpNode) { dragline = alchemy.vis.select("#dragline"); targetX = dragline.attr("x2"); targetY = dragline.attr("y2"); this.targetNode = { id: "" + (_.uniqueId('addedNode_')), x: parseFloat(targetX), y: parseFloat(targetY), caption: "node added" }; } this.newEdge = { id: "" + this.sourceNode.id + "-" + this.targetNode.id, source: this.sourceNode.id, target: this.targetNode.id, caption: "edited" }; alchemy.editor.update(this.targetNode, this.newEdge); } this.reset(); return this; }; EditorInteractions.prototype.deleteSelected = function(d) { switch (d3.event.keyCode) { case 8: case 46: if (!(d3.select(d3.event.target).node().tagName === ("INPUT" || "TEXTAREA"))) { d3.event.preventDefault(); return alchemy.editor.remove(); } } }; EditorInteractions.prototype.reset = function() { this.mouseUpNode = null; this.sourceNode = null; this.targetNode = null; this.newEdge = null; this.click = null; alchemy.vis.select("#dragline").classed({ "hidden": true }).attr("x1", 0).attr("y1", 0).attr("x2", 0).attr("y2", 0); return this; }; EditorInteractions; return EditorInteractions; })(); EditorUtils = (function() { function EditorUtils() { this.enableEditor = __bind(this.enableEditor, this); this.drawNodes = alchemy._drawNodes; this.drawEdges = alchemy._drawEdges; } EditorUtils.prototype.enableEditor = function() { var dragLine, editor, selectedElements; alchemy.set.state("interactions", "editor"); dragLine = alchemy.vis.append("line").attr("id", "dragline"); this.drawNodes.updateNode(alchemy.node); this.drawEdges.updateEdge(alchemy.edge); selectedElements = alchemy.vis.selectAll(".selected"); editor = new alchemy.editor.Editor; if ((!selectedElements.empty()) && (selectedElements.length === 1)) { if (selectedElements.classed('node')) { editor.nodeEditor(selectedElements.datum()); return alchemy.dash.select("#node-editor").attr("class", "enabled").style("opacity", 1); } else if (selectedElements.classed('edge')) { editor.edgeEditor(selectedElements.datum()); return alchemy.dash.select("#edge-editor").attr("class", "enabled").style("opacity", 1); } } else { return selectedElements.classed({ "selected": false }); } }; EditorUtils.prototype.disableEditor = function() { alchemy.setState("interactions", "default"); alchemy.vis.select("#dragline").remove(); alchemy.dash.select("#node-editor").transition().duration(300).style("opacity", 0); alchemy.dash.select("#node-editor").transition().delay(300).attr("class", "hidden"); this.drawNodes.updateNode(alchemy.node); return alchemy.vis.selectAll(".node").classed({ "selected": false }); }; EditorUtils.prototype.remove = function() { var edge, node, nodeID, node_data, selectedNodes, _i, _j, _len, _len1, _ref, _ref1, _results; selectedNodes = alchemy.vis.selectAll(".selected.node"); _ref = selectedNodes[0]; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { node = _ref[_i]; nodeID = alchemy.vis.select(node).data()[0].id; node_data = alchemy._nodes[nodeID]; if (node_data != null) { _ref1 = node_data.adjacentEdges; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { edge = _ref1[_j]; alchemy._edges = _.omit(alchemy._edges, "" + edge.id + "-" + edge._index); alchemy.edge = alchemy.edge.data(_.map(alchemy._edges, function(e) { return e._d3; }), function(e) { return e.id; }); alchemy.vis.select("#edge-" + edge.id + "-" + edge._index).remove(); } alchemy._nodes = _.omit(alchemy._nodes, "" + nodeID); alchemy.node = alchemy.node.data(_.map(alchemy._nodes, function(n) { return n._d3; }), function(n) { return n.id; }); alchemy.vis.select(node).remove(); if (alchemy.get.state("interactions") === "editor") { _results.push(alchemy.modifyElements.nodeEditorClear()); } else { _results.push(void 0); } } else { _results.push(void 0); } } return _results; }; EditorUtils.prototype.addNode = function(node) { var newNode; newNode = alchemy._nodes[node.id] = new alchemy.models.Node({ id: "" + node.id }); newNode.setProperty("caption", node.caption); newNode.setD3Property("x", node.x); newNode.setD3Property("y", node.y); return alchemy.node = alchemy.node.data(_.map(alchemy._nodes, function(n) { return n._d3; }), function(n) { return n.id; }); }; EditorUtils.prototype.addEdge = function(edge) { var newEdge; newEdge = alchemy._edges[edge.id] = new alchemy.models.Edge(edge); return alchemy.edge = alchemy.edge.data(_.map(alchemy._edges, function(e) { return e._d3; }), function(e) { return e.id; }); }; EditorUtils.prototype.update = function(node, edge) { if (!this.mouseUpNode) { alchemy.editor.addNode(node); alchemy.editor.addEdge(edge); this.drawEdges.createEdge(alchemy.edge); this.drawNodes.createNode(alchemy.node); } else { alchemy.editor.addEdge(edge); this.drawEdges.createEdge(alchemy.edge); } return alchemy.layout.tick(); }; return EditorUtils; })(); Alchemy.prototype.Edge = function(instance) { var Edge; return Edge = (function() { function Edge(edge, index) { var conf; if (index == null) { index = null; } this.allNodesActive = __bind(this.allNodesActive, this); this.setProperties = __bind(this.setProperties, this); this.getStyles = __bind(this.getStyles, this); this.setProperties = __bind(this.setProperties, this); this.getProperties = __bind(this.getProperties, this); this._setID = __bind(this._setID, this); this._setD3Properties = __bind(this._setD3Properties, this); this.a = instance; conf = this.a.conf; this.id = this._setID(edge); this._index = index; this._state = "active"; this._properties = edge; this._edgeType = this._setEdgeType(); this._style = conf.edgeStyle[this._edgeType] != null ? _.merge(_.clone(conf.edgeStyle["all"]), conf.edgeStyle[this._edgeType]) : _.clone(conf.edgeStyle["all"]); this._d3 = _.merge({ 'id': this.id, 'pos': this._index, 'edgeType': this._edgeType, 'source': this.a._nodes[this._properties.source]._d3, 'target': this.a._nodes[this._properties.target]._d3, 'self': this }, this.a.svgStyles.edge.populate(this)); this._setCaption(edge, conf); this.a._nodes["" + edge.source]._addEdge(this); this.a._nodes["" + edge.target]._addEdge(this); } Edge.prototype._setD3Properties = function(props) { return _.merge(this._d3, props); }; Edge.prototype._setID = function(e) { if (e.id != null) { return e.id; } else { return "" + e.source + "-" + e.target; } }; Edge.prototype._setCaption = function(edge, conf) { var cap, edgeCaption; cap = conf.edgeCaption; edgeCaption = (function(edge) { switch (typeof cap) { case 'string' || 'number': return edge[cap]; case 'function': return cap(edge); } })(edge); if (edgeCaption) { return this._d3.caption = edgeCaption; } }; Edge.prototype._setEdgeType = function() { var conf, edgeType, lookup; conf = this.a.conf; if (conf.edgeTypes) { if (_.isPlainObject(conf.edgeTypes)) { lookup = Object.keys(this.a.conf.edgeTypes); edgeType = this._properties[lookup]; } else if (_.isArray(conf.edgeTypes)) { edgeType = this._properties["caption"]; } else if (typeof conf.edgeTypes === 'string') { edgeType = this._properties[conf.edgeTypes]; } } if (edgeType === void 0) { edgeType = "all"; } this._setD3Properties('edgeType', edgeType); return edgeType; }; Edge.prototype.getProperties = function() { var key, keys, query; key = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; if (key == null) { key = null; } if ((key == null) && (keys.length === 0)) { return this._properties; } else if (keys.length !== 0) { query = _.union([key], keys); return _.pick(this._properties, query); } else { return this._properties[key]; } }; Edge.prototype.setProperties = function(property, value) { if (value == null) { value = null; } if (_.isPlainObject(property)) { _.assign(this._properties, property); if ('source' in property) { this._setD3Properties({ 'source': alchemy._nodes[property.source]._d3 }); } if ('target' in property) { this._setD3Properties({ 'target': alchemy._nodes[property.target]._d3 }); } } else { this._properties[property] = value; if ((property === 'source') || (property === 'target')) { this._setD3Properties({ property: alchemy._nodes[value]._d3 }); } } return this; }; Edge.prototype.getStyles = function() { var edge, key, keys; key = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; edge = this; if (key === void 0) { return edge._style; } return _.map(arguments, function(arg) { return edge._style[arg]; }); }; Edge.prototype.setProperties = function(property, value) { if (value == null) { value = null; } if (_.isPlainObject(property)) { _.assign(this._properties, property); if ('source' in property) { this._setD3Properties({ 'source': this.a._nodes[property.source]._d3 }); } if ('target' in property) { this._setD3Properties({ 'target': this.a._nodes[property.target]._d3 }); } } else { this._properties[property] = value; if ((property === 'source') || (property === 'target')) { this._setD3Properties({ property: this.a._nodes[value]._d3 }); } } return this; }; Edge.prototype.setStyles = function(key, value) { if (value == null) { value = null; } if (key === void 0) { key = this.a.svgStyles.edge.populate(this); } else if (_.isPlainObject(key)) { _.assign(this._style, key); } else { this._style[key] = value; } this._setD3Properties(this.a.svgStyles.edge.update(this)); this.a._drawEdges.updateEdge(this._d3); return this; }; Edge.prototype.toggleHidden = function() { this._state = this._state === "hidden" ? "active" : "hidden"; return this.setStyles(); }; Edge.prototype.allNodesActive = function() { var sourceId, sourceNode, targetId, targetNode; sourceId = this._properties.source; targetId = this._properties.target; sourceNode = alchemy.get.nodes(sourceId)[0]; targetNode = alchemy.get.nodes(targetId)[0]; return sourceNode._state === "active" && targetNode._state === "active"; }; Edge.prototype.remove = function() { var edge, filteredLinkList; edge = this; delete this.a._edges[edge.id]; if (this.a._nodes[edge._properties.source] != null) { _.remove(this.a._nodes[edge._properties.source]._adjacentEdges, function(e) { if (e.id === edge.id) { return e; } }); } if (this.a._nodes[edge._properties.target] != null) { _.remove(this.a._nodes[edge._properties.target]._adjacentEdges, function(e) { if (e.id === edge.id) { return e; } }); } this.a.vis.select("#edge-" + edge.id + "-" + edge._index).remove(); filteredLinkList = _.filter(this.a.force.links(), function(link) { if (link.id !== edge.id) { return link; } }); return this.a.force.links(filteredLinkList); }; return Edge; })(); }; Alchemy.prototype.Node = function(instance) { var Node; return Node = (function() { function Node(node) { this.getStyles = __bind(this.getStyles, this); this.setProperty = __bind(this.setProperty, this); this.getProperties = __bind(this.getProperties, this); this._setD3Properties = __bind(this._setD3Properties, this); this._setNodeType = __bind(this._setNodeType, this); var conf; this.a = instance; conf = this.a.conf; this.id = node.id; this._properties = node; this._d3 = _.merge({ 'id': this.id, 'root': this._properties[conf.rootNodes], 'self': this }, this.a.svgStyles.node.populate(this)); this._nodeType = this._setNodeType(); this._style = conf.nodeStyle[this._nodeType] ? conf.nodeStyle[this._nodeType] : conf.nodeStyle["all"]; this._state = "active"; this._adjacentEdges = []; } Node.prototype._setNodeType = function() { var conf, lookup, nodeType, types; conf = this.a.conf; if (conf.nodeTypes) { if (_.isPlainObject(conf.nodeTypes)) { lookup = Object.keys(this.a.conf.nodeTypes); types = _.values(conf.nodeTypes); nodeType = this._properties[lookup]; } else if (typeof conf.nodeTypes === 'string') { nodeType = this._properties[conf.nodeTypes]; } } if (nodeType === void 0) { nodeType = "all"; } this._setD3Properties('nodeType', nodeType); return nodeType; }; Node.prototype._setD3Properties = function(props) { return _.merge(this._d3, props); }; Node.prototype._addEdge = function(edge) { return this._adjacentEdges = _.union(this._adjacentEdges, [edge]); }; Node.prototype.getProperties = function() { var key, keys, query; key = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; if (key == null) { key = null; } if ((key == null) && (keys.length === 0)) { return this._properties; } else if (keys.length !== 0) { query = _.union([key], keys); return _.pick(this._properties, query); } else { return this._properties[key]; } }; Node.prototype.setProperty = function(property, value) { if (value == null) { value = null; } if (_.isPlainObject(property)) { _.assign(this._properties, property); } else { this._properties[property] = value; } return this; }; Node.prototype.removeProperty = function() { var prop, properties, property, _i, _len; property = arguments[0], properties = 2 <= arguments.length ? __slice.call(arguments, 1) : []; for (_i = 0, _len = arguments.length; _i < _len; _i++) { prop = arguments[_i]; delete this._properties[prop]; } return this; }; Node.prototype.getStyles = function() { var key, keys, node; key = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; node = this; if (key === void 0) { return node._style; } return _.map(arguments, function(arg) { return node._style[arg]; }); }; Node.prototype.setStyles = function(key, value) { if (value == null) { value = null; } if (key === void 0) { key = this.a.svgStyles.node.populate(this); } else if (_.isPlainObject(key)) { _.assign(this._style, key); } else { this._style[key] = value; } this._setD3Properties(this.a.svgStyles.node.populate(this)); this.a._drawNodes.updateNode(this._d3); return this; }; Node.prototype.toggleHidden = function() { var a; a = this.a; this._state = this._state === "hidden" ? "active" : "hidden"; this.setStyles(); return _.each(this._adjacentEdges, function(e) { var source, sourceState, target, targetState, _ref; _ref = e.id.split("-"), source = _ref[0], target = _ref[1]; sourceState = a._nodes["" + source]._state; targetState = a._nodes["" + target]._state; if (e._state === "hidden" && (sourceState === "active" && targetState === "active")) { return e.toggleHidden(); } else if (e._state === "active" && (sourceState === "hidden" || targetState === "hidden")) { return e.toggleHidden(); } }); }; Node.prototype.outDegree = function() { return this._adjacentEdges.length; }; Node.prototype.remove = function() { while (!_.isEmpty(this._adjacentEdges)) { this._adjacentEdges[0].remove(); } delete this.a._nodes[this.id]; return this.a.vis.select("#node-" + this.id).remove(); }; return Node; })(); }; Alchemy.prototype.plugins = function(instance) { return { init: function() { return _.each(_.keys(instance.conf.plugins), function(key) { instance.plugins[key] = Alchemy.prototype.plugins[key](instance); if (instance.plugins[key].init != null) { return instance.plugins[key].init(); } }); } }; }; Alchemy.prototype.themes = { "default": { "backgroundColour": "#000000", "nodeStyle": { "all": { "radius": function() { return 10; }, "color": function() { return "#68B9FE"; }, "borderColor": function() { return "#127DC1"; }, "borderWidth": function(d, radius) { return radius / 3; }, "captionColor": function() { return "#FFFFFF"; }, "captionBackground": function() { return null; }, "captionSize": 12, "selected": { "color": function() { return "#FFFFFF"; }, "borderColor": function() { return "#349FE3"; } }, "highlighted": { "color": function() { return "#EEEEFF"; } }, "hidden": { "color": function() { return "none"; }, "borderColor": function() { return "none"; } } } }, "edgeStyle": { "all": { "width": 4, "color": "#CCC", "opacity": 0.2, "directed": true, "curved": true, "selected": { "opacity": 1 }, "highlighted": { "opacity": 1 }, "hidden": { "opacity": 0 } } } }, "white": { "theme": "white", "backgroundColour": "#FFFFFF", "nodeStyle": { "all": { "radius": function() { return 10; }, "color": function() { return "#68B9FE"; }, "borderColor": function() { return "#127DC1"; }, "borderWidth": function(d, radius) { return radius / 3; }, "captionColor": function() { return "#FFFFFF"; }, "captionBackground": function() { return null; }, "captionSize": 12, "selected": { "color": function() { return "#FFFFFF"; }, "borderColor": function() { return "38DD38"; } }, "highlighted": { "color": function() { return "#EEEEFF"; } }, "hidden": { "color": function() { return "none"; }, "borderColor": function() { return "none"; } } } }, "edgeStyle": { "all": { "width": 4, "color": "#333", "opacity": 0.4, "directed": false, "curved": false, "selected": { "color": "#38DD38", "opacity": 0.9 }, "highlighted": { "color": "#383838", "opacity": 0.7 }, "hidden": { "opacity": 0 } } } } }; Alchemy.prototype.exports = function(instance) { var a; a = instance; return { init: function() { return a.exports.show(); }, show: function() { return a.dash.select("#all-exports").append("li").attr({ "class": "list-group-item active-label toggle" }).html("SVG").on("click", function(e) { var sanitizedUrl, str, svg, url, win; svg = d3.select("" + a.conf.divSelector + " svg").node(); str = (new XMLSerializer).serializeToString(svg); url = "data:image/svg+xml;utf8," + str; sanitizedUrl = url.replace("xlink:", ""); win = window.open(sanitizedUrl); return win.focus(); }); } }; }; warnings = (function() { function warnings(instance) { this.dataWarning = __bind(this.dataWarning, this); this.a = instance; } warnings.prototype.dataWarning = function() { if (this.a.conf.dataWarning && typeof this.a.conf.dataWarning === 'function') { return this.a.conf.dataWarning(); } else if (this.a.conf.dataWarning === 'default') { return console.log("No dataSource was loaded"); } }; warnings.prototype.divWarning = function() { return "create an element that matches the value for 'divSelector' in your conf.\nFor instance, if you are using the default 'divSelector' conf, simply provide\n
      ."; }; return warnings; })(); }).call(this); //# sourceMappingURL=alchemy.js.map




      © 2015 - 2025 Weber Informatics LLC | Privacy Policy