如何让SVG元素鼠标事件通过另一个元素冒泡?

时间:2013-08-20 15:39:32

标签: javascript svg d3.js

我有一个D3折线图,我在图表后面放置一个矩形。这个rec​​t附加了一个鼠标事件,但问题是我的图表还有另一个矩形覆盖在图表上方,该图表上还附有事件。

如何让较低的矩形鼠标事件在顶部覆盖的较高矩形上方冒泡?非常感谢!

我在这里创建了一个小提琴:

http://jsfiddle.net/TnjCC/1/

这是我的代码。寻找“这就是我需要鼠标悬停起泡的地方”评论,看看我想要冒出哪个元素。

var data = [
  {"date":"1-May-13","close":58.13},
  {"date":"30-Apr-13","close":53.98},
  {"date":"27-Apr-13","close":67.00},
  {"date":"26-Apr-13","close":89.70},
  {"date":"25-Apr-13","close":99.00},
  {"date":"24-Apr-13","close":130.28},
  {"date":"23-Apr-13","close":166.70},
  {"date":"20-Apr-13","close":234.98},
  {"date":"19-Apr-13","close":345.44},
  {"date":"18-Apr-13","close":443.34},
];

var margin = {top: 20, right: 50, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var parseDate = d3.time.format("%d-%b-%y").parse,
    bisectDate = d3.bisector(function(d) { return d.date; }).left,
    formatValue = d3.format(",.2f"),
    formatCurrency = function(d) { return "$" + formatValue(d); };

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var line = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.close = +d.close;

  data.sort(function(a, b) {
    return a.date - b.date;
  });

  x.domain([data[0].date, data[data.length - 1].date]);
  y.domain(d3.extent(data, function(d) { return d.close; }));

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Price ($)");

  <!-- This is where I need the mouseover to bubble up -->
  var left = x(new Date("Apr 23 2013"));
  var right = x(new Date("Apr 26 2013"));
  var wid = right - left;
  svg.append("rect")
      .attr("id", "range")
      .attr("class", "range")
      .attr("x", left)
      .attr("width", wid)
      .attr("height", height)
      .on("mouseover", function () {
          alert("I can see you!");
      })      

  svg.append("path")
      .datum(data)
      .attr("class", "line")
      .attr("d", line);

  var focus = svg.append("g")
      .attr("class", "focus")
      .style("display", "none");

  focus.append("circle")
      .attr("r", 4.5);

  focus.append("text")
      .attr("x", 9)
      .attr("dy", ".35em");

  svg.append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)
      .on("mouseover", function() { focus.style("display", null); })
      .on("mouseout", function() { focus.style("display", "none"); })
      .on("mousemove", mousemove);

  function mousemove() {
    var x0 = x.invert(d3.mouse(this)[0]),
        i = bisectDate(data, x0, 1),
        d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
    focus.select("text").text(formatCurrency(d.close));
  }
});

3 个答案:

答案 0 :(得分:6)

要进行快速修复,您可以移动叠加层上方的范围,并从该范围手动调用叠加事件处理程序。

http://jsfiddle.net/Rk5Hp/

 svg.append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)
      .on("mouseover", function() { focus.style("display", null); })
      .on("mouseout", function() { focus.style("display", "none"); })
      .on("mousemove", mousemove);

  // move range above overlay and call the overlay event handlers from there
  svg.append("rect")
      .attr("id", "range")
      .attr("class", "range")
      .attr("x", left)
      .attr("width", wid)
      .attr("height", height)
      .on("mousemove", mousemove)
      .on("mouseout", function() { focus.style("display", "none"); })
      .on("mouseover", function() {
          focus.style("display", null);
          // event handling for range mouseover (alert broke mouse move)
          console.log("I can see you!");
      });

鼓泡在dom级别起作用,因为没有办法让rect成为另一个rect的孩子,所以冒泡不会为你处理这个问题。将元素组合在一起并放置一个检查组上事件目标的处理程序将使您无法注册事件处理程序两次,但遇到相同的基本问题:当元素重叠时,在源顺序中最后声明的元素将获得事件

答案 1 :(得分:4)

您还可以使用以下样式来“隐藏”鼠标事件的某些svg元素。就我而言,这是mouseover事件,我想要通过:

pointer-events: none;

答案 2 :(得分:0)

以上所有答案都是正确的,但我想举一个例子:

    let log = console.log
let data = []
let pointStart = document.querySelector("svg").createSVGPoint()
let pointStop = document.querySelector("svg").createSVGPoint()
let divLog = d3.select("#log")
var svg = d3.select("svg")

var linearfn = d3.line()
  .x(d => d.x)
  .y(d => d.y)
  .curve(d3.curveLinear)

function logTagName(eventName, tagName) {
  divLog.html(divLog.html() + eventName + " : " + tagName + "<br/>")
}

svg.on("click", function() {
    log("tagName: ", event.target.tagName)
    logTagName("svg click", event.target.tagName)
    pointStart.x = event.x - 8
    pointStart.y = event.y - 8

    data.push({
      x: pointStart.x,
      y: pointStart.y
    })

    svg.selectAll("path") // SVG içinde tanımlı path elemanlarını bul
      .data([1]).enter() // 1 elemanlı dizinin eleman sayısı kadarı için enter()
      .append('path') // dizi elemanı kadar path oluştur
      .attr("fill", "none")
      .attr("stroke", "black")
      .attr("stroke-width", 8)
      .attr("d", linearfn(data))
      .on("click", function() {
        log("tagName: ", event.target.tagName)
        logTagName("path click", event.target.tagName)
        /* click event will start from path and pass to the svg element */
        // event.stopPropagation(); // letting pass event bubbling 
      })

    svg.selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("cx", d => d.x + .5)
      .attr("cy", d => d.y + .5)
      .attr("r", 20)
      .attr("stroke-width", 3)
      .attr("stroke", "red")
      .attr("cursor", "move")
      .style("fill", "transparent")
      .attr("pointer-events", "all") // when clicked in/outside of circle, it'll handle events
      .on("mouseover", function() {
        log("over oldu")
        d3.select(this).style("stroke", "blue");
      })
      .on("mouseout", function() {
        log("out oldu")
        d3.select(this).style("stroke", "red");
      })
      .on("click", function() {
        event.stopPropagation(); // not letting pass event bubbling
        event.preventDefault();
        log("click oldu")
        d3.select(this).style("stroke", "black");
      })
  })
  .on("mousemove", function() {
    // fare hareketinde de çizdireceğiz ama x,y noktasını 
    // tıklanıncaya kadar diziye eklemeyeceğiz
    pointStop.x = event.x - 8
    pointStop.y = event.y - 8

    svg.select("path")
      .attr("d", linearfn(data.concat({
        x: pointStop.x,
        y: pointStop.y
      })))
  })

https://jsfiddle.net/jsfiddleCem/hnsu68jw/