yavg
yavg

Reputation: 3051

How can I put circles at each point of the x and y coordinate of my line chart?

I would like to put circles at each point of the x and y coordinates, as I show in this image:

enter image description here

how can I do it?

 
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
 
// Set the ranges
var x = d3.scale.ordinal().domain(["OCT 2020",
"SEP 2020",
"AGO 2020",
"JUL 2020",
"MAY 2020",
"ABR 2020",
"MAR 2020",
"FEB 2020",
"ENE 2020"])
.rangePoints([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// Define the axes
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(100);
 
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(10);

 // Define the line
var valueline = d3.svg.line().interpolate("basis")
    .x(function(d) { return x(d.date); })
    .y(function(d) {  return y(d.close); }); 

// Adds the svg canvas
var svg = d3.select("body")
    .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 + ")");
 
// Get the data
 let data=[
 {date: "OCT 2020", close: 57370},
 {date: "SEP 2020", close: 60100},
 {date: "AGO 2020", close: 62530},
 {date: "JUL 2020", close: 68840},
 {date: "MAY 2020", close: 91470},
 {date: "ABR 2020", close: 54130},
 {date: "MAR 2020", close: 57960},
 {date: "FEB 2020", close: 55720},
 {date: "ENE 2020", close: 54360}
]

    // Scale the range of the data
    // x.domain(d3.extent(data, function(d) { return d.date; }));
    
    y.domain([0, d3.max(data, function(d) { return d.close; })]);

    // Add the valueline path.
    svg.append("path")  
        .attr("class", "line")
        .attr("d", valueline(data));
 
    // Add the X Axis
    svg.append("g")     
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
 
    // Add the Y Axis
    svg.append("g")     
        .attr("class", "y axis")
        .call(yAxis);

    let circle=svg.append("circle").attr("r",2).attr("x",0).attr("y",0)
 
body { font: 12px Arial;}
 
path { 
  stroke: steelblue;
  stroke-width: 2;
  fill: none;
}
 
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
 

this is my code live:

http://plnkr.co/edit/ZSHvIygi7Wgp0nBc?open=lib%2Fscript.js

Upvotes: 0

Views: 83

Answers (1)

Mark
Mark

Reputation: 108512

Just create a selection, bind your data and enter your circles:

svg.selectAll(".points")
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 4)
  .attr("cx", function(d){
     return x(d.date);
  })
  .attr("cy", function(d){
    return y(d.close);
  });

<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
 
body { font: 12px Arial;}
 
path { 
  stroke: steelblue;
  stroke-width: 2;
  fill: none;
}
 
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
 
<!-- load the d3.js library --> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
 
<script>
 
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
 
// Set the ranges
var x = d3.scale.ordinal().domain(["OCT 2020",
"SEP 2020",
"AGO 2020",
"JUL 2020",
"MAY 2020",
"ABR 2020",
"MAR 2020",
"FEB 2020",
"ENE 2020"])
.rangePoints([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// Define the axes
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(100);
 
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(10);

 // Define the line
var valueline = d3.svg.line().interpolate("basis")
    .x(function(d) { console.log(x(d.date),"ddd",d); return x(d.date); })
    .y(function(d) { console.log(y(d.close)); return y(d.close); }); 

// Adds the svg canvas
var svg = d3.select("body")
    .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 + ")");
 
// Get the data
 let data=[
 {date: "OCT 2020", close: 57370},
 {date: "SEP 2020", close: 60100},
 {date: "AGO 2020", close: 62530},
 {date: "JUL 2020", close: 68840},
 {date: "MAY 2020", close: 91470},
 {date: "ABR 2020", close: 54130},
 {date: "MAR 2020", close: 57960},
 {date: "FEB 2020", close: 55720},
 {date: "ENE 2020", close: 54360}
]

    // Scale the range of the data
    // x.domain(d3.extent(data, function(d) { return d.date; }));
    
    y.domain([0, d3.max(data, function(d) { return d.close; })]);

    // Add the valueline path.
    svg.append("path")  
        .attr("class", "line")
        .attr("d", valueline(data));
 
    // Add the X Axis
    svg.append("g")     
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
 
    // Add the Y Axis
    svg.append("g")     
        .attr("class", "y axis")
        .call(yAxis);

    svg.selectAll(".points")
        .data(data)
        .enter()
        .append("circle")
        .attr("r", 4)
        .attr("cx", function(d){
            return x(d.date);
        })
        .attr("cy", function(d){
            return y(d.close);
        });

</script>
</body>

But when you do this, you are going to find that the circles don't fall directly on the curve. This is because your curve is an interpolated basis spline (a curve fit to the data). If you want points directly on the curve, you'll need to extrapolate their position from the curve itself.

Borrowing from my own answer here, we can walk the curve and use getPointAtLength to find the y position on the curve matching the x.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  /* set the CSS */
  
  body {
    font: 12px Arial;
  }
  
  path {
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
  }
  
  .axis path,
  .axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
  }
</style>

<body>

  <!-- load the d3.js library -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

  <script>
    // Set the dimensions of the canvas / graph
    var margin = {
        top: 30,
        right: 20,
        bottom: 30,
        left: 50
      },
      width = 600 - margin.left - margin.right,
      height = 270 - margin.top - margin.bottom;


    // Set the ranges
    var x = d3.scale.ordinal().domain(["OCT 2020",
        "SEP 2020",
        "AGO 2020",
        "JUL 2020",
        "MAY 2020",
        "ABR 2020",
        "MAR 2020",
        "FEB 2020",
        "ENE 2020"
      ])
      .rangePoints([0, width]);
    var y = d3.scale.linear().range([height, 0]);

    // Define the axes
    var xAxis = d3.svg.axis().scale(x)
      .orient("bottom").ticks(100);

    var yAxis = d3.svg.axis().scale(y)
      .orient("left").ticks(10);

    // Define the line
    var valueline = d3.svg.line().interpolate("basis")
      .x(function(d) {
        console.log(x(d.date), "ddd", d);
        return x(d.date);
      })
      .y(function(d) {
        console.log(y(d.close));
        return y(d.close);
      });

    // Adds the svg canvas
    var svg = d3.select("body")
      .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 + ")");

    // Get the data
    let data = [{
        date: "OCT 2020",
        close: 57370
      },
      {
        date: "SEP 2020",
        close: 60100
      },
      {
        date: "AGO 2020",
        close: 62530
      },
      {
        date: "JUL 2020",
        close: 68840
      },
      {
        date: "MAY 2020",
        close: 91470
      },
      {
        date: "ABR 2020",
        close: 54130
      },
      {
        date: "MAR 2020",
        close: 57960
      },
      {
        date: "FEB 2020",
        close: 55720
      },
      {
        date: "ENE 2020",
        close: 54360
      }
    ]

    // Scale the range of the data
    // x.domain(d3.extent(data, function(d) { return d.date; }));

    y.domain([0, d3.max(data, function(d) {
      return d.close;
    })]);

    // Add the valueline path.
    var path = svg.append("path")
      .attr("class", "line")
      .attr("d", valueline(data));

    // Add the X Axis
    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

    // Add the Y Axis
    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);

    svg.selectAll(".points")
      .data(data)
      .enter()
      .append("circle")
      .attr("r", 4)
      .attr("cx", function(d) {
        return x(d.date);
      })
      .attr("cy", function(d) {
        return y(d.close);
      });

    svg.selectAll(".pointsFit")
      .data(data)
      .enter()
      .append("circle")
      .attr("r", 4)
      .attr("cx", function(d) {
        return x(d.date);
      })
      .attr("cy", function(d) {
        return yValueForX(d.date);
      })
      .attr("fill", "red");

    function yValueForX(xCor) {
      var xp = x(xCor),
        pathEl = path.node(),
        pathLength = pathEl.getTotalLength();

      var beginning = xp,
        end = pathLength,
        target;
      while (true) {
        target = Math.floor((beginning + end) / 2);
        pos = pathEl.getPointAtLength(target);
        if ((target === end || target === beginning) && pos.x !== xp) {
          break;
        }
        if (pos.x > xp) end = target;
        else if (pos.x < xp) beginning = target;
        else break; //position found
      }

      return pos.y;
    }
  </script>
</body>

Producing this:

enter image description here

Upvotes: 4

Related Questions