I am trying to add a tooltip to this simple bubblechart that displays the key/value pair on mouseover of each bubble. I'm also having trouble adding a border to individual bubbles on mouseover as well, another styling component I'd like to add to this visualization.
Here is my code:
var width = 400,
height = 400;
var svg = d3.select("#borough")
.append("svg")
.attr("height", height)
.attr("width", width)
.append("g")
.attr("transform", "translate(0,0)")
var toolTip = d3.select('body').append('div').attr('class',
'tooltipbor').style('opacity', 0)
var simulation = d3.forceSimulation()
.force("x", d3.forceX(width / 2).strength(0.05))
.force("y", d3.forceY(height / 2).strength(0.05))
.force("collide", d3.forceCollide(function(d) {
return radiusScale(d.number)+ 1;
}))
var radiusScale = d3.scaleSqrt().domain(["0.26", "1.07"]).range([5,60])
d3.queue()
.defer(d3.csv, "borough.csv")
.await(ready)
function ready (error, datapoints) {
var circles = svg.selectAll(".rate")
.data(datapoints)
.enter().append("circle")
.attr("class", "rate")
.attr("r", function(d) {
return radiusScale(d.number)
})
.attr("fill", function (d) {
if (d.borough === "StatenIsland") {
return "#EAC435"
} else if (d.borough === "Queens") {
return "#345995"
} else if (d.borough === "Manhattan") {
return "#03CEA4"
} else if (d.borough === "Brooklyn") {
return "#FA7921"
} else if (d.borough == "Bronx") {
return "#E40066"
}
})
.on ('mouseover', function (d) {
div.style("display", "inline")
})
.on('mousemove', function (d) {
toolTip.transition()
.duration(200)
.style('opacity', 0.9)
toolTip.html(`${d.borough} <br/>${d.number}`)
.style('left', (d3.event.pageX + 10) + 'px')
.style('top', (d3.event.pageY + 10) + 'px')
})
.on('mouseout', function (d) {
toolTip.transition()
.duration(500)
.style('opacity', 0)
})
simulation.nodes(datapoints)
.on('tick', ticked)
function ticked() {
circles
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
}
}
borough.csv :
borough,number
Bronx,1.07
Brooklyn,0.59
Manhattan,1.025
Queens,0.40
StatenIsland,0.26
Thank you in advance! Probably needless to say, but I am very new to JavaScript much less d3!
Related
I've a problem with my directed graph in d3js,in a few words:
if number of nodes < 24 the graph is rendered correctly
if number of nodes >= 24 everything was stacked on top left of svg but if I inspect my html code I see all nodes, links and labels...
this is my code:
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation()
.force("x", d3.forceX().strength(8).x( function(d){ return yScale(d.type) }))
.force("y", d3.forceY().strength(10).y(height/2))
.force("center", d3.forceCenter().x(width / 2).y(height / 2)) // Attraction to the center of the svg area
.force("charge", d3.forceManyBody().strength(1)) // Nodes are attracted one each other of value is > 0
.force("collide", d3.forceCollide().strength(0.9).radius(50).iterations(10)) // Force that avoids circle overlapping
.force("link", d3.forceLink().distance(function(d){return d.link_distance}).strength(1))
var yScale = d3.scalePoint()
.domain([1, 2, 3])
.range([150, width-150])
.padding(0.6)
.round(false);
d3.json("{{=URL('professionista', 'get_current_plan_graph', extension=False)}}", function(error, graph) {
if (error) throw error;
var links = graph.links;
function getNeighbors(node) {
return links.reduce(function(neighbors, link) {
if (link.target.id === node.id) {
neighbors.push(link.source.id)
} else if (link.source.id === node.id) {
neighbors.push(link.target.id)
}
return neighbors
}, [node.id])
}
function isNeighborLink(node, link) {
return link.target.id === node.id || link.source.id === node.id
}
function getNodeColor(node, neighbors) {
if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
if (node.type === 1)
return "#3C99FB"
else if (node.type === 2)
return "#9EC2E2"
else if (node.type === 3)
return "#577EA7"
else
return '#dee2e6'
}
return '#dee2e6'
}
function getLinkColor(node, link) {
return isNeighborLink(node, link) ? '#000444' : '#E5E5E5'
}
function getTextColor(node, neighbors) {
return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? 'green' : 'black'
}
function selectNode(selectedNode) {
var neighbors = getNeighbors(selectedNode)
nodeElement.attr('fill', function(node) {
return getNodeColor(node, neighbors)
})
labelElement.attr('fill', function(node) {
return getTextColor(node, neighbors)
})
linkElement.attr('stroke', function(link) {
return getLinkColor(selectedNode, link)
})
}
var linkElement = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", 1)
.attr("stroke", "rgba(50, 50, 50, 0.2)");
var nodeElement = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 30)
.attr("fill", getNodeColor)
.on('click', selectNode)
var labelElement = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.text(function(d) {
return d.codice;
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
linkElement
.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; });
nodeElement
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
labelElement
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
});
what's wrong with this code?
This happen also if I disable all links and labels, the nodes goes to the corner, please help me!!!
I refered to this video in YouTube to make a bubble graph. However, the author didn't use a nest function to group his data. After I pre-processed my data using nest() function, I don't know how to pass the value to a function called radiusScale() in my code. I was thinking maybe I should pass the value of
d3.entries(groupByAgeAndtime)[i]["value"]
to radiusScale().
Here is my code snippet for my problem.
var radiusScale = d3.scaleSqrt()
.domain([d3.min(Object.values(groupByAgeAndtime), function(d){
return d.mean_time_in_hospital;
}),d3.max(Object.values(groupByAgeAndtime), function(d){
return d.mean_time_in_hospital;
})])
.range([50,150]);
for (i = 0; i < 10; i++)
{
console.log(d3.entries(groupByAgeAndtime)[i]["value"]);
}
var simulation = d3.forceSimulation()
.force("x",d3.forceX(width/2).strength(0.05))
.force("y",d3.forceY(height/2).strength(0.05))
.force("collide", d3.forceCollide(function(d){
return radiusScale(d.mean_time_in_hospital) + 2;
}))
var circles = svg.selectAll(".artist")
.data(groupByAgeAndtime)
.enter()
.append("circle")
.attr("class","artist")// the "artist" will transform into class name in HTML
.attr("r", function(d){
return radiusScale(Object.values(groupByAgeAndtime))
})
.attr("fill","lightblue")
.on("click",function(d){
console.log(d)
})
This is the screenshot: for the thing I want to pass to the function radiusScale. I think after passing the correct value, the circle will appear immediately. If not, can anyone tell me what is the value I should pass to get a circle?
Here is my JSFiddle for my js, html and .csv file. I would really appreciate anyone who can tell me what value should I pass to the function.
The grouped data groupByAgeAndtime using d3.nest() has to be used on your simulation and circle drawing.
Note that your radiusScale now gets the correct value to be mapped to chosen range range([50, 150]);
var simulation = d3.forceSimulation()
.force("x", d3.forceX(width / 2).strength(0.05))
.force("y", d3.forceY(height / 2).strength(0.05))
.force("collide", d3.forceCollide(function(d) {
return radiusScale(d.mean_time_in_hospital);
}))
simulation.nodes(Object.values(groupByAgeAndtime))
.on('tick', ticked)
The same for the circles, and the circles radius now matches the simulation radius
var circles = svg.selectAll(".artist")
.data(Object.values(groupByAgeAndtime))
.enter()
.append("circle")
.attr("class", "artist")
.attr("r", function(d) {
return radiusScale(d.mean_time_in_hospital)
})
.attr("fill", "lightblue")
.on("click", function(d) {
console.log(d)
})
Here is the functional example, your text still needs to be implemented.
I've pasted your csv data here https://hastebin.com/raw/pasacimala
(function() {
var width = 800,
height = 350;
var svg = d3.select("#chart")
.append("svg")
.attr("height", height)
.attr("width", width)
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("preserveAspectRatio","xMidYMid meet")
.append("g")
.attr("transform", "translate(0,0)");
// import csv file
d3.csv("https://cors-anywhere.herokuapp.com/https://hastebin.com/raw/pasacimala")
.then(function(d) {
//data preprocessing
d.forEach(e => {
e.age = e.age.replace("[", "").replace(")", "");
e.time_in_hospital = + e.time_in_hospital;
});
return d; //must return something
})
.then((data, err) => ready(err, data))
function ready(error, datapoints) {
var groupByAgeAndtime = d3.nest()
.key(function(d) {
return d.age;
})
//.key(function(d) { return d.time_in_hospital; })
.rollup(function(v) {
return {
mean_time_in_hospital: d3.mean(v, function(d) {
return d.time_in_hospital;
})
}
})
.object(datapoints); //specify the dataset used
/**************************************** SCALING PART **************************************************/
var radiusScale = d3.scaleSqrt()
.domain([d3.min(Object.values(groupByAgeAndtime), function(d) {
return d.mean_time_in_hospital;
}), d3.max(Object.values(groupByAgeAndtime), function(d) {
return d.mean_time_in_hospital;
})])
.range([50, 150]);
/* for (i = 0; i < 10; i++) {
//console.log(d3.entries(groupByAgeAndtime)[i]["key"]);
console.log(d3.entries(groupByAgeAndtime)[i]["value"]);
} */
console.log(Object.values(groupByAgeAndtime))
// STUCK HERE
var simulation = d3.forceSimulation()
.force("x", d3.forceX(width / 2).strength(0.05))
.force("y", d3.forceY(height / 2).strength(0.05))
.force("collide", d3.forceCollide(function(d) {
return radiusScale(d.mean_time_in_hospital);
}))
// END OF STUCK HERE
var circles = svg.selectAll(".artist")
.data(Object.values(groupByAgeAndtime))
.enter()
.append("circle")
.attr("class", "artist")
.attr("r", function(d) {
return radiusScale(d.mean_time_in_hospital)
})
.attr("fill", "lightblue")
.on("click", function(d) {
console.log(d)
})
// append = add something
// text
var texts = svg.selectAll('.text')
.data(Object.keys(groupByAgeAndtime))
.enter()
.append('text')
.text(e => e)
.attr("text-anchor", "middle")
.attr('color', 'black')
.attr('font-size', '13')
simulation.nodes(Object.values(groupByAgeAndtime))
.on('tick', ticked)
function ticked() {
texts
.attr("x", function(d) {
return d.x
})
.attr("y", function(d) {
return d.y
})
circles
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
}
}
})();
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="chart"></div>
I am trying to turn this graph https://bl.ocks.org/scresawn/0e7e4cf9a0a459e59bacad492f73e139, into a react component. So far what I have is not rendering anything to the screen. If someone can take a look and make suggestions on improving the code that would be great. Also, I will be passing data to the component from another component instead fetching a CSV file. Any help will be greatly appreciated.
class ScaleTime extends Component {
constructor(){
super();
this.state = {
data: []
};
};
componentDidMount() {
fetch(data)
.then( (response) => {
return response.json() })
.then( (json) => {
this.setState({data: json});
});
};
//What is happening here?
componentWillMount(){
d3.csv("phage-history.csv", function (error, data) {
svgEnter = svg.selectAll("rect")
.data(data)
.enter();
svgEnter.append("rect")
.attr("rx", 25)
.attr("x", function (d) {
x = xScale(new Date(d.start));
return x;
})
.attr("y", function(d, i) { return 400 - (d.count*30); })
.attr("width", 25)
.attr("height", 25)
.attr("fill", "green")
.attr("stroke-width", "1px")
.attr("stroke", "black")
.on("mouseover", function (d) {
rect = d3.select(this);
rect.transition()
.duration(500)
.attr("y", function(d, i) {
console.log(this);
var x = rect.x;
return 20; })
.transition()
.duration(500)
.attr("rx", 2)
.attr("width", 300)
.attr("height", 100)
.attr("fill", "skyblue");
tooltip.html(d.authors + "<br>" + d.description);
tooltip
.style("top", 30)
.style("left",function () {
console.log("x", x);
return d3.event.pageX;
})
setTimeout(function () {
tooltip.style("visibility", "visible");
}, 1500);
})
.on("mouseout", function (d) {
d3.select(this)
.transition()
.duration(500)
.attr("rx", 25)
.attr("width", 25)
.attr("height", 25)
.transition()
.duration(500)
.delay(500)
.attr("y", function(d, i) { return 400 - (d.count*30); })
.attr("fill", "green");
//tooltip.text(d.authors);
return tooltip.style("visibility", "hidden");
});
});
}//componentWillMount
componentDidUpdate() {
var tooltip = d3.select("body")
.append("div")
.style("font-size", "12px")
.style("width", 285)
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var xScale = d3.scaleTime()
.domain([new Date("January 1, 1940 00:00:00"), new Date("January 4, 1980 00:00:00")])
.range([20, 900]);
var xAxis = d3.axisBottom(xScale);
}
render() {
return (
<div className="ScaleTime">
<svg width="960" height="500" />
svg.append("g")
.attr("transform", "translate(0,450)")
.call(xAxis);
</div>
);
}
}
I've been wrestling very hard with D3 to try to make a simple bubble-chart using force collide that live-updates the bubble size.
I can get the chart to show on the first data update with force collide. However subsequent data calls freeze the chart and the sizes are never updated:
https://jsfiddle.net/d2zcfjfa/1/
node = svg.selectAll('g.node')
.data(root.children)
.enter()
.append('g')
.attr('class', 'node')
.append('circle')
.attr('r', function(d) { return d.r * 1.4; })
.attr('fill', function(d) { return color(d.data.name); })
.call(d3.drag()
.on("start", dragStart)
.on("drag", dragged)
.on("end", dragEnd));
var circleUpdate = node.select('circle')
.attr('r', function(d)
{
return d.r;
});
simulation.nodes(root.children);
I can get updates to work but only without using the collide simulation as seen here:
https://jsfiddle.net/rgdox7g7/1/
node = svg.selectAll('g.node')
.data(root.children)
.enter()
.append('g')
.attr('id', function(d) { return d.id; })
.attr('class', 'node')
.attr('transform', function(d)
{
return "translate(" + d.x + "," + d.y + ")";
});
var nodeUpdate = svg.selectAll('g.node')
.transition()
.duration(2000)
.ease(d3.easeLinear);
var circleUpdate = nodeUpdate.select('circle')
.attr('r', function(d)
{
return d.r;
});
node.append("circle")
.attr("r", function(d) { return d.r; })
.style('fill', function(d) { return color(d.data.name); });
Everything I have tried to mix these two solutions together simply does not work. I have scoured the internet for other examples and nothing I can find is helping. Can someone please help me understand what to do? I never thought D3 would be such a frustration!
stackoverflow: the place where you have to answer your own questions.
here is my working solution:
https://jsfiddle.net/zc0fgh6y/
var subscription = null;
var width = 600;
var height = 300;
var maxSpeed = 1000000;
var pack = d3.pack().size([width, height]).padding(0);
var svg = d3.select('svg');
var node = svg.selectAll("g.node");
var root;
var nodes = [];
var first = true;
var scaleFactor = 1.4;
var color = d3.interpolateHcl("#0faac3", "#dd2323");
var forceCollide = d3.forceCollide()
.strength(.8)
.radius(function(d)
{
return d.r;
}).iterations(10);
var simulationStart = d3.forceSimulation()
.force("forceX", d3.forceX(width/2).strength(.04))
.force("forceY", d3.forceY(height/2).strength(.2))
.force('collide', forceCollide)
.on('tick', ticked);
var simulation = d3.forceSimulation()
.force("forceX", d3.forceX(width/2).strength(.0005))
.force("forceY", d3.forceY(height/2).strength(.0025))
.force('collide', forceCollide)
.on('tick', ticked);
function ticked()
{
if (node)
{
node.attr('transform', function(d)
{
return "translate(" + d.x + "," + d.y + ")";
}).select('circle').attr('r', function(d)
{
return d.r;
});
}
}
function rand(min, max)
{
return Math.random() * (max - min) + min;
};
setInterval(function()
{
var hosts = [];
for (var i = 0; i < 100; i++)
{
hosts.push({name: i, cpu: rand(10,100), speed: rand(0,maxSpeed)});
}
root = d3.hierarchy({children: hosts})
.sum(function(d)
{
return d.cpu ? d.cpu : 0;
});
var leaves = pack(root).leaves().map(function(item)
{
return {
id: 'node-'+item.data.name,
name: item.data.name,
r: item.r * scaleFactor,
x: width/2,
y: height/2,
cpu: item.data.cpu,
speed: item.data.speed
};
});
for (var i = 0; i < leaves.length; i++)
{
if (nodes[i] && nodes[i].id == leaves[i].id)
{
var oldR = nodes[i].newR;
nodes[i].oldR = oldR;
nodes[i].newR = leaves[i].r;
nodes[i].cpu = leaves[i].cpu;
nodes[i].speed = leaves[i].speed;
}
else
{
nodes[i] = leaves[i];
//nodes[i].r = 1;
nodes[i].oldR = 1;//nodes[i].r;
nodes[i].newR = leaves[i].r;
}
}
if (first)
{
first = false;
node = node.data(nodes, function(d) { return d.id; });
node = node.enter()
.append('g')
.attr('class', 'node');
node.append("circle")
.style("fill", 'transparent');
node.append("text")
.attr("dy", "0.3em")
.style('fill', 'transparent')
.style("text-anchor", "middle")
.text(function(d)
{
return d.name;//.substring(0, d.r / 4);
});
// transition in size
node.transition()
.ease(d3.easePolyInOut)
.duration(950)
.tween('radius', function(d)
{
var that = d3.select(this);
var i = d3.interpolate(1, d.newR);
return function(t)
{
d.r = i(t);
that.attr('r', function(d)
{
return d.r;
});
simulationStart.nodes(nodes).alpha(1);
}
});
// fade in text color
node.select('text')
.transition()
.ease(d3.easePolyInOut)
.duration(950)
.style('fill', 'white');
// fade in circle size
node.select('circle')
.transition()
.ease(d3.easePolyInOut)
.duration(950)
.style('fill', function(d)
{
return color(d.speed / maxSpeed);
});
}
else
{
// transition to new size
node.transition()
.ease(d3.easeLinear)
.duration(950)
.tween('radius', function(d)
{
var that = d3.select(this);
var i = d3.interpolate(d.oldR, d.newR);
return function(t)
{
d.r = i(t);
that.attr('r', function(d)
{
return d.r;
});
simulation.nodes(nodes).alpha(1);
}
});
// transition to new color
node.select('circle')
.transition()
.ease(d3.easeLinear)
.duration(950)
.style('fill', function(d)
{
return color(d.speed / maxSpeed);
});
}
}, 1000);
I have been trying to project a heat map with data loaded from csv onto a orthogonal projection on D3. While rotating the earth (i.e. D3, orthogonal projection), the points/circles remain static. I have tried many combinations but failed to figure out what is missing.
Basically, i need the small circles move along the path of countries.
Here is the complete code :
<script>
var width = 600,
height = 500,
sens = 0.25,
focused;
//Setting projection
var projection = d3.geo.orthographic()
.scale(245)
.rotate([0,0])
.translate([width / 2, height / 2])
.clipAngle(90);
var path = d3.geo.path()
.projection(projection);
//SVG container
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Define the gradient
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
// Define the gradient colors
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#FFFF00")
.attr("stop-opacity", 0);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#FF0000")
.attr("stop-opacity", 1);
//Adding water
svg.append("path")
.datum({type: "Sphere"})
.attr("class", "water")
.attr("d", path)
var countryTooltip = d3.select("body").append("div").attr("class", "countryTooltip"),
countryList = d3.select("body").append("select").attr("name", "countries");
queue()
.defer(d3.json, "world-110m.json")
.defer(d3.tsv, "world-110m-country-names.tsv")
.await(ready);
//Main function
function ready(error, world, countryData) {
var countryById = {},
countries = topojson.feature(world, world.objects.countries).features;
//Adding countries to select
countryData.forEach(function(d) {
countryById[d.id] = d.name;
option = countryList.append("option");
option.text(d.name);
option.property("value", d.id);
});
//circles for heatmap are coming from the csv below
d3.csv("cities.csv", function(error, data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("a")
.attr("xlink:href", function(d) {
return "https://www.google.com/search?q="+d.city;}
)
.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5.5)
.attr('fill', 'url(#gradient)');
var world = svg.selectAll("path.circle")
.data(countries) //countries from the tsc file is used to populate the names
.enter().append("path")
.attr("class", "land")
.attr("d", path)
//.attr('fill', 'url(#gradient)')
//Drag event
.call(d3.behavior.drag()
.origin(function() { var r = projection.rotate(); return {x: r[0] / sens, y: -r[1] / sens}; })
.on("drag", function() {
var rotate = projection.rotate();
projection.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]);
svg.selectAll("path.land").attr("d", path);
svg.selectAll(".focused").classed("focused", focused = false);
}))
//Mouse events
.on("mouseover", function(d) {
countryTooltip.text(countryById[d.id])
.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px")
.style("display", "block")
.style("opacity", 1);
})
.on("mouseout", function(d) {
countryTooltip.style("opacity", 0)
.style("display", "none");
})
.on("mousemove", function(d) {
countryTooltip.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px");
});
});//closing d3.csv here
//Country focus on option select
d3.select("select").on("change", function() {
var rotate = projection.rotate(),
focusedCountry = country(countries, this),
p = d3.geo.centroid(focusedCountry);
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
//svg.selectAll("circle").attr("d", data)
//.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
});
function country(cnt, sel) {
for(var i = 0, l = cnt.length; i < l; i++) {
if(cnt[i].id == sel.value) {return cnt[i];}
}
};
};
</script>
Please help.
Thank you in advance
Here is an option, using point geometries:
.enter().append('path')
.attr('class', 'circle_el')
.attr('fill', function(d) {return d.fill; })
.datum(function(d) {
return {type: 'Point', coordinates: [d.lon, d.lat], radius: some_radius};
})
.attr('d', path);
This is cool because you will update the circles simultaneously with a path redraw. And in addition it will account for the projection as a sphere, not showing circles that should be on the non-visible side of the sphere. I got the idea from this post by Jason Davies.