diff --git a/_includes/cytoscape.html b/_includes/cytoscape.html index 74dac64..ea19d63 100644 --- a/_includes/cytoscape.html +++ b/_includes/cytoscape.html @@ -1,7 +1,10 @@ diff --git a/_includes/head.html b/_includes/head.html index 6a841c3..09a277b 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -82,6 +82,7 @@ {% endif %} {% if page.cytoscape %} + {% endif %} diff --git a/public/js/cytoscape/cytoscape-automove.js b/public/js/cytoscape/cytoscape-automove.js new file mode 100644 index 0000000..7cb6b89 --- /dev/null +++ b/public/js/cytoscape/cytoscape-automove.js @@ -0,0 +1,711 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["cytoscapeAutomove"] = factory(); + else + root["cytoscapeAutomove"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 3); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +var defaults = __webpack_require__(2); + +var typeofStr = _typeof(''); +var typeofObj = _typeof({}); +var typeofFn = _typeof(function () {}); + +var isObject = function isObject(x) { + return (typeof x === 'undefined' ? 'undefined' : _typeof(x)) === typeofObj; +}; +var isString = function isString(x) { + return (typeof x === 'undefined' ? 'undefined' : _typeof(x)) === typeofStr; +}; +var isFunction = function isFunction(x) { + return (typeof x === 'undefined' ? 'undefined' : _typeof(x)) === typeofFn; +}; +var isCollection = function isCollection(x) { + return isObject(x) && isFunction(x.collection); +}; + +// Object.assign() polyfill +var assign = __webpack_require__(1); + +var eleExists = function eleExists(ele) { + return ele != null && !ele.removed(); +}; + +var _elesHasEle = function elesHasEle(eles, ele) { + if (eles.has != undefined) { + // 3.x + _elesHasEle = function elesHasEle(eles, ele) { + return eles.has(ele); + }; + } else { + // 2.x + _elesHasEle = function elesHasEle(eles, ele) { + return eles.intersection(ele).length > 0; + }; + } + + return _elesHasEle(eles, ele); +}; + +var getEleMatchesSpecFn = function getEleMatchesSpecFn(spec) { + if (isString(spec)) { + return function (ele) { + return ele.is(spec); + }; + } else if (isFunction(spec)) { + return spec; + } else if (isCollection(spec)) { + return function (ele) { + return _elesHasEle(spec, ele); + }; + } else { + throw new Error('Can not create match function for spec', spec); + } +}; + +var bindings = []; + +var bind = function bind(cy, events, selector, fn) { + var b = { cy: cy, events: events, selector: selector || 'node', fn: fn }; + + bindings.push(b); + + cy.on(b.events, b.selector, b.fn); + + return b; +}; + +var bindOnRule = function bindOnRule(rule, cy, events, selector, fn) { + var b = bind(cy, events, selector, fn); + var bindings = rule.bindings = rule.bindings || []; + + bindings.push(b); +}; + +var unbindAllOnRule = function unbindAllOnRule(rule) { + var unbind = function unbind(b) { + b.cy.off(b.events, b.selector, b.fn); + }; + + rule.bindings.forEach(unbind); + + rule.bindings = []; +}; + +var getRepositioner = function getRepositioner(rule, cy) { + var r = rule.reposition; + + if (r === 'mean') { + return meanNeighborhoodPosition(getEleMatchesSpecFn(rule.meanIgnores)); + } else if (r === 'viewport') { + return viewportPosition(cy); + } else if (r === 'drag') { + return dragAlong(rule); + } else if (isObject(r)) { + if (r.type == undefined || r.type == "inside") { + return boxPosition(r); + } else if (r.type == "outside") { + return outsideBoxPosition(r); + } + } else { + return r; + } +}; + +var dragAlong = function dragAlong(rule) { + return function (node) { + var pos = node.position(); + var delta = rule.delta; + + if (rule.delta != null && !node.same(rule.grabbedNode) && !node.grabbed()) { + return { + x: pos.x + delta.x, + y: pos.y + delta.y + }; + } + }; +}; + +var meanNeighborhoodPosition = function meanNeighborhoodPosition(ignore) { + return function (node) { + var nhood = node.neighborhood(); + var avgPos = { x: 0, y: 0 }; + var nhoodSize = 0; + + for (var i = 0; i < nhood.length; i++) { + var nhoodEle = nhood[i]; + + if (nhoodEle.isNode() && !ignore(nhoodEle)) { + var pos = nhoodEle.position(); + + avgPos.x += pos.x; + avgPos.y += pos.y; + + nhoodSize++; + } + } + + // the position should remain unchanged if we would stack the nodes on top of each other + if (nhoodSize < 2) { + return undefined; + } + + avgPos.x /= nhoodSize; + avgPos.y /= nhoodSize; + + return avgPos; + }; +}; + +var constrain = function constrain(val, min, max) { + return val < min ? min : val > max ? max : val; +}; + +var constrainInBox = function constrainInBox(node, bb) { + var pos = node.position(); + + return { + x: constrain(pos.x, bb.x1, bb.x2), + y: constrain(pos.y, bb.y1, bb.y2) + }; +}; + +var boxPosition = function boxPosition(bb) { + return function (node) { + return constrainInBox(node, bb); + }; +}; + +var constrainOutsideBox = function constrainOutsideBox(node, bb) { + var pos = node.position(); + var x = pos.x, + y = pos.y; + var x1 = bb.x1, + y1 = bb.y1, + x2 = bb.x2, + y2 = bb.y2; + + var inX = x1 <= x && x <= x2; + var inY = y1 <= y && y <= y2; + var abs = Math.abs; + + if (inX && inY) { + // inside + var dx1 = abs(x1 - x); + var dx2 = abs(x2 - x); + var dy1 = abs(y1 - y); + var dy2 = abs(y2 - y); + var min = Math.min(dx1, dx2, dy1, dy2); // which side of box is closest? + + // get position outside, by closest side of box + if (min === dx1) { + return { x: x1, y: y }; + } else if (min === dx2) { + return { x: x2, y: y }; + } else if (min === dy1) { + return { x: x, y: y1 }; + } else { + // min === dy2 + return { x: x, y: y2 }; + } + } else { + // outside already + return { x: x, y: y }; + } +}; + +var outsideBoxPosition = function outsideBoxPosition(bb) { + return function (node) { + return constrainOutsideBox(node, bb); + }; +}; + +var viewportPosition = function viewportPosition(cy) { + return function (node) { + var extent = cy.extent(); + var w = node.outerWidth(); + var h = node.outerHeight(); + var bb = { + x1: extent.x1 + w / 2, + x2: extent.x2 - w / 2, + y1: extent.y1 + h / 2, + y2: extent.y2 - h / 2 + }; + + return constrainInBox(node, bb); + }; +}; + +var meanListener = function meanListener(rule) { + return function (update, cy) { + var matches = function matches(ele) { + // must meet ele set and be connected to more than (1 edge + 1 node) + return rule.matches(ele) && ele.neighborhood().length > 2 && !ele.grabbed(); + }; + + bindOnRule(rule, cy, 'position', 'node', function () { + var movedNode = this; + + if (movedNode.neighborhood().some(matches) || rule.meanOnSelfPosition(movedNode) && matches(movedNode)) { + update(cy, [rule]); + } + }); + + bindOnRule(rule, cy, 'add remove', 'edge', function () { + var edge = this; + var src = cy.getElementById(edge.data('source')); + var tgt = cy.getElementById(edge.data('target')); + + if ([src, tgt].some(matches)) { + update(cy, [rule]); + } + }); + }; +}; + +var dragListener = function dragListener(rule) { + return function (update, cy) { + bindOnRule(rule, cy, 'grab', 'node', function () { + var node = this; + + if (rule.dragWithMatches(node)) { + var p = node.position(); + + rule.grabbedNode = node; + rule.p1 = { x: p.x, y: p.y }; + rule.delta = { x: 0, y: 0 }; + } + }); + + bindOnRule(rule, cy, 'drag', 'node', function () { + var node = this; + + if (node.same(rule.grabbedNode)) { + var d = rule.delta; + var p1 = rule.p1; + var p = node.position(); + var p2 = { x: p.x, y: p.y }; + + d.x = p2.x - p1.x; + d.y = p2.y - p1.y; + + rule.p1 = p2; + + update(cy, [rule]); + } + }); + + bindOnRule(rule, cy, 'free', 'node', function () { + rule.grabbedNode = null; + rule.delta = null; + rule.p1 = null; + }); + }; +}; + +var matchingNodesListener = function matchingNodesListener(rule) { + return function (update, cy) { + bindOnRule(rule, cy, 'position', 'node', function () { + var movedNode = this; + + if (rule.matches(movedNode)) { + update(cy, [rule]); + } + }); + }; +}; + +var getListener = function getListener(cy, rule) { + if (rule.reposition === 'mean') { + return meanListener(rule); + } else if (rule.reposition === 'drag') { + return dragListener(rule); + } else if (isObject(rule.reposition) || rule.when === 'matching' || rule.reposition === 'viewport') { + return matchingNodesListener(rule); + } else { + return rule.when; + } +}; + +var addRule = function addRule(cy, scratch, options) { + var rule = assign({}, defaults, options); + + rule.getNewPos = getRepositioner(rule, cy); + rule.listener = getListener(cy, rule); + + var nodesAreCollection = isCollection(rule.nodesMatching); + + if (nodesAreCollection) { + rule.nodes = rule.nodesMatching.slice(); + + rule.matches = function (ele) { + return eleExists(ele) && _elesHasEle(rule.nodes, ele); + }; + } else { + var matches = getEleMatchesSpecFn(rule.nodesMatching); + + rule.matches = function (ele) { + return eleExists(ele) && matches(ele); + }; + } + + if (rule.dragWith != null) { + rule.dragWithMatches = getEleMatchesSpecFn(rule.dragWith); + } + + rule.listener(function () { + update(cy, [rule]); + }, cy); + + rule.enabled = true; + + scratch.rules.push(rule); + + return rule; +}; + +var bindForNodeList = function bindForNodeList(cy, scratch) { + scratch.onAddNode = function (evt) { + var target = evt.target; + + scratch.nodes.merge(target); + }; + + scratch.onRmNode = function (evt) { + var target = evt.target; + + scratch.nodes.unmerge(target); + }; + + cy.on('add', 'node', scratch.onAddNode); + cy.on('remove', 'node', scratch.onRmNode); +}; + +var unbindForNodeList = function unbindForNodeList(cy, scratch) { + cy.removeListener('add', 'node', scratch.onAddNode); + cy.removeListener('remove', 'node', scratch.onRmNode); +}; + +var update = function update(cy, rules) { + var scratch = cy.scratch().automove; + + rules = rules != null ? rules : scratch.rules; + + cy.batch(function () { + // batch for performance + for (var i = 0; i < rules.length; i++) { + var rule = rules[i]; + + if (rule.destroyed || !rule.enabled) { + break; + } // ignore destroyed rules b/c user may use custom when() + + var nodes = rule.nodes || scratch.nodes; + + for (var j = nodes.length - 1; j >= 0; j--) { + var node = nodes[j]; + + if (node.removed()) { + // remove from list for perf + nodes.unmerge(node); + continue; + } + + if (!rule.matches(node)) { + continue; + } + + var pos = node.position(); + var newPos = rule.getNewPos(node); + var newPosIsDiff = newPos != null && (pos.x !== newPos.x || pos.y !== newPos.y); + + if (newPosIsDiff) { + // only update on diff for perf + node.position(newPos); + + node.trigger('automove', [rule]); + } + } + } + }); +}; + +var automove = function automove(options) { + var cy = this; + + var scratch = cy.scratch().automove = cy.scratch().automove || { + rules: [] + }; + + if (scratch.rules.length === 0) { + scratch.nodes = cy.nodes().slice(); + + bindForNodeList(cy, scratch); + } + + if (options === 'destroy') { + scratch.rules.forEach(function (r) { + r.destroy(); + }); + scratch.rules.splice(0, scratch.rules.length); + + unbindForNodeList(cy, scratch); + + return; + } + + var rule = addRule(cy, scratch, options); + + update(cy, [rule]); // do an initial update to make sure the start state is correct + + return { + apply: function apply() { + update(cy, [rule]); + }, + + disable: function disable() { + this.toggle(false); + }, + + enable: function enable() { + this.toggle(true); + }, + + enabled: function enabled() { + return rule.enabled; + }, + + toggle: function toggle(on) { + rule.enabled = on !== undefined ? on : !rule.enabled; + + if (rule.enabled) { + update(cy, [rule]); + } + }, + + destroy: function destroy() { + var rules = scratch.rules; + + unbindAllOnRule(rule); + + rule.destroyed = true; + + rules.splice(rules.indexOf(rule), 1); + + if (rules.length === 0) { + unbindForNodeList(cy, scratch); + } + + return this; + } + }; +}; + +module.exports = automove; + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +// Simple, internal Object.assign() polyfill for options objects etc. + +module.exports = Object.assign != null ? Object.assign.bind(Object) : function (tgt) { + for (var _len = arguments.length, srcs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + srcs[_key - 1] = arguments[_key]; + } + + srcs.forEach(function (src) { + Object.keys(src).forEach(function (k) { + return tgt[k] = src[k]; + }); + }); + + return tgt; +}; + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* eslint-disable no-unused-vars */ +var defaults = { + // specify nodes that should be automoved with one of + // - a function that returns true for matching nodes + // - a selector that matches the nodes + // - a collection of nodes (very good for performance) + nodesMatching: function nodesMatching(node) { + return false; + }, + + // specify how a node's position should be updated with one of + // - function( node ){ return { x: 1, y: 2 }; } => put the node where the function returns + // - { x1, y1, x2, y2 } => constrain the node position within the bounding box (in model co-ordinates) + // - { x1, y1, x2, y2, type: 'inside' } => constrain the node position within the bounding box (in model co-ordinates) + // - { x1, y1, x2, y2, type: 'outside' } => constrain the node position outside the bounding box (in model co-ordinates) + // - 'mean' => put the node in the average position of its neighbourhood + // - 'viewport' => keeps the node body within the viewport + // - 'drag' => matching nodes are effectively dragged along + reposition: 'mean', + + // specify when the repositioning should occur by specifying a function that + // calls update() when reposition updates should occur + // - function( update ){ /* ... */ update(); } => a manual function for updating + // - 'matching' => automatically update on position events for nodesMatching + // - set efficiently and automatically for + // - reposition: 'mean' + // - reposition: { x1, y1, x2, y2 } + // - reposition: 'viewport' + // - reposition: 'drag' + // - default/undefined => on a position event for any node (not as efficient...) + when: undefined, + + // + // customisation options for non-function `reposition` values + // + + // `reposition: 'mean'` + + // specify nodes that should be ignored in the mean calculation + // - a function that returns true for nodes to be ignored + // - a selector that matches the nodes to be ignored + // - a collection of nodes to be ignored (very good for performance) + meanIgnores: function meanIgnores(node) { + return false; + }, + + // specify whether moving a particular `nodesMatching` node causes repositioning + // - true : the mid node can't be independently moved/dragged + // - false : the mid node can be independently moved/dragged (useful if you want the mid node to use `reposition: 'drag' in another rule with its neighbourhood`) + meanOnSelfPosition: function meanOnSelfPosition(node) { + return true; + }, + + // `reposition: 'drag'` + + // specify nodes that when dragged cause the matched nodes to move along (i.e. the master nodes) + // - a function that returns true for nodes to be listened to for drag events + // - a selector that matches the nodes to be listened to for drag events + // - a collection of nodes to be listened to for drag events (very good for performance) + dragWith: function dragWith(node) { + return false; + } +}; + +/* eslint-enable */ + +module.exports = defaults; + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var automove = __webpack_require__(0); + +// registers the extension on a cytoscape lib ref +var register = function register(cytoscape) { + if (!cytoscape) { + return; + } // can't register if cytoscape unspecified + + cytoscape('core', 'automove', automove); // register with cytoscape.js +}; + +if (typeof cytoscape !== 'undefined') { + // expose to global cytoscape (i.e. window.cytoscape) + register(cytoscape); +} + +module.exports = register; + +/***/ }) +/******/ ]); +}); \ No newline at end of file