Home > Software design >  D3 - Area graph with logarithmic scale
D3 - Area graph with logarithmic scale

Time:11-24

I'm trying to make area graph with Y axis in a log scale. Below I reproduced my error using some example code I've found online (my code is a bit different but essentially works the same).


I started with basic graph:
enter image description here

// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
    width = 460 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
  .append("svg")
    .attr("width", width   margin.left   margin.right)
    .attr("height", height   margin.top   margin.bottom)
  .append("g")
    .attr("transform",
          "translate("   margin.left   ","   margin.top   ")");

//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/3_TwoNumOrdered_comma.csv",

  // When reading the csv, I must format variables:
  function(d){
    return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
  },

  // Now I can use this dataset:
  function(data) {

    // Add X axis --> it is a date format
    var x = d3.scaleTime()
      .domain(d3.extent(data, function(d) { return d.date; }))
      .range([ 0, width ]);
    svg.append("g")
      .attr("transform", "translate(0,"   height   ")")
      .call(d3.axisBottom(x));

    // Add Y axis
    var y = d3.scaleLinear()
      .domain([1, d3.max(data, function(d) { return  d.value; })])
      .range([ height, 0 ]);
    svg.append("g")
      .call(d3.axisLeft(y));

    // Add the line
    svg.append("path")
      .datum(data)
      .attr("fill", "none")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 1.5)
      .attr("d", d3.line()
        .x(function(d) { return x(d.date) })
        .y(function(d) { return y(d.value) })
        )

})
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>


Then I tried to change it to an area graph with log scale. Each feature separately works fine - but combined they are breaking the graph:
enter image description here

// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
    width = 460 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
  .append("svg")
    .attr("width", width   margin.left   margin.right)
    .attr("height", height   margin.top   margin.bottom)
  .append("g")
    .attr("transform",
          "translate("   margin.left   ","   margin.top   ")");

//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/3_TwoNumOrdered_comma.csv",

  // When reading the csv, I must format variables:
  function(d){
    return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
  },

  // Now I can use this dataset:
  function(data) {

    // Add X axis --> it is a date format
    var x = d3.scaleTime()
      .domain(d3.extent(data, function(d) { return d.date; }))
      .range([ 0, width ]);
    svg.append("g")
      .attr("transform", "translate(0,"   height   ")")
      .call(d3.axisBottom(x));

    // Add Y axis
    var y = d3.scaleLog()
      .domain([1, d3.max(data, function(d) { return  d.value; })])
      .range([ height, 0 ]);
    svg.append("g")
      .call(d3.axisLeft(y));

    // Add the line
    svg.append("path")
      .datum(data)
      .attr("fill", "steelblue")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 1.5)
      .attr("d", d3.area()
        .x(function(d) { return x(d.date) })
        .y0(y(0))
        .y1(function(d) { return y(d.value) })
        )

})
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>


The only thing I've changed is:

var y = d3.scaleLinear()
// into
var y = d3.scaleLog()

and

svg.append("path")
    .datum(data)
    .attr("fill", "none")
    .attr("stroke", "steelblue")
    .attr("stroke-width", 1.5)
    .attr("d", d3.line()
        .x(function(d) { return x(d.date) })
        .y(function(d) { return y(d.value) })
    )
// into
svg.append("path")
    .datum(data)
    .attr("fill", "steelblue")
    .attr("stroke", "steelblue")
    .attr("stroke-width", 1.5)
    .attr("d", d3.area()
        .x(function(d) { return x(d.date) })
        .y0(y(0))
        .y1(function(d) { return y(d.value) })
    )

I don't understand what is wrong.

Every other type of scale (linear, sqrt, pow) works just fine when combined with area graph. Only problem is with log scale. And it is literally the only difference. You can change scaleLog into scaleSqrt and it just works...

And I would like to note here that my scale does not start at 0 (log(0) is negative infinity). So that is not the problem.

CodePudding user response:

The issue is how you set the baseline (y0):

.attr("d", d3.area()
    .x(function(d) { return x(d.date) })
    .y0(y(0))
    .y1(function(d) { return y(d.value) })
)

Here you are using 0 with the log scale (y(0)), which of course will return undefined. This is why other scales don't give you trouble but the log scale does. You could provide a value very close to zero to the y scale, but you know where the baseline should be: it should be equal to the first value in the y scale's range, there is no need to use the scale to determine this:

  .y0(height)

As seen below:

Show code snippet

// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
    width = 460 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
  .append("svg")
    .attr("width", width   margin.left   margin.right)
    .attr("height", height   margin.top   margin.bottom)
  .append("g")
    .attr("transform",
          "translate("   margin.left   ","   margin.top   ")");

//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/3_TwoNumOrdered_comma.csv",

  // When reading the csv, I must format variables:
  function(d){
    return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
  },

  // Now I can use this dataset:
  function(data) {

    // Add X axis --> it is a date format
    var x = d3.scaleTime()
      .domain(d3.extent(data, function(d) { return d.date; }))
      .range([ 0, width ]);
    svg.append("g")
      .attr("transform", "translate(0,"   height   ")")
      .call(d3.axisBottom(x));

    // Add Y axis
    var y = d3.scaleLog()
      .domain([1, d3.max(data, function(d) { return  d.value; })])
      .range([ height, 0 ]);
    svg.append("g")
      .call(d3.axisLeft(y));

    // Add the line
    svg.append("path")
      .datum(data)
      .attr("fill", "steelblue")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 1.5)
      .attr("d", d3.area()
        .x(function(d) { return x(d.date) })
        .y0(height)
        .y1(function(d) { return y(d.value) })
        )

})
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related