Search code examples
javascriptselectd3.jspie-chart

Each Selection in D3 Pie Chart


currently I am trying to design dynamic pie chart whereby the pie chart's slices will change according to the random generated data. Below is the code.

var dataset = [
    { name: 'Smooth', percent: 40.00, class: 'custom-normal' },
    { name: 'Moderate', percent: 10.00, class: 'custom-warning' },
    { name: 'Heavy', percent: 50.00, class: 'custom-danger' }
];
var width = 960,
    height = 500,
    radius = Math.min(width, height) / 2; //Math.min return the smallest value between width and height (for optimization purposes)

var colorValues = d3.scaleOrdinal().domain(["Smooth", "Moderate", "Heavy"]).range(["#605A4C", "#ff9900", "#ff1a1a"]);
var percent = "percent"; //predefine the legend of dataset (the string index)
var category = "class";
var name = "name";

var pie = d3.pie()
    .value(function(d) { return d[percent]; })
    .sort(null)
    .padAngle(.02); //the gap

var arc = d3.arc()
    .innerRadius(radius - 100) //optimization
    .outerRadius(radius - 20); //optimization

var svg = d3.select("#chart")
    .append("svg")
    .attrs({
        width: width,
        height: height,
        class: "shadow"
    }).append("g")
    .attrs({
        transform: 'translate(' + width / 2 + ',' + height / 2 + ')'
    });
svg.append('g')
    .attrs({
        class: 'slices'
    });

var path = svg.select('.slices')
    .selectAll('path')
    .data(pie(dataset))
    .enter().append('path')
    .attrs({
        d: arc
    }).each(function(d, i) {
        this._current = d;
        console.log(this._current);
        console.log('okay!');
    }).attrs({
        class: function(d, i){
        return d[category];
        },
        fill: function(d, i) {
            console.log("this is color value" + colorValues());
            return colorValues(d[i]);
        }
    }); //initial details (this._current)


var randomGenerator = setInterval(function() {
    var data = dataset.map(function(d, i) {
        for (var key in d) {
            if (d[key] === "Smooth") {
                //console.log("smooth");
                dataset[0].percent = Math.floor(Math.random() * 100);
                //console.log(dataset[0].percent);
            } else if (d[key] === "Moderate") {
                dataset[1].percent = Math.floor(Math.random() * 100);
                //console.log(dataset[1].percent);
                //console.log("moderate");
            } else if (d[key] === "Heavy") {
                dataset[2].percent = Math.floor(Math.random() * 100);
                //console.log(dataset[2].percent);
                //console.log("heavy");
            }
        }

    });

}, 3000);


var timer = setInterval(function() {
    pie.value(function(d) {
        return d[percent];
    }); // change the value function
    path = path.data(pie(dataset)); // compute the new angles
    path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}, 3000);


// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
function arcTween(a) {
    var i = d3.interpolate(this._current, a);
    console.log(this._current);
    this._current = i(0);
    return function(t) {
        return arc(i(t));
    };
}
body {
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    margin: auto;
    position: relative;
    width: 960px;
}
<meta charset="utf-8">
<div id="mydiv" class="widget">
    <div id="chart" class="Chart chart-container"></div>
</div>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>

Notice that the color of each slices is not scaled according to what I have defined earlier. i.e. :var colorValues = d3.scaleOrdinal().domain(["Smooth", "Moderate", "Heavy"]).range(["#605A4C", "#ff9900", "#ff1a1a"]);. I believe it should be the selection problem, however I found no clue on that. And yet, the class as well, it only get the first row of data in var dataset and apply to all.


Solution

  • If I understand correctly,

    By pushing your data through the pie function, you are altering your data. While your initial structure is:

    { name: 'Smooth', percent: 40.00, class: 'custom-normal' }
    

    After running this data through the pie function, the datum bound to each arc has the following structure:

    {
      "data": {
        "name": "Heavy",
        "percent": 48,
        "class": "custom-danger"
      },
      "index": 2,
      "value": 50,
      "startAngle": 3.1515926535897933,
      "endAngle": 6.283185307179586,
      "padAngle": 0.02
    }
    

    In your code you are coloring the wedges based on this:

    fill: function(d, i) {
        console.log("this is color value" + colorValues());
        return colorValues(d[i]);
    }
    

    The d in function(d) {} refers to the datum, so it is just a single item in the input data (the datum bound to a particular arc), which is an object - the use of d[number] suggests that you anticipate an array. In any event, each time you run this function you will get undefined.

    Instead access the property of the datum you want: name (I assume) and use:

    fill: function(d, i) {
        console.log("this is color value" + colorValues());
        return colorValues(d.data.name);
    }
    

    var dataset = [
        { name: 'Smooth', percent: 40.00, class: 'custom-normal' },
        { name: 'Moderate', percent: 10.00, class: 'custom-warning' },
        { name: 'Heavy', percent: 50.00, class: 'custom-danger' }
    ];
    var width = 960,
        height = 500,
        radius = Math.min(width, height) / 2; //Math.min return the smallest value between width and height (for optimization purposes)
    
    var colorValues = d3.scaleOrdinal().domain(["Smooth", "Moderate", "Heavy"]).range(["#605A4C", "#ff9900", "#ff1a1a"]);
    var percent = "percent"; //predefine the legend of dataset (the string index)
    var category = "class";
    var name = "name";
    
    var pie = d3.pie()
        .value(function(d) { return d[percent]; })
        .sort(null)
        .padAngle(.02); //the gap
    
    var arc = d3.arc()
        .innerRadius(radius - 100) //optimization
        .outerRadius(radius - 20); //optimization
    
    var svg = d3.select("#chart")
        .append("svg")
        .attrs({
            width: width,
            height: height,
            class: "shadow"
        }).append("g")
        .attrs({
            transform: 'translate(' + width / 2 + ',' + height / 2 + ')'
        });
    svg.append('g')
        .attrs({
            class: 'slices'
        });
    
    var path = svg.select('.slices')
        .selectAll('path')
        .data(pie(dataset))
        .enter().append('path')
        .attrs({
            d: arc
        }).each(function(d, i) {
            this._current = d;
            console.log(this._current);
            console.log('okay!');
        }).attrs({
            class: function(d, i){
            return d.data.class;
            },
            fill: function(d, i) {
                console.log("this is color value" + colorValues());
                return colorValues(d.data.name);
            }
        }); //initial details (this._current)
    
    
    var randomGenerator = setInterval(function() {
        var data = dataset.map(function(d, i) {
            for (var key in d) {
                if (d[key] === "Smooth") {
                    //console.log("smooth");
                    dataset[0].percent = Math.floor(Math.random() * 100);
                    //console.log(dataset[0].percent);
                } else if (d[key] === "Moderate") {
                    dataset[1].percent = Math.floor(Math.random() * 100);
                    //console.log(dataset[1].percent);
                    //console.log("moderate");
                } else if (d[key] === "Heavy") {
                    dataset[2].percent = Math.floor(Math.random() * 100);
                    //console.log(dataset[2].percent);
                    //console.log("heavy");
                }
            }
    
        });
    
    }, 3000);
    
    
    var timer = setInterval(function() {
        pie.value(function(d) {
            return d[percent];
        }); // change the value function
        path = path.data(pie(dataset)); // compute the new angles
        path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
    }, 3000);
    
    
    // Store the displayed angles in _current.
    // Then, interpolate from _current to the new angles.
    // During the transition, _current is updated in-place by d3.interpolate.
    function arcTween(a) {
        var i = d3.interpolate(this._current, a);
        console.log(this._current);
        this._current = i(0);
        return function(t) {
            return arc(i(t));
        };
    }
    body {
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        margin: auto;
        position: relative;
        width: 960px;
    }
    <meta charset="utf-8">
    <div id="mydiv" class="widget">
        <div id="chart" class="Chart chart-container"></div>
    </div>
    <script src="https://d3js.org/d3.v4.js"></script>
    <script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>