Reputation: 227
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
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
Reputation:
You can check out this example.
note the : .style("visibility", "hidden") in tooltip options.
Upvotes: 0