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>
Related
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>
I want to insert some text inside my Circle which is plot in bubble chart using D3.js.
I am able to draw circle in svg as per the data provided to it, but facing a problem while append text to it.
below is my code:
<script type="text/javascript">
var sampleData = [{"x": 8,"y": 1}, {"x": 2,"y": 1}, {"x": 4,"y": 1},{"x": 5,"y": 1}];
// {"x": 6,"y": 40}, {"x": 8,"y": 100}, {"x": 10,"y": 60}];
$(function() {
InitChart();
});
function InitChart() {
// Chart creation code goes here
var vis = d3.select("#svgVisualize");
var xRange = d3.scale.linear().range([40, 400]).domain([0,10]);
var yRange = d3.scale.linear().range([200, 40]).domain([0,2]);
/* var xRange = d3.scale.linear().range([40, 400]).domain([d3.min(sampleData, function(d) {
return (d.x);
}), d3.max(sampleData, function(d) {
return d.x;
})]);
var yRange = d3.scale.linear().range([400, 40]).domain([d3.min(sampleData, function(d) {
return d.y;
}), d3.max(sampleData, function(d) {
return d.y;
})]); */
var xAxis = d3.svg.axis().scale(xRange).ticks(2);
var yAxis = d3.svg.axis().scale(yRange).ticks(2).orient("left");
vis.append("svg:g").call(xAxis).attr("transform", "translate(0,200)");
vis.append("svg:g").call(yAxis).attr("transform", "translate(40,0)");
var circles = vis.selectAll("circle").data(sampleData);
circles
.enter()
.insert("circle")
.attr("cx", function(d) { return xRange (d.x); })
//.attr("cy", function(d) { return yRange (d.y); })
.attr("cy", function(d) { return yRange (d.y); })
.attr("r", function(d) { return Math.log(d.x) * 30; })
.attr("stroke","black")
.style("fill", "yellow");
var text = vis.selectAll("text")
.data(sampleData)
.enter()
.insert("text");
//Add SVG Text Element Attributes
var textLabels = text
.attr("x", function(d) { return d.cx; })
.attr("y", function(d) { return d.cy; })
.text( function (d) { return "( " + d.cx + ", " + d.cy +" )"; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "red");
}
</script>
<body>
<svg id="svgVisualize" width="500" height="250" style="border:1px solid Red;"></svg>
</body>
Can anyone suggest what is the problem with above code?
Thanks
When you do this:
var text = vis.selectAll("text")
.data(sampleData)
.enter()
.insert("text");
You are selecting <text> elements that already exist in that SVG (in your case, the axes' ticks). Because of that, your "enter" selection is empty.
Solution: select something that doesn't exist, like null:
var text = vis.selectAll(null)
.data(sampleData)
.enter()
.insert("text");
Here is the updated code:
var sampleData = [{
"x": 8,
"y": 1
}, {
"x": 2,
"y": 1
}, {
"x": 4,
"y": 1
}, {
"x": 5,
"y": 1
}];
var vis = d3.select("svg");
var xRange = d3.scale.linear().range([40, 400]).domain([0, 10]);
var yRange = d3.scale.linear().range([200, 40]).domain([0, 2]);
var xAxis = d3.svg.axis().scale(xRange).ticks(2);
var yAxis = d3.svg.axis().scale(yRange).ticks(2).orient("left");
vis.append("svg:g").call(xAxis).attr("transform", "translate(0,200)");
vis.append("svg:g").call(yAxis).attr("transform", "translate(40,0)");
var circles = vis.selectAll("circle").data(sampleData);
circles
.enter()
.append("circle")
.attr("cx", function(d) {
return xRange(d.x);
})
//.attr("cy", function(d) { return yRange (d.y); })
.attr("cy", function(d) {
return yRange(d.y);
})
.attr("r", function(d) {
return Math.log(d.x) * 30;
})
.attr("stroke", "black")
.style("fill", "yellow");
var text = vis.selectAll(null)
.data(sampleData)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) {
return xRange(d.x);
})
.attr("text-anchor", "middle")
.attr("y", function(d) {
return yRange(d.y);
})
.text(function(d) {
return "( " + d.x + ", " + d.y + " )";
})
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
line, path{
fill: none;
stroke: black;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<svg width="400" height="220"></svg>
PS: Don't use insert, use append instead.
A better way of doing this is to use svg groups, then you can position both the circles and the text together:
var join = vis.selectAll(".points").data(sampleData);
var groups = join
.enter()
.append("g")
.attr("transform", function(d) { return "translate(" + [d.x, d.y] + ")"; });
.attr("cx", function(d) { return xRange (d.x); })
groups.append("circle")
.attr("r", function(d) { return Math.log(d.x) * 30; })
.attr("stroke","black")
.style("fill", "yellow");
groups.append("text")
.data(sampleData)
.text( function (d) { return "( " + d.cx + ", " + d.cy +" )"; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "red");
I'm trying to create force directed graph like this. It drew just fine when I was using the sample data. But when I use my own data, the nodes seem to be drawn out of the svg size.
Here is what I get:
And here is my code:
var nodes = createFDGNodes(stopsByLine);
var links = createFDGLinks(stopsByLine);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d) { return d.id; })
)
.force("charge", d3.forceManyBody()
.distanceMin(function(d) {return 1; })
)
.force("center", d3.forceCenter(960/2, 500/2));
const circleGroup = d3.select("div.transit-network")
.append("svg")
.attr("width", 960)
.attr("height", 500)
.append("g")
.attr("class","fdg");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var link = circleGroup.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke", "black")
.attr("stroke-width", 1);
var node = circleGroup.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
.attr("class", function(d) {return "line-"+d.lineId+" stop-"+d.id;})
.attr("fill", function(d){
return color(d.lineId);
});
simulation.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
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; });
}
How could I make the graph so it is drawn within the allocated svg size?
The "correct" way to make your simulation fitting inside your allocated area is tweaking all the forces in the simulation, like forceManyBody, forceLink, forceCenter etc...
However, you can force the simulation (no pun intended) to fit in a given area. For instance, in the following demo, the simulation will be constrained in a small area of 100 x 100 pixels using this inside the tick function:
node.attr("transform", (d) => {
return "translate(" + (d.x < 10 ? dx = 10 : d.x > 90 ? d.x = 90 : d.x) +
"," + (d.y < 10 ? d.y = 10 : d.y > 90 ? d.y = 90 : d.y) + ")"
})
Here is the demo:
var width = 100;
var height = 100;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
"id": "foo"
}, {
"id": "bar"
}, {
"id": "baz"
}, {
"id": "foobar"
}];
var edges = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(width / 2, height / 2));
var links = svg.selectAll("foo")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var node = svg.selectAll("foo")
.data(nodes)
.enter()
.append("g");
var nodeCircle = node.append("circle")
.attr("r", 5)
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", "white");
simulation.nodes(nodes);
simulation.force("link")
.links(edges);
simulation.on("tick", function() {
node.attr("transform", (d) => {
return "translate(" + (d.x < 10 ? dx = 10 : d.x > 90 ? d.x = 90 : d.x) + "," + (d.y < 10 ? d.y = 10 : d.y > 90 ? d.y = 90 : d.y) + ")"
})
links.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;
})
});
svg{
background-color: lemonchiffon;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
Mike Bostock's Bounded force layout also works and you can set the radius to match your nodes.
node.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
Just tweak with .strength function; e.g.
.force("charge", d3.forceManyBody().strength(-5) )
I have a stacked bar chart here not cooperating.
For some reason. The part where I have my code update is not working.
At this point the code seems fine but I am just not able to get the transition to occur
Anyone know how to do this?
Thanks!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>US2016</title>
<!-- d3 and plugins -->
<script src="http://d3js.org/d3.v3.js"></script>
<!-- custom css -->
<style type="text/css">
body {
font: 10px sans-serif;
}
</style>
</head>
<body>
<h2>Random</h4>
<div id="stacked-rep"></div>
<script>
var repColors = ['#403153','#9e7742','#0084ab','#e30513'];
var repColorsLight = ['#e5dae7','#deba96','#c5d7e9','#f6b89f'];
var repCandidates = ["Trump", "Cruz", "Rubio", "Kasich"];
var carnival_colors = ["blue", "lightblue"];
//Width and height
var m = {top: 10, right: 10, bottom: 10, left: 10}, // margins
h = 150 - m.left - m.right, // height
w = 960 - m.top - m.bottom; // width
//Create SVG element
var svg = d3.select("#stacked-rep")
.append("svg")
.attr("width", w)
.attr("height", h);
function cumulChart(id, dataset) {
// Set up stack method
var stack = d3.layout.stack();
//Data, stacked
stack(dataset);
// Set up scales
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length))
.rangeRoundBands([0, h], 0.2); // This is actually the Y scale (candidates)
var yScale = d3.scale.linear()
.domain([0,
d3.sum(dataset, function(d) {return d[0].y;})*1.2
])
.range([0, w]); // This is actually the X Scale (States)
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.classed("g_stacked elements", true)
// .attr("class", "g_stacked elements")
.attr("transform", "translate(" + m.left + ",0)");
// Add a rect for each data value
var rects = groups
.selectAll("rect")
.data(function(d) { return d; });
rects.enter()
.append("rect")
.attr("class", "stacked")
.attr("stacked_state", function(d) { return "st"+ d.state; })
.attr("x", function(d) {
return yScale(d.y0);
})
.attr("y", function(d, i) {
return xScale(i);
})
.attr("width", function(d) {
return yScale(d.y);
})
.attr("height", xScale.rangeBand())
.style("fill", function(d, i) {
if (d.state == 19){
return carnival_colors[1];
}
else {
return carnival_colors[0];
}
})
.style("stroke", function(d, i) {
if (d.state == 19){
return d3.rgb(carnival_colors[1]).darker() ;
}
else {
return d3.rgb(carnival_colors[0]).darker() ;
}
})
/*.on("mouseover", function(d) {
console.log(d.state);
})*/;
// transition
rects.transition() // this is to create animations
.duration(500) // 500 millisecond
.ease("bounce")
.delay(500)
// .attr("class", "stacked")
// .attr("stacked_state", function(d) { return "st"+ d.state; })
.attr("x", function(d) {
return yScale(d.y0);
})
.attr("y", function(d, i) {
return xScale(i);
})
.attr("width", function(d) {
return yScale(d.y);
})
.attr("height", xScale.rangeBand());
};
var data = [
[{
"state": 19,
"x": "Trump1",
"y": 2000
}],
[{
"state": 33,
"x": "Trump2",
"y": 3000
}]
];
cumulChart("#stacked-rep", data);
// create a function to randomize things
function rand_it(x){
return Math.floor((Math.random() * x) + 1);
};
setInterval(function(){
var object = [
[{
"state": 19,
"x": "Trump1",
"y": rand_it(20)
}],
[{
"state": 33,
"x": "Trump2",
"y": rand_it(20)
}]
];
cumulChart("#stacked-rep", object);
console.log(object[0][0].y,"---",object[1][0].y);
}, 3000);
</script>
</body>
</html>
Just figured it out.
The data part was not updating in the function.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>US2016</title>
<!-- d3 and plugins -->
<script src="http://d3js.org/d3.v3.js"></script>
<!-- custom css -->
<style type="text/css">
body {
font: 10px sans-serif;
}
</style>
</head>
<body>
<h2>Random</h4>
<div id="stacked-rep"></div>
<script>
var repColors = ['#403153','#9e7742','#0084ab','#e30513'];
var repColorsLight = ['#e5dae7','#deba96','#c5d7e9','#f6b89f'];
var repCandidates = ["Trump", "Cruz", "Rubio", "Kasich"];
var carnival_colors = ["blue", "lightblue"];
//Width and height
var m = {top: 10, right: 10, bottom: 10, left: 10}, // margins
h = 150 - m.left - m.right, // height
w = 960 - m.top - m.bottom; // width
//Create SVG element
var svg = d3.select("#stacked-rep")
.append("svg")
.attr("width", w)
.attr("height", h);
// Add a group for each row of data
var data = [
[{
"state": 19,
"x": "Trump1",
"y": 2000
}],
[{
"state": 33,
"x": "Trump2",
"y": 3000
}]
];
var groups = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.classed("g_stacked_elements", true)
// .attr("class", "g_stacked elements")
.attr("transform", "translate(" + m.left + ",0)");
function cumulChart(id, dataset) {
// Set up stack method
var stack = d3.layout.stack();
//Data, stacked
stack(dataset);
// console.log("the data sum is:", d3.sum(dataset, function(d) {return d[0].y;}));
// Set up scales
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length))
.rangeRoundBands([0, h], 0.2); // This is actually the Y scale (candidates)
var yScale = d3.scale.linear()
.domain([0,
d3.sum(dataset, function(d) {return d[0].y;})*1.2])
.range([0, w]); // This is actually the X Scale (States)
// console.log(xScale);
// Add a rect for each data value
groups_w_data = svg.selectAll("g")
.data(data);
var rects = groups_w_data
.selectAll("rect")
.data(function(d) { return d; });
rects.enter()
.append("rect")
.attr("class", "stacked")
.attr("stacked_state", function(d) { return "st"+ d.state; })
.attr("x", function(d) {
return yScale(d.y0);
})
.attr("y", function(d, i) {
return xScale(i);
})
.attr("width", function(d) {
return yScale(d.y);
})
.attr("height", xScale.rangeBand())
.style("fill", function(d, i) {
if (d.state == 19){
return carnival_colors[1];
}
else {
return carnival_colors[0];
}
})
.style("stroke", function(d, i) {
if (d.state == 19){
return d3.rgb(carnival_colors[1]).darker() ;
}
else {
return d3.rgb(carnival_colors[0]).darker() ;
}
})
/*.on("mouseover", function(d) {
console.log(d.state);
})*/;
// transition
rects.transition() // this is to create animations
.duration(500) // 500 millisecond
.ease("bounce")
.delay(500)
// .attr("class", "stacked")
// .attr("stacked_state", function(d) { return "st"+ d.state; })
.attr("x", function(d) {
console.log("This is the X value: ", yScale(d.y0));
return yScale(d.y0);
})
.attr("width", function(d) {
console.log("This is the width value: ", yScale(d.y));
return yScale(d.y);
})
};
cumulChart("#stacked-rep", data);
// create a function to randomize things
function rand_it(x){
return Math.floor((Math.random() * x) + 1);
};
setInterval(function(){
var object = [
[{
"state": 19,
"x": "Trump1",
"y": rand_it(20)
}],
[{
"state": 33,
"x": "Trump2",
"y": rand_it(20)
}]
];
data = object;
cumulChart("#stacked-rep", data);
console.log(object[0][0].y,"---",object[1][0].y);
}, 3000);
</script>
</body>
</html>
New here. I'm working with D3 and basically I have 2 datasets in the form of arrays. What I want to achieve is upon button click, the new dataset overwrites the old one (I have achieved this much) and then the new dataset is bound and redraws the stacked bar charts. This doesn't happen for me. When the button is pressed it just deletes a couple of the bars.
Would appreciate any tips. I think it's awkward because I'm working with stacked bar charts and not normal ones.
Thanks! :)
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
// .ticks(5);
//Width and height
var w = 600;
var h = 300;
var barPadding = 50;
//Original data
var dataset = [
[
{ y: 20 }, //male
{ y: 4 },
{ y: 16},
{ y: 53},
{ y: 15 }
],
[
{ y: 12 }, //female
{ y: 4 },
{ y: 3 },
{ y: 36 },
{ y: 2 }
],
];
console.log(dataset);
// var myDataSet = dataset;
// var totalDeaths = d.y0 + d.y1;
//Set up stack method
var stack = d3.layout.stack();
//Data, stacked
stack(dataset);
//Set up scales
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length))
.rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0,
d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})
])
.range([0, h]);
//Easy colors accessible via a 10-step ordinal scale
// var colors = d3.scale.category20c();
var color = d3.scale.ordinal()
.domain(["Male", "Female"])
.range(["#00B2EE", "#FF69B4"]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function(d, i) {
return color(i);
});
// Add a rect for each data value
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.rangeBand() / 2;
var yPosition = parseFloat(d3.select(this).attr("y")) + 14;
//Create the tooltip label
svg.append("text")
.attr("id", "tooltip")
.attr("x", xPosition)
.attr("y", yPosition)
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.html("Female deaths: " + d.y + "\n" + " \nMale deaths: " + d.y0);
})
.on("mouseout", function() {
//Remove the tooltip
d3.select("#tooltip").remove();
})
.attr("width", xScale.rangeBand())
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y) ;
})
.attr("height", function(d) {
return yScale(d.y);
});
d3.select("#target")
.on("click", function() { //event listener on button click
// alert("heeeey");
//New values for dataset
dataset = [
[
{ y: 100 }, //male
{ y: 20 },
{ y: 16},
{ y: 53},
{ y: 15 }
],
[
{ y: 5 }, //female
{ y: 4 },
{ y: 3 },
{ y: 36 },
{ y: 2 }
],
];
console.log(dataset);
//Data, stacked
// stack(dataset);
//Update all rects
var gas = svg.selectAll("rect")
.data(dataset)
// .transition()
// .duration(1000)
// .ease("cubic-in-out")
.attr("width", xScale.rangeBand())
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y) ;
})
.attr("height", function(d) {
return yScale(d.y);
})
.attr("x", function(d, i) {
return xScale(i);
});
});