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

标签 javascript svg d3.js

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

  1. 单击 svg 元素时在末尾添加一个新栏
  2. 点击 p 元素时生成一组新的 50 个条形图

我添加了一个鼠标悬停事件监听器,以在悬停时更改条形的颜色。问题是通过上面 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 对 this related SO post 的回答。 .

虽然我想象有一种更高效的方法来处理这个问题,但我决定在每次鼠标进入 svg 元素时使用以下代码重新绑定(bind) mouseover 事件监听器:

  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);
        });
  }

最佳答案

您的事件仅在第一个栏触发而不是动态栏触发的原因是您添加事件监听器的方式。

您的方式仅将事件放在页面上已存在的元素上(它们位于 DOM 结构中)。任何新元素都不会绑定(bind)此事件监听器。

您可以通过将代码放入类似

的函数中来快速检查这一点
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 如何一起使用,上次我这样做时他们遇到了一些麻烦。 在这种情况下,事件应该是鼠标悬停,选择器应该是您的矩形条,函数应该是您想要运行的内容。

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

关于javascript - selectAll ("rect") 选择所有矩形,但不对某些矩形应用鼠标悬停功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30078916/

相关文章:

javascript - Bootstrap btn-group-toggle 根据值更改另一个组

css - 带有指针事件的 Svg 在 Safari 中不起作用

image - 从另一个 SVG <path> 中剪出一个

css - svg 中的中心组元素

javascript - d3js svg viewBox 不允许计算 window.innerWidth - 170

javascript - D3.js 雷达图工具提示

javascript - 如何使用nodejs在mariadb中批量插入?

javascript - 使用来自 ajax 的 javascript 和 php 编辑表中的数据

d3.js - 如何绘制绘制数学函数的自动缩放 D3.js 图表?

javascript - 多个 jQuery 幻灯片实例