Related
I posted a previous question with perhaps too much specificity. I'm trying to create a multi-line chart in d3 with a dropdown, similar to this.
I've switched out the obvious changes needed for v7 but am still running into trouble, I believe in the if/else statement right after var initialGraph but I'm not 100% sure. It may also be because my d3.groups isn't set up / referenced correctly.
The current error I receive is:
Uncaught (in promise) TypeError: Cannot read property 'year' of undefined
at <anonymous>:23:33
at a (d3.v7.min.js:2)
at initialGraph (<anonymous>:83:18)
at <anonymous>:89:3
My dataset has four values: year, state, wvalues, and lvalues. state and lvalues are strings, year and wvalues are numeric. Here's my code so far:
var margin = { top: 50, right: 50, bottom: 50, left: 50 }
var h = 500 - margin.top - margin.bottom
var w = 700 - margin.left - margin.right
var formatDecimal = d3.format('.2')
d3.csv('15/data.csv').then(function (data) {
// Scales
var x = d3.scaleLinear()
.range([0,w])
var y = d3.scaleLinear()
.range([h,0])
y.domain([
d3.min([0,d3.min(data,function (d) { return d.wvalue })]),
d3.max([0,d3.max(data,function (d) { return d.wvalue })])
]);
x.domain([1968, 2016])
// Define the line
var valueLine = d3.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.wvalue); })
// Create the svg canvas in the "d3block" div
var svg = d3.select("#d3block")
.append("svg")
.style("width", w + margin.left + margin.right + "px")
.style("height", h + margin.top + margin.bottom + "px")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")")
.attr("class", "svg");
//nest variable
var nest = d3.groups(data,
d => d.state, d => d.lvalue)
// X-axis
var xAxis = d3.axisBottom()
.scale(x)
.tickFormat(formatDecimal)
.ticks(7)
// Y-axis
var yAxis = d3.axisLeft()
.scale(y)
.tickFormat(formatDecimal)
.ticks(5)
// Create a dropdown
var legisMenu = d3.select("#legisDropdown")
legisMenu
.append("select")
.selectAll("option")
.data(nest)
.enter()
.append("option")
.attr("value", ([key, ]) => key)
.text(([key, ]) => key)
// Function to create the initial graph
var initialGraph = function(legis){
// Filter the data to include only state of interest
var selectLegis = nest.filter(([key, ]) => key == legis)
var selectLegisGroups = svg.selectAll(".legisGroups")
.data(selectLegis, function(d){
return d ? d.key : this.key;
})
.enter()
.append("g")
.attr("class", "legisGroups")
var initialPath = selectLegisGroups.selectAll(".line")
.data(([, values]) => values)
.enter()
.append("path")
initialPath
.attr("d", valueLine(([, values]) => values))
.attr("class", "line")
}
// Create initial graph
initialGraph("Alabama")
// Update the data
var updateGraph = function(legis){
// Filter the data to include only state of interest
var selectLegis = nest.filter(([key, ]) => key == legis)
// Select all of the grouped elements and update the data
var selectLegisGroups = svg.selectAll(".legisGroups")
.data(selectLegis)
// Select all the lines and transition to new positions
selectLegisGroups.selectAll("path.line")
.data(([, values]) => values)
.transition()
.duration(1000)
.attr("d", valueLine(([, values ]) => values))
}
// Run update function when dropdown selection changes
legisMenu.on('change', function(){
// Find which state was selected from the dropdown
var selectedLegis = d3.select(this)
.select("select")
.property("value")
// Run update function with the selected state
updateGraph(selectedLegis)
});
// X-axis
svg.append('g')
.attr('class','axis')
.attr('id','xAxis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis)
.append('text') // X-axis Label
.attr('id','xAxisLabel')
.attr('fill','black')
.attr('y',-10)
.attr('x',w)
.attr('dy','.71em')
.style('text-anchor','end')
.text('')
// Y-axis
svg.append('g')
.attr('class','axis')
.attr('id','yAxis')
.call(yAxis)
.append('text') // y-axis Label
.attr('id', 'yAxisLabel')
.attr('fill', 'black')
.attr('transform','rotate(-90)')
.attr('x',0)
.attr('y',5)
.attr('dy','.71em')
.style('text-anchor','end')
.text('wvalue')
})
I did more digging and found the answer here.
I had to replace the initial path attribute .attr("d", valueLine(([, values]) => values)) with .attr('d', (d) => valueLine(Array.from(d.values())[1])). I also had to replace the code further down within the updateGraph function under selectLegisGroups .attr for it to update properly.
I am building a data visualization project utilizing the d3 library. I have created a legend and am trying to match up text labels with that legend.
To elaborate further, I have 10 rect objects created and colored per each line of my graph. I want text to appear adjacent to each rect object corresponding with the line's color.
My Problem
-Right now, an array containing all words that correspond to each line appears adjacent to the top rect object. And that's it.
I think it could be because I grouped my data using the d3.nest function. Also, I noticed only one text element is created in the HTML. Can anyone take a look and tell me what I'm doing wrong?
JS Code
const margin = { top: 20, right: 30, bottom: 30, left: 0 },
width = 1000 - margin.left - margin.right;
height = 600 - margin.top - margin.bottom;
// maybe a translate line
// document.body.append(svg);
const div_block = document.getElementById("main-div");
// console.log(div_block);
const svg = d3
.select("svg")
.attr("width", width + margin.left + margin.right) // viewport size
.attr("height", height + margin.top + margin.bottom) // viewport size
.append("g")
.attr("transform", "translate(40, 20)"); // center g in svg
// load csv
d3.csv("breitbartData.csv").then((data) => {
// convert Count column values to numbers
data.forEach((d) => {
d.Count = +d.Count;
d.Date = new Date(d.Date);
});
// group the data with the word as the key
const words = d3
.nest()
.key(function (d) {
return d.Word;
})
.entries(data);
// create x scale
const x = d3
.scaleTime() // creaters linear scale for time
.domain(
d3.extent(
data,
// d3.extent returns [min, max]
(d) => d.Date
)
)
.range([margin.left - -30, width - margin.right]);
// x axis
svg
.append("g")
.attr("class", "x-axis")
.style("transform", `translate(-3px, 522px)`)
.call(d3.axisBottom(x))
.append("text")
.attr("class", "axis-label-x")
.attr("x", "55%")
.attr("dy", "4em")
// .attr("dy", "20%")
.style("fill", "black")
.text("Months");
// create y scale
const y = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d.Count)])
.range([height - margin.bottom, margin.top]);
// y axis
svg
.append("g")
.attr("class", "y-axis")
.style("transform", `translate(27px, 0px)`)
.call(d3.axisLeft(y));
// line colors
const line_colors = words.map(function (d) {
return d.key; // list of words
});
const color = d3
.scaleOrdinal()
.domain(line_colors)
.range([
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
"#999999",
"#872ff8",
]); //https://observablehq.com/#d3/d3-scaleordinal
// craete legend variable
const legend = svg
.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 100)
.attr("transform", "translate(-20, 50)");
// create legend shapes and locations
legend
.selectAll("rect")
.data(words)
.enter()
.append("rect")
.attr("x", width + 65)
.attr("y", function (d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("fill", function (d) {
return color(d.key);
});
// create legend labels
legend
.append("text")
.attr("x", width + 85)
.attr("y", function (d, i) {
return i * 20 + 9;
})
// .attr("dy", "0.32em")
.text(
words.map(function (d, i) {
return d.key; // list of words
})
);
// returning an array as text
// });
svg
.selectAll(".line")
.data(words)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function (d) {
return color(d.key);
})
.attr("stroke-width", 1.5)
.attr("d", function (d) {
return d3
.line()
.x(function (d) {
return x(d.Date);
})
.y(function (d) {
return y(d.Count);
})(d.values);
});
});
Image of the problem:
P.S. I cannot add a JSfiddle because I am hosting this page on a web server, as that is the only way chrome can read in my CSV containing the data.
My Temporary Solution
function leg_labels() {
let the_word = "";
let num = 0;
for (i = 0; i < words.length; i++) {
the_word = words[i].key;
num += 50;
d3.selectAll(".legend")
.append("text")
.attr("x", width + 85)
.attr("y", function (d, i) {
return i + num;
})
// .attr("dy", "0.32em")
.text(the_word);
}
}
leg_labels();
Problem
Your problem has to do with this code
legend
.append("text")
.attr("x", width + 85)
.attr("y", function (d, i) {
return i * 20 + 9;
})
// .attr("dy", "0.32em")
.text(
words.map(function (d, i) {
return d.key; // list of words
})
);
You are appending only a single text element and in the text function you are returning the complete array of words, which is why all words are shown.
Solution
Create a corresponding text element for each legend rectangle and provide the correct word. There are multiple ways to go about it.
You could use foreignObject to append HTML inside your SVG, which is very helpful for text, but for single words, plain SVG might be enough.
I advise to use a g element for each legend item. This makes positioning a lot easier, as you only need to position the rectangle and text relative to the group, not to the whole chart.
Here is my example:
let legendGroups = legend
.selectAll("g.legend-item")
.data(words)
.enter()
.append("g")
.attr("class", "legend-item")
.attr("transform", function(d, i) {
return `translate(${width + 65}px, ${i * 20}px)`;
});
legendGroups
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 10)
.attr("height", 10)
.style("fill", function (d) {
return color(d.key);
});
legendGroups
.append("text")
.attr("x", 20)
.attr("y", 9)
.text(function(d, i) { return words[i].key; });
This should work as expected.
Please note the use of groups for easier positioning.
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')).
I have a visualization task that I need to make it done with d3.js. Here's my code.
var w = 700;
var h = 500;
var offset = 100;
var padding = 20;
var colors = d3.scale.category10();
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var texts = function(ds,ds2){
var stack = d3.layout.stack();
stack_data = stack(ds);
var xScale = d3.scale.ordinal()
.domain(d3.range(ds[0].length))
.rangeRoundBands([0, w-offset], 0.50);
var yScale = d3.scale.linear()
.domain([0,
d3.max(stack_data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y -20;
});
})
])
.range([padding, h-50]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(ds[0].length);
gs = svg.selectAll("g").data(stack_data);
for (var i5 = 0; i5 < ds.length; i5++) {
gs.enter()
.append("g")
.attr("class", "stacked_bars")
.attr("fill", function(d, i) {
return colors(i);
});
asd = gs.selectAll("rect").data(function(d) { return d; });
asd.enter().append("rect");
asd.transition()
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y);
})
.attr("height", function(d) {
return yScale(d.y);
})
.attr("width", xScale.rangeBand())
.attr("class", "rectbar");
};
gs.append("g") // add a group element to the svg
.attr("class", "axis") //Assign class "axis" to group
.attr("transform", "translate(0," + (h - padding) + ")") // shift the axis to bottom
.call(xAxis); // call the axis function
gs.exit().remove();
}
res = dataGenerator("Europe");
dataset = res[0];
dataset2 = res[1];
texts(dataset,dataset2);
d3.select("#selector").on("change", function() {
cont = d3.select(this).property('value');
res = dataGenerator(cont)
dataset = res[0]
dataset2 = res[1]
//svg.selectAll(".sym").remove()
texts(dataset,dataset2);
});
It basically gets the data and generates stacked bars. When user uses the select element on the page, it updates the data and generates new results. It successfully gets the first results and when user selects another option, it makes it happen also. But when user tries to use select part once again. It only generates bars for dataset's first item.
So, in this particular case I have countries and their data as numbers, at first load and first update it successfully shows all but when it comes to second update, it only generate bars for first country in dataset. It's been hours that I'm trying to fix this. I know I only have a little mistake but couldn't make it to solve.
Also here's the jsfiddle of the code: https://jsfiddle.net/510ep9ux/4/
Since I'm new at d3.js, I may not understand the update concept well.
So, any guesses?
Solved, using two separate functions textsInit and textsUpdate :
https://jsfiddle.net/qxqdp36x/2/
Essentially you need to separate initialization and update logic, and avoid re-creating elements when updating, that causes unintended behaviours.
Also, the variables gs and asd needs to be global to be accessible to both functions.
var textsInit = function(ds, ds2) {
var stack = d3.layout.stack();
stack_data = stack(ds);
var xScale = d3.scale.ordinal()
.domain(d3.range(ds[0].length))
.rangeRoundBands([0, w - offset], 0.50);
var yScale = d3.scale.linear()
.domain([0,
d3.max(stack_data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y - 20;
});
})
])
.range([padding, h - 50]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(ds[0].length);
gs = svg.selectAll("g").data(stack_data);
bars = gs.enter();
bars.append("g")
.attr("class", "stacked_bars")
.attr("fill", function(d, i) {
return colors(i);
});
asd = gs.selectAll("rect").data(function(d) {
return d;
});
asd.enter().append("rect");
asd.transition()
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y);
})
.attr("height", function(d) {
return yScale(d.y);
})
.attr("width", xScale.rangeBand())
.attr("class", "rectbar");
gs.append("g") // add a group element to the svg
.attr("class", "axis") //Assign class "axis" to group
.attr("transform", "translate(0," + (h - padding) + ")") // shift the axis to bottom
.call(xAxis); // call the axis function
}
And:
var textsUpdate = function(ds, ds2) {
var stack = d3.layout.stack();
stack_data = stack(ds);
var xScale = d3.scale.ordinal()
.domain(d3.range(ds[0].length))
.rangeRoundBands([0, w - offset], 0.50);
var yScale = d3.scale.linear()
.domain([0,
d3.max(stack_data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y - 20;
});
})
])
.range([padding, h - 50]);
gs.data(stack_data);
asd = gs.selectAll("rect").data(function(d) { return d; });
asd.transition()
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y);
})
.attr("height", function(d) {
return yScale(d.y);
})
.attr("width", xScale.rangeBand())
.attr("class", "rectbar");
}
Edited to fix a small bug, updating the asd selection's data.
I made 2 simple but crucial changes to your code.
https://jsfiddle.net/guanzo/510ep9ux/6/
From
gs = svg.selectAll("g").data(stack_data);
to
gs = svg.selectAll("g.stacked_bars").data(stack_data);
The axis is also contained in a g element, so you have to ensure you're only selecting elements that are used for your data, and not unrelated elements.
From
gs.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
to
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
If you go into the browser inspector you'll see that you have an axis element for EVERY stacked_bars element, you only need 1 obviously. It only looks like there's 1 axis because they're absolutely positioned and stacked on top of each other.
I changed it so that the axis is appended when the svg is created, and every time new data is selected, the axis will update itself.
Well the rendering of bar chart works fine with default given data. The problem occurs on the button click which should also cause the get of new data set. Updating the x-axis y-axis works well but the rendering data causes problems.
First Ill try to remove all the previously added rects and then add the new data set. But all the new rect elements gets added into wrong place, because there is no reference to old rects.
Here is the code and the redraw is in the end of code.
http://jsfiddle.net/staar2/wBNWK/9/
var data = JSON.parse('[{"hour":0,"time":147},{"hour":1,"time":0},{"hour":2,"time":74},{"hour":3,"time":141},{"hour":4,"time":137},{"hour":5,"time":210},{"hour":6,"time":71},{"hour":7,"time":73},{"hour":8,"time":0},{"hour":9,"time":68},{"hour":10,"time":70},{"hour":11,"time":0},{"hour":12,"time":147},{"hour":13,"time":0},{"hour":14,"time":0},{"hour":15,"time":69},{"hour":16,"time":67},{"hour":17,"time":67},{"hour":18,"time":66},{"hour":19,"time":0},{"hour":20,"time":0},{"hour":21,"time":66},{"hour":22,"time":210},{"hour":23,"time":0}] ');
var w = 15,
h = 80;
var xScale = d3.scale.linear()
.domain([0, 1])
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function (d) {
return d.time;
})])
.rangeRound([5, h]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var chart = d3.select("#viz")
.append("svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) {
return xScale(i) - 0.5;
})
.attr("y", function(d) {
return h - yScale(d.time) - 0.5;
})
.attr("width", w)
.attr("height", function(d) {
return yScale(d.time);
});
chart.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
if (d.time > 10) {
return Math.round(d.time);
}
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "#FFF")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + w / 2;
})
.attr("y", function(d) {
return h - yScale(d.time) - 0.5 + 10;
});
chart.append("g")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis);
function redraw() {
// This the part where the incoming data set also changes, which means the update to x-axis y-axis, labels
yScale.domain([0, d3.max(data, function (d) {
return d.time;
})]);
var bars = d3.selectAll("rect")
.data(data, function (d) {
return d.hour;
});
bars
.transition()
.duration(500)
.attr("x", w) // <-- Exit stage left
.remove();
d3.selectAll("rect") // This is actually empty
.data(data, function (d) {
return d.hour;
})
.enter()
.append("rect")
.attr("x", function(d, i) {
console.log(d, d.day, xScale(d.day));
return xScale(d.day);
})
.attr("y", function(d) {
return yScale(d.time);
})
.attr("width", w)
.attr("height", function (d) {
return h - yScale(d.time);
});
}
d3.select("button").on("click", function() {
console.log('Clicked');
redraw();
});
Agree with Sam (although there were a few more issues, like using remove() without exit(), etc.) and I am putting this out because I was playing with it as I was cleaning the code and applying the update pattern. Here is the FIDDLE with changes in code I made. I only changed the first few data points but this should get you going.
var data2 = JSON.parse('[{"hour":0,"time":153},{"hour":1,"time":10},{"hour":2,"time":35},{"hour":3,"time":150},
UPDATE: per request, adding logic to consider an update with new data. UPDATED FIDDLE.
Since you're binding the same data to bars, the enter selection is empty. Once you remove the existing bars, you append a new bar for each data point in the enter selection - which again is empty. If you had different data, the bars should append.
If you haven't read through it already, the general update pattern is a great resource for understanding this sort of thing.