Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circle points are not aligned with area shape

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.

enter image description here

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');
like image 224
Edgar Kiljak Avatar asked Apr 02 '19 09:04

Edgar Kiljak


1 Answers

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:

enter image description here

My suggestion is using an interpolator which generates a path that passes over the control points, such as d3.curveCatmullRom:

enter image description here

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>
like image 135
Gerardo Furtado Avatar answered Oct 03 '22 14:10

Gerardo Furtado