I'm trying to recreate a simple example as seen here:
https://www.d3-graph-gallery.com/graph/barplot_stacked_basicWide.html
Here is a simple version of my code (note that the data has different labels but is structurally identical):
var data = [
{period:'t1', fmc1:+10, fmc2:9, fmc3:6, fmc4:5, fmc5:2},
{period:'t2', fmc1:+11, fmc2:8, fmc3:6, fmc4:4, fmc5:3},
{period:'t3', fmc1:+12, fmc2:10, fmc3:7, fmc4:5, fmc5:3},
];
var groups = d3.map(data, function(d){return(d.period)}).keys()
var subgroups = data.columns.slice(1);
var stackedData = d3.stack()
.keys(subgroups)
(data);
var yScale = d3.scaleLinear()
.domain([0,80])
.range([height,0]);
var xScale = d3.scaleBand()
.domain(groups)
.range([0,width])
.padding([.2]);
var colorScale = d3.scaleOrdinal()
.domain(subgroups)
.range(["#003366","#366092","#4f81b9","#95b3d7","#b8cce4","#e7eef8","#a6a6a6","#d9d9d9","#ffffcc","#f6d18b","#e4a733","#b29866","#a6a6a6","#d9d9d9","#e7eef8","#b8cce4","#95b3d7","#4f81b9","#366092","#003366"]);
graphGroup.append("g")
.selectAll("g")
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) { return colorScale(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return xScale(d.data.period); })
.attr("y", function(d) { return yScale(d[1]); })
.attr("height", function(d) { return yScale(d[0]) - yScale(d[1]); })
.attr("width",xScale.bandwidth())
<script src="https://d3js.org/d3.v5.min.js"></script>
I'm confronted with an error that reads:
TypeError: Cannot read property 'slice' of undefined # line 47.
That line is:
var subgroups = data.columns.slice(1);
I confirmed in the console that data.columns is indeed, undefined. This begs the question as to what the author of the d3-graph-gallery is trying to do. I'm convinced there is an explanation, and the visual seems to work just fine on the website. Initial troubleshooting on my end didn't turn up much, doesn't seem to be a version issue.
Question
If data.columns.slice(1) is a conventional approach to constructing a stacked bar chart (as the tutorial would lead one to believe) why isn't it working for me? I'm hoping there is a quick fix or something that can be tweaked easily so that I can follow the rest of the tutorial without diverging too much in terms of syntax and methodology.
data is an array. JavaScript arrays can be given properties, but it's not really considered good practice, because you're "sneaking" stuff into the object.
In this case, data.columns is set by the CSV reader, which you don't use here. The following is equivalent to the value of data.columns:
Object.keys(data[0])
Related
I have a D3.js line chart that updates when the user chooses a new country. Overall I'm satisfied with the way my chart updates and it looks very clean when two conditions are met:
There is no missing data for either country
Country one has no missing data but country two has missing data.
Here is the problem scenario: country one has missing data but country two has no missing data.
If country one has missing data on the left, when the chart updates, the new segments of the line get appended on the right side (usually on top of the existing segments of the line) and then gradually slide over to the left. It's a very ugly transition and words might not suffice so here are a few pictures.
Country One: Missing data on left
Transition to Country Two (which has no missing data)
Country Two: No missing data
I have a hunch about the source of the problem and how I might be able to fix it.
A potential solution?
This line chart by Mike Bostock is interesting. It has missing data but it uses linear interpolation to fill in the gaps. Would I be able to solve my problem by filling in the gaps like this? I tried implementing this solution but I just can't get the second line for the missing data to appear.
https://observablehq.com/#d3/line-with-missing-data
The Code for my Line Chart
I have two separate functions for my line chart. The render function initializes the chart and then I have to use the update function to transition to new data. I know that's a bit weird but I'm relatively inexperienced with D3.js and I can't get it to work with one function. For the That is another potential problem in and of itself, but I don't think it's causing the botched transition, is it?
For the sake of brevity, I only included the parts where the line gets appended and where it gets updated.
function render(url){
d3.json(url).then(function (data) {
data.forEach(function (d) {
d.Year = +d.Year;
d.Value = +d.Value / 1000;
});
...
...
omitted code
...
...
svg.append("path")
.datum(data)
.attr('id','mainline')
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 3)
.attr("d", d3.line()
.defined(d => !isNaN(d.Value))
.x(function (d) {
return x(d.Year)
})
.y(function (d) {
return y(d.Value)
})
);
}
function update(url) {
//Reading in new data and updating values
d3.json(url).then(function (data) {
data.forEach(function (d) {
d.Year = +d.Year;
d.Value = +d.Value / 1000;
});
...
...
omitted code
...
...
var lines = svg.selectAll("#mainline").datum(data).attr("class","line");
lines.transition().ease(d3.easeLinear).duration(1500)
.attr("d", d3.line()
.defined(d => !isNaN(d.Value))
.x(function (d) {
return x(d.Year)
})
.y(function (d) {
return y(d.Value)
})
);
Any help would be greatly appreciated. Thanks!
Similar to question here, I've been working on the same thing as in the last question. My question now is similar to the link, but is more about the implementation. When I run my code, I get an error in my log that says "TypeError: x.ticks is not a function". This is the piece of code it refers to:
svg.selectAll("g.grid")
.data(y.ticks()).enter()
.append("g").attr("class", "grid")
.selectAll("rect")
.data(x.ticks())
.enter()
.append("rect")
.attr("x", function(d, i, j) {
return xScale(j);
})
.attr("y", function(d, i, j) {
return yScale(i);
})
.attr("width", xScale.rangeBand())
.attr("height", yScale.rangeBand())
.attr("fill", function(d, i) {
return ((i) % 2) == 1 ? "green" : "blue";
});
This code works perfectly well in this fiddle, but gives me an error while running it in my code, as seen here. Any help?
In your example you are trying to call ticks on an ordinal scale instead of a linear scale. Utilising rangeBoundBands, like in the question you've linked to, is probably the way to go.
Your problem is you're using an ordinal scale. D3 makes you use a linear scale if you want to call .ticks(), otherwise it throws an error
So I've been trying to get my d3.js bar chart to transition to different values when a button is pressed. Though, at the moment some elements seem to be adding, but extremely wide and all over the place, as well as the previous elements not being removed.
This is my code:
function updateData(time) {
timeValue = time;
// Get the data again
d3.tsv(timeValue + ".tsv", function(error, data) {
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
// Scale the range of the data again
x.domain(d3.extent(data, function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
//Selecting Data
var bars = svg.selectAll("rect")
.data(data);
//Removing
bars.exit().remove("rect");
//Changes
bars.enter().append('rect')
.attr("class", "bar")
bars.attr('x', function(d) { return x(d.letter); })
.attr('width', x.rangeBand())
.attr('y', function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
//var svg = d3.select("div.innerScreen2").transition();
});
Now I've looked at similar questions asked and tried to apply the solutions, but nothing seems to get removed or change :/ Maybe I have code in the wrong place? Any help would be much appreciated
There are two problems to solve in your question. The first issue is related to the update pattern that some elements are added and some not. Do you have an unique identifier in your data set? If yes, you can use a function within the update process:
// Join new data with old elements, if any.
var text = svg.selectAll("text")
.data(data, function(d) { return d.id; });
Source:http://bl.ocks.org/mbostock/3808234
The second issue is related to your x/y positions and may depend on your underlying data. An data excerpt or debugging information are required to solve this issue.
I have a map of the USA that I'm trying to display lat/lon points over. I've mashed together a few examples to get this far, but I've hit a wall. My points are in a csv file, which I'm not sure how to upload here, but it's just 65,000 rows of number pairs. For instance 31.4671154,-84.9486771.
I'm mostly following the example from Scott Murray's book here.
I'm using the Albers USA projection.
var projection = d3.geo.albersUsa()
.scale(1200)
.translate([w / 2, h / 2]);
And setting up the landmarks as an svg group appended to the map container.
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.on("click", stopped, true);
svg.append("rect")
.attr("class", "background")
.attr("width", w)
.attr("height", h)
.on("click", reset);
var g = svg.append("g");
var landmarks = svg.append("g")
I read the data and try to set circles at each lat/lon point.
d3.csv("./public/assets/data/landmark_latlon_edited.csv", function(error, latlon){
console.log(latlon);
landmarks.selectAll("circle")
.data(latlon)
.enter()
.append("circle")
.attr({
'fill': '#F00',
'r': 3
})
.attr('cx', function(d){
return projection([d.lon, d.lat][0]);
})
.attr('cy', function(d){
return projection([d.lon, d.lat])[1];
})
.style({
'opacity': .75
});
});
Now, the problem is that the cx property is not receiving a value. When viewed in the inspector the circles don't show a cx, and, indeed, appear in the svg at the appropriate y values, but in a stacked column at x=0.
<circle fill="#F00" r="3" cy="520.8602676002965" style="opacity: 0.75;"></circle>
I found an old issue I thought might be related here which states that the projection method will return null if you try to feed it values outside of its normal bounding box. I opened the csv in Tableau and saw a couple values that were in Canada or some U.S. territory in the middle of the Pacific (not Hawaii), and I removed those, but that didn't solve the problem.
I'm decidedly novice here, and I'm sure I'm missing something obvious, but if anyone can help me figure out where to look I would greatly appreciate it. Lots of positive vibes for you. If I can add anything to clarify the problem please let me know.
Thanks,
Brian
I had the same problem when I updated to d3 v3.5.6. Here is what I did to check for null values, so that you don't try to access the [0] position of null:
.attr("cx", function(d) {
var coords = projection([d.lon, d.lat]);
if (coords) {
return coords[0];
}
})
I'm sure there is a cleaner way to do this, but it worked for me.
You have a little error in your function generating cx values which messes it all up. It's just one parenthesis in the wrong place:
.attr('cx', function(d){
return projection([d.lon, d.lat][0]);
})
By coding [d.lon, d.lat][0] you are just passing the first value of the array, which is d.lon, to the projection and are returning the result of projection() which is an array. Instead, you have to place the [0] outside the call of projection() because you want to access the value it returned. Check your function for cy where you got things right. Adjusting it as follows should yield the correct values for cx:
.attr('cx', function(d){
return projection([d.lon, d.lat])[0];
})
This is a followup of this question on SO: D3 - issues on first transition after initialization.
I have studies and implemented the key functions as explained by Mike and Jason in their answers. I also found this blog post useful http://knowledgestockpile.blogspot.com/2012/01/understanding-selectall-data-enter.html
I have used a key like
.data(svg.chartData, function(d) { return d.idSeries;})
to map one-to-one data and index.
However, I do not get the expected result and data points swap on redraw. Please have a look at http://jsbin.com/avoced/8/edit, specifically to the following relevant functions:
Chart initialization:
svg.series = svg.selectAll(".series").data(svg.chartData, function(d) { return d.idSer;})
svg.series.enter().append("g").classed("series", true)
svg.rects = svg.series.selectAll("rect").data(Object, function(d) { return d.x;})
svg.rects.enter().append("rect")
Redraw:
svg.series = svg.selectAll(".series").data(svg.chartData, function(d) { return d.idSer;})
svg.series.enter().append("g").classed("series", true)
svg.series.exit().remove()
svg.rects = svg.series.selectAll("rect").data(Object, function(d) { return d.x;})
svg.rects.enter().append("rect")
svg.rects.exit().remove()
Scales:
x = d3.scale.ordinal()
.domain(svg.pointsNames, function(d) { return d;})
.rangeRoundBands([0,width], .1);
What am I missing here?
Many thanks