close #32 (beta) new feature "system magnetization"

This commit is contained in:
Exodus4D
2015-10-25 14:36:44 +01:00
parent 50c97f96f8
commit 18a2838f1b
9 changed files with 666 additions and 77 deletions

View File

@@ -28,6 +28,7 @@ requirejs.config({
velocityUI: 'lib/velocity.ui.min', // v5.0.4 plugin for velocity - http://julian.com/research/velocity/#uiPack
slidebars: 'lib/slidebars', // v0.10 Slidebars - side menu plugin http://plugins.adchsm.me/slidebars/
jsPlumb: 'lib/dom.jsPlumb-1.7.6-min', // v1.7.6 jsPlumb (Vanilla)- main map draw plugin https://jsplumbtoolkit.com/
farahey: 'lib/farahey-0.5', // v0.5 jsPlumb "magnetizing" extension - https://github.com/jsplumb/farahey
customScrollbar: 'lib/jquery.mCustomScrollbar.concat.min', // v3.0.9 Custom scroll bars - http://manos.malihu.gr/
datatables: 'lib/datatables/jquery.dataTables.min', // v1.10.7 DataTables - https://datatables.net/
//datatablesBootstrap: 'lib/datatables/dataTables.bootstrap', // DataTables - not used (bootstrap style)
@@ -70,6 +71,9 @@ requirejs.config({
bootstrap: {
deps: ['jquery']
},
farahey: {
deps: ['jsPlumb']
},
velocity: {
deps: ['jquery']
},

156
js/app/map/magnetizing.js Normal file
View File

@@ -0,0 +1,156 @@
/**
* Map "magnetizing" feature
* jsPlumb extension used: http://morrisonpitt.com/farahey/
*/
define([
'jquery',
'farahey'
], function($) {
'use strict';
var config = {
systemClass: 'pf-system' // class for all systems
};
/**
* Cached current "Magnetizer" object
* @type {Magnetizer}
*/
var m8 = null;
/**
* init a jsPlumb (map) Element for "magnetised" function.
* this is optional and prevents systems from being overlapped
*/
$.fn.initMagnetizer = function(){
var mapContainer = this;
var systemsOnMap = mapContainer.find('.' + config.systemClass);
/**
* helper function
* get current system offset
* @param system
* @returns {{left, top}}
* @private
*/
var _offset = function(system) {
var _ = function(p) {
var v = system.style[p];
return parseInt(v.substring(0, v.length - 2));
};
return {
left:_('left'),
top:_('top')
};
};
/**
* helper function
* set new syste offset
* @param system
* @param o
* @private
*/
var _setOffset = function(system, o) {
var markAsUpdated = false;
// new position must be within parent container
// no negative offset!
if(
o.left >= 0 &&
o.left <= 2300
){
markAsUpdated = true;
system.style.left = o.left + 'px';
}
if(
o.top >= 0 &&
o.top <= 498
){
markAsUpdated = true;
system.style.top = o.top + 'px';
}
if(markAsUpdated === true){
$(system).markAsChanged();
}
};
/**
* helper function
* exclude current dragged element(s) from position update
* @param id
* @returns {boolean}
* @private
*/
var _dragFilter = function(id) {
return !$('#' + id).hasClass('jsPlumb_dragged');
};
// main init for "magnetize" feature ------------------------------------------------------
m8 = new Magnetizer({
container: mapContainer,
getContainerPosition:function(c) {
return c.offset();
},
getPosition:_offset,
getSize: function(system) {
return [ $(system).outerWidth(), $(system).outerHeight() ];
},
getId : function(system) {
return $(system).attr('id');
},
setPosition:_setOffset,
elements: systemsOnMap,
filter:_dragFilter,
padding:[8, 8]
});
};
$.fn.destroyMagnetizer = function(){
var mapContainer = this;
// remove cached "magnetizer" instance
m8 = null;
};
/**
* update system positions for "all" systems that are effected by drag&drop
* @param map
* @param e
*/
var executeAtEvent = function(map, e){
// check if magnetizer is active
if(m8 !== null && e ){
m8.executeAtEvent(e);
map.repaintEverything();
}
};
/**
* rearrange all systems of a map
* needs "magnetization" to be active
* @param map
*/
var executeAtCenter = function(map){
if(m8 !== null){
m8.executeAtCenter();
map.repaintEverything();
}
};
return {
executeAtCenter: executeAtCenter,
executeAtEvent: executeAtEvent
};
});

View File

@@ -1,3 +1,7 @@
/**
* Main map functionality
*/
define([
'jquery',
'app/init',
@@ -5,12 +9,12 @@ define([
'app/render',
'bootbox',
'app/ccp',
'jsPlumb',
'app/map/magnetizing',
'dragToSelect',
'select2',
'app/map/contextmenu',
'app/map/overlay'
], function($, Init, Util, Render, bootbox, CCP) {
], function($, Init, Util, Render, bootbox, CCP, MagnetizerWrapper) {
'use strict';
@@ -22,7 +26,8 @@ define([
},
mapSnapToGridDimension: 20, // px for grid snapping (grid YxY)
mapSnapToGrid: false, // Snap systems to grid while dragging
mapSnapToGrid: false, // "Snap to Grid" feature for drag&drop systems on map (optional)
mapMagnetizer: false, // "Magnetizer" feature for drag&drop systems on map (optional)
mapTabContentClass: 'pf-map-tab-content', // Tab-Content element (parent element)
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
headMapTrackingId: 'pf-head-map-tracking', // id for "map tracking" toggle (checkbox)
@@ -616,6 +621,8 @@ define([
var mapContainer = mapConfig.map.getContainer();
var newSystems = 0;
if(mapContainer === undefined){
// add new map
@@ -688,6 +695,7 @@ define([
if( addNewSystem === true){
drawSystem(mapConfig.map, systemData);
newSystems++;
}
}
@@ -780,6 +788,16 @@ define([
}
});
// init/update map "magnetization" feature if new systems where added
if(
config.mapMagnetizer === true &&
newSystems > 0
){
mapContainer.initMagnetizer();
}
}
return mapContainer;
@@ -876,7 +894,10 @@ define([
connectorElements.velocity('transition.fadeIn', {
stagger: 30,
duration: 120
duration: 120,
complete: function(){
callback();
}
});
// show overlay elements (if some exist)
@@ -884,10 +905,7 @@ define([
overlayElements.delay(500).velocity('transition.fadeIn', {
stagger: 50,
duration: 180,
display: 'auto',
complete: function(){
callback();
}
display: 'auto'
});
}
}
@@ -1677,7 +1695,7 @@ define([
constrain: true,
//scroll: true, // not working because of customized scrollbar
filter: '.' + config.systemHeadNameClass, // disable drag on "system name"
snapThreshold: config.mapSnapToGridDimension, // distance for grid snapping "magnet" effect
snapThreshold: config.mapSnapToGridDimension, // distance for grid snapping "magnet" effect (optional)
start: function(params, a, b){
var dragSystem = $(params.el);
@@ -1707,9 +1725,13 @@ define([
// move them to the "top"
$(selectedSystems).updateSystemZIndex();
},
drag: function(){
drag: function(p){
// start map update timer
mapOverlayTimer.startMapUpdateCounter();
// update system positions for "all" systems that are effected by drag&drop
// this requires "magnet" feature to be active! (optional)
MagnetizerWrapper.executeAtEvent(map, p.e);
},
stop: function(params){
var dragSystem = $(params.el);
@@ -1736,7 +1758,6 @@ define([
// set all selected systems as "changes" for update
tempSystem.markAsChanged();
// set new position for popover edit field (system name)
var tempPosition = tempSystem.position();
@@ -2035,7 +2056,6 @@ define([
// connection element
this.setParameter('updated', 0);
}
});
};
@@ -2419,32 +2439,44 @@ define([
// catch events =========================================================
// toggle "snap to grid" option
$(mapContainer).on('pf:menuGrid', function(e, data){
// toggle global map option (e.g. "grid snap", "magnetization"
$(mapContainer).on('pf:menuMapOption', function(e, data){
var currentMapElement = $(this);
config.mapSnapToGrid = !config.mapSnapToGrid;
// toggle map option
config[data.option] = !config[data.option];
// toggle grid class
currentMapElement.toggleClass(config.mapGridClass);
// toggle map class (e.g. for grid)
if(data.class){
currentMapElement.toggleClass( config[data.class] );
}
// toggle button class
$(data.button).toggleClass('active');
var notificationText = 'disabled';
if(config.mapSnapToGrid){
if( config[data.option] ){
// call optional jQuery extension on mapElement
if(data.onEnable){
$.fn[ data.onEnable ].apply( currentMapElement );
}
// show map overlay info icon
notificationText = 'enabled';
// show map overlay grid info
currentMapElement.getMapOverlay('info').updateOverlayIcon('grid', 'show');
currentMapElement.getMapOverlay('info').updateOverlayIcon(data.option, 'show');
}else{
// call optional jQuery extension on mapElement
if(data.onDisable){
$.fn[ data.onDisable ].apply( currentMapElement );
}
// hide map overlay grid info
currentMapElement.getMapOverlay('info').updateOverlayIcon('grid', 'hide');
// hide map overlay info icon
currentMapElement.getMapOverlay('info').updateOverlayIcon(data.option, 'hide');
}
Util.showNotify({title: 'Grid snapping', text: notificationText, type: 'info'});
Util.showNotify({title: data.description, text: notificationText, type: 'info'});
});
// delete system event
@@ -2902,6 +2934,15 @@ define([
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
// re-init "magnetizer" with new added system
if(config.mapMagnetizer === true){
var mapContainer = this.map.getContainer();
$(mapContainer).initMagnetizer();
// re/arrange systems (prevent overlapping)
MagnetizerWrapper.executeAtCenter(this.map);
}
if(callback){
callback();
}
@@ -3319,19 +3360,19 @@ define([
jsPlumb.Defaults.LogEnabled = true;
var newJsPlumbInstance = jsPlumb.getInstance({
Anchor: 'Continuous', // anchors on each site
Container: null, // will be set as soon as container is connected to DOM
Anchor: 'Continuous', // anchors on each site
Container: null, // will be set as soon as container is connected to DOM
PaintStyle:{
lineWidth: 4, // width of a Connector's line. An integer.
strokeStyle: 'red', // color for a Connector
outlineColor: 'red', // color of the outline for an Endpoint or Connector. see fillStyle examples.
outlineWidth: 2 // width of the outline for an Endpoint or Connector. An integer.
lineWidth: 4, // width of a Connector's line. An integer.
strokeStyle: 'red', // color for a Connector
outlineColor: 'red', // color of the outline for an Endpoint or Connector. see fillStyle examples.
outlineWidth: 2 // width of the outline for an Endpoint or Connector. An integer.
},
Connector:[ 'Bezier', { curviness: 40 } ], // default connector style (this is not used!) all connections have their own style (by scope)
Connector:[ 'Bezier', { curviness: 40 } ], // default connector style (this is not used!) all connections have their own style (by scope)
Endpoints: [ [ 'Dot', { radius: 5 } ], [ 'Dot', { radius: 5 } ] ],
// Endpoint: 'Blank', // does not work... :(
ReattachConnections: false, // re-attach connection if dragged with mouse to "nowhere"
Scope: Init.defaultMapScope, // default map scope for connections
ReattachConnections: false, // re-attach connection if dragged with mouse to "nowhere"
Scope: Init.defaultMapScope, // default map scope for connections
LogEnabled: true
});
@@ -3447,18 +3488,23 @@ define([
}
// callback function after tab switch
function switchTabCallback( mapName ){
function switchTabCallback( mapName, mapContainer ){
Util.showNotify({title: 'Map initialized', text: mapName + ' - loaded', type: 'success'});
if( config.mapMagnetizer === true ){
mapContainer.initMagnetizer();
}
return false;
}
if(options.showAnimation){
// show nice visualization effect
mapContainer.visualizeMap('show', function(){
switchTabCallback( mapConfig.config.name );
switchTabCallback( mapConfig.config.name, mapContainer );
});
}
});
};

View File

@@ -19,12 +19,27 @@ define([
// map overlays
mapOverlayClass: 'pf-map-overlay', // class for all map overlays
mapOverlayTimerClass: 'pf-map-overlay-timer', // class for map overlay timer e.g. map timer
mapOverlayInfoClass: 'pf-map-overlay-info', // class for map overlay info e.g. map info
mapOverlayInfoClass: 'pf-map-overlay-info' // class for map overlay info e.g. map info
// map overlay icons
mapOverlayFilterClass: 'pf-map-overlay-filter', // class for "filter" icon within a overlay
mapOverlayGridClass: 'pf-map-overlay-grid' // class for "grid" icon within a overlay
};
// overlay options (all available map options shown in overlay)
var options = {
filter: {
title: 'active filter',
class: 'pf-map-overlay-filter',
iconClass: ['fa', 'fa-fw', 'fa-filter']
},
mapSnapToGrid: {
title: 'active grid',
class: 'pf-map-overlay-grid',
iconClass: ['glyphicon', 'glyphicon-th']
},
mapMagnetizer: {
title: 'active magnetizer',
class: 'pf-map-overlay-magnetizer',
iconClass: ['fa', 'fa-fw', 'fa-magnet']
}
};
/**
@@ -172,24 +187,18 @@ define([
/**
* update (show/hide) a overlay icon in the "info"-overlay
* show/hide the overlay itself is no icons are visible
* @param iconName
* @param option
* @param viewType
*/
$.fn.updateOverlayIcon = function(iconName, viewType){
$.fn.updateOverlayIcon = function(option, viewType){
var mapOverlayInfo = $(this);
var showOverlay = false;
var mapOverlayIconClass = options[option].class;
// look for the overlay icon that should be updated
var iconElement = null;
switch(iconName){
case 'filter':
iconElement = mapOverlayInfo.find('.' + config.mapOverlayFilterClass);
break;
case 'grid':
iconElement = mapOverlayInfo.find('.' + config.mapOverlayGridClass);
break;
}
var iconElement = mapOverlayInfo.find('.' + mapOverlayIconClass);
if(iconElement){
if(viewType === 'show'){
@@ -198,7 +207,7 @@ define([
}else if(viewType === 'hide'){
iconElement.hide();
// check if ther is any visible icon remaining
// check if there is any visible icon remaining
var visibleIcons = mapOverlayInfo.find('i:visible');
if(visibleIcons.length > 0){
showOverlay = true;
@@ -228,33 +237,33 @@ define([
*/
$.fn.initMapOverlays = function(){
return this.each(function(){
var parentElemtn = $(this);
var parentElement = $(this);
var mapOverlayTimer = $('<div>', {
class: [config.mapOverlayClass, config.mapOverlayTimerClass].join(' ')
});
parentElemtn.append(mapOverlayTimer);
parentElement.append(mapOverlayTimer);
// ---------------------------------------------------------------------------
// add map overlay info. after scrollbar is initialized
var mapOverlayInfo = $('<div>', {
class: [config.mapOverlayClass, config.mapOverlayInfoClass].join(' ')
}).append(
$('<i>', {
class: ['fa', 'fa-fw', 'fa-filter', 'pull-right', config.mapOverlayFilterClass].join(' ')
}).attr('title', 'active filter').tooltip({
placement: 'left',
container: 'body'
})
).append(
$('<i>', {
class: ['glyphicon', 'glyphicon-th', 'pull-right', config.mapOverlayGridClass].join(' ')
}).attr('title', 'active grid').tooltip({
placement: 'left',
container: 'body'
})
);
parentElemtn.append(mapOverlayInfo);
});
// add all overlay elements
for (var prop in options) {
if(options.hasOwnProperty(prop)){
mapOverlayInfo.append(
$('<i>', {
class: options[prop].iconClass.concat( ['pull-right', options[prop].class] ).join(' ')
}).attr('title', options[prop].title).tooltip({
placement: 'left',
container: 'body'
})
);
}
}
parentElement.append(mapOverlayInfo);
// reset map update timer
mapOverlayTimer.setMapUpdateCounter(100, config.logTimerCount);

View File

@@ -280,12 +280,39 @@ define([
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;&nbsp;Grid snapping').prepend(
$('<i>',{
class: 'glyphicon glyphicon-th'
})
).on('click', function(){
Util.getMapModule().getActiveMap().triggerMenuEvent('Grid', {button: this});
$('<i>',{
class: 'glyphicon glyphicon-th'
})
).on('click', function(){
Util.getMapModule().getActiveMap().triggerMenuEvent('MapOption', {
option: 'mapSnapToGrid',
button: this,
description: 'Grid snapping',
class: 'mapGridClass'
});
})
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;&nbsp;Magnetizing').prepend(
$('<i>',{
class: 'fa fa-magnet fa-fw'
})
).on('click', function(){
Util.getMapModule().getActiveMap().triggerMenuEvent('MapOption', {
option: 'mapMagnetizer',
button: this,
description: 'Magnetizer',
onEnable: 'initMagnetizer', // jQuery extension function
onDisable: 'destroyMagnetizer' // jQuery extension function
});
}).append(
$('<span>',{
class: ['badge', 'bg-color', 'bg-color-orange', 'pull-right'].join(' '),
html: '&beta;'
})
)
).append(
$('<a>', {
class: 'list-group-item',

View File

@@ -1 +0,0 @@
{"version":3,"file":"bootstrap2-toggle.min.js","sources":["bootstrap2-toggle.js"],"names":["$","Plugin","option","this","each","$this","data","options","Toggle","element","$element","extend","defaults","render","VERSION","DEFAULTS","on","off","onstyle","offstyle","size","style","width","height","prototype","attr","_onstyle","_offstyle","$toggleOn","html","addClass","$toggleOff","$toggleHandle","$toggleGroup","append","$toggle","prop","wrap","parent","Math","max","outerWidth","css","update","trigger","toggle","silent","removeClass","enable","removeAttr","disable","change","proxy","destroy","remove","removeData","unwrap","old","fn","bootstrapToggle","Constructor","noConflict","document","e","$checkbox","find","preventDefault","jQuery"],"mappings":"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"}

350
js/lib/farahey-0.5.js Normal file
View File

@@ -0,0 +1,350 @@
;(function() {
var root = this;
var Farahey;
if (typeof exports !== 'undefined') {
Farahey = exports;
} else {
Farahey = root.Farahey = {};
}
var findInsertionPoint = function(sortedArr, val, comparator) {
var low = 0, high = sortedArr.length;
var mid = -1, c = 0;
while(low < high) {
mid = parseInt((low + high)/2);
c = comparator(sortedArr[mid], val);
if(c < 0) {
low = mid + 1;
}else if(c > 0) {
high = mid;
}else {
return mid;
}
}
return low;
},
geomSupport = typeof jsPlumbGeom !== "undefined" ? jsPlumbGeom : Biltong,
insertSorted = function(array, value, comparator) {
var ip = findInsertionPoint(array, value, comparator);
array.splice(ip, 0, value);
},
distanceFromOriginComparator = function(r1, r2, origin) {
var d1 = geomSupport.lineLength(origin, [ r1.x + (r1.w / 2), r1.y + (r1.h / 2)]),
d2 = geomSupport.lineLength(origin, [ r2.x + (r2.w / 2), r2.y + (r2.h / 2)]);
return d1 < d2 ? -1 : d1 == d2 ? 0 : 1;
},
EntryComparator = function(origin, getSize) {
var _origin = origin,
_cache = {},
_get = function(entry) {
if (!_cache[entry[1]]) {
var s = getSize(entry[2]);
_cache[entry[1]] = {
l:entry[0][0],
t:entry[0][1],
w:s[0],
h:s[1],
center:[entry[0][0] + (s[0] / 2), entry[0][1] + (s[1] / 2) ]
};
}
return _cache[entry[1]];
}
this.setOrigin = function(o) {
_origin = o;
_cache = {};
};
this.compare = function(e1, e2) {
var d1 = geomSupport.lineLength(_origin, _get(e1).center),
d2 = geomSupport.lineLength(_origin, _get(e2).center);
return d1 < d2 ? -1 : d1 == d2 ? 0 : 1;
};
};
var _isOnEdge = function(r, axis, dim, v) { return (r[axis] <= v && v <= r[axis] + r[dim]); },
_xAdj = [ function(r1, r2) { return r1.x + r1.w - r2.x; }, function(r1, r2) { return r1.x - (r2.x + r2.w); } ],
_yAdj = [ function(r1, r2) { return r1.y + r1.h - r2.y; }, function(r1, r2) { return r1.y - (r2.y + r2.h); } ],
_adj = [ null, [ _xAdj[0], _yAdj[1] ], [ _xAdj[0], _yAdj[0] ], [ _xAdj[1], _yAdj[0] ], [ _xAdj[1], _yAdj[1] ] ],
_genAdj = function(r1, r2, m, b, s) {
if (isNaN(m)) m = 0;
var y = r2.y + r2.h,
x = (m == Infinity || m == -Infinity) ? r2.x + (r2.w / 2) : (y - b) / m,
theta = Math.atan(m);
if (_isOnEdge(r2, "x", "w", x)) {
var rise = _adj[s][1](r1, r2),
hyp = rise / Math.sin(theta),
run = hyp * Math.cos(theta);
return { left:run, top:rise };
}
else {
var run = _adj[s][0](r1, r2),
hyp = run / Math.cos(theta),
rise = hyp * Math.sin(theta);
return { left:run, top:rise };
}
},
/*
* Calculates how far to move r2 from r1 so that it no longer overlaps.
* if origin is supplied, then it means we want r2 to move along a vector joining r2's center to that point.
* otherwise we want it to move along a vector joining the two rectangle centers.
*/
_calculateSpacingAdjustment = Farahey.calculateSpacingAdjustment = function(r1, r2) {
var c1 = r1.center || [ r1.x + (r1.w / 2), r1.y + (r1.h / 2) ],
c2 = r2.center || [ r2.x + (r2.w / 2), r2.y + (r2.h / 2) ],
m = geomSupport.gradient(c1, c2),
s = geomSupport.quadrant(c1, c2),
b = (m == Infinity || m == -Infinity || isNaN(m)) ? 0 : c1[1] - (m * c1[0]);
return _genAdj(r1, r2, m, b, s);
},
// calculate a padded rectangle for the given element with offset & size, and desired padding.
_paddedRectangle = Farahey.paddedRectangle = function(o, s, p) {
return { x:o[0] - p[0], y: o[1] - p[1], w:s[0] + (2 * p[0]), h:s[1] + (2 * p[1]) };
},
_magnetize = function(positionArray, positions, sizes, padding,
constrain, origin, filter,
updateOnStep, stepInterval, stepCallback)
{
origin = origin || [0,0];
stepCallback = stepCallback || function() { };
var focus = _paddedRectangle(origin, [1,1], padding),
iterations = 100, iteration = 1, uncleanRun = true, adjustBy, constrainedAdjustment,
_movedElements = {},
_move = function(id, o, x, y) {
_movedElements[id] = true;
o[0] += x;
o[1] += y;
},
step = function() {
for (var i = 0; i < positionArray.length; i++) {
var o1 = positions[positionArray[i][1]],
oid = positionArray[i][1],
a1 = positionArray[i][2], // angle to node from magnet origin
s1 = sizes[positionArray[i][1]],
// create a rectangle for first element: this encompasses the element and padding on each
//side
r1 = _paddedRectangle(o1, s1, padding);
if (filter(positionArray[i][1]) && geomSupport.intersects(focus, r1)) {
adjustBy = _calculateSpacingAdjustment(focus, r1);
constrainedAdjustment = constrain(positionArray[i][1], o1, adjustBy);
_move(oid, o1, constrainedAdjustment.left, constrainedAdjustment.top);
}
// now move others to account for this one, if necessary.
// reset rectangle for node
r1 = _paddedRectangle(o1, s1, padding);
for (var j = 0; j < positionArray.length; j++) {
if (i != j) {
var o2 = positions[positionArray[j][1]],
a2 = positionArray[j][2], // angle to node from magnet origin
s2 = sizes[positionArray[j][1]],
// create a rectangle for the second element, again by putting padding of the desired
// amount around the bounds of the element.
r2 = _paddedRectangle(o2, s2, padding);
// if the two rectangles intersect then figure out how much to move the second one by.
if (geomSupport.intersects(r1, r2)) {
// TODO in 0.3, instead of moving neither, the other node should move.
if (filter(positionArray[j][1])) {
uncleanRun = true;
adjustBy = _calculateSpacingAdjustment(r1, r2),
constrainedAdjustment = constrain(positionArray[j][1], o2, adjustBy);
_move(positionArray[j][1], o2, constrainedAdjustment.left, constrainedAdjustment.top);
}
}
}
}
}
if (updateOnStep)
stepCallback();
if (uncleanRun && iteration < iterations) {
uncleanRun = false;
iteration++;
if (updateOnStep) {
window.setTimeout(step, stepInterval);
}
else
step();
}
};
step();
return _movedElements;
};
/**
* @name Magnetizer
* @classdesc Applies repulsive magnetism to a set of elements relative to a given point, with a specified
* amount of padding around the point.
*/
/**
* @name Magnetizer#constructor
* @function
* @param {Selector|Element} [container] Element that contains the elements to magnetize. Only required if you intend to use the `executeAtEvent` method.
* @param {Function} [getContainerPosition] Function that returns the position of the container (as an object of the form `{left:.., top:..}`) when requested. Only required if you intend to use the `executeAtEvent` method.
* @param {Function} getPosition A function that takes an element id and returns its position. It does not matter to which element this position is computed as long as you remain consistent with this method, `setPosition` and the `origin` property.
* @param {Function} setPosition A function that takes an element id and position, and sets it. See note about offset parent above.
* @param {Function} getSize A function that takes an element id and returns its size, in pixels.
* @param {Integer[]} [padding] Optional padding for x and y directions. Defaults to 20 pixels in each direction.
* @param {Function} [constrain] Optional function that takes an id and a proposed amount of movement in each axis, and returns the allowed amount of movement in each axis. You can use this to constrain your elements to a grid, for instance, or a path, etc.
* @param {Integer[]} [origin] The origin of magnetization, in pixels. Defaults to 0,0. You can also supply this to the `execute` call.
* @param {Selector|String[]|Element[]} elements List of elements on which to operate.
* @param {Boolean} [executeNow=false] Whether or not to execute the routine immediately.
* @param {Function} [filter] Optional function that takes an element id and returns whether or not that element can be moved.
* @param {Boolean} [orderByDistanceFromOrigin=false] Whether or not to sort elements first by distance from origin. Can have better results but takes more time.
*/
root.Magnetizer = function(params) {
var getPosition = params.getPosition,
getSize = params.getSize,
getId = params.getId,
setPosition = params.setPosition,
padding = params.padding || [20, 20],
// expects a { left:.., top:... } object. returns how far it can actually go.
constrain = params.constrain || function(id, current, delta) { return delta; },
positionArray = [],
positions = {},
sizes = {},
elements = params.elements || [],
origin = params.origin || [0,0],
executeNow = params.executeNow,
minx, miny, maxx, maxy,
getOrigin = this.getOrigin = function() { return origin; },
filter = params.filter || function(_) { return true; },
orderByDistanceFromOrigin = params.orderByDistanceFromOrigin,
comparator = new EntryComparator(origin, getSize),
updateOnStep = params.updateOnStep,
stepInterval = params.stepInterval || 350,
originDebugMarker,
debug = params.debug,
createOriginDebugger = function() {
var d = document.createElement("div");
d.style.position = "absolute";
d.style.width = "10px";
d.style.height = "10px";
d.style.backgroundColor = "red";
document.body.appendChild(d);
originDebugMarker = d;
},
_addToPositionArray = function(p) {
if (!orderByDistanceFromOrigin || positionArray.length == 0)
positionArray.push(p);
else {
insertSorted(positionArray, p, comparator.compare);
}
},
_updatePositions = function() {
comparator.setOrigin(origin);
positionArray = []; positions = {}; sizes = {};
minx = miny = Infinity;
maxx = maxy = -Infinity;
for (var i = 0; i < elements.length; i++) {
var p = getPosition(elements[i]),
s = getSize(elements[i]),
id = getId(elements[i]);
positions[id] = [p.left, p.top];
_addToPositionArray([ [p.left, p.top], id, elements[i]]);
sizes[id] = s;
minx = Math.min(minx, p.left);
miny = Math.min(miny, p.top);
maxx = Math.max(maxx, p.left + s[0]);
maxy = Math.max(maxy, p.top + s[1]);
}
},
_run = function() {
if (elements.length > 1) {
var _movedElements = _magnetize(positionArray, positions, sizes, padding, constrain, origin, filter, updateOnStep, stepInterval, _positionElements);
_positionElements(_movedElements);
}
},
_positionElements = function(_movedElements) {
for (var i = 0; i < elements.length; i++) {
var id = getId(elements[i]);
if (_movedElements[id])
setPosition(elements[i], { left:positions[id][0], top:positions[id][1] });
}
},
setOrigin = function(o) {
if (o != null) {
origin = o;
comparator.setOrigin(o);
}
};
/**
* @name Magnetizer#execute
* @function
* @desc Runs the magnetize routine.
* @param {Integer[]} [o] Optional origin to use. You may have set this in the constructor and do not wish to supply it, or you may be happy with the default of [0,0].
*/
this.execute = function(o) {
setOrigin(o);
_updatePositions();
_run();
};
/**
* @name Magnetizer#executeAtCenter
* @function
* @desc Computes the center of all the nodes and then uses that as the magnetization origin when it runs the routine.
*/
this.executeAtCenter = function() {
_updatePositions();
setOrigin([
(minx + maxx) / 2,
(miny + maxy) / 2
]);
_run();
};
/**
* @name Magnetizer#executeAtEvent
* @function
* @desc Runs the magnetize routine using the location of the given event as the origin. To use this
* method you need to have provided a `container`, and a `getContainerPosition` function to the
* constructor.
* @param {Event} e Event to get origin location from.
*/
this.executeAtEvent = function(e) {
var c = params.container,
o = params.getContainerPosition(c),
x = e.pageX - o.left + c[0].scrollLeft,
y = e.pageY - o.top + c[0].scrollTop;
if (debug) {
originDebugMarker.style.left = e.pageX + "px";
originDebugMarker.style.top = e.pageY + "px";
}
this.execute([x,y]);
};
/**
* @name Magnetize#setElements
* @function
* @desc Sets the current list of elements.
* @param {Object[]} _els List of elements, in whatever format the magnetizer is setup to use.
*/
this.setElements = function(_els) {
elements = _els;
};
if (debug)
createOriginDebugger();
if (executeNow) this.execute();
};
}).call(this);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long