Reputation: 1521
In my snippet below I'm using the following filter method to get the desired data:
var data = csv.filter(d => d.date.includes(year))
The problem here is that whenever I'm updating the chart every bar gets redrawn from the top rather than updating from the previous data.
Is this because I'm redeclaring the data
variable in my update function and therefore it's not updating from previous data?
Is there a simple way to get around this or do I have to re-map and separate each year in a new data array and base my update pattern on this?
Here's the code:
var csvData = `date,value
2018-jan,100
2018-feb,75
2018-mar,45
2018-apr,65
2019-jan,21
2019-feb,43
2019-mar,55
2019-apr,33`;
var csv = d3.csvParse(csvData, d => d)
var margin = {top: 25, bottom: 25, left: 25, right: 25};
var svg = d3.select("#chart"),
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([margin.left, width - margin.right])
.padding(0.1)
.paddingOuter(0.2)
var xAxis = g => g
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
svg.append("g")
.attr("class", "x-axis")
var y = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
update("2018")
function update(year) {
var data = csv.filter(d => d.date.includes(year))
y.domain([0, d3.max(data, d => Math.max(d.value))])
x.domain(data.map(d => d.date))
svg.selectAll(".x-axis")
.call(xAxis);
var bar = svg.selectAll(".bar")
.data(data, d => d.date)
bar.exit().remove();
bar = bar
.enter()
.append("rect")
.attr("class", "bar")
.attr("fill", "steelblue")
.attr("width", x.bandwidth())
.attr("x", d => x(d.date))
.merge(bar)
.transition().duration(750)
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value))
}
d3.select("#year").on("change", function() {
update(this.value)
})
body {
padding-top: 25px;
margin: auto;
width: 450px;
font: 11px arial;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Choose year
<select id="year">
<option value="2018">2018</option>
<option value="2019">2019</option>
</select><br>
<svg id="chart" width="450" height="200"></svg>
Upvotes: 1
Views: 250
Reputation: 1521
So I found out on my own that I can just do the filtering within the data
method itself like this...
var bar = svg.selectAll(".bar")
.data(data.filter(f => f.date.includes(year)))
...and everything gets updated properly, not sure if I should close the question but I'll leave it upf or now:
Here's the updated code:
var csvData = `date,value
2018-jan,100
2018-feb,75
2018-mar,45
2018-apr,65
2019-jan,21
2019-feb,43
2019-mar,55
2019-apr,33`;
var data = d3.csvParse(csvData, d => d)
var margin = {top: 25, bottom: 25, left: 25, right: 25};
var svg = d3.select("#chart"),
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([margin.left, width - margin.right])
.padding(0.1)
.paddingOuter(0.2)
var xAxis = g => g
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
svg.append("g")
.attr("class", "x-axis")
var y = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
update("2018")
function update(year) {
var maxVal = d3.max(data, function(d) {
var f = d.date.includes(year)
return (f == true ? d.value : null)
})
y.domain([0, maxVal]).nice()
x.domain(data.map(d => d.date).filter(f => f.includes(year)))
svg.selectAll(".x-axis")
.call(xAxis);
var bar = svg.selectAll(".bar")
.data(data.filter(f => f.date.includes(year)));
bar.exit().remove();
bar.enter()
.append("rect")
.attr("class", "bar")
.attr("fill", "steelblue")
.attr("width", x.bandwidth())
.attr("x", d => x(d.date))
.merge(bar)
.transition().duration(750)
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value))
}
d3.select("#year").on("change", function() {
update(this.value)
})
body {
padding-top: 25px;
margin: auto;
width: 650px;
font: 11px arial;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Choose year
<select id="year">
<option value="2018">2018</option>
<option value="2019">2019</option>
</select><br>
<svg id="chart" width="650" height="200"></svg>
Upvotes: 1
Reputation: 102198
The problem here has nothing to do with filtering the data, or re-assigning variables, or merging selections, or any other issue mentioned in the question and comments.
The problem here is simply the key function. When you specify the key function, the data for each element is bound according to its string identifier. And, since the dates are different, the expected behaviour is that the bars should be removed and new bars should be drawn.
What you want, instead of that, is a join-by-index behaviour. So, change your data method from...
.data(data, d => d.date)
..to:
.data(data)
Here is your code with that change only:
var csvData = `date,value
2018-jan,100
2018-feb,75
2018-mar,45
2018-apr,65
2019-jan,21
2019-feb,43
2019-mar,55
2019-apr,33`;
var csv = d3.csvParse(csvData, d => d)
var margin = {top: 25, bottom: 25, left: 25, right: 25};
var svg = d3.select("#chart"),
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([margin.left, width - margin.right])
.padding(0.1)
.paddingOuter(0.2)
var xAxis = g => g
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
svg.append("g")
.attr("class", "x-axis")
var y = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
update("2018")
function update(year) {
var data = csv.filter(d => d.date.includes(year))
y.domain([0, d3.max(data, d => Math.max(d.value))])
x.domain(data.map(d => d.date))
svg.selectAll(".x-axis")
.call(xAxis);
var bar = svg.selectAll(".bar")
.data(data)
bar.exit().remove();
bar = bar
.enter()
.append("rect")
.attr("class", "bar")
.attr("fill", "steelblue")
.attr("width", x.bandwidth())
.attr("x", d => x(d.date))
.merge(bar)
.transition().duration(750)
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value))
}
d3.select("#year").on("change", function() {
update(this.value)
})
body {
padding-top: 25px;
margin: auto;
width: 450px;
font: 11px arial;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Choose year
<select id="year">
<option value="2018">2018</option>
<option value="2019">2019</option>
</select><br>
<svg id="chart" width="450" height="200"></svg>
Upvotes: 2