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

Minimal drag and drop patch based on PR 292 #307

Open
wants to merge 2 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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"_test": "tape 'test/**/*-test.js'",
"_prepublish": "npm run test && uglifyjs build/d3-org-chart.js -c -m -o build/d3-org-chart.min.js",
"_postpublish": "zip -j build/d3-org-chart.zip -- LICENSE README.md build/d3-org-chart.js build/d3-org-chart.min.js",
"pretest": "rm -rf build && mkdir build && rollup -g d3-selection:d3,d3-array:d3,d3-hierarchy:d3,d3-zoom:d3,d3-flextree:d3,d3-shape:d3,d3-group:d3 -f umd -n d3 -o build/d3-org-chart.js -- index.js",
"pretest": "rm -rf build && mkdir build && rollup -g d3-drag:d3,d3-selection:d3,d3-array:d3,d3-hierarchy:d3,d3-zoom:d3,d3-flextree:d3,d3-shape:d3,d3-group:d3 -f umd -n d3 -o build/d3-org-chart.js -- index.js",
"test": "tape 'test/**/*-test.js'",
"prepublish": "npm run test && uglifyjs build/d3-org-chart.js -c -m -o build/d3-org-chart.min.js",
"postpublish": "zip -j build/d3-org-chart.zip -- LICENSE README.md build/d3-org-chart.js build/d3-org-chart.min.js"
Expand All @@ -49,6 +49,7 @@
"uglify-js": "3"
},
"dependencies": {
"d3-drag": "^3.0.0",
"d3-selection": "3",
"d3-array": "3",
"d3-hierarchy": "3",
Expand Down
186 changes: 183 additions & 3 deletions src/d3-org-chart.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { selection, select } from "d3-selection";
import { selection, select, selectAll } from "d3-selection";
import { max, min, sum, cumsum } from "d3-array";
import { tree, stratify } from "d3-hierarchy";
import { zoom, zoomIdentity } from "d3-zoom";
import { flextree } from 'd3-flextree';
import { linkHorizontal } from 'd3-shape';
import { drag } from 'd3-drag';

const d3 = {
selection,
select,
selectAll,
max,
min,
sum,
Expand All @@ -17,6 +19,11 @@ const d3 = {
zoom,
zoomIdentity,
linkHorizontal,
drag,
data: null,
sourceNode: null,
targetNode: null,
attrs: null,
flextree
}

Expand Down Expand Up @@ -287,6 +294,36 @@ export class OrgChart {
.attr("marker-start", d => `url(#${d.from + "_" + d.to})`)
.attr("marker-end", d => `url(#arrow-${d.from + "_" + d.to})`)
},
linkUpdate: function (d, i, arr) {
d3.select(this)
.attr("stroke", d => d.data._upToTheRootHighlighted ? '#152785' : 'lightgray')
.attr("stroke-width", d => d.data._upToTheRootHighlighted ? 5 : 2)

if (d.data._upToTheRootHighlighted) {
d3.select(this).raise()
}
},
nodeUpdate: function (d, i, arr) {
d3.select(this)
.select('.node-rect')
.attr("stroke", d => d.data._highlighted || d.data._upToTheRootHighlighted ? '#152785' : 'none')
.attr("stroke-width", d.data._highlighted || d.data._upToTheRootHighlighted ? 10 : 1)
},

nodeWidth: d3Node => 250,
nodeHeight: d => 150,
siblingsMargin: d3Node => 20,
childrenMargin: d => 60,
neightbourMargin: (n1, n2) => 80,
compactMarginPair: d => 100,
compactMarginBetween: (d3Node => 20),
onNodeClick: (d) => d,
svg: null,
dragHandler: null,
refresh: this,
descendants: null,
onDrop: (dropData) => dropData,
onDrag: () => {},
// Link generator for connections
linkGroupArc: d3.linkHorizontal().x(d => d.x).y(d => d.y),

Expand Down Expand Up @@ -678,9 +715,134 @@ export class OrgChart {
attrs.firstDraw = false;
}

attrs.svg = svg;

// // add drag and drop
d3.attrs = attrs;

return this;
}

dragAttachHandler() {
const attrs = this.getChartState();
attrs.svg.selectAll(".node").call(
d3.drag()
.on("start", this.dragstarted)
.on("drag", this.dragged)
.on("end", this.dragended)
);
}
dragstarted(d) {
console.log(d)
d.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
d3.sourceNode = d;
}
dragged(d, event) {
const x = (d.x) - (event.width / 2);

// const _x = (event.x - d.x);
// const _y = (event.y - d.y);
// const moveThreshold = 30;
// const isMoved = (_x > -(moveThreshold) && _x < (moveThreshold)) || (_y > -(moveThreshold) && _y < (moveThreshold));

// if(!isMoved) return;

d3.select(this).raise().attr("transform", `translate(${x},${d.y})`);
// set default style
d3.selectAll("rect").attr("fill", "#fff").attr("stroke", "null").attr(
"stroke-width",
"1px"
);
d3.targetNode = null;
// check nodes overlapping
const cP = {
x0: d.x,
y0: d.y,
x1: d.x + event.width,
y1: d.y + event.height,
};

d3.selectAll("g.node:not(.dragging)").filter((d, i) => {
const cPInner = {
x0: d.x,
y0: d.y,
x1: d.x + d.width,
y1: d.y + d.height,
};
if (
(cP.x1 > cPInner.x0 && cP.x0 < cPInner.x1) &&
(cP.y1 > cPInner.y0 && cP.y0 < cPInner.y1)
Copy link

@joelcoxokc joelcoxokc Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into issues here where a node would have collisions with multiple nodes.
Ideally we want a collision with one node.

So in my code I changed this condition to account for the width of the node.

Basically if the sourceNode is halfway over the targetNode I then assign it as the target node.

cP.x1 - event.width / 2 > cPInner.x0 &&
cP.x0 + event.width / 2 < cPInner.x1 &&
cP.y1 - event.height / 2 > cPInner.y0 &&
cP.y0 + event.height / 2 < cPInner.y1

Using the half way mark is my best approach because the sourceNode could never be halfway over multiple target nodes

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also store halfWidth and halfHeight on cP so that you don't have to keep calculating inside the condition

Copy link

@joelcoxokc joelcoxokc Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const cP = {
    x0: d.x,
    y0: d.y,
    x1: d.x + event.width,
    y1: d.y + event.height,
    midX: d.x + event.width / 2,
    midY: d.y + event.height / 2
};



cP.midX > cPInner.x0 &&
cP.midX < cPInner.x1 &&
cP.midY > cPInner.y0 &&
cP.midY < cPInner.y1

) {
d3.targetNode = d;
return d;
}
}).select("rect").attr("fill", "#e4e1e1").attr("stroke", "#e4e1e1")
.attr("stroke-width", "2px");
}
dragended(d) {
if (!d3.attrs.data || d3.attrs.data.length == 0) {
console.log("ORG CHART - Data is empty");
return this;
}
d3.select(this).classed("dragging", false);

// set default style
d3.selectAll("rect").attr("fill", "#fff").attr("stroke", "null").attr(
"stroke-width",
"1px"
);

const x = (d.subject.x) - (d.subject.width / 2);

d3.select(this).attr("transform", `translate(${x},${(d.subject.y)})`);

if (d3.sourceNode && d3.targetNode) {
const sourceNodeData = d3.sourceNode.subject.data;
const targetNodeData = d3.targetNode.data;

const sourceNodeIndex = d3.attrs.data.findIndex((d) =>
d.id == sourceNodeData.id
);
const targetNodeIndex = d3.attrs.data.findIndex((d) =>
d.id == targetNodeData.id
);

if (targetNodeData.parentId == sourceNodeData.id) {
d3.attrs.data[targetNodeIndex].parentId =
sourceNodeData.parentId;
} else {
const sourceId = sourceNodeData.id;
const sourceParentId = sourceNodeData.parentId;
// get all children of source node
const sourceChildren = d3.attrs.data.filter((d) =>
d.parentId == sourceId
);

if (sourceChildren) {
// replace parentId of all children with source ParentId
sourceChildren.forEach((d) => {
d.parentId = sourceParentId;
});
}
}

d3.attrs.data[sourceNodeIndex].parentId = targetNodeData.id;

d3.attrs.refresh.updateNodesState();

d3.attrs.onDrop({
source: d3.attrs.data[sourceNodeIndex],
target: d3.attrs.data[targetNodeIndex],
});
}
// clear current state
d3.sourceNode = null;
d3.targetNode = null;

// return this;
}

// This function can be invoked via chart.addNode API, and it adds node in tree at runtime
addNode(obj) {
const attrs = this.getChartState();
Expand Down Expand Up @@ -1258,7 +1420,16 @@ export class OrgChart {
nodes: centeredNodes
})
}

// This function detects whether current browser is edge
//
// attach drag and drop event
this.dragAttachHandler();

const _attrs = this.getChartState();
const { root } = _attrs;
if (root && root.descendants()) {
this.descendants = root.descendants();
}
}

// This function detects whether current browser is edge
Expand Down Expand Up @@ -1898,10 +2069,19 @@ export class OrgChart {
return measurement.width;
}

exportData() {
const attrs = this.getChartState();
if (attrs && attrs.data) {
return attrs.data;
} else {
return null;
}
}

// Clear after moving off from the page
clear() {
const attrs = this.getChartState();
d3.select(window).on(`resize.${attrs.id}`, null);
attrs.svg && attrs.svg.selectAll("*").remove();
}
}
}