简体   繁体   English

如何在 dc.js 饼图切片中添加图标而不是文本

[英]How to add icons in dc.js piechart slices instead texts

I have been working with dc.js for a year now.我已经使用 dc.js 一年了。 Recently I have been tasked to implement a pie chart as below:最近我的任务是实现一个饼图,如下所示:

用户需求图片

I want to replace the text labels in the pie chart slices with appropriate images.我想用适当的图像替换饼图切片中的文本标签。

I saw this implemented in pure d3.js.我看到这是在纯 d3.js 中实现的。 Can someone help me translate the implementation to dc.js?有人可以帮我将实现翻译成 dc.js 吗?

http://jsfiddle.net/LLwr4q7s/ http://jsfiddle.net/LLwr4q7s/

pie
    .width(600)
    .height(500)
    .radius(200)
    .innerRadius(120)
    .dimension(disastersDimension)
    .group(disastersGroup)
    .on("filtered", function (chart, filter) {
      var sel = filter;
      let percentage = 0,
        value = 0;
      let disastersBuffer = [];
      totalAmount = 0;

      pie.selectAll("text.pie-slice").text((d) => {
        percentage = dc.utils.printSingleValue(
          ((d.endAngle - d.startAngle) / (2 * Math.PI)) * 100
        );
        disastersBuffer.push({ ...d.data, percentage });
        totalAmount += parseFloat(d.data.value);
      });

      filterPiechart(sel, percentage, totalAmount, disastersBuffer, value);
    })
    .on("renderlet", (chart) => {
      if (!chart.selectAll("g.selected")._groups[0].length) {
        chart.filter(null);
        filterPiechart("", 100, totalAmount, [], 0);
      }
      var arc = chart.radius(250).innerRadius(100);
      console.log(arc);
      var g = chart.selectAll(".pie-slice");

      chart
        .selectAll(".pie-slice")
        .append("image")
        .attr("xlink:href", "img/disasters/Floods.png")
        .attr("transform", "translate(-10,10) rotate(315)")
        .attr("width", "26px")
        .attr("hight", "26px")
        .style("background-color", "white")
        .attr("x", function (d) {
          var bbox = this.parentNode.getBBox();
          return bbox.x;
        })
        .attr("y", function () {
          var bbox = this.parentNode.getBBox();
          return bbox.y;
        });

      g.append("g")
        .append("svg:image")
        .attr("xlink:href", function (d) {
          let filteredImage = self.piedata.find(
            (i) => i.label == d.data.key
          );
          let image = filteredImage ? filteredImage.image : "";
          return image;
        })
        .attr("width", 30)
        .attr("height", 40)
        .attr("x", function (d) {
          var bbox = this.parentNode.getBBox();
          return bbox.x;
        })
        .attr("y", function (d) {
          var bbox = this.parentNode.getBBox();
          return bbox.y;
        });
    })
    .addFilterHandler(function (filters, filter) {
      filters.length = 0; // empty the array
      filters.push(filter);
      return filters;
    });

I took the fiddle and added a couple of 'Meteoicons' I found here .我拿起小提琴,添加了几个我在这里找到的“Meteoicons”。

( Of course, those icons are taken as an example and I have no permission to use them commercially ) 当然,这些图标只是一个例子,我没有商业使用的许可

The icons are stored in a separated <svg> elements.图标存储在单独的<svg>元素中。 To render an icon, just select its root <g> element and copy its content to another <g> you create in your piechart:要呈现图标,只需 select 其根<g>元素并将其内容复制到您在饼图中创建的另一个<g>中:

g.append("g")
  .attr("transform", d => `translate(${arc.centroid(d)}) scale(0.25)`)
  .append('g')
  .attr('transform', 'translate(-256,-256)') // The original icons are 256 x 256
  .html(d => d3.select(`#meteo-icon-${... some attribute of d ...} > g`).html())

The code is for demonstration purposes only, you will need to modify it for your needs.该代码仅用于演示目的,您需要根据需要对其进行修改。

See the result in the snippet below:请参阅以下代码段中的结果:

 var width = 550, height = 550, radius = 250, colors = d3.scale.ordinal().range(['#336699 ','#336699 ','#ACD1E9','#ACD1E9','#ACD1E9']); var image_width=40, image_height=40; var piedata = [ { label: "test", image: "http://placeimg.com/40/40/any", value: 50 }, { label: "", image: "http://placeimg.com/42/42/any", value: 50 }, { label: "Jonathan", image: "http://placeimg.com/44/44/any", value: 50 }, { label: "Lorenzo", image: "http://placeimg.com/46/46/any", value: 50 }, { label: "Hillary", image: "http://placeimg.com/38/38/any", value: 50 } ] var pie = d3.layout.pie().value(function(d) { return d.value; }) var arc = d3.svg.arc().outerRadius(250).innerRadius(100) var svg = d3.select('body').append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate('+(width-radius)+','+(height-radius)+')'); var g = svg.selectAll(".arc").data(pie(piedata)).enter().append("g").attr("class", "arc"); g.append("path").attr("d", arc).style("fill", function(d,i) { return colors(i); }); g.append("g").attr("transform", d => `translate(${arc.centroid(d)}) scale(0.25)`).append('g').attr('transform', 'translate(-256,-256)').html(() => d3.select(`#meteo-icon-${Math.random() < 0.5? 1: 2} > g`).html()).selectAll('path').style('fill', 'orange');
 path { stroke: #fff; fill-rule: evenodd; } text { font-family: Arial, sans-serif; font-size: 12px; }.meteo-icon { display: none; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> <svg id="meteo-icon-1" class="meteo-icon" width="24" height="24" viewBox="0 0 512 512"> <g> <path fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M177.615,288c7.438-36.521,39.688-64,78.396-64 c38.709,0,70.958,27.479,78.376,64h32c-7.834-54.125-54.084-96-110.376-96c-56.271,0-102.541,41.875-110.375,96H177.615z M256.011,160c8.833,0,16-7.167,16-16v-32c0-8.833-7.167-16-16-16c-8.832,0-16,7.167-16,16v32 C240.011,152.833,247.179,160,256.011,160z M403.073,156.917c-6.249-6.25-16.374-6.25-22.625,0l-22.625,22.625 c-6.249,6.25-6.249,16.375,0,22.625c6.251,6.25,16.376,6.25,22.625,0l22.625-22.625 C409.323,173.292,409.323,163.167,403.073,156.917z M154.177,179.542l-22.625-22.625c-6.249-6.25-16.373-6.25-22.625,0 c-6.249,6.25-6.249,16.375,0,22.625l22.625,22.625c6.252,6.25,16.376,6.25,22.625,0 C160.429,195.917,160.429,185.792,154.177,179.542z M352.011,320h-192c-8.832,0-16,7.167-16,16s7.168,16,16,16h192 c8.833,0,16-7.167,16-16S360.844,320,352.011,320z M320.011,384h-128c-8.832,0-16,7.167-16,16s7.168,16,16,16h128 c8.833,0,16-7.167,16-16S328.844,384,320.011,384z"/> </g> </svg> <svg id="meteo-icon-2" class="meteo-icon" width="24" height="24" viewBox="0 0 512 512"> <g> <path fill="#1D1D1B" d="M349.852,343.15c-49.876,49.916-131.083,49.916-181,0c-49.916-49.917-49.916-131.125,0-181.021 c13.209-13.187,29.312-23.25,47.832-29.812c5.834-2.042,12.293-0.562,16.625,3.792c4.376,4.375,5.855,10.833,3.793,16.625 c-12.542,35.375-4,73.666,22.249,99.917c26.209,26.228,64.501,34.75,99.917,22.25c5.792-2.062,12.271-0.583,16.625,3.792 c4.376,4.333,5.834,10.812,3.771,16.625C373.143,313.838,363.06,329.941,349.852,343.15z M191.477,184.754 c-37.438,37.438-37.438,98.354,0,135.771c40,40.021,108.125,36.417,143-8.167c-35.959,2.25-71.375-10.729-97.75-37.084 c-26.375-26.354-39.333-61.771-37.084-97.729C196.769,179.796,194.039,182.192,191.477,184.754z"/> </g> </svg>

First, apologies for an incomplete example, but I ran out of time and I think this shows the principles.首先,为一个不完整的例子道歉,但我没时间了,我认为这表明了原则。

  1. I agree with @MichaelRovinsky that SVG icons would be better than images, but I couldn't find a CDN for SVG icons that would be suitable for the example, and I think the principles are exactly the same, since you could embed SVGs as image just as well.我同意@MichaelRovinsky 的观点,即 SVG 图标会比图像更好,但我找不到适合该示例的 SVG 图标的 CDN,我认为原理完全相同,因为您可以将 SVG 嵌入为image也一样。

  2. Using placeimg.com for this purpose leads to weird results because the same URL will yield different results when read twice, so eg two slices may end up with the same image, and images change when the chart redraws.为此目的使用 placeimg.com 会导致奇怪的结果,因为相同的 URL 在读取两次时会产生不同的结果,因此例如两个切片可能最终得到相同的图像,并且在图表重绘时图像会发生变化。

Luckily these are both beside the point of customizing dc.js!幸运的是,这些都与自定义 dc.js 无关!

Adding things to dc.js pie slices向 dc.js 饼图添加内容

It would be nice if dc.js used an svg g group element to put the text in. Then we could just add to it and the position would be correct.如果 dc.js 使用 svg g组元素来放入文本,那就太好了。然后我们可以添加它,position 是正确的。

Instead, we have to add our image element and read the corresponding data from the pie label to get the placement:相反,我们必须添加我们的image元素并从饼图 label 中读取相应的数据以获取位置:

  chart.on('pretransition', chart => {
    let labelg = chart.select('g.pie-label-group');
    let data = labelg.selectAll('text.pie-label').data();
    console.log('data', data);

Then we can add image elements in the same layer/g:然后我们可以在同一层/g中添加image元素:

    let pieImage = labelg.selectAll('image.pie-image');
    let arcs = chart._buildArcs();
    pieImage.data(data)
      .join(
        enter => enter.append('image')
                    .attr('class', 'pie-image')
                    .attr('x', -19) 
                    .attr('y', -19))
       .attr('href', d => images[d.data.key === 'Others' ? 'Others' : d.data.key.slice(4)])
       .attr('transform', d => chart._labelPosition(d, arcs));
  });

Notice that the attributes which only need to be set once (on enter) are inside the join call, and the attributes which need to be set every redraw (on update) are outside the join call.请注意,只需要设置一次(输入时)的属性在加入调用内部,而每次重绘(更新时)需要设置的属性在加入调用之外。

x and y are negative one half the image size to center the images. xy是图像大小的一半以使图像居中。

I used an object to store the URLs but you could use whatever.我使用了 object 来存储 URL,但你可以使用任何东西。

带有图像的馅饼

Demo fiddle演示小提琴

Limitation局限性

As with any customization of the pie chart, this doesn't account for animations well.与饼图的任何自定义一样,这并不能很好地解释动画。 The images will move before the animation is complete.图像将在 animation 完成之前移动。 If you care, I think I wrote an answer some years ago which dealt with this properly.如果您在乎,我想我几年前写了一个正确处理此问题的答案。 I can probably dig it up but it was quite complicated and IMHO not worth it.我可能可以把它挖出来,但它很复杂,恕我直言,不值得。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM