From 9e6c5d41ccb85063ccaa7640d2972bf809e1e456 Mon Sep 17 00:00:00 2001 From: Uwe Steinmann Date: Wed, 31 Aug 2016 16:09:37 +0200 Subject: [PATCH] add grid extension for cytoscape --- .../cytoscape/cytoscape-grid-guide.js | 1276 +++++++++++++++++ views/bootstrap/class.WorkflowGraph.php | 11 +- 2 files changed, 1285 insertions(+), 2 deletions(-) create mode 100644 styles/bootstrap/cytoscape/cytoscape-grid-guide.js diff --git a/styles/bootstrap/cytoscape/cytoscape-grid-guide.js b/styles/bootstrap/cytoscape/cytoscape-grid-guide.js new file mode 100644 index 000000000..50946c510 --- /dev/null +++ b/styles/bootstrap/cytoscape/cytoscape-grid-guide.js @@ -0,0 +1,1276 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + /** Used as the `TypeError` message for "Functions" methods. */ + var FUNC_ERROR_TEXT = 'Expected a function'; + + /* Native method references for those with the same name as other `lodash` methods. */ + var nativeMax = Math.max, + nativeNow = Date.now; + + /** + * Gets the number of milliseconds that have elapsed since the Unix epoch + * (1 January 1970 00:00:00 UTC). + * + * @static + * @memberOf _ + * @category Date + * @example + * + * _.defer(function(stamp) { + * console.log(_.now() - stamp); + * }, _.now()); + * // => logs the number of milliseconds it took for the deferred function to be invoked + */ + var now = nativeNow || function() { + return new Date().getTime(); + }; + + /** + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked. The debounced function comes with a `cancel` method to cancel + * delayed invocations. Provide an options object to indicate that `func` + * should be invoked on the leading and/or trailing edge of the `wait` timeout. + * Subsequent calls to the debounced function return the result of the last + * `func` invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked + * on the trailing edge of the timeout only if the the debounced function is + * invoked more than once during the `wait` timeout. + * + * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation) + * for details over the differences between `_.debounce` and `_.throttle`. + * + * @static + * @memberOf _ + * @category Function + * @param {Function} func The function to debounce. + * @param {number} [wait=0] The number of milliseconds to delay. + * @param {Object} [options] The options object. + * @param {boolean} [options.leading=false] Specify invoking on the leading + * edge of the timeout. + * @param {number} [options.maxWait] The maximum time `func` is allowed to be + * delayed before it's invoked. + * @param {boolean} [options.trailing=true] Specify invoking on the trailing + * edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * // avoid costly calculations while the window size is in flux + * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); + * + * // invoke `sendMail` when the click event is fired, debouncing subsequent calls + * jQuery('#postbox').on('click', _.debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * })); + * + * // ensure `batchLog` is invoked once after 1 second of debounced calls + * var source = new EventSource('/stream'); + * jQuery(source).on('message', _.debounce(batchLog, 250, { + * 'maxWait': 1000 + * })); + * + * // cancel a debounced call + * var todoChanges = _.debounce(batchLog, 1000); + * Object.observe(models.todo, todoChanges); + * + * Object.observe(models, function(changes) { + * if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) { + * todoChanges.cancel(); + * } + * }, ['delete']); + * + * // ...at some point `models.todo` is changed + * models.todo.completed = true; + * + * // ...before 1 second has passed `models.todo` is deleted + * // which cancels the debounced `todoChanges` call + * delete models.todo; + */ + function debounce(func, wait, options) { + var args, + maxTimeoutId, + result, + stamp, + thisArg, + timeoutId, + trailingCall, + lastCalled = 0, + maxWait = false, + trailing = true; + + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + wait = wait < 0 ? 0 : (+wait || 0); + if (options === true) { + var leading = true; + trailing = false; + } else if (isObject(options)) { + leading = !!options.leading; + maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait); + trailing = 'trailing' in options ? !!options.trailing : trailing; + } + + function cancel() { + if (timeoutId) { + clearTimeout(timeoutId); + } + if (maxTimeoutId) { + clearTimeout(maxTimeoutId); + } + lastCalled = 0; + maxTimeoutId = timeoutId = trailingCall = undefined; + } + + function complete(isCalled, id) { + if (id) { + clearTimeout(id); + } + maxTimeoutId = timeoutId = trailingCall = undefined; + if (isCalled) { + lastCalled = now(); + result = func.apply(thisArg, args); + if (!timeoutId && !maxTimeoutId) { + args = thisArg = undefined; + } + } + } + + function delayed() { + var remaining = wait - (now() - stamp); + if (remaining <= 0 || remaining > wait) { + complete(trailingCall, maxTimeoutId); + } else { + timeoutId = setTimeout(delayed, remaining); + } + } + + function maxDelayed() { + complete(trailing, timeoutId); + } + + function debounced() { + args = arguments; + stamp = now(); + thisArg = this; + trailingCall = trailing && (timeoutId || !leading); + + if (maxWait === false) { + var leadingCall = leading && !timeoutId; + } else { + if (!maxTimeoutId && !leading) { + lastCalled = stamp; + } + var remaining = maxWait - (stamp - lastCalled), + isCalled = remaining <= 0 || remaining > maxWait; + + if (isCalled) { + if (maxTimeoutId) { + maxTimeoutId = clearTimeout(maxTimeoutId); + } + lastCalled = stamp; + result = func.apply(thisArg, args); + } + else if (!maxTimeoutId) { + maxTimeoutId = setTimeout(maxDelayed, remaining); + } + } + if (isCalled && timeoutId) { + timeoutId = clearTimeout(timeoutId); + } + else if (!timeoutId && wait !== maxWait) { + timeoutId = setTimeout(delayed, wait); + } + if (leadingCall) { + isCalled = true; + result = func.apply(thisArg, args); + } + if (isCalled && !timeoutId && !maxTimeoutId) { + args = thisArg = undefined; + } + return result; + } + debounced.cancel = cancel; + return debounced; + } + + /** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ + function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); + } + + return debounce; + +})(); + +module.exports = debounce; +},{}],3:[function(require,module,exports){ +module.exports = function (cy, snap) { + + var discreteDrag = {}; + + var attachedNode; + var draggedNodes; + + var startPos; + var endPos; + + + discreteDrag.onTapStartNode = function (e) { + if (e.cyTarget.selected()) + draggedNodes = e.cy.$(":selected"); + else + draggedNodes = e.cyTarget; + + startPos = e.cyPosition; + + attachedNode = e.cyTarget; + attachedNode.lock(); + attachedNode.trigger("grab"); + cy.on("tapdrag", onTapDrag); + cy.on("tapend", onTapEndNode); + + }; + + var onTapEndNode = function (e) { + //attachedNode.trigger("free"); + cy.off("tapdrag", onTapDrag); + cy.off("tapend", onTapEndNode); + attachedNode.unlock(); + e.preventDefault(); + }; + + var getDist = function () { + return { + x: endPos.x - startPos.x, + y: endPos.y - startPos.y + } + }; + + function getTopMostNodes(nodes) { + var nodesMap = {}; + + for (var i = 0; i < nodes.length; i++) { + nodesMap[nodes[i].id()] = true; + } + + var roots = nodes.filter(function (i, ele) { + var parent = ele.parent()[0]; + while (parent != null) { + if (nodesMap[parent.id()]) { + return false; + } + parent = parent.parent()[0]; + } + return true; + }); + + return roots; + } + + var moveNodesTopDown = function (nodes, dx, dy) { + +/* + console.log(nodes.map(function (e) { + return e.id(); + })); + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + var pos = node.position(); + + if (!node.isParent()) { + node.position({ + x: pos.x + dx, + y: pos.y + dy + }); + console.log(node.id() + " " + dx + " " + dy); + } + + moveNodesTopDown(nodes.children(), dx, dy); + } +*/ + }; + + var onTapDrag = function (e) { + + var nodePos = attachedNode.position(); + endPos = e.cyPosition; + endPos = snap.snapPos(endPos); + var dist = getDist(); + if (dist.x != 0 || dist.y != 0) { + attachedNode.unlock(); + //var topMostNodes = getTopMostNodes(draggedNodes); + var nodes = draggedNodes.union(draggedNodes.descendants()); + + nodes.positions(function (i, node) { + var pos = node.position(); + return snap.snapPos({ + x: pos.x + dist.x, + y: pos.y + dist.y + }); + }); + + startPos = endPos; + attachedNode.lock(); + attachedNode.trigger("drag"); + } + + }; + + return discreteDrag; + + +}; +},{}],4:[function(require,module,exports){ +module.exports = function (opts, cy, $, debounce) { + + var options = opts; + + var changeOptions = function (opts) { + options = opts; + }; + + + var $canvas = $( '' ); + var $container = $( cy.container() ); + var ctx = $canvas[ 0 ].getContext( '2d' ); + $container.append( $canvas ); + + var drawGrid = function() { + clearDrawing(); + + var zoom = cy.zoom(); + var canvasWidth = $container.width(); + var canvasHeight = $container.height(); + var increment = options.gridSpacing*zoom; + var pan = cy.pan(); + var initialValueX = pan.x%increment; + var initialValueY = pan.y%increment; + + ctx.strokeStyle = options.strokeStyle; + ctx.lineWidth = options.lineWidth; + + if(options.zoomDash) { + var zoomedDash = options.lineDash.slice(); + + for(var i = 0; i < zoomedDash.length; i++) { + zoomedDash[ i ] = options.lineDash[ i ]*zoom; + } + ctx.setLineDash( zoomedDash ); + } else { + ctx.setLineDash( options.lineDash ); + } + + if(options.panGrid) { + ctx.lineDashOffset = -pan.y; + } else { + ctx.lineDashOffset = 0; + } + + for(var i = initialValueX; i < canvasWidth; i += increment) { + ctx.beginPath(); + ctx.moveTo( i, 0 ); + ctx.lineTo( i, canvasHeight ); + ctx.stroke(); + } + + if(options.panGrid) { + ctx.lineDashOffset = -pan.x; + } else { + ctx.lineDashOffset = 0; + } + + for(var i = initialValueY; i < canvasHeight; i += increment) { + ctx.beginPath(); + ctx.moveTo( 0, i ); + ctx.lineTo( canvasWidth, i ); + ctx.stroke(); + } + }; + var clearDrawing = function() { + var width = $container.width(); + var height = $container.height(); + + ctx.clearRect( 0, 0, width, height ); + }; + + var resizeCanvas = debounce(function() { + $canvas + .attr( 'height', $container.height() ) + .attr( 'width', $container.width() ) + .css( { + 'position': 'absolute', + 'top': 0, + 'left': 0, + 'z-index': options.gridStackOrder + } ); + + setTimeout( function() { + var canvasBb = $canvas.offset(); + var containerBb = $container.offset(); + + $canvas + .attr( 'height', $container.height() ) + .attr( 'width', $container.width() ) + .css( { + 'top': -( canvasBb.top - containerBb.top ), + 'left': -( canvasBb.left - containerBb.left ) + } ); + drawGrid(); + }, 0 ); + + }, 250); + + + + + return { + initCanvas: resizeCanvas, + resizeCanvas: resizeCanvas, + clearCanvas: clearDrawing, + drawGrid: drawGrid, + changeOptions: changeOptions, + sizeCanvas: drawGrid + }; +}; +},{}],5:[function(require,module,exports){ +module.exports = function (cy, snap, resize, discreteDrag, drawGrid, guidelines, parentPadding, $) { + + var feature = function (func) { + return function (enable) { + func(enable); + }; + }; + + var controller = { + discreteDrag: new feature(setDiscreteDrag), + resize: new feature(setResize), + snapToGrid: new feature(setSnapToGrid), + drawGrid: new feature(setDrawGrid), + guidelines: new feature(setGuidelines), + parentPadding: new feature(setParentPadding) + }; + + function applyToCyTarget(func, allowParent) { + return function (e) { + if (!e.cyTarget.is(":parent") || allowParent) + func(e.cyTarget); + } + } + + function applyToActiveNodes(func, allowParent) { + return function (e) { + if (!e.cyTarget.is(":parent") || allowParent) + if (e.cyTarget.selected()) + func(e.cyTarget, e.cy.$(":selected")); + else + func(e.cyTarget, e.cyTarget); + } + } + + function applyToAllNodesButNoParent(func) { + return function () { + cy.nodes().not(":parent").each(function (i, ele) { + func(ele); + }); + }; + } + function applyToAllNodes(func) { + return function () { + cy.nodes().each(function (i, ele) { + func(ele); + }); + }; + } + + function eventStatus(enable) { + return enable ? "on" : "off"; + } + + + // Discrete Drag + function setDiscreteDrag(enable) { + cy[eventStatus(enable)]("tapstart", "node", discreteDrag.onTapStartNode); + } + + // Resize + var resizeAllNodes = applyToAllNodesButNoParent(resize.resizeNode); + var resizeNode = applyToCyTarget(resize.resizeNode); + var recoverAllNodeDimensions = applyToAllNodesButNoParent(resize.recoverNodeDimensions); + + function setResize(enable) { + cy[eventStatus(enable)]("ready", resizeAllNodes); + // cy[eventStatus(enable)]("style", "node", resizeNode); + enable ? resizeAllNodes() : recoverAllNodeDimensions(); + } + + // Snap To Grid + var snapAllNodes = applyToAllNodes(snap.snapNodesTopDown); + var recoverSnapAllNodes = applyToAllNodes(snap.recoverSnapNode); + var snapCyTarget = applyToCyTarget(snap.snapNode, true); + + function setSnapToGrid(enable) { + cy[eventStatus(enable)]("add", "node", snapCyTarget); + cy[eventStatus(enable)]("ready", snapAllNodes); + + cy[eventStatus(enable)]("free", "node", snap.onFreeNode); + + if (enable) { + snapAllNodes(); + } else { + recoverSnapAllNodes(); + } + } + + // Draw Grid + var drawGridOnZoom = function () { + if (currentOptions.zoomDash) drawGrid.drawGrid() + }; + var drawGridOnPan = function () { + if (currentOptions.panGrid) drawGrid.drawGrid() + }; + + function setDrawGrid(enable) { + cy[eventStatus(enable)]('zoom', drawGridOnZoom); + cy[eventStatus(enable)]('pan', drawGridOnPan); + cy[eventStatus(enable)]('ready', drawGrid.resizeCanvas); + + if (enable) { + drawGrid.initCanvas(); + $(window).on('resize', drawGrid.resizeCanvas); + } else { + drawGrid.clearCanvas(); + $(window).off('resize', drawGrid.resizeCanvas); + } + } + + // Guidelines + + function setGuidelines(enable) { + cy[eventStatus(enable)]('zoom', guidelines.onZoom); + cy[eventStatus(enable)]('drag', "node", guidelines.onDragNode); + cy[eventStatus(enable)]('grab', "node", guidelines.onGrabNode); + cy[eventStatus(enable)]('free', "node", guidelines.onFreeNode); + + } + + // Parent Padding + var setAllParentPaddings = function (enable) { + parentPadding.setPaddingOfParent(cy.nodes(":parent"), enable); + }; + var enableParentPadding = function (node) { + parentPadding.setPaddingOfParent(node, true); + }; + + + function setParentPadding(enable) { + + setAllParentPaddings(enable); + + cy[eventStatus(enable)]('ready', setAllParentPaddings); + cy[eventStatus(enable)]("add", "node:parent", applyToCyTarget(enableParentPadding, true)); + } + + // Sync with options: Enables/disables changed via options. + var latestOptions = {}; + var currentOptions; + + var specialOpts = { + drawGrid: ["gridSpacing", "zoomDash", "panGrid", "gridStackOrder", "strokeStyle", "lineWidth", "lineDash"], + guidelines: ["gridSpacing", "guidelinesStackOrder", "guidelinesTolerance", "guidelinesStyle"], + resize: ["gridSpacing"], + parentPadding: ["gridSpacing", "parentSpacing"], + snapToGrid: ["gridSpacing"] + }; + + function syncWithOptions(options) { + currentOptions = $.extend(true, {}, options); + for (var key in options) + if (latestOptions[key] != options[key]) + if (controller.hasOwnProperty(key)) { + controller[key](options[key]); + } else { + for (var optsKey in specialOpts) { + var opts = specialOpts[optsKey]; + if (opts.indexOf(key) >= 0) { + if(optsKey == "drawGrid") { + drawGrid.changeOptions(options); + if (options.drawGrid) + drawGrid.resizeCanvas(); + } + + if (optsKey == "snapToGrid"){ + snap.changeOptions(options); + if (options.snapToGrid) + snapAllNodes(); + } + + if(optsKey == "guidelines") + guidelines.changeOptions(options); + + if (optsKey == "resize") { + resize.changeOptions(options); + if (options.resize) + resizeAllNodes(); + } + + if (optsKey == "parentPadding") + parentPadding.changeOptions(options); + + + } + } + } + latestOptions = $.extend(true, latestOptions, options); + } + + return { + init: syncWithOptions, + syncWithOptions: syncWithOptions + }; + +}; +},{}],6:[function(require,module,exports){ +module.exports = function (opts, cy, $, debounce) { + + var options = opts; + + var changeOptions = function (opts) { + options = opts; + }; + + function calcDistance(p1, p2) { + return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); + } + + function getExtraDim(node, paddingDim) { + + } + + var dims = function (node) { + + var pos = node.renderedPosition(); + var width = node.renderedWidth(); + var height = node.renderedHeight(); + var padding = { + left: Number(node.renderedStyle("padding-left").replace("px", "")), + right: Number(node.renderedStyle("padding-right").replace("px", "")), + top: Number(node.renderedStyle("padding-top").replace("px", "")), + bottom: Number(node.renderedStyle("padding-bottom").replace("px", "")) + }; + + this.horizontal = { + center: pos.x, + left: pos.x - (padding.left + width / 2), + right: pos.x + (padding.right + width / 2) + }; + + this.vertical = { + center: pos.y, + top: pos.y - (padding.top + height / 2), + bottom: pos.y + (padding.bottom + height / 2) + }; + + return this; + }; + + var $canvas = $(''); + var $container = $(cy.container()); + var ctx = $canvas[0].getContext('2d'); + $container.append($canvas); + + $canvas + .attr('height', $container.height()) + .attr('width', $container.width()) + .css({ + 'position': 'absolute', + 'top': 0, + 'left': 0, + 'z-index': options.guidelinesStackOrder + }); + + var canvasBb = $canvas.offset(); + var containerBb = $container.offset(); + + $canvas + .attr( 'height', $container.height() ) + .attr( 'width', $container.width() ) + .css( { + 'top': -( canvasBb.top - containerBb.top ), + 'left': -( canvasBb.left - containerBb.left ) + } ); + var clearDrawing = function () { + var width = $container.width(); + var height = $container.height(); + + ctx.clearRect(0, 0, width, height); + }; + + + var pickedNode; + + function onGrabNode(e) { + pickedNode = e.cyTarget; + onDragNode(e); + } + + var onDragNode = debounce(function(e) { + if (pickedNode) { + var node = pickedNode; + + var mainDims = new dims(node); + + var cy = e.cy; + var nearests = { + horizontal: { + distance: Number.MAX_VALUE + }, + vertical: { + distance: Number.MAX_VALUE + } + }; + + cy.nodes(":visible").not(node.ancestors()).not(node.descendants()).not(node).each(function (i, ele) { + var nodeDims = new dims(ele); + + + for (var dim in mainDims) { + var mainDim = mainDims[dim]; + var nodeDim = nodeDims[dim]; + var otherDim = dim == "horizontal" ? "y" : "x"; + var eitherDim = otherDim == "x" ? "y" : "x"; + for (var key in mainDim) { + for (var key2 in nodeDim) { + if (Math.abs(mainDim[key] - nodeDim[key2]) < options.guidelinesTolerance) { + var distance = calcDistance(node.renderedPosition(), ele.renderedPosition()); + if (nearests[dim].distance > distance) { + + nearests[dim] = { + to: ele.id(), + toPos: {}, + from: node.id(), + fromPos: {}, + distance: distance + }; + nearests[dim].fromPos[eitherDim] = mainDim[key]; + nearests[dim].fromPos[otherDim] = node.renderedPosition(otherDim); + nearests[dim].toPos[eitherDim] = nodeDim[key2]; + nearests[dim].toPos[otherDim] = ele.renderedPosition(otherDim); + } + } + // console.log(key + " of " + node.id() + " -> " + key2 + " of " + ele.id()) + } + } + } + }); + + clearDrawing(); + for (var key in nearests) { + var item = nearests[key]; + if (item.from) { + ctx.beginPath(); + ctx.moveTo(item.fromPos.x, item.fromPos.y); + ctx.lineTo(item.toPos.x, item.toPos.y); + + ctx.setLineDash(options.guidelinesStyle.lineDash); + for (var styleKey in options.guidelinesStyle) + ctx[styleKey] = options.guidelinesStyle[styleKey]; + + ctx.stroke(); + } + } + + } + }, 0, true); + + function onFreeNode() { + pickedNode = undefined; + clearDrawing(); + } + + return { + onDragNode: onDragNode, + onZoom: onDragNode, + onGrabNode: onGrabNode, + onFreeNode: onFreeNode, + changeOptions: changeOptions + } + +}; +},{}],7:[function(require,module,exports){ +;(function(){ 'use strict'; + + // registers the extension on a cytoscape lib ref + var register = function( cytoscape ){ + + if( !cytoscape ){ return; } // can't register if cytoscape unspecified + + + var options = { + // On/Off Modules + snapToGrid: true, // Snap to grid functionality + discreteDrag: true, // Discrete Drag + guidelines: true, // Guidelines on dragging nodes + resize: true, // Adjust node sizes to cell sizes + parentPadding: true, // Adjust parent sizes to cell sizes by padding + drawGrid: true, // Draw grid background + + // Other settings + + // General + gridSpacing: 20, // Distance between the lines of the grid. + + // Draw Grid + zoomDash: true, // Determines whether the size of the dashes should change when the drawing is zoomed in and out if grid is drawn. + panGrid: true, // Determines whether the grid should move then the user moves the graph if grid is drawn. + gridStackOrder: -1, // Namely z-index + strokeStyle: '#dedede', // Color of grid lines + lineWidth: 1.0, // Width of grid lines + lineDash: [2.5, 4], // Defines style of dash. Read: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash + + // Guidelines + guidelinesStackOrder: 4, // z-index of guidelines + guidelinesTolerance: 2.00, // Tolerance distance for rendered positions of nodes' interaction. + guidelinesStyle: { // Set ctx properties of line. Properties are here: + strokeStyle: "#8b7d6b", + lineDash: [3, 5] + }, + + // Parent Padding + parentSpacing: -1 // -1 to set paddings of parents to gridSpacing + }; + + var _snap = require("./snap"); + var _discreteDrag = require("./discrete_drag"); + var _drawGrid = require("./draw_grid"); + var _resize = require("./resize"); + var _eventsController = require("./events_controller"); + var _guidelines = require("./guidelines"); + var _parentPadding = require("./parentPadding"); + var _alignment = require("./alignment"); + var debounce = require("./debounce"); + var snap, resize, discreteDrag, drawGrid, eventsController, guidelines, parentPadding, alignment; + + function getScratch() { + if (!cy.scratch("_gridGuide")) { + cy.scratch("_gridGuide", { }); + + } + return cy.scratch("_gridGuide"); + } + + cytoscape( 'core', 'gridGuide', function(opts){ + var cy = this; + $.extend(true, options, opts); + + if (!getScratch().initialized) { + snap = _snap(options.gridSpacing); + resize = _resize(options.gridSpacing); + discreteDrag = _discreteDrag(cy, snap); + drawGrid = _drawGrid(options, cy, $, debounce); + guidelines = _guidelines(options, cy, $, debounce); + parentPadding = _parentPadding(options, cy); + + eventsController = _eventsController(cy, snap, resize, discreteDrag, drawGrid, guidelines, parentPadding, $); + + alignment = _alignment(cytoscape, $); + + eventsController.init(options); + getScratch().initialized = true; + } else + eventsController.syncWithOptions(options); + + + return this; // chainability + } ) ; + + + }; + + if( typeof module !== 'undefined' && module.exports ){ // expose as a commonjs module + module.exports = register; + } + + if( typeof define !== 'undefined' && define.amd ){ // expose as an amd/requirejs module + define('cytoscape-grid-guide', function(){ + return register; + }); + } + + if( typeof cytoscape !== 'undefined' ){ // expose to global cytoscape (i.e. window.cytoscape) + register( cytoscape ); + } + +})(); + +},{"./alignment":1,"./debounce":2,"./discrete_drag":3,"./draw_grid":4,"./events_controller":5,"./guidelines":6,"./parentPadding":8,"./resize":9,"./snap":10}],8:[function(require,module,exports){ +module.exports = function (opts, cy) { + + var options = opts; + var ppClass = "_gridParentPadding"; + + function initPadding() { + var padding = options.parentSpacing < 0 ? options.gridSpacing : options.parentSpacing; + cy.style() + .selector('.' + ppClass) + .style("compound-sizing-wrt-labels", "exclude") + .style("padding-left", padding) + .style("padding-right", padding) + .style("padding-top", padding) + .style("padding-bottom", padding) + .update(); + + } + + function changeOptions(opts) { + options = opts; + padding = options.parentSpacing < 0 ? options.gridSpacing : options.parentSpacing; + initPadding(); + } + + function setPaddingOfParent(node, enable) { + if (enable) + node.addClass(ppClass); + else + node.removeClass(ppClass); + } + + return { + changeOptions: changeOptions, + setPaddingOfParent: setPaddingOfParent + }; +}; +},{}],9:[function(require,module,exports){ +module.exports = function (gridSpacing) { + + + var changeOptions = function (opts) { + gridSpacing = Number(opts.gridSpacing); + }; + + var getScratch = function (node) { + if (!node.scratch("_gridGuide")) + node.scratch("_gridGuide", {}); + + return node.scratch("_gridGuide"); + }; + + function resizeNode(node) { + var width = node.width(); + var height = node.height(); + + var newWidth = Math.round((width - gridSpacing) / (gridSpacing * 2)) * (gridSpacing * 2); + var newHeight = Math.round((height - gridSpacing) / (gridSpacing * 2)) * (gridSpacing * 2); + newWidth = newWidth > 0 ? newWidth + gridSpacing : gridSpacing; + newHeight = newHeight > 0 ? newHeight + gridSpacing : gridSpacing; + + if (width != newWidth || height != newHeight) { + node.style({ + "width": newWidth, + "height": newHeight + }); + getScratch(node).resize = { + oldWidth: width, + oldHeight: height + }; + } + } + + function recoverNodeDimensions(node) { + var oldSizes = getScratch(node).resize; + if (oldSizes) + node.style({ + "width": oldSizes.oldWidth, + "height": oldSizes.oldHeight + }); + + + } + + + return { + resizeNode: resizeNode, + recoverNodeDimensions: recoverNodeDimensions, + changeOptions: changeOptions + }; + +}; +},{}],10:[function(require,module,exports){ +module.exports = function (gridSpacing) { + + var snap = { }; + + snap.changeOptions = function (opts) { + gridSpacing = opts.gridSpacing; + }; + + var getScratch = function (node) { + if (!node.scratch("_gridGuide")) + node.scratch("_gridGuide", {}); + + return node.scratch("_gridGuide"); + }; + + + function getTopMostNodes(nodes) { + var nodesMap = {}; + + for (var i = 0; i < nodes.length; i++) { + nodesMap[nodes[i].id()] = true; + } + + var roots = nodes.filter(function (i, ele) { + var parent = ele.parent()[0]; + while(parent != null){ + if(nodesMap[parent.id()]){ + return false; + } + parent = parent.parent()[0]; + } + return true; + }); + + return roots; + } + + snap.snapPos = function (pos) { + var newPos = { + x: (Math.floor(pos.x / gridSpacing) + 0.5) * gridSpacing, + y: (Math.floor(pos.y / gridSpacing) + 0.5) * gridSpacing + }; + + return newPos; + }; + + snap.snapNode = function (node) { + + var pos = node.position(); + var newPos = snap.snapPos(pos); + + node.position(newPos); + }; + + function snapTopDown(nodes) { + + nodes.union(nodes.descendants()).positions(function (i, node) { + var pos = node.position(); + return snap.snapPos(pos); + }); + /* + for (var i = 0; i < nodes.length; i++) { + + if (!nodes[i].isParent()) + snap.snapNode(nodes[i]); + + snapTopDown(nodes.children()); + }*/ + + } + + snap.snapNodesTopDown = function (nodes) { + // getTOpMostNodes -> nodes + cy.startBatch(); + nodes.union(nodes.descendants()).positions(function (i, node) { + var pos = node.position(); + return snap.snapPos(pos); + }); + cy.endBatch(); + }; + + snap.onFreeNode = function (e) { + var nodes; + if (e.cyTarget.selected()) + nodes = e.cy.$(":selected"); + else + nodes = e.cyTarget; + + snap.snapNodesTopDown(nodes); + + }; + + + snap.recoverSnapNode = function (node) { + var snapScratch = getScratch(node).snap; + if (snapScratch) { + node.position(snapScratch.oldPos); + } + }; + + return snap; + + + + + +}; +},{}]},{},[7]); diff --git a/views/bootstrap/class.WorkflowGraph.php b/views/bootstrap/class.WorkflowGraph.php index 351aedde2..4af56a73c 100644 --- a/views/bootstrap/class.WorkflowGraph.php +++ b/views/bootstrap/class.WorkflowGraph.php @@ -109,9 +109,14 @@ var cy = cytoscape({ } }] -} +}); -); +cy.gridGuide({ + discreteDrag: false, + guidelinesStyle: { + strokeStyle: "red" + } +}); cy.on('free', 'node', function(evt) { $('#png').attr('src', cy.png({'full': true})); @@ -270,6 +275,8 @@ $(document).ready(function() { $this->htmlAddHeader( ''."\n"); + $this->htmlAddHeader( + ''."\n"); $this->htmlAddHeader('