【问题标题】:Vue D3JS create interactive doughnut chartVue D3JS 创建交互式圆环图
【发布时间】:2020-12-29 09:17:30
【问题描述】:

我有一个项目需要重新创建某个圆环图。该图表由蓝色部分组成,悬停时需要弹出并更改颜色。我在网上找到了一些关于如何重新创建此动画的文档:documentation

问题是我试图在我的 Vue 组件中实现这个解决方案,但是当我在图表内外徘徊时它不断抛出错误。

当我将鼠标悬停在图表中时,出现以下错误:Cannot read property 'startAngle' of undefined 在以下行:path.transition().attr("d", d3.arc().innerRadius(radius * 0.7).outerRadius(radius * 1.08));。我似乎无法理解为什么会发生此错误,因为我没有使用 startAngle 函数。

当我将鼠标移出图表时,我在if (!thisPath.classed("clicked")) { 线上也收到以下错误node.getAttribute is not a function

这是我第一次使用 D3.JS,我仍在尝试了解有关该库的很多信息。我喜欢你可以创建多少不同的图表

这是我的组件的完整代码:

<template>
  <div class="p-3 flex flex-col items-center h-full">
    <div class="w-full flex-1 h-full">
      <div ref="chart" class="flex justify-center h-full"></div>
    </div>
  </div>
</template>
<script>
import * as d3 from "d3";

export default {
  name: "DoughnutChartItem",
  props: {
    data: {
      type: Array,
      required: true
    },
    height: {
      type: Number,
      required: true
    },
    width: {
      type: Number,
      required: true
    }
  },
  mounted() {
    // // set the dimensions and margins of the graph
    // var width = 450;
    // var height = 450;
    var margin = 50;

    // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
    var radius = Math.min(this.width, this.height) / 2 - margin;

    // append the svg object to the div called 'chart'
    var svg = d3
      .select(this.$refs["chart"])
      .append("svg")
      .attr("width", "100%")
      .attr("height", "100%")
      .attr("viewBox", `0 0 ${this.width} ${this.height}`)
      .append("g")
      .attr(
        "transform",
        "translate(" + this.width / 2 + "," + this.height / 2 + ")"
      );

    // // Create dummy data
    // var data = { a: 9, b: 20, c: 30, d: 8, e: 12 };

    // set the color scale
    var color = d3
      .scaleOrdinal()
      .domain(Object.keys(this.data))
      .range(["#206BF3"]);

    // Compute the position of each group on the pie:
    var pie = d3.pie().value(function(d) {
      return d[1];
    });

    var data_ready = pie(Object.entries(this.data));

    // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
    svg
      .selectAll("whatever")
      .data(data_ready)
      .enter()
      .append("path")
      .attr(
        "d",
        d3
          .arc()
          .innerRadius(100) // This is the size of the donut hole
          .outerRadius(radius)
      )
      .attr("fill", function(d) {
        return color(d.data[0]);
      })
      .attr("stroke", "black")
      .style("stroke-width", "2px")
      .style("opacity", 0.7);

    svg.on("mouseover", () => {
      this.pathAnim(radius, d3.select(this), 1);
    });
    svg.on("mouseout", () => {
      var thisPath = d3.select(this);
      if (!thisPath.classed("clicked")) {
        this.pathAnim(radius, thisPath, 0);
      }
    });
  },
  methods: {
    pathAnim(radius, path, dir) {
      switch (dir) {
        case 0:
          path
            .transition()
            .duration(500)
            .ease("bounce")
            .attr(
              "d",
              d3
                .arc()
                .innerRadius(radius * 0.7)
                .outerRadius(radius)
            );
          break;

        case 1:
          console.log(
            path.transition().attr(
              "d",
              d3
                .arc()
                .innerRadius(radius * 0.7)
                .outerRadius(radius * 1.08)
            )
          );
          // path.transition().attr(
          //   "d",
          //   d3
          //     .arc()
          //     .innerRadius(radius * 0.7)
          //     .outerRadius(radius * 1.08)
          // );
          break;
      }
    }
  }
};
</script>

</script>

【问题讨论】:

    标签: javascript vue.js animation d3.js charts


    【解决方案1】:

    三个问题。

    首先,您将“mouseover”(和“mouseout”)事件分配给整个SVG。您想将其分配给每个人 path

    其次,this 有问题。

    svg.on("mouseover", () => {
      this.pathAnim(radius, d3.select(this), 1);
    });
    

    这里的 this 变量是 VUE 组件(因此 this.pathAnim 是可调用的)。但是,当您选择 d3.select(this) 时,您选择的是整个 VUE 组件,而不是您要设置动画的 path 元素(参见第一点)。

    您可以将其重写为:

    var paths = svg //<-- hold reference to selection of paths
        .selectAll('whatever')
        .data(data_ready)
        .enter()
        .append('path')
        .attr(
          'd',
          d3
           .arc()
           .innerRadius(100)
           .outerRadius(radius)
        )
        .attr('fill', function (d) {
          return color(d.data[0]);
        })
        .attr('stroke', 'black')
        .style('stroke-width', '2px')
        .style('opacity', 0.7);
    
        paths.on('mouseover', (d,i,j) => { //<-- operate on paths
            this.pathAnim(radius, d3.select(j[i]), 1); //<-- use alternate way to select path without needing this variable
        });
    

    第三,您再次查看旧的 d3 代码,但使用更新的 d3 库。轻松方法now accepts a function,而不是字符串。

    path
        .transition()
        .duration(500)
        .ease(d3.easeBounce) //<-- ease function
        .attr(
            'd',
            d3
             .arc()
             .innerRadius(radius * 0.7)
             .outerRadius(radius)
         );
    

    运行代码:

    <!DOCTYPE html>
    <html>
      <head>
        <title></title>
        <script src="https://unpkg.com/vue"></script>
        <script src="https://d3js.org/d3.v5.js"></script>
      </head>
      <body>
        <div class="p-3 flex flex-col" id="one">
          <div class="w-full flex-1">
            <div id="my_dataviz"></div>
          </div>
        </div>
    
        <script>
          new Vue({
            el: '#one',
            data: {
              type: Array,
              required: true,
            },
            mounted() {
              // set the dimensions and margins of the graph
              var width = 450;
              var height = 450;
              var margin = 40;
    
              // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
              var radius = Math.min(width, height) / 2 - margin;
    
              // append the svg object to the div called 'my_dataviz'
              var svg = d3
                .select('#my_dataviz')
                .append('svg')
                .attr('width', width)
                .attr('height', height)
                .append('g')
                .attr(
                  'transform',
                  'translate(' + width / 2 + ',' + height / 2 + ')'
                );
    
              // Create dummy data
              var data = { a: 9, b: 20, c: 30, d: 8, e: 12 };
    
              // set the color scale
              var color = d3
                .scaleOrdinal()
                .domain(Object.keys(data))
                .range(['#98abc5', '#8a89a6', '#7b6888', '#6b486b', '#a05d56']);
    
              // Compute the position of each group on the pie:
              var pie = d3.pie().value(function (d) {
                return d[1];
              });
    
              var data_ready = pie(Object.entries(data));
    
              // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
              var paths = svg
                .selectAll('whatever')
                .data(data_ready)
                .enter()
                .append('path')
                .attr(
                  'd',
                  d3
                    .arc()
                    .innerRadius(100) // This is the size of the donut hole
                    .outerRadius(radius)
                )
                .attr('fill', function (d) {
                  return color(d.data[0]);
                })
                .attr('stroke', 'black')
                .style('stroke-width', '2px')
                .style('opacity', 0.7);
    
              paths.on('mouseover', (d,i,j) => {
                this.pathAnim(radius, d3.select(j[i]), 1);
              });
              paths.on('mouseout', (d,i,j) => {
                var thisPath = d3.select(j[i]);
                if (!thisPath.classed('clicked')) {
                  this.pathAnim(radius, thisPath, 0);
                }
              });
            },
            methods: {
              pathAnim(radius, path, dir) {
                switch (dir) {
                  case 0:
                    path
                      .transition()
                      .duration(500)
                      .ease(d3.easeBounce)
                      .attr(
                        'd',
                        d3
                          .arc()
                          .innerRadius(radius * 0.7)
                          .outerRadius(radius)
                      );
                    break;
    
                  case 1:
                    //console.log(
                      path
                      .transition()
                      .attr(
                        'd',
                        d3
                          .arc()
                          .innerRadius(radius * 0.7)
                          .outerRadius(radius * 1.08)
                      )
                    //);
                    // path.transition().attr(
                    //   "d",
                    //   d3
                    //     .arc()
                    //     .innerRadius(radius * 0.7)
                    //     .outerRadius(radius * 1.08)
                    // );
                    break;
                }
              },
            },
          });
        </script>
      </body>
    </html>

    评论更新 (d3.js V6.0)

    对不起,我错过了那个。版本 6 中的事件处理 changed significantly。请参阅下面的更新。

    <!DOCTYPE html>
    <html>
      <head>
        <title></title>
        <script src="https://unpkg.com/vue"></script>
        <script src="https://d3js.org/d3.v6.js"></script>
      </head>
      <body>
        <div class="p-3 flex flex-col" id="one">
          <div class="w-full flex-1">
            <div id="my_dataviz"></div>
          </div>
        </div>
    
        <script>
          new Vue({
            el: '#one',
            data: {
              type: Array,
              required: true,
            },
            mounted() {
              // set the dimensions and margins of the graph
              var width = 450;
              var height = 450;
              var margin = 40;
    
              // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
              var radius = Math.min(width, height) / 2 - margin;
    
              // append the svg object to the div called 'my_dataviz'
              var svg = d3
                .select('#my_dataviz')
                .append('svg')
                .attr('width', width)
                .attr('height', height)
                .append('g')
                .attr(
                  'transform',
                  'translate(' + width / 2 + ',' + height / 2 + ')'
                );
    
              // Create dummy data
              var data = { a: 9, b: 20, c: 30, d: 8, e: 12 };
    
              // set the color scale
              var color = d3
                .scaleOrdinal()
                .domain(Object.keys(data))
                .range(['#98abc5', '#8a89a6', '#7b6888', '#6b486b', '#a05d56']);
    
              // Compute the position of each group on the pie:
              var pie = d3.pie().value(function (d) {
                return d[1];
              });
    
              var data_ready = pie(Object.entries(data));
    
              // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
              var paths = svg
                .selectAll('whatever')
                .data(data_ready)
                .enter()
                .append('path')
                .attr(
                  'd',
                  d3
                    .arc()
                    .innerRadius(100) // This is the size of the donut hole
                    .outerRadius(radius)
                )
                .attr('fill', function (d) {
                  return color(d.data[0]);
                })
                .attr('stroke', 'black')
                .style('stroke-width', '2px')
                .style('opacity', 0.7);
    
              paths.on('mouseover', (e,d) => {
                this.pathAnim(radius, d3.select(e.currentTarget), 1);
              });
              paths.on('mouseout', (e,d) => {
                var thisPath = d3.select(e.currentTarget);
                if (!thisPath.classed('clicked')) {
                  this.pathAnim(radius, thisPath, 0);
                }
              });
            },
            methods: {
              pathAnim(radius, path, dir) {
                switch (dir) {
                  case 0:
                    path
                      .transition()
                      .duration(500)
                      .ease(d3.easeBounce)
                      .attr(
                        'd',
                        d3
                          .arc()
                          .innerRadius(radius * 0.7)
                          .outerRadius(radius)
                      );
                    break;
    
                  case 1:
                    //console.log(
                      path
                      .transition()
                      .attr(
                        'd',
                        d3
                          .arc()
                          .innerRadius(radius * 0.7)
                          .outerRadius(radius * 1.08)
                      )
                    //);
                    // path.transition().attr(
                    //   "d",
                    //   d3
                    //     .arc()
                    //     .innerRadius(radius * 0.7)
                    //     .outerRadius(radius * 1.08)
                    // );
                    break;
                }
              },
            },
          });
        </script>
      </body>
    </html>

    【讨论】:

    • (d,i,j) 中的j 是什么?
    • 感谢您的详细回复。我已经尝试了 sn-p,但它不适用于我的 V6(我目前正在使用)我可以降级到 V5,但是有没有办法让它适用于 V6?我收到以下错误:Cannot read property '#&lt;Object&gt;' of undefined 当我更新到 &lt;script src="https://d3js.org/d3.v6.js"&gt;&lt;/script&gt;
    • @dgknca, in d3.js j 是当前数据绑定中的选择(元素集合)。这在第 6 版中发生了显着变化,见上文。
    • @Mark 非常感谢。你是天使!!继续努力:)。
    • @Mark 感谢队友的详细解释!
    猜你喜欢
    • 1970-01-01
    • 2022-07-07
    • 1970-01-01
    • 2019-03-08
    • 2019-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-27
    相关资源
    最近更新 更多