I have a linechart made with d3
, but due to the shape of the data, the lines and dots (I'm using dot's over the lines for each specific data point) usually end up being in top of each other.
To counter this problem, I ended giving opacity 0.4
to the lines and dots, and when you hover over a line, the lines and dots of this specific line of data pops out, and sets it's opacity to 1
.
My problem is: I'm using the .raise()
funcion to make them pop out and stand over the rest of the lines and dots, the function is working only with my lines selection and not with my dots selection, and I don't know why.
My code:
// draw the data lines
const lines = svg.selectAll('.line')
.data(this.data)
.enter()
.append('path')
.attr('class', 'data.line')
.attr("fill", "none")
.attr("stroke", d => colors(d.key))
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 2.5)
.attr('stroke-opacity', 0.4)
.attr('d', d => line(d.values))
.on('mouseenter', d => {
// Highlight them
let myCircles = circles.selectAll('.circle');
lines.attr('stroke-opacity', b => {
return b.key === d.key ? 1 : 0.4;
});
myCircles.attr('fill-opacity', b => {
return b[this.typeIdentifier] === d.key ? 1 : 0.4;
});
// Bring them to the front
myCircles = circles.selectAll('.circle')
.filter(b => b[this.typeIdentifier] === d.key);
const myLines = lines.filter(b => b.key === d.key);
myLines.raise();
myCircles.raise();
});
// draw the circles
const circles = svg.selectAll('.circle')
.data(this.data)
.enter()
.append('g');
circles.selectAll('.circle')
.data(d => d.values)
.enter()
.append('circle')
.attr('class', 'circle')
.attr('stroke', 'white')
.attr('stroke-width', 1)
.attr('r', 6)
.attr('fill', d => colors(d[this.typeIdentifier]))
.attr('fill-opacity', 0.4)
.attr('cx', d => x(d[this.xAxisValue]) + x.bandwidth() / 2)
.attr('cy', d => y(d[this.yAxisValue]))
.on('mouseenter', (d, b, j) => {
tooltip.raise();
tooltip.style("display", null);
tooltip.select("#text1").text(d[this.typeIdentifier])
.attr('fill', colors(d[this.typeIdentifier]));
tooltip.select('#text4').text(d[this.yAxisValue]);
tooltip.select('#text5').text(d[this.xAxisValue]);
const tWidth = tooltip.select('#text1').node().getComputedTextLength() > 60 ? tooltip.select('#text1').node().getComputedTextLength() + 20 : 80;
tooltipRect.attr('width', tWidth);
const xPosition = d3.mouse(j[b])[0];
const yPosition = d3.mouse(j[b])[1];
if (xPosition + tWidth + 35 < this.xWIDTH) { // display on the right
tooltip.attr("transform", `translate(${xPosition + 15}, ${yPosition - 25})`);
} else { // display on the left
tooltip.attr("transform", `translate(${xPosition - tWidth - 15}, ${yPosition - 25})`);
}
})
.on('mouseleave', d => {
tooltip.style("display", "none");
})
So, when you hover the mouse over a line, this should bring the line and dots associated to it to the front, with opacity 1
, but for some reason, it's only working on the lines
selection, and not on the myCircles
selection. The selection is not empty, and I've been printing them all along to test it out. Also, I've tried to bring the circles one by one (with singular selections, and with raw elements) to the front using the .raise()
method, and it's not working eiter.
Why is it not working? Could it have to do with the tooltip on hover over the circles? Am I doing something wrong and not seeing it?
I have a linechart made with d3
, but due to the shape of the data, the lines and dots (I'm using dot's over the lines for each specific data point) usually end up being in top of each other.
To counter this problem, I ended giving opacity 0.4
to the lines and dots, and when you hover over a line, the lines and dots of this specific line of data pops out, and sets it's opacity to 1
.
My problem is: I'm using the .raise()
funcion to make them pop out and stand over the rest of the lines and dots, the function is working only with my lines selection and not with my dots selection, and I don't know why.
My code:
// draw the data lines
const lines = svg.selectAll('.line')
.data(this.data)
.enter()
.append('path')
.attr('class', 'data.line')
.attr("fill", "none")
.attr("stroke", d => colors(d.key))
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 2.5)
.attr('stroke-opacity', 0.4)
.attr('d', d => line(d.values))
.on('mouseenter', d => {
// Highlight them
let myCircles = circles.selectAll('.circle');
lines.attr('stroke-opacity', b => {
return b.key === d.key ? 1 : 0.4;
});
myCircles.attr('fill-opacity', b => {
return b[this.typeIdentifier] === d.key ? 1 : 0.4;
});
// Bring them to the front
myCircles = circles.selectAll('.circle')
.filter(b => b[this.typeIdentifier] === d.key);
const myLines = lines.filter(b => b.key === d.key);
myLines.raise();
myCircles.raise();
});
// draw the circles
const circles = svg.selectAll('.circle')
.data(this.data)
.enter()
.append('g');
circles.selectAll('.circle')
.data(d => d.values)
.enter()
.append('circle')
.attr('class', 'circle')
.attr('stroke', 'white')
.attr('stroke-width', 1)
.attr('r', 6)
.attr('fill', d => colors(d[this.typeIdentifier]))
.attr('fill-opacity', 0.4)
.attr('cx', d => x(d[this.xAxisValue]) + x.bandwidth() / 2)
.attr('cy', d => y(d[this.yAxisValue]))
.on('mouseenter', (d, b, j) => {
tooltip.raise();
tooltip.style("display", null);
tooltip.select("#text1").text(d[this.typeIdentifier])
.attr('fill', colors(d[this.typeIdentifier]));
tooltip.select('#text4').text(d[this.yAxisValue]);
tooltip.select('#text5').text(d[this.xAxisValue]);
const tWidth = tooltip.select('#text1').node().getComputedTextLength() > 60 ? tooltip.select('#text1').node().getComputedTextLength() + 20 : 80;
tooltipRect.attr('width', tWidth);
const xPosition = d3.mouse(j[b])[0];
const yPosition = d3.mouse(j[b])[1];
if (xPosition + tWidth + 35 < this.xWIDTH) { // display on the right
tooltip.attr("transform", `translate(${xPosition + 15}, ${yPosition - 25})`);
} else { // display on the left
tooltip.attr("transform", `translate(${xPosition - tWidth - 15}, ${yPosition - 25})`);
}
})
.on('mouseleave', d => {
tooltip.style("display", "none");
})
So, when you hover the mouse over a line, this should bring the line and dots associated to it to the front, with opacity 1
, but for some reason, it's only working on the lines
selection, and not on the myCircles
selection. The selection is not empty, and I've been printing them all along to test it out. Also, I've tried to bring the circles one by one (with singular selections, and with raw elements) to the front using the .raise()
method, and it's not working eiter.
Why is it not working? Could it have to do with the tooltip on hover over the circles? Am I doing something wrong and not seeing it?
Share Improve this question edited Dec 30, 2018 at 15:17 Gerardo Furtado 102k9 gold badges128 silver badges177 bronze badges asked Dec 25, 2018 at 16:37 Maximiliano Andres Friedl SchwMaximiliano Andres Friedl Schw 3016 silver badges21 bronze badges1 Answer
Reset to default 8Actually, selection.raise()
is working. The problem here is just the tree structure of your SVG: all the circles for a given line belong to a <g>
element.
If you look at the docs, you'll see that selection.raise()
:
Re-inserts each selected element, in order, as the last child of its parent.
The emphasis above is mine: the key work here is parent. So, what you want is to raise the <g>
element that contains the selected circles above the other <g>
elements for the other circles, not the circles inside their <g>
parent.
In your case, it's as simple as changing...
myCircles = circles.selectAll('.circle').filter(etc...)
...to:
myCircles = circles.filter(etc...)
Now, myCircles
is the selection with the <g>
element, which you can raise. Pay attention to the filter
function: as you didn't share your data structure I don't know if the data array for the <g>
elements (that is, this.data
) contains the key
property. Change it accordingly.
Here is a demo:
We have a set of circles for each line, each set inside their own <g>
parent. Only the left circles are separated, all other circles are draw one over the other on purpose. When you hover over a circle (use the ones on the left) its <g>
container is raised, in this case using...
d3.select(this.parentNode).raise()
..., so all circles are visible:
const svg = d3.select("svg");
const scale = d3.scaleOrdinal(d3.schemeSet1);
const lineGenerator = d3.line()
.x(function(d) {
return d.x
})
.y(function(d) {
return d.y
})
const data = d3.range(5).map(function(d) {
return {
key: d,
values: d3.range(5).map(function(e) {
return {
x: 50 + 100 * e,
y: e ? 150 : 50 + 50 * d
}
})
}
});
const lines = svg.selectAll(null)
.data(data)
.enter()
.append("path")
.attr("d", function(d) {
return lineGenerator(d.values);
})
.style("fill", "none")
.style("stroke-width", "3px")
.style("stroke", function(d) {
return scale(d.key)
});
const circleGroups = svg.selectAll(null)
.data(data)
.enter()
.append("g");
const circles = circleGroups.selectAll(null)
.data(function(d) {
return d.values
})
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.style("fill", function(d) {
return scale(d3.select(this.parentNode).datum().key)
});
circles.on("mouseover", function(d) {
const thisKey = d3.select(this.parentNode).datum().key;
lines.filter(function(e) {
return e.key === thisKey;
}).raise();
d3.select(this.parentNode).raise();
})
<script src="https://d3js/d3.v5.min.js"></script>
<svg width="500" height="300"></svg>