1

My goal is to create a webpage that presents a data-driven legend, and radio buttons. I would like the user's selection of radio buttons to change the legend.

The complicating factor is that the legend relies on a nesting function, and I can't figure out how to make the "key" of the nesting function responsive to the radio buttons.

Here's are the steps I've taken so far:

  • Create the legend (nest data, then attach nest keys to rectangles).
  • On the click of the radio button, make d3 select all the rectangles in the DOM.
  • Send the radio button's selection to a function called updateKey, which contains the nest variable. Substitute the d.key portion of nest with the radio button selection.
  • Change the fill attribute of the rectangle to color(d.key).

My problem is that nest variable doesn't seem to change within the updateKey function, even though my research on global variables indicates that it should change.

Code here:

var doc = URL.createObjectURL(new Blob([`Passed Category    Owner   Dup
    yes msg Hailey  yes
    yes msg Hailey  yes
    yes doc Hailey  yes
    yes doc Robert  yes
    yes msg Laquila yes
    no  spreadsheet Hailey  yes
    `]));

d3.tsv(doc)
  .row(function(d) {
    return {
      Passed: d.Passed,
      Category: d.Category,
      Owner: d.Owner,
      Dup: d.Dup
    };
  })
  .get(function(error, data) {

    var svg = d3.select("#chart").append("svg")
      .append("g");
    //create a function for applying colours
    var cat20 = d3.schemeCategory20

    var colors = d3.scaleOrdinal()
      .domain(function(d) {
        return nest(d.key);
      })
      .range(cat20);
    //pick one column of data and nest according to that column
    nest = d3.nest()
      .key(function(d) {
        return d.Category
      })
      .sortKeys(d3.descending)
      .entries(data)

    var legend = d3.select("#legend")
      //Make the legend
      .append("svg")
      .attr("class", "legend")
      .selectAll("g")
      .data(d3.range(20))
      .enter()
      .append("g")
      .attr("transform", function(d, i) {
        return "translate(0," + i * 25 + ")";
      });
    //make the legend squares
    legend.append("rect")
      .data(nest)
      .attr("class", "rect")
      .attr("width", 18)
      .attr("height", 18)
      .style("fill", function(d) {
        return colors(d.key)
      })
      .style('stroke', "Grey");
    //make the legend text
    legend.append("text")
      .data(nest)
      .attr("x", 24)
      .attr("y", 9)
      .attr("dy", ".35em")
      .text(function(d) {
        return d.key;
      });

    function updateKey(h) {

      var nest = d3.nest()
        .key(function(d) {
          return (h)
        })
        .sortKeys(d3.descending)
        .entries(data)
    }
   
    d3.selectAll(("input[name='stack']")).on("change", function(d) {
      var colVar = this.value;
      if (colVar == 'Category') {
        d3.selectAll(".rect").style("fill", function(d) {
          updateKey("d['" + colVar + "']")
          return colors(d.key)
        })
      }
    })


  })

UPDATE: I have confirmed that the function is retrieving a new nest variable, because when I input console.log(nest) in the last block of code, it returns the correct key-value object. However, if I add console log(d.key) immediately below console.log(nest), it returns the keys of the nest variable that was set at the beginning of the code. This suggests that d.key is somehow not changing, even though nest is changing. I think I need to set d.key explicitly.

UPDATE 2: My new strategy is to create all the legends, then use the buttons to appear/disappear legends based on class.

oymonk
  • 427
  • 9
  • 27

1 Answers1

2

I think your problem here is confusion around the nest function and that your nest variable isn't holding a reference to that function but your data instead.

That said, I find your code overly confusing and not written to how you should be using d3 data binding abilities. So here's my commented refactor working example:

<!DOCTYPE html>

<html>
  <head>
    <script src="https://d3js.org/d3.v4.min.js"></script>
  </head>

  <body>
    <input type="radio" id="passed" name="stack" value="Passed" />
    <label for="passed">Passed</label><br />
    <input type="radio" id="category" name="stack" value="Category" checked />
    <label for="category">Category</label><br />
    <input type="radio" id="owner" name="stack" value="Owner" />
    <label for="owner">Owner</label>

    <div id="chart"></div>
    <br/><br/>
    <div id="legend"></div>
    <script>
      var doc = URL.createObjectURL(
        new Blob([
          `Passed,Category,Owner,Dup
yes,msg,Hailey,yes
yes,msg,Hailey,yes
yes,doc,Hailey,yes
yes,doc,Robert,yes
yes,msg,Laquila,yes
no,spreadsheet,Hailey,yes`,
        ])
      );

      // had to switch to csv for stacksnippet editor
      d3.csv(doc)
        .row(function (d) {
          return {
            Passed: d.Passed,
            Category: d.Category,
            Owner: d.Owner,
            Dup: d.Dup,
          };
        })
        .get(function (error, data) {

          // container for legend
          var legend = d3
            .select('#legend')
            //Make the legend
            .append('svg')
            .attr('class', 'legend');

          // initial draw
          drawLegend('Category');

          // function to redraw legend
          // based on d3 enter, update, exit
          function drawLegend(currentSelection) {

            // apply nest function for new selection
            let nestedData = d3
              .nest()
              .sortKeys(d3.descending)
              .key(function (d) {
                return d[currentSelection];
              }).entries(data);

            // update selection
            let lu = legend
              .selectAll('g')
              // the key function here is important
              // so that d3 can calculate the enter, update, exit
              .data(nestedData, d => d.key);

            // exit selection
            lu.exit().remove();

            // enter selection
            // add container g
            let le = lu
              .enter()
              .append('g')
              .attr('transform', function (d, i) {
                return 'translate(0,' + i * 25 + ')';
              });

            // on enter add rect
            le.append('rect')
              .attr('class', 'rect')
              .attr('width', 18)
              .attr('height', 18)
              .style('fill', function (d,i) {
                return d3.schemeCategory20[i];
              })
              .style('stroke', 'Grey');

            // on enter add text
            le.append('text')
              .attr('x', 24)
              .attr('y', 9)
              .attr('dy', '.35em');

            // enter + update
            lu = le.merge(lu);

            // on enter or update, change text
            lu.selectAll('text')
              .text(function(d){
                return d.key;
              });
          }

          d3.selectAll("input[name='stack']").on('change', function (d) {
            drawLegend(this.value);
          });
        });
    </script>
  </body>
</html>
Mark
  • 106,305
  • 20
  • 172
  • 230