简体   繁体   中英

D3: Why doesn't the heatmap color legend appear?

SITUATION:

在此处输入图片说明

QUESTION:

I am trying to create a heatmap graph with D3.js which I managed. I now have trouble making the legend appear.

Why doesn't the legend appear ?

The legend should appear like here for example: https://codepen.io/freeCodeCamp/full/aNLYPp

CODE:

<script type="text/javascript">  

    // Excellent example from Tom May helped me when I got stuck: http://bl.ocks.org/tjdecke/5558084
    d3.json("https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/global-temperature.json", function(error, json) {
      if (error) {
          return console.warn(error);
      }
      visualizeThe(json);
    });

    function visualizeThe(data) {

        const baseTemperature = data.baseTemperature;
        const tempData = data.monthlyVariance;

        const margin = {
            top: 10,
            right: 85,
            bottom: 65,
            left: 70
        }

        const w = 1250 - margin.left - margin.right;
        const h = 500 - margin.top - margin.bottom;
        const barWidth = Math.ceil(w / tempData.length);
        const legendElementWidth = w/12;


        const colors = ["#5e4fa2", "#3288bd", "#66c2a5", "#abdda4", "#e6f598", "#ffffbf", "#fee08b", "#fdae61", "#f46d43", "#d53e4f", "#9e0142"];
        const buckets = colors.length;
        const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

        const  minTime = d3.min(tempData, (d) => new Date(d.year,1,1,0,0));
        const  maxTime = d3.max(tempData, (d) => new Date(d.year,1,1,0,0));

        const xScale = d3.scaleTime()
            .domain([minTime, maxTime]) 
            .range([margin.left, w]);   

        const xAxis = d3.axisBottom(xScale).ticks(20);

        const svg = d3.select("#results")
            .append("svg")
            .attr("width",  w + margin.left + margin.right)
            .attr("height", h + margin.top + margin.bottom);

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

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

        const monthsLabels = svg.selectAll("monthLabel")
            .data(months)
            .enter()
            .append("text")
            .text((d) => d)
            .attr("x", 100)
            .attr("y", (d,i) => i * h/12 + 21)
            .style("text-anchor", "end")
            .attr("transform", "translate(-40," +0+ ")")
            .style("font-size", 10);

        const colorScale = d3.scaleQuantile()
          .domain([d3.min(tempData, (d) => d.variance + baseTemperature ), d3.max(tempData, (d) => d.variance + baseTemperature )])
          .range(colors);

        const heatMap = svg.selectAll("month")
          .data(tempData, (d) => d);

        const rects = heatMap.enter()
          .append("rect")
          .attr("x", (d) => xScale(new Date(d.year,1,1,0,0)))
          .attr("y", (d) => d.month * h/12 - margin.bottom + margin.top -1)
          .attr("width", barWidth + 3)
          .attr("height", h/12 )
          .style("fill", colors[0])
          .on("mouseover", function(d) {        
            div.transition()        
                .duration(200)      
                .style("opacity", .9);      
            div .html( d.year +" "+ months[d.month-1]+"<br>"+(d.variance + baseTemperature).toFixed(3)+"    &deg;C  <br>"+d.variance+" Variance")   
                .style("left", (d3.event.pageX) + "px")     
                .style("top", (d3.event.pageY - 50) + "px");    
            })                  
            .on("mouseout", function(d) {       
            div.transition()        
                .duration(500)      
                .style("opacity", 0);   
            });

      rects.transition().duration(1000)
          .style("fill", (d) => colorScale(d.variance + baseTemperature));

          svg.append("text")             
              .attr("transform",
                    "translate(" + (w/2) + " ," + 
                    (h+ margin.top + 45) + ")")
              .style("text-anchor", "middle")
              .text("Years");

          svg.append("text")
              .attr("transform", "rotate(-90)")
              .attr("y", -5)
              .attr("x",0 - (h / 2))
              .attr("dy", "1em")
              .style("text-anchor", "middle")
              .text("Months");   

       const legend = svg.selectAll("legend")
          .data([0].concat(colorScale.quantiles()), (d) => d);

       const legendEntries = legend.enter().append("g")
              .attr("class", "legend");

          legendEntries.append("rect")
            .attr("x", (d, i)  => (w*0.7)+ legendElementWidth/4 * i )
            .attr("y", h + 40)
            .attr("width", legendElementWidth)
            .attr("height", 20)
            .style("fill", (d, i) => colors[i]);

          legendEntries.append("text")
            .data(tempData)
            .text((d) => "≥ " + Math.round(d.variance+baseTemperature))
            .attr("x", (d, i) =>(w*0.7)+ legendElementWidth/4 * i)
            .attr("y", h + 75);
    }

</script>

This code is causing you problems:

   const legend = svg.selectAll("legend")
      .data(tempData.concat(colorScale.quantiles()), (d) => d["month"] );

      legend.enter().append("g")
          .attr("class", "legend");

      legend.append("rect")
        .attr("x", (d,i)  => legendElementWidth * i)
        .attr("y", h)
        .attr("width", legendElementWidth)
        .attr("height", 5)
        .style("fill", (d, i) => colors[i]);

      legend.append("text")
        .attr("class", "mono")
        .text((d) => "≥ " + Math.round(d.variance+baseTemperature))
        .attr("x", (d, i) =>legendElementWidth * i)
        .attr("y", h + 4);

Note that your enter selection is used only for appending 'g' elements. You need to append rectangles and text using the enter selection too. Contrast your code with:

   const legend = svg.selectAll("legend")
      .data(tempData.concat(colorScale.quantiles()), (d) => d["month"] );

   const legendEntries = legend.enter().append("g")
          .attr("class", "legend");

      legendEntries.append("rect")
        .attr("x", (d,i)  => legendElementWidth * i)
        .attr("y", h)
        .attr("width", legendElementWidth)
        .attr("height", 5)
        .style("fill", (d, i) => colors[i]);

      legendEntries.append("text")
        .attr("class", "mono")
        .text((d) => "≥ " + Math.round(d.variance+baseTemperature))
        .attr("x", (d, i) =>legendElementWidth * i)
        .attr("y", h + 4);

The enter selection returns elements that need to be added to the DOM. Here, we append a 'g', with its datum, for each element that needs to added. Then we append items to the 'g's we just entered.

It is not part of your question, but you might want to examine the data you are passing to your legend.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM