selectAll( "rect" ) 选择所有矩形,但不将鼠标悬停功能应用于某些矩形



通过网络书的出色交互式数据可视化工作,并创建了(a的怪物)脚本来创建一个交互式条形图:

  1. 单击SVG元素时,将一个新的栏添加到结束时
  2. 单击P元素时,将生成一组新的50个bar

我添加了一个鼠标事件侦听器,以更改悬停时的条形。问题在于,通过1添加的条形。上面没有改变颜色。据我所知,这些条形的选择是正确选择的,但是无论出于何种原因,鼠标事件永远不会被解雇:

svg.select(".bars").selectAll("rect")
  .on("mouseover", function() {
    d3.select(this)
        .transition()
        .attr("fill", "red");
  })

事先感谢您的帮助,它总是非常感谢。

这是完整的代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Interactive Data Visualization for the Web Program-Along</title>
    <style>
      /* axes are made up of path, line, and text elements */
      .axis path,
      .axis line {
        fill: none;
        stroke: navy;
        shape-rendering: crispEdges;
      }
      .axis text {
        font-family: sans-serif;
        font-size: 11px;
        /* color is CSS property, but need SVG property fill to change color */
        fill: navy;
      }
    </style>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
  </head>
  <body>
    <p>Click on this text to update the chart with new data values.</p>
    <script type="text/javascript">
      var n = 50;
      var domain = Math.random() * 1000;
      function gen_data(n, domain) {
        var d = [];
        for (var i = 0; i < n; i++) {
          d.push(
                  { id: i, val: Math.random() * domain }
          );
        }
        return d;
      }
      // define key function once for use in .data calls
      var key = function(d) {
        return d.id;
      };
      var dataset = gen_data(n, domain);
      // define graphic dimensions
      var w = 500, h = 250, pad = 30;
      // get input domains
      var ylim = d3.extent(dataset, function(d) {
        return d.val;
      });
      // define scales
      var x_scale = d3.scale.ordinal()
          .domain(d3.range(dataset.length))
          .rangeRoundBands([0, w - pad], 0.15);
      var y_scale = d3.scale.linear()
          .domain([ylim[0], ylim[1] + pad])  // could have ylim[0] instead
        // range must be backward [upper, lower] to accommodate svg y inversion
          .range([h, 0]);  // tolerance to avoid clipping points
      var color_scale = d3.scale.linear()
          .domain([ylim[0], ylim[1]])
          .range([0, 255]);
      // create graphic
      var svg = d3.select("body").append("div").append("svg")
          .attr("width", w)
          .attr("height", h);
      svg.append("g")
          .attr("class", "bars")
          .selectAll(".bars rect")
          .data(dataset)
          .enter()
          .append("rect")
          .attr({
            x: function(d, i) {
              return x_scale(i) + pad;
            },
            y: function(d) {
              return y_scale(d.val);
            },
            width: x_scale.rangeBand(),  // calculates width automatically
            height: function(d) { return h - y_scale(d.val); },
            opacity: 0.6,
            fill: function(d) {
              return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
            }
          });
      // add axes
      var yAxis = d3.svg.axis()
          .scale(y_scale)  // must be passed data-to-pixel mapping (scale)
          .ticks(3)  // optional (d3 can assign ticks automatically)
          .orient("left");
      // since function, must be called
      // create <g> to keep things tidy, to style via CSS, & to adjust placement
      svg.append("g")
          .attr({
            class: "axis",
            transform: "translate(" + pad + ",0)"
          })
          .call(yAxis);
      // add event listener for clearing/adding all new values
      d3.select("p")
          .on("click", function() {
            // generate new dataset
            dataset = gen_data(n, domain);
            // remove extra bars
            d3.selectAll(".bars rect")
              .data(dataset, function(d, i) { if (i < 50) { return d; }})
                .exit()
                .transition()
                .attr("opacity", 0)
                .remove();
            // update scales
            x_scale.domain(d3.range(dataset.length))
                .rangeRoundBands([0, w - pad], 0.15);
            ylim = d3.extent(dataset, function(d) {
              return d.val;
            });
            y_scale.domain([ylim[0], ylim[1] + pad]);
            // update bar values & colors
            d3.selectAll(".bars rect")
                .data(dataset)
                .transition()
                .duration(500)
                .attr("x", function(d, i) { return x_scale(i) + pad; })
                .transition()  // yes, it's really this easy...feels like cheating
                .delay(function(d, i) { return i * (1000 / dataset.length); })  // set dynamically
                .duration(1000)  // optional: control transition duration in ms
                .each("start", function() {
                  // "start" results in immediate effect (no nesting transitions)
                  d3.select(this)  // this to select each element (ie, rect)
                      .attr("fill", "magenta")
                      .attr("opacity", 0.2);
                })
                .attr({
                  y: function(d) { return y_scale(d.val); },
                  height: function(d) { return h - y_scale(d.val); }
                })
                .each("end", function() {
                  d3.selectAll(".bars rect")
                      .transition()
                      // needs delay or may interrupt previous transition
                      .delay(700)
                      .attr("fill", function(d) {
                        return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
                      })
                      .attr("opacity", 0.6)
                      .transition()
                      .duration(100)
                      .attr("fill", "red")
                      .transition()
                      .duration(100)
                      .attr("fill", function(d) {
                        return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
                      });
                });
            // update axis (no need to update axis-generator function)
            svg.select(".axis")
                .transition()
                .duration(1000)
                .call(yAxis);
          });
      // extend dataset by 1 for each click on svg
      svg.on("click", function() {
        // extend dataset & update x scale
        dataset.push({ id: dataset.length, val: Math.random() * domain });
        x_scale.domain(d3.range(dataset.length));
        // add this datum to the bars <g> tag as a rect
        var bars = svg.select(".bars")
            .selectAll("rect")
            .data(dataset, key);
        bars.enter()  // adds new data point(s)
            .append("rect")
            .attr({
              x: w,
              y: function(d) {
                return y_scale(d.val);
              },
              width: x_scale.rangeBand(),  // calculates width automatically
              height: function(d) { return h - y_scale(d.val); },
              opacity: 0.6,
              fill: function(d) {
                return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
              }
            });
        // how does this move all the other bars!?
        // because the entire dataset is mapped to bars
        bars.transition()
            .duration(500)
            .attr("x", function(d, i) {
              return x_scale(i) + pad;
            });
      });
      // add mouseover color change transition using d3 (vs CSS)
      svg.select(".bars").selectAll("rect")
          .on("mouseover", function() {
            d3.select(this)
                .transition()
                .attr("fill", "red");
          })
          .on("mouseout", function(d) {
            d3.select(this)
                .transition()
                .attr("fill", function() {
                  return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
                })
                .attr("opacity", 0.6);
          })
          // print to console when clicking on bar = good for debugging
          .on("click", function(d) { console.log(d); });
    </script>
  </body>
</html>

更新:

感谢Miroslav的建议,我开始以不同的方式解决问题,并遇到了Makyen对此相关的回答。

虽然我认为有一种更有性能的方法来处理此操作,但每当鼠标使用以下代码进入SVG元素时,我决定重新命令鼠标浏览器侦听器:

  svg.on("mouseover", mouse_over_highlight);
  // add mouseover color change transition using d3 (vs CSS)
  function mouse_over_highlight() {
    d3.selectAll("rect")
        .on("mouseover", function () {
          d3.select(this)
              .transition()
              .attr("fill", "red");
        })
        .on("mouseout", function (d) {
          d3.select(this)
              .transition()
              .attr("fill", function () {
                return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
              })
              .attr("opacity", 0.6);
        })
      // print to console when clicking on bar = good for debugging
        .on("click", function (d) {
          console.log(d);
        });
  }

原因您的事件仅为第一个bar触发而不是动态的原因是因为您添加了事件侦听器的方式。

您的方式只将事件放在页面上已经存在的元素上(它们处于DOM结构中)。任何新元素都不会与他们绑在一起的侦听器。

您可以通过将代码像

一样快速检查一下
function setListeners() {
  svg.select(".bars").selectAll("rect").on("mouseover", function() {
    d3.select(this)
      .transition()
      .attr("fill", "red");
  })
}

在屏幕上添加任何新条后,添加此功能调用,然后查看是否适用于所有元素。如果确实如此,则需要以一种为所有元素(包括动态添加的元素)编写事件侦听器。做到这一点的方法是将事件设置为某些父dom节点,然后检查您是否正好徘徊在要触发事件的东西上。

示例:

$(document).on(EVENT, SELECTOR, function(){
    code;
});

这将使事件放在身体上,如果您触发了正确的元素,则可以在触发后检查选择器。但是,自从我与D3和一起工作以来已经有一段时间了,我不确定D3,SVG和JQuery如何一起玩,上次我这样做时,他们遇到了一些麻烦。在这种情况下,应该是鼠标,选择器应该是您的矩形栏,并且功能应该是您想要运行的。

如果其他所有内容都失败了,以防万一它们不合作,只需使用该函数设置事件侦听器,并在您动态添加新元素后每次调用它。

最新更新