drag = state => {
function dragsubject(event) {
let subject = null;
let subjectIsOutput = false;
let subjectIsInput = false;
let distance = maxDistance;
var nodeIdx = 0;
const ex = state.transform.invertX(event.x);
const ey = state.transform.invertY(event.y);
const pinSep = 15;
const pin = {w: 15, h: 15};
for (const c of state.nodes) {
var idx = 0;
for (var i in c.outputs) {
const cx = c.x + idx*pinSep + pin.w/2;
const cy = c.y + c.height - pin.h/2;
if ((Math.abs(ex - cx) < pin.w/2) && (Math.abs(ey - cy) < pin.h/2) && (state.selectedNodes.length === 0)) {
subject = state.connecting;
subject.start.x = cx;
subject.start.y = cy;
subject.start.node = nodeIdx;
subject.start.output = idx;
subject.start.input = -1;
subject.x = state.transform.applyX(cx);
subject.y = state.transform.applyY(cy);
subjectIsOutput = true;
break;
}
idx++;
}
idx = 0;
for (var i in c.inputs) {
const cx = c.x + idx*pinSep + pin.w/2;
const cy = c.y + pin.h/2;
if ((Math.abs(ex - cx) < pin.w/2) && (Math.abs(ey - cy) < pin.h/2) && (state.selectedNodes.length === 0)) {
subject = state.connecting;
subject.start.x = cx;
subject.start.y = cy;
subject.start.node = nodeIdx;
subject.start.input = idx;
subject.start.output = -1;
subject.x = state.transform.applyX(cx);
subject.y = state.transform.applyY(cy);
subjectIsInput = true;
break;
}
idx++;
}
if ((subjectIsOutput || subjectIsInput) && subject) {
break;
}
if ((c.x < ex) && (ex < c.x+c.width) && (c.y < ey) && (ey < c.y+c.height)) {
subject = c;
subject.x = state.transform.applyX(c.x);
subject.y = state.transform.applyY(c.y);
break;
}
nodeIdx++;
}
if (!subject && event.sourceEvent.shiftKey) {
subject = state.selection;
subject.start.x = ex;
subject.start.y = ey;
subject.x = state.transform.applyX(ex);
subject.y = state.transform.applyY(ey);
subject.active = true;
}
return subject;
}
function dragstarted({subject}) {
subject.x = state.transform.invertX(subject.x);
subject.y = state.transform.invertY(subject.y);
subject.active = true;
}
// When dragging, update the subject’s position.
function dragged(event) {
const ex = event.x;
const ey = event.y;
const edx = event.dx;
const edy = event.dy;
const {x, y, k} = state.transform;
// translate all selected nodes
if (!state.selection.active) {
for (var nodeIdx of state.selectedNodes) {
state.nodes[nodeIdx].x += 1/k * edx;
state.nodes[nodeIdx].y += 1/k * edy;
}
}
event.subject.x = state.transform.invertX(ex);
event.subject.y = state.transform.invertY(ey);
state.mouse.x = state.transform.invertX(ex);
state.mouse.y = state.transform.invertY(ey);
}
// When ending a drag gesture, mark the subject as inactive again.
function dragended({subject}) {
const pinSep = 15;
const pin = {w: 15, h: 15};
// handle selection
if (state.selection.active) {
state.selectedNodes = [];
const s = state.selection;
var nodeIdx = 0;
for (var i = 0; i < state.nodes.length; i++) {
const n = state.nodes[i];
// handle reversal in rectangle
const sx = s.start.x > s.x ? s.x : s.start.x;
const sy = s.start.y > s.y ? s.y : s.start.y;
const sxm = s.start.x > s.x ? s.start.x : s.x;
const sym = s.start.y > s.y ? s.start.y : s.y;
// compute overlap
const x_overlap = Math.max(0, Math.min(sxm, n.x+n.width) - Math.max(sx, n.x))
const y_overlap = Math.max(0, Math.min(sym, n.y+n.height) - Math.max(sy, n.y));
if (x_overlap * y_overlap > 0.1) {
state.selectedNodes.push(i);
}
}
}
// handle connection
if (state.connecting.active) {
var nodeIdx = 0;
for (const c of state.nodes) {
if (subject.start.output >= 0) {
var idx = 0;
for (var i in c.inputs) {
const cx = c.x + idx*pinSep + pin.w/2;
const cy = c.y + pin.h/2;
if ((Math.abs(subject.x - cx) < pin.w/2) && (Math.abs(subject.y - cy) < pin.h/2)) {
state.connections.push(
{
start: subject.start.node,
end: nodeIdx,
output: subject.start.output,
input: idx
}
);
break;
}
idx++;
}
} else if (subject.start.input >= 0) {
var idx = 0;
for (var i in c.outputs) {
const cx = c.x + idx*pinSep + pin.w/2;
const cy = c.y + c.height - pin.h/2;
if ((Math.abs(subject.x - cx) < pin.w/2) && (Math.abs(subject.y - cy) < pin.h/2)) {
state.connections.push(
{
end: subject.start.node,
start: nodeIdx,
output: idx,
input: subject.start.input
}
);
break;
}
idx++;
}
}
nodeIdx++;
}
var blockIdx;
for (const b of state.blocks) {
if (subject.start.output >= 0) {
var idx = 0;
for (var i in b.outputs) {
const cx = b.x + idx*pinSep + pin.w/2;
const cy = b.y + b.height - pin.h/2;
if ((Math.abs(subject.x - cx) < pin.w/2) && (Math.abs(subject.y - cy) < pin.h/2)) {
break;
}
idx++;
}
} else if (subject.start.input >= 0) {
}
blockIdx++;
}
}
subject.active = false;
}
return d3.drag()
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}