d3 Update stacked bar graph using selection.join - javascript

I am creating a stacked bar graph which should update on changing data and I want to use d3v5 and selection.join as explained here https://observablehq.com/#d3/learn-d3-joins?collection=#d3/learn-d3.
When entering the data everything works as expected, however the update function is never called (that's the console.log() for debugging.).
So it looks like it is just entering new data all the time.
How can I get this to work as expected?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
}
.y.axis .domain {
display: none;
}
</style>
</head>
<body>
<div id="chart"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
let xVar = "year";
let alphabet = "abcdef".split("");
let years = [1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003];
let margin = { left:80, right:20, top:50, bottom:100 };
let width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
let g = d3.select("#chart")
.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 + ")");
let color = d3.scaleOrdinal(["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f"])
let x = d3.scaleBand()
.rangeRound([0, width])
.domain(years)
.padding(.25);
let y = d3.scaleLinear()
.rangeRound([height, 0]);
let xAxis = d3.axisBottom(x);
let yAxis = d3.axisRight(y)
.tickSize(width)
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
g.append("g")
.attr("class", "y axis")
.call(yAxis);
let stack = d3.stack()
.keys(alphabet)
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone);
redraw(randomData());
d3.interval(function(){
redraw(randomData());
}, 1000);
function redraw(data){
// update the y scale
y.domain([0, d3.max(data.map(d => d.sum ))])
g.select(".y")
.transition().duration(1000)
.call(yAxis);
groups = g.append('g')
.selectAll('g')
.data(stack(data))
.join('g')
.style('fill', (d,i) => color(d.key));
groups.selectAll('.stack')
.data(d => d)
.attr('class', 'stack')
.join(
enter => enter.append('rect')
.data(d => d)
.attr('x', d => x(d.data.year))
.attr('y', y(0))
.attr('width', x.bandwidth())
.call(enter => enter.transition().duration(1000)
.attr('y', d => y(d[1]))
.attr('height', d => y(d[0]) - y(d[1]))
),
update => update
.attr('x', d => x(d.data.year))
.attr('y', y(0))
.attr('width', x.bandwidth())
.call(update => update.transition().duration(1000)
.attr('y', d => y(d[1]))
.attr('height', d => y(d[0]) - y(d[1]))
.attr(d => console.log('update stack'))
)
)
}
function randomData(data){
return years.map(function(d){
let obj = {};
obj.year = d;
let nums = [];
alphabet.forEach(function(e){
let num = Math.round(Math.random()*2);
obj[e] = num;
nums.push(num);
});
obj.sum = nums.reduce((a, b) => a + b, 0);
return obj;
});
}
</script>
</body>
</html>
Here is it in a working jsfiddle: https://jsfiddle.net/blabbath/yeq5d1tp/
EDIT: I provided a wrong link first, here is the right one.
My example is heavily based on this: https://bl.ocks.org/HarryStevens/7e3ec1a6722a153a5d102b6c42f4501d

I had the same issue a few days ago. The way I did it is as follows:
We have two .join, the parent one is for the stack and the child is for the rectangles.
In the enter of the parent join, we call the updateRects in order to draw the rectangles for the first time, this updateRects function will do the child .join, this second join function will draw the rectangles.
For the update we do the same, but instead of doing it in the enter function of the join, we do it in the update.
Also, my SVG is structured in a different way, I have a stacks groups, then I have the stack group, and a bars group, in this bars group I add the rectangles. In the fiddle below, you can see that I added the parent group with the class stack.
The two functions are below:
updateStack:
function updateStack(data) {
// update the y scale
y.domain([0, d3.max(data.map((d) => d.sum))]);
g.select(".y").transition().duration(1000).call(yAxis);
const stackData = stack(data);
stackData.forEach((stackedBar) => {
stackedBar.forEach((stack) => {
stack.id = `${stackedBar.key}-${stack.data.year}`;
});
});
let bars = g
.selectAll("g.stacks")
.selectAll(".stack")
.data(stackData, (d) => {
return d.key;
});
bars.join(
(enter) => {
const barsEnter = enter.append("g").attr("class", "stack");
barsEnter
.append("g")
.attr("class", "bars")
.attr("fill", (d) => {
return color(d.key);
});
updateRects(barsEnter.select(".bars"));
return enter;
},
(update) => {
const barsUpdate = update.select(".bars");
updateRects(barsUpdate);
},
(exit) => {
return exit.remove();
}
);
}
updateRects:
function updateRects(childRects) {
childRects
.selectAll("rect")
.data(
(d) => d,
(d) => d.id
)
.join(
(enter) =>
enter
.append("rect")
.attr("id", (d) => d.id)
.attr("class", "bar")
.attr("x", (d) => x(d.data.year))
.attr("y", y(0))
.attr("width", x.bandwidth())
.call((enter) =>
enter
.transition()
.duration(1000)
.attr("y", (d) => y(d[1]))
.attr("height", (d) => y(d[0]) - y(d[1]))
),
(update) =>
update.call((update) =>
update
.transition()
.duration(1000)
.attr("y", (d) => y(d[1]))
.attr("height", (d) => y(d[0]) - y(d[1]))
),
(exit) =>
exit.call((exit) =>
exit
.transition()
.duration(1000)
.attr("y", height)
.attr("height", height)
)
);
}
Here is an update jsfiddle https://jsfiddle.net/5oqwLxdj/1/
I hope it helps.

It is getting called, but you can't see it. Try changing .attr(d => console.log('update stack')) to .attr(console.log('update stack')).

Related

How to get data labels to show in D3 stacked bar chart?

I'm trying to get data labels to show inside of each bar of my stacked bar chart. When I view source, I can see the <text> elements in each bar with the correct number, but they aren't visible in the bar itself
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<div id="legend"></div>
<style>
</style>
<script>
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 20, left: 50 },
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Parse the Data
d3.csv("https://raw.githubusercontent.com/JakeRatliff/dh-valve-data/main/Valves%20Data%20-%20Sheet1.csv", function(data) {
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1)
subgroups.pop()
console.log(subgroups)
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function(d) { return (d.Year) }).keys()
console.log(groups)
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSizeOuter(0));
// Add Y axis
var y = d3.scaleLinear()
//.domain([0, 60])
.domain([0, d3.max(data, function(d) { return +d.Total; })])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#00539B', '#E0750B'])
//stack the data? --> stack per subgroup
var stackedData = d3.stack()
.keys(subgroups)
(data)
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in the stack data = loop key per key = group per group
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
// enter a second time = loop subgroup per subgroup to add all rectangles
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.attr("class", "bar")
.append("text")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.text(function(d) { return d[1] })
})
</script>
Here is a codepen if that is preferred:
https://codepen.io/jake2134/pen/QWMQJOB
Thanks in advance for any help. I've Google around but the results seem to be outdated.
Use a variable to create a group, then append twice to it.
var bar_groups = svg.append("g")
.selectAll("g")
// Enter in the stack data = loop key per key = group per group
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
var bars = bar_groups.selectAll("g")
// enter a second time = loop subgroup per subgroup to add all rectangles
.data(function(d) { return d; })
.enter().append("g")
bars.append('rect')
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.attr("class", "bar")
bars.append("text")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.text(function(d) { return d[1] })

Creating d3 bar chart

I have a csv file that contains the top songs on Spotify from 2010-2019. The attributes of each song have things like genre, artist, year, popularity, bpm, etc. I wanted to create a bar graph that has the year on the x-axis, popularity on the y-axis, and then each genre represents a different color bar on the chart. I have attached the csv file I am using, any help would be appreciated.
link to csv:
https://github.com/moonpieluvincutie/Spotify
I'll assume you are looking for stacked bars but if not, un-stacked would be the easier approach. Using this example as a base, we need to first transform the data to represent the configuration you described in your post. Essentially we need to group by year, genre and transform it in a way that is suitable for stacking. For example something like this...
const data = [
{ month: new Date(2015, 0, 1), apples: 3840, bananas: 1920, cherries: 960, dates: 400 },
];
or in the case of your data something like this...
const data = [
{ year: 2015, genre1: popularity1, genre2: popularity1, ...rest },
];
Fortunately d3 offers a plethora of transformations for us to use.
Note: I am using d3 v6 not v4 (like the example) in the following code, the API is slightly different but essentially the same concepts.
I created the following working snippet for you to review. I used d3.rollups to group the data in the way described above. I would advise you to log each transform to see what the new structure looks like. I took the d3.mean of the popularity if there was more than 1 song per genre per year. Once you have the structure in the shape defined above, we pass that to d3.stack to format the data with correct y0 and y1 values for each series (aka group or genre).
Finally, by adding the stacked dataset to the rendering logic from the base example with a few tweaks, you get something like this below. Sorry, it's a bit unpolished but this should give you a good start point to iterate from.
const svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Set x, y and colors
const x = d3.scaleBand()
.rangeRound([10, width - 10])
.padding(0.2)
const y = d3.scaleLinear()
.range([height, 0]);
const z = d3.scaleOrdinal(d3.schemePaired);
d3.csv('https://raw.githubusercontent.com/moonpieluvincutie/Spotify/main/top10s%20(version%201).xlsb.csv', (d) => ({
year: +d.year,
pop: d.pop,
genre: d['top genre'],
})).then((data) => {
// Transform the data into groups by genre and year
const groupedDataset = d3.rollups(data, v => d3.mean(v, d => d.pop), d => d.year, d => d.genre)
const flattenedDataset = groupedDataset.map(([year, values]) => {
return {
year,
...values.reduce((acc, [genre, pop]) => {
acc[genre] = pop;
return acc;
}, {}),
}
});
const genres = d3.rollups(data, () => null, d => d.genre).map(([genre]) => genre);
// Stack grouped data
const dataset = d3.stack()
.keys(genres)
.value((d, key) => d[key] ?? 0)
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone)
(flattenedDataset)
// Assign x, y and z (aka genre group) domains
x.domain(groupedDataset.map(([year]) => year));
y.domain([0, d3.max(
dataset, (d) => {
return d3.max(d, ([y]) => y)
}
)]).nice();
z.domain(genres);
const yAxis = d3.axisLeft()
.scale(y)
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat( (d) => { return d } );
const xAxis = d3.axisBottom()
.scale(x)
g.append("g")
.selectAll("g")
.data(dataset)
.enter().append("g")
.attr("fill", (d) => z(d.key))
.selectAll("rect")
.data((d) => d)
.enter().append("rect")
.attr("x", (d) => x(+d.data.year))
.attr("y", (d) => y(d[1]))
.attr("height", (d) => y(d[0]) - y(d[1]))
.attr("width", x.bandwidth());
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
g.append("g")
.attr("class", "axis")
.call(yAxis)
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Popularity");
const legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(genres.slice().reverse())
.enter().append("g")
.attr("transform", (d, i) => "translate(0," + i * 20 + ")");
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", z);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text((d) => d);
})
.axis .domain {
display: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script src="https://d3js.org/d3.v6.min.js" charset="utf-8"></script>
</head>
<body>
<!-- chart appended to svg in javaScript -->
<svg width="960" height="500"></svg>
</body>
</html>

How to get d3 treemap cell text to wrap and not overflow other cells

The text for the cells in my d3 treemap don't wrap and overflow the other cells. This my project
I want the text to look like this project. I've looked at their code (and many others) but I can't get it to work in my project.
The problem area is:
svg.append('text')
.selectAll('tspan')
.data(root.leaves())
.enter()
.append('tspan')
.attr("x", (d) => d.x0 + 5)
.attr("y", (d) => d.y0 + 20)
.text( (d) => d.data.name) //.html( (d) => d.data.name.replace(/\s/g, "<br>"))
.attr("font-size", "0.6em")
.attr("fill", "white");
I tried using .html rather than .text as in the comment. In Safari and Chrome the text still overflowed the cells. In Firefox only the first word of the movie name was displayed.
We have two options to display the text ina similar way to the example you provide.
The first and easisest approach is to keep your code structure and make a similar procedure to split the text as the example provided:
d.data.name.split(/(?=[A-Z][^A-Z])/g)
So lets change your code a little bit:
svg.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.selectAll('tspan')
.data(d => {
return d.data.name.split(/(?=[A-Z][^A-Z])/g) // split the name of movie
.map(v => {
return {
text: v,
x0: d.x0, // keep x0 reference
y0: d.y0 // keep y0 reference
}
});
})
.enter()
.append('tspan')
.attr("x", (d) => d.x0 + 5)
.attr("y", (d, i) => d.y0 + 15 + (i * 10)) // offset by index
.text((d) => d.text)
.attr("font-size", "0.6em")
.attr("fill", "white");
This should accomplish the desired display. We have to take into account that labels are a very difficult to position and display in way which avoids overlapping since it would require a little more computation at build time.
The second approach is to change a little the code structure and create cells, pretty much like the example provided:
const cell = svg.selectAll('g')
.data(root.leaves())
.enter()
.append('g') // create a group for each cell / movie
.attr('transform', d => `translate(${d.x0},${d.y0})`) // let the group element handle the general positioning
.on('mousemove', d => {
//...
})
.on('mouseout', d => {
//...
});
cell.append('rect') // append rect for each cell / movie
.attr('id', d => d.data.id)
.attr('class', 'tile')
.attr('data-name', d => d.data.name)
.attr('data-value', d => d.data.value)
.attr('data-category', d => d.data.category)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', d => color(d.data.category));
cell.append('text') // append text node for each cell / movie
.selectAll('tspan')
.data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g)) // split the name and use that as data to create indiviual tspan elements
.enter()
.append('tspan') // append tspan node for each element of the string which got split
.attr('font-size', '8px')
.attr('x', 4)
.attr('y', (d, i) => 13 + 10 * i) // offset the y positioning with the index of the data
.text(d => d);
CodePen for approach 1
CodePen for approach 2
Full code for approach 1:
// !! IMPORTANT README:
// You may add additional external JS and CSS as needed to complete the project, however the current external resource MUST remain in place for the tests to work. BABEL must also be left in place.
const w = 960;
const h = 600;
const padding = 60;
const svg = d3.select("#container").append("svg")
.attr("width", w).attr("height", h);
const legendsvg = d3.select("#legend").append("svg")
.attr("width", 960).attr("height", 50);
const legendPadding = 10;
d3.json("https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json")
.then(function(data) {
var root = d3.hierarchy(data).sum(function(d){ return d.value});
var treeMap = d3.treemap()
.size([w, h])
.paddingInner(1);
treeMap(root);
const toolTip = d3
.select("#container")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var color = d3.scaleOrdinal()
.domain(["Action", "Drama", "Adventure", "Family", "Animation", "Comedy", "Biography"])
.range(["#db8a00", "#75b0ff", "#13ad37", "#5d6d00", "#757582", "#d37cff", "#f96868"])
svg.selectAll("rect")
.data(root.leaves())
.enter().append("rect")
.attr("class", "tile")
.attr("data-name", (d) => d.data.name)
.attr("data-category", (d) => d.data.category)
.attr("data-value", (d) => d.data.value)
.attr('x', (d) => d.x0)
.attr('y', (d) => d.y0)
.attr('width', (d) => d.x1 - d.x0)
.attr('height', (d) => d.y1 - d.y0)
.style("stroke", "black")
.style("fill", (d) => color(d.parent.data.name))
.on("mouseover", (d, i) => {
toolTip
.transition()
.duration(0)
.style("opacity", 0.8);
toolTip
.attr("id", "tooltip")
.html(function() {
return "<span>" + "Name: " + d.data.name + "<br />" + "Category: " + d.data.category + "<br />" + "Value: " + d.data.value + "</span>";
})
.style("left", d3.event.pageX - 87.5 + "px") // -87.5 is half width of tooltip in css
.style("top", d3.event.pageY - 75 + "px")
.attr("data-value", d.data.value);
})
.on("mouseout", function(d) {
toolTip
.transition()
.duration(0)
.style("opacity", 0);
});
svg.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.selectAll('tspan')
.data(d => {
return d.data.name.split(/(?=[A-Z][^A-Z])/g) // split the name of movie
.map(v => {
return {
text: v,
x0: d.x0, // keep x0 reference
y0: d.y0 // keep y0 reference
}
});
})
.enter()
.append('tspan')
.attr("x", (d) => d.x0 + 5)
.attr("y", (d, i) => d.y0 + 15 + (i * 10)) // offset by index
.text((d) => d.text)
.attr("font-size", "0.6em")
.attr("fill", "white");
console.log(root.leaves());
/*svg.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", function(d){ return d.x0+5})
.attr("y", function(d){ return d.y0+20})
.text(function(d){ return d.data.name })
.attr("font-size", "0.6em")
.attr("fill", "white")*/
legendsvg.selectAll('rect')
.data(root.children)
.enter()
.append('rect')
.attr('class', 'legend-item')
.style('stroke', 'white')
.attr('x', (d,i) => i*140 )
.attr('width', 130)
.attr('height', 20)
.style('fill', d => color(d.data.name))
legendsvg.selectAll('text')
.data(root.children)
.enter()
.append('text')
.attr('x', (d,i) => i*140)
.attr('y', 40)
.text(d => d.data.name);
//had to change the legend below because it wouldn't pass fcc test
/*legendsvg.append("g").classed("legend", true).classed("legend-item", true);
const legend = d3.legendColor().shape("rect")
.shapeWidth(90).cells(7).orient("horizontal").scale(color);
legendsvg.select(".legend").call(legend);*/
});
Full code for approach 2:
// !! IMPORTANT README:
// You may add additional external JS and CSS as needed to complete the project, however the current external resource MUST remain in place for the tests to work. BABEL must also be left in place.
const w = 960;
const h = 600;
const padding = 60;
const svg = d3.select("#container").append("svg")
.attr("width", w).attr("height", h);
const legendsvg = d3.select("#legend").append("svg")
.attr("width", 960).attr("height", 50);
const legendPadding = 10;
d3.json("https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json")
.then(function(data) {
var root = d3.hierarchy(data).sum(function(d){ return d.value});
var treeMap = d3.treemap()
.size([w, h])
.paddingInner(1);
treeMap(root);
const toolTip = d3
.select("#container")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var color = d3.scaleOrdinal()
.domain(["Action", "Drama", "Adventure", "Family", "Animation", "Comedy", "Biography"])
.range(["#db8a00", "#75b0ff", "#13ad37", "#5d6d00", "#757582", "#d37cff", "#f96868"])
const cell = svg.selectAll('g')
.data(root.leaves())
.enter()
.append('g')
.attr('transform', d => `translate(${d.x0},${d.y0})`)
.on('mousemove', d => {
toolTip.transition()
.duration(200)
.style('opacity', 0.75);
toolTip.attr('data-value', d.data.value);
toolTip.html(
'Name: ' + d.data.name + '<br>' +
'Category: ' + d.data.category + '<br>' +
'Value: ' + d.data.value
)
.style('top', `${d3.event.pageY + 10}px`)
.style('left', `${d3.event.pageX + 8}px`);
})
.on('mouseout', d => {
toolTip.transition()
.duration(200)
.style('opacity', 0);
});
cell.append('rect')
.attr('id', d => d.data.id)
.attr('class', 'tile')
.attr('data-name', d => d.data.name)
.attr('data-value', d => d.data.value)
.attr('data-category', d => d.data.category)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', d => color(d.data.category));
cell.append('text')
.selectAll('tspan')
.data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g))
.enter()
.append('tspan')
.attr('font-size', '8px')
.attr('x', 4)
.attr('y', (d, i) => 13 + 10*i)
.text(d => d);
legendsvg.selectAll('rect')
.data(root.children)
.enter()
.append('rect')
.attr('class', 'legend-item')
.style('stroke', 'white')
.attr('x', (d,i) => i*140 )
.attr('width', 130)
.attr('height', 20)
.style('fill', d => color(d.data.name))
legendsvg.selectAll('text')
.data(root.children)
.enter()
.append('text')
.attr('x', (d,i) => i*140)
.attr('y', 40)
.text(d => d.data.name);
//had to change the legend below because it wouldn't pass fcc test
/*legendsvg.append("g").classed("legend", true).classed("legend-item", true);
const legend = d3.legendColor().shape("rect")
.shapeWidth(90).cells(7).orient("horizontal").scale(color);
legendsvg.select(".legend").call(legend);*/
});

Axes not showing up in D3

I am creating a bar chart in d3 and it seems that the execution of the javascript is getting hung up on creating the axis and as a result no axis is showing up and console logs don't work after I create the axis. Here is my code:
req.onload=function() {
const dataset = json.data;
const w = 880;
const h = 440;
const yScale = d3.scaleLinear();
yScale.domain([0,d3.max(dataset, (d) => d[1])]);
yScale.range([10,h]) // axis will start at 10
const svg = d3.select('body')
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.select("body")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => {
return i * 3.2;
})
.attr("y", (d) => {
return (h - yScale(d[1]));
})
.attr("width", 3.2 * .95)
.attr("height", (d, i) => {
return (yScale(d[1]));
})
.attr("class", "bar")
.append("title")
.text((d) => {
return d[0];
})
console.log('one') // logs
const xAxis = d3.axisBottom(xScale); // PROBLEM
console.log('two') // won't log
svg.append("g")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis);
console.log('three') // won't log
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.attr("transform", "translate(100,50)")
.call(yAxis);
console.log('four'); // won't log
};
console.log('this will log')
});
It seems that calling axisBottom() is the problem but I'm not sure why. The d3 documentation isn't very detailed, but it says that the function is d3.axisBottom(scale)
Truly a foolish error. xAxis was not defined. I was developing this in codepen and as such wasn't seeing console error messages. Decided to create a file and open it in chrome and solved in a second. Lesson here is avoid codepen for debugging.

d3js - Sortable Group Bar Chart

Full disclosure: I'm not new to programming, but I'm pretty new to d3 and javascript.
I am trying to combine the Grouped Bar Chart Example and the Sortable Bar Chart Example. I have a total of 51 groups of 3 variables. Here is a truncated form of my dataset you can use to run the code if you want:
State,Response,Predicted,Difference
1,0.0526,0.0983,0.0456
2,0.1161,0.1093,0.0068
5,0.0967,0.1035,0.0067
4,0.0998,0.0942,0.0055
6,0.0888,0.0957,0.0069
I want to be able to order the data by the Response variable by checking a box. Right now I can get the x-axis labels to move accordingly, but I can't get the bars to move with them. To get to this point I renamed the variables in the change() function according to my data. I tried saving the transition.selectAll(".state") function as state2 and then using state2.selectAll(".rect") to modify the x-coordinates of the rectangles, but I realized that wasn't going to get me anywhere.
Here is my code right now (mostly copied from the examples linked above). The relevant function is at the end.
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
code = "";
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".0%"));
var svg = d3.select("body").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 + ")");
d3.csv("data.csv", function(error, data) {
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; });
data.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d[name]}; });
});
x0.domain(data.map(function(d) { return d.State; }));
x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.ages, function(d) { return d.value; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Prevalence");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x2 = x0.domain(data.sort(this.checked
? function(a, b) { return b.Response - a.Response; }
: function(a, b) { return d3.ascending(a.State, b.State); })
.map(function(d) { return d.State; }))
.copy();
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
var state2 = transition.selectAll(".state")
.delay(delay)
.attr("x", function(d) { return x2(d.State); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
})
Any help would be greatly appreciated. I've found nothing so far searching SO and Google.
I assume that you want to keep the grouping when sorting. Your groups are contained in g elements, so all you need to do is adjust the coordinates of the groups. That is, the code to move the groups would look something like
svg.selectAll("g.g")
.transition().duration(750)
.delay(delay)
.attr("transform", function(d) { return "translate(" + x2(d.State) + ",0)"; });
Am tried with the stacked bar chart. To sort the stacked chart, please find the
Stacked Bar Chart
function change() {
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.noncomplete - a.noncomplete; }
: function(a, b) { return d3.ascending(a.moduleName, b.moduleName); })
.map(function(d) { return d.moduleName; }))
.copy();
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 60; };
transition.selectAll(".moduleName")
.delay(delay)
.attr("transform",function(d, i) { return "translate(" + (x0(d.moduleName)) + ",0)"; } );
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}

Categories