I'm trying to append a line to an end of an area chart path. The most difficult part my area is animated. I have a clipPath which width being transformed from width: 0
to width: 960
and the line at the end goes along with it so shoud be synchronised. Also the text on top of that line needs to be updated while it goes along.
Desired output:
My initial idea was to build a chart area and add a clipPath and then add a bar chart inside of area chart so I can update my text based on the bar appended, however bars are not inside my area chart. What am I doing wrong to place bars inside area chart or is there a better solution to this?
// Area chart width and height
const width1 = 1000,
height1 = 100;
// Define x and y scale for area chart
const xScale1 = d3.scaleTime().range([0, width1]);
const yScale1 = d3.scaleLinear().range([height1, 0]);
// Define x and y range for bar chart
let xScale2 = d3.scaleBand().range([0, width1]);
let yScale2 = d3.scaleLinear().range([height1, 0]);
// Add SVG to #areachart
const svg1 = d3
.select('#areachart')
.append('svg')
.attr('viewBox', `0 0 ${width1} ${height1}`)
.attr('transform', 'translate(' + 0 + ',' + -50 + ')');
const g1 = svg1.append('g');
// Fetch data
d3.json(
'https://api.coronavirus.data.gov.uk/v1/data?filters=areaName=United%2520Kingdom;areaType=overview&structure=%7B%22areaType%22:%22areaType%22,%22areaName%22:%22areaName%22,%22areaCode%22:%22areaCode%22,%22date%22:%22date%22,%22newCasesByPublishDate%22:%22newCasesByPublishDate%22,%22cumCasesByPublishDate%22:%22cumCasesByPublishDate%22%7D&format=json'
)
.then(function(data) {
console.log('DATES SLICED ----->', data.data.slice(30, 281));
//Define xScale1 & yScale1 domain after data loaded
yScale1.domain([
0,
d3.max(data.data, function(d) {
return +d.cumCasesByPublishDate;
}),
]);
xScale1.domain(
d3.extent(data.data, function(d) {
return new Date(d.date);
})
);
// Area generator
const area = d3
.area()
.curve(d3.curveStepAfter)
.x((d) => xScale1(new Date(d.date)))
.y1((d) => yScale1(+d.cumCasesByPublishDate))
.y0(yScale1(0));
g1.append('path')
.datum(data.data.slice(30, 200))
.attr('d', area)
.classed('placeholder-layer', true)
.style('fill', '#dadada')
.style('opacity', '0.3');
// clipPath for areachart fill animation
const clip = g1.append('clipPath').attr('id', 'clip');
const clipRect = clip.append('rect').attr('width', 0).attr('height', 750);
g1.append('path')
.datum(data.data.slice(30, 200))
.attr('d', area)
.attr('clip-path', 'url(#clip)')
.classed('overlay-layer', true)
.style('fill', 'yellow')
.style('opacity', '0.3');
g1.append('line').attr('stroke-width', 960).style('stroke', 'yellow');
clipRect
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr('width', 960);
//x and y domain for bar chart
xScale2.domain(data.data.slice(30, 200).map((d) => new Date(d.date)));
yScale2.domain([
0,
d3.max(data.data, function(d) {
return +d.cumCasesByPublishDate;
}),
]);
g1.selectAll('rect')
.data(data.data.slice(30, 200))
.enter()
.append('rect')
.style('fill', 'red')
.attr('width', xScale2.bandwidth() * 10)
.attr('height', (d) => yScale2(+d.cumCasesByPublishDate))
.attr('x', 0)
.attr('y', function(d) {
return yScale2(+d.cumCasesByPublishDate);
})
.transition()
.delay(function(d, i) {
return i * 30;
})
.attr('x', function(d) {
return xScale2(new Date(d.date));
})
.duration(100);
})
// if there's an error, log it
.catch((error) => console.log(error));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<section id="map">
<div id="visualisation-container">
<div id="visualisation"></div>
<div id="areachart"></div>
</div>
</section>
I'd just use a line, no bars, and use transition.tween
with d3.interpolateDate
to make the text change.
// Area chart width and height
const width1 = 800,
height1 = 250,
marginBottom = 50
// Define x and y scale for area chart
const xScale1 = d3.scaleTime().range([0, width1]);
const yScale1 = d3.scaleLinear().range([height1, marginBottom]);
// Define x and y range for bar chart
let xScale2 = d3.scaleBand().range([0, width1]);
let yScale2 = d3.scaleLinear().range([height1, marginBottom]);
// Add SVG to #areachart
const svg1 = d3
.select('#areachart')
.append('svg')
.attr('width', width1)
.attr('height', height1);
const g1 = svg1.append('g');
// Fetch data
d3.json(
'https://api.coronavirus.data.gov.uk/v1/data?filters=areaName=United%2520Kingdom;areaType=overview&structure=%7B%22areaType%22:%22areaType%22,%22areaName%22:%22areaName%22,%22areaCode%22:%22areaCode%22,%22date%22:%22date%22,%22newCasesByPublishDate%22:%22newCasesByPublishDate%22,%22cumCasesByPublishDate%22:%22cumCasesByPublishDate%22%7D&format=json'
)
.then(data => {
data.data.forEach(d => {
d.cumCasesByPublishDate = +d.cumCasesByPublishDate;
d.date = new Date(d.date);
});
return data.data.slice(30, 200);
})
.then(function(data) {
//Define xScale1 & yScale1 domain after data loaded
yScale1.domain([
0,
d3.max(data, d => d.cumCasesByPublishDate),
]);
xScale1.domain(
d3.extent(data, d => d.date)
);
// Area generator
const area = d3
.area()
.curve(d3.curveStepAfter)
.x((d) => xScale1(d.date))
.y1((d) => yScale1(d.cumCasesByPublishDate))
.y0(yScale1(0));
g1.append('path')
.datum(data)
.attr('d', area)
.classed('placeholder-layer', true)
.style('fill', '#dadada')
.style('opacity', '0.3');
// clipPath for areachart fill animation
const clip = g1.append('clipPath').attr('id', 'clip');
const clipRect = clip.append('rect').attr('width', 0).attr('height', 750);
g1.append('path')
.datum(data)
.attr('d', area)
.attr('clip-path', 'url(#clip)')
.classed('overlay-layer', true)
.style('fill', 'yellow')
.style('opacity', '0.3');
const format = d3.timeFormat("%B %d, %Y");
const duration = 10000;
g1.append('line')
.attr('stroke-width', 5)
.style('stroke', 'black')
.attr('x1', xScale1.range()[0])
.attr('x2', xScale1.range()[0])
.attr('y1', yScale1.range()[0])
.attr('y2', yScale1.range()[1])
.transition()
.duration(duration)
.ease(d3.easeLinear)
.attr('x1', xScale1.range()[1])
.attr('x2', xScale1.range()[1])
g1.append('text')
.attr('x', xScale1.range()[0])
.attr('y', marginBottom / 2)
.attr('text-anchor', 'middle')
.transition()
.duration(duration)
.ease(d3.easeLinear)
.attr('x', xScale1.range()[1])
.tween('text', function() {
const i = d3.interpolateDate(xScale1.domain()[0], xScale1.domain()[1]);
return (t) => d3.select(this).text(format(i(t)));
})
})
// if there's an error, log it
.catch((error) => console.log(error));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<section id="map">
<div id="visualisation-container">
<div id="visualisation"></div>
<div id="areachart"></div>
</div>
</section>