Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple elements are draggable #31

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion app/components/Caption.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import merge from 'lodash/object/merge';
export default class Caption extends BaseComponent {
constructor(props) {
super(props);
this.bindAll('_handleDragStart', '_handleDrag', '_handleDragStop', '_handleClick');
this.bindAll('_handleDragStart', '_handleDrag', '_handleDragStop', '_doDragStart', '_doDrag', '_doDragStop', '_handleClick');
this.state = props.caption.display;
}

Expand Down Expand Up @@ -78,18 +78,39 @@ export default class Caption extends BaseComponent {
}
}


// keep initial position for comparison with drag position
_handleDragStart(e, ui) {
e.preventDefault();
var isOnlyOne = (this.graph.props.selection["nodeIds"].length + this.graph.props.selection["captionIds"].length + this.graph.props.selection["edgeIds"].length) < 2;
if (!this.graph.props.showEditTools || !this.props.selected || isOnlyOne) {
this._doDragStart(e, ui);
} else {
this.props.onStart(e, ui, this);
}
}

_doDragStart(e, ui) {
this._startDrag = ui.position;
this._startPosition = {
x: this.state.x,
y: this.state.y
};
}

// while dragging node and its edges are updated only in state, not store
_handleDrag(e, ui) {

if (this.props.isLocked) return;
var isOnlyOne = (this.graph.props.selection["nodeIds"].length + this.graph.props.selection["captionIds"].length + this.graph.props.selection["edgeIds"].length) < 2;
if (!this.graph.props.showEditTools || !this.props.selected || isOnlyOne) {
this._doDrag(e, ui, false);
} else {
this.props.onDrag(e, ui, this);
}
}

_doDrag(e, ui, isMultiple) {
this._dragging = true;

let deltaX = (ui.position.clientX - this._startDrag.clientX) / this.graph.state.actualZoom;
Expand All @@ -98,10 +119,29 @@ export default class Caption extends BaseComponent {
let y = this._startPosition.y + deltaY;

this.setState({ x, y });

//update throughout drag so nodes know their siblings' positions when
//multiple nodes are dragged simultaneously
if (isMultiple){
if (this._dragging) {
this.props.moveCaption(this.props.caption.id, this.state.x, this.state.y);
}
}
}

// store updated once dragging is done
_handleDragStop(e, ui) {
// event fires every mouseup so we check for actual drag before updating store
var isOnlyOne = (this.graph.props.selection["nodeIds"].length + this.graph.props.selection["captionIds"].length + this.graph.props.selection["edgeIds"].length) < 2;
if (!this.graph.props.showEditTools || !this.props.selected || isOnlyOne) {
this._doDragStop(e, ui);
} else {
this.props.onStop(e, ui, this);
}
}

_doDragStop(e, ui){
// event fires every mouseup so we check for actual drag before updating store
if (this._dragging) {
this.props.moveCaption(this.props.caption.id, this.state.x, this.state.y);
}
Expand Down
40 changes: 35 additions & 5 deletions app/components/Edge.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import classNames from 'classnames';
export default class Edge extends BaseComponent {
constructor(props) {
super(props);
this.bindAll('_handleDragStart', '_handleDrag', '_handleDragStop', '_handleClick', '_handleTextClick');
this.bindAll('_handleDragStart', '_handleDrag', '_handleDragStop', '_doDragStart', '_doDrag', '_doDragStop', '_handleClick', '_handleTextClick');
// need control point immediately for dragging
let { cx, cy } = this._calculateGeometry(props.edge.display);
this.state = merge({}, props.edge.display, { cx, cy });
Expand Down Expand Up @@ -85,7 +85,7 @@ export default class Edge extends BaseComponent {
JSON.stringify(nextState) !== JSON.stringify(this.state);
}

_handleDragStart(event, ui) {
_doDragStart(event, ui) {
event.preventDefault();
this._startDrag = ui.position;
this._startPosition = {
Expand All @@ -94,7 +94,7 @@ export default class Edge extends BaseComponent {
}
}

_handleDrag(event, ui) {
_doDrag(event, ui, isMultiple) {
if (this.props.isLocked) return;

this._dragging = true; // so that _handleClick knows it's not just a click
Expand All @@ -106,15 +106,45 @@ export default class Edge extends BaseComponent {
let cy = this._startPosition.y + deltaY;

this.setState({ cx, cy });

if (isMultiple){
if (this._dragging) {
this.props.moveEdge(this.props.edge.id, this.state.cx, this.state.cy);
}
}
}

_handleDragStop(e, ui) {
// event fires every mouseup so we check for actual drag before updating store
_doDragStop(event, ui) {
if (this._dragging) {
this.props.moveEdge(this.props.edge.id, this.state.cx, this.state.cy);
}
}

_handleDragStart(event, ui) {
if (!this.graph.props.showEditTools || !this.props.selected) {
this._doDragStart(event, ui, false);
} else {
this.props.onStart(event, ui, this);
}
}

_handleDrag(event, ui) {
if (!this.graph.props.showEditTools || !this.props.selected) {
this._doDrag(event, ui);
} else {
this.props.onDrag(event, ui, this);
}
}

_handleDragStop(e, ui) {
// event fires every mouseup so we check for actual drag before updating store
if (!this.graph.props.showEditTools || !this.props.selected) {
this._doDragStop(event, ui);
} else {
this.props.onStop(event, ui, this);
}
}

_handleClick() {
if (this._dragging) {
this._dragging = false;
Expand Down
65 changes: 59 additions & 6 deletions app/components/Graph.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ import values from 'lodash/object/values';
import min from 'lodash/collection/min';
import max from 'lodash/collection/max';
import includes from 'lodash/collection/includes';
import filter from 'lodash/collection/filter';


export default class Graph extends BaseComponent {
constructor(props) {
super(props);
this.bindAll('_handleDragStart', '_handleDrag', '_handleDragStop');
this.bindAll('_handleDragStart', '_handleDrag', '_handleDragStop', '_handleDragGroupStart', '_handleDragGroup', '_handleDragGroupStop', '_manageDragStage');
this.nodes = {};
this.edges = {};
this.captions = {};
this.mounted = false;
let viewBox = this._computeViewbox(props.graph, props.zoom, props.viewOnlyHighlighted);
this.state = { x: 0, y: 0, viewBox, height: props.height };

}

render() {
let { x, y, prevGraph, viewBox, height } = this.state;

return (
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" className="Graph" width="100%" height={height} viewBox={viewBox} preserveAspectRatio="xMidYMid">
<DraggableCore
Expand Down Expand Up @@ -69,7 +72,10 @@ export default class Graph extends BaseComponent {
selected={this.props.selection && includes(this.props.selection.edgeIds, e.id)}
clickEdge={this.props.clickEdge}
moveEdge={this.props.moveEdge}
isLocked={this.props.isLocked} />);
isLocked={this.props.isLocked}
onStart={this._handleDragGroupStart}
onDrag={this._handleDragGroup}
onStop={this._handleDragGroupStop} />);
}

_renderNodes() {
Expand All @@ -83,20 +89,26 @@ export default class Graph extends BaseComponent {
selected={this.props.selection && includes(this.props.selection.nodeIds, n.id)}
clickNode={this.props.clickNode}
moveNode={this.props.moveNode}
isLocked={this.props.isLocked} />);
isLocked={this.props.isLocked}
onStart={this._handleDragGroupStart}
onDrag={this._handleDragGroup}
onStop={this._handleDragGroupStop} />);
}

_renderCaptions() {
return values(this.props.graph.captions).map((c, i) =>
<Caption
ref={(c) => { if (c) { c.graph = this; } }}
ref={(a) => { this.captions[c.id] = a; if (a) { a.graph = this; }} }
key={c.id}
caption={c}
graphId={this.props.graph.id}
selected={this.props.selection && includes(this.props.selection.captionIds, c.id)}
moveCaption={this.props.moveCaption}
clickCaption={this.props.clickCaption}
isLocked={this.props.isLocked} />);
isLocked={this.props.isLocked}
onStart={this._handleDragGroupStart}
onDrag={this._handleDragGroup}
onStop={this._handleDragGroupStop} />);
}

_renderMarkers() {
Expand Down Expand Up @@ -214,6 +226,47 @@ export default class Graph extends BaseComponent {
return { x, y };
}

//GROUP DRAGGING
// keep initial position for comparison with drag position
_handleDragGroupStart(e, ui, that) {
this._manageDragStage(e, ui, that, "start");
}

_handleDragGroup(e, ui, that) {
this._manageDragStage(e, ui, that, "drag");
}

_handleDragGroupStop(e, ui, that) {
this._manageDragStage(e, ui, that, "stop");
}

_manageDragStage(e, ui, that, stage){
var theSelection = this.props.selection;
var elements = ["node", "caption", "edge"];
//cycle through elements and move selected elements
for (var i = 0; i < elements.length; i++){
var thisElement = theSelection[elements[i] + "Ids"];
var selectedElements = _.filter(this[elements[i] + "s"], function(d){
return _.indexOf(thisElement, d.props[elements[i]]["id"]) != -1;
})

if (stage == "start"){
_.forEach(selectedElements, function(d){
d._doDragStart(e, ui);
})
} else if (stage == "drag"){
_.forEach(selectedElements, function(d){
d._doDrag(e, ui, true);
})
} else {
_.forEach(selectedElements, function(d){
d._doDragStop(e, ui);
})
}
}
}


// TRANSITION ANIMATION

_animateTransition(oldViewBox, viewBox, duration) {
Expand Down
71 changes: 54 additions & 17 deletions app/components/Node.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Helpers from '../models/Helpers';
export default class Node extends BaseComponent {
constructor(props) {
super(props);
this.bindAll('_handleDragStart', '_handleDrag', '_handleDragStop', '_handleClick');
this.bindAll('_handleDragStart', '_handleDrag', '_handleDragStop', '_doDragStart', '_doDrag', '_doDragStop', '_handleClick');
this.state = props.node.display;
}

Expand Down Expand Up @@ -47,13 +47,23 @@ export default class Node extends BaseComponent {
}

shouldComponentUpdate(nextProps, nextState) {
// return true;
return nextProps.selected !== this.props.selected ||
JSON.stringify(nextState) !== JSON.stringify(this.state);
}

// keep initial position for comparison with drag position
_handleDragStart(e, ui) {
e.preventDefault();
var isOnlyOne = (this.graph.props.selection["nodeIds"].length + this.graph.props.selection["captionIds"].length + this.graph.props.selection["edgeIds"].length) < 2;
if (!this.graph.props.showEditTools || !this.props.selected || isOnlyOne) {
this._doDragStart(e, ui);
} else {
this.props.onStart(e, ui, this);
}
}

_doDragStart(e, ui) {
this._startDrag = ui.position;
this._startPosition = {
x: this.state.x,
Expand All @@ -63,36 +73,63 @@ export default class Node extends BaseComponent {

// while dragging node and its edges are updated only in state, not store
_handleDrag(e, ui) {

if (this.props.isLocked) return;

this._dragging = true; // so that _handleClick knows it's not just a click
var isOnlyOne = (this.graph.props.selection["nodeIds"].length + this.graph.props.selection["captionIds"].length + this.graph.props.selection["edgeIds"].length) < 2;
if (!this.graph.props.showEditTools || !this.props.selected || isOnlyOne) {
this._doDrag(e, ui, false);
} else {
this.props.onDrag(e, ui, this);
}
}

let n = this.props.node;
let deltaX = (ui.position.clientX - this._startDrag.clientX) / this.graph.state.actualZoom;
let deltaY = (ui.position.clientY - this._startDrag.clientY) / this.graph.state.actualZoom;
let x = this._startPosition.x + deltaX;
let y = this._startPosition.y + deltaY;
_doDrag(e, ui, isMultiple) {
this._dragging = true; // so that _handleClick knows it's not just a click

this.setState({ x, y });
let n = this.props.node;
let deltaX = (ui.position.clientX - this._startDrag.clientX) / this.graph.state.actualZoom;
let deltaY = (ui.position.clientY - this._startDrag.clientY) / this.graph.state.actualZoom;
let x = this._startPosition.x + deltaX;
let y = this._startPosition.y + deltaY;

// update state of connecting edges
let edges = Graph.edgesConnectedToNode(this.props.graph, n.id);
this.setState({ x, y });

edges.forEach(edge => {
let thisNodeNum = edge.node1_id == n.id ? 1 : 2;
let newEdge = Graph.moveEdgeNode(edge, thisNodeNum, x, y);
this.graph.edges[edge.id].setState(newEdge.display);
});
// update state of connecting edges
let edges = Graph.edgesConnectedToNode(this.props.graph, n.id);

edges.forEach(edge => {
let thisNodeNum = edge.node1_id == n.id ? 1 : 2;
let newEdge = Graph.moveEdgeNode(edge, thisNodeNum, x, y);
this.graph.edges[edge.id].setState(newEdge.display);
});

//update throughout drag so nodes know their siblings' positions when
//multiple nodes are dragged simultaneously
if (isMultiple){
if (this._dragging) {
this.props.moveNode(this.props.node.id, this.state.x, this.state.y);
}
}
}

// store updated once dragging is done
_handleDragStop(e, ui) {
// event fires every mouseup so we check for actual drag before updating store
if (this._dragging) {
this.props.moveNode(this.props.node.id, this.state.x, this.state.y);
var isOnlyOne = (this.graph.props.selection["nodeIds"].length + this.graph.props.selection["captionIds"].length + this.graph.props.selection["edgeIds"].length) < 2;
if (!this.graph.props.showEditTools || !this.props.selected || isOnlyOne) {
this._doDragStop(e, ui);
} else {
this.props.onStop(e, ui, this);
}
}

_doDragStop(e, ui){
if (this._dragging) {
this.props.moveNode(this.props.node.id, this.state.x, this.state.y);
}
}

_handleClick() {
if (this._dragging) {
this._dragging = false;
Expand Down