Chew Kah Meng
Chew Kah Meng

Reputation: 227

How to hide and show points on a line graph

I am trying to hide and show points on my line chart when I click on the text of my legend. Here is the link to my current fiddle. From my fiddle, my blue line working properly as it is showing and hiding on every click of 'Value'. However, the points are not doing so when I click on 'Point'.

I also tried following the advice in this link but to no avail. Any help is greatly appreciated!

    var data = [ {x: 0, y: 0}, {x: 5, y: 30}, {x: 10, y: 40},
                {x: 15, y: 60}, {x: 20, y: 70}, {x: 25, y: 100} ];

    const margin = {
      left: 20,
      right: 20,
      top: 20,
      bottom: 80
    };

    const svg = d3.select('svg');
    svg.selectAll("*").remove();

    const width = 200 - margin.left - margin.right;
    const height = 200 - margin.top - margin.bottom;

    const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);


    var x = d3.scaleLinear()
            .domain([0, d3.max(data, function(d){ return d.x; })])
            .range([0,width])
            .nice();

    var y = d3.scaleLinear()
            .domain([0, d3.max(data, function(d){ return d.y; })])
            .range([0,height])
            .nice();

    const xAxis = d3.axisTop()
                .scale(x)
                .ticks(5)
                .tickPadding(5)
                .tickSize(-height)

    const yAxis = d3.axisLeft()
                .scale(y)
                .ticks(5)
                .tickPadding(5)
                .tickSize(-width);

    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", `translate(20,${height-margin.top-60})`)
      .call(xAxis);

    svg.append("g")
      .attr("class", "y axis")
      .attr("transform", "translate(20,20)")
      .call(yAxis);

    var lineFunction = d3.line()
    .x(function(d) {return x(d.x); })
    .y(function(d) {return y(d.y); })
    .curve(d3.curveLinear);

    //defining and plotting the lines
    var path = g.append("path")
                .attr("class", "path1")
                .attr("id", "blueLine")
                .attr("d", lineFunction(data))
                .attr("stroke", "blue")
                .attr("stroke-width", 2)
                .attr("fill", "none")
                .attr("clip-path", "url(#clip)");

          // plot a circle at each data point
          g.selectAll(".dot")
              .data(data)
              .enter().append("circle")
              .attr("cx", function(d) { return x(d.x); } )
              .attr("cy", function(d) { return y(d.y); } )
              .attr("r", 3)
              .attr("class", "blackDot")
              .attr("clip-path", "url(#clip)");

      //************* Legend ***************
      var legend = svg.selectAll(".legend")
              .data(data)
              .enter().append("g")

            legend.append("rect")
              .attr("x", width + 65)
              .attr("y", 50)
              .attr("width", 18)
              .attr("height", 4)
              .style("fill", "blue")

            legend.append("text")
              .attr("x", width + 60)
              .attr("y", 50)
              .attr("dy", ".35em")
              .style("text-anchor", "end")
              .on("click", function(){
              // Determine if current line is visible
              var active   = blueLine.active ? false : true,
                newOpacity = active ? 0 : 1;
              // Hide or show the elements
              d3.select("#blueLine").style("opacity", newOpacity);
              // Update whether or not the elements are active
              blueLine.active = active;
              })
                .text(function(d) {
                  return "Value";
                });

      var pointLegend = svg.selectAll(".pointLegend")
              .data(data)
              .enter().append("g")

            pointLegend.append("circle")
              .attr("r", 3)
              .attr("cx", width + 70)
              .attr("cy", 70)

            pointLegend.append("text")
              .attr("x", width + 60)
              .attr("y", 70)
              .attr("dy", ".35em")
              .style("text-anchor", "end")
            .on("click", function(){
            // Determine if dots are visible
            var active   = blackDot.active ? false : true,
              newOpacity = active ? 0 : 1;
            // Hide or show the elements
            d3.selectAll(".blackDot").style("opacity", newOpacity);
            // Update whether or not the elements are active
            blackDot.active = active;
            })
              .text(function(d) {
                return "Point";
              });

Upvotes: 4

Views: 886

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

The problem is the way you track the clicked state. Specifically, this variable for the line...

//Determine if current line is visible
var active = blueLine.active ? false : true;

... and this variable for the circles:

// Determine if dots are visible
var active = blackDot.active ? false : true;

The line (actually, a <path> element) has an id named blueLine. Because of that, the element itself is a property of the window object, that is, a global variable. For instance, have a look at this:

console.log(window.foo)
<div id="foo"></div>

Thus, blueLine.active works, even if you never declared blueLine anywhere in the code.

However, while the line has an id, your circle's don't (be it blackDot or not). Also, the line in question is just one and therefore can have an id, but you have several circles and, because of that, an id cannot be used (more on that below).

So, the solution is tracking the clicked state another way. For instance, using a property of the datum:

.on("click", function(d){
    // Determine if dots are visible
    var active   = d.active ? false : true;

Here is the code with that change:

var data = [{
    x: 0,
    y: 0
  }, {
    x: 5,
    y: 30
  }, {
    x: 10,
    y: 40
  },
  {
    x: 15,
    y: 60
  }, {
    x: 20,
    y: 70
  }, {
    x: 25,
    y: 100
  }
];

const margin = {
  left: 20,
  right: 20,
  top: 20,
  bottom: 80
};

const svg = d3.select('svg');
svg.selectAll("*").remove();

const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;

const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);


var x = d3.scaleLinear()
  .domain([0, d3.max(data, function(d) {
    return d.x;
  })])
  .range([0, width])
  .nice();

var y = d3.scaleLinear()
  .domain([0, d3.max(data, function(d) {
    return d.y;
  })])
  .range([0, height])
  .nice();

const xAxis = d3.axisTop()
  .scale(x)
  .ticks(5)
  .tickPadding(5)
  .tickSize(-height)

const yAxis = d3.axisLeft()
  .scale(y)
  .ticks(5)
  .tickPadding(5)
  .tickSize(-width);

svg.append("g")
  .attr("class", "x axis")
  .attr("transform", `translate(20,${height-margin.top-60})`)
  .call(xAxis);

svg.append("g")
  .attr("class", "y axis")
  .attr("transform", "translate(20,20)")
  .call(yAxis);

var lineFunction = d3.line()
  .x(function(d) {
    return x(d.x);
  })
  .y(function(d) {
    return y(d.y);
  })
  .curve(d3.curveLinear);

//defining and plotting the lines
var path = g.append("path")
  .attr("class", "path1")
  .attr("id", "blueLine")
  .attr("d", lineFunction(data))
  .attr("stroke", "blue")
  .attr("stroke-width", 2)
  .attr("fill", "none")
  .attr("clip-path", "url(#clip)");

// plot a circle at each data point
g.selectAll(".dot")
  .data(data)
  .enter().append("circle")
  .attr("cx", function(d) {
    return x(d.x);
  })
  .attr("cy", function(d) {
    return y(d.y);
  })
  .attr("r", 3)
  .attr("class", "blackDot")
  .attr("clip-path", "url(#clip)");

//************* Legend ***************
var legend = svg.selectAll(".legend")
  .data(data)
  .enter().append("g")

legend.append("rect")
  .attr("x", width + 65)
  .attr("y", 50)
  .attr("width", 18)
  .attr("height", 4)
  .style("fill", "blue")

legend.append("text")
  .attr("x", width + 60)
  .attr("y", 50)
  .attr("dy", ".35em")
  .style("text-anchor", "end")
  .on("click", function() {
    // Determine if current line is visible
    var active = blueLine.active ? false : true,
      newOpacity = active ? 0 : 1;
    // Hide or show the elements
    d3.select("#blueLine").style("opacity", newOpacity);
    // Update whether or not the elements are active
    blueLine.active = active;
  })
  .text(function(d) {
    return "Value";
  });

var pointLegend = svg.selectAll(".pointLegend")
  .data(data)
  .enter().append("g")

pointLegend.append("circle")
  .attr("r", 3)
  .attr("cx", width + 70)
  .attr("cy", 70);

pointLegend.append("text")
  .attr("x", width + 60)
  .attr("y", 70)
  .attr("dy", ".35em")
  .style("text-anchor", "end")
  .on("click", function(d) {
    // Determine if dots are visible
    var active = d.active ? false : true,
      newOpacity = active ? 0 : 1;
    // Hide or show the elements
    d3.selectAll(".blackDot").style("opacity", newOpacity);
    // Update whether or not the elements are active
    d.active = active;
  })
  .text(function(d) {
    return "Point";
  });
.xy_chart {
  position: relative;
  left: 50px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg class="xy_chart"></svg>

Bear in mind that, while setting a single id to several elements is not advisable, it will work, as you can see in the demo below, where I just take your code as it is and set the blackDot id to the circles:

var data = [{
    x: 0,
    y: 0
  }, {
    x: 5,
    y: 30
  }, {
    x: 10,
    y: 40
  },
  {
    x: 15,
    y: 60
  }, {
    x: 20,
    y: 70
  }, {
    x: 25,
    y: 100
  }
];

const margin = {
  left: 20,
  right: 20,
  top: 20,
  bottom: 80
};

const svg = d3.select('svg');
svg.selectAll("*").remove();

const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;

const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);


var x = d3.scaleLinear()
  .domain([0, d3.max(data, function(d) {
    return d.x;
  })])
  .range([0, width])
  .nice();

var y = d3.scaleLinear()
  .domain([0, d3.max(data, function(d) {
    return d.y;
  })])
  .range([0, height])
  .nice();

const xAxis = d3.axisTop()
  .scale(x)
  .ticks(5)
  .tickPadding(5)
  .tickSize(-height)

const yAxis = d3.axisLeft()
  .scale(y)
  .ticks(5)
  .tickPadding(5)
  .tickSize(-width);

svg.append("g")
  .attr("class", "x axis")
  .attr("transform", `translate(20,${height-margin.top-60})`)
  .call(xAxis);

svg.append("g")
  .attr("class", "y axis")
  .attr("transform", "translate(20,20)")
  .call(yAxis);

var lineFunction = d3.line()
  .x(function(d) {
    return x(d.x);
  })
  .y(function(d) {
    return y(d.y);
  })
  .curve(d3.curveLinear);

//defining and plotting the lines
var path = g.append("path")
  .attr("class", "path1")
  .attr("id", "blueLine")
  .attr("d", lineFunction(data))
  .attr("stroke", "blue")
  .attr("stroke-width", 2)
  .attr("fill", "none")
  .attr("clip-path", "url(#clip)");

// plot a circle at each data point
g.selectAll(".dot")
  .data(data)
  .enter().append("circle")
  .attr("cx", function(d) {
    return x(d.x);
  })
  .attr("cy", function(d) {
    return y(d.y);
  })
  .attr("r", 3)
  .attr("id", "blackDot")
  .attr("class", "blackDot")
  .attr("clip-path", "url(#clip)");

//************* Legend ***************
var legend = svg.selectAll(".legend")
  .data(data)
  .enter().append("g")

legend.append("rect")
  .attr("x", width + 65)
  .attr("y", 50)
  .attr("width", 18)
  .attr("height", 4)
  .style("fill", "blue")

legend.append("text")
  .attr("x", width + 60)
  .attr("y", 50)
  .attr("dy", ".35em")
  .style("text-anchor", "end")
  .on("click", function() {
    // Determine if current line is visible
    var active = blueLine.active ? false : true,
      newOpacity = active ? 0 : 1;
    // Hide or show the elements
    d3.select("#blueLine").style("opacity", newOpacity);
    // Update whether or not the elements are active
    blueLine.active = active;
  })
  .text(function(d) {
    return "Value";
  });

var pointLegend = svg.selectAll(".pointLegend")
  .data(data)
  .enter().append("g")

pointLegend.append("circle")
  .attr("r", 3)
  .attr("cx", width + 70)
  .attr("cy", 70)

pointLegend.append("text")
  .attr("x", width + 60)
  .attr("y", 70)
  .attr("dy", ".35em")
  .style("text-anchor", "end")
  .on("click", function() {
    // Determine if dots are visible
    var active = blackDot.active ? false : true,
      newOpacity = active ? 0 : 1;
    // Hide or show the elements
    d3.selectAll(".blackDot").style("opacity", newOpacity);
    // Update whether or not the elements are active
    blackDot.active = active;
  })
  .text(function(d) {
    return "Point";
  });
.xy_chart {
  position: relative;
  left: 50px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg class="xy_chart"></svg>

But, again, it's not recommended setting the same id to several elements: ids must be unique.

Upvotes: 2

user12171106
user12171106

Reputation:

You can check out this example.

note the : .style("visibility", "hidden") in tooltip options.

Upvotes: 0

Related Questions