D3: How to handle zooming and tooltips in a single

2019-05-06 20:32发布

问题:

I have a visualization that is essentially a series of stacked bar charts, each of which contains several panels. For example, here are three such bar charts, each with four panels.

I have managed to implement pan/zoom functionality that is coordinated across the charts. This is what it looks like if I zoom into the third panel from the last image, for example. The zoom behavior is called from an invisible rectangle that is placed over each chart.

My problem is that I want to enable tooltip functionality based on the location of the user's cursor within a plot. Since the zoom-rectangles are placed on top of the charts, however, no mouse events are registered for any SVG elements in the actual charts themselves.

Des anyone know a way around this?

回答1:

I was following Mike Bostock's example, and like you placing a rect across my whole chart and then calling the zoom behaviour on that, and like you found that it was consuming all the pointer events.

I found an example here that seemed to be achieving what I wanted, and I found that if I scrap the rect and just call the zoom behaviour on the svg element directly, I still get pointer events for the child elements.

I'm a noob here, I don't really understand why this works. I also guess this might have its own ramifications e.g. I guess this stops you limiting the area of your graphic in which mouse events cause a zoom. You may notice that the example I linked creates a sub-svg; I don't know, but perhaps this is to solve that problem.



回答2:

You can probably put the mouseevent on the same rectangle you are using for detecting your zoom. Hard to say for sure without a code sample, but I would expect you can do something along these lines:

 svg.select("rect.overlay")
     .on("mouseover.tooltip", function() { tooltip.style("display", null); })
     .on("mouseout.tooltip", function() { tooltip.style("display", "none"); })
     .on("mousemove.tooltip", mousemoveFunc);

Adding the ".tooltip" to the event adds a namespace to the event, so if you end up having any collision with your zoom listeners, you can add a namespace to them too.



回答3:

In your css put the style ponter-events:none for your zooming rectangles. That way the cursor events will be sensed by the elements blow.



回答4:

I know this is way too late, but I've just figured out a workaround. For me the crucial thing is order of adding bits to the svg.

svg1.append("rect")//put the rectangle for zoom events on the bottom
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(d3.zoom()
    .scaleExtent([0.5, 10])
    .on("zoom", zoomed));

var g = svg1.append("g"); //add g element for visualisation (above the rectangle).

function zoomed() { //zoom around the g's (g has to be before this, but after the rectangle)
g.attr("transform", d3.event.transform)  
}

Then a little bit later on add my force elements to the g

 var nodes = g.append("g")
    .attr("class", "nodes")
    .selectAll("circles")
    .attr('id', function(d) {
        return d.n_id
    })

etc. Slight issue here is I can't actually zoom with my mouse over the circles, but I have loads of white space. I'm going to try and allow propagation of zoom events, or call zoom events from the circles