Search code examples
javascriptd3.jsvisualization

How to hide/show specific lines with a checkbox on a line chart?


I made a multiple line chart visualization with d3.js. But with so many lines (each line is a column on my dataset), it's obviously unreadable. So I want a checkbox where the user can choose which line he wants to display. My problem is, in all the tutorials I see, I need to manually write the name of each column I want to display in the checkbox...is there a way to take automatically all my variables in the checkbox ?

As an example, I would like a result like this one : https://d3-graph-gallery.com/graph/line_select.html

For the moment I only have a script which displays all my variables on the graph, and I don't know what I need to add.

I leave you my code it its current state:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Test pollen avec code D3 Graph Gallery</title>
  <style>
    body {
font: 12px sans-serif;
    }
.axis path,
axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue; stroke-width: 1.5px;
}
  </style>
</head>
<body>
  <h1 style="margin: 2em;">Concentration pollinique au Luxembourg, 1992-2018</h1>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
  <script>
    const margin = {top: 20, right: 80, bottom: 30, left: 100},
width = 1200 - margin. left - margin. right,
height = 500 - margin.top - margin. bottom;
// formater date
const parseDate = d3. time.format("%Y-%m-%d").parse;
const x = d3.time.scale()
  .range ([0, width]);
const y = d3.scale. linear()
  .range ( [height, 0]);
const color = d3. scale. category10();
// axes x et y avec leur orientation
const xAxis = d3. svg.axis ()
  .scale (x)
  .orient ("bottom");
const yAxis = d3. svg.axis ()
  .scale (y)
  .orient ("left");

const line = d3.svg.line()
.interpolate ("basis")
.x(function(d) { return x(d. date); }) 
.y(function(d) { return y(d.concentration); });

let 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 + ")");


d3.csv ("pollen_tri.csv", function(error, data) {
  color.domain (d3.keys (data[0]).filter(function (key) { return key !== "date"; })); 

  data.forEach(function(d) {
  d.date = parseDate (d.date);
  });

  var plants = color.domain().map (function (name) {
  return {
    name: name,
  values: data.map(function (d) {
  return {date: d.date, concentration: +d [name]};
    })
  };
});

x.domain (d3.extent (data, function(d) { return d.date; }));
y.domain([
  d3.min(plants, function(c) { return d3.min(c.values, function(v) {return v.concentration;}); }),
  d3.max(plants, function(c) { return d3.max(c.values, function(v) { return v.concentration;});})
]);


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

  
svg.append ("g")
    .attr("class", "y axis") 
    .call (yAxis) 
  .append ("text")
  . attr("transform", "rotate (-90)") 
  .attr("y", 6)
  .attr("dy", " .71em")
  .style("text-anchor", "end") 
  .text ("Concentration (pollens/m3)");

var plant = svg.selectAll(".plant")
    .data(plants)
  .enter() 
  .append ("g")
  .attr("class", "plant");  


plant.append("path")
  .attr("class", "line")
  .attr("d", function(d) { return line(d.values); })
  .style("stroke", function(d) { return color(d.name); });  


});
</script>

</body>
</html>

Here's also an image of my current graph: enter image description here

Finally, here's a sample of my csv file:

date Ambrosia Artemisia Asteraceae Alnus Betula Ericaceae Carpinus Castanea Quercus Chenopodium Cupressaceae Acer Fraxinus Gramineae Fagus Juncaceae Aesculus Larix Corylus Juglans Umbellifereae Ulmus Urtica Rumex Populus Pinaceae Plantago Platanus Salix Cyperaceae Filipendula Sambucus Tilia
1992-01-01 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1992-01-02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1992-01-03 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1992-01-04 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1992-01-05 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1992-01-06 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1992-01-07 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1992-01-08 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1992-01-09 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Solution

  • Here's an example of how to automatically create checkboxes which will show/hide the lines.

    I used plain Javascript, but you could do the same thing using d3.

    This example creates checkboxes (<input type="checkbox">). To get a dropdown menu instead, use <select> and <option>.

    There are three steps:

    1. Create a Javascript object which will map a plant name to an SVG <path>. In other words, it will let us look up an SVG <path> from a plant name.
    2. Populate the map.
    3. Create checkboxes and add event listeners.

    The code I changed:

    const mapPlantNameToSvgPath = {};
    
    plant.append("path")
        .attr("class", "line")
        .attr("d", function (d) { return line(d.values); })
        .style("stroke", function (d) { return color(d.name); })
        //
        // https://d3js.org/d3-selection/control-flow#selection_each
        .each(function (d) {
            const svgPath = this;
            const plantName = d.name;
            mapPlantNameToSvgPath[plantName] = svgPath;
        })
    
    
    color.domain().forEach(function (plantName) {
    
        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label
        const label = document.createElement("label");
    
        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox
        const checkbox = document.createElement("input");
        checkbox.setAttribute("type", "checkbox");
        checkbox.setAttribute("checked", "true");
    
        checkbox.addEventListener("change", function () {
            const svgPath = mapPlantNameToSvgPath[plantName];
    
            // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/visibility
            if (checkbox.checked) {
                svgPath.setAttribute("visibility", "visible");
            } else {
                svgPath.setAttribute("visibility", "hidden");
            }
    
    
        });
    
        label.append(checkbox);
        label.append(document.createTextNode(plantName));
    
        document.body.append(document.createElement("br"));    
        document.body.append(label);
    });
    

    Screenshot (with fake data in the csv file):

    line plot with checkboxes to show or hide lines