Burnup charts in d3.js

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












The task is creating a burn-up chart from Tickets on Trello



We make use of the Trello-API in C#, to get the tickets (Done/Created/etc...) however that is not what I want reviewed, since this has been done mostly before I joined this project. However I am a bit worried about my JavaScript for the d3.js code I created.



Code



<!DOCTYPE html>
<meta charset="utf-8">
<title>d3 burn chart</title>
<div id="charts"></div>

<style>
div.tooltip
position: absolute;
text-align: center;
width: 100px;
height: 32px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;

</style>

<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
const burnuplist = [
Date: "/Date(1476079569717)/", Total:1, Burn:0 ,
Date: "/Date(1476684369717)/", Total:23, Burn:2 ,
Date: "/Date(1477289169717)/", Total:32, Burn:17 ,
Date: "/Date(1477897569717)/", Total:57, Burn:40 ,
Date: "/Date(1478502369717)/", Total:74, Burn:56
]

var formatTime = d3.timeFormat("%d-%m-%Y");
var drawLine = function(xvalues, yvalues, g, xscale, yscale, options)
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);

// line parts
const lineparts = yvalues
.map((y, i, ar) => (
i : i,
x1: xvalues[i - 1],
x2: xvalues[i],
y1 : ar[i - 1],
y2 : y
))
.filter(d => d.i > 0);

// line
g.append("g")
.attr("id", options.id)
.selectAll("line")
.data(lineparts)
.enter()
.append("line")
.attr("x1", d => xscale(d.x1))
.attr("x2", d => xscale(d.x2))
.attr("y1", d => yscale(d.y1))
.attr("y2", d => yscale(d.y2))
.attr("stroke", options.color);

if (options.circles)
g.append("g")
.selectAll("circle")
.data(lineparts)
.enter()
.append("circle")
.attr("r", 5)
.attr("cx", d => xscale(d.x2))
.attr("cy", d => yscale(d.y2))
.attr("fill", options.color)
.on("mouseover", function (d)
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.y2 + "</br>" + d.x2)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
)
.on("mouseout", function (d)
div.transition()
.duration(500)
.style("opacity", 0);
);



var burnchart = function(xlabels, burnlist, totallist)
const svg = d3.select('svg'),
margin = top: 20, right: 40, bottom: 75, left: 50,
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

// x-axis
const x = d3.scalePoint()
.domain(xlabels)
.range([0, width]);
g.append("g")
.attr("transform", `translate(0,$height)`)
.call(d3.axisBottom(x))
.selectAll("text")
.style("text-anchor", "end")
.attr("transform", "rotate(-65)")
.attr("y", 4)
.attr("x", -10)
.attr("dy", ".35em");

// y-axis
const y = d3.scaleLinear()
.domain([0, 1.05 * Math.max(d3.max(burnlist), d3.max(totallist))])
.range([height, 0])
.nice();
g.append("g")
.call(d3.axisLeft(y));

// total
drawLine(xlabels, totallist, g, x, y,

color: "#000000",
circles: true,
id: "totallist"
);

// burn
drawLine(xlabels, burnlist, g, x, y,

color: "#3c763d",
circles: true,
id: "burnlist"
);


d3.select('#charts')
.selectAll('svg')
.data([burnuplist])
.enter()
.append('svg')
.attr('width', 860)
.attr('height', 480)
.style('margin', '0.5em')
.style('border', 'solid 2px #eef6fc')
.call(burnchart(
burnuplist.map(d => formatTime(new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10)))),
burnuplist.map(d => d.Burn),
burnuplist.map(d => d.Total)
)
);
</script>


Result



burn-up



Questions



  • Am I using d3.js correctly?

  • I somehow needed to make an array over the Json data [burnuplist]

  • Maybe out of scope, but is there a better way to change how I handle the Date's?

  • Any general tips on JavaScript styling?

I don't write much JS, as you can see ;) Any review is welcome.







share|improve this question



























    up vote
    4
    down vote

    favorite












    The task is creating a burn-up chart from Tickets on Trello



    We make use of the Trello-API in C#, to get the tickets (Done/Created/etc...) however that is not what I want reviewed, since this has been done mostly before I joined this project. However I am a bit worried about my JavaScript for the d3.js code I created.



    Code



    <!DOCTYPE html>
    <meta charset="utf-8">
    <title>d3 burn chart</title>
    <div id="charts"></div>

    <style>
    div.tooltip
    position: absolute;
    text-align: center;
    width: 100px;
    height: 32px;
    padding: 2px;
    font: 12px sans-serif;
    background: lightsteelblue;
    border: 0px;
    border-radius: 8px;
    pointer-events: none;

    </style>

    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script>
    const burnuplist = [
    Date: "/Date(1476079569717)/", Total:1, Burn:0 ,
    Date: "/Date(1476684369717)/", Total:23, Burn:2 ,
    Date: "/Date(1477289169717)/", Total:32, Burn:17 ,
    Date: "/Date(1477897569717)/", Total:57, Burn:40 ,
    Date: "/Date(1478502369717)/", Total:74, Burn:56
    ]

    var formatTime = d3.timeFormat("%d-%m-%Y");
    var drawLine = function(xvalues, yvalues, g, xscale, yscale, options)
    // Define the div for the tooltip
    var div = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);

    // line parts
    const lineparts = yvalues
    .map((y, i, ar) => (
    i : i,
    x1: xvalues[i - 1],
    x2: xvalues[i],
    y1 : ar[i - 1],
    y2 : y
    ))
    .filter(d => d.i > 0);

    // line
    g.append("g")
    .attr("id", options.id)
    .selectAll("line")
    .data(lineparts)
    .enter()
    .append("line")
    .attr("x1", d => xscale(d.x1))
    .attr("x2", d => xscale(d.x2))
    .attr("y1", d => yscale(d.y1))
    .attr("y2", d => yscale(d.y2))
    .attr("stroke", options.color);

    if (options.circles)
    g.append("g")
    .selectAll("circle")
    .data(lineparts)
    .enter()
    .append("circle")
    .attr("r", 5)
    .attr("cx", d => xscale(d.x2))
    .attr("cy", d => yscale(d.y2))
    .attr("fill", options.color)
    .on("mouseover", function (d)
    div.transition()
    .duration(200)
    .style("opacity", .9);
    div.html(d.y2 + "</br>" + d.x2)
    .style("left", (d3.event.pageX) + "px")
    .style("top", (d3.event.pageY - 28) + "px");
    )
    .on("mouseout", function (d)
    div.transition()
    .duration(500)
    .style("opacity", 0);
    );



    var burnchart = function(xlabels, burnlist, totallist)
    const svg = d3.select('svg'),
    margin = top: 20, right: 40, bottom: 75, left: 50,
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

    // x-axis
    const x = d3.scalePoint()
    .domain(xlabels)
    .range([0, width]);
    g.append("g")
    .attr("transform", `translate(0,$height)`)
    .call(d3.axisBottom(x))
    .selectAll("text")
    .style("text-anchor", "end")
    .attr("transform", "rotate(-65)")
    .attr("y", 4)
    .attr("x", -10)
    .attr("dy", ".35em");

    // y-axis
    const y = d3.scaleLinear()
    .domain([0, 1.05 * Math.max(d3.max(burnlist), d3.max(totallist))])
    .range([height, 0])
    .nice();
    g.append("g")
    .call(d3.axisLeft(y));

    // total
    drawLine(xlabels, totallist, g, x, y,

    color: "#000000",
    circles: true,
    id: "totallist"
    );

    // burn
    drawLine(xlabels, burnlist, g, x, y,

    color: "#3c763d",
    circles: true,
    id: "burnlist"
    );


    d3.select('#charts')
    .selectAll('svg')
    .data([burnuplist])
    .enter()
    .append('svg')
    .attr('width', 860)
    .attr('height', 480)
    .style('margin', '0.5em')
    .style('border', 'solid 2px #eef6fc')
    .call(burnchart(
    burnuplist.map(d => formatTime(new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10)))),
    burnuplist.map(d => d.Burn),
    burnuplist.map(d => d.Total)
    )
    );
    </script>


    Result



    burn-up



    Questions



    • Am I using d3.js correctly?

    • I somehow needed to make an array over the Json data [burnuplist]

    • Maybe out of scope, but is there a better way to change how I handle the Date's?

    • Any general tips on JavaScript styling?

    I don't write much JS, as you can see ;) Any review is welcome.







    share|improve this question























      up vote
      4
      down vote

      favorite









      up vote
      4
      down vote

      favorite











      The task is creating a burn-up chart from Tickets on Trello



      We make use of the Trello-API in C#, to get the tickets (Done/Created/etc...) however that is not what I want reviewed, since this has been done mostly before I joined this project. However I am a bit worried about my JavaScript for the d3.js code I created.



      Code



      <!DOCTYPE html>
      <meta charset="utf-8">
      <title>d3 burn chart</title>
      <div id="charts"></div>

      <style>
      div.tooltip
      position: absolute;
      text-align: center;
      width: 100px;
      height: 32px;
      padding: 2px;
      font: 12px sans-serif;
      background: lightsteelblue;
      border: 0px;
      border-radius: 8px;
      pointer-events: none;

      </style>

      <script src="https://d3js.org/d3.v4.min.js"></script>
      <script>
      const burnuplist = [
      Date: "/Date(1476079569717)/", Total:1, Burn:0 ,
      Date: "/Date(1476684369717)/", Total:23, Burn:2 ,
      Date: "/Date(1477289169717)/", Total:32, Burn:17 ,
      Date: "/Date(1477897569717)/", Total:57, Burn:40 ,
      Date: "/Date(1478502369717)/", Total:74, Burn:56
      ]

      var formatTime = d3.timeFormat("%d-%m-%Y");
      var drawLine = function(xvalues, yvalues, g, xscale, yscale, options)
      // Define the div for the tooltip
      var div = d3.select("body").append("div")
      .attr("class", "tooltip")
      .style("opacity", 0);

      // line parts
      const lineparts = yvalues
      .map((y, i, ar) => (
      i : i,
      x1: xvalues[i - 1],
      x2: xvalues[i],
      y1 : ar[i - 1],
      y2 : y
      ))
      .filter(d => d.i > 0);

      // line
      g.append("g")
      .attr("id", options.id)
      .selectAll("line")
      .data(lineparts)
      .enter()
      .append("line")
      .attr("x1", d => xscale(d.x1))
      .attr("x2", d => xscale(d.x2))
      .attr("y1", d => yscale(d.y1))
      .attr("y2", d => yscale(d.y2))
      .attr("stroke", options.color);

      if (options.circles)
      g.append("g")
      .selectAll("circle")
      .data(lineparts)
      .enter()
      .append("circle")
      .attr("r", 5)
      .attr("cx", d => xscale(d.x2))
      .attr("cy", d => yscale(d.y2))
      .attr("fill", options.color)
      .on("mouseover", function (d)
      div.transition()
      .duration(200)
      .style("opacity", .9);
      div.html(d.y2 + "</br>" + d.x2)
      .style("left", (d3.event.pageX) + "px")
      .style("top", (d3.event.pageY - 28) + "px");
      )
      .on("mouseout", function (d)
      div.transition()
      .duration(500)
      .style("opacity", 0);
      );



      var burnchart = function(xlabels, burnlist, totallist)
      const svg = d3.select('svg'),
      margin = top: 20, right: 40, bottom: 75, left: 50,
      width = +svg.attr("width") - margin.left - margin.right,
      height = +svg.attr("height") - margin.top - margin.bottom,
      g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

      // x-axis
      const x = d3.scalePoint()
      .domain(xlabels)
      .range([0, width]);
      g.append("g")
      .attr("transform", `translate(0,$height)`)
      .call(d3.axisBottom(x))
      .selectAll("text")
      .style("text-anchor", "end")
      .attr("transform", "rotate(-65)")
      .attr("y", 4)
      .attr("x", -10)
      .attr("dy", ".35em");

      // y-axis
      const y = d3.scaleLinear()
      .domain([0, 1.05 * Math.max(d3.max(burnlist), d3.max(totallist))])
      .range([height, 0])
      .nice();
      g.append("g")
      .call(d3.axisLeft(y));

      // total
      drawLine(xlabels, totallist, g, x, y,

      color: "#000000",
      circles: true,
      id: "totallist"
      );

      // burn
      drawLine(xlabels, burnlist, g, x, y,

      color: "#3c763d",
      circles: true,
      id: "burnlist"
      );


      d3.select('#charts')
      .selectAll('svg')
      .data([burnuplist])
      .enter()
      .append('svg')
      .attr('width', 860)
      .attr('height', 480)
      .style('margin', '0.5em')
      .style('border', 'solid 2px #eef6fc')
      .call(burnchart(
      burnuplist.map(d => formatTime(new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10)))),
      burnuplist.map(d => d.Burn),
      burnuplist.map(d => d.Total)
      )
      );
      </script>


      Result



      burn-up



      Questions



      • Am I using d3.js correctly?

      • I somehow needed to make an array over the Json data [burnuplist]

      • Maybe out of scope, but is there a better way to change how I handle the Date's?

      • Any general tips on JavaScript styling?

      I don't write much JS, as you can see ;) Any review is welcome.







      share|improve this question













      The task is creating a burn-up chart from Tickets on Trello



      We make use of the Trello-API in C#, to get the tickets (Done/Created/etc...) however that is not what I want reviewed, since this has been done mostly before I joined this project. However I am a bit worried about my JavaScript for the d3.js code I created.



      Code



      <!DOCTYPE html>
      <meta charset="utf-8">
      <title>d3 burn chart</title>
      <div id="charts"></div>

      <style>
      div.tooltip
      position: absolute;
      text-align: center;
      width: 100px;
      height: 32px;
      padding: 2px;
      font: 12px sans-serif;
      background: lightsteelblue;
      border: 0px;
      border-radius: 8px;
      pointer-events: none;

      </style>

      <script src="https://d3js.org/d3.v4.min.js"></script>
      <script>
      const burnuplist = [
      Date: "/Date(1476079569717)/", Total:1, Burn:0 ,
      Date: "/Date(1476684369717)/", Total:23, Burn:2 ,
      Date: "/Date(1477289169717)/", Total:32, Burn:17 ,
      Date: "/Date(1477897569717)/", Total:57, Burn:40 ,
      Date: "/Date(1478502369717)/", Total:74, Burn:56
      ]

      var formatTime = d3.timeFormat("%d-%m-%Y");
      var drawLine = function(xvalues, yvalues, g, xscale, yscale, options)
      // Define the div for the tooltip
      var div = d3.select("body").append("div")
      .attr("class", "tooltip")
      .style("opacity", 0);

      // line parts
      const lineparts = yvalues
      .map((y, i, ar) => (
      i : i,
      x1: xvalues[i - 1],
      x2: xvalues[i],
      y1 : ar[i - 1],
      y2 : y
      ))
      .filter(d => d.i > 0);

      // line
      g.append("g")
      .attr("id", options.id)
      .selectAll("line")
      .data(lineparts)
      .enter()
      .append("line")
      .attr("x1", d => xscale(d.x1))
      .attr("x2", d => xscale(d.x2))
      .attr("y1", d => yscale(d.y1))
      .attr("y2", d => yscale(d.y2))
      .attr("stroke", options.color);

      if (options.circles)
      g.append("g")
      .selectAll("circle")
      .data(lineparts)
      .enter()
      .append("circle")
      .attr("r", 5)
      .attr("cx", d => xscale(d.x2))
      .attr("cy", d => yscale(d.y2))
      .attr("fill", options.color)
      .on("mouseover", function (d)
      div.transition()
      .duration(200)
      .style("opacity", .9);
      div.html(d.y2 + "</br>" + d.x2)
      .style("left", (d3.event.pageX) + "px")
      .style("top", (d3.event.pageY - 28) + "px");
      )
      .on("mouseout", function (d)
      div.transition()
      .duration(500)
      .style("opacity", 0);
      );



      var burnchart = function(xlabels, burnlist, totallist)
      const svg = d3.select('svg'),
      margin = top: 20, right: 40, bottom: 75, left: 50,
      width = +svg.attr("width") - margin.left - margin.right,
      height = +svg.attr("height") - margin.top - margin.bottom,
      g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

      // x-axis
      const x = d3.scalePoint()
      .domain(xlabels)
      .range([0, width]);
      g.append("g")
      .attr("transform", `translate(0,$height)`)
      .call(d3.axisBottom(x))
      .selectAll("text")
      .style("text-anchor", "end")
      .attr("transform", "rotate(-65)")
      .attr("y", 4)
      .attr("x", -10)
      .attr("dy", ".35em");

      // y-axis
      const y = d3.scaleLinear()
      .domain([0, 1.05 * Math.max(d3.max(burnlist), d3.max(totallist))])
      .range([height, 0])
      .nice();
      g.append("g")
      .call(d3.axisLeft(y));

      // total
      drawLine(xlabels, totallist, g, x, y,

      color: "#000000",
      circles: true,
      id: "totallist"
      );

      // burn
      drawLine(xlabels, burnlist, g, x, y,

      color: "#3c763d",
      circles: true,
      id: "burnlist"
      );


      d3.select('#charts')
      .selectAll('svg')
      .data([burnuplist])
      .enter()
      .append('svg')
      .attr('width', 860)
      .attr('height', 480)
      .style('margin', '0.5em')
      .style('border', 'solid 2px #eef6fc')
      .call(burnchart(
      burnuplist.map(d => formatTime(new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10)))),
      burnuplist.map(d => d.Burn),
      burnuplist.map(d => d.Total)
      )
      );
      </script>


      Result



      burn-up



      Questions



      • Am I using d3.js correctly?

      • I somehow needed to make an array over the Json data [burnuplist]

      • Maybe out of scope, but is there a better way to change how I handle the Date's?

      • Any general tips on JavaScript styling?

      I don't write much JS, as you can see ;) Any review is welcome.









      share|improve this question












      share|improve this question




      share|improve this question








      edited Jan 26 at 0:36









      Sam Onela

      5,88461545




      5,88461545









      asked Jan 25 at 14:04









      Ludisposed

      5,77621758




      5,77621758




















          2 Answers
          2






          active

          oldest

          votes

















          up vote
          3
          down vote



          accepted










          First of all, your code is working, which is a good thing. However... for any seasoned D3 developer your code is somewhat weird: it's not idiomatic and it has some strange patterns (which will make your life harder in the future).



          Here I'll deal with your 4 bullet-point questions together, since they are deeply interconnected.



          So, let's first see the major problems:



          1. You're binding the data to the SVG in an enter selection, but you never use it. So, just append a single SVG, without binding any data to it.

          2. Drop the burnChart and the drawLine functions. If you want to create a single drawing function, which is a good approach (regarding DRY), pass just the changing data to it, avoiding repetitions (the scales are always the same, for instance). But in a chart like this one such pattern is unnecessary (have a look at the each() in my last snippet).

          3. You're binding a single array of numbers both to the lines and to the circles. Instead of that, just filter the burnuplist array accordingly (or create two separate data arrays, as I'm doing here), and bind the whole array of objects to the circles and the path (which brings us to the next point). That way, each element has a datum with all properties in the object, that we can access easily. More importantly, that way you don't rely on the index of each element in the array as you're doing right now and which is an inadequate approach.

          4. Don't append a bunch of <line> elements to create a line chart. D3 has line generators that you can easily use to create a <path> element: the whole line will be a single SVG path. This is way more convenient, and on top of that you can use curves to smooth the path.

          5. You have time in the x axis. However, you're converting the date objects to strings and using a point scale with those strings as the domain, which is probably not the best practice. Treat time as time, using a time scale, unless you really want to treat each individual moment as a categorical variable (for instance, when the time span doesn't matter and you want the space between the ticks to be the same, regardless the actual time).

          Some minor problems:



          1. Name all your selections. It's easier if you want to use them in the future.

          2. Give meaningful name to your variables, like xScale, not just x.

          3. You're mixing const and var, which may seem strange fome some people, as well as arrow functions with regular functions.

          4. Use lowercase for the properties: date instead of Date, total instead of Total etc...

          All that being said, this is my version of your code. It's a major refactor (actually, I wrote it from scratch).






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var totalLine = g.append("path")
          .datum(totalData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#000");

          var burnLine = g.append("path")
          .datum(burnData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#3c763d");

          var totalCircles = g.selectAll(null)
          .data(totalData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#000");

          var burnCircles = g.selectAll(null)
          .data(burnData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#3c763d");

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

          div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>





          The totalLine, burnLine, totalCircles and burnCircles may seem a lot of repetition, but this is idiomatic D3. If it bothers you, just use an each() for DRY, like this:






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var groups = g.selectAll(null)
          .data([totalData, burnData])
          .enter()
          .append("g")
          .each(function(d, i)
          var line = d3.select(this).append("path")
          .datum(d)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", i ? "#3c763d" : "#000");

          var circles = g.selectAll(null)
          .data(d)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", i ? "#3c763d" : "#000");
          )

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

           div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>








          share|improve this answer























          • This is really good! Actually this is my first encounter with the d3.js library. I was hoping for an answer like this, showing me where I did horribly wrong.
            – Ludisposed
            Jan 26 at 13:08











          • @Ludisposed You didn't do horribly wrong... it works. It's just not idiomatic. When you use a library/language, the idiomatic use not only facilitates understanding codes and tutorials but also allows you to improve the code, adding complexity without things breaking apart.
            – Gerardo Furtado
            Jan 26 at 13:15










          • For instance, every now and then I see people using loops (for, forEach, while...) to append elements in a D3 code. That may even work, but not only it can go wrong in several situations as also it hinders the proper learning of D3.
            – Gerardo Furtado
            Jan 26 at 13:36


















          up vote
          2
          down vote













          Since Gerardo has already given a great review and focused on d3 code, I would like to focus on the Javascript code.



          Abstracting opacity setting code - D.R.Y.



          Per the D.R.Y. principle, the code to set the opacity on the tooltip could be abstracted to a function which accepts the duration and opacity. Building on Gerardo's advice, the tooltip element could also have a more appropriate name, like tooltipContainer (unless you're into that whole brevity thing... then maybe just tooltip, though that could be confusing with the CSS class).



          var tooltipContainer = d3.select("body").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);


          Gerardo pointed out the mix of const and var usage. If you wanted to keep the strict usage of const then that container could be declared using const.



          Then after that, a function can be defined that sets the opacity



          var transitionTooltipToOpacity = function(duration, opacity) 
          tooltipContainer.transition()
          .duration(duration)
          .style("opacity", opacity);
          ;


          Then the mouseover and mouseout event handlers can utilize that function.
          The following lines:




          .on("mouseover", function (d) 
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function (d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );



          can be simplified to the following:



          .on("mouseover", function (d) 
          transitionTooltipToOpacity(200, .9);
          tooltipContainer.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", transitionTooltipToOpacity.bind(null, 500, 0));


          Notice that the mouseout handler has a partial function, which is created using Function.bind(). That way, the function will be called with the duration and opacity values pre-set (and the other arguments that are passed to the event handlers like the current datum d, the index i, etc. are also passed to that function in this case).



          CSS border



          Bearing in mind that you mostly wanted feedback on the Javascript, I noticed that the stylesheet for the tooltip contains: border: 0px. I don't believe that is necessary, unless there is some other style declaration that would need to be overridden. If it is required, then one can omit the units (i.e. px) or use none. While it is over 9 years old (and about ems), this SO question seems relevant.






          share|improve this answer

















          • 1




            Good advice regarding the names. I myself use pretty long names, like var horizontalAxisTickValuesFiltered and so on.... sometimes too long!
            – Gerardo Furtado
            Jan 26 at 9:21











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185975%2fburnup-charts-in-d3-js%23new-answer', 'question_page');

          );

          Post as a guest






























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          3
          down vote



          accepted










          First of all, your code is working, which is a good thing. However... for any seasoned D3 developer your code is somewhat weird: it's not idiomatic and it has some strange patterns (which will make your life harder in the future).



          Here I'll deal with your 4 bullet-point questions together, since they are deeply interconnected.



          So, let's first see the major problems:



          1. You're binding the data to the SVG in an enter selection, but you never use it. So, just append a single SVG, without binding any data to it.

          2. Drop the burnChart and the drawLine functions. If you want to create a single drawing function, which is a good approach (regarding DRY), pass just the changing data to it, avoiding repetitions (the scales are always the same, for instance). But in a chart like this one such pattern is unnecessary (have a look at the each() in my last snippet).

          3. You're binding a single array of numbers both to the lines and to the circles. Instead of that, just filter the burnuplist array accordingly (or create two separate data arrays, as I'm doing here), and bind the whole array of objects to the circles and the path (which brings us to the next point). That way, each element has a datum with all properties in the object, that we can access easily. More importantly, that way you don't rely on the index of each element in the array as you're doing right now and which is an inadequate approach.

          4. Don't append a bunch of <line> elements to create a line chart. D3 has line generators that you can easily use to create a <path> element: the whole line will be a single SVG path. This is way more convenient, and on top of that you can use curves to smooth the path.

          5. You have time in the x axis. However, you're converting the date objects to strings and using a point scale with those strings as the domain, which is probably not the best practice. Treat time as time, using a time scale, unless you really want to treat each individual moment as a categorical variable (for instance, when the time span doesn't matter and you want the space between the ticks to be the same, regardless the actual time).

          Some minor problems:



          1. Name all your selections. It's easier if you want to use them in the future.

          2. Give meaningful name to your variables, like xScale, not just x.

          3. You're mixing const and var, which may seem strange fome some people, as well as arrow functions with regular functions.

          4. Use lowercase for the properties: date instead of Date, total instead of Total etc...

          All that being said, this is my version of your code. It's a major refactor (actually, I wrote it from scratch).






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var totalLine = g.append("path")
          .datum(totalData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#000");

          var burnLine = g.append("path")
          .datum(burnData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#3c763d");

          var totalCircles = g.selectAll(null)
          .data(totalData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#000");

          var burnCircles = g.selectAll(null)
          .data(burnData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#3c763d");

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

          div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>





          The totalLine, burnLine, totalCircles and burnCircles may seem a lot of repetition, but this is idiomatic D3. If it bothers you, just use an each() for DRY, like this:






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var groups = g.selectAll(null)
          .data([totalData, burnData])
          .enter()
          .append("g")
          .each(function(d, i)
          var line = d3.select(this).append("path")
          .datum(d)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", i ? "#3c763d" : "#000");

          var circles = g.selectAll(null)
          .data(d)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", i ? "#3c763d" : "#000");
          )

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

           div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>








          share|improve this answer























          • This is really good! Actually this is my first encounter with the d3.js library. I was hoping for an answer like this, showing me where I did horribly wrong.
            – Ludisposed
            Jan 26 at 13:08











          • @Ludisposed You didn't do horribly wrong... it works. It's just not idiomatic. When you use a library/language, the idiomatic use not only facilitates understanding codes and tutorials but also allows you to improve the code, adding complexity without things breaking apart.
            – Gerardo Furtado
            Jan 26 at 13:15










          • For instance, every now and then I see people using loops (for, forEach, while...) to append elements in a D3 code. That may even work, but not only it can go wrong in several situations as also it hinders the proper learning of D3.
            – Gerardo Furtado
            Jan 26 at 13:36















          up vote
          3
          down vote



          accepted










          First of all, your code is working, which is a good thing. However... for any seasoned D3 developer your code is somewhat weird: it's not idiomatic and it has some strange patterns (which will make your life harder in the future).



          Here I'll deal with your 4 bullet-point questions together, since they are deeply interconnected.



          So, let's first see the major problems:



          1. You're binding the data to the SVG in an enter selection, but you never use it. So, just append a single SVG, without binding any data to it.

          2. Drop the burnChart and the drawLine functions. If you want to create a single drawing function, which is a good approach (regarding DRY), pass just the changing data to it, avoiding repetitions (the scales are always the same, for instance). But in a chart like this one such pattern is unnecessary (have a look at the each() in my last snippet).

          3. You're binding a single array of numbers both to the lines and to the circles. Instead of that, just filter the burnuplist array accordingly (or create two separate data arrays, as I'm doing here), and bind the whole array of objects to the circles and the path (which brings us to the next point). That way, each element has a datum with all properties in the object, that we can access easily. More importantly, that way you don't rely on the index of each element in the array as you're doing right now and which is an inadequate approach.

          4. Don't append a bunch of <line> elements to create a line chart. D3 has line generators that you can easily use to create a <path> element: the whole line will be a single SVG path. This is way more convenient, and on top of that you can use curves to smooth the path.

          5. You have time in the x axis. However, you're converting the date objects to strings and using a point scale with those strings as the domain, which is probably not the best practice. Treat time as time, using a time scale, unless you really want to treat each individual moment as a categorical variable (for instance, when the time span doesn't matter and you want the space between the ticks to be the same, regardless the actual time).

          Some minor problems:



          1. Name all your selections. It's easier if you want to use them in the future.

          2. Give meaningful name to your variables, like xScale, not just x.

          3. You're mixing const and var, which may seem strange fome some people, as well as arrow functions with regular functions.

          4. Use lowercase for the properties: date instead of Date, total instead of Total etc...

          All that being said, this is my version of your code. It's a major refactor (actually, I wrote it from scratch).






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var totalLine = g.append("path")
          .datum(totalData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#000");

          var burnLine = g.append("path")
          .datum(burnData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#3c763d");

          var totalCircles = g.selectAll(null)
          .data(totalData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#000");

          var burnCircles = g.selectAll(null)
          .data(burnData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#3c763d");

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

          div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>





          The totalLine, burnLine, totalCircles and burnCircles may seem a lot of repetition, but this is idiomatic D3. If it bothers you, just use an each() for DRY, like this:






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var groups = g.selectAll(null)
          .data([totalData, burnData])
          .enter()
          .append("g")
          .each(function(d, i)
          var line = d3.select(this).append("path")
          .datum(d)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", i ? "#3c763d" : "#000");

          var circles = g.selectAll(null)
          .data(d)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", i ? "#3c763d" : "#000");
          )

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

           div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>








          share|improve this answer























          • This is really good! Actually this is my first encounter with the d3.js library. I was hoping for an answer like this, showing me where I did horribly wrong.
            – Ludisposed
            Jan 26 at 13:08











          • @Ludisposed You didn't do horribly wrong... it works. It's just not idiomatic. When you use a library/language, the idiomatic use not only facilitates understanding codes and tutorials but also allows you to improve the code, adding complexity without things breaking apart.
            – Gerardo Furtado
            Jan 26 at 13:15










          • For instance, every now and then I see people using loops (for, forEach, while...) to append elements in a D3 code. That may even work, but not only it can go wrong in several situations as also it hinders the proper learning of D3.
            – Gerardo Furtado
            Jan 26 at 13:36













          up vote
          3
          down vote



          accepted







          up vote
          3
          down vote



          accepted






          First of all, your code is working, which is a good thing. However... for any seasoned D3 developer your code is somewhat weird: it's not idiomatic and it has some strange patterns (which will make your life harder in the future).



          Here I'll deal with your 4 bullet-point questions together, since they are deeply interconnected.



          So, let's first see the major problems:



          1. You're binding the data to the SVG in an enter selection, but you never use it. So, just append a single SVG, without binding any data to it.

          2. Drop the burnChart and the drawLine functions. If you want to create a single drawing function, which is a good approach (regarding DRY), pass just the changing data to it, avoiding repetitions (the scales are always the same, for instance). But in a chart like this one such pattern is unnecessary (have a look at the each() in my last snippet).

          3. You're binding a single array of numbers both to the lines and to the circles. Instead of that, just filter the burnuplist array accordingly (or create two separate data arrays, as I'm doing here), and bind the whole array of objects to the circles and the path (which brings us to the next point). That way, each element has a datum with all properties in the object, that we can access easily. More importantly, that way you don't rely on the index of each element in the array as you're doing right now and which is an inadequate approach.

          4. Don't append a bunch of <line> elements to create a line chart. D3 has line generators that you can easily use to create a <path> element: the whole line will be a single SVG path. This is way more convenient, and on top of that you can use curves to smooth the path.

          5. You have time in the x axis. However, you're converting the date objects to strings and using a point scale with those strings as the domain, which is probably not the best practice. Treat time as time, using a time scale, unless you really want to treat each individual moment as a categorical variable (for instance, when the time span doesn't matter and you want the space between the ticks to be the same, regardless the actual time).

          Some minor problems:



          1. Name all your selections. It's easier if you want to use them in the future.

          2. Give meaningful name to your variables, like xScale, not just x.

          3. You're mixing const and var, which may seem strange fome some people, as well as arrow functions with regular functions.

          4. Use lowercase for the properties: date instead of Date, total instead of Total etc...

          All that being said, this is my version of your code. It's a major refactor (actually, I wrote it from scratch).






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var totalLine = g.append("path")
          .datum(totalData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#000");

          var burnLine = g.append("path")
          .datum(burnData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#3c763d");

          var totalCircles = g.selectAll(null)
          .data(totalData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#000");

          var burnCircles = g.selectAll(null)
          .data(burnData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#3c763d");

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

          div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>





          The totalLine, burnLine, totalCircles and burnCircles may seem a lot of repetition, but this is idiomatic D3. If it bothers you, just use an each() for DRY, like this:






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var groups = g.selectAll(null)
          .data([totalData, burnData])
          .enter()
          .append("g")
          .each(function(d, i)
          var line = d3.select(this).append("path")
          .datum(d)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", i ? "#3c763d" : "#000");

          var circles = g.selectAll(null)
          .data(d)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", i ? "#3c763d" : "#000");
          )

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

           div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>








          share|improve this answer















          First of all, your code is working, which is a good thing. However... for any seasoned D3 developer your code is somewhat weird: it's not idiomatic and it has some strange patterns (which will make your life harder in the future).



          Here I'll deal with your 4 bullet-point questions together, since they are deeply interconnected.



          So, let's first see the major problems:



          1. You're binding the data to the SVG in an enter selection, but you never use it. So, just append a single SVG, without binding any data to it.

          2. Drop the burnChart and the drawLine functions. If you want to create a single drawing function, which is a good approach (regarding DRY), pass just the changing data to it, avoiding repetitions (the scales are always the same, for instance). But in a chart like this one such pattern is unnecessary (have a look at the each() in my last snippet).

          3. You're binding a single array of numbers both to the lines and to the circles. Instead of that, just filter the burnuplist array accordingly (or create two separate data arrays, as I'm doing here), and bind the whole array of objects to the circles and the path (which brings us to the next point). That way, each element has a datum with all properties in the object, that we can access easily. More importantly, that way you don't rely on the index of each element in the array as you're doing right now and which is an inadequate approach.

          4. Don't append a bunch of <line> elements to create a line chart. D3 has line generators that you can easily use to create a <path> element: the whole line will be a single SVG path. This is way more convenient, and on top of that you can use curves to smooth the path.

          5. You have time in the x axis. However, you're converting the date objects to strings and using a point scale with those strings as the domain, which is probably not the best practice. Treat time as time, using a time scale, unless you really want to treat each individual moment as a categorical variable (for instance, when the time span doesn't matter and you want the space between the ticks to be the same, regardless the actual time).

          Some minor problems:



          1. Name all your selections. It's easier if you want to use them in the future.

          2. Give meaningful name to your variables, like xScale, not just x.

          3. You're mixing const and var, which may seem strange fome some people, as well as arrow functions with regular functions.

          4. Use lowercase for the properties: date instead of Date, total instead of Total etc...

          All that being said, this is my version of your code. It's a major refactor (actually, I wrote it from scratch).






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var totalLine = g.append("path")
          .datum(totalData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#000");

          var burnLine = g.append("path")
          .datum(burnData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#3c763d");

          var totalCircles = g.selectAll(null)
          .data(totalData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#000");

          var burnCircles = g.selectAll(null)
          .data(burnData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#3c763d");

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

          div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>





          The totalLine, burnLine, totalCircles and burnCircles may seem a lot of repetition, but this is idiomatic D3. If it bothers you, just use an each() for DRY, like this:






          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var groups = g.selectAll(null)
          .data([totalData, burnData])
          .enter()
          .append("g")
          .each(function(d, i)
          var line = d3.select(this).append("path")
          .datum(d)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", i ? "#3c763d" : "#000");

          var circles = g.selectAll(null)
          .data(d)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", i ? "#3c763d" : "#000");
          )

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

           div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>








          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var totalLine = g.append("path")
          .datum(totalData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#000");

          var burnLine = g.append("path")
          .datum(burnData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#3c763d");

          var totalCircles = g.selectAll(null)
          .data(totalData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#000");

          var burnCircles = g.selectAll(null)
          .data(burnData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#3c763d");

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

          div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>





          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var totalLine = g.append("path")
          .datum(totalData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#000");

          var burnLine = g.append("path")
          .datum(burnData)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", "#3c763d");

          var totalCircles = g.selectAll(null)
          .data(totalData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#000");

          var burnCircles = g.selectAll(null)
          .data(burnData)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", "#3c763d");

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

          div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>





          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var groups = g.selectAll(null)
          .data([totalData, burnData])
          .enter()
          .append("g")
          .each(function(d, i)
          var line = d3.select(this).append("path")
          .datum(d)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", i ? "#3c763d" : "#000");

          var circles = g.selectAll(null)
          .data(d)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", i ? "#3c763d" : "#000");
          )

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

           div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>





          var svg = d3.select('#charts')
          .append('svg')
          .attr('width', 860)
          .attr('height', 480)
          .style('margin', '0.5em')
          .style('border', 'solid 2px #eef6fc');

          var div = d3.select("#charts").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);

          var burnuplist = [
          Date: "/Date(1476079569717)/",
          Total: 1,
          Burn: 0
          ,
          Date: "/Date(1476684369717)/",
          Total: 23,
          Burn: 2
          ,
          Date: "/Date(1477289169717)/",
          Total: 32,
          Burn: 17
          ,
          Date: "/Date(1477897569717)/",
          Total: 57,
          Burn: 40
          ,
          Date: "/Date(1478502369717)/",
          Total: 74,
          Burn: 56
          ];

          var margin =
          top: 20,
          right: 40,
          bottom: 75,
          left: 50
          ,
          width = +svg.attr("width") - margin.left - margin.right,
          height = +svg.attr("height") - margin.top - margin.bottom,
          g = svg.append("g").attr("transform", `translate($margin.left,$margin.top)`);

          var formatTime = d3.timeFormat("%d-%m-%Y");

          burnuplist.forEach(function(d)
          d.Date = new Date(parseInt(d.Date.replace("/Date(", "").replace(")/", ""), 10))
          );

          var totalData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Total

          );

          var burnData = burnuplist.map(function(d)
          return
          date: d.Date,
          value: d.Burn

          );

          var xScale = d3.scaleTime()
          .range([0, width])
          .domain(d3.extent(burnuplist, function(d)
          return d.Date
          ));


          var yScale = d3.scaleLinear()
          .range([height, 0])
          .domain([0, d3.max(burnuplist, function(d)
          return d.Total
          ) * 1.05]);

          var lineGenerator = d3.line()
          .x(function(d)
          return xScale(d.date)
          )
          .y(function(d)
          return yScale(d.value)
          );

          var gX = g.append("g")
          .attr("transform", `translate(0,$height)`)
          .call(d3.axisBottom(xScale).tickFormat(function(d)
          return formatTime(d)
          ).tickValues(burnuplist.map(function(d)
          return d.Date
          )))
          .selectAll("text")
          .style("text-anchor", "end")
          .attr("transform", "rotate(-65)")
          .attr("y", 4)
          .attr("x", -10)
          .attr("dy", ".35em");

          var gY = g.append("g")
          .call(d3.axisLeft(yScale));

          var groups = g.selectAll(null)
          .data([totalData, burnData])
          .enter()
          .append("g")
          .each(function(d, i)
          var line = d3.select(this).append("path")
          .datum(d)
          .attr("d", lineGenerator)
          .style("fill", "none")
          .style("stroke", i ? "#3c763d" : "#000");

          var circles = g.selectAll(null)
          .data(d)
          .enter()
          .append("circle")
          .attr("r", 5)
          .attr("cx", function(d)
          return xScale(d.date)
          )
          .attr("cy", function(d)
          return yScale(d.value)
          )
          .style("fill", i ? "#3c763d" : "#000");
          )

          d3.selectAll("circle").on("mouseover", function(d)
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html("Value: " + d.value + "</br>Date: " + formatTime(d.date))
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function(d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );

           div.tooltip 
          position: absolute;
          text-align: center;
          width: 100px;
          height: 32px;
          padding: 2px;
          font: 12px sans-serif;
          background: lightsteelblue;
          border: 0px;
          border-radius: 8px;
          pointer-events: none;

          <script src="https://d3js.org/d3.v4.min.js"></script>
          <div id="charts"></div>






          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited Jan 26 at 2:55


























          answered Jan 26 at 0:16









          Gerardo Furtado

          1,1442420




          1,1442420











          • This is really good! Actually this is my first encounter with the d3.js library. I was hoping for an answer like this, showing me where I did horribly wrong.
            – Ludisposed
            Jan 26 at 13:08











          • @Ludisposed You didn't do horribly wrong... it works. It's just not idiomatic. When you use a library/language, the idiomatic use not only facilitates understanding codes and tutorials but also allows you to improve the code, adding complexity without things breaking apart.
            – Gerardo Furtado
            Jan 26 at 13:15










          • For instance, every now and then I see people using loops (for, forEach, while...) to append elements in a D3 code. That may even work, but not only it can go wrong in several situations as also it hinders the proper learning of D3.
            – Gerardo Furtado
            Jan 26 at 13:36

















          • This is really good! Actually this is my first encounter with the d3.js library. I was hoping for an answer like this, showing me where I did horribly wrong.
            – Ludisposed
            Jan 26 at 13:08











          • @Ludisposed You didn't do horribly wrong... it works. It's just not idiomatic. When you use a library/language, the idiomatic use not only facilitates understanding codes and tutorials but also allows you to improve the code, adding complexity without things breaking apart.
            – Gerardo Furtado
            Jan 26 at 13:15










          • For instance, every now and then I see people using loops (for, forEach, while...) to append elements in a D3 code. That may even work, but not only it can go wrong in several situations as also it hinders the proper learning of D3.
            – Gerardo Furtado
            Jan 26 at 13:36
















          This is really good! Actually this is my first encounter with the d3.js library. I was hoping for an answer like this, showing me where I did horribly wrong.
          – Ludisposed
          Jan 26 at 13:08





          This is really good! Actually this is my first encounter with the d3.js library. I was hoping for an answer like this, showing me where I did horribly wrong.
          – Ludisposed
          Jan 26 at 13:08













          @Ludisposed You didn't do horribly wrong... it works. It's just not idiomatic. When you use a library/language, the idiomatic use not only facilitates understanding codes and tutorials but also allows you to improve the code, adding complexity without things breaking apart.
          – Gerardo Furtado
          Jan 26 at 13:15




          @Ludisposed You didn't do horribly wrong... it works. It's just not idiomatic. When you use a library/language, the idiomatic use not only facilitates understanding codes and tutorials but also allows you to improve the code, adding complexity without things breaking apart.
          – Gerardo Furtado
          Jan 26 at 13:15












          For instance, every now and then I see people using loops (for, forEach, while...) to append elements in a D3 code. That may even work, but not only it can go wrong in several situations as also it hinders the proper learning of D3.
          – Gerardo Furtado
          Jan 26 at 13:36





          For instance, every now and then I see people using loops (for, forEach, while...) to append elements in a D3 code. That may even work, but not only it can go wrong in several situations as also it hinders the proper learning of D3.
          – Gerardo Furtado
          Jan 26 at 13:36













          up vote
          2
          down vote













          Since Gerardo has already given a great review and focused on d3 code, I would like to focus on the Javascript code.



          Abstracting opacity setting code - D.R.Y.



          Per the D.R.Y. principle, the code to set the opacity on the tooltip could be abstracted to a function which accepts the duration and opacity. Building on Gerardo's advice, the tooltip element could also have a more appropriate name, like tooltipContainer (unless you're into that whole brevity thing... then maybe just tooltip, though that could be confusing with the CSS class).



          var tooltipContainer = d3.select("body").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);


          Gerardo pointed out the mix of const and var usage. If you wanted to keep the strict usage of const then that container could be declared using const.



          Then after that, a function can be defined that sets the opacity



          var transitionTooltipToOpacity = function(duration, opacity) 
          tooltipContainer.transition()
          .duration(duration)
          .style("opacity", opacity);
          ;


          Then the mouseover and mouseout event handlers can utilize that function.
          The following lines:




          .on("mouseover", function (d) 
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function (d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );



          can be simplified to the following:



          .on("mouseover", function (d) 
          transitionTooltipToOpacity(200, .9);
          tooltipContainer.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", transitionTooltipToOpacity.bind(null, 500, 0));


          Notice that the mouseout handler has a partial function, which is created using Function.bind(). That way, the function will be called with the duration and opacity values pre-set (and the other arguments that are passed to the event handlers like the current datum d, the index i, etc. are also passed to that function in this case).



          CSS border



          Bearing in mind that you mostly wanted feedback on the Javascript, I noticed that the stylesheet for the tooltip contains: border: 0px. I don't believe that is necessary, unless there is some other style declaration that would need to be overridden. If it is required, then one can omit the units (i.e. px) or use none. While it is over 9 years old (and about ems), this SO question seems relevant.






          share|improve this answer

















          • 1




            Good advice regarding the names. I myself use pretty long names, like var horizontalAxisTickValuesFiltered and so on.... sometimes too long!
            – Gerardo Furtado
            Jan 26 at 9:21















          up vote
          2
          down vote













          Since Gerardo has already given a great review and focused on d3 code, I would like to focus on the Javascript code.



          Abstracting opacity setting code - D.R.Y.



          Per the D.R.Y. principle, the code to set the opacity on the tooltip could be abstracted to a function which accepts the duration and opacity. Building on Gerardo's advice, the tooltip element could also have a more appropriate name, like tooltipContainer (unless you're into that whole brevity thing... then maybe just tooltip, though that could be confusing with the CSS class).



          var tooltipContainer = d3.select("body").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);


          Gerardo pointed out the mix of const and var usage. If you wanted to keep the strict usage of const then that container could be declared using const.



          Then after that, a function can be defined that sets the opacity



          var transitionTooltipToOpacity = function(duration, opacity) 
          tooltipContainer.transition()
          .duration(duration)
          .style("opacity", opacity);
          ;


          Then the mouseover and mouseout event handlers can utilize that function.
          The following lines:




          .on("mouseover", function (d) 
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function (d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );



          can be simplified to the following:



          .on("mouseover", function (d) 
          transitionTooltipToOpacity(200, .9);
          tooltipContainer.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", transitionTooltipToOpacity.bind(null, 500, 0));


          Notice that the mouseout handler has a partial function, which is created using Function.bind(). That way, the function will be called with the duration and opacity values pre-set (and the other arguments that are passed to the event handlers like the current datum d, the index i, etc. are also passed to that function in this case).



          CSS border



          Bearing in mind that you mostly wanted feedback on the Javascript, I noticed that the stylesheet for the tooltip contains: border: 0px. I don't believe that is necessary, unless there is some other style declaration that would need to be overridden. If it is required, then one can omit the units (i.e. px) or use none. While it is over 9 years old (and about ems), this SO question seems relevant.






          share|improve this answer

















          • 1




            Good advice regarding the names. I myself use pretty long names, like var horizontalAxisTickValuesFiltered and so on.... sometimes too long!
            – Gerardo Furtado
            Jan 26 at 9:21













          up vote
          2
          down vote










          up vote
          2
          down vote









          Since Gerardo has already given a great review and focused on d3 code, I would like to focus on the Javascript code.



          Abstracting opacity setting code - D.R.Y.



          Per the D.R.Y. principle, the code to set the opacity on the tooltip could be abstracted to a function which accepts the duration and opacity. Building on Gerardo's advice, the tooltip element could also have a more appropriate name, like tooltipContainer (unless you're into that whole brevity thing... then maybe just tooltip, though that could be confusing with the CSS class).



          var tooltipContainer = d3.select("body").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);


          Gerardo pointed out the mix of const and var usage. If you wanted to keep the strict usage of const then that container could be declared using const.



          Then after that, a function can be defined that sets the opacity



          var transitionTooltipToOpacity = function(duration, opacity) 
          tooltipContainer.transition()
          .duration(duration)
          .style("opacity", opacity);
          ;


          Then the mouseover and mouseout event handlers can utilize that function.
          The following lines:




          .on("mouseover", function (d) 
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function (d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );



          can be simplified to the following:



          .on("mouseover", function (d) 
          transitionTooltipToOpacity(200, .9);
          tooltipContainer.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", transitionTooltipToOpacity.bind(null, 500, 0));


          Notice that the mouseout handler has a partial function, which is created using Function.bind(). That way, the function will be called with the duration and opacity values pre-set (and the other arguments that are passed to the event handlers like the current datum d, the index i, etc. are also passed to that function in this case).



          CSS border



          Bearing in mind that you mostly wanted feedback on the Javascript, I noticed that the stylesheet for the tooltip contains: border: 0px. I don't believe that is necessary, unless there is some other style declaration that would need to be overridden. If it is required, then one can omit the units (i.e. px) or use none. While it is over 9 years old (and about ems), this SO question seems relevant.






          share|improve this answer













          Since Gerardo has already given a great review and focused on d3 code, I would like to focus on the Javascript code.



          Abstracting opacity setting code - D.R.Y.



          Per the D.R.Y. principle, the code to set the opacity on the tooltip could be abstracted to a function which accepts the duration and opacity. Building on Gerardo's advice, the tooltip element could also have a more appropriate name, like tooltipContainer (unless you're into that whole brevity thing... then maybe just tooltip, though that could be confusing with the CSS class).



          var tooltipContainer = d3.select("body").append("div")
          .attr("class", "tooltip")
          .style("opacity", 0);


          Gerardo pointed out the mix of const and var usage. If you wanted to keep the strict usage of const then that container could be declared using const.



          Then after that, a function can be defined that sets the opacity



          var transitionTooltipToOpacity = function(duration, opacity) 
          tooltipContainer.transition()
          .duration(duration)
          .style("opacity", opacity);
          ;


          Then the mouseover and mouseout event handlers can utilize that function.
          The following lines:




          .on("mouseover", function (d) 
          div.transition()
          .duration(200)
          .style("opacity", .9);
          div.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", function (d)
          div.transition()
          .duration(500)
          .style("opacity", 0);
          );



          can be simplified to the following:



          .on("mouseover", function (d) 
          transitionTooltipToOpacity(200, .9);
          tooltipContainer.html(d.y2 + "</br>" + d.x2)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
          )
          .on("mouseout", transitionTooltipToOpacity.bind(null, 500, 0));


          Notice that the mouseout handler has a partial function, which is created using Function.bind(). That way, the function will be called with the duration and opacity values pre-set (and the other arguments that are passed to the event handlers like the current datum d, the index i, etc. are also passed to that function in this case).



          CSS border



          Bearing in mind that you mostly wanted feedback on the Javascript, I noticed that the stylesheet for the tooltip contains: border: 0px. I don't believe that is necessary, unless there is some other style declaration that would need to be overridden. If it is required, then one can omit the units (i.e. px) or use none. While it is over 9 years old (and about ems), this SO question seems relevant.







          share|improve this answer













          share|improve this answer



          share|improve this answer











          answered Jan 26 at 6:04









          Sam Onela

          5,88461545




          5,88461545







          • 1




            Good advice regarding the names. I myself use pretty long names, like var horizontalAxisTickValuesFiltered and so on.... sometimes too long!
            – Gerardo Furtado
            Jan 26 at 9:21













          • 1




            Good advice regarding the names. I myself use pretty long names, like var horizontalAxisTickValuesFiltered and so on.... sometimes too long!
            – Gerardo Furtado
            Jan 26 at 9:21








          1




          1




          Good advice regarding the names. I myself use pretty long names, like var horizontalAxisTickValuesFiltered and so on.... sometimes too long!
          – Gerardo Furtado
          Jan 26 at 9:21





          Good advice regarding the names. I myself use pretty long names, like var horizontalAxisTickValuesFiltered and so on.... sometimes too long!
          – Gerardo Furtado
          Jan 26 at 9:21













           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185975%2fburnup-charts-in-d3-js%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Chat program with C++ and SFML

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          Will my employers contract hold up in court?