Related
I am building a map where circles appended to cities on a US map are sized based upon a value in the CSV (guns column or d.guns in the JavaScript).
I was able to get the circles to resize using Math.sqrt() while appending the circles, but I do not think this is the right way to do it (or is it fine?):
.attr('r', function(d) {
return Math.sqrt(d.guns * 0.0010);
})
I attempted to use the d3.scaleSqrt() and d3.extent to resize the circles based upon the values, but I was thrown these errors in the console:
Here is the code when I attempted using d3.scaleSqrt:
<head>
<script>
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv('top100cities.csv', function(error, data) {
// Converts strings to integers.
data.forEach(function(d) {
return d.guns = +d.guns;
})
var guns_extent = d3.extent(function(d) {
return d.guns;
});
var radius = d3.scaleSqrt()
.domain(guns_extent)
.range([0, 12]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.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', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
</script>
<body>
<script>
d3.json('us_states.json', draw);
</script>
</body>
Although Xavier Guihot's answer is technically correct and proposes a working solution it slightly deviates from the D3 track. The error in your code was caused by not providing all parameters to d3.extent(); you simply forgot to pass in the array, namely data, from which to determine the extent (emphasis mine):
# d3.extent(array[, accessor]) <>
Returns the minimum and maximum value in the given array using natural order. Providing both, the array as well as the accessor, your code would look like this:
var guns_extent = d3.extent(data, function(d) { // Pass in data as first parameter
return d.guns;
});
Below is the working demo:
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv("https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/top100cities.csv", function(error, data) {
// Converts strings to integers.
data.forEach(function(d) {
return d.guns = +d.guns;
})
var guns_extent = d3.extent(data, function(d) { // Pass in data
return d.guns;
});
var radius = d3.scaleSqrt()
.domain(guns_extent)
.range([0, 12]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.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', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
d3.json('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/us_states.json', draw);
<script src="https://d3js.org/d3.v4.js"></script>
The error, I think, was in the retrieval of the max number of guns.
Here is the diff:
let guns = data.map( function(d) { return d.guns });
console.log(Math.max(...guns));
var radius = d3.scaleSqrt().domain([0, Math.max(...guns)]).range([0, 25]);
Here is the modified demo:
<head>
<script>
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/top100cities.csv', function(error, data) {
let guns = data.map( function(d) { return d.guns });
console.log(Math.max(...guns));
var radius = d3.scaleSqrt().domain([0, Math.max(...guns)]).range([0, 25]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.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', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
</script>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
d3.json('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/us_states.json', draw);
</script>
</body>
Setting up the formula for the area of a circle could work.
The formula is A=πr2, where A equals the area of the circle, r equals the radius.
if A = d.guns
We know Area and π
then:
r = √(d.guns/π)
.attr("r", function (d) {return Math.sqrt(d.guns/3.1416);})
https://www.wikihow.com/Calculate-the-Radius-of-a-Circle#/Image:Calculate-the-Radius-of-a-Circle-Step-10-Version-4.jpg
Very new to D3, so thanks for being patient! I'm building a map where I want to append circles to certain cities on a US map. The lat and lon coordinates are in a .csv I'm hosting on GitHub.
Right now, the two circles I'm trying to append just appear in the far left corner of the map:
The Map
These are the errors I'm getting:
Console Errors
Here's the code:
<head>
<script>
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv('top100cities.csv', function(error, data) {
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.lon, d.lat]);
})
.attr('cy', function(d) {
return projection([d.lon, d.lat]);
})
.attr('r', 20)
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
</script>
</head>
<body>
<script>
d3.json('us_states.json', draw);
</script>
</body>
You are setting the same value for both cx and cy:
.attr('cx', function(d) {
return projection([d.lon, d.lat]);
})
.attr('cy', function(d) {
return projection([d.lon, d.lat]);
})
That value is not either cx or cy, it is both: projection([d.lon,d.lat]) will return a two element array containing x and y, so you should be using:
.attr('cx', function(d) {
return projection([d.lon, d.lat])[0]; // x coordinate
})
.attr('cy', function(d) {
return projection([d.lon, d.lat])[1]; // y coordinate
})
The error occurs as you pass an array and not a number.
After appending or creating a new circle on drag, I want to able to drag the circle around. I tried with the following code using .call(d3.behavior.drag()...) but don't know why it isn't working
Preview: http://jsbin.com/vabowofiqe/1/edit?html,output
code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 803);
//Draw the Circle
var circle = svgContainer.append("circle")
.attr("cx", 35)
.attr("cy", 145)
.attr("r", 25)
.style("stroke-opacity", .9)
.style("stroke", "green")
.style("stroke-width", 2)
.style('cursor', 'move')
.style("fill", "white");
function move() {
d3.select(this)
.attr('x', d3.event.x)
.attr('y', d3.event.y);
};
var drag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on('dragend', function (d) {
var mouseCoordinates = d3.mouse(this);
if (mouseCoordinates[0] > 170) {
//Append new element
var newCircle = d3.select("svg").append("circle")
.classed("drg", true)
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 20)
.attr("cx", mouseCoordinates[0])
.attr("cy", mouseCoordinates[1])
.style("fill", "white")
.style("stroke-width", 2)
.style("stroke", "#CDB483")
//Calling the drag behavior after clonning .call(
d3.behavior.drag()
.on('drag', move).origin(function () {
var t = d3.select(this);
return {x: t.attr("cx"), y: t.attr("cy")};
}));
;
}
});
circle.call(drag);
A SVG circle element doesn't have x and y properties, but cx and cy properties that can be used to position them. You should change your move function accordingly.
function move() {
d3.select(this)
.attr('cx', d3.event.x)
.attr('cy', d3.event.y);
};
Using d3.js, how would I modify the following code to add a nested, yellow-filled circle of radius "inner_radius" to each of the existing generated circles:
var circleData = [
{ "cx": 300, "cy": 100, "radius": 80, "inner_radius": 40},
{ "cx": 75, "cy": 85, "radius": 50, "inner_radius": 20}];
var svgContainer = d3.select("body").append("svg")
.attr("width",500)
.attr("height",500);
var circles = svgContainer.selectAll("circle")
.data(circleData)
.enter()
.append("circle");
var circleAttributes = circles
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.radius; })
.style("fill", function (d) { return "red"; });
As imrane said in his comment, you will want to group the circles together in a g svg element. You can see the updated code here with relevant changes below.
var circles = svgContainer.selectAll("g")
.data(circleData)
.enter()
.append("g");
// Add outer circle.
circles.append("circle")
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.radius; })
.style("fill", "red");
// Add inner circle.
circles.append("circle")
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.inner_radius; })
.style("fill", "yellow");
I'm generating voronoi paths based on some points in forced layout. I'd like to randomly assign these paths 1 of 10 classes and then wrap some of these classes with a clipPath that I can then apply to another element.
Is it possible to wrap svg tags around elements using d3 as opposed to appending?
Or is it even possible to use multiple paths generated by d3 in a clipPath?
Thank you for your help,
w = $(window).width();
h = $(window).height();
function ranNum(){
return Math.floor((Math.random()*10)+1);
}
$('#grid').css('height', h);
var vertices = d3.range(50).map(function(d) { return {x: d.x, y: d.y}; });
//console.log(vertices);
links = [];
voronoiVertices = [];
var force = d3.layout.force()
.nodes(vertices)
.size([w, h])
.linkDistance(60)
.charge(-900)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
//path gradient
var defs = svg.append('defs')
var radialGradient = defs.append('radialGradient')
.attr('id', 'pathGrad')
.attr('cx', '50%')
.attr('cy', '50%')
.attr('r', '50%')
.attr('fx', '50%')
.attr('fy', '50%');
var stop1 = radialGradient.append('stop')
.attr('offset', '.2')
.attr('stop-color', '#a8a8a8');
var stop2 = radialGradient.append('stop')
.attr('offset', '1')
.attr('stop-color', '#0000000');
//path dropShadow
var filterShadow = defs.append('filter')
.attr('id', 'pathShadow')
.attr('height', '130%');
var gCir = svg.append('g')
.attr("class", "gCircle");
var gPath = svg.append('g')
.attr("class", "gPath");
var circle = svg.selectAll("circle");
var path = gPath.selectAll("path")
.data(d3.geom.voronoi(vertices))
.enter().append("path")
.attr("fill", "url(#pathGrad)");
//wraps path with random class after generation
$('path').each(function(){$(this).attr('class', 'path-' + Math.floor((Math.random()*10)+1))});
var clip = defs.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr("width", '900px')
.attr("height", '900px');
var gClip = svg.append("svg:g")
.attr('clip-path', 'url(#clip)');
/*
var clip = gClip.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", "clip.jpg")
.attr("x", "0px")
.attr("y", "0px")
.attr("width", w)
.attr("height", h);
*/
var selectPath = d3.selectAll('.path-10');
console.log(selectPath);
function tick() {
voronoiVertices = vertices.map(function(t){return [t.x, t.y]})
path = path.data(d3.geom.voronoi(voronoiVertices))
path.enter().append("path")
.attr("d", function(t) { return "M" + t.join("L") + "Z"; });
path.attr("fill", "url(#pathGrad)")
.attr("d", function(t) { return "M" + t.join("L") + "Z"; });
circle = circle.data(vertices)
circle.enter().append("circle")
.call(force.drag)
.attr("r", 0)
.attr('class', function(d) { return d.index; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.transition().duration(5000).attr("r", 5);
circle.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
});
There is a strange mix of jQuery and D3 in this. Try to do the things in D3 when you work with it. For example I'd rather do this:
.attr("class", function(d){return 'path-'+Math.floor((Math.random()*10)+1))});
than this:
$('path').each(function(){$(this).attr('class', 'path-' + Math.floor((Math.random()*10)+1))});
d3 has an exotic but smart way of doing things, it's better to learn this update pattern well before doing something serious.
And here is the working code:
w = 1200;
h = 500;
function ranNum(){
return Math.floor((Math.random()*10)+1);
}
var vertices = d3.range(50).map(function(d) { return {x: d.x, y: d.y}; });
//console.log(vertices);
links = [];
voronoiVertices = [];
var force = d3.layout.force()
.nodes(vertices)
.size([w, h])
.linkDistance(60)
.charge(-900)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
//path gradient
var defs = svg.append('defs')
var radialGradient = defs.append('radialGradient')
.attr('id', 'pathGrad')
.attr('cx', '50%')
.attr('cy', '50%')
.attr('r', '50%')
.attr('fx', '50%')
.attr('fy', '50%');
var stop1 = radialGradient.append('stop')
.attr('offset', '.2')
.attr('stop-color', '#a8a8a8');
var stop2 = radialGradient.append('stop')
.attr('offset', '1')
.attr('stop-color', '#0000000');
//path dropShadow
var filterShadow = defs.append('filter')
.attr('id', 'pathShadow')
.attr('height', '130%');
var gCir = svg.append('g')
.attr("class", "gCircle");
var gPath = svg.append('g')
.attr("class", "gPath");
var circle = svg.selectAll("circle");
var path = gPath.selectAll("path")
.data(d3.geom.voronoi(vertices))
.enter().append("path")
.attr("fill", "url(#pathGrad)");
//wraps path with random class after generation
//$('path').each(function(){$(this).attr('class', 'path-' + Math.floor((Math.random()*10)+1))});
var clip = defs.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr("width", '900px')
.attr("height", '900px');
var gClip = svg.append("svg:g")
.attr('clip-path', 'url(#clip)');
function tick() {
voronoiVertices = vertices.map(function(t){return [t.x, t.y]})
path = path.data(d3.geom.voronoi(voronoiVertices))
path.enter().append("path")
.attr("d", function(t) { return "M" + t.join("L") + "Z"; });
path.attr("fill", "url(#pathGrad)")
.attr("d", function(t) { return "M" + t.join("L") + "Z"; });
circle = circle.data(vertices)
circle.enter().append("circle")
.call(force.drag)
.attr("r", 0)
.attr('class', function(d) { return d.index; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.transition().duration(5000).attr("r", 5);
circle.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
Good luck!