【问题标题】:How to show images on doughnut chart slice?如何在圆环图切片上显示图像?
【发布时间】:2021-04-06 20:05:05
【问题描述】:

我有一个项目需要显示一个圆环图。对于图表中的每个切片,图例中都有一个相应的图标。该图标也应显示在图表内的切片本身上。

我在网上找到了一个关于如何在圆环图上显示图像的工作示例:Working example。我试图将此解决方案实施到我自己的项目中。图像被加载,当我检查 SVG 时,每个路径节点(切片)都包含一个具有正确图像的图像元素。但是图像没有显示在图表上。

这是我在 atm 运行的代码。如果您对如何改进我的整体代码有一些建议,那么欢迎您这样做。我仍然是 D3.JS 的新手,目前正在学习很多:

    <!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 = 1;
        var image_width = 32;
        var image_height = 32;
        
        var data = [
            {
              key: "One",
              value: 20,
              icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
            },
            {
              key: "Two",
              value: 30,
              icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
            },
            {
              key: "Three",
              value: 10,
              icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
            },
            {
              key: "Four",
              value: 15,
              icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
            }
          ]
        // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of 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 + ')'
                );

        var radius = Math.min(width, height) / 2 - margin;

        // set the color scale
        var color = d3
          .scaleOrdinal()
          .domain(
            data.map(function(d) {
              return d["key"];
            })
          )
          .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(
          data.map(function(d) {
            return [d["key"], d["value"], d["icon"]];
          })
        );

        // declare an arc generator function
        var arc = d3
          .arc()
          .outerRadius(100)
          .innerRadius(50);

        console.log(arc);

        // 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", d => {
            return arc(d);
          })
          .attr("fill", function(d) {
            return color(d.data[0]);
          })
          .attr("stroke", "#2D3546")
          .style("stroke-width", "2px")
          .style("opacity", 0.7);

        paths
          .append("svg:image")
          .attr("transform", function(d) {
            var x = arc.centroid(d)[0] - image_width / 2;
            var y = arc.centroid(d)[1] - image_height / 2;
            return "translate(" + width / 2 + x + "," + height + y + ")";
          })
          .attr("xlink:href", function(d) {
            console.log(d);
            return d.data[2];
          })
          .attr("width", image_width)
          .attr("height", image_height);

        paths.on("mouseover", e => {
          this.pathAnim(radius, d3.select(e.currentTarget), 1);
        });
        paths.on("mouseout", e => {
          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(100)
                    .outerRadius(50)
                );
              path.style("fill", "#206BF3");
              break;

            case 1:
              path.transition().attr(
                "d",
                d3
                  .arc()
                  .innerRadius(50)
                  .outerRadius(110)
              );
              path.style("fill", "white");
              break;
          }
        }
      }
          });
        </script>
      </body>
    </html>

【问题讨论】:

    标签: image vue.js svg d3.js


    【解决方案1】:

    &lt;path&gt; 元素不能包含&lt;image&gt;。取而代之的是,使用数据创建 &lt;g&gt; 元素并将 &lt;path&gt;&lt;image&gt; 附加到它们:

    <!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 = 1;
            var image_width = 32;
            var image_height = 32;
    
            var data = [{
                key: "One",
                value: 20,
                icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
              },
              {
                key: "Two",
                value: 30,
                icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
              },
              {
                key: "Three",
                value: 10,
                icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
              },
              {
                key: "Four",
                value: 15,
                icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
              }
            ]
            // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of 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 + ')'
              );
    
            var radius = Math.min(width, height) / 2 - margin;
    
            // set the color scale
            var color = d3
              .scaleOrdinal()
              .domain(
                data.map(function(d) {
                  return d["key"];
                })
              )
              .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(
              data.map(function(d) {
                return [d["key"], d["value"], d["icon"]];
              })
            );
    
            // declare an arc generator function
            var arc = d3
              .arc()
              .outerRadius(100)
              .innerRadius(50);
    
            // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
            var g = svg
              .selectAll("whatever")
              .data(data_ready)
              .enter()
              .append("g")
              .attr("transform", function(d) {
                var x = arc.centroid(d)[0] - image_width / 2;
                var y = arc.centroid(d)[1] - image_height / 2;
                return "translate(" + width / 2 + x + "," + height + y + ")";
              });
    
            g.append("path")
              .attr("d", d => {
                return arc(d);
              })
              .attr("fill", function(d) {
                return color(d.data[0]);
              })
              .attr("stroke", "#2D3546")
              .style("stroke-width", "2px")
              .style("opacity", 0.7);
    
            g.append("svg:image")
              .attr("transform", function(d) {
                var x = arc.centroid(d)[0] - image_width / 2;
                var y = arc.centroid(d)[1] - image_height / 2;
                return "translate(" + x + "," + y + ")";
              })
              .attr("xlink:href", function(d) {
                return d.data[2];
              })
              .attr("width", image_width)
              .attr("height", image_height);
    
            g.on("mouseover", e => {
              this.pathAnim(radius, d3.select(e.currentTarget), 1);
            });
            g.on("mouseout", e => {
              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(100)
                      .outerRadius(50)
                    );
                  path.style("fill", "#206BF3");
                  break;
    
                case 1:
                  path.transition().attr(
                    "d",
                    d3
                    .arc()
                    .innerRadius(50)
                    .outerRadius(110)
                  );
                  path.style("fill", "white");
                  break;
              }
            }
          }
        });
      </script>
    </body>
    
    </html>

    【讨论】:

    • 感谢您的回复!如果将路径和图像都添加到 元素中,这不会杀死我添加的鼠标悬停过渡吗?它在代码 sn-p 中不再起作用。
    • 我还有一个问题。你真的知道如何用本地图像显示这个吗例如.attr("xlink:href", function(d) {return "img/" + d.data[2];})
    • @Stephen 我无法仅从评论中理解您的问题。我建议您发布一个新问题,并附上相关详细信息。
    • @GerardoFutado 这是我的新问题的链接,附有详细说明:stackoverflow.com/questions/65520519/…
    【解决方案2】:

    我更新了@GerardoFurtado 的代码。我只是将事件移至paths 并添加pointer-events: none 用于图像。过渡效果很好。

    g image {
      pointer-events: none;
    }
    <!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 = 1;
            var image_width = 32;
            var image_height = 32;
    
            var data = [{
                key: "One",
                value: 20,
                icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
              },
              {
                key: "Two",
                value: 30,
                icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
              },
              {
                key: "Three",
                value: 10,
                icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
              },
              {
                key: "Four",
                value: 15,
                icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png"
              }
            ]
            // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of 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 + ')'
              );
    
            var radius = Math.min(width, height) / 2 - margin;
    
            // set the color scale
            var color = d3
              .scaleOrdinal()
              .domain(
                data.map(function(d) {
                  return d["key"];
                })
              )
              .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(
              data.map(function(d) {
                return [d["key"], d["value"], d["icon"]];
              })
            );
    
            // declare an arc generator function
            var arc = d3
              .arc()
              .outerRadius(100)
              .innerRadius(50);
    
            // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
            var g = svg
              .selectAll("whatever")
              .data(data_ready)
              .enter()
              .append("g")
              /* I commented this lines and nothing changed.
              .attr("transform", function(d) {
                var x = arc.centroid(d)[0] - image_width / 2;
                var y = arc.centroid(d)[1] - image_height / 2;
                return "translate(" + width / 2 + x + "," + height + y + ")";
              });
              */
    
            g.append("path")
              .attr("d", d => {
                return arc(d);
              })
              .attr("fill", function(d) {
                return color(d.data[0]);
              })
              .attr("stroke", "#2D3546")
              .style("stroke-width", "2px")
              .style("opacity", 0.7)
              .on("mouseover", e => {
                console.log(this)
                this.pathAnim(radius, d3.select(e.currentTarget), 1);
              })
              .on("mouseout", e => {
                var thisPath = d3.select(e.currentTarget);
                if (!thisPath.classed("clicked")) {
                  this.pathAnim(radius, thisPath, 0);
                }
              });
    
            g.append("svg:image")
              .attr("transform", function(d) {
                var x = arc.centroid(d)[0] - image_width / 2;
                var y = arc.centroid(d)[1] - image_height / 2;
                return "translate(" + x + "," + y + ")";
              })
              .attr("xlink:href", function(d) {
                return d.data[2];
              })
              .attr("width", image_width)
              .attr("height", image_height);
          },
          methods: {
            pathAnim(radius, path, dir) {
              switch (dir) {
                case 0:
                  path
                    .transition()
                    .duration(500)
                    .ease(d3.easeBounce)
                    .attr(
                      "d",
                      d3
                      .arc()
                      .innerRadius(100)
                      .outerRadius(50)
                    );
                  path.style("fill", "#206BF3");
                  break;
    
                case 1:
                  path.transition().attr(
                    "d",
                    d3
                    .arc()
                    .innerRadius(50)
                    .outerRadius(110)
                  );
                  path.style("fill", "white");
                  break;
              }
            }
          }
        });
      </script>
    </body>
    
    </html>

    【讨论】:

    • 如果我运行代码 sn-p,Chrome 中的控制台仍然会报错:d3.v6.js:1720 Error: &lt;g&gt; attribute transform: Expected ')', "….593376811331936,45057.361070055…".。动画正在运行。
    • 您可能在复制代码时出错。这段代码没有任何错误。
    • 当我在 Chrome 中打开 sn-p 并打开控制台窗口 (F12) 时出现此错误。
    • 在 Firefox 中我得到 0 个错误。但在 Chrome 中我会这样做。
    • 是的,我明白了。请看我的更新。我从外部 g 中删除了变换。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-26
    • 2016-08-28
    • 1970-01-01
    • 2016-08-01
    • 2021-08-22
    • 1970-01-01
    • 2022-11-16
    相关资源
    最近更新 更多