I am really new into d3 and js. I wanted to make a choropleth map of the Us showing the different states. However something is not working since i don't get any path appended to my g object. Also the console.log(states) doesn't work. I'm pretty sure this is a newbie error and due to my lacks in js/d3. However i think i must use Promise.all since i want to add another csv file later on.
Thank you in advance!
Developer Tool
Below is the Code
//Standard Method to start our d3 visualization. For Maps the margin is not that important,but we make best practices
(margin = { top: 0, left: 0, right: 0, right: 0, bottom: 0 }),
(height = 400 - margin.top - margin.bottom),
(width = 800 - margin.left - margin.right);
var svg = d3
.select("#map")
.append("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.left + margin.right)
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
//Read in our us Data
Promise.all([d3.json("us.json")]).then(([data]) => {
console.log(data)
});
var projection = d3
.geoAlbersUsa()
.translate([width / 2, height / 2])
.scale(100);
//create path of projection so that we can work with latidudes and longitudes
var path = d3.geoPath().projection(projection);
function ready(error, data) {
console.log(data)
var states = topojson.feature(data, data.objects.states).features;
console.log(states);
svg
.selectAll(".state")
.data(states)
.enter()
.append("path")
.attr("class", "state")
.attr("d", path);
}
Related
I am trying to recreate Nadia Bremer's chord diagram for storytelling with data from https://gist.github.com/nbremer/94db779237655907b907
She accesses the chord.groups element when creating the g element:
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", function(d) {return "group " + NameProvider[d.index];});
This works fine in v3 of D3. Here's a jsfiddle that works
However, when I try the same thing in D3 v7, chord.groups becomes undefined. Here is a jsfiddle with the v7 variant which gives an error when accessing chord.groups
The d3.chord() reference states that "The chords array also defines a secondary array of length n, chords.groups, where each group represents the combined outflow for node i..."
I've also found several examples on Observable which access chord.groups and they run fine. What am I doing wrong?
The error is very subtle, and here I'd blame the docs for lack of clarity, not you.
The problem is that you passed the data to the chord generator like this:
var chord = d3.chord(matrix)
But d3.chord() doesn't accept arguments. Instead, it returns a function which accepts the arguments. Thus, it should be:
var chord = d3.chord()(matrix)
Alternatively, defining the generator and then passing the data:
const chord = d3.chord();
const chordLayout = chord(matrix);
As you can see, it's a bit different. The confusion is maybe worsened by the fact that some D3 methods, like scales, accept data arguments like that, e. g. d3.scaleLinear(domain, range).
Most of D3 generators are like this. For instance, using a common line generator...
const lineGenerator = d3.line();
You get the path d attribute using:
lineGenerator(data);
Which is the same of d3.line()(data), but it's not the same of d3.line(data).
Here's your working v7 version:
var NameProvider = ["Apple", "HTC", "Huawei", "LG", "Nokia", "Samsung", "Sony", "Other"];
var matrix = [
[9.6899, 0.8859, 0.0554, 0.443, 2.5471, 2.4363, 0.5537, 2.5471], /*Apple 19.1584*/
[0.1107, 1.8272, 0, 0.4983, 1.1074, 1.052, 0.2215, 0.4983], /*HTC 5.3154*/
[0.0554, 0.2769, 0.2215, 0.2215, 0.3876, 0.8306, 0.0554, 0.3322], /*Huawei 2.3811*/
[0.0554, 0.1107, 0.0554, 1.2182, 1.1628, 0.6645, 0.4983, 1.052], /*LG 4.8173*/
[0.2215, 0.443, 0, 0.2769, 10.4097, 1.2182, 0.4983, 2.8239], /*Nokia 15.8915*/
[1.1628, 2.6024, 0, 1.3843, 8.7486, 16.8328, 1.7165, 5.5925], /*Samsung 38.0399*/
[0.0554, 0.4983, 0, 0.3322, 0.443, 0.8859, 1.7719, 0.443], /*Sony 4.4297*/
[0.2215, 0.7198, 0, 0.3322, 1.6611, 1.495, 0.1107, 5.4264] /*Other 9.9667*/
];
/*Sums up to exactly 100*/
var colors = ["#C4C4C4", "#69B40F", "#EC1D25", "#C8125C", "#008FC8", "#10218B", "#134B24", "#737373"];
/*Initiate the color scale*/
var fill = d3.scaleOrdinal()
.domain(d3.range(NameProvider.length))
.range(colors);
/*//////////////////////////////////////////////////////////
/////////////// Initiate Chord Diagram /////////////////////
//////////////////////////////////////////////////////////*/
var margin = {
top: 30,
right: 25,
bottom: 20,
left: 25
},
width = 650 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom,
innerRadius = Math.min(width, height) * .39,
outerRadius = innerRadius * 1.04;
/*Initiate the SVG*/
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform", "translate(" + (margin.left + width / 2) + "," + (margin.top + height / 2) + ")");
var chord = d3.chord()
.sortSubgroups(d3.descending) /*sort the chords inside an arc from high to low*/
.sortChords(d3.descending)(matrix);
/*//////////////////////////////////////////////////////////
////////////////// Draw outer Arcs /////////////////////////
//////////////////////////////////////////////////////////*/
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", function(d) {
return "group " + NameProvider[d.index];
});
g.append("svg:path")
.attr("class", "arc")
.style("stroke", function(d) {
return fill(d.index);
})
.style("fill", function(d) {
return fill(d.index);
})
.attr("d", arc)
.style("opacity", 0)
.transition().duration(1000)
.style("opacity", 0.4);
#chart rect {
fill: steelblue;
}
#chart text {
fill: white;
font: 10px Helvetica;
text-anchor: end;
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg id="chart"></svg>
I am currently trying to draw a map of the US with the counties-albers-10m.json file found on the topojson repo. I initially got a solid rectangle and, after changing fill to none, I am getting specks here and there. Going through stack, I found that the winding order may be wrong so I incorporated turf.js, but nothing is really changing. Here is the code:
var margin = {top: 0, left: 0, right: 0, bottom: 0},
height = 600 - margin.top - margin.bottom,
width = 1200 - margin.left - margin.right;
var svg = d3.select("#map")
.append("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.left + margin.right)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top +")");
d3.json("counties-albers-10m.json").then(function(data) {
console.log(data);
var projection = d3.geoAlbersUsa();
var path = d3.geoPath()
.projection(projection);
var counties = topojson.feature(data, data.objects.counties).features
console.log(counties)
counties.forEach(function(feature) {
feature.geometry = turf.rewind(feature.geometry, {reverse:true});
})
svg.selectAll(".county")
.data(counties)
.enter().append("path")
.attr("class", "county")
.attr("fill", "none")
.attr("stroke", "black")
.attr("d", path);
})
The dreaded black box
I am trying to read a .json file into an HTML file using d3.
When I use one .json file -- for the entire US -- the map shows up fine. However, when I tried switching out the US file for a state of Wisconsin .json file it shows up like this...
Anyone know what the issue could be?
Here's the code:
<script>
var margin = {top: 20, left: 20, bottom: 20, right: 20}
height = 600-margin.top - margin.bottom,
width = 960 - margin.left - margin.right;
var svg2 = d3.select("#map2").append("svg")
.attr("height", height)
.attr("width", width)
.append("g")
.attr("transform", "translate(" + margin.left + "," +
margin.right + ")");
d3.queue()
.defer(d3.json, "wiscCountiesGeneralized.json")
.await(ready);
var projection2 = d3.geoAlbers()
.translate([width/2, height/1.5])
.scale(75)
var path2 = d3.geoPath()
.projection(projection2)
function ready (error, data) {
console.log(data)
var counties = topojson.feature(data,
data.objects.counties).features
console.log(counties)
svg2.selectAll(".counties")
.data(counties)
.enter().append("path")
.attr("class", "counties")
.attr("d", path2)
}
</script>
I'm trying to implement box plots as part of a data visualization interface that uses d3 and AngularJS. I'm working with this box plot package: https://bl.ocks.org/mbostock/4061502.
However, I can't figure out which part of the sample code controls the positioning of the box plots. In the example, the five box plots are arranged sequentially. When I try to generate my plots, they all appear on top of each other.
Here is the code that I'm using to generate the box plots:
boxplots = svg.selectAll("svg")
.data(boxPlotData)
.enter().append("svg")
.attr("class", "box")
.attr("width", boxWidth + margin.left + margin.right)
.attr("height", boxHeight + margin.bottom + margin.top)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart);
Here's the code for how my svg canvas is created. This is done in an angular directive:
template:"<svg width='825' height='600'></svg>",
link: function($scope, elem){
var d3 = $window.d3;
var rawSvg=elem.find('svg'); // this is the svg created in the template
var width = rawSvg[0].attributes[0].value;
var height = rawSvg[0].attributes[1].value;
var svg = d3.select(rawSvg[0]);
Edit: not perfect yet but getting there:
What you need is an ordinal scale to position the svg-elements for the boxes within the parent svg. Assuming width represents the width of your parent svg element and data is an array of your data elements, you can use this to create the scale:
const x = d3.scaleBand()
.range( [0, width] )
.domain( data.map( (el,i) => i ) );
Within the svg creation you can now use
boxplots = svg.selectAll("svg")
.data(boxPlotData)
.enter().append("svg")
.attr( "x", (d,i) => x(i) ) // this is added
.attr("class", "box")
.attr("width", boxWidth + margin.left + margin.right)
.attr("height", boxHeight + margin.bottom + margin.top)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart);
PS: This assumes you use v4 of d3js. The syntax in v3 for the scale is different.
PPS: I currently can not test the code, but it should work like described.
I think I'm almost there. I have a working version for a single line that updates dynamically, but am having trouble getting to the multi-line part. I believe it has something to do with the way the data is being filtered at _selection.each. Not sure the best way to proceed from here though. The example found here (Drawing Multiple Lines in D3.js) seems to deal with this without much work.
Here is the jsfiddle for this, but the code is represented below as well:
http://jsfiddle.net/seoulbrother/NhK43/
Thanks in advance. Also, would love to hear best practices regarding this as well.
So if I have a page with a matrix where each row represents a time-series:
<html>
<body>
<div id="container"><div id="viz"></div></div>
</body>
</html>
<script type="text/javascript">
var matrix = [
[23,12,44,22,12,33,14,76,45,55,66,55],
[33,22,11,88,32,63,13,36,35,51,26,25]
];
</script>
I call this using:
mult_line = d3.graph.line(500, 250, "#viz");
d3.select("#container").datum(matrix).call(mult_line);
where d3.graph.line is:
d3.graph.line = function module(w, h, id) {
var svg;
var margin = {top: 10, right: 20, bottom: 20, left: 40},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
function chart(_selection) {
_selection.each(function(_data) {
console.log(_data);
var x = d3.scale.linear().domain([1, _data.length]).range([0, width]);
var y = d3.scale.linear().domain([0, 100]).range([height, 0]);
var xAxis = d3.svg.axis().scale(x).ticks(5).orient("bottom");
var yAxis = d3.svg.axis().scale(y).ticks(5).orient("left");
var line = d3.svg.line()
.x(function(d,i) { return x(i+1); })
.y(function(d) { return y(d); });
if (!svg){
svg = d3.select(id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("path")
.attr("class","line")
.attr("d", line(_data));
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y-axis")
.call(yAxis);
}
var line_m = d3.selectAll("path.line")
.data(_data);
line_m.transition()
.duration(1000)
.attr('d', line(_data));
line_m.enter().append("path")
.attr("d",line(_data));
update_axis();
function update_axis() {
d3.select(".x-axis").call(xAxis);
}
});
}
return chart;
}
I think there may be varying opinions about this, but I wouldn't use selection.datum() as the mechanism of passing data into the chart component. Instead, I would add a .data() setter method as a property of the multiline component. I would do the same for width, height, etc. This is the way d3's components are implemented (for example, check out the source code for the d3.svg.axis). Then your chart creation would look like this:
mult_line = d3.graph.line()
.width(500)
.height(250)
.id("#viz")
.data(matrix);
d3.select("#container").call(mult_line);
You can refine this so that there are 2 data-setting methods of multiline: .data() and .multilineData(). The first would expect data for a single line (an array) and the 2nd would expect an array of arrays (multiline). Internally, they'd always be represented as an array of arrays. That way, the chart drawing code always expects to draw multiline data, except sometimes the data has just one line.