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"]);
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>
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");
})