Earlier, the data array was used only for reading, and the positions of the elements such as rectangle, the dots and the text were calculated manually. In the following, we will be updating the data set by adding new values such as position details and dimensions, so that each of the elements corresponding to one object of the data array, have attributes that are relative to each other. In simpler words, we are mapping one object to a set comprising of a rectangle, a text, 2 dots and a line.
Further more, the dragging functionality is updated to detect if it has entered a node on the opposite side. If yes, the end will be attached to the dot on the corresponding node or the end will be removed.
The working application:
The code gist is as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>D3 mapper</title> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
.canvas{ | |
border:1px solid black; | |
} | |
</style> | |
</head> | |
<body> | |
<svg class="canvas" width="1000" height="500"> | |
<g id="input-container"> </g> | |
<g id="output-container"> </g> | |
</svg> | |
<div > | |
<button type="button" onclick="addInput()">Add input node</button> | |
<button type="button" onclick="addOutput()">Add output node</button> | |
</div> | |
<script> | |
var inputs = [{"text": "inleaf1", "col": "red"}, {"text": "inleaf2", "col": "blue"}, {"text": "inleaf3", "col": "green"}], | |
outputs = [{"text": "outleaf1", "col": "blue"}, {"text": "outleaf2", "col": "red"}, {"text": "outleaf3", "col": "green"}]; | |
var elementwidth = 120, | |
elementheight = 50, | |
inputstartx = 20, | |
inputstarty = 20, | |
outputstartx = 700, | |
outputstarty = 20; | |
var verticalmargin = 20; | |
var canvas = d3.select(".canvas"), | |
inputcontainer = d3.select("#input-container"), | |
outputcontainer = d3.select("#output-container"); | |
canvas.append("g").attr("id", function (d) { | |
return inputcontainer.attr("id") + "-2"; | |
}); | |
canvas.append("g").attr("id", function (d) { | |
return outputcontainer.attr("id") + "-2"; | |
}); | |
drawNodeStack(inputcontainer, elementwidth, elementheight, inputstartx, inputstarty, verticalmargin, inputs, "RIGHT", 0); | |
drawNodeStack(outputcontainer, elementwidth, elementheight, outputstartx, outputstarty, verticalmargin, outputs, "LEFT", 1); | |
function detectDropNode(xx, yy, data) { | |
var target = [0, 0]; | |
if (data[0].type === 0) { //if root is from input target is outputs | |
target = outputs; | |
} else { | |
target = inputs; | |
} | |
var i; | |
for (i = 0; i < target.length; i++) { | |
var x = target[i].x, | |
y = target[i].y, | |
width = target[i].width, | |
height = target[i].height; | |
if (xx > x && xx < x + width) { //check whether horizontally in | |
if (yy > y && yy < y + height) { //check whether vertically in | |
return target[i]; | |
} | |
} | |
} | |
return "null"; | |
} | |
function addInput() { | |
inputs.push({"text": "newelement", "col": "red"}); | |
drawNodeStack(inputcontainer, elementwidth, elementheight, inputstartx, inputstarty, verticalmargin, inputs, "RIGHT", 0); | |
} | |
function addOutput() { | |
outputs.push({"text": "newelement", "col": "red"}); | |
drawNodeStack(outputcontainer, elementwidth, elementheight, outputstartx, outputstarty, verticalmargin, outputs, "LEFT", 1); | |
} | |
function drawNodeStack(container, elementwidth, elementheight, startX, startY, verticalmargin, data, dotposition, type) { | |
var coordinates, dragdot2, dragline, | |
dotpositionABS = [0, 0], | |
childcontainer = d3.select("#" + (container.attr("id") + "-2")); | |
if (dotposition === "RIGHT") { | |
dotpositionABS[0] = startX + elementwidth; | |
} else if (dotposition === "LEFT") { | |
dotpositionABS[0] = startX; | |
} | |
dotpositionABS[1] = elementheight / 2 + startY;//updated later | |
var dragme = d3.drag() | |
.on("start", function (d) { | |
var thisdragY = d3.select(this).attr("cy"); | |
var thisdragX = d3.select(this).attr("cx"); | |
var thisdragR = d3.select(this).attr("r"); | |
coordinates = [0, 0]; | |
dragdot2 = childcontainer.append("circle") | |
.attr("cx", thisdragX) | |
.attr("cy", thisdragY) | |
.attr("r", thisdragR) | |
.attr("fill", "black"); | |
dragline = childcontainer.append("line") | |
.attr("x1", thisdragX) | |
.attr("x2", thisdragX) | |
.attr("y1", thisdragY) | |
.attr("y2", thisdragY) | |
.style("stroke", d.col) | |
.style("stroke-width", "2"); | |
}) | |
.on("drag", function (d) { | |
coordinates = d3.mouse(this); | |
xx = coordinates[0]; | |
yy = coordinates[1]; | |
dragline.attr("x2", xx).attr("y2", yy); | |
dragdot2.attr("cx", xx).attr("cy", yy); | |
//if position is inside the outleafs - stroke color change | |
}) | |
.on("end", function (d) { | |
var target = detectDropNode(xx, yy, data); | |
if (target !== "null") { | |
dragline.attr("x2", target.dotposition[0]).attr("y2", target.dotposition[1]); | |
dragdot2.attr("cx", target.dotposition[0]).attr("cy", target.dotposition[1]); | |
} else { | |
dragline.remove(); | |
dragdot2.remove(); | |
} | |
}); | |
var inputleaf = container.selectAll("rect") | |
.data(data) | |
.enter().append("rect") | |
.attr("class", "input-leaf") | |
.attr("width", function (d) { | |
d.width = elementwidth; | |
return elementwidth; | |
}) | |
.attr("height", function (d) { | |
d.height = elementheight; | |
return elementheight; | |
}) | |
.attr("x", function (d) { | |
var myX = startX; | |
d.x = myX; | |
return myX; | |
}) | |
.attr("y", function (d, i) { | |
var myY = startY + ((elementheight + verticalmargin) * i); | |
d.y = myY; | |
d.dotposition = [0, 0]; | |
d.dotposition[0] = dotpositionABS[0]; //set the dot position | |
d.dotposition[1] = myY + (d.height) / 2; | |
d.type = type; //set the type | |
return myY; | |
}) | |
.attr("stroke-width", "2") | |
.attr("fill", "none") | |
.attr("stroke", function (d) { | |
return d.col; | |
}); | |
var inputtext = container.selectAll("text") | |
.data(data) | |
.enter().append("text") | |
.attr("x", function (d) { | |
return d.x; | |
}) | |
.attr("y", function (d, i) { | |
return d.y + (d.height) / 2; | |
}) | |
.text(function (d) { | |
return d.text; | |
}); | |
var inputdragdot = container.selectAll("circle") | |
.data(data) | |
.enter().append("circle").attr("r", elementheight / 4) | |
.attr("cx", function (d) { | |
return d.dotposition[0]; | |
}) | |
.attr("cy", function (d, i) { | |
return d.dotposition[1]; | |
}) | |
.attr("r", function (d) { | |
return (d.height) / 5; | |
}) | |
.attr("fill", "black") | |
.call(dragme); | |
} | |
</script> | |
</body> | |
</html> |
Hi nice one. Can you tell me why you use childcontainer? can't we achieve the functionality without child container?
ReplyDelete