Circle Leaving Trails while transition in D3.js - javascript

I am drawing circles with every update using a smooth transition which goes from one location to another.
Here is the code I am using...
function drawChart(newData)
{
var circles = slider.selectAll(".dot")
.data(newData);
circles.enter()
.append("circle")
.merge(circles)
.transition() // and apply changes to all of them
.duration(1000)
.ease(d3.easeLinear)
.attr("class", "dot")
.attr("r", 10.5)
.attr("cx", function(d,i) {
return Math.pow(d.open,i); })
.attr("cy", function(d,i) { return Math.pow(i,5)+d.close; })
.style("fill", function(d) { return color(d.class); });
circles.exit()
.remove();
}
This is how data is updated using the filterData function.
function filterData(dd){
var newData = dataset.filter(function(d) {
return d.date.getDate() == dd.getDate() && d.date.getMonth() == dd.getMonth();
})
drawChart(newData)
}
This code shows the simple circle and transition, whereas I want to have the transition in a way circles are leaving trails while moving as in this picture. .
Is there any way to do this? Any help would be appreciated.

I made your starting positions a little easier to mock, the true calculations are in the .tween function. Note that I execute the function only a few times, otherwise you get a continuous flow of circles.
You can often find solutions like this by looking at similar problems. In this case, I based it on this answer, which led me to tween.
var svg = d3.select('svg');
var color = (v) => v;
var nTrails = 20;
function createTraceBall(x, y) {
svg.append('circle')
.classed('shadow', true)
.attr('cx', x)
.attr('cy', y)
.attr('r', 10)
.style('fill', 'grey')
.style('opacity', 0.5)
.transition()
.duration(500)
.ease(d3.easeLinear)
.style('fill', 'lightgrey')
.style('opacity', 0.1)
.attr('r', 3)
.remove();
}
function drawChart(newData) {
var circles = svg.selectAll(".dot")
.data(newData);
circles.enter()
.append("circle")
.attr("cx", (d) => d.open.x)
.attr("cy", (d) => d.open.y)
.merge(circles)
.transition() // and apply changes to all of them
.duration(1000)
.ease(d3.easeLinear)
.tween("shadow", function(d) {
var xRange = d.close.x - d.open.x;
var yRange = d.close.y - d.open.y;
var nextT = 0;
return function(t) {
// t is in [0, 1), and we only want to execute it nTrails times
if(t > nextT) {
nextT += 1 / nTrails;
createTraceBall(
d.open.x + xRange * t,
d.open.y + yRange * t
);
}
};
})
.attr("class", "dot")
.attr("r", 10.5)
.attr("cx", (d) => d.close.x)
.attr("cy", (d) => d.close.y)
.style("fill", function(d) { return color(d.class); });
circles.exit()
.remove();
}
drawChart([
{open: {x: 20, y: 20}, close: {x: 150, y: 150}, class: 'red'},
{open: {x: 150, y: 20}, close: {x: 20, y: 150}, class: 'blue'},
{open: {x: 20, y: 20}, close: {x: 150, y: 20}, class: 'green'}
]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Related

D3.js bubble diagram with lines - making it responsive

I want to create a bubble diagram with a vertical and horizontal line. There is also force simulation applied in order not to allow overlaps of bubbles and text.
The diagram should be responsive within a bootstrap container. But when I resize the window the bubbles move out of the visible field and the alignment with the lines is not correct anymore.
I tried multiple combinations of force stengths, but nothing helped. Here is the jsfiddle: jsfiddle - version 1
I tried removing the force simulation part and then it works, but obviously bubbles will now overlap: jsfiddle without force simulation
Can you help me to setup force simulation?
Code for jsfiddle-1:
<!DOCTYPE html>
<html>
<head>
<title>d3 test bubble</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<!-- d3.js TEST START-->
<div class="container" id="chart-container" style="margin-top: 8vh; ">
<div id="chart" style="height:80vh; width:80vw"></div>
</div>
<script>
var data = [
{x: 1, y: 1, size: 33, name: "Bubble 1A", link: "https://example.com/bubble1"},
{x: 1, y: 1, size: 33, name: "Bubble 1B", link: "https://example.com/bubble1"},
{x: 100, y: 1, size: 55, name: "Bubble 2", link: "https://example.com/bubble2"},
{x: 250, y: 1, size: 20, name: "Bubble 3", link: "https://example.com/bubble3"},
{x: 1, y: 50, size: 30, name: "Bubble 4A", link: "https://example.com/bubble1"},
{x: 1, y: 50, size: 30, name: "Bubble 4B", link: "https://example.com/bubble1"},
{x: 100, y: 100, size: 40, name: "Bubble 5", link: "https://example.com/bubble2"},
{x: 340, y: 200, size: 50, name: "Bubble 6", link: "https://example.com/bubble3"},
{x: 200, y: 800, size: 10, name: "Bubble 7", link: "https://example.com/bubble3"},
];
let xScale_domain_min = -50.0/340.0 *(d3.max(data, function(d) { return d.x; }));//0;//-50;
let xScale_domain_max_add = 50.0/340.0 *(d3.max(data, function(d) { return d.x; })); //0;//+50;
//let xScale_domain_max = 340;
let xScale_domain_max = (d3.max(data, function(d) { return d.x; }));
console.log("xScale_domain_max");
console.log(xScale_domain_max);
let yScale_domain_min = -55.0/800.0 * (d3.max(data, function(d) { return d.y; }));//0;//-55;
let yScale_domain_max_add = 50.0/800.0 * (d3.max(data, function(d) { return d.y; })); //0;//+50;
//let yScale_domain_max = 800;
let yScale_domain_max = (d3.max(data, function(d) { return d.y; }));
let size_domain_max = 55;
// simulation forces
let force_y = 2;
let force_x = 2;
let force_collide = 0.1;
let alpha_target = 0.0001;
var svg = d3.select("#chart").append("svg")
.attr("width", "100%")
.attr("height", "100%");
var xScale = d3.scaleLinear()
.domain([xScale_domain_min, xScale_domain_max + xScale_domain_max_add])
.range([0, d3.select("#chart").node().getBoundingClientRect().width]);
var yScale = d3.scaleLinear()
.domain([yScale_domain_min, yScale_domain_max + yScale_domain_max_add])
.range([d3.select("#chart").node().getBoundingClientRect().height, 0]);
// bubble size scaler:
let sizeScale_min_factor = 0.005;
let sizeScale_max_factor = 0.05;
var sizeScale = d3.scaleLinear()
.domain([0, size_domain_max])
.range([d3.select("#chart").node().getBoundingClientRect().height * sizeScale_min_factor, d3.select("#chart").node().getBoundingClientRect().height * sizeScale_max_factor]);
// append bubbles:
var bubbles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", function(d) { return sizeScale(d.size); })
.style("fill", "blue")
.on("click", function(d) { window.location.href = d.link; });
// force simulation
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) { return xScale(d.x); }).strength(force_x))
.force("y", d3.forceY(function(d) { return yScale(d.y); }).strength(force_y))
.force("collide", d3.forceCollide(function(d) { return sizeScale(d.size) ; }).strength(force_collide))
.on("tick", ticked);
//Start the simulation
simulation.alphaTarget(alpha_target).restart();
function ticked() {
bubbles
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svg.selectAll("text")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
bubbles.append("title").text(function(d) { return d.name; });
// append text:
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) { return d.name; })
.attr("x", function(d) { return xScale(d.x); })
.attr("y", function(d) { return yScale(d.y) + 1.4*sizeScale(d.size); })
.attr("text-anchor", "middle")
.attr("fill", "white")
.style("font-size", "10px");
// add horizontally line:
// Calculate the average y-value of the data points
var averageY = (yScale_domain_min + yScale_domain_max + yScale_domain_max_add) / 2.0;
// Append the line to the svg
svg.append("line")
.attr("id", "horizontal-line")
.attr("x1", 0)
.attr("y1", yScale(averageY)) // Use the yScale to position the line based on the average y-value
.attr("x2", d3.select("#chart").node().getBoundingClientRect().width)
.attr("y2", yScale(averageY))
.attr("stroke", "red") // set the color of the line
.attr("stroke-width", 0.3); // set the width of the line
// add vertically line:
// Calculate the average y-value of the data points
var averageX = (xScale_domain_min + xScale_domain_max + xScale_domain_max_add) / 2.0;
// Append the line to the svg
svg.append("line")
.attr("id", "vertical-line")
.attr("x1", xScale(averageX))
.attr("y1", 0) // Use the yScale to position the line based on the average y-value
.attr("x2", xScale(averageX))
.attr("y2", d3.select("#chart").node().getBoundingClientRect().height)
.attr("stroke", "red") // set the color of the line
.attr("stroke-width", 0.3); // set the width of the line
//window.addEventListener("resize", updateChart);
window.addEventListener("resize", function() {
updateChart();
});
function updateChart() {
let xScale_domain_min = -50.0/340.0 *(d3.max(data, function(d) { return d.x; }));//0;//-50;
let xScale_domain_max_add = 50.0/340.0 *(d3.max(data, function(d) { return d.x; })); //0;//+50;
let xScale_domain_max = (d3.max(data, function(d) { return d.x; }));
console.log("xScale_domain_max");
console.log(xScale_domain_max);
let yScale_domain_min = -55.0/800.0 * (d3.max(data, function(d) { return d.y; }));//0;//-55;
let yScale_domain_max_add = 50.0/800.0 * (d3.max(data, function(d) { return d.y; })); //0;//+50;
let yScale_domain_max = (d3.max(data, function(d) { return d.y; }));
// Update the scales and any other elements that need to be changed when the window is resized
xScale.range([0, d3.select("#chart").node().getBoundingClientRect().width]);
xScale.domain([xScale_domain_min, xScale_domain_max + xScale_domain_max_add]);
yScale.range([d3.select("#chart").node().getBoundingClientRect().height, 0]);
yScale.domain([yScale_domain_min, yScale_domain_max + yScale_domain_max_add]);
sizeScale.range([d3.select("#chart").node().getBoundingClientRect().height * sizeScale_min_factor, d3.select("#chart").node().getBoundingClientRect().height * sizeScale_max_factor]);
// Update the position and size of the bubbles
simulation.force("x", d3.forceX(function(d) { return xScale(d.x); }).strength(force_x))
.force("y", d3.forceY(function(d) { return yScale(d.y); }).strength(force_y))
.force("collide", d3.forceCollide(function(d) { return sizeScale(d.size) ; }).strength(force_collide));
// update the width and height of the svg element
svg.attr("width", "100%")
.attr("height", "100%");
bubbles
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", function(d) { return sizeScale(d.size); });
svg.selectAll("text")
.attr("x", function(d) { return xScale(d.x); })
.attr("y", function(d) { return yScale(d.y); });
// update line horizontally:
// Recalculate the averageX and averageY
var averageX = (xScale_domain_min + xScale_domain_max + xScale_domain_max_add) / 2.0;
var averageY = (yScale_domain_min + yScale_domain_max + yScale_domain_max_add) / 2.0;
// Update the x1, x2, y1, y2 attributes of the line
svg.select("#horizontal-line")
.attr("y1", yScale(averageY))
.attr("y2", yScale(averageY))
.attr("x2", svg.node().getBoundingClientRect().width);
svg.select("#vertical-line")
.attr("x1", xScale(averageX))
.attr("x2", xScale(averageX))
.attr("y2", svg.node().getBoundingClientRect().height);
simulation.alphaTarget(alpha_target).restart();
}
</script>
</body>
</html>
Code for jsfiddle-2:
<!DOCTYPE html>
<html>
<head>
<title>d3 test bubble</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<!-- d3.js TEST START-->
<div class="container" id="chart-container" style="margin-top: 8vh; ">
<div id="chart" style="height:80vh; width:80vw"></div>
</div>
<script>
var data = [
{x: 1, y: 1, size: 33, name: "Bubble 1A", link: "https://example.com/bubble1"},
{x: 1, y: 1, size: 33, name: "Bubble 1B", link: "https://example.com/bubble1"},
{x: 100, y: 1, size: 55, name: "Bubble 2", link: "https://example.com/bubble2"},
{x: 250, y: 1, size: 20, name: "Bubble 3", link: "https://example.com/bubble3"},
{x: 1, y: 50, size: 30, name: "Bubble 4A", link: "https://example.com/bubble1"},
{x: 1, y: 50, size: 30, name: "Bubble 4B", link: "https://example.com/bubble1"},
{x: 100, y: 100, size: 40, name: "Bubble 5", link: "https://example.com/bubble2"},
{x: 340, y: 200, size: 50, name: "Bubble 6", link: "https://example.com/bubble3"},
{x: 200, y: 800, size: 10, name: "Bubble 7", link: "https://example.com/bubble3"},
];
let xScale_domain_min = -50.0/340.0 *(d3.max(data, function(d) { return d.x; }));//0;//-50;
let xScale_domain_max_add = 50.0/340.0 *(d3.max(data, function(d) { return d.x; })); //0;//+50;
//let xScale_domain_max = 340;
let xScale_domain_max = (d3.max(data, function(d) { return d.x; }));
console.log("xScale_domain_max");
console.log(xScale_domain_max);
let yScale_domain_min = -55.0/800.0 * (d3.max(data, function(d) { return d.y; }));//0;//-55;
let yScale_domain_max_add = 50.0/800.0 * (d3.max(data, function(d) { return d.y; })); //0;//+50;
//let yScale_domain_max = 800;
let yScale_domain_max = (d3.max(data, function(d) { return d.y; }));
let size_domain_max = 55;
// simulation forces
let force_y = 2;
let force_x = 2;
let force_collide = 0.1;
let alpha_target = 0.0001;
var svg = d3.select("#chart").append("svg")
.attr("width", "100%")
.attr("height", "100%");
var xScale = d3.scaleLinear()
.domain([xScale_domain_min, xScale_domain_max + xScale_domain_max_add])
.range([0, d3.select("#chart").node().getBoundingClientRect().width]);
var yScale = d3.scaleLinear()
.domain([yScale_domain_min, yScale_domain_max + yScale_domain_max_add])
.range([d3.select("#chart").node().getBoundingClientRect().height, 0]);
// bubble size scaler:
let sizeScale_min_factor = 0.005;
let sizeScale_max_factor = 0.05;
var sizeScale = d3.scaleLinear()
.domain([0, size_domain_max])
.range([d3.select("#chart").node().getBoundingClientRect().height * sizeScale_min_factor, d3.select("#chart").node().getBoundingClientRect().height * sizeScale_max_factor]);
// append bubbles:
var bubbles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", function(d) { return sizeScale(d.size); })
.style("fill", "blue")
.on("click", function(d) { window.location.href = d.link; });
bubbles.append("title").text(function(d) { return d.name; });
// append text:
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) { return d.name; })
.attr("x", function(d) { return xScale(d.x); })
.attr("y", function(d) { return yScale(d.y) + 1.4*sizeScale(d.size); })
.attr("text-anchor", "middle")
.attr("fill", "white")
.style("font-size", "10px");
// add horizontally line:
// Calculate the average y-value of the data points
var averageY = (yScale_domain_min + yScale_domain_max + yScale_domain_max_add) / 2.0;
// Append the line to the svg
svg.append("line")
.attr("id", "horizontal-line")
.attr("x1", 0)
.attr("y1", yScale(averageY)) // Use the yScale to position the line based on the average y-value
.attr("x2", d3.select("#chart").node().getBoundingClientRect().width)
.attr("y2", yScale(averageY))
.attr("stroke", "red") // set the color of the line
.attr("stroke-width", 0.3); // set the width of the line
// add vertically line:
// Calculate the average y-value of the data points
var averageX = (xScale_domain_min + xScale_domain_max + xScale_domain_max_add) / 2.0;
// Append the line to the svg
svg.append("line")
.attr("id", "vertical-line")
.attr("x1", xScale(averageX))
.attr("y1", 0) // Use the yScale to position the line based on the average y-value
.attr("x2", xScale(averageX))
.attr("y2", d3.select("#chart").node().getBoundingClientRect().height)
.attr("stroke", "red") // set the color of the line
.attr("stroke-width", 0.3); // set the width of the line
//window.addEventListener("resize", updateChart);
window.addEventListener("resize", function() {
updateChart();
});
function updateChart() {
let xScale_domain_min = -50.0/340.0 *(d3.max(data, function(d) { return d.x; }));//0;//-50;
let xScale_domain_max_add = 50.0/340.0 *(d3.max(data, function(d) { return d.x; })); //0;//+50;
let xScale_domain_max = (d3.max(data, function(d) { return d.x; }));
console.log("xScale_domain_max");
console.log(xScale_domain_max);
let yScale_domain_min = -55.0/800.0 * (d3.max(data, function(d) { return d.y; }));//0;//-55;
let yScale_domain_max_add = 50.0/800.0 * (d3.max(data, function(d) { return d.y; })); //0;//+50;
let yScale_domain_max = (d3.max(data, function(d) { return d.y; }));
// Update the scales and any other elements that need to be changed when the window is resized
xScale.range([0, d3.select("#chart").node().getBoundingClientRect().width]);
xScale.domain([xScale_domain_min, xScale_domain_max + xScale_domain_max_add]);
yScale.range([d3.select("#chart").node().getBoundingClientRect().height, 0]);
yScale.domain([yScale_domain_min, yScale_domain_max + yScale_domain_max_add]);
sizeScale.range([d3.select("#chart").node().getBoundingClientRect().height * sizeScale_min_factor, d3.select("#chart").node().getBoundingClientRect().height * sizeScale_max_factor]);
// update the width and height of the svg element
svg.attr("width", "100%")
.attr("height", "100%");
bubbles
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", function(d) { return sizeScale(d.size); });
svg.selectAll("text")
.attr("x", function(d) { return xScale(d.x); })
.attr("y", function(d) { return yScale(d.y); });
// update line horizontally:
// Recalculate the averageX and averageY
var averageX = (xScale_domain_min + xScale_domain_max + xScale_domain_max_add) / 2.0;
var averageY = (yScale_domain_min + yScale_domain_max + yScale_domain_max_add) / 2.0;
// Update the x1, x2, y1, y2 attributes of the line
svg.select("#horizontal-line")
.attr("y1", yScale(averageY))
.attr("y2", yScale(averageY))
.attr("x2", svg.node().getBoundingClientRect().width);
svg.select("#vertical-line")
.attr("x1", xScale(averageX))
.attr("x2", xScale(averageX))
.attr("y2", svg.node().getBoundingClientRect().height);
}
</script>
</body>
</html>

D3 remove circle on dbclick

I am using Bostock's Circle Dragging I and Will's D3 Mouse Event so i can click on svg and create a circle and also they are all draggable. That is working, although there is a side issue if I double click when creating a circle sometimes dragging circles causes them to jump around.
But the main issue is that I would like to be able to double click on a circle and have it disappear but also remove it from the data.
When circles are drawn I added a dbclick event which calls a function
function removeElement(d) {
// need to remove this object from data
d3.select(this)
.exit()
.remove();
}
This function is also called when a new circle is created.
This function is not removing circles, what is the correct way to do this?
And is there a conflict between a single click doing one thing and a double click doing something else?
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 32;
var data = [{
x: 100,
y: 200
},
{
x: 200,
y: 300
},
{
x: 300,
y: 200
},
{
x: 400,
y: 300
}
];
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.x_pos
})]).range([0, width]);
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("dblclick", removeElement());
svg.on("click", function() {
var coords = d3.mouse(this);
var newData = {
x: d3.event.x,
y: d3.event.y
};
data.push(newData);
svg.selectAll("circle") // For new circle, go through the update process
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", "red")
.attr('id', function(d, i) {
return 'circle_' + i;
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("dblclick", removeElement());
})
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this)
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
// need to remove this object from data
d3.select(this)
.exit()
.remove();
}
.active {
stroke: #000;
stroke-width: 2px;
}
<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
The biggest issue you'll face with your code is telling a click from a double click. However, since you asked specifically how to remove the circles, this answer will deal with that problem only.
Your code for removing the circles has two problems.
First, this...
.on("dblclick", removeElement())
... will call removeElement immediately and return its value (which, by the way, is undefined). This is not what you want.
Instead, do this:
.on("dblclick", removeElement)
Which is the same of:
.on("dbclick", function(d){
removeElement(d);
}
That way, removeElement will be called only when the user clicks the circle, not immediately.
The second problem is this:
d3.select(this).exit().remove();
Since there are still data associated with that circle, your "exit" selection is empty.
Instead of that, it should be:
d3.select(this).remove();
Here is your code with those changes:
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 32;
var data = [{
x: 100,
y: 200
},
{
x: 200,
y: 300
},
{
x: 300,
y: 200
},
{
x: 400,
y: 300
}
];
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.x_pos
})]).range([0, width]);
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("dblclick", removeElement);
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this)
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this)
.classed("active", false);
}
function removeElement(d) {
// need to remove this object from data
d3.select(this)
.remove();
}
.active {
stroke: #000;
stroke-width: 2px;
}
<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
PS: I removed the click on the SVG for creating the circles. Since that issue (distinguishing click from double click) is very complex, it may be worth a new, separated question.

SVG circles piling up in d3

I am writing a d3 program that reads in stats from NFL teams from a csv file and the user selects the team to view from a dropdown menu. The program then creates circles in the formation of the offense and defense for the stats for that team and displays it. I have the program able to display the some of it so far, but when I select another team, the old circles stay on the screen and the new ones are just appended on top. My question is how do I fix this? I tried adding remove at the end of the below function but it just removes that circle completely
function menuChanged()
{
var selectedValue = d3.event.target.value;
var picked;
for(var i = 0; i < 32; i++)
if(nest[i].key == selectedValue)
picked = i;
rT = svg.selectAll("rTcircle")
.data(nest)
.enter().append("circle")
.attr("class", "dot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].rushing) * 0.7; })
.attr("cx", 40)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
rG = svg.selectAll("rGcircle")
.data(nest)
.enter().append("circle")
.attr("class", "rGdot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].rushing) * 0.7; })
.attr("cx", 190)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
cO = svg.selectAll("oCcircle")
.data(nest)
.enter().append("circle")
.attr("class", "oCdot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].rushing) * 0.7; })
.attr("cx", 340)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
lG = svg.selectAll("lGcircle")
.data(nest)
.enter().append("circle")
.attr("class", "lGdot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].rushing) * 0.7; })
.attr("cx", 490)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
lT = svg.selectAll("lTcircle")
.data(nest)
.enter().append("circle")
.attr("class", "lTdot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].rushing) * 0.7; })
.attr("cx", 640)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
var oLineT = svg.selectAll(".text")
.data(nest)
.enter().append("text")
.attr("class","text")
.style("text-anchor", "middle")
.attr("x", 40)
.attr("y", 300)
.style("fill", function() { return nest[picked].values[0].color1; })
.style("stroke", function() { return nest[picked].values[0].color2; })
.style("font-family", "verdana")
.style("stroke-width", 0.7)
.text(function () {return nest[picked].values[0].team; });
rT.data(nest).transition()
.duration(500)
.attr("class", "dot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].passing) * 0.7; })
.attr("cx", 40)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
rG.data(nest).transition()
.duration(500)
.attr("class", "rGdot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].passing) * 0.7; })
.attr("cx", 190)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
cO.data(nest).transition()
.duration(500)
.attr("class", "oCdot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].passing) * 0.7; })
.attr("cx", 340)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
lG.data(nest).transition()
.duration(500)
.attr("class", "lGdot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].passing) * 0.7; })
.attr("cx", 490)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
lT.data(nest).transition()
.duration(500)
.attr("class", "lTdot")
.attr("r", function() { return Math.sqrt(nest[picked].values[0].passing) * 0.7; })
.attr("cx", 640)
.attr("cy", 300)
.style("fill", function () { return nest[picked].values[0].color1; })
.style("stroke", function () { return nest[picked].values[0].color2; });
oLineT.data(nest).transition()
.duration(500)
.attr("class","text")
.style("text-anchor", "middle")
.attr("x", 40)
.attr("y", 300)
.style("fill", function() { return nest[picked].values[0].color1; })
.style("stroke", function() { return nest[picked].values[0].color2; })
.style("font-family", "verdana")
.style("stroke-width", 0.7)
.text(function () {return nest[picked].values[0].team; });
}
This is a classic case of not properly defining the "enter" and the 'update" selections (and the "exit", if any). Unfortunately, just quickly skimming your menuChanged function, it seems to me that you will have to do a lot of changes, in order to make it the "D3 way". Also, you could store the variable parts in... well, variables!
I made a simple demo to show you how the "enter" and the "update" selections work (I'm using v4 here). First, bind the data:
var circles = svg.selectAll(".teamCircles")
.data(data[0][team].positions);
Both "enter" and "update" selections rely on this bound data.
Then, set the "enter" selection (here, I put it inside the menuChanged function, but if the number of players never change, it can be outside the function):
circlesEnter = circles.enter()
.append("circle")
.attr("class", "teamCircles")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 8)
.attr("fill", data[0][team].color);
And, finally, the "update" selection. The update selection only works on previously existing elements:
circlesUpdate = circles.transition()
.duration(1000)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 8)
.attr("fill", data[0][team].color);
Here is the demo:
var w = 500,
h = 300;
var svg = d3.select("#svg").append("svg")
.attr("width", w)
.attr("height", h);
var data = [{
"San Francisco 49": {
color: "red",
positions: [{
x: 110,
y: 50
}, {
x: 35,
y: 56
}, {
x: 230,
y: 200
}, {
x: 431,
y: 50
}, {
x: 310,
y: 250
}]
},
"Green Bay Packers": {
color: "green",
positions: [{
x: 360,
y: 120
}, {
x: 51,
y: 156
}, {
x: 30,
y: 60
}, {
x: 130,
y: 210
}, {
x: 410,
y: 250
}]
},
"Baltimore Ravens": {
color: "purple",
positions: [{
x: 200,
y: 200
}, {
x: 34,
y: 236
}, {
x: 390,
y: 98
}, {
x: 330,
y: 66
}, {
x: 10,
y: 210
}]
}
}];
d3.select("#selection").on("change", menuChanged);
function menuChanged() {
var team = this.value;
var circles = svg.selectAll(".teamCircles")
.data(data[0][team].positions);
circlesEnter = circles.enter()
.append("circle")
.attr("class", "teamCircles")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 8)
.attr("fill", data[0][team].color);
circlesUpdate = circles.transition()
.duration(1000)
.attr("class", "teamCircles")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 8)
.attr("fill", data[0][team].color);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<select name="select" id="selection">
<option value="">Select</option>
<option value="San Francisco 49">San Francisco 49</option>
<option value="Green Bay Packers">Green Bay Packers</option>
<option value="Baltimore Ravens">Baltimore Ravens</option>
</select>
<div id="svg"></div>
PS: by the way, the reason your circles are piling up is because in your enter selections you're selecting something that doesn't exist. For instance:
rT = svg.selectAll("rTcircle")
rTcircle doesn't exist, and thus your enter selection is never empty.

How to draw lines between circles?

I'm attemtping to draw three circles and draw connected lines between each of these circles.
The end goal is to configure what circles are connected using json configuration but prior to this im just trying to connect the circles using the callbacks and hard code values.
Here is what I have so far :
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes":[
{"name":"1","group":1, "x" : 100, "y" : 100 , r : 20},
{"name":"2","group":1, "x" : 200, "y" : 150 ,r : 30},
{"name":"3","group":2 , "x" : 300, "y" : 250 , r : 50}
],
"links":[
{"source":1,"target":0,"value":1}
]
}
var width = 2000;
var height = 500;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.links)
.enter().append("line")
.attr("x1", function(d) { return 50 })
.attr("y1", function(d) { return 50 })
.attr("x2", function(d) { return 100 })
.attr("y2", function(d) { return 100 })
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function(d, i){ return d.r })
.attr("cx", function(d, i){ return d.x })
.attr("cy", function(d, i){ return d.y })
</script>
</body>
</html>
But no lines are being drawn. Each circle should contain a single line connecting it to the other circle.
I'm just hard coding the x1,y1,x2,y2 co-ordinates but I will be using the
co-ordinates of the other circles in order to determine the postions of the lines. Why are the lines not being drawn ? Is there standard d3 methods I should be utilising in order to connect these circles ?
fiddle : http://jsfiddle.net/zzz8svuq/10/
Update :
Here is the updated code which draws connected lines between circles as configured in dataset graph.nodes :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes": [
{name: "1", "group": 1, x: 100, y: 50, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 90, r: 15, connected : "1"},
{name: "3", "group": 2, x: 300, y: 230, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 500;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.nodes)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function (d, i) {
return d.x
})
.attr("y1", function (d) {
return d.y
})
.attr("x2", function (d) {
return findAttribute(d.connected).x
})
.attr("y2", function (d) {
return findAttribute(d.connected).y
})
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function (d, i) {
return d.r
})
.attr("cx", function (d, i) {
return d.x
})
.attr("cy", function (d, i) {
return d.y
});
});
function findAttribute(name) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
if (graph.nodes[i].name === name)
return graph.nodes[i]; // Return as soon as the object is found
}
return null; // The object was not found
}
</script>
</body>
</html>
You need to make sure that the lines have a stroke color or else they will be drawn white and you won't be able to see them.
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.links)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function(d) { return 50 })
.attr("y1", function(d) { return 50 })
.attr("x2", function(d) { return 100 })
.attr("y2", function(d) { return 100 })

I would like to restrict circles within a shape & partitions within that shape

var svgcanvas = d3.select("body").append("svg:svg")
.attr("width", 725)
.attr("height", 500);
The Dataset
var jsoncirclehigh = [
{cx:100, cy: 100, r: 2.5,
label:"technology"},
{cx:200, cy: 200, r: 2.5,
label:"rocks"},
{cx:50, cy:50, r:2.5,
label:"blah"}
];
The actual shape that I have created
svgcanvas.append("svg:path")
.attr("d","M -200,0 A200,200 0 0,0 500,0 L -200,0")
.attr("transform", "translate(220,400) scale(1, -1)")
.style("stroke-width", 2)
.style("stroke", "steelblue")
.style("fill", "yellow");
I wish the circles to be restricted within the shape above
svgcanvas.selectAll("circle")
.data(jsoncirclehigh)
.enter().append("circle")
.attr("r", function (d) { return d.r; })
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function() {d3.select(this).style("fill", "blue");})
.style("stroke", "steelblue")
.style("fill", "blue");
svgcanvas.selectAll("text")
.data(jsoncirclehigh)
.enter().append("svg:text")
.text(function(d) { return d.label; })
.attr("x", function (d) { return d.cx + 10; })
.attr("y", function (d) { return d.cy + 10; });
I have tried using d3.scale, but it hasn't worked out for me
This may be more simple than you're looking for, but have you looked into using a clipPath?

Categories