D3js dynamically attach circles into a line with drag and drop - javascript

I am very new to d3js v3 and I was trying out a new program where there are lines and the according to the data, circles get embedded into them.
This is what I have so far.
var width = 500,
height = 500;
var animals = ['dog', 'cat', 'bat'];
var fruits = ['apple', 'banana'];
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var line1 = svg.append("line")
.attr("x1", 350)
.attr("y1", 5)
.attr("x2", 350)
.attr("y2", 350)
.attr("stroke-width", 2)
.attr("stroke", "black");
var line2 = svg.append("line")
.attr("x1", 80)
.attr("y1", 5)
.attr("x2", 100)
.attr("y2", 350)
.attr("stroke-width", 2)
.attr("stroke", "black");
var animal_scale = d3.scale.ordinal()
.domain(animals)
.rangePoints([5, 350],.2);
var fruit_scale = d3.scale.ordinal()
.domain(fruits)
.rangePoints([5, 350],.2);
var animal_circles = svg.selectAll('circle')
.data(animals)
.enter()
.append('circle')
.attr('cx', function(d) {
// is there a way to calc it automatically according to line 1
})
.attr('cy', function(d) {
return animal_scale(d);
})
.attr('id', function(d) {
return d;
})
.attr('r', 20);
var fruits_circles = svg.selectAll('circle')
.data(fruits)
.enter()
.append('circle')
.attr('cx', function(d) {
// is there a way to calc it automatically according to line 2
})
.attr('cy', function(d) {
return fruit_scale(d);
})
.attr('id', function(d) {
return d;
})
.attr('r', 20);
<!DOCTYPE html>
<html>
<meta charset=utf-8>
<head>
<title></title>
</head>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
</body>
</html>
I looked at some sources and being new, its kinda hard to understand most of it. I eventually want to be able to move and drag the circles between lines at the end of the project.There are some issues with the current code, as it does not display the second set of circles too.
Could someone please help me understand further how to do this. It would be a great way for me to learn.

You can select objects by class name and set data. Here is my fast solution for drag-n-drop: jsFiddle. You can modify drag function to add limits to cx position

Related

Displaying text after an onclick event

I'm using click events to log data to the console, but i'd like to display this data in a separate box (which i have created). Does anyone have any advice or suggestions for this? Or is there a decent library that can help me achieve this?
Cheers
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 7)
.attr("cx", function(d) { return xScale(d[1]); })
.attr("cy", function(d) { return yScale(d[2]); })
.on('click', function(d, i) {
console.log("click", d[0]);
})
.attr("fill", function(d) {
var result = null;
if (data.indexOf(d) >= 0) {
result = colours(d);
} else {
result = "white";
}
return result;
});
var textBox = svg.append("rect")
.attr("x", 5)
.attr("y", 385)
.attr("height", 150)
.attr("width", 509)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
In the "click" listener just select your box, or use the selection you already have:
circles.on("click", function(d) {
selection.append("text")
//etc...
})
Here is a simple demo, click the circle:
var svg = d3.select("svg");
var circle = svg.append("circle")
.datum({
name: "foo"
})
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 60)
.style("fill", "teal");
var box = svg.append("g")
.attr("transform", "translate(300,50)");
box.append("rect")
.attr("height", 50)
.attr("width", 100)
.style("stroke", "black")
.style("fill", "none")
.style("stroke-width", "2px");
circle.on("click", function(d) {
box.append("text")
.attr("x", 10)
.attr("y", 20)
.text(d.name)
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="300"></svg>
Finally, two tips: make your selection a group or any other valid container for the text, not a rectangle, because you cannot append a text to a rectangle. Also, be prepared for all kinds of problems trying to fit your texts inside that rectangle: wrapping texts in an SVG is notoriously complicated.

d3 mouse move cross hair boundary

[Update]
Click jsfiddle here.
[Original Post]
How can I limit the coordinates of cross hair when moving the mouse? Notice when I move my mouse to the left of x-axis or bottom of y-axis, the cross hair and text still shows. I want the cross hair and text to stop showing when either mouse is moved to the left of x-axis or bottom of y-axis.
I have tried to add if else to limit the cross hair, but it didn't work. For instance, I tried something like .style("display", (xCoord>=minX & xCoord<=maxX & yCoord>=minY & yCoord<=maxY) ? "block" : "none") in the addCrossHair() function.
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
.axis path,
.axis line
{
fill:none;
rendering:crispEdges;
stroke:black;
width:2.5;
}
</style>
</head>
<body>
<script src="d3.min.js"></script>
<script>
var width = 800,
height = 600;
var randomX = [],
randomY = [];
for (var i = 0; i <= 500; i++) {
randomX[i] = Math.random() * 400;
randomY[i] = Math.random() * 400;
}
var minX = d3.min(randomX),
maxX = d3.max(randomX),
minY = d3.min(randomY),
maxY = d3.max(randomY);
var xScale = d3.scale.linear().domain([minX, maxX]).range([0, width]);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yScale = d3.scale.linear().domain([minY, maxY]).range([height, 0]);
var yAxis = d3.svg.axis().scale(yScale).orient("left");
var svgContainer = d3.select("body").append("div").append("svg").attr("width", width).attr("height", height);
var svg = svgContainer.append("g").attr("transform", "translate(50, 50)");
svg.append("g").attr("class", "axis").attr("transform", "translate(0,530)").call(xAxis);
svg.append("g").attr("class", "axis").call(yAxis);
var crossHair = svg.append("g").attr("class", "crosshair");
crossHair.append("line").attr("id", "h_crosshair") // horizontal cross hair
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", 0)
.style("stroke", "gray")
.style("stroke-width", "1px")
.style("stroke-dasharray", "5,5")
.style("display", "none");
crossHair.append("line").attr("id", "v_crosshair") // vertical cross hair
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", 0)
.style("stroke", "gray")
.style("stroke-width", "1px")
.style("stroke-dasharray", "5,5")
.style("display", "none");
crossHair.append("text").attr("id", "crosshair_text") // text label for cross hair
.style("font-size", "10px")
.style("stroke", "gray")
.style("stroke-width", "0.5px");
svgContainer.on("mousemove", function () {
var xCoord = d3.mouse(this)[0] - 50,
yCoord = d3.mouse(this)[1] - 50;
addCrossHair(xCoord, yCoord);
})
.on("mouseover", function () {d3.selectAll(".crosshair").style("display", "block");})
.on("mouseout", function () {d3.selectAll(".crosshair").style("display", "none");});
function addCrossHair(xCoord, yCoord) {
// Update horizontal cross hair
d3.select("#h_crosshair")
.attr("x1", xScale(minX))
.attr("y1", yCoord)
.attr("x2", xScale(maxX))
.attr("y2", yCoord)
.style("display", "block");
// Update vertical cross hair
d3.select("#v_crosshair")
.attr("x1", xCoord)
.attr("y1", yScale(minY))
.attr("x2", xCoord)
.attr("y2", yScale(maxY))
.style("display", "block");
// Update text label
d3.select("#crosshair_text")
.attr("transform", "translate(" + (xCoord + 5) + "," + (yCoord - 5) + ")")
.text("(" + xScale.invert(xCoord) + " , " + yScale.invert(yCoord) + ")");
}
svg.selectAll("scatter-dots")
.data(randomY)
.enter().append("svg:circle")
.attr("cy", function (d) {
return yScale(d);
})
.attr("cx", function (d, i) {
return xScale(randomX[i]);
})
.style("fill", "brown")
.attr("r", 3)
</script>
</body>
The way to do this is to make the actual canvas (i.e. where you're drawing the dots) a separate g element from the ones that the axes are rendered into. Then the canvas can be translated such that it sits to the right and above the axes. The crosshair handler would be attached to this canvas g element (which is not quite straightforward, see this question) and the crosshairs or dots won't appear outside of the canvas.
Complete demo here.

D3: adding a line when clicking on a circle in scatter plot

I have a scatter plot. Now if I click on one of the points, how can I generate a line passing through that point?
I am stuck at two places:
With the following code, why is my line now showing?
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="d3.min.js"></script>
<script>
var width = 500, height = 500;
var randomX=[], randomY=[];
for (var i=0; i<=50; i++) {
randomX[i] = Math.random()*400;
randomY[i] = Math.random()*400;}
var data = randomX.concat(randomY);
var x = d3.scale.linear()
.domain([0, d3.max(randomX)])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(randomY)])
.range([height, 0]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
svg.selectAll("scatter-dots")
.data(randomY)
.enter().append("svg:circle")
.attr("cy", function(d) {return y(d); } )
.attr("cx", function(d,i) {return x(randomX[i]); } )
.style("fill", "brown")
.attr("r", 5)
.on("click", function(d,i) {
d3.select(this)
.append("svg:line")
.attr("x1", 300).attr("y1", 300)
.attr("x2", 50).attr("y2", 50)
.style("stroke", "steelblue")
.style("stroke-width", 3);
});
</script>
Where is the coordinates of my clicked point stored? I tried this.cx and this.cy, but none of them gave me the actual coordinates.
First, you need to append the line element to the top-level SVG or a g element, not a circle element, otherwise it won't be shown. So in your click handler, you would need to do this:
.on("click", function(d,i) {
svg.append("svg:line")
.attr("x1", 300).attr("y1", 300)
.attr("x2", 50).attr("y2", 50)
.style("stroke", "steelblue")
.style("stroke-width", 3);
});
You can get the coordinates of the click either through d3.event or the coordinates of the circle itself, i.e.
.on("click", function(d,i) {
var x = x(randomX[i]),
y = y(d);
});
or even
.on("click", function(d,i) {
var x = d3.select(this).attr("cx"),
y = d3.select(this).attr("cy");
});

Add node on click on another node with d3js

Recently I tried mastering the d3js library (which is great) and I am trying to do the easiest stuff but for some reason it doesn't work correctly at all. I did look at related issues but none could help me to find my problem.
The setup is simple, I have a force layout with only one node and no link. When the user clicks on that node, I would like to add a new node that gets linked to the node that was clicked. Here is my code so far. The first added node has a very random position and after that I keep having a message "Uncaught TypeError: Cannot read property '__data__' of null".
Thank you for your help !
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 test</title>
</head>
<body>
<div id="viz">
</div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript">
var w = 800;
var h = 800;
var padding = 100;
var svg = d3.select("#viz")
.append("svg")
.attr("width", w)
.attr("height", h);
var nodes = [{name:"n0"}];
var links = [];
var force = d3.layout.force()
.size([w, h])
.linkDistance([100])
.charge([-30])
.start();
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
update();
function update() {
force.nodes(nodes)
.links(links)
.start();
link = link.data(force.links(), function(d) {return d.source.name + "-" + d.target.name;})
.enter()
.insert("line", ".node")
.attr("class", "link")
.style("stroke", "red")
.style("stroke-width", 2);
node = node.data(force.nodes(), function(d) {return d.name;})
.enter()
.append("circle")
.attr("class", "node")
.attr("r", 10)
.style("fill", "black")
.on("click", function(d) {
var n = {name:"n"+nodes.length};
nodes.push(n);
links.push({source:d, target:n});
update();
})
.call(force.drag);
}
force.on("tick", function() {
link.attr("x1", function(d) {return d.source.x;})
.attr("y1", function(d) {return d.source.y;})
.attr("x2", function(d) {return d.target.x;})
.attr("y2", function(d) {return d.target.y;});
node.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y});
});
</script>
</body>
For a reason that I can't explain, it works perfectly when I change this bit
node = node.data(force.nodes(), function(d) {return d.name;}).enter()...
to this
node = node.data(force.nodes(), function(d) {return d.name;})
node.enter()...
And do the same to the "link" selection
I guess it has to do with enter selections and co.

d3.js - the location of circle is not where it is expected. Is this a bug, or am I missing something?

Please see the working example of d3.js script below. The vertical line, mline1 is located at x-axis value of 16000. The line sloping down, dline is supposed to represent the equation
x = 48000 - 16000*y. The intersection of mline1 and dline should occur at value of x=16000 and y=2. I draw a circle with center at x=16000 and y=2, and I expect it to be at the intersection of the two lines above. But it is not. Would really appreciate if you could help me understand this, or let me know if this is a bug in d3.js scaling behavior, or some other bug.
Thanks in advance.
The Code
<html>
<head>
<script src="http://d3js.org/d3.v2.js"></script>
</head>
<body>
<div id="viz"></div>
<script type="text/javascript">
var paddingH = 50;
var paddingV = 50;
var width = 700;
var height = 400
cv = d3.select("#viz").append("svg:svg")
.attr("width", width)
.attr("height", height)
var xs = d3.scale.linear()
.domain([0,50000])
.range([paddingH,width-paddingH]);
var ys = d3.scale.linear()
.domain([0,5.5])
.range([height-paddingV,paddingV]);
xline = cv.append("svg:line")
.attr("x1", xs(0))
.attr("x2", xs(50000))
.attr("y1", ys(0))
.attr("y2", ys(0))
.style("stroke", "darkgray");
yline = cv.append("svg:line")
.attr("x1", xs(0))
.attr("x2", xs(0))
.attr("y1", ys(0))
.attr("y2", ys(5))
.style("stroke", "darkgray");
dline = cv.append("svg:line")
.attr("x1", xs(0))
.attr("y1", ys(4))
.attr("x2", xs(48000))
.attr("y2", ys(0))
.style("stroke", "steelblue")
.style("stroke-width", 2);
mline1 = cv.append("svg:line")
.attr("x1", xs(16000))
.attr("y1", ys(0))
.attr("x2", xs(16000))
.attr("y2", ys(5))
.style("stroke", "green");
circleIntersect = cv.append("svg:circle")
.attr("cx", xs(16000))
.attr("cy", ys(2))
.attr("r",4)
.style("stroke", "red")
.style("fill", "red");
</script>
</body>
</html>
Your dline does not represent x = 48000 - 16000y; if that were the case, it should cross the y axis at a value of 3, not 4 (16 * 4 = 64).
Either your dline should start at y = 3, or your circle should be at y =~ 2.67.
You can see for yourself here.

Categories