Daniel Carter
Daniel Carter

Reputation: 13

Trying to create D3.js chart for Temperature for highs and lows on days Saturday to Saturday

Having trouble with D3.js. Originally this code x axis was 0,1,2,3,4,5,6,7. I'm trying to set the x axis as x: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]. Please help! Not sure how to fix it.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
  font: 10px sans-serif;
}
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
.grid path,
.grid line {
  fill: none;
  stroke: rgba(0, 0, 0, 0.25);
  shape-rendering: crispEdges;
}
.x.axis path {
  display: none;
}
.line {
  fill: none;
  stroke-width: 2.5px;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.js"></script>
<script>
var data = [ { label: "High", 
               x: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 
               y: [82, 81, 81, 70, 77, 78, 79, 80] }, 
             { label: "Low", 
               x: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 
               y: [67, 59, 50, 47, 51, 60, 66, 66] } ] ;
var xy_chart = d3_xy_chart()
    .width(960)
    .height(500)
    .xlabel("Days")
    .ylabel("Temperature F*") ;
var svg = d3.select("body").append("svg")
    .datum(data)
    .call(xy_chart) ;
function d3_xy_chart() {
    var width = 640,  
        height = 480, 
        xlabel = "X Axis Label",
        ylabel = "Y Axis Label" ;
    
    function chart(selection) {
        selection.each(function(datasets) {
            //
            // Create the plot. 
            //
            var margin = {top: 20, right: 80, bottom: 30, left: 50}, 
                innerwidth = width - margin.left - margin.right,
                innerheight = height - margin.top - margin.bottom ;
            
            var x_scale = d3.scale.linear()
                .range([0, innerwidth])
                .domain([ d3.min(datasets, function(d) { return d3.min(d.x); }), 
                          d3.max(datasets, function(d) { return d3.max(d.x); }) ]) ;
            
            var y_scale = d3.scale.linear()
                .range([innerheight, 0])
                .domain([ d3.min(datasets, function(d) { return d3.min(d.y); }),
                          d3.max(datasets, function(d) { return d3.max(d.y); }) ]) ;
            var color_scale = d3.scale.category10()
                .domain(d3.range(datasets.length)) ;
            var x_axis = d3.svg.axis()
                .scale(x_scale)
                .orient("bottom") ;
            var y_axis = d3.svg.axis()
                .scale(y_scale)
                .orient("left") ;
            var x_grid = d3.svg.axis()
                .scale(x_scale)
                .orient("bottom")
                .tickSize(-innerheight)
                .tickFormat("") ;
            var y_grid = d3.svg.axis()
                .scale(y_scale)
                .orient("left") 
                .tickSize(-innerwidth)
                .tickFormat("") ;
            var draw_line = d3.svg.line()
                .interpolate("basis")
                .x(function(d) { return x_scale(d[0]); })
                .y(function(d) { return y_scale(d[1]); }) ;
            var svg = d3.select(this)
                .attr("width", width)
                .attr("height", height)
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")") ;
            
            svg.append("g")
                .attr("class", "x grid")
                .attr("transform", "translate(0," + innerheight + ")")
                .call(x_grid) ;
            svg.append("g")
                .attr("class", "y grid")
                .call(y_grid) ;
            svg.append("g")
                .attr("class", "x axis")
                .attr("transform", "translate(0," + innerheight + ")") 
                .call(x_axis)
                .append("text")
                .attr("dy", "-.71em")
                .attr("x", innerwidth)
                .style("text-anchor", "end")
                .text(xlabel) ;
            
            svg.append("g")
                .attr("class", "y axis")
                .call(y_axis)
                .append("text")
                .attr("transform", "rotate(-90)")
                .attr("y", 6)
                .attr("dy", "0.71em")
                .style("text-anchor", "end")
                .text(ylabel) ;
            var data_lines = svg.selectAll(".d3_xy_chart_line")
                .data(datasets.map(function(d) {return d3.zip(d.x, d.y);}))
                .enter().append("g")
                .attr("class", "d3_xy_chart_line") ;
            
            data_lines.append("path")
                .attr("class", "line")
                .attr("d", function(d) {return draw_line(d); })
                .attr("stroke", function(_, i) {return color_scale(i);}) ;
            
            data_lines.append("text")
                .datum(function(d, i) { return {name: datasets[i].label, final: d[d.length-1]}; }) 
                .attr("transform", function(d) { 
                    return ( "translate(" + x_scale(d.final[0]) + "," + 
                             y_scale(d.final[1]) + ")" ) ; })
                .attr("x", 3)
                .attr("dy", ".35em")
                .attr("fill", function(_, i) { return color_scale(i); })
                .text(function(d) { return d.name; }) ;
        }) ;
    }
    chart.width = function(value) {
        if (!arguments.length) return width;
        width = value;
        return chart;
    };
    chart.height = function(value) {
        if (!arguments.length) return height;
        height = value;
        return chart;
    };
    chart.xlabel = function(value) {
        if(!arguments.length) return xlabel ;
        xlabel = value ;
        return chart ;
    } ;
    chart.ylabel = function(value) {
        if(!arguments.length) return ylabel ;
        ylabel = value ;
        return chart ;
    } ;
    return chart;
}
</script>

Upvotes: 1

Views: 1025

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38161

This is an issue of scaling. You are scaling strings with a linear scale:

var x_scale = d3.scale.linear()
    .range([0, innerwidth])
    .domain([ d3.min(datasets, function(d) { return d3.min(d.x); }), 
              d3.max(datasets, function(d) { return d3.max(d.x); }) ]) ;

There are two issues here: the min/max functions will evaluate the min/max of the x value strings, which are likely not the min/max you want. Also, the same issue arises with the use of the linear scale, it expects numbers.

With d3v3 we can use an ordinal scale:

var x_scale = d3.scale.ordinal()
  .rangePoints([0, innerwidth])
  .domain(datasets[0].x) ;

This assumes all data series have the same x values

Which gives us:

var data = [ { label: "High", 
               x: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 
               y: [82, 81, 81, 70, 77, 78, 79, 80] }, 
             { label: "Low", 
               x: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 
               y: [67, 59, 50, 47, 51, 60, 66, 66] } ] ;
var xy_chart = d3_xy_chart()
    .width(960)
    .height(500)
    .xlabel("Days")
    .ylabel("Temperature F*") ;
var svg = d3.select("body").append("svg")
    .datum(data)
    .call(xy_chart) ;
function d3_xy_chart() {
    var width = 640,  
        height = 480, 
        xlabel = "X Axis Label",
        ylabel = "Y Axis Label" ;
    
    function chart(selection) {
        selection.each(function(datasets) {
            //
            // Create the plot. 
            //
            var margin = {top: 20, right: 80, bottom: 30, left: 50}, 
                innerwidth = width - margin.left - margin.right,
                innerheight = height - margin.top - margin.bottom ;
            
            var x_scale = d3.scale.ordinal()
                .rangePoints([0, innerwidth])
                .domain(datasets[0].x) ;
            
            var y_scale = d3.scale.linear()
                .range([innerheight, 0])
                .domain([ d3.min(datasets, function(d) { return d3.min(d.y); }),
                          d3.max(datasets, function(d) { return d3.max(d.y); }) ]) ;
            var color_scale = d3.scale.category10()
                .domain(d3.range(datasets.length)) ;
            var x_axis = d3.svg.axis()
                .scale(x_scale)
                .orient("bottom") ;
            var y_axis = d3.svg.axis()
                .scale(y_scale)
                .orient("left") ;
            var x_grid = d3.svg.axis()
                .scale(x_scale)
                .orient("bottom")
                .tickSize(-innerheight)
                .tickFormat("") ;
            var y_grid = d3.svg.axis()
                .scale(y_scale)
                .orient("left") 
                .tickSize(-innerwidth)
                .tickFormat("") ;
            var draw_line = d3.svg.line()
                .interpolate("basis")
                .x(function(d) { return x_scale(d[0]); })
                .y(function(d) { return y_scale(d[1]); }) ;
            var svg = d3.select(this)
                .attr("width", width)
                .attr("height", height)
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")") ;
            
            svg.append("g")
                .attr("class", "x grid")
                .attr("transform", "translate(0," + innerheight + ")")
                .call(x_grid) ;
            svg.append("g")
                .attr("class", "y grid")
                .call(y_grid) ;
            svg.append("g")
                .attr("class", "x axis")
                .attr("transform", "translate(0," + innerheight + ")") 
                .call(x_axis)
                .append("text")
                .attr("dy", "-.71em")
                .attr("x", innerwidth)
                .style("text-anchor", "end")
                .text(xlabel) ;
            
            svg.append("g")
                .attr("class", "y axis")
                .call(y_axis)
                .append("text")
                .attr("transform", "rotate(-90)")
                .attr("y", 6)
                .attr("dy", "0.71em")
                .style("text-anchor", "end")
                .text(ylabel) ;
            var data_lines = svg.selectAll(".d3_xy_chart_line")
                .data(datasets.map(function(d) {return d3.zip(d.x, d.y);}))
                .enter().append("g")
                .attr("class", "d3_xy_chart_line") ;
            
            data_lines.append("path")
                .attr("class", "line")
                .attr("d", function(d) {return draw_line(d); })
                .attr("stroke", function(_, i) {return color_scale(i);}) ;
            
            data_lines.append("text")
                .datum(function(d, i) { return {name: datasets[i].label, final: d[d.length-1]}; }) 
                .attr("transform", function(d) { 
                    return ( "translate(" + x_scale(d.final[0]) + "," + 
                             y_scale(d.final[1]) + ")" ) ; })
                .attr("x", 3)
                .attr("dy", ".35em")
                .attr("fill", function(_, i) { return color_scale(i); })
                .text(function(d) { return d.name; }) ;
        }) ;
    }
    chart.width = function(value) {
        if (!arguments.length) return width;
        width = value;
        return chart;
    };
    chart.height = function(value) {
        if (!arguments.length) return height;
        height = value;
        return chart;
    };
    chart.xlabel = function(value) {
        if(!arguments.length) return xlabel ;
        xlabel = value ;
        return chart ;
    } ;
    chart.ylabel = function(value) {
        if(!arguments.length) return ylabel ;
        ylabel = value ;
        return chart ;
    } ;
    return chart;
}
body {
  font: 10px sans-serif;
}
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
.grid path,
.grid line {
  fill: none;
  stroke: rgba(0, 0, 0, 0.25);
  shape-rendering: crispEdges;
}
.x.axis path {
  display: none;
}
.line {
  fill: none;
  stroke-width: 2.5px;
}
<script src="https://d3js.org/d3.v3.js"></script>

Or:

enter image description here

Ok, now we at least see the axis and the lines. But we have a new problem: we have two data points with the same x value: "Saturday". A scale will always map a given input value to the same output value. So we need to differentiate the two Saturdays. One way we could differentiate the input strings is to use their index. This assumes an ordinal input:

This requires a slightly different scale set up:

var x_scale = d3.scale.ordinal()
    .rangePoints([0,innerwidth])
    .domain(d3.range(datasets[0].y.length));

d3.range(n) produces an array of numbers from 0 through to n-1), good for replicating each index in the data array

But, now we have to pass the index to each place where the scale is used (as opposed to the day). In this example I think it's only the line generator.

  var draw_line = d3.svg.line()
      .interpolate("basis")
      .x(function(d,i) { return x_scale(i); })
      .y(function(d) { return y_scale(d[1]); }) ;

Of course now we need to format the ticks so that they aren't numerical:

   var x_axis = d3.svg.axis()
       .scale(x_scale)
       .orient("bottom")
       .tickFormat(function(d) { return datasets[0].x[d]; })

Which gives us:

var data = [ { label: "High", 
               x: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 
               y: [82, 81, 81, 70, 77, 78, 79, 80] }, 
             { label: "Low", 
               x: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 
               y: [67, 59, 50, 47, 51, 60, 66, 66] } ] ;
var xy_chart = d3_xy_chart()
    .width(960)
    .height(500)
    .xlabel("Days")
    .ylabel("Temperature F*") ;
var svg = d3.select("body").append("svg")
    .datum(data)
    .call(xy_chart) ;
function d3_xy_chart() {
    var width = 640,  
        height = 480, 
        xlabel = "X Axis Label",
        ylabel = "Y Axis Label" ;
    
    function chart(selection) {
        selection.each(function(datasets) {
            //
            // Create the plot. 
            //
            var margin = {top: 20, right: 80, bottom: 30, left: 50}, 
                innerwidth = width - margin.left - margin.right,
                innerheight = height - margin.top - margin.bottom ;
            
			var x_scale = d3.scale.ordinal()
                .rangePoints([0,innerwidth])
                .domain(d3.range(datasets[0].y.length));			// assuming all x axes are the same
		  
		    var y_scale = d3.scale.linear()
                .range([innerheight, 0])
                .domain([ d3.min(datasets, function(d) { return d3.min(d.y); }),
                          d3.max(datasets, function(d) { return d3.max(d.y); }) ]) ;
            var color_scale = d3.scale.category10()
                .domain(d3.range(datasets.length)) ;
            var x_axis = d3.svg.axis()
                .scale(x_scale)
                .orient("bottom")
				.tickFormat(function(d) { return datasets[0].x[d]; })
            var y_axis = d3.svg.axis()
                .scale(y_scale)
                .orient("left") ;
            var x_grid = d3.svg.axis()
                .scale(x_scale)
                .orient("bottom")
                .tickSize(-innerheight)
                .tickFormat("") ;
            var y_grid = d3.svg.axis()
                .scale(y_scale)
                .orient("left") 
                .tickSize(-innerwidth)
                .tickFormat("") ;
            var draw_line = d3.svg.line()
                .interpolate("basis")
                .x(function(d,i) { return x_scale(i); })
                .y(function(d) { return y_scale(d[1]); }) ;
            var svg = d3.select(this)
                .attr("width", width)
                .attr("height", height)
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")") ;
            
            svg.append("g")
                .attr("class", "x grid")
                .attr("transform", "translate(0," + innerheight + ")")
                .call(x_grid) ;
            svg.append("g")
                .attr("class", "y grid")
                .call(y_grid) ;
            svg.append("g")
                .attr("class", "x axis")
                .attr("transform", "translate(0," + innerheight + ")") 
                .call(x_axis)
                .append("text")
                .attr("dy", "-.71em")
                .attr("x", innerwidth)
                .style("text-anchor", "end")
                .text(xlabel) ;
            
            svg.append("g")
                .attr("class", "y axis")
                .call(y_axis)
                .append("text")
                .attr("transform", "rotate(-90)")
                .attr("y", 6)
                .attr("dy", "0.71em")
                .style("text-anchor", "end")
                .text(ylabel) ;
            var data_lines = svg.selectAll(".d3_xy_chart_line")
                .data(datasets.map(function(d) {return d3.zip(d.x, d.y);}))
                .enter().append("g")
                .attr("class", "d3_xy_chart_line") ;
            
            data_lines.append("path")
                .attr("class", "line")
                .attr("d", function(d) {return draw_line(d); })
                .attr("stroke", function(_, i) {return color_scale(i);}) ;
            
            data_lines.append("text")
                .datum(function(d, i) { return {name: datasets[i].label, final: d[d.length-1]}; }) 
                .attr("transform", function(d) { 
                    return ( "translate(" + x_scale(d.final[0]) + "," + 
                             y_scale(d.final[1]) + ")" ) ; })
                .attr("x", 3)
                .attr("dy", ".35em")
                .attr("fill", function(_, i) { return color_scale(i); })
                .text(function(d) { return d.name; }) ;
        }) ;
    }
    chart.width = function(value) {
        if (!arguments.length) return width;
        width = value;
        return chart;
    };
    chart.height = function(value) {
        if (!arguments.length) return height;
        height = value;
        return chart;
    };
    chart.xlabel = function(value) {
        if(!arguments.length) return xlabel ;
        xlabel = value ;
        return chart ;
    } ;
    chart.ylabel = function(value) {
        if(!arguments.length) return ylabel ;
        ylabel = value ;
        return chart ;
    } ;
    return chart;
}
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
.grid path,
.grid line {
  fill: none;
  stroke: rgba(0, 0, 0, 0.25);
  shape-rendering: crispEdges;
}
.x.axis path {
  display: none;
}
.line {
  fill: none;
  stroke-width: 2.5px;
}
<script src="https://d3js.org/d3.v3.js"></script>

Notes

This answer assumes that you are passing ordinal data. Most of the challenge and the answer here regards input values that are the same. If every input value for the x axis is different then the first snippet will be fine (when dealing with ordinal data).

If you are hoping to have the chart function take in arbitrary data types, continuous, date/time, or ordinal, you'll need to build in additional logic that selects between the appropriate scale. I haven't otherwise touched that subject as the question was about why the axis and scale were not behaving as desired.

Upvotes: 0

Related Questions