I'm trying to fit dot points on the line with area chart shape, however it doesn't fit. I assume this is due to .curve
using d3.curveBasis.
data = [
{
"login_date": "2017-09-24",
"unique_user_count": 2,
"total_login_count": 2
},
{
"login_date": "2017-09-25",
"unique_user_count": 25,
"total_login_count": 46
},
{
"login_date": "2017-09-26",
"unique_user_count": 31,
"total_login_count": 74
},
{
"login_date": "2017-09-27",
"unique_user_count": 29,
"total_login_count": 58
},
{
"login_date": "2017-09-28",
"unique_user_count": 29,
"total_login_count": 60
},
{
"login_date": "2017-09-29",
"unique_user_count": 31,
"total_login_count": 71
},
{
"login_date": "2017-09-30",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-01",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-02",
"unique_user_count": 41,
"total_login_count": 71
},
{
"login_date": "2017-10-03",
"unique_user_count": 30,
"total_login_count": 67
},
{
"login_date": "2017-10-04",
"unique_user_count": 28,
"total_login_count": 45
},
{
"login_date": "2017-10-05",
"unique_user_count": 32,
"total_login_count": 48
},
{
"login_date": "2017-10-06",
"unique_user_count": 30,
"total_login_count": 50
},
{
"login_date": "2017-10-07",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-08",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-09",
"unique_user_count": 35,
"total_login_count": 76
},
{
"login_date": "2017-10-10",
"unique_user_count": 37,
"total_login_count": 63
},
{
"login_date": "2017-10-11",
"unique_user_count": 41,
"total_login_count": 76
},
{
"login_date": "2017-10-12",
"unique_user_count": 42,
"total_login_count": 83
},
{
"login_date": "2017-10-13",
"unique_user_count": 41,
"total_login_count": 68
},
{
"login_date": "2017-10-15",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-16",
"unique_user_count": 48,
"total_login_count": 84
},
{
"login_date": "2017-10-17",
"unique_user_count": 50,
"total_login_count": 98
},
{
"login_date": "2017-10-18",
"unique_user_count": 38,
"total_login_count": 56
},
{
"login_date": "2017-10-19",
"unique_user_count": 38,
"total_login_count": 98
},
{
"login_date": "2017-10-20",
"unique_user_count": 40,
"total_login_count": 71
},
{
"login_date": "2017-10-22",
"unique_user_count": 2,
"total_login_count": 2
}];
//Define SVG container full width and height
const fullWidth = 600;
const fullHeight = 200;
//Define bar chart area widht and height
const margin = {
top: 10,
bottom: 10,
left: 20,
right: 20
}
const chartWidth = fullWidth - margin.left - margin.right;
const chartHeight = fullHeight - margin.top - margin.bottom;
//Draw SVG container
let svg = d3.select('body')
.append('svg')
.attr('width', fullWidth)
.attr('height', fullHeight);
//Define xand y scale range of the bar chart
const xScale = d3.scaleBand()
.range([0, chartWidth]);
const yScale = d3.scaleLinear()
.range([chartHeight, 0]);
const yScale2 = d3.scaleLinear()
.range([chartHeight, 0]);
console.log('Data received from an API:', data)
//defiene x and y scale domain
yScale
.domain([0, d3.max(data, d => +d.total_login_count)]);
yScale2
.domain([0, d3.max(data, d => +d.unique_user_count)]);
xScale
.domain(data.map(d => d.login_date));
//Generate total login area chart
let area = d3.area()
.curve(d3.curveBasis)
.x(function (d) {
return xScale(d.login_date);
})
.y0(fullHeight)
.y1(function (d) {
return yScale(+d.total_login_count)
});
//Generate unique user count area chart
let area2 = d3.area()
.curve(d3.curveBasis)
.x(function (d) {
return xScale(d.login_date);
})
.y0(fullHeight)
.y1(function (d) {
return yScale2(+d.unique_user_count)
});
//Draw bar chart
let group = svg.selectAll('g')
.data([data])
.enter()
.append('g');
//Draw area for total login count
group
.append('path')
.attr('class', 'area')
.attr('d', area);
//Draw area for unique user count
group
.append('path')
.attr('class', 'area2')
.attr('d', area2);
//Dot points
let points = group.selectAll('circle')
.data(data)
.enter()
.append('circle');
//Dot points
let points2 = group.select('circle')
.data(data)
.enter()
.append('circle');
points.attrs({
"cx": d => xScale(d.login_date),
"cy": d => yScale(+d.total_login_count) + 10,
"r": 5
})
.style("opacity", 1)
.style('fill', '#F9A2CB');
points2.attrs({
"cx": d => xScale(d.login_date),
"cy": d => yScale2(+d.unique_user_count) + 5,
"r": 5
})
.style("opacity", 1)
.style('fill', '#8BDBCE');
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bar chart</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-fetch.v1.min.js"></script>
<script src='https://d3js.org/d3-selection-multi.v0.4.min.js'></script>
</head>
<style>
.line {
fill: none;
stroke: orange;
stroke-width: 1px;
}
.area {
fill: #F9A2CB;
stroke: none;
opacity: 0.6;
}
.area2 {
fill: #8BDBCE;
stroke: none;
opacity: 0.6;
}
</style>
<body>
<script type="text/javascript" src="main.js"></script>
</body>
</html>
Here is my approach:
//define x and y scale domain
yScale
.domain([0, d3.max(data, d => +d.total_login_count)]);
yScale2
.domain([0, d3.max(data, d => +d.unique_user_count)]);
xScale
.domain(data.map(d => d.login_date));
//Generate total login area chart
let area = d3.area()
.curve(d3.curveBasis)
.x(function (d) {
return xScale(d.login_date);
})
.y0(fullHeight)
.y1(function (d) {
return yScale(+d.total_login_count)
});
//Generate unique user count area chart
let area2 = d3.area()
.curve(d3.curveBasis)
.x(function (d) {
return xScale(d.login_date);
})
.y0(fullHeight)
.y1(function (d) {
return yScale2(+d.unique_user_count)
});
//Draw bar chart
let group = svg.selectAll('g')
.data([data])
.enter()
.append('g');
//Draw area for total login count
group
.append('path')
.attr('class', 'area')
.attr('d', area);
//Draw area for unique user count
group
.append('path')
.attr('class', 'area2')
.attr('d', area2);
//Dot points
let points = group.selectAll('circle')
.data(data)
.enter()
.append('circle');
//Dot points
let points2 = group.select('circle')
.data(data)
.enter()
.append('circle');
points.attrs({
"cx": d => xScale(d.login_date),
"cy": d => yScale(+d.total_login_count) + 10,
"r": 5
})
.style("opacity", 1)
.style('fill', '#F9A2CB');
points2.attrs({
"cx": d => xScale(d.login_date),
"cy": d => yScale2(+d.unique_user_count) + 5,
"r": 5
})
.style("opacity", 1)
.style('fill', '#8BDBCE');
You are correct, the problem is d3.curveBasis
indeed.
As you can see, d3.curveBasis
is an interpolator that generates a path which will not pass exactly over the control points:
My suggestion is using an interpolator which generates a path that passes over the control points, such as d3.curveCatmullRom
:
Here is your code with that change:
data = [
{
"login_date": "2017-09-24",
"unique_user_count": 2,
"total_login_count": 2
},
{
"login_date": "2017-09-25",
"unique_user_count": 25,
"total_login_count": 46
},
{
"login_date": "2017-09-26",
"unique_user_count": 31,
"total_login_count": 74
},
{
"login_date": "2017-09-27",
"unique_user_count": 29,
"total_login_count": 58
},
{
"login_date": "2017-09-28",
"unique_user_count": 29,
"total_login_count": 60
},
{
"login_date": "2017-09-29",
"unique_user_count": 31,
"total_login_count": 71
},
{
"login_date": "2017-09-30",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-01",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-02",
"unique_user_count": 41,
"total_login_count": 71
},
{
"login_date": "2017-10-03",
"unique_user_count": 30,
"total_login_count": 67
},
{
"login_date": "2017-10-04",
"unique_user_count": 28,
"total_login_count": 45
},
{
"login_date": "2017-10-05",
"unique_user_count": 32,
"total_login_count": 48
},
{
"login_date": "2017-10-06",
"unique_user_count": 30,
"total_login_count": 50
},
{
"login_date": "2017-10-07",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-08",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-09",
"unique_user_count": 35,
"total_login_count": 76
},
{
"login_date": "2017-10-10",
"unique_user_count": 37,
"total_login_count": 63
},
{
"login_date": "2017-10-11",
"unique_user_count": 41,
"total_login_count": 76
},
{
"login_date": "2017-10-12",
"unique_user_count": 42,
"total_login_count": 83
},
{
"login_date": "2017-10-13",
"unique_user_count": 41,
"total_login_count": 68
},
{
"login_date": "2017-10-15",
"unique_user_count": 1,
"total_login_count": 1
},
{
"login_date": "2017-10-16",
"unique_user_count": 48,
"total_login_count": 84
},
{
"login_date": "2017-10-17",
"unique_user_count": 50,
"total_login_count": 98
},
{
"login_date": "2017-10-18",
"unique_user_count": 38,
"total_login_count": 56
},
{
"login_date": "2017-10-19",
"unique_user_count": 38,
"total_login_count": 98
},
{
"login_date": "2017-10-20",
"unique_user_count": 40,
"total_login_count": 71
},
{
"login_date": "2017-10-22",
"unique_user_count": 2,
"total_login_count": 2
}];
//Define SVG container full width and height
const fullWidth = 600;
const fullHeight = 200;
//Define bar chart area widht and height
const margin = {
top: 10,
bottom: 10,
left: 20,
right: 20
}
const chartWidth = fullWidth - margin.left - margin.right;
const chartHeight = fullHeight - margin.top - margin.bottom;
//Draw SVG container
let svg = d3.select('body')
.append('svg')
.attr('width', fullWidth)
.attr('height', fullHeight);
//Define xand y scale range of the bar chart
const xScale = d3.scaleBand()
.range([0, chartWidth]);
const yScale = d3.scaleLinear()
.range([chartHeight, 0]);
const yScale2 = d3.scaleLinear()
.range([chartHeight, 0]);
console.log('Data received from an API:', data)
//defiene x and y scale domain
yScale
.domain([0, d3.max(data, d => +d.total_login_count)]);
yScale2
.domain([0, d3.max(data, d => +d.unique_user_count)]);
xScale
.domain(data.map(d => d.login_date));
//Generate total login area chart
let area = d3.area()
.curve(d3.curveCatmullRom)
.x(function (d) {
return xScale(d.login_date);
})
.y0(fullHeight)
.y1(function (d) {
return yScale(+d.total_login_count)
});
//Generate unique user count area chart
let area2 = d3.area()
.curve(d3.curveCatmullRom)
.x(function (d) {
return xScale(d.login_date);
})
.y0(fullHeight)
.y1(function (d) {
return yScale2(+d.unique_user_count)
});
//Draw bar chart
let group = svg.selectAll('g')
.data([data])
.enter()
.append('g');
//Draw area for total login count
group
.append('path')
.attr('class', 'area')
.attr('d', area);
//Draw area for unique user count
group
.append('path')
.attr('class', 'area2')
.attr('d', area2);
//Dot points
let points = group.selectAll('circle')
.data(data)
.enter()
.append('circle');
//Dot points
let points2 = group.select('circle')
.data(data)
.enter()
.append('circle');
points.attrs({
"cx": d => xScale(d.login_date),
"cy": d => yScale(+d.total_login_count),
"r": 5
})
.style("opacity", 1)
.style('fill', '#F9A2CB');
points2.attrs({
"cx": d => xScale(d.login_date),
"cy": d => yScale2(+d.unique_user_count),
"r": 5
})
.style("opacity", 1)
.style('fill', '#8BDBCE');
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bar chart</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-fetch.v1.min.js"></script>
<script src='https://d3js.org/d3-selection-multi.v0.4.min.js'></script>
</head>
<style>
.line {
fill: none;
stroke: orange;
stroke-width: 1px;
}
.area {
fill: #F9A2CB;
stroke: none;
opacity: 0.6;
}
.area2 {
fill: #8BDBCE;
stroke: none;
opacity: 0.6;
}
</style>
<body>
<script type="text/javascript" src="main.js"></script>
</body>
</html>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With