web-dev-qa-db-fra.com

Comment créer une échelle de couleurs dans D3 JS à utiliser dans l'attribut de remplissage?

Je fais une carte thermique dans D3 JS avec l'année le long de l'axe X et le mois le long de l'axe des ordonnées. Chaque cellule est une température et obtient une couleur de "remplissage" différente en fonction de cela. Ma question est la suivante: comment créer une échelle de couleurs qui mappe un domaine minTemp/maxTemp avec une gamme de codes de couleur. J'ai le code ci-dessous jusqu'à présent, mais cela ne fonctionne pas:

var url = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/global-temperature.json"

d3.json(url, function(json){

  //load data from API and save in variable data
  var data = json.monthlyVariance;
  var baseTemp = json.baseTemperature;

  //Add temperature to each object in data set
  for(var i = 0; i < data.length; i++){

    var temperature = baseTemp + data[i].variance  
    data[i].temperature = temperature;

    var monthString = "";
    switch(data[i].month){

      case 1:
        data[i].monthString = "January";
        break;
      case 2:
        data[i].monthString = "February";
        break;
      case 3:
        data[i].monthString = "March";
        break;
      case 4:
        data[i].monthString = "April";
        break;
      case 5:
        data[i].monthString = "May";
        break;
      case 6:
        data[i].monthString = "June";
        break;
      case 7:
        data[i].monthString = "July";
        break;
      case 8:
        data[i].monthString = "August";
        break;
      case 9:
        data[i].monthString = "September";
        break;
      case 10:
        data[i].monthString = "October";
        break;  
      case 11:
        data[i].monthString = "November";
        break;
      case 12:
        data[i].monthString = "December";
        break;
    }


  }

  //Set dimensions of div container, svg, and chart area(g element)
  var margin = {top: 20, right: 40, bottom: 40, left: 80};

  //Width of the chart, within SVG element
  var w = 1000 - margin.left - margin.right;
  //Height of the chart, within SVG element
  var h = 500 - margin.top - margin.bottom;

  //Create SVG element and append to #chart div container
  var svg = d3.select("#chart")
              .append("svg")
                .attr("width", w + margin.left + margin.right)
                .attr("height", h + margin.top + margin.bottom)
              .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


  //Get Min Max values
  var maxYear = d3.max(data, function(d){

      return d.year;

  });

  var minYear = d3.min(data, function(d){

      return d.year;

  });

  var maxTemp = d3.max(data, function(d){

    return d.temperature;

  });     

  var minTemp = d3.min(data, function(d){

    return d.temperature;

  })

  //Create X scale, axis and label
  var xScale = d3.scaleLinear()
                 .domain([minYear, maxYear])
                 .range([0,w]);

  var xAxis = d3.axisBottom()
                .scale(xScale)
                .ticks(20)
                .tickFormat(d3.format("d"));

  svg.append("g")
     .attr("class", "axis")
     .attr("transform", "translate(0," + h + ")")
     .call(xAxis);

  //Create Y scale, axis and label

  var cellHeight = (h / 12);

  var yRange = [];

  for(var i = 0; i < 12 ; i++){

      yRange.Push(i * cellHeight);

  }

  var yScale = d3.scaleOrdinal()
                 .domain(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"])
                 .range(yRange);

  var yAxis = d3.axisLeft()
                .scale(yScale)
                .ticks(12);

  svg.append("g")
  //append a g element
     .attr("class", "axis")
     .call(yAxis)
      //call yAxis function on this g element
     .selectAll(".tick text")
     //select all elements with class tick and nested text element
     .attr("transform", "translate(0," + (cellHeight/2) + ")");
     //move all text elements half a cell height down

  //Create color scale
  var colors = d3.scaleOrdinal()
                 .domain([minTemp,maxTemp])
                 .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598", "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);

  //Select all rect elements in G container element, bind data and append
  var cells = svg.selectAll("cells")
                 .data(data)
                 .enter()
                 .append("rect");


  var cellAttributes = cells
                        .attr("x", function(d){

                          return xScale(d.year);

                        })
                        .attr("y", function(d){

                          return yScale(d.monthString);

                        })
                        .attr("width", w/(maxYear-minYear))
                        .attr("height", h/12)
                        .attr("fill", function(d){

                          return colors(d);

                        })
                        .attr("class", "cell");


});

Je pourrais écrire une longue instruction if/else dans la fonction d'attribut de remplissage, pour mapper la température sur un code de couleur, mais ce n'est pas la "manière D3", je pense. Comment puis-je le faire avec une balance ?:

  var colors = d3.scaleOrdinal()
                 .domain([minTemp,maxTemp])
                 .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598", "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
5
chemook78

Vous n'avez pas besoin d'une échelle ordinale ici. Vous avez besoin d'un quantize scale à la place:

Les échelles de quantification sont similaires aux échelles linéaires, sauf qu'elles utilisent une plage discrète plutôt que continue. Le domaine d'entrée continu est divisé en segments uniformes basés sur le nombre de valeurs dans (c'est-à-dire la cardinalité de) la plage de sortie. 

Ainsi, ceci devrait être votre échelle:

var colors = d3.scaleQuantize()
    .domain([minTemp,maxTemp])
    .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598", 
    "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);

Voici une démo:

var data = d3.range(50);

var colors = d3.scaleQuantize()
    .domain([0,50])
    .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598", 
    "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
		
var svg = d3.select("svg");

var rects = svg.selectAll(".rects")
	.data(data)
	.enter()
	.append("rect")
	.attr("y", 10)
	.attr("height", 100)
	.attr("x", (d,i)=>10 + i*9)
	.attr("width", 6)
	.attr("fill", d=>colors(d))
	.attr("stroke", "gray");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500"></svg>

Vous pouvez également utiliser scaleLinear, qui présente l'avantage d'interpoler entre vos couleurs (vous aurez donc plus que les 11 couleurs de votre tableau de couleurs). Cependant, faites attention à définir le même nombre d'éléments dans le domaine, en utilisant d3.ticks:

d3.ticks(minTemp, maxTemp, 11);

Voici une démo avec scaleLinear:

var data = d3.range(50);

var colors = d3.scaleLinear()
    .domain(d3.ticks(0, 50, 11))
    .range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598", 
    "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
		
var svg = d3.select("svg");

var rects = svg.selectAll(".rects")
	.data(data)
	.enter()
	.append("rect")
	.attr("y", 10)
	.attr("height", 100)
	.attr("x", (d,i)=>10 + i*9)
	.attr("width", 6)
	.attr("fill", d=>colors(d))
	.attr("stroke", "gray");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500"></svg>

9
Gerardo Furtado

merci beaucoup pour l'aide, voici comment je l'ai finalement fait:

démo: http://codepen.io/chemok78/full/qRXmWX/

var url = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/global-temperature.json"

d3.json(url, function(json) {

  //load data from API and save in variable data
  var data = json.monthlyVariance;
  var baseTemp = json.baseTemperature;

  var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

  //Add temperature to each object in data set
  for (var i = 0; i < data.length; i++) {

    var temperature = baseTemp + data[i].variance
    data[i].temperature = temperature;

    var monthString = "";
    switch (data[i].month) {

      case 1:
        data[i].monthString = "January";
        break;
      case 2:
        data[i].monthString = "February";
        break;
      case 3:
        data[i].monthString = "March";
        break;
      case 4:
        data[i].monthString = "April";
        break;
      case 5:
        data[i].monthString = "May";
        break;
      case 6:
        data[i].monthString = "June";
        break;
      case 7:
        data[i].monthString = "July";
        break;
      case 8:
        data[i].monthString = "August";
        break;
      case 9:
        data[i].monthString = "September";
        break;
      case 10:
        data[i].monthString = "October";
        break;
      case 11:
        data[i].monthString = "November";
        break;
      case 12:
        data[i].monthString = "December";
        break;
    }


  }

  //Set dimensions of div container, svg, and chart area(g element)
  var margin = {
    top: 40,
    right: 60,
    bottom: 100,
    left: 100
  };

  //Width of the chart, within SVG element
  var w = 1000 - margin.left - margin.right;
  //Height of the chart, within SVG element
  var h = 600 - margin.top - margin.bottom;

  //Create SVG element and append to #chart div container
  //SVG is nested G element
  var svg = d3.select("#chart")
    .append("svg")
    .attr("width", w + margin.left + margin.right)
    .attr("height", h + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


  //Get Min Max values
  var maxYear = d3.max(data, function(d) {

    return d.year;

  });

  var minYear = d3.min(data, function(d) {

    return d.year;

  });


  var maxTemp = d3.max(data, function(d) {

    return d.temperature;

  });

  var minTemp = d3.min(data, function(d) {

    return d.temperature;

  })


  //Create X scale, axis and label
  var xScale = d3.scaleLinear()
    .domain([minYear, maxYear])
    .range([0, w]);

  var xAxis = d3.axisBottom()
    .scale(xScale)
    .ticks(20)
    .tickFormat(d3.format("d"));

  svg.append("g")
    .attr("class", "axis")
    .attr("transform", "translate(0," + h + ")")
    .call(xAxis);

  var xLabel = svg.append("text")
    .text("Year")
    .attr("x", w / 2)
    .attr("y", h + (margin.bottom / 2.5))
    .attr("font-size", "14px");

  //Create Y scale, axis and label

  var cellHeight = (h / 12);

  var yRange = [];

  for (var i = 0; i < 12; i++) {

    yRange.Push(i * cellHeight);

  }


  var yScale = d3.scaleOrdinal()
    .domain(months)
    .range(yRange);

  var yAxis = d3.axisLeft()
    .scale(yScale)
    .ticks(12);

  svg.append("g")
    //append a g element
    .attr("class", "axis")
    .call(yAxis)
    //call yAxis function on this g element
    .selectAll(".tick text")
    //select all elements with class tick and nested text element
    .attr("transform", "translate(0," + (cellHeight / 2) + ")");
  //move all text elements half a cell height down

  var yLabel = svg.append("text")
    .attr("transform", "rotate(-90)")
    .attr("x", 0 - (h / 2))
    .attr("y", 0 - (margin.left / 1.8))
    .style("font-size", "14px")
    .style("text-anchor", "middle")
    .text("Month");


  //Create color scale

  var colorCodes = ["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598", "#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"];

  var colors = d3.scaleQuantile()
    //quantize scale divides domain in bands according to ordinal scale range
    .domain([minTemp, maxTemp])
    //.domain(d3.ticks(minTemp,maxTemp,11))
    .range(colorCodes);

  var colorQuantiles = colors.quantiles();
  colorQuantiles.unshift(0);
  //save the upper ranges of each temperature quantile + 0 at the beginning (quantile function does not count 0 as start)


  //Append tooltip to chart area. Fully transparant at first
  var tip = d3.select("#chart").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);

  //Select all rect elements in G container element, bind data and append
  var cells = svg.selectAll("cells")
    .data(data)
    .enter()
    .append("rect");

  var cellAttributes = cells
    .attr("x", function(d) {

      return xScale(d.year);

    })
    .attr("y", function(d) {

      return yScale(d.monthString);

    })
    .attr("width", w / (maxYear - minYear))
    .attr("height", cellHeight)
    .attr("fill", function(d) {

      return colors(d.temperature);

    })
    .attr("class", "cell")
    .on("mouseover", function(d) {

      tip.transition()
        .style("opacity", 0.7);
      tip.html("<strong>" + months[d.month - 1] + " - " + d.year + "</strong><br>" + d.temperature.toFixed(2) + " °C<br>" + d.variance.toFixed(2) + " °C")
        .style("left", d3.event.pageX + "px")
        .style("top", d3.event.pageY - 70 + "px");

    })
    .on("mouseout", function(d) {

      tip.transition()
        .style("opacity", 0);

    })

  //Create a legend

  var blockWidth = 35;
  var blockHeight = 20;

  var legend = svg.selectAll(".legend")
    .data(colorQuantiles)
    .enter()
    .append("g")
    .attr("class", "legend")
    .attr("font-size", "14px")
    .attr("font-style", "PT Sans")
    .attr("transform", function(d, i) {

      return ("translate(" + i * blockWidth + ",0)")

    });

  legend.append("rect")
    .attr("x", (w / 5) * 3)
    .attr("y", h + (margin.bottom / 3))
    .attr("width", blockWidth)
    .attr("height", blockHeight)
    .style("fill", function(d, i) {

      return (colorCodes[i]);

    });

  legend.append("text")
    .attr("x", ((w / 5) * 3) + (blockWidth / 2))
    .attr("y", (h + (margin.bottom / 3)) + blockHeight + 15)
    .text(function(d, i) {

      return colorQuantiles[i].toFixed(1);

    })
    .style("text-anchor", "middle");

})
0
chemook78