Search code examples
javascriptd3.jsdata-visualizationpie-chartdonut-chart

Multilevel D3 Pie/Donut-chart doesnt show and no error messages


I am trying to get a multilevel pie/donut-chart. The data doesnt have a numeric value and should be distributed evenly in the rings. Thus I gave them all the same number. Maybe there's a more elegant way but in the simple dataset it worked. Sadly now that I took a dataset which is a bit more similar to my endgoal and it stopped working. I am a bloody d3 newbie and I would be really grateful for any kind of help what might be wrong as I dont get any error messages. Cheers and thanks a lot! (I have the script in the html file). I took this as an inspiration: fiddle

edit: To see my graphic I use a local webserver so I am able to load stuff locally

/* Old simple data set
         var dataset = {
          ring0 : [1],  
          ring1: [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
          ring2: [1,1,1],
          ring3: [1,1,1,1,1,1,1,1,1,1,1,1,1,1],
          ring4: [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
        }; */

        var dataset = {
            ring0:[{"Arbeitsbereich":"IT", "number": 1}],
            ring1:[{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1}],
            ring2:[{"Arbeitsbereich":"IT", "number": 1}, {"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1}],
            ring3:[{"Arbeitsbereich":"IT", "number": 1}, {"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1}],
            ring4:[{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1}]
        };

        
       
        var width  = d3.select('#pie-chart').node().offsetWidth,
            height = 600,
            cwidth = 60;
        
        var colorO = '#8a0101';
        var colorA = '#db3131';
        var colorB = '#ff4a4a';
        var colorC = '#aa0000';
        var colorD = '#ff0000';

                        
        var pie = d3.pie()
            .value(function(d){return d.number;});

            console.log(pie(dataset.ring1))

        var svg = d3.select("#duration svg")
            .append("g") //used to group svg elements
            .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

        console.log(dataset);
 
        var arc = d3.arc();

        var gs = svg.selectAll("g").data(pie(dataset)).enter().append("g"); 

        var path = gs.selectAll("path")
            .data(function(d) { return pie(d); }) //.data(function(d, i)
            .enter().append("path")
            .attr("fill", function(d, i, j) {
                switch (j) {
                case 0:
                    return colorO(d.dataset.number);
                    break;
                case 1:
                    return colorA(d.dataset.number);
                    break;
                case 2:
                    return colorB(d.dataset.number);
                    break;
                case 3:
                    return colorC(d.dataset.number);
                    break;
                case 4:
                    return colorD(d.dataset.number);
                } 
            
            })

            .attr("d", function(d, i, j) {
                if (j == 0) {
                    
                    return arc.innerRadius(0).outerRadius(40)(d);
                    
                } else if (j == 1) {
                    
                    return arc.innerRadius(40).outerRadius(cwidth * (j + 1))(d);        

            } else {
                    
                    return arc.innerRadius(cwidth * j).outerRadius(cwidth * (j + 1))(d);
                    
                }        
            });
body {
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    margin: auto;
    position: relative;
    width: 960px;
  }
  
  text {
    font: 10px sans-serif;
  }
  
  form {
    position: absolute;
    right: 10px;
    top: 10px;
  }
<!DOCTYPE html>
<html>
<head>
    <script src="https://d3js.org/d3.v5.js"></script>
    <link rel="stylesheet" href="./style.css" type="text/css" />
    <meta charset="utf-8"
</head>
        <body> 
                <div id="pie-chart">
                        <svg style="height:1000px;width:100%"></svg>
                    </div>
        </body>    
</html>


Solution

  • D3 can frazzle the brain a bit when you're new to it, don't worry!

    I changed a few little things to get your code working in the 'all combined' html below. Some of which look like mistakes that might have crept in whilst you were debugging, and some bits that were more about understanding. I think the key bits were:

    • Your svg selection d3.select("#duration svg") doesn't actually point to anything in your document so nothing else was actually happening, which is why you were not getting any errors. This is probably just what I would call a 'facepalm' error as you took the code from that fiddle, if you change it to d3.select("#pie-chart svg") you should start to see some spicier errors! To debug this kind of thing, I always go to the browser debug tools and inspect the document, you will see that there are no <g> elements under your svg, and start homing in on the problem from there
    • Your data is just one object, denoted by being surrounded by a set of curly brackets {}. This is a weird thing to bind on in d3, we normally bind on an array of objects and do different things based on those data items. Sure enough, in the fiddle the objects are the same shape, but it doesn't bind on just the objects (which would have been .data(dataset), but uses d3.values() to change the shape of the data into an array first with .data(d3.values(dataset))
    • You've jumped the gun a little bit where you've tried to bind to the pie(d) dataset. This example uses two levels of data join (check out https://bost.ocks.org/mike/nest/ when you have plenty of time and tea available!) The top level data join (i.e. .data(d3.values(dataset)) has one element per 'ring' in your doughnut chart - so each ring has the one <g> element with all that ring's data in it as one object. Then there's another data join (.data(function(d, i) { return pie(d); }) in the fiddle, and I've done something subtly different below) to create a set of <path> elements in the ring's <g> element, and it's each of those <path> that are a segment of your doughnuts.

    Hope that helps give you a few pointers, here's how I ended up mashing your code into something that would put something on the screen, though you'll probably want to sort the colouring out! Note: I did simplify things a touch by just saying that all data accessors just return 1 (because in your case you always want evenly segmented rings, and it then doesn't matter what shape each data point is, they all just count as 1)

    <!DOCTYPE html>
    <html>
    <head>
        <script src="https://d3js.org/d3.v5.js"></script>
        <style>
        body {
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        margin: auto;
        position: relative;
        width: 960px;
      }
    
      text {
        font: 10px sans-serif;
      }
    
      form {
        position: absolute;
        right: 10px;
        top: 10px;
      }
        </style>
        <meta charset="utf-8">
    </head>
            <body> 
                    <div id="pie-chart">
                            <svg style="height:1000px;width:100%"></svg>
                        </div>
    
                    <script>
          /* Old simple data set */
          /* var dataset = {
              ring0:[1],  
              ring1:[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
              ring2:[1,1,1],
              ring3:[1,1,1,1,1,1,1,1,1,1,1,1,1,1],
              ring4:[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
           }; 
         */
    
    
         ///*
         var dataset = {
                ring0:[{"Arbeitsbereich":"IT", "number": 1}],
                ring1:[{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1}],
                ring2:[{"Arbeitsbereich":"IT", "number": 1}, {"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1}],
                ring3:[{"Arbeitsbereich":"IT", "number": 1}, {"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1}],
                ring4:[{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1},{"Arbeitsbereich":"IT", "number": 1}]
            };
    
    
    
            var width  = d3.select('#pie-chart').node().offsetWidth,
                height = 600,
                cwidth = 60;
    
            var colorO = '#8a0101';
            var colorA = '#db3131';
            var colorB = '#ff4a4a';
            var colorC = '#aa0000';
            var colorD = '#ff0000';
    
    
            var pie = d3.pie().value(function(d){return 1})
    
            var svg = d3.select("#pie-chart svg")
                .append("g") //used to group svg elements
                .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
    
            var arc = d3.arc();
    
            var gs = svg.selectAll("g")
              .data(d3.values(dataset)).enter().append("g"); 
    
            var path = gs
                .selectAll("path")
                .data(function(d,i) {
                    return pie(d).map(function(e){e.ringIndex = i; return e});
                })
                .enter()
                .append("path")
                .attr("fill", function(d, i, j) {
                    switch (i) {
                    case 0:
                        return colorO;
                        break;
                    case 1:
                        return colorA;
                        break;
                    case 2:
                        return colorB;
                        break;
                    case 3:
                        return colorC;
                        break;
                    case 4:
                        return colorD;
                    } 
    
                })
    
                .attr("d", function(d, i, j) {
                    return arc.innerRadius(cwidth * d.ringIndex).outerRadius(cwidth * (d.ringIndex + 1))(d);
                });
                </script>
    
            </body>  
    </html>