.exit()。remove()似乎删除了行图表图例中的文本而不是矩形

时间:2015-12-08 15:52:11

标签: javascript d3.js

我有一个用D3构建的折线图。当用户从下拉列表中选择一个选项时,图表会更新。

图例的复选框填充了与图表上的线条匹配的颜色,以及指示线条和复选框所代表国家/地区的文字。

这是我的问题。对于产品'大豆',香港没有数据。当我将下拉选项更改为'soy'时,香港的文本将从图例中删除。但是复选框不是。此外,下面的国家(加拿大)向上移动,这使得传奇不正确。

我认为这是与.exit()。remove()有关的问题,但我无法弄清楚发生了什么。任何想法都将不胜感激。

Here is a plunker

代码:

<!DOCTYPE html>
    <meta charset="utf-8">
    <style> /* set the CSS */

    body { font: 12px Arial;}

    path {
        stroke: #ccc;
        stroke-width: 2;
        fill: none;
    }

    .axis path,
    .axis line {
        fill: none;
        stroke: grey;
        stroke-width: 1;
        shape-rendering: crispEdges;
    }
    #legendContainer{
        position:absolute;
        top:60px;
        left:10px;
        overflow: auto;
        height:490px;
        width:150px;
    }
    #legend{
        width:130px;
        height:200px;
    }
    .legend {
        font-size: 12px;
        font-weight: normal;
        text-anchor: left;
    }
    .legendcheckbox{
        cursor: pointer;
    }
    #showAll{
        position:absolute;
        top:600px;
        left:880px;
    }
    #clearAll{
        position:absolute;
        top:600px;
        left:950px;
    }
    input{
        border-radius:5px;
        padding:5px 10px;
        background:#999;
        border:0;
        color:#fff;
    }
    #inds{
        position:absolute;
        top:10px;
        left:10px;
    }
    </style>
    <body>

    <!-- load the d3.js library -->
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script src="//code.jquery.com/jquery-1.10.2.js"></script>
    <select id="inds">
            <option value="corn" selected="selected">corn</option>
            <option value="soy">soy</option>
            <option value="grain">grain</option>
    </select>
    <div id="legendContainer" class="legendContainer">
        <svg id="legend"></svg>
    </div>
    <div id="showAll">
        <input name="showAllButton"
         type="button"
         value="Show All"
         onclick="showAll()" />
    </div>
    <div id="clearAll">
        <input name="clearAllButton"
         type="button"
         value="Clear All"
         onclick="clearAll()" />
    </div>
    <script>

    function filterJSON(json, key, value) {
      var result = [];
      json.forEach(function(val,idx,arr){
        if(val[key] == value){

          result.push(val)
        }
      })
      return result;
    }

    // Set the dimensions of the canvas / graph
    var margin = {top: 50, right: 20, bottom: 30, left: 200},
        width = 1000 - margin.left - margin.right,
        height = 550 - margin.top - margin.bottom;

    // Parse the date / time
    var parseDate = d3.time.format("%Y").parse;

    // Set the ranges
    var x = d3.time.scale().range([0, width]);
    var y = d3.scale.linear().range([height, 0]);

    // Define the axes
    var xAxis = d3.svg.axis().scale(x)
        .orient("bottom").ticks(5);

    var yAxis = d3.svg.axis().scale(y)
        .orient("left").ticks(5);

    // Define the line
    var countryline = d3.svg.line()
            .interpolate("cardinal")
        .x(function(d) { return x(d.year); })
        .y(function(d) { return y(d.value); });

    // Adds the svg canvas
    var svg = d3.select("body")
        .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 + ")");
    var data;
    // Get the data
    d3.json("data.json", function(error, json) {

      json.forEach(function(d) {
            d.value = +d.value;
      });

        d3.select('#inds')
                .on("change", function () {
                    var sect = document.getElementById("inds");
                    var section = sect.options[sect.selectedIndex].value;


                    data = filterJSON(json, 'product', section);

                    data.forEach(function(d) {
                    d.value = +d.value;
                    d.year = parseDate(String(d.year));
                    d.active = true;
                });

                    updateGraph(data);
                });

                // generate initial graph
                data = filterJSON(json, 'product', 'corn');
                updateGraph(data);

    });

    var color = d3.scale.ordinal().range(["limegreen", "orange", "purple"]);

    function updateGraph(data) {

        // Scale the range of the data
        x.domain(d3.extent(data, function(d) { return d.year; }));
        y.domain([d3.min(data, function(d) { return d.value; }), d3.max(data, function(d) { return d.value; })]);

        // Nest the entries by country
        dataNest = d3.nest()
            .key(function(d) {return d.country;})
            .entries(data);

            var result = dataNest.filter(function(val,idx, arr){
                      return $("." + val.key.replace(/\s|\(|\)|\'|\,+/g, '')).attr("fill") != "#ccc" 
                      // matching the data with selector status
                    })

            var country = svg.selectAll(".line")
                  .data(result, function(d){return d.key.replace(/\s|\(|\)|\'|\,+/g, '')});

            country.enter().append("path")
                .attr("class", "line");

            country.transition()
                .style("stroke", function(d,i) { return d.color = color(d.key.replace(/\s|\(|\)|\'|\,+/g, '')); })
                .attr("id", function(d){ return 'tag'+d.key.replace(/\s|\(|\)|\'|\,+/g, '');}) // assign ID
                .attr("d", function(d){
                    return countryline(d.values)
                });

            country.exit().remove();

            var legend = d3.select("#legend")
                .selectAll("text")
                .data(dataNest);

                //checkboxes
                legend.enter().append("rect")
                  .attr("width", 10)
                  .attr("height", 10)
                  .attr("x", 0)
                  .attr("y", function (d, i) { return 0 +i*15; })  // spacing
                  .attr("fill",function(d) { 
                    return color(d.key.replace(/\s|\(|\)|\'|\,+/g, ''));

                  })
                  .attr("class", function(d,i){return "legendcheckbox " + d.key.replace(/\s|\(|\)|\'|\,+/g, '')})
                    .on("click", function(d){
                      d.active = !d.active;

                      d3.select(this).attr("fill", function(d){
                        if(d3.select(this).attr("fill")  == "#ccc"){
                          return color(d.key.replace(/\s|\(|\)|\'|\,+/g, ''));
                        }else {
                          return "#ccc";
                        }
                      })


                     var result = dataNest.filter(function(val,idx, arr){
                 return $("." + val.key.replace(/\s|\(|\)|\'|\,+/g, '')).attr("fill") != "#ccc" 
               // matching the data with selector status
              })

               // Hide or show the lines based on the ID
               svg.selectAll(".line").data(result, function(d){return d.key.replace(/\s|\(|\)|\'|\,+/g, '')})
                 .enter()
                 .append("path")
                 .attr("class", "line")
                 .style("stroke", function(d,i) { return d.color = color(d.key.replace(/\s|\(|\)|\'|\,+/g, '')); })
                .attr("d", function(d){
                        return countryline(d.values);
                 });

              svg.selectAll(".line").data(result, function(d){return d.key.replace(/\s|\(|\)|\'|\,+/g, '')}).exit().remove()  

                    })

        // Add the Legend text
        legend.enter().append("text")
          .attr("x", 15)
          .attr("y", function(d,i){return 10 +i*15;})
          .attr("class", "legend");

            legend.transition()
          .style("fill", "#777" )
          .text(function(d){return d.key;});

            legend.exit().remove();

            svg.selectAll(".axis").remove();

        // Add the X Axis
        svg.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .call(xAxis);

        // Add the Y Axis
        svg.append("g")
            .attr("class", "y axis")
            .call(yAxis);
    };

    function clearAll(){
      d3.selectAll(".line")
        .transition().duration(100)
                .attr("d", function(d){
            return null;
          });
      d3.select("#legend").selectAll("rect")
      .transition().duration(100)
          .attr("fill", "#ccc");
    };

    function showAll(){
      d3.selectAll(".line")
        .transition().duration(100)
                .attr("d", function(d){
            return countryline(d.values);
          });
      d3.select("#legend").selectAll("rect")
      .attr("fill",function(d) {
        if (d.active == true){
           return color(d.key.replace(/\s|\(|\)|\'|\,+/g, ''));
         }
       })
    };

    </script>
    </body>

数据:

[
    {
      "country":"Côte d'Ivoire",
      "product":"corn",
      "year":1900,
      "value":"131"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"corn",
      "year":1950,
      "value":"231"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"corn",
      "year":2000,
      "value":"191"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"corn",
      "year":2015,
      "value":"302"
    },
    {
      "country":"Hong Kong, China",
      "product":"corn",
      "year":1900,
      "value":"31"
    },
    {
      "country":"Hong Kong, China",
      "product":"corn",
      "year":1950,
      "value":"331"
    },
    {
      "country":"Hong Kong, China",
      "product":"corn",
      "year":2000,
      "value":"291"
    },
    {
      "country":"Hong Kong, China",
      "product":"corn",
      "year":2015,
      "value":"250"
    },
    {
      "country":"Canada",
      "product":"corn",
      "year":1900,
      "value":"11"
    },
    {
      "country":"Canada",
      "product":"corn",
      "year":1950,
      "value":"230"
    },
    {
      "country":"Canada",
      "product":"corn",
      "year":2000,
      "value":"185"
    },
    {
      "country":"Canada",
      "product":"corn",
      "year":2015,
      "value":"310"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"soy",
      "year":1900,
      "value":"171"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"soy",
      "year":1950,
      "value":"121"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"soy",
      "year":2000,
      "value":"231"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"soy",
      "year":2015,
      "value":"202"
    },
    {
      "country":"Canada",
      "product":"soy",
      "year":1900,
      "value":"146"
    },
    {
      "country":"Canada",
      "product":"soy",
      "year":1950,
      "value":"130"
    },
    {
      "country":"Canada",
      "product":"soy",
      "year":2000,
      "value":"195"
    },
    {
      "country":"Canada",
      "product":"soy",
      "year":2015,
      "value":"210"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"grain",
      "year":1900,
      "value":"71"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"grain",
      "year":1950,
      "value":"221"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"grain",
      "year":2000,
      "value":"31"
    },
    {
      "country":"Côte d'Ivoire",
      "product":"grain",
      "year":2015,
      "value":"102"
    },
    {
      "country":"Hong Kong, China",
      "product":"grain",
      "year":1900,
      "value":"173"
    },
    {
      "country":"Hong Kong, China",
      "product":"grain",
      "year":1950,
      "value":"194"
    },
    {
      "country":"Hong Kong, China",
      "product":"grain",
      "year":2000,
      "value":"195"
    },
    {
      "country":"Hong Kong, China",
      "product":"grain",
      "year":2015,
      "value":"230"
    },
    {
      "country":"Canada",
      "product":"grain",
      "year":1900,
      "value":"216"
    },
    {
      "country":"Canada",
      "product":"grain",
      "year":1950,
      "value":"184"
    },
    {
      "country":"Canada",
      "product":"grain",
      "year":2000,
      "value":"125"
    },
    {
      "country":"Canada",
      "product":"grain",
      "year":2015,
      "value":"150"
    }
    ]

1 个答案:

答案 0 :(得分:1)

首先,问问自己,是什么构成了传奇的每一部分?它是textrect元素,因此,我们应该将它们分组并处理它们的输入,更新和退出。其次,既然我们有一个小组,那么在每个输入,更新和退出场景中会发生什么......

// find me all the things classed with legendGroup
// if you are selecting by element type with d3 
// you are usually asking for trouble
var legendGroups = d3.select("#legend")
  .selectAll(".legendGroup")
  .data(dataNest, function(d){
    return d.key; // always try and use a key function to uniquely identify
  });

// let's handle the enter situation
// we are going to append a `g` to hold our rect and text for each piece of the legend
// note we assign this to a variable for later use
var enterGroups = legendGroups
  .enter()
  .append("g")
  .attr("class","legendGroup"); // remember to class it, so we can selectAll it later

// exit scenerio
// this is easy, it'll remove the whole group, both rect and text
legendGroups
  .exit()
  .remove();

// update scenerio
// if the element is not added or removed, we really just change it's position
legendGroups
  .attr("transform",function(d,i){
     return "translate(10," + (10 + i* 15) + ")"; // position the whole group
   });

// finally let's set up our legend items...
// Add the Legend text
enterGroups.append("text")
  .text(function(d){return d.key;})
  .attr("x", 10);

enterGroups
  .append("rect")
  .attr("width", 10)
  .attr("height", 10)
  ...