I am trying to create a simple bubblemap that plots longitude-latitude pairs by a number of admissions for a county using US DHS data. My CSV file has the name of county, longitude, latitude, type of Admission, class of admission, number of admissions, and country of origin. I've created some checkboxes that will allow the user to see a bubblemap of the different classes of admissions that were admitted to the United States.
I've learned that the d3geoAlbersUsa projection projects the map of the USA at [0,0] which is off the coast of Africa. From the photo below (see Imgur link), you can see that my points seem to plot at the correct coordinates. However, the background map is not visible. When I use d3.geoMercator() and center the projection on [0,0] I see the map. In both cases, I don't know how to make the bubbles appear on the map.
I'm new to d3 so I'm not sure how to go about this. How do I create a bubblemap using long lat coordinates with the d3geoAlbersUsa projection? Thanks for the help.
Here is my index.html:
var width = 750
var height = 750
// The svg
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", height)
// Map and projection
// Map and projection
// var projection = d3.geoMercator()
// //.center([-100, 30]) // GPS of location to zoom on
// .center([0, 0])
// .scale(200) // This is like the zoom
// .translate([ width/2, height/2 ])
var projection = d3.geoAlbersUsa()
//.center([0,0])
.scale([1000]) // This is like the zoom
.translate([width / 2, height / 2])
var data = d3.csv("sheet.csv", function(data) {
var markers = data.filter(function(d) {
if (
(d["MajorClassAdmission"] == "EmploymentPreference1st" ||
d["MajorClassAdmission"] == "EmploymentPreference2nd" ||
d["MajorClassAdmission"] == "EmploymentPreference3rd") &&
d["CountryofBirth"] == "Bangladesh" && d["Admissions"] != "D" && d["lon"] != "NA" && d["lat"] != "NA") {
return d;
}
})
//console.log(markers)
// Load external data and boot
//d3.json("projectedgeography.json", function(data){
d3.json("projectedgeography.geojson", function(data) {
//d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson", function(data){
// Filter data
//data.features = data.features.filter( function(d){return d.properties.name=="USA"} )
//data.features = data.features.filter( function(d){return d.properties.name=="USA"} )
// Create a color scale
var color = d3.scaleOrdinal()
.domain(["EmploymentPreference1st", "EmploymentPreference2nd", "EmploymentPreference3rd"])
.range(["#402D54", "#D18975", "#8FD175"])
// Add a scale for bubble size
var size = d3.scaleLinear()
.domain([1, 100]) // What's in the data
.range([4, 50]) // Size in pixel
//var path = d3.geo.path().projection(projection)
//Draw the map
svg.append("g")
.selectAll("path")
.data(data.features)
.enter()
.append("path")
.style("stroke", "#black")
.style("opacity", .3)
//create a tooltip (hover information)
var Tooltip = d3.select("#my_dataviz")
.append("div")
.attr("class", "tooltip")
.style("opacity", 1)
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function(d) {
Tooltip.style("opacity", 1)
}
var mousemove = function(d) {
Tooltip
.html(d.CountyState + "<br>" + "long: " + d.lon + "<br>" + "lat: " + d.lat + "<br>" + "Admissions: " + d.Admissions)
.style("left", (d3.mouse(this)[0] + 10) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mouseleave = function(d) {
Tooltip.style("opacity", 0)
}
// Add circles:
svg
.selectAll("myCircles")
.data(markers)
.enter()
.append("circle")
.attr("class", function(d) {
return (d.MajorClassAdmission)
})
.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 d.Admissions
})
.style("fill", function(d) {
return color(d.MajorClassAdmission)
})
.attr("stroke", function(d) {
return color(d.MajorClassAdmission)
})
.attr("stroke-width", 3)
.attr("fill-opacity", .4)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
// This function is gonna change the opacity and size of selected and unselected circles
function update() {
// For each check box:
d3.selectAll(".checkbox").each(function(d) {
cb = d3.select(this);
group = cb.property("value")
//console.log(group)
// If the box is check, I show the group
if (cb.property("checked")) {
//console.log("checked")
svg.selectAll("." + group).transition().duration(1000).style("opacity", 1).attr("r", function(d) {
return d.Admissions
})
// Otherwise I hide it
} else {
//console.log("unchecked")
svg.selectAll("." + group).transition().duration(1000).style("opacity", 0).attr("r", 0)
}
})
}
// When a button change, I run the update function
d3.selectAll(".checkbox").on("change", update)
// And I initialize it at the beginning
update()
})
})
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
.circle:hover {
stroke: black;
stroke-width: 4px;
}
.legend circle {
fill: none;
stroke: #ccc;
}
.legend text {
fill: #777;
font: 10px sans-serif;
text-anchor: middle;
}
</style>
<!-- Load d3.js and the geo projection plugin -->
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://d3js.org/queue.v1.min.js"></script>
<!-- <script src="http://d3js.org/d3.geo.projection.v0.min.js"></script> optional, depending on projection -->
<h1>LPR Top 200 Bangladesh</h1>
<!-- Button -->
<div>
<input type="checkbox" class="checkbox" value="EmploymentPreference1st" checked><label>Employment Preference 1</label>
<input type="checkbox" class="checkbox" value="EmploymentPreference2nd" checked><label>Employment Preference 2</label>
<input type="checkbox" class="checkbox" value="EmploymentPreference3rd" checked><label>Employment Preference 3</label>
</div>
<!-- Create an element where the map will take place -->
<!-- <svg id="my_dataviz" width="500" height="500"></svg> -->
<div id="my_dataviz"></div>
Attached are screenshots of my data and how my bubblemap looks like using d3.geoMercator() and d3.geoAlbersUsa()
Here are the screenshots: https://imgur.com/gallery/dRghqAf
Edit: It looks like I was using a bad geojson. I had initially downloaded the shapefile from US Census and used Mapshaper to convert to a geojson file. Using that geojson, I used a projection app to create a geojson with the geoalbersUsa projection. In effect, I was using d3.geoAlbersUsa() on a geojson already converted to that projection. I misunderstood how geoAlbersUsa() works. Using the original geojson from Mapshaper, I get the map I'm looking for: https://imgur.com/gallery/9sj68SM
D3 projections are all pretty similar. If one projection projects a given point correctly, chances are so does every other one. They take coordinates in decimal degrees and spit out coordinates in pixels and not much else. Consequently, your statement: "d3geoAlbersUsa projection projects the map of the USA at [0,0] which is off the coast of Africa." is incorrect. [0,0] in degrees is off the coast of Africa, [0,0] in pixels can be anywhere. D3 is giving you pixels, not degrees.
If you have features/tiles/raster that show Africa where the US is or vice versa, you have conflicting projections or coordinate systems, not a failure of d3.geoAlbersUsa.
Also, if you are mixing pre-projected geometry and unprojected geometry, you're making it too difficult: you'll need to ensure that the projections used to pre-project one geometry matches the projection used to project the second, which unless you use d3 to preproject the geometry offline somewhere will cause you some headaches.
If you have a csv with coordinates in decimal degrees and a geojson with the same, you can assume that the projection (and consequently, any paths) will be rendered consistently, and correctly (where correctly is not necessarily equivalent to as desired...).
Now d3.geoAlbersUsa is a bit of a special projection in that it is a composite projection combining several distinct projections (all Albers, while also shrinking Alaska down several times). It is calibrated to be centered on the US assuming a screen size of 960x600 pixels. You shouldn't need to alter the centering point, it has been set for you. The translate must be modified in your case, however, as this translates the projected coordinates. The default translate expects a container of 960x600 pixels. You want the translate to be equal to width/2,height/2, as you have. Trickier is the scale, the default scale is 1070, which extends the US across 960 pixels. This scale factor is linear, so we can use: 1070/960*width to create a new scale factor (assuming width is the limiting factor).
d3.geoMercator is more plain, but we need to center that projection properly because it isn't by default centered on the US. We can use:
d3.geoMercator()
.center([-100,30]) // as you had
.scale(1000) // or whatever
.translate([width/2,height/2])
We still need to apply the translate, because the default values don't expect a square 750x750 svg.
Note there is also projection.fitSize() which will automatically adjust scale and translate to center a geojson feature like so:
d3.geoMercator()
.fitSize([width,height],geojson) // geojson must be a valid geojson object, not an array of geojson features.
We pass the projection to the path generator and then draw points and geojson relatively straightforwardly:
The following use the commented out line accessing a geojson, if you share projectedgeography.geojson, I may be able to provide you with some information on why your code doesn't work as expected. But given its name, it sure seems like it isn't unprojected data
AlbersUsa
var width = 750
var height = 400
// The svg
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", height);
// Create a color scale
var color = d3.scaleOrdinal()
.domain(["A", "B", "C" ])
.range([ "#402D54", "#D18975", "#8FD175"])
var projection = d3.geoAlbersUsa()
.translate([width/2,height/2])
.scale(1070/960*width); // scale the scale factor, otherwise map will overflow SVG bounds.
var path = d3.geoPath(projection);
d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson", function(geojson){
geojson.features = geojson.features.filter( function(d){return d.properties.name=="USA"} )
// d3.csv("file.csv", function(csv) {
// As I cannot access your csv, I'm reproducing a few lines here:
var csv = [
{lon: -116.2,lat: 43.5, Admissions: 24, MajorClassAdmission: "A"},
{lon: -81.7,lat: 41.4, Admissions: 13, MajorClassAdmission: "B"},
{lon: -74.1,lat: 40.9, Admissions: 35, MajorClassAdmission: "C"},
{lon: -121.6,lat: 37.3, Admissions: 14, MajorClassAdmission: "B"},
{lon: -73.9,lat: 40.68, Admissions: 13, MajorClassAdmission: "A"},
]
svg.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("d", path);
svg.selectAll("circle")
.data(csv)
.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 d.Admissions
})
.attr("fill", function(d) {
return color(d.MajorClassAdmission);
})
// ...
// })
})
.circle:hover{
stroke: black;
stroke-width: 1px;
}
path {
fill: none;
stroke: #ccc;
stroke-width: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>
Mercator
var width = 750
var height = 400
// The svg
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", height);
// Create a color scale
var color = d3.scaleOrdinal()
.domain(["A", "B", "C" ])
.range([ "#402D54", "#D18975", "#8FD175"])
var projection = d3.geoMercator()
var path = d3.geoPath(projection);
d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson", function(geojson){
geojson.features = geojson.features.filter( function(d){return d.properties.name=="USA"} )
projection.fitSize([width,height],geojson)
// d3.csv("file.csv", function(csv) {
// As I cannot access your csv, I'm reproducing a few lines here:
var csv = [
{lon: -116.2,lat: 43.5, Admissions: 24, MajorClassAdmission: "A"},
{lon: -81.7,lat: 41.4, Admissions: 13, MajorClassAdmission: "B"},
{lon: -74.1,lat: 40.9, Admissions: 35, MajorClassAdmission: "C"},
{lon: -121.6,lat: 37.3, Admissions: 14, MajorClassAdmission: "B"},
{lon: -73.9,lat: 40.68, Admissions: 13, MajorClassAdmission: "A"},
]
svg.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("d", path);
svg.selectAll("circle")
.data(csv)
.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 d.Admissions
})
.attr("fill", function(d) {
return color(d.MajorClassAdmission);
})
// ...
// })
})
.circle:hover{
stroke: black;
stroke-width: 1px;
}
path {
fill: none;
stroke: #ccc;
stroke-width: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<div id="my_dataviz"></div>
While the comments in your code obfuscate the code you are using to generate problematic maps, there are a few potential simplifications in your code: you appear to be using both d3v4 and d3v3 given the use of d3.geo.path (v3) instead of d3.geoPath (v4+). The base d3 bundle includes all the geographic functions you need, so you don't need to import https://d3js.org/d3-geo-projection.v2.min.js or any other modules.
Related
I want to repeat a group of shapes specifically
text rect text circles
Where in circles is again a repeat of circle
My data is
Jsondata =[
{ "name":"A", "WidthOfRect":50, "justAnotherText":"250", "numberOfCircles" :3 },
{ "name":"B", "WidthOfRect":150, "justAnotherText":"350","numberOfCircles" :2 },
{ "name":"C", "WidthOfRect":250, "justAnotherText":"450","numberOfCircles" :1 }]
Basically Out of this data i am trying to construct a customized bar chart.
The width of the rect is based upon the data widthofrect from the json, as well as number of circles is based upon numberofcircles property.
I looked out for a number of options to repeat group of shapes but couldn't find one.
First of all, you're right in your comment: do not use loops to append elements in a D3 code. Also, your supposition about the length of the data is correct.
Back to the question:
The text and rect part is pretty basic, D3 101, so let's skip that. The circles is the interesting part here.
My proposed solution involves using d3.range to create an array whose number of elements (or length) is specified by numberOfCircles. That involves two selections.
First, we create the groups (here, scale is, obviously, a scale):
var circlesGroups = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(20," + scale(d.name) + ")"
});
And then we create the circles. Pay attention to the d3.range:
var circles = circlesGroups.selectAll(null)
.data(function(d) {
return d3.range(d.numberOfCircles)
})
.enter()
.append("circle")
//etc...
Here is a demo, I'm changing the numberOfCircles in your data to paint more circles:
var width = 500,
height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var data = [{
"name": "A",
"WidthOfRect": 50,
"justAnotherText": "250",
"numberOfCircles": 13
},
{
"name": "B",
"WidthOfRect": 150,
"justAnotherText": "350",
"numberOfCircles": 22
},
{
"name": "C",
"WidthOfRect": 250,
"justAnotherText": "450",
"numberOfCircles": 17
}
];
var scale = d3.scalePoint()
.domain(data.map(function(d) {
return d.name
}))
.range([20, height - 20])
.padding(0.5);
var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
var circlesGroups = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(20," + scale(d.name) + ")"
})
.style("fill", function(d) {
return colorScale(d.name)
})
var circles = circlesGroups.selectAll(null)
.data(function(d) {
return d3.range(d.numberOfCircles)
})
.enter()
.append("circle")
.attr("r", 5)
.attr("cx", function(d) {
return 10 + 12 * d
});
var axis = d3.axisLeft(scale)(svg.append("g").attr("transform", "translate(20,0)"));
<script src="https://d3js.org/d3.v5.min.js"></script>
PS: I'm using D3 v5.
I have problem with adding the axis to stem plot. I'm using D3.js in version 3.
I draw my own stems that consist of circles and lines.
I have two scenarios:
1.
1.1 First I add the stems
1.2 Then I add axis
The Y-axis is plotted on the stem. On the following image the magenta line covers green one (I want the opposite, stem should cover axis)
2
2.1 First I add axis
2.2 Then I add the stems
The lines of stems are not plotted.
I need someone to explain me why the lines are not drawn.
File js/stem-functions.js
var svgParams = {
// Graph field
graphWidth : 200,
graphHeight : 120,
// Margins
leftPadding : 30,
rightPadding : 10,
upPadding : 15,
downPadding : 25,
}
function setParametersSvg() {
// Size of the SVG object
this.svgWidth = this.graphWidth + this.leftPadding + this.rightPadding;
this.svgHeight = this.graphHeight + this.upPadding + this.downPadding;
}
setParametersSvg.apply(svgParams);
// Create Scale functions
var xScale = (function ustawSkaleX(minX, maxX, svgParam) {
var xSc = d3.scale.linear()
.domain([minX, maxX])
.range([svgParam.leftPadding, svgParam.leftPadding + svgParam.graphWidth]);
return xSc;
} (0, 5, svgParams) );
var yScale = (function ustawSkaleY(minY, maxY, svgParam) {
var ySc = d3.scale.linear()
.domain([minY, maxY])
.range([svgParam.upPadding + svgParam.graphHeight, svgParam.upPadding]);
return ySc;
} (0, 0.5, svgParams) );
function addAxis(svg, svgParam, xScale, yScale) {
// Functions drawing axis X
var xAxis = d3.svg.axis();
xAxis.scale(xScale) // Scale function for X
.orient("bottom") // Location of label
.ticks(7); // Ticks
// Add group
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (svgParam.svgHeight - svgParam.downPadding) +")")
.call(xAxis);
// Functions drawing axis Y
var yAxis = d3.svg.axis();
yAxis.scale(yScale) // Scale function for Y
.orient("left") // Location of label
.ticks(4); // Ticks
// Add group
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + (svgParam.leftPadding) + ", 0)")
.call(yAxis);
}
function addStems(svg, dataset, xScale, yScale) {
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("fill", "green")
.attr("cx", function(d) { return xScale(d.x); } )
.attr("cy", function(d) { return yScale(d.n); } )
.attr("r", 4);
var lines = svg.selectAll("line")
.data(dataset)
.enter()
.append("line")
.attr("class", "stem-line")
.attr("stroke", "green")
.attr("stroke-width", "1")
.attr("x1", function(d) { return xScale(d.x); } )
.attr("x2", function(d) { return xScale(d.x); } )
.attr("y1", function(d) { return yScale(0); } )
.attr("y2", function(d) { return yScale(d.n); } );
}
File js/stem-examples.js
// Data set
var p = [
{ x: 0, n: 0.15 },
{ x: 1, n: 0.25 },
{ x: 2, n: 0.40 },
{ x: 3, n: 0.15 },
{ x: 4, n: 0.05 }
];
console.log('probabilities ', p);
d3.select("body").append("h4").html("Call Stems printing before Axis printing");
// Stems before Axis => Axis is over stem
var svg1 = d3.select("body")
.append("svg")
.attr("width", svgParams.svgWidth)
.attr("height", svgParams.svgHeight);
addStems(svg1, p, xScale, yScale);
addAxis(svg1, svgParams, xScale, yScale);
d3.select("body").append("br");
d3.select("body").append("h4").html("Call Axis printing before Stems printing");
// Axis before Stems => stem lines are gone (why?)
var svg2 = d3.select("body")
.append("svg")
.attr("width", svgParams.svgWidth)
.attr("height", svgParams.svgHeight);
addAxis(svg2, svgParams, xScale, yScale);
addStems(svg2, p, xScale, yScale);
File css/styl.js
svg { border: teal 1px solid; }
.axis path, .axis line {
fill: none;
stroke: magenta;
stroke-width: 1;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 12px;
fill: DarkViolet;
}
File index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Stem plot with axis</title>
<link rel="stylesheet" href="css/styl.css">
</head>
<body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script> <!-- V3 -->
<!-- TODO: try version 4 of D3.js --> <!-- V4 -->
<script src="js/stem-functions.js"></script>
<script src="js/stem-examples.js"></script>
</body>
</html>
Appending the axis first and then the stems is the correct approach. The problem is not that.
The problem is that, when you do this...
var lines = svg.selectAll("line")
.data(dataset)
.enter()
.append("line")
//etc...
... you are selecting lines that already exist in that SVG, and binding data to them.
Solution
Do this:
var lines = svg.selectAll(null)
.data(dataset)
.enter()
.append("line")
//etc...
To understand why I'm selecting null, have a look at this question/answer of mine here: Selecting null: what is the reason of using 'selectAll(null)' in D3.js?
I have the following D3.js project that is available here:
http://bl.ocks.org/diggetybo/raw/e75dcb649ae3b26e2312a63434fc970c/
The latitude and longitude inputs are below the map.
It's supposed to take user input numbers of latitude and longitude and "project" svg circles at the given coordinate. The issue is I'm either getting ____ is not a function error or dev tools throws no errors at all, but the circles are never projected.
It's a short file, can someone explain why it's not working the way I thought?
Your update function doesn't make any sense.
It accepts two inputs, but you only ever call it with one.
.selectAll("circle").enter() is not valid d3 syntax.
You need to call projection with both the latitude and longitude, you pass 0 which will result in it returning null since it's outside of the projection.
After you fix all this, you'll still be off because you've moved your paths by your margin and would have been better off putting them in a g moved by the margins.
All that said, a simple rewrite would be:
var lat = d3.select("#latValue").on("input", function() {
update();
}).node();
var long = d3.select("#lonValue").on("input", function() {
update();
}).node();
function update() {
// lat/long to pixel
var coors = projection([long.value, lat.value]);
// if outside projection don't add circle
if (coors === null) return;
// add circle
container
.append("circle")
.attr("cx", coors[0])
.attr("cy", coors[1])
.attr("r", Math.sqrt(5) * 4)
.style("fill", "black")
.style("opacity", 0.85);
}
Running code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
/* On mouse hover, lighten state color */
path:hover {
fill-opacity: .7;
}
</style>
</head>
<body>
<script type="text/javascript">
//Width and height of map
var width = 960;
var height = 500;
var margins = { left: 0, top: 100, right: 0, bottom: 0 };
// D3 Projection
var projection = d3.geo.albersUsa()
.translate([width/2, height/2]) // translate to center of screen
.scale([1000]); // scale things down so see entire US
// Define path generator
var path = d3.geo.path() // path generator that will convert GeoJSON to SVG paths
.projection(projection); // tell path generator to use albersUsa projection
// Define linear scale for output
var color = d3.scale.linear()
.range(["#c3e2ff","#15198e"]);
//Create SVG element and append map to the SVG
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height+margins.top);
svg.append('text')
.text('Coordinate Query')
.attr('font-size','24px')
.attr('transform', 'translate(' + 30 + ',' +70 + ')')
.attr('font-family','Calibri');
svg.append('text')
.text('Data as of 12/2016')
.attr('font-size','12px')
.attr('transform', 'translate(' + 35 + ',' +100 + ')')
.attr('font-family','Calibri');
// Load in my states data!
color.domain([0,100]); // setting the range of the input data
// Load GeoJSON data and merge with states data
d3.json("https://jsonblob.com/api/573228c3-d068-11e6-b16a-b501dc8d2b08", function(json) {
//var coordinates = d3.mouse(this);
// Bind the data to the SVG and create one path per GeoJSON feature
var container = svg.append("g")
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
container.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-linejoin","round")
.style("stroke-width", "1.5")
.style("fill", 'steelblue');
// Modified Legend Code from Mike Bostock: http://bl.ocks.org/mbostock/3888852
var lat = d3.select("#latValue").on("input", function() {
update();
}).node();
var long = d3.select("#lonValue").on("input", function() {
update();
}).node();
function update() {
// lat/long to pixel
var coors = projection([long.value, lat.value]);
// if outside projection don't add circle
if (coors === null) return;
// add circle
container
.append("circle")
.attr("cx", coors[0])
.attr("cy", coors[1])
.attr("r", Math.sqrt(5) * 4)
.style("fill", "black")
.style("opacity", 0.85);
}
});
</script>
<p>
<label for="latValue"
style="display: inline-block;width:240px;text-align:right;font-size:18px;font-family:Play">
Lattitude:<span id="latValue-value"></span>
</label>
<input type="number"min="-360"max="360"step="1"value="0" id="latValue">
<label for="lonValue"
style="display: inline-block;width:240px;text-align:right;font-size:18px;font-family:Play">
Longitude:<span id="lonValue-value"></span>
</label>
<input type="number"min="-360"max="360"step="1"value="0" id="lonValue">
</p>
</body>
</html>
Im creating a small d3 (http://d3js.org/) pie chart where some pieces of the pie are separated
and translated away from the center of the pie on hover.
So far i managed separating the different parts of the pie and translating them away from the center of the circle, but they are translated separatedly. My goal is to group the pieces and translate them away together.
Im very new to d3js.
Basically what I want to achieve is grouping some of the elements and translating them away from the center of origin grouped.
Right now the pieces are translated separately and Im guessing that I can fix the problem by adding a parent and translating that parent away from the center.
So basically my question is :
How can i group pieces of data (fx the ones labeled food) and translate that group away from the center of origin?
The project can be found below and the content of the csv file in the bottom. Copy paste the code into a html-document and copy paste the csv data into a file named 'data.csv' located in the same folder.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<!--https://github.com/mhemesath/r2d3/-->
<!--[if lte IE 8]><script src="r2d3.min.js"></script><![endif]-->
<!--[if gte IE 9]><!-->
<script src="http://d3js.org/d3.v3.min.js"></script>
<!--<![endif]-->
<script>
// dimension
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
// colors
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
//append svg to body
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("data.csv", function(error, data) {
var g = svg.selectAll(".arcs")
.data(pie(data))
.enter().append("g")
.attr("class", function (d, i) {
return data[i].class; // i is 0-based.
});
// Select all classes named 'pizza' and translate away from center of circle
svg.selectAll('.food')
.attr("transform", function(d) { //set the origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.innerRadius = 0;
d.outerRadius = 0;
x = arc.centroid(d)[0];
y = arc.centroid(d)[1];
return "translate(" + x/4 +','+ y/4 + ")"; //this gives us a pair of coordinates like [50, 50]
});
// color fill
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
// append text
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
});
</script>
//content of csv file :
age,population,class
<5,2704659,food
5-13,4499890,food
14-17,2159981,food
18-24,3853788,icecream
25-44,14106543,icecream
45-64,8819342,pizza
≥65,612463,pizza
I can think of two ways to do this.
1) Create an outer pie chart with a single pie segment for each class, then create addition pie charts inside each outer pie segment. The inner pie charts need to specify a start/end angle for their piechart (which matches the outer pie segment for that class). Here's a example on Plunker: http://plnkr.co/edit/cblP4d?p=preview
2) Instead of translating each datapoint individually, create an array of translations for each class. Each class's translation uses the centroid for that class. When you translate each arc, use the translation array (referenced by class).
I am new to 3Djs and today I've built a geographic map of England from a JSON (TopoJSON) to be used as a chart.
What I would need now is to color fill the resulting SVG dynamically based on a percentage (it's an achievement, like how much of the country you have traveled) but I can't find which technique should I use to accomplish that.
EDIT: just a color is used to fill the map. What changes is what percentage of the map area is color filled (eg. 30% (achieved) red 70% white (not achieved), 50% red 50% white and so on). (My reputation doesn't allow me to post images.)
This is the code I've used to build the map
var width = 120,
height = 145;
var projection = d3.geo.albers()
.center([0, 55.4])
.rotate([4.4, 0])
.parallels([50, 60])
.scale(1200 * .6)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection)
.pointRadius(2);
var svg = d3.select(".target-distribution").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("uk.json", function(error, uk) {
svg.selectAll(".subunit")
.data(topojson.feature(uk, uk.objects.subunits).features)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", path);
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a, b) { return a !== b && a.id !== "IRL"; }))
.attr("d", path)
.attr("class", "subunit-boundary");
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a, b) { return a === b && a.id === "IRL"; }))
.attr("d", path)
.attr("class", "subunit-boundary IRL");
svg.append("path")
.datum(topojson.feature(uk, uk.objects.places))
.attr("d", path)
.attr("class", "place");
svg.selectAll(".place-label")
.data(topojson.feature(uk, uk.objects.places).features)
});
What I did to accomplish my goal:
Clone the SVG map (with the jQuery clone() method)
Overlaid the clone on the original (via position: absolute)
Used the CSS clip property on the clone to only show the area I needed.
Try checking out this useful example from d3's creator:
This shows a map with geo data from a TopoJSON file and population data loaded separately from a .csv file, in which the population data determines the fill color:
http://bl.ocks.org/mbostock/4060606