Issue with useInteractiveGuideline() in nvd3.js - d3.js

I have a chart object which uses interactive guideline:
var chart = nv.models.lineChart();
chart.useInteractiveGuideline(true);
[...]
If I later (say during an event) clear the chart via something like this:
d3.select("#{{id}} > *").remove();
Then the problem for me is that whilst the chart area is made blank (as desired), the mouse tooltip / interactive guideline remains.
My question is: How can I remove the interactive guideline at the same time that the chart is removed?

Before running d3.select("#{{id}} > *").remove(); you can run
d3.select("#{{id}}")
.on("mousemove", null)
.on("mouseout", null)
.on("dblclick", null)
.on("click", null);
and d3 will remove the event listeners.
Look at the documentation https://github.com/mbostock/d3/wiki/Selections#on

d3.select only finds the first element matching the selector. In this situation that would be the svg of the plot but not the tooltip which is a div sibling of the svg.
Instead use selectAll, which would find the svg and div:
d3.selectAll("#{{id}} > *").remove();

Related

Highlight all content inside g tag, whenever mouse above said tag. D3

I'm creating a 'hand-drawn' style chart in d3:
Each bar below is a path inside of a g of class bar. When I hover over each g.bar, I highlight all paths inside with a basic mouseover function:
d3.selectAll('g.bar')
.on('mouseover', function() {
d3.select(this).selectAll('path').style('stroke', 'red')
})
The problem is, the highlighting only occurs when I hover over the paths, not the entire g.bar.
This makes the highlighting look super glitchy (running a mouse across it repeatedly highlights/unhighlights the path).
My question is: Is there a way to have all associated paths highlight whenever I'm hovering over the entire g.bar outlining the bar itself, and not just when I highlight over the path elements themselves?
A live-demo of my code is here: https://blockbuilder.org/jwilber/4bd8f5dd73666cdc5a30d7d6481e231a
Thanks for any help!
You could just add the following css:
g.bar {
pointer-events: bounding-box;
}
or directly set the g.bar elements' pointer-events attribute, which in your code would look like:
bar.setAttribute('pointer-events', 'bounding-box');
this sets up the g.bar elements to listen to events anywhere within the actual space that they take up (the bounding box).
However, the above only works in Chrome.
Another alternative that seems to work in all the browsers I've tried is to add a transparent rect element to each g.bar element (just as a sibling of the path).
data.forEach(function (d) {
let node = rc.rectangle(0, y(d.trick), x(d.count), y.bandwidth());
bar = roughSvg.appendChild(node);
bar.setAttribute('class', 'bar');
});
d3.selectAll('g.bar')
.data(data)
.append('rect')
.attr('x', 0)
.attr('y', d => y(d.trick))
.attr('width', d => x(d.count))
.attr('height', y.bandwidth())
.attr('fill', 'transparent');
I'm guessing that this works because rather than the g.bar mouseout event happening whenever it falls between the strokes of the path, there is a solid rect element filling the space, even if it is transparent.

Rendering in the background of a dc.js chart with renderlet

I use dc.js for showing the results of multiple classification algorithms. More specifically, I want to show a precision recall chart (each point corresponds to a result of a classification system).
I already used a dc.js scatter chart for this which works fine.
Additionally I would like to have a d3 contour in the background of the chart which shows the F-measure.
This is already implemented. The only issue is that the contour part is in the foreground and not in the background of the chart.
Please have a look at the jsfiddle for a full example.
Two questions are still open for me because I'm not a dc.js or d3 expert:
Is there a way to put the contour in the background or the symbols(cycles) of the scatter chart in the foreground (I already tried it with the help of this stackoverflow question but with no success)
I used the 'g.brush' selector to get the area of the inner chart. This works fine as long as the brushing is turned on. Is the selector a good way to go or are there better alternatives (which may also work if brushing is switched off).
In my example I put the contour part in the upper left corner to see if it works but I also provide the code (currently uncommented) to increase the width and height of the contour to the correct size.
chart
.on('renderlet', function (chart) {
var innerChart = chart.select('g.brush');
var width = 300, height=300;
//getting the correct width, height
//var innerChartBoundingRect = innerChart.node().getBoundingClientRect();
//var width = innerChartBoundingRect.width, height=innerChartBoundingRect.height;
[contours, color] = generateFmeasureContours(width,height, 1);
innerChart
.selectAll("path")
.data(contours)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", d => color(d.value));
var symbols = chart.chartBodyG().selectAll('path.symbol');
symbols.moveToFront();
});
jsfiddle
Putting something in the background is a general purpose SVG skill.
SVG renders everything in the order it is declared, from back to front, so the key is to put your content syntactically before everything else in the chart.
I recommend encapsulating it in an svg <g> element, and to get the order right you can use d3-selection's insert method and the :first-child CSS selector instead of append:
.on('pretransition', function (chart) {
// add contour layer to back (beginning of svg) only if it doesn't exist
var contourLayer = chart.g().selectAll('g.contour-layer').data([0]);
contourLayer = contourLayer
.enter().insert('g', ':first-child')
.attr('class', 'contour-layer')
.attr('transform', 'translate(' + [chart.margins().left,chart.margins().top].join(',') + ')')
.merge(contourLayer);
A few more points on this implementation:
use dc's pretransition event because it happens immediately after rendering and redrawing (whereas renderlet waits for transitions to complete)
the pattern .data([0]).enter() adds the element only if it doesn't exist. (It binds a 1-element array; it doesn't matter what that element is.) This matters because the event handler will get called on every redraw and we don't want to keep adding layers.
we give our layer the distinct class name contour-layer so that we can identify it, and so the add-once pattern works
contourLayer = contourLayer.enter().insert(...)...merge(contourLayer) is another common D3 pattern to insert stuff and merge it back into the selection so that we treat insertion and modification the same later on. This would probably be simpler with the newer selection.join method but tbh I haven't tried that yet.
(I think there may also have been some improvements in ordering that might be easier than insert, but again, I'm going with what I know works.)
finally, we fetch the upper-left offset from the margin mixin
Next, we can retrieve the width and height of the actual chart body using
(sigh, undocumented) methods from dc.marginMixin:
var width = chart.effectiveWidth(), height = chart.effectiveHeight();
And we don't need to move dots to front or any of that; the rest of your code is as before except we use this new layer instead of drawing to the brushing layer:
contourLayer
.selectAll("path")
.data(contours)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", d => color(d.value));
Fork of your fiddle.
Again, if you'd like to collaborate on getting a contour example into dc.js, that would be awesome!

D3 - Select element in this svg?

I have a codepen here - https://codepen.io/anon/pen/yvgJKB
I have two d3 charts with different data.
I want to have tooltips on the bars so when you rollover them it shows the value.
I want the tooltip to be separate html in the svg cos the actual charts are responsive and I dont want the tooltips to scale with the svg.
The tooltip works as expected in the top chart.
In the bottom chart the tooltip appears in the top chart.
I think this is becasue the let tooltip = d3.select('.tooltip').append("div") is selecting the first tooltip which is in the first chart.
The charts and tooltip is dynamically created.
This there a way in the D3 to say d3.select('.tooltip') in this svg something like d3.select(this)('.tooltip')
let tooltip = d3.select('.tooltip').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
There is a lot of unnecessary repetition in your code.
Create the tooltip div only once. Also, since you are creating the div in your D3 code, you don't need any div with the tooltip class in your HTML. Remove both.
Besides that, use d3.event.pageX and d3.event.pageY to position your tooltip div anywhere in the page. A single tooltip div can be used to show the tooltip to any number of SVGs you have on that page.
Here is your updated CodePen: https://codepen.io/anon/pen/KQaBvE?editors=0010
Tooltips should be created separately by selecting different charts (or some other unique marker) and given unique variable names within the same scope:
let tooltip1 = d3.select('.chart1').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
let tooltip2 = d3.select('.chart2').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
https://codepen.io/mortonanalytics/pen/BYpwaz

D3.js : Select the NOT selected to fade them (line chart, line chart legend)

In a multi line chart I successfully highlight a line when I mouseover, as well as make the corresponding entry in a legend appear bold and larger. I also want to fade the other names in the legend to further increase contrast. How do I make a selection of the names NOT in my selection for the mouseover?
Here is an excerpt of my mouseover event:
.on("mouseover", function (d) {
d3.select(this)
.style("stroke-width",'6px'); // Make the line thicker on mouseover.
var getname = document.getElementById(d.name); // Line name to highlight in legend
// How to get the list of names NOT selected??
//var notsel = ??? (not(getname) ??
d3.selectAll(notsel) // Fade the non-selected names in the legend
.style("opacity",.2);
//This class highlights the selected name by CSS (not shown)
d3.select(getname)
.attr("class", "legend-select"); //Change class to highlight name
D3 uses css3 selectors, so what you're looking for is a negative css selector. Turns out those exist. Negative CSS selectors
Try something like this
d3.selectAll(':not('+getname+')') // Fade the non-selected names in the legend
.style("opacity",.2);

How to properly add and use D3 Events?

I'm having trouble understanding using D3 events and dispatch functions. I have a chart example that I've been working on called: "Vertical Bar Charts With Legends."
Drawing the charts and the legends was easy enough but I'd like to add the ability to highlight each bar as I mouseover its correlating text legend, located to the right of the chart.
I've read through all of the event documentation and even looked at a number of examples, most of which are pretty complicated, but I seem to be missing something. Would anyone know how to best accomplish the text legend mouseover functionality that dispatches events to automatically change colors of the corresponding vertical bars?
This question is similar to the one you posted in the d3-js Google Group. Without duplicating what I wrote there, I would reiterate that you probably don't want d3.dispatch; that is intended for custom event abstractions (such as brushes and behaviors). It'll be simpler to use native events.
If you want your legend to change the color of the corresponding bar on mouseover, then breakdown the problem into steps:
Detect mouseover on the legend.
Select the corresponding bar.
Change the bar's fill color.
First, use selection.on to listen for "mouseover" events on the legend elements. Your listener function will be called when the mouse goes over a legend element, and will be called with two arguments: the data (d) and the index (i). You can use this information to select the corresponding bar via d3.select. Lastly, use selection.style to change the "fill" style with the new color.
If you're not sure how to select the corresponding bar on legend mouseover, there are typically several options. The most straightforward is to select by index, assuming that the number of legend elements and number of rect elements are the same, and they are in the same order. In that case, if a local variable rect contains the rect elements, you could say:
function mouseover(d, i) {
d3.select(rect[0][i]).style("fill", "red");
}
If you don't want to rely on index, another option is to scan for the matching bar based on identical data. This uses selection.filter:
function mouseover(d, i) {
rect.filter(function(p) { return d === p; }).style("fill", "red");
}
Yet another option is to give each rect a unique ID, and then select by id. For example, on initialization, you could say:
rect.attr("id", function(d, i) { return "rect-" + i; });
Then, you could select the rect by id on mouseover:
function mouseover(d, i) {
d3.select("#rect-" + i).style("fill", "red");
}
The above example is contrived since I used the index to generate the id attribute (in which case, it's simpler and faster to use the first technique of selecting by index). A more realistic example would be if your data had a name property; you could then use d.name to generate the id attribute, and likewise select by id. You could also select by other attributes or class, if you don't want to generate a unique id.
Mike's answer is great.
I used it come up with this for selecting a cell in a grid I was drawing:
.on('click', (d, i) ->
console.log("X:" + d.x, "Y:" + d.y) #displays the cell x y location
d3.select(this).style("fill", "red");
So when I am entering the data in I added the event listener and using d3.select(this).
See the code in context below:
vis.selectAll("rect")
.data(singleArray)
.enter().append("svg:rect")
.attr("stroke", "none")
.attr("fill", (d) ->
if d.lifeForm
return "green"
else
return "white")
.attr("x", (d) -> xs(d.x))
.attr("y", (d) -> ys(d.y))
.attr("width", cellWidth)
.attr("height", cellHeight)
.on('click', (d, i) ->
console.log("X:" + d.x, "Y:" + d.y)
d3.select(this).style("fill", "red");
return
)

Resources