PMTREE / index.html
jarvisx17's picture
Upload 10 files
0f75f16 verified
raw
history blame
10.5 kB
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-org-chart@3.1.0"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-flextree@2.1.2/build/d3-flextree.js"></script>
<style type="text/css">
.hide {
display: none;
}
.drag-enabled:not(.dragging-active) .node.draggable {
stroke: grey;
stroke-width: 3px;
stroke-dasharray: 2px;
}
.drag-enabled.dragging-active .droppable {
stroke: green;
stroke-width: 3px;
stroke-dasharray: 5px;
}
.node.dragging {
stroke-dasharray: 0 !important;
stroke-width: 0 !important;
}
.node.dragging .content-container {
background-color: #ffffff;
}
</style>
<script>
var chart = null;
let dragNode;
let dropNode;
let dragEnabled = false;
let dragStartX;
let dragStartY;
let isDragStarting = false;
let undoActions = [];
let redoActions = [];
// This is the data used - https://github.com/bumbeishvili/sample-data/blob/main/data-oracle.csv
d3.csv(
'new.csv',
).then((data) => {
console.log(data);
chart = new d3.OrgChart()
.nodeHeight((d) => 85 + 25)
.nodeWidth((d) => 220 + 2)
.childrenMargin((d) => 50)
.compactMarginBetween((d) => 35)
.compactMarginPair((d) => 30)
.neighbourMargin((a, b) => 20)
.nodeContent(function (d, i, arr, state) {
return generateContent(d);
})
.nodeEnter(function (node) {
d3.select(this).call(
d3
.drag()
.filter(function (x, node) {
return dragEnabled && this.classList.contains('draggable');
})
.on('start', function (d, node) {
onDragStart(this, d, node);
})
.on('drag', function (dragEvent, node) {
onDrag(this, dragEvent);
})
.on('end', function (d) {
onDragEnd(this, d);
})
);
})
.nodeUpdate(function (d) {
if (d.id === '102' || d.id === '120' || d.id === '124') {
d3.select(this).classed('droppable', false);
} else {
d3.select(this).classed('droppable', true);
}
if (d.id === '101') {
d3.select(this).classed('draggable', false);
} else {
d3.select(this).classed('draggable', true);
}
})
.container('.chart-container')
.data(data)
.render();
});
function onDragStart(element, dragEvent, node) {
dragNode = node;
const width = dragEvent.subject.width;
const half = width / 2;
const x = dragEvent.x - half;
dragStartX = x;
dragStartY = parseFloat(dragEvent.y);
isDragStarting = true;
d3.select(element).classed('dragging', true);
}
function onDrag(element, dragEvent) {
if (!dragNode) {
return;
}
const state = chart.getChartState();
const g = d3.select(element);
// This condition is designed to run at the start of a drag only
if (isDragStarting) {
isDragStarting = false;
document
.querySelector('.chart-container')
.classList.add('dragging-active');
// This sets the Z-Index above all other nodes, by moving the dragged node to be the last-child.
g.raise();
const descendants = dragEvent.subject.descendants();
const linksToRemove = [...(descendants || []), dragEvent.subject];
const nodesToRemove = descendants.filter(
(x) => x.data.id !== dragEvent.subject.id
);
// Remove all links associated with the dragging node
state['linksWrapper']
.selectAll('path.link')
.data(linksToRemove, (d) => state.nodeId(d))
.remove();
// Remove all descendant nodes associated with the dragging node
if (nodesToRemove) {
state['nodesWrapper']
.selectAll('g.node')
.data(nodesToRemove, (d) => state.nodeId(d))
.remove();
}
}
dropNode = null;
const cP = {
width: dragEvent.subject.width,
height: dragEvent.subject.height,
left: dragEvent.x,
right: dragEvent.x + dragEvent.subject.width,
top: dragEvent.y,
bottom: dragEvent.y + dragEvent.subject.height,
midX: dragEvent.x + dragEvent.subject.width / 2,
midY: dragEvent.y + dragEvent.subject.height / 2,
};
// eslint-disable-next-line @typescript-eslint/no-this-alias
const allNodes = d3.selectAll('g.node:not(.dragging)');
allNodes.select('rect').attr('fill', 'none');
allNodes
.filter(function (d2, i) {
const cPInner = {
left: d2.x,
right: d2.x + d2.width,
top: d2.y,
bottom: d2.y + d2.height,
};
if (
cP.midX > cPInner.left &&
cP.midX < cPInner.right &&
cP.midY > cPInner.top &&
cP.midY < cPInner.bottom &&
this.classList.contains('droppable')
) {
dropNode = d2;
return d2;
}
})
.select('rect')
.attr('fill', '#e4e1e1');
dragStartX += parseFloat(dragEvent.dx);
dragStartY += parseFloat(dragEvent.dy);
g.attr('transform', 'translate(' + dragStartX + ',' + dragStartY + ')');
}
function onDragEnd(element, dragEvent) {
document
.querySelector('.chart-container')
.classList.remove('dragging-active');
if (!dragNode) {
return;
}
d3.select(element).classed('dragging', false);
if (!dropNode) {
chart.render();
return;
}
if (dragEvent.subject.parent.id === dropNode.id) {
chart.render();
return;
}
d3.select(element).remove();
const data = chart.getChartState().data;
const node = data?.find((x) => x.id === dragEvent.subject.id);
const oldParentId = node.parentId;
node.parentId = dropNode.id;
redoActions = [];
undoActions.push({
id: dragEvent.subject.id,
parentId: oldParentId,
});
dropNode = null;
dragNode = null;
chart.render();
updateDragActions();
}
function enableDrag() {
dragEnabled = true;
document.querySelector('.chart-container').classList.add('drag-enabled');
document.getElementById('enableDragButton').classList.add('hide');
document.getElementById('dragActions').classList.remove('hide');
}
function disableDrag() {
dragEnabled = false;
document.querySelector('.chart-container').classList.remove('drag-enabled');
document.getElementById('enableDragButton').classList.remove('hide');
document.getElementById('dragActions').classList.add('hide');
undoActions = [];
redoActions = [];
updateDragActions();
}
function cancelDrag() {
if (undoActions.length === 0) {
disableDrag();
return;
}
const data = chart.getChartState().data;
undoActions.reverse().forEach((action) => {
const node = data.find((x) => x.id === action.id);
node.parentId = action.parentId;
});
disableDrag();
chart.render();
}
function undo() {
const action = undoActions.pop();
if (action) {
const node = chart.getChartState().data.find((x) => x.id === action.id);
const currentParentId = node.parentId;
const previousParentId = action.parentId;
action.parentId = currentParentId;
node.parentId = previousParentId;
redoActions.push(action);
chart.render();
updateDragActions();
}
}
function redo() {
const action = redoActions.pop();
if (action) {
const node = chart.getChartState().data.find((x) => x.id === action.id);
const currentParentId = node.parentId;
const previousParentId = action.parentId;
action.parentId = currentParentId;
node.parentId = previousParentId;
undoActions.push(action);
chart.render();
updateDragActions();
}
}
function updateDragActions() {
if (undoActions.length > 0) {
const undoButton = document.getElementById('undoButton');
undoButton.disabled = false;
} else {
undoButton.disabled = true;
}
if (redoActions.length > 0) {
const redoButton = document.getElementById('redoButton');
redoButton.disabled = false;
} else {
redoButton.disabled = true;
}
}
function generateContent(d) {
const color = '#FFFFFF';
const imageDiffVert = 25 + 2;
return `
<div class="node-container" style='
width:${d.width}px;
height:${d.height}px;
padding-top:${imageDiffVert - 2}px;
padding-left:1px;
padding-right:1px'>
<div class="content-container" style="font-family: 'Inter', sans-serif; margin-left:-1px;width:${
d.width - 2
}px;height:${
d.height - imageDiffVert
}px;border-radius:10px;border: ${
d.data._highlighted || d.data._upToTheRootHighlighted
? '5px solid #E27396"'
: '1px solid #E4E2E9"'
} >
<div style="display:flex;justify-content:flex-end;margin-top:5px;margin-right:8px">#${
d.data.id
}</div>
<div style="margin-top:${
-imageDiffVert - 20
}px;margin-left:${15}px;border-radius:100px;width:50px;height:50px;" ></div>
<div style="margin-top:${
-imageDiffVert - 20
}px;"> <img src=" ${
d.data.image
}" style="margin-left:${20}px;border-radius:100px;width:40px;height:40px;" /></div>
<div style="font-size:15px;color:#08011E;margin-left:20px;margin-top:10px"> ${
d.data.name
} </div>
<div style="color:#716E7B;margin-left:20px;margin-top:3px;font-size:10px;"> ${
d.data.position
} </div>
</div>
</div>
`;
}
</script>
<!--
This is the code which is used to show of feature , other parts of the code are part of the boilerplate
-->
<button id="enableDragButton" onclick="enableDrag()">Organize</button>
<div id="dragActions" class="hide">
<button id="finishDrag" onclick="disableDrag()">Done</button>
<button id="undoButton" disabled onclick="undo()">Undo</button>
<button id="redoButton" disabled onclick="redo()">Redo</button>
<button id="cancelDrag" onclick="cancelDrag()">Cancel</button>
</div>
<!--
End of adding node functionality
-->
<div class="chart-container"></div>