Browse Source

Implemented dragging items

css_transitions
josdejong 10 years ago
parent
commit
2feb514a2a
22 changed files with 889 additions and 331 deletions
  1. +1
    -0
      Jakefile.js
  2. +0
    -1
      examples/graph/20_navigation.html
  3. +1
    -0
      examples/graph/index.html
  4. +1
    -0
      package.json
  5. +7
    -0
      src/graph/ClusterMixin.js
  6. +12
    -2
      src/graph/Edge.js
  7. +100
    -44
      src/graph/Graph.js
  8. +2
    -2
      src/graph/Node.js
  9. +260
    -127
      src/graph/SelectionMixin.js
  10. +1
    -2
      src/module/imports.js
  11. +11
    -2
      src/timeline/Controller.js
  12. +38
    -17
      src/timeline/Range.js
  13. +7
    -7
      src/timeline/Stack.js
  14. +24
    -75
      src/timeline/Timeline.js
  15. +17
    -0
      src/timeline/component/Component.js
  16. +181
    -0
      src/timeline/component/ItemSet.js
  17. +57
    -46
      src/timeline/component/RootPanel.js
  18. +11
    -2
      src/timeline/component/item/Item.js
  19. +1
    -1
      src/timeline/component/item/ItemBox.js
  20. +1
    -1
      src/timeline/component/item/ItemPoint.js
  21. +2
    -2
      src/timeline/component/item/ItemRange.js
  22. +154
    -0
      src/util.js

+ 1
- 0
Jakefile.js View File

@ -82,6 +82,7 @@ task('build', {async: true}, function () {
'./src/graph/Popup.js',
'./src/graph/Groups.js',
'./src/graph/Images.js',
'./src/graph/manipulationMixin.js',
'./src/graph/SectorsMixin.js',
'./src/graph/ClusterMixin.js',
'./src/graph/SelectionMixin.js',

+ 0
- 1
examples/graph/20_navigation.html View File

@ -31,7 +31,6 @@
div.table_description {
width:100px;
}
</style>
<script type="text/javascript" src="../../dist/vis.js"></script>

+ 1
- 0
examples/graph/index.html View File

@ -32,6 +32,7 @@
<p><a href="18_fully_random_nodes_clustering.html">18_fully_random_nodes_clustering.html</a></p>
<p><a href="19_scale_free_graph_clustering.html">19_scale_free_graph_clustering.html</a></p>
<p><a href="20_navigation.html">20_navigation.html</a></p>
<p><a href="21_data_manipulation.html">21_data_manipulation.html</a></p>
<p><a href="graphviz/graphviz_gallery.html">graphviz_gallery.html</a></p>
</div>

+ 1
- 0
package.json View File

@ -33,6 +33,7 @@
"moment": "latest",
"hammerjs": "1.0.5",
"mousetrap": "latest",
"emitter-component": "latest",
"node-watch": "latest"
}
}

+ 7
- 0
src/graph/ClusterMixin.js View File

@ -335,6 +335,10 @@ var ClusterMixin = {
// if child node has been added on smaller scale than current, kick out
if (childNode.formationScale < this.scale || force == true) {
// remove the selection, first remove the selection from the connected edges
this._unselectConnectedEdges(parentNode);
parentNode.unselect();
// put the child node back in the global nodes object
this.nodes[containedNodeId] = childNode;
@ -383,6 +387,9 @@ var ClusterMixin = {
// recalculate the size of the node on the next time the node is rendered
parentNode.clearSizeCache();
// this unselects the rest of the edges
this._unselectConnectedEdges(parentNode);
}
// check if a further expansion step is possible if recursivity is enabled

+ 12
- 2
src/graph/Edge.js View File

@ -32,6 +32,7 @@ function Edge (properties, graph, constants) {
this.width = constants.edges.width;
this.value = undefined;
this.length = constants.edges.length;
this.selected = false;
this.from = null; // a node
this.to = null; // a node
@ -268,7 +269,7 @@ Edge.prototype._drawLine = function(ctx) {
* @private
*/
Edge.prototype._getLineWidth = function() {
if (this.from.selected || this.to.selected) {
if (this.selected == true) {
return Math.min(this.width * 2, this.widthMax)*this.graphScaleInv;
}
else {
@ -617,4 +618,13 @@ Edge._dist = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
*/
Edge.prototype.setScale = function(scale) {
this.graphScaleInv = 1.0/scale;
};
};
Edge.prototype.select = function() {
this.selected = true;
}
Edge.prototype.unselect = function() {
this.selected = false;
}

+ 100
- 44
src/graph/Graph.js View File

@ -94,6 +94,9 @@ function Graph (container, data, options) {
enabled: false,
speed: {x: 10, y: 10, zoom: 0.02}
},
dataManipulationToolbar: {
enabled: false
},
minVelocity: 2, // px/s
maxIterations: 1000 // maximum number of iteration to stabilize
};
@ -116,15 +119,15 @@ function Graph (container, data, options) {
// load the sector system. (mandatory, fully integrated with Graph)
this._loadSectorSystem();
// apply options
this.setOptions(options);
// load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
this._loadClusterSystem();
// load the selection system. (mandatory, required by Graph)
this._loadSelectionSystem();
// apply options
this.setOptions(options);
// other vars
var graph = this;
this.freezeSimulation = false;// freeze the simulation
@ -135,6 +138,7 @@ function Graph (container, data, options) {
this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
this.scale = 1; // defining the global scale variable in the constructor
@ -417,6 +421,17 @@ Graph.prototype.setOptions = function (options) {
this.constants.keyboard.enabled = false;
}
if (options.dataManipulationToolbar) {
this.constants.dataManipulationToolbar.enabled = true;
for (var prop in options.dataManipulationToolbar) {
if (options.dataManipulationToolbar.hasOwnProperty(prop)) {
this.constants.dataManipulationToolbar[prop] = options.dataManipulationToolbar[prop];
}
}
}
else if (options.dataManipulationToolbar !== undefined) {
this.constants.dataManipulationToolbar.enabled = false;
}
// TODO: work out these options and document them
if (options.edges) {
@ -478,16 +493,18 @@ Graph.prototype.setOptions = function (options) {
}
}
this.setSize(this.width, this.height);
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
// load the navigation system.
this._loadNavigationControls();
// load the data manipulation system
this._loadManipulationSystem();
// bind keys. If disabled, this will not do anything;
this._createKeyBinds();
this.setSize(this.width, this.height);
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
this._redraw();
};
@ -546,6 +563,7 @@ Graph.prototype._create = function () {
this.frame.className = 'graph-frame';
this.frame.style.position = 'relative';
this.frame.style.overflow = 'hidden';
this.frame.style.zIndex = "1";
// create the graph canvas (HTML canvas element)
this.frame.canvas = document.createElement( 'canvas' );
@ -581,6 +599,7 @@ Graph.prototype._create = function () {
// add the frame to the container element
this.containerElement.appendChild(this.frame);
};
@ -616,14 +635,10 @@ Graph.prototype._createKeyBinds = function() {
this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown");
this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup");
}
/*
this.mousetrap.bind("=",this.decreaseClusterLevel.bind(me));
this.mousetrap.bind("-",this.increaseClusterLevel.bind(me));
this.mousetrap.bind("s",this.singleStep.bind(me));
this.mousetrap.bind("h",this.updateClustersDefault.bind(me));
this.mousetrap.bind("c",this._collapseSector.bind(me));
this.mousetrap.bind("f",this.toggleFreeze.bind(me));
*/
if (this.constants.dataManipulationToolbar.enabled == true) {
this.mousetrap.bind("escape",this._createManipulatorBar.bind(me));
}
}
/**
@ -670,31 +685,32 @@ Graph.prototype._onDragStart = function () {
drag.nodeId = node.id;
// select the clicked node if not yet selected
if (!node.isSelected()) {
this._selectNode(node,false);
this._selectObject(node,false);
}
// create an array with the selected nodes and their original location and status
var me = this;
this.selection.forEach(function (id) {
var node = me.nodes[id];
if (node) {
var s = {
id: id,
node: node,
// store original x, y, xFixed and yFixed, make the node temporarily Fixed
x: node.x,
y: node.y,
xFixed: node.xFixed,
yFixed: node.yFixed
};
node.xFixed = true;
node.yFixed = true;
drag.selection.push(s);
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
var object = this.selectionObj[objectId];
if (object instanceof Node) {
var s = {
id: object.id,
node: object,
// store original x, y, xFixed and yFixed, make the node temporarily Fixed
x: object.x,
y: object.y,
xFixed: object.xFixed,
yFixed: object.yFixed
};
object.xFixed = true;
object.yFixed = true;
drag.selection.push(s);
}
}
});
}
}
};
@ -771,7 +787,9 @@ Graph.prototype._onDragEnd = function () {
*/
Graph.prototype._onTap = function (event) {
var pointer = this._getPointer(event.gesture.touches[0]);
this.pointerPosition = pointer;
this._handleTap(pointer);
};
@ -792,6 +810,7 @@ Graph.prototype._onDoubleTap = function (event) {
*/
Graph.prototype._onHold = function (event) {
var pointer = this._getPointer(event.gesture.touches[0]);
this.pointerPosition = pointer;
this._handleOnHold(pointer);
};
@ -1120,6 +1139,10 @@ Graph.prototype.setSize = function(width, height) {
this.frame.canvas.width = this.frame.canvas.clientWidth;
this.frame.canvas.height = this.frame.canvas.clientHeight;
if (this.manipulationDiv !== undefined) {
this.manipulationDiv.style.width = this.frame.canvas.clientWidth;
}
if (this.constants.navigation.enabled == true) {
this._relocateNavigation();
}
@ -1184,7 +1207,7 @@ Graph.prototype._addNodes = function(ids) {
var node = new Node(data, this.images, this.groups, this.constants);
this.nodes[id] = node; // note: this may replace an existing node
if (!node.isFixed()) {
if (!node.isFixed() && this.createNodeOnClick != true) {
// TODO: position new nodes in a smarter way!
var radius = this.constants.edges.length * 2;
var count = ids.length;
@ -1200,6 +1223,7 @@ Graph.prototype._addNodes = function(ids) {
this._updateNodeIndexList();
this._reconnectEdges();
this._updateValueRange(this.nodes);
this.updateLabels();
};
/**
@ -1468,7 +1492,7 @@ Graph.prototype._redraw = function() {
this._doInAllSectors("_drawAllSectorNodes",ctx);
this._doInAllSectors("_drawEdges",ctx);
this._doInAllSectors("_drawNodes",ctx);
this._doInAllSectors("_drawNodes",ctx,true);
// restore original scaling and translation
ctx.restore();
@ -1722,6 +1746,7 @@ Graph.prototype._calculateForces = function() {
// we loop from i over all but the last entree in the array
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
var a_base = (-2/3); var b = 4/3;
for (i = 0; i < this.nodeIndices.length-1; i++) {
node1 = nodes[this.nodeIndices[i]];
for (j = i+1; j < this.nodeIndices.length; j++) {
@ -1734,6 +1759,7 @@ Graph.prototype._calculateForces = function() {
// clusters have a larger region of influence
minimumDistance = (clusterSize == 0) ? this.constants.nodes.distance : (this.constants.nodes.distance * (1 + clusterSize * this.constants.clustering.distanceAmplification));
var a = a_base / minimumDistance;
if (distance < 2*minimumDistance) { // at 2.0 * the minimum distance, the force is 0.000045
angle = Math.atan2(dy, dx);
@ -1744,13 +1770,13 @@ Graph.prototype._calculateForces = function() {
// TODO: correct factor for repulsing force
//repulsingForce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
//repulsingForce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force
//repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force
repulsingForce = a * distance + b; // TODO: test the approximation of the function above
}
// amplify the repulsion for clusters.
repulsingForce *= (clusterSize == 0) ? 1 : 1 + clusterSize * this.constants.clustering.forceAmplification;
repulsingForce *= this.forceFactor;
fx = Math.cos(angle) * repulsingForce;
fy = Math.sin(angle) * repulsingForce ;
@ -1963,9 +1989,9 @@ Graph.prototype.start = function() {
}
};
/**
* Debug function, does one step of the graph
*/
Graph.prototype.singleStep = function() {
if (this.moving) {
this._initializeForceCalculation();
@ -2044,7 +2070,6 @@ Graph.prototype._loadSectorSystem = function() {
* @private
*/
Graph.prototype._loadSelectionSystem = function() {
this.selection = [];
this.selectionObj = {};
for (var mixinFunction in SelectionMixin) {
@ -2055,6 +2080,37 @@ Graph.prototype._loadSelectionSystem = function() {
}
/**
* Mixin the navigationUI (User Interface) system and initialize the parameters required
*
* @private
*/
Graph.prototype._loadManipulationSystem = function() {
// reset global variables -- these are used by the selection of nodes and edges.
this.blockConnectingEdgeSelection = false;
this.forceAppendSelection = false
if (this.constants.dataManipulationToolbar.enabled == true) {
// load the manipulator HTML elements. All styling done in css.
if (this.manipulationDiv === undefined) {
this.manipulationDiv = document.createElement('div');
this.manipulationDiv.className = 'graph-manipulationDiv';
this.containerElement.insertBefore(this.manipulationDiv, this.frame);
}
// load the manipulation functions
for (var mixinFunction in manipulationMixin) {
if (manipulationMixin.hasOwnProperty(mixinFunction)) {
Graph.prototype[mixinFunction] = manipulationMixin[mixinFunction];
}
}
// create the manipulator toolbar
this._createManipulatorBar();
}
}
/**
* Mixin the navigation (User Interface) system and initialize the parameters required
*

+ 2
- 2
src/graph/Node.js View File

@ -188,8 +188,8 @@ Node.prototype.setProperties = function(properties, constants) {
}
}
this.xFixed = this.xFixed || (properties.x !== undefined);
this.yFixed = this.yFixed || (properties.y !== undefined);
this.xFixed = this.xFixed || (properties.x !== undefined && properties.fixed);
this.yFixed = this.yFixed || (properties.y !== undefined && properties.fixed);
this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
if (this.shape == 'image') {

+ 260
- 127
src/graph/SelectionMixin.js View File

@ -108,7 +108,7 @@ var SelectionMixin = {
_getNodeAt : function (pointer) {
// we first check if this is an navigation controls element
var positionObject = this._pointerToPositionObject(pointer);
overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
// if there are overlapping nodes, select the last one, this is the
// one which is drawn on top of the others
@ -121,6 +121,36 @@ var SelectionMixin = {
},
/**
* retrieve all edges overlapping with given object, selector is around center
* @param {Object} object An object with parameters left, top, right, bottom
* @return {Number[]} An array with id's of the overlapping nodes
* @private
*/
_getEdgesOverlappingWith : function (object, overlappingEdges) {
var edges = this.edges;
for (var edgeId in edges) {
if (edges.hasOwnProperty(edgeId)) {
if (edges[edgeId].isOverlappingWith(object)) {
overlappingEdges.push(edgeId);
}
}
}
},
/**
* retrieve all nodes overlapping with given object
* @param {Object} object An object with parameters left, top, right, bottom
* @return {Number[]} An array with id's of the overlapping nodes
* @private
*/
_getAllEdgesOverlappingWith : function (object) {
var overlappingEdges = [];
this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
return overlappingEdges;
},
/**
* Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
* _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
@ -130,18 +160,25 @@ var SelectionMixin = {
* @private
*/
_getEdgeAt : function(pointer) {
return null;
var positionObject = this._pointerToPositionObject(pointer);
var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
if (overlappingEdges.length > 0) {
return this.edges[overlappingEdges[overlappingEdges.length - 1]];
}
else {
return null;
}
},
/**
* Add object to the selection array. The this.selection id array may not be needed.
* Add object to the selection array.
*
* @param obj
* @private
*/
_addToSelection : function(obj) {
this.selection.push(obj.id);
this.selectionObj[obj.id] = obj;
},
@ -149,16 +186,10 @@ var SelectionMixin = {
/**
* Remove a single option from selection.
*
* @param obj
* @param {Object} obj
* @private
*/
_removeFromSelection : function(obj) {
for (var i = 0; i < this.selection.length; i++) {
if (obj.id == this.selection[i]) {
this.selection.splice(i,1);
break;
}
}
delete this.selectionObj[obj.id];
},
@ -174,10 +205,9 @@ var SelectionMixin = {
doNotTrigger = false;
}
this.selection = [];
for (var objId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objId)) {
this.selectionObj[objId].unselect();
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
this.selectionObj[objectId].unselect();
}
}
this.selectionObj = {};
@ -189,6 +219,89 @@ var SelectionMixin = {
}
},
/**
* Unselect all clusters. The selectionObj is useful for this.
*
* @param {Boolean} [doNotTrigger] | ignore trigger
* @private
*/
_unselectClusters : function(doNotTrigger) {
if (doNotTrigger === undefined) {
doNotTrigger = false;
}
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Node) {
if (this.selectionObj[objectId].clusterSize > 1) {
this.selectionObj[objectId].unselect();
this._removeFromSelection(this.selectionObj[objectId]);
}
}
}
}
if (doNotTrigger == false) {
this._trigger('select', {
nodes: this.getSelection()
});
}
},
/**
* return the number of selected nodes
*
* @returns {number}
* @private
*/
_getSelectedNodeCount : function() {
var count = 0;
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Node) {
count += 1;
}
}
}
return count;
},
/**
* return the number of selected edges
*
* @returns {number}
* @private
*/
_getSelectedEdgeCount : function() {
var count = 0;
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Edge) {
count += 1;
}
}
}
return count;
},
/**
* return the number of selected objects.
*
* @returns {number}
* @private
*/
_getSelectedObjectCount : function() {
var count = 0;
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
count += 1;
}
}
return count;
},
/**
* Check if anything is selected
@ -197,41 +310,93 @@ var SelectionMixin = {
* @private
*/
_selectionIsEmpty : function() {
if (this.selection.length == 0) {
return true;
for(var objectId in this.selectionObj) {
if(this.selectionObj.hasOwnProperty(objectId)) {
return false;
}
}
else {
return false;
return true;
},
/**
* check if one of the selected nodes is a cluster.
*
* @returns {boolean}
* @private
*/
_clusterInSelection : function() {
for(var objectId in this.selectionObj) {
if(this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Node) {
if (this.selectionObj[objectId].clusterSize > 1) {
return true;
}
}
}
}
return false;
},
/**
* select the edges connected to the node that is being selected
*
* @param {Node} node
* @private
*/
_selectConnectedEdges : function(node) {
for (var i = 0; i < node.dynamicEdges.length; i++) {
var edge = node.dynamicEdges[i];
edge.select();
this._addToSelection(edge);
}
},
/**
* unselect the edges connected to the node that is being selected
*
* @param {Node} node
* @private
*/
_unselectConnectedEdges : function(node) {
for (var i = 0; i < node.dynamicEdges.length; i++) {
var edge = node.dynamicEdges[i];
edge.unselect();
this._removeFromSelection(edge);
}
},
/**
* This is called when someone clicks on a node. either select or deselect it.
* If there is an existing selection and we don't want to append to it, clear the existing selection
*
* @param {Node} node
* @param {Node || Edge} object
* @param {Boolean} append
* @param {Boolean} [doNotTrigger] | ignore trigger
* @private
*/
_selectNode : function(node, append, doNotTrigger) {
_selectObject : function(object, append, doNotTrigger) {
if (doNotTrigger === undefined) {
doNotTrigger = false;
}
if (this._selectionIsEmpty() == false && append == false) {
if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
this._unselectAll(true);
}
if (node.selected == false) {
node.select();
this._addToSelection(node);
if (object.selected == false) {
object.select();
this._addToSelection(object);
if (object instanceof Node && this.blockConnectingEdgeSelection == false) {
this._selectConnectedEdges(object);
}
}
else {
node.unselect();
this._removeFromSelection(node);
object.unselect();
this._removeFromSelection(object);
}
if (doNotTrigger == false) {
this._trigger('select', {
@ -251,6 +416,7 @@ var SelectionMixin = {
*/
_handleTouch : function(pointer) {
if (this.constants.navigation.enabled == true) {
this.pointerPosition = pointer;
var node = this._getNavigationNodeAt(pointer);
if (node != null) {
if (this[node.triggerFunction] !== undefined) {
@ -270,10 +436,16 @@ var SelectionMixin = {
_handleTap : function(pointer) {
var node = this._getNodeAt(pointer);
if (node != null) {
this._selectNode(node,false);
this._selectObject(node,false);
}
else {
this._unselectAll();
var edge = this._getEdgeAt(pointer);
if (edge != null) {
this._selectObject(edge,false);
}
else {
this._unselectAll();
}
}
this._redraw();
},
@ -305,7 +477,13 @@ var SelectionMixin = {
_handleOnHold : function(pointer) {
var node = this._getNodeAt(pointer);
if (node != null) {
this._selectNode(node,true);
this._selectObject(node,true);
}
else {
var edge = this._getEdgeAt(pointer);
if (edge != null) {
this._selectObject(edge,true);
}
}
this._redraw();
},
@ -327,27 +505,54 @@ var SelectionMixin = {
/**
*
* retrieve the currently selected nodes
* retrieve the currently selected objects
* @return {Number[] | String[]} selection An array with the ids of the
* selected nodes.
*/
getSelection : function() {
return this.selection.concat([]);
var nodeIds = this.getSelectedNodes();
var edgeIds = this.getSelectedEdges();
return {nodes:nodeIds, edges:edgeIds};
},
/**
*
* retrieve the currently selected nodes as objects
* @return {Objects} selection An array with the ids of the
* retrieve the currently selected nodes
* @return {String} selection An array with the ids of the
* selected nodes.
*/
getSelectionObjects : function() {
return this.selectionObj;
getSelectedNodes : function() {
var idArray = [];
for(var objectId in this.selectionObj) {
if(this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Node) {
idArray.push(objectId);
}
}
}
return idArray
},
/**
* // TODO: rework this function, it is from the old system
*
* retrieve the currently selected edges
* @return {Array} selection An array with the ids of the
* selected nodes.
*/
getSelectedEdges : function() {
var idArray = [];
for(var objectId in this.selectionObj) {
if(this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Edge) {
idArray.push(objectId);
}
}
}
return idArray
},
/**
* select zero or more nodes
* @param {Number[] | String[]} selection An array with the ids of the
* selected nodes.
@ -368,89 +573,34 @@ var SelectionMixin = {
if (!node) {
throw new RangeError('Node with id "' + id + '" not found');
}
this._selectNode(node,true,true);
this._selectObject(node,true,true);
}
this.redraw();
},
/**
* TODO: rework this function, it is from the old system
*
* Validate the selection: remove ids of nodes which no longer exist
* @private
*/
_updateSelection : function () {
var i = 0;
while (i < this.selection.length) {
var nodeId = this.selection[i];
if (!this.nodes.hasOwnProperty(nodeId)) {
this.selection.splice(i, 1);
delete this.selectionObj[nodeId];
}
else {
i++;
}
}
}
/**
* Unselect selected nodes. If no selection array is provided, all nodes
* are unselected
* @param {Object[]} selection Array with selection objects, each selection
* object has a parameter row. Optional
* @param {Boolean} triggerSelect If true (default), the select event
* is triggered when nodes are unselected
* @return {Boolean} changed True if the selection is changed
* @private
*/
/* _unselectNodes : function(selection, triggerSelect) {
var changed = false;
var i, iMax, id;
if (selection) {
// remove provided selections
for (i = 0, iMax = selection.length; i < iMax; i++) {
id = selection[i];
if (this.nodes.hasOwnProperty(id)) {
this.nodes[id].unselect();
}
var j = 0;
while (j < this.selection.length) {
if (this.selection[j] == id) {
this.selection.splice(j, 1);
changed = true;
}
else {
j++;
for(var objectId in this.selectionObj) {
if(this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Node) {
if (!this.nodes.hasOwnProperty(objectId)) {
delete this.selectionObj[objectId];
}
}
}
}
else if (this.selection && this.selection.length) {
// remove all selections
for (i = 0, iMax = this.selection.length; i < iMax; i++) {
id = this.selection[i];
if (this.nodes.hasOwnProperty(id)) {
this.nodes[id].unselect();
else { // assuming only edges and nodes are selected
if (!this.edges.hasOwnProperty(objectId)) {
delete this.selectionObj[objectId];
}
}
changed = true;
}
this.selection = [];
}
if (changed && (triggerSelect == true || triggerSelect == undefined)) {
// fire the select event
this._trigger('select', {
nodes: this.getSelection()
});
}
}
return changed;
},
*/
}
/**
* select all nodes on given location x, y
* @param {Array} selection an array with node ids
@ -475,40 +625,23 @@ var SelectionMixin = {
if (selection[i] != this.selection[i]) {
selectionAlreadyThere = false;
break;
>>>>>>> develop
}
}
}
if (selectionAlreadyThere) {
return changed;
}
if (append == undefined || append == false) {
// first deselect any selected node
var triggerSelect = false;
changed = this._unselectNodes(undefined, triggerSelect);
}
}
for (i = 0, iMax = selection.length; i < iMax; i++) {
// add each of the new selections, but only when they are not duplicate
var id = selection[i];
var isDuplicate = (this.selection.indexOf(id) != -1);
if (!isDuplicate) {
this.nodes[id].select();
this.selection.push(id);
changed = true;
}
}
<<<<<<< HEAD
=======
if (changed) {
// fire the select event
this._trigger('select', {
nodes: this.getSelection()
});
}
>>>>>>> develop
return changed;
},
*/
};

+ 1
- 2
src/module/imports.js View File

@ -6,6 +6,7 @@
// If not available there, load via require.
var moment = (typeof window !== 'undefined') && window['moment'] || require('moment');
var Emitter = require('emitter-component');
var Hammer;
if (typeof window !== 'undefined') {
@ -28,5 +29,3 @@ else {
throw Error('mouseTrap is only available in a browser, not in node.js.');
}
}

+ 11
- 2
src/timeline/Controller.js View File

@ -11,6 +11,9 @@ function Controller () {
this.reflowTimer = undefined;
}
// Extend controller with Emitter mixin
Emitter(Controller.prototype);
/**
* Add a component to the controller
* @param {Component} component
@ -26,7 +29,7 @@ Controller.prototype.add = function add(component) {
}
// add the component
component.controller = this;
component.setController(this);
this.components[component.id] = component;
};
@ -38,13 +41,17 @@ Controller.prototype.remove = function remove(component) {
var id;
for (id in this.components) {
if (this.components.hasOwnProperty(id)) {
if (id == component || this.components[id] == component) {
if (id == component || this.components[id] === component) {
break;
}
}
}
if (id) {
// unregister the controller (gives the component the ability to unregister
// event listeners and clean up other stuff)
this.components[id].setController(null);
delete this.components[id];
}
};
@ -54,6 +61,7 @@ Controller.prototype.remove = function remove(component) {
* @param {Boolean} [force] If true, an immediate reflow is forced. Default
* is false.
*/
// TODO: change requestReflow into an event
Controller.prototype.requestReflow = function requestReflow(force) {
if (force) {
this.reflow();
@ -74,6 +82,7 @@ Controller.prototype.requestReflow = function requestReflow(force) {
* @param {Boolean} [force] If true, an immediate repaint is forced. Default
* is false.
*/
// TODO: change requestReflow into an event
Controller.prototype.requestRepaint = function requestRepaint(force) {
if (force) {
this.repaint();

+ 38
- 17
src/timeline/Range.js View File

@ -48,42 +48,48 @@ function validateDirection (direction) {
/**
* Add listeners for mouse and touch events to the component
* @param {Component} component
* @param {Controller} controller
* @param {Component} component Should be a rootpanel
* @param {String} event Available events: 'move', 'zoom'
* @param {String} direction Available directions: 'horizontal', 'vertical'
*/
Range.prototype.subscribe = function (component, event, direction) {
Range.prototype.subscribe = function (controller, component, event, direction) {
var me = this;
if (event == 'move') {
// drag start listener
component.on('dragstart', function (event) {
controller.on('dragstart', function (event) {
me._onDragStart(event, component);
});
// drag listener
component.on('drag', function (event) {
controller.on('drag', function (event) {
me._onDrag(event, component, direction);
});
// drag end listener
component.on('dragend', function (event) {
controller.on('dragend', function (event) {
me._onDragEnd(event, component);
});
// ignore dragging when holding
controller.on('hold', function (event) {
me._onHold();
});
}
else if (event == 'zoom') {
// mouse wheel
function mousewheel (event) {
me._onMouseWheel(event, component, direction);
}
component.on('mousewheel', mousewheel);
component.on('DOMMouseScroll', mousewheel); // For FF
controller.on('mousewheel', mousewheel);
controller.on('DOMMouseScroll', mousewheel); // For FF
// pinch
component.on('touch', function (event) {
me._onTouch();
controller.on('touch', function (event) {
me._onTouch(event);
});
component.on('pinch', function (event) {
controller.on('pinch', function (event) {
me._onPinch(event, component, direction);
});
}
@ -311,7 +317,7 @@ var touchParams = {};
Range.prototype._onDragStart = function(event, component) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
if (touchParams.pinching) return;
if (touchParams.ignore) return;
touchParams.start = this.start;
touchParams.end = this.end;
@ -334,7 +340,7 @@ Range.prototype._onDrag = function (event, component, direction) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
if (touchParams.pinching) return;
if (touchParams.ignore) return;
var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY,
interval = (touchParams.end - touchParams.start),
@ -356,7 +362,7 @@ Range.prototype._onDrag = function (event, component, direction) {
Range.prototype._onDragEnd = function (event, component) {
// refuse to drag when we where pinching to prevent the timeline make a jump
// when releasing the fingers in opposite order from the touch screen
if (touchParams.pinching) return;
if (touchParams.ignore) return;
if (component.frame) {
component.frame.style.cursor = 'auto';
@ -417,14 +423,29 @@ Range.prototype._onMouseWheel = function(event, component, direction) {
};
/**
* On start of a touch gesture, initialize scale to 1
* Start of a touch gesture
* @private
*/
Range.prototype._onTouch = function () {
Range.prototype._onTouch = function (event) {
touchParams.start = this.start;
touchParams.end = this.end;
touchParams.pinching = false;
touchParams.ignore = false;
touchParams.center = null;
// don't move the range when dragging a selected event
// TODO: it's not so neat to have to know about the state of the ItemSet
var item = ItemSet.itemFromTarget(event);
if (item && item.selected) {
touchParams.ignore = true;
}
};
/**
* On start of a hold gesture
* @private
*/
Range.prototype._onHold = function () {
touchParams.ignore = true;
};
/**
@ -435,7 +456,7 @@ Range.prototype._onTouch = function () {
* @private
*/
Range.prototype._onPinch = function (event, component, direction) {
touchParams.pinching = true;
touchParams.ignore = true;
if (event.gesture.touches.length > 1) {
if (!touchParams.center) {

+ 7
- 7
src/timeline/Stack.js View File

@ -1,11 +1,11 @@
/**
* @constructor Stack
* Stacks items on top of each other.
* @param {ItemSet} parent
* @param {ItemSet} itemset
* @param {Object} [options]
*/
function Stack (parent, options) {
this.parent = parent;
function Stack (itemset, options) {
this.itemset = itemset;
this.options = options || {};
this.defaultOptions = {
@ -43,14 +43,14 @@ function Stack (parent, options) {
/**
* Set options for the stack
* @param {Object} options Available options:
* {ItemSet} parent
* {ItemSet} itemset
* {Number} margin
* {function} order Stacking order
*/
Stack.prototype.setOptions = function setOptions (options) {
util.extend(this.options, options);
// TODO: register on data changes at the connected parent itemset, and update the changed part only and immediately
// TODO: register on data changes at the connected itemset, and update the changed part only and immediately
};
/**
@ -70,9 +70,9 @@ Stack.prototype.update = function update() {
* @private
*/
Stack.prototype._order = function _order () {
var items = this.parent.items;
var items = this.itemset.items;
if (!items) {
throw new Error('Cannot stack items: parent does not contain items');
throw new Error('Cannot stack items: ItemSet does not contain items');
}
// TODO: store the sorted items, to have less work later on

+ 24
- 75
src/timeline/Timeline.js View File

@ -1,7 +1,7 @@
/**
* Create a timeline visualization
* @param {HTMLElement} container
* @param {vis.DataSet | Array | DataTable} [items]
* @param {vis.DataSet | Array | google.visualization.DataTable} [items]
* @param {Object} [options] See Timeline.setOptions for the available options.
* @constructor
*/
@ -45,6 +45,13 @@ function Timeline (container, items, options) {
this.rootPanel = new RootPanel(container, rootOptions);
this.controller.add(this.rootPanel);
// single select (or unselect) when tapping an item
// TODO: implement ctrl+click
this.controller.on('tap', this._onSelectItem.bind(this));
// multi select when holding mouse/touch, or on ctrl+click
this.controller.on('hold', this._onMultiSelectItem.bind(this));
// item panel
var itemOptions = Object.create(this.options);
itemOptions.left = function () {
@ -84,26 +91,20 @@ function Timeline (container, items, options) {
// TODO: reckon with options moveable and zoomable
// TODO: put the listeners in setOptions, be able to dynamically change with options moveable and zoomable
this.range.subscribe(this.rootPanel, 'move', 'horizontal');
this.range.subscribe(this.rootPanel, 'zoom', 'horizontal');
// TODO: enable moving again
this.range.subscribe(this.controller, this.rootPanel, 'move', 'horizontal');
this.range.subscribe(this.controller, this.rootPanel, 'zoom', 'horizontal');
this.range.on('rangechange', function (properties) {
var force = true;
me.controller.requestReflow(force);
me._trigger('rangechange', properties);
me.emit('rangechange', properties);
});
this.range.on('rangechanged', function (properties) {
var force = true;
me.controller.requestReflow(force);
me._trigger('rangechanged', properties);
me.emit('rangechanged', properties);
});
// single select (or unselect) when tapping an item
// TODO: implement ctrl+click
this.rootPanel.on('tap', this._onSelectItem.bind(this));
// multi select when holding mouse/touch, or on ctrl+click
this.rootPanel.on('hold', this._onMultiSelectItem.bind(this));
// time axis
var timeaxisOptions = Object.create(rootOptions);
timeaxisOptions.range = this.range;
@ -140,6 +141,9 @@ function Timeline (container, items, options) {
}
}
// extend Timeline with the Emitter mixin
Emitter(Timeline.prototype);
/**
* Set options
* @param {Object} options TODO: describe the available options
@ -173,7 +177,7 @@ Timeline.prototype.getCustomTime = function() {
/**
* Set items
* @param {vis.DataSet | Array | DataTable | null} items
* @param {vis.DataSet | Array | google.visualization.DataTable | null} items
*/
Timeline.prototype.setItems = function(items) {
var initialLoad = (this.itemsData == null);
@ -234,7 +238,7 @@ Timeline.prototype.setItems = function(items) {
/**
* Set groups
* @param {vis.DataSet | Array | DataTable} groups
* @param {vis.DataSet | Array | google.visualization.DataTable} groups
*/
Timeline.prototype.setGroups = function(groups) {
var me = this;
@ -367,56 +371,19 @@ Timeline.prototype.getSelection = function getSelection() {
return this.content ? this.content.getSelection() : [];
};
/**
* Add event listener
* @param {String} event Event name. Available events:
* 'rangechange', 'rangechanged', 'select'
* @param {function} callback Callback function, invoked as callback(properties)
* where properties is an optional object containing
* event specific properties.
*/
Timeline.prototype.on = function on (event, callback) {
var available = ['rangechange', 'rangechanged', 'select'];
if (available.indexOf(event) == -1) {
throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
}
events.addListener(this, event, callback);
};
/**
* Remove an event listener
* @param {String} event Event name
* @param {function} callback Callback function
*/
Timeline.prototype.off = function off (event, callback) {
events.removeListener(this, event, callback);
};
/**
* Trigger an event
* @param {String} event Event name, available events: 'rangechange',
* 'rangechanged', 'select'
* @param {Object} [properties] Event specific properties
* @private
*/
Timeline.prototype._trigger = function _trigger(event, properties) {
events.trigger(this, event, properties || {});
};
/**
* Handle selecting/deselecting an item when tapping it
* @param {Event} event
* @private
*/
// TODO: move this function to ItemSet
Timeline.prototype._onSelectItem = function (event) {
var item = this._itemFromTarget(event);
var item = ItemSet.itemFromTarget(event);
var selection = item ? [item.id] : [];
this.setSelection(selection);
this._trigger('select', {
this.emit('select', {
items: this.getSelection()
});
@ -428,9 +395,10 @@ Timeline.prototype._onSelectItem = function (event) {
* @param {Event} event
* @private
*/
// TODO: move this function to ItemSet
Timeline.prototype._onMultiSelectItem = function (event) {
var selection,
item = this._itemFromTarget(event);
item = ItemSet.itemFromTarget(event);
if (!item) {
// do nothing...
@ -449,28 +417,9 @@ Timeline.prototype._onMultiSelectItem = function (event) {
}
this.setSelection(selection);
this._trigger('select', {
this.emit('select', {
items: this.getSelection()
});
event.stopPropagation();
};
/**
* Find an item from an event target:
* searches for the attribute 'timeline-item' in the event target's element tree
* @param {Event} event
* @return {Item | null| item
* @private
*/
Timeline.prototype._itemFromTarget = function _itemFromTarget (event) {
var target = event.target;
while (target) {
if (target.hasOwnProperty('timeline-item')) {
return target['timeline-item'];
}
target = target.parentNode;
}
return null;
};

+ 17
- 0
src/timeline/component/Component.js View File

@ -55,6 +55,23 @@ Component.prototype.getOption = function getOption(name) {
return value;
};
/**
* Set controller for this component, or remove current controller by passing
* null as parameter value.
* @param {Controller | null} controller
*/
Component.prototype.setController = function setController (controller) {
this.controller = controller || null;
};
/**
* Get controller of this component
* @return {Controller} controller
*/
Component.prototype.getController = function getController () {
return this.controller;
};
/**
* Get the container element of the component, which can be used by a child to
* add its own widgets. Not all components do have a container for childs, in

+ 181
- 0
src/timeline/component/ItemSet.js View File

@ -16,6 +16,13 @@ function ItemSet(parent, depends, options) {
this.parent = parent;
this.depends = depends;
// event listeners
this.eventListeners = {
dragstart: this._onDragStart.bind(this),
drag: this._onDrag.bind(this),
dragend: this._onDragEnd.bind(this)
};
// one options object is shared by this itemset and all its items
this.options = options || {};
this.defaultOptions = {
@ -35,6 +42,7 @@ function ItemSet(parent, depends, options) {
this.itemsData = null; // DataSet
this.range = null; // Range or Object {start: number, end: number}
// data change listeners
this.listeners = {
'add': function (event, params, senderId) {
if (senderId != me.id) {
@ -59,6 +67,8 @@ function ItemSet(parent, depends, options) {
this.stack = new Stack(this, Object.create(this.options));
this.conversion = null;
this.touchParams = {}; // stores properties while dragging
// TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis
}
@ -99,6 +109,55 @@ ItemSet.types = {
*/
ItemSet.prototype.setOptions = Component.prototype.setOptions;
/**
* Set controller for this component
* @param {Controller | null} controller
*/
ItemSet.prototype.setController = function setController (controller) {
var event;
// unregister old event listeners
if (this.controller) {
for (event in this.eventListeners) {
if (this.eventListeners.hasOwnProperty(event)) {
this.controller.off(event, this.eventListeners[event]);
}
}
}
this.controller = controller || null;
// register new event listeners
if (this.controller) {
for (event in this.eventListeners) {
if (this.eventListeners.hasOwnProperty(event)) {
this.controller.on(event, this.eventListeners[event]);
}
}
}
};
// attach event listeners for dragging items to the controller
(function (me) {
var _controller = null;
var _onDragStart = null;
var _onDrag = null;
var _onDragEnd = null;
Object.defineProperty(me, 'controller', {
get: function () {
return _controller;
},
set: function (controller) {
}
});
}) (this);
/**
* Set range (start and end).
* @param {Range | Object} range A Range or an object containing start and end.
@ -195,6 +254,7 @@ ItemSet.prototype.repaint = function repaint() {
if (!frame) {
frame = document.createElement('div');
frame.className = 'itemset';
frame['timeline-itemset'] = this;
var className = options.className;
if (className) {
@ -610,3 +670,124 @@ ItemSet.prototype.toScreen = function toScreen(time) {
var conversion = this.conversion;
return (time.valueOf() - conversion.offset) * conversion.scale;
};
/**
* Start dragging the selected events
* @param {Event} event
* @private
*/
ItemSet.prototype._onDragStart = function (event) {
var itemSet = ItemSet.itemSetFromTarget(event),
item = ItemSet.itemFromTarget(event),
me = this;
if (item && item.selected) {
this.touchParams.items = this.getSelection().map(function (id) {
return me.items[id];
});
event.stopPropagation();
}
};
/**
* Drag selected items
* @param {Event} event
* @private
*/
ItemSet.prototype._onDrag = function (event) {
if (this.touchParams.items) {
var deltaX = event.gesture.deltaX;
// adjust the offset of the items being dragged
this.touchParams.items.forEach(function (item) {
item.setOffset(deltaX);
});
// TODO: implement snapping to nice dates
// TODO: implement dragging from one group to another
this.requestReflow();
event.stopPropagation();
}
};
/**
* End of dragging selected items
* @param {Event} event
* @private
*/
ItemSet.prototype._onDragEnd = function (event) {
if (this.touchParams.items) {
var deltaX = event.gesture.deltaX,
scale = this.conversion.scale;
// prepare a changeset for the changed items
var changes = this.touchParams.items.map(function (item) {
item.setOffset(0);
var change = {
id: item.id
};
if ('start' in item.data) {
change.start = new Date(item.data.start.valueOf() + deltaX / scale);
}
if ('end' in item.data) {
change.end = new Date(item.data.end.valueOf() + deltaX / scale);
}
return change;
});
this.touchParams.items = null;
// find the root DataSet from our DataSet/DataView
var data = this.itemsData;
while (data instanceof DataView) {
data = data.data;
}
// apply the changes to the data
data.update(changes);
event.stopPropagation();
}
};
/**
* Find an item from an event target:
* searches for the attribute 'timeline-item' in the event target's element tree
* @param {Event} event
* @return {Item | null} item
*/
ItemSet.itemFromTarget = function itemFromTarget (event) {
var target = event.target;
while (target) {
if (target.hasOwnProperty('timeline-item')) {
return target['timeline-item'];
}
target = target.parentNode;
}
return null;
};
/**
* Find the ItemSet from an event target:
* searches for the attribute 'timeline-itemset' in the event target's element tree
* @param {Event} event
* @return {ItemSet | null} item
*/
ItemSet.itemSetFromTarget = function itemSetFromTarget (event) {
var target = event.target;
while (target) {
if (target.hasOwnProperty('timeline-itemset')) {
return target['timeline-itemset'];
}
target = target.parentNode;
}
return null;
};

+ 57
- 46
src/timeline/component/RootPanel.js View File

@ -10,12 +10,29 @@ function RootPanel(container, options) {
this.id = util.randomUUID();
this.container = container;
// create functions to be used as DOM event listeners
var me = this;
this.hammer = null;
// create listeners for all interesting events, these events will be emitted
// via the controller
var events = [
'touch', 'pinch', 'tap', 'hold',
'dragstart', 'drag', 'dragend',
'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is for Firefox
];
this.listeners = {};
events.forEach(function (event) {
me.listeners[event] = function () {
var args = [event].concat(Array.prototype.slice.call(arguments, 0));
me.controller.emit.apply(me.controller, args);
};
});
this.options = options || {};
this.defaultOptions = {
autoResize: true
};
this.listeners = {}; // event listeners
}
RootPanel.prototype = new Panel();
@ -48,6 +65,8 @@ RootPanel.prototype.repaint = function () {
this.frame = frame;
this._registerListeners();
changed += 1;
}
if (!frame.parentNode) {
@ -69,7 +88,6 @@ RootPanel.prototype.repaint = function () {
changed += update(frame.style, 'width', asSize(options.width, '100%'));
changed += update(frame.style, 'height', asSize(options.height, '100%'));
this._updateEventEmitters();
this._updateWatch();
return (changed > 0);
@ -158,58 +176,51 @@ RootPanel.prototype._unwatch = function () {
};
/**
* Event handler
* @param {String} event name of the event, for example 'click', 'mousemove'
* @param {function} callback callback handler, invoked with the raw HTML Event
* as parameter.
* Set controller for this component, or remove current controller by passing
* null as parameter value.
* @param {Controller | null} controller
*/
RootPanel.prototype.on = function (event, callback) {
// register the listener at this component
var arr = this.listeners[event];
if (!arr) {
arr = [];
this.listeners[event] = arr;
}
arr.push(callback);
RootPanel.prototype.setController = function setController (controller) {
this.controller = controller || null;
this._updateEventEmitters();
if (this.controller) {
this._registerListeners();
}
else {
this._unregisterListeners();
}
};
/**
* Update the event listeners for all event emitters
* Register event emitters emitted by the rootpanel
* @private
*/
RootPanel.prototype._updateEventEmitters = function () {
if (this.listeners) {
var me = this;
util.forEach(this.listeners, function (listeners, event) {
if (!me.emitters) {
me.emitters = {};
RootPanel.prototype._registerListeners = function () {
if (this.frame && this.controller && !this.hammer) {
this.hammer = Hammer(this.frame, {
prevent_default: true
});
for (var event in this.listeners) {
if (this.listeners.hasOwnProperty(event)) {
this.hammer.on(event, this.listeners[event]);
}
if (!(event in me.emitters)) {
// create event
var frame = me.frame;
if (frame) {
//console.log('Created a listener for event ' + event + ' on component ' + me.id); // TODO: cleanup logging
var callback = function(event) {
listeners.forEach(function (listener) {
// TODO: filter on event target!
listener(event);
});
};
me.emitters[event] = callback;
if (!me.hammer) {
me.hammer = Hammer(frame, {
prevent_default: true
});
}
me.hammer.on(event, callback);
}
}
}
};
/**
* Unregister event emitters from the rootpanel
* @private
*/
RootPanel.prototype._unregisterListeners = function () {
if (this.hammer) {
for (var event in this.listeners) {
if (this.listeners.hasOwnProperty(event)) {
this.hammer.off(event, this.listeners[event]);
}
});
}
// TODO: be able to delete event listeners
// TODO: be able to move event listeners to a parent when available
this.hammer = null;
}
};

+ 11
- 2
src/timeline/component/item/Item.js View File

@ -20,6 +20,7 @@ function Item (parent, data, options, defaultOptions) {
this.left = 0;
this.width = 0;
this.height = 0;
this.offset = 0;
}
/**
@ -72,10 +73,18 @@ Item.prototype.reflow = function reflow() {
return false;
};
/**
* Give the item a display offset in pixels
* @param {Number} offset Offset on screen in pixels
*/
Item.prototype.setOffset = function setOffset(offset) {
this.offset = offset;
};
/**
* Return the items width
* @return {Integer} width
* @return {Number} width
*/
Item.prototype.getWidth = function getWidth() {
return this.width;
}
};

+ 1
- 1
src/timeline/component/item/ItemBox.js View File

@ -187,7 +187,7 @@ ItemBox.prototype.reflow = function reflow() {
update = util.updateProperty;
props = this.props;
options = this.options;
start = this.parent.toScreen(this.data.start);
start = this.parent.toScreen(this.data.start) + this.offset;
align = options.align || this.defaultOptions.align;
margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis;
orientation = options.orientation || this.defaultOptions.orientation;

+ 1
- 1
src/timeline/component/item/ItemPoint.js View File

@ -157,7 +157,7 @@ ItemPoint.prototype.reflow = function reflow() {
options = this.options;
orientation = options.orientation || this.defaultOptions.orientation;
margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis;
start = this.parent.toScreen(this.data.start);
start = this.parent.toScreen(this.data.start) + this.offset;
changed += update(this, 'width', dom.point.offsetWidth);
changed += update(this, 'height', dom.point.offsetHeight);

+ 2
- 2
src/timeline/component/item/ItemRange.js View File

@ -157,8 +157,8 @@ ItemRange.prototype.reflow = function reflow() {
props = this.props;
options = this.options;
parent = this.parent;
start = parent.toScreen(this.data.start);
end = parent.toScreen(this.data.end);
start = parent.toScreen(this.data.start) + this.offset;
end = parent.toScreen(this.data.end) + this.offset;
update = util.updateProperty;
box = dom.box;
parentWidth = parent.width;

+ 154
- 0
src/util.js View File

@ -671,3 +671,157 @@ util.option.asElement = function (value, defaultValue) {
return value || defaultValue || null;
};
util.GiveDec = function GiveDec(Hex)
{
if(Hex == "A")
Value = 10;
else
if(Hex == "B")
Value = 11;
else
if(Hex == "C")
Value = 12;
else
if(Hex == "D")
Value = 13;
else
if(Hex == "E")
Value = 14;
else
if(Hex == "F")
Value = 15;
else
Value = eval(Hex)
return Value;
}
util.GiveHex = function GiveHex(Dec)
{
if(Dec == 10)
Value = "A";
else
if(Dec == 11)
Value = "B";
else
if(Dec == 12)
Value = "C";
else
if(Dec == 13)
Value = "D";
else
if(Dec == 14)
Value = "E";
else
if(Dec == 15)
Value = "F";
else
Value = "" + Dec;
return Value;
}
/**
* http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
*
* @param {String} hex
* @returns {{r: *, g: *, b: *}}
*/
util.hexToRGB = function hexToRGB(hex) {
hex = hex.replace("#","").toUpperCase();
var a = util.GiveDec(hex.substring(0, 1));
var b = util.GiveDec(hex.substring(1, 2));
var c = util.GiveDec(hex.substring(2, 3));
var d = util.GiveDec(hex.substring(3, 4));
var e = util.GiveDec(hex.substring(4, 5));
var f = util.GiveDec(hex.substring(5, 6));
var r = (a * 16) + b;
var g = (c * 16) + d;
var b = (e * 16) + f;
return {r:r,g:g,b:b};
};
util.RGBToHex = function RGBToHex(red,green,blue) {
var a = util.GiveHex(Math.floor(red / 16));
var b = util.GiveHex(red % 16);
var c = util.GiveHex(Math.floor(green / 16));
var d = util.GiveHex(green % 16);
var e = util.GiveHex(Math.floor(blue / 16));
var f = util.GiveHex(blue % 16);
var hex = a + b + c + d + e + f;
return "#" + hex;
};
/**
* http://www.javascripter.net/faq/rgb2hsv.htm
*
* @param red
* @param green
* @param blue
* @returns {*}
* @constructor
*/
util.RGBToHSV = function RGBToHSV (red,green,blue) {
red=red/255; green=green/255; blue=blue/255;
var minRGB = Math.min(red,Math.min(green,blue));
var maxRGB = Math.max(red,Math.max(green,blue));
// Black-gray-white
if (minRGB == maxRGB) {
return {h:0,s:0,v:minRGB};
}
// Colors other than black-gray-white:
var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
var hue = 60*(h - d/(maxRGB - minRGB))/360;
var saturation = (maxRGB - minRGB)/maxRGB;
var value = maxRGB;
return {h:hue,s:saturation,v:value};
};
/**
* https://gist.github.com/mjijackson/5311256
* @param hue
* @param saturation
* @param value
* @returns {{r: number, g: number, b: number}}
* @constructor
*/
util.HSVToRGB = function HSVToRGB(h, s, v) {
var r, g, b;
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
};
util.HSVToHex = function HSVToHex(h,s,v) {
var rgb = util.HSVToRGB(h,s,v);
return util.RGBToHex(rgb.r,rgb.g,rgb.b);
}
util.hexToHSV = function hexToHSV(hex) {
var rgb = util.hexToRGB(hex);
return util.RGBToHSV(rgb.r,rgb.g,rgb.b);
}

Loading…
Cancel
Save