Search code examples
javascriptd3.jschartspie-chart

How to create multiple pie charts with D3 from a single array with multiple objects?


I am learning how to use D3.js and I am trying to render multiple pie charts from JSON data that I fetched from an external API. The data that I am working with look like below

tickers = [{
    "symbol": "GME",
    "buy": 0,
    "hold": 3,
    "period": "2021-08-01",
    "sell": 5,
    "strongBuy": 0,
    "strongSell": 2
}, {
    "symbol": "AMD",
    "buy": 21,
    "hold": 16,
    "period": "2021-08-01",
    "sell": 1,
    "strongBuy": 8,
    "strongSell": 0
}]

I am trying to render a pie chart for each object in the array, but I cant seem to get the data flow correctly to my pie charts. I only want "Strong Sell", "Sell", "Hold", "Buy", and "Strong Buy" information on each pie chart.

I have tried the below codes, and attached screenshots showing the HTML and the pie charts

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

    var new_data = tickers.map(function(d){
        if (d){
            var temp = {"Strong Sell": d.strongSell, "Sell": d.sell, "Hold": d.hold, "Buy": d.buy, "Strong Buy": d.strongBuy}
        } else {
            var temp = {"Strong Sell": 0, "Sell": 0, "Hold": 0, "Buy": 0, "Strong Buy": 0}
        }
        return temp
    })

    var color = d3.scaleOrdinal()
        .domain(["Strong Sell", "Sell", "Hold", "Buy", "Strong Buy"])
        .range(["#570e00", "#ff2a00", "#ffff00", "#00ff08", "#00570c"])

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

    var data_ready = pie(d3.entries(new_data))

    var recommendSvg = d3.selectAll(".company-recommend")
        .append("svg")
            .attr("width", width)
            .attr("height", height)
        .append("g")
            .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

    recommendSvg
        .selectAll('g')
        .data(data_ready)
        .enter()
        .append('path')
            .attr('d', d3.arc()
                .innerRadius(0)
                .outerRadius(radius)
            )
            .attr('fill', function(d){ return(color(d.data.key)) })
            .attr("stroke", "black")
            .style("stroke-width", "2px")
            .style("opacity", 0.7)

Any help or advice would be greatly appreciated!

Locations where I want pie charts to show

HTML in the console


Solution

  • Here's a complete example, which assumes that the "company-recommend" divs are already created.

    I've modified your code so that you don't need to use d3.entries. I put the data in that format from the start. In addition, I've updated recommendSvg so that the data array is bound to it. This means that each div is bound to one element in the data array. When drawing the pie chart in each group, we then pass the data for that chart to the pie generator and create one path for each slice.

    <html>
        <head>
            <script src="https://d3js.org/d3.v7.min.js"></script>
        </head>
        
        <body>
            <div class="company-container">
                <div class="company-profile"></div>
                <div class="company-recommend"></div>
            </div>
    
            <div class="company-container">
                <div class="company-profile"></div>
                <div class="company-recommend"></div>
            </div>
    
            <script>
                const tickers = [
                    {
                        "symbol": "GME",
                        "buy": 0,
                        "hold": 3,
                        "period": "2021-08-01",
                        "sell": 5,
                        "strongBuy": 0,
                        "strongSell": 2
                    },
                    {
                        "symbol": "AMD",
                        "buy": 21,
                        "hold": 16,
                        "period": "2021-08-01",
                        "sell": 1,
                        "strongBuy": 8,
                        "strongSell": 0
                    }
                ];
    
                const margin = 20;
                const width = 250;
                const height = 250;
                const radius = Math.min(width, height) / 2 - margin;
    
                const data = tickers.map(d => [
                    { key: 'Strong Sell', value: d.strongSell },
                    { key: 'Sell', value: d.sell },
                    { key: 'Hold', value: d.hold },
                    { key: 'Buy', value: d.buy },
                    { key: 'Strong Buy', value: d.strongBuy },
                ]);
    
                const color = d3.scaleOrdinal()
                    .domain(["Strong Sell", "Sell", "Hold", "Buy", "Strong Buy"])
                    .range(["#570e00", "#ff2a00", "#ffff00", "#00ff08", "#00570c"]);
    
                const pie = d3.pie().value(d => d.value);
    
                const arc = d3.arc()
                    .innerRadius(0)
                    .outerRadius(radius);
    
                // bind our data to the divs. add a group to each div.
                const recommendSvg = d3.selectAll('.company-recommend')
                    .data(data)
                    .append('svg')
                        .attr('width', width)
                        .attr('height', height)
                    .append('g')
                        .attr('transform', `translate(${width / 2},${height / 2})`);
    
                // draw the pie chart in each group
                // by creating one path for each slice
                recommendSvg.selectAll('path')
                    .data(d => pie(d))
                    .join('path')
                        .attr('d', arc)
                        .attr('fill', d => color(d.data.key))
                        .attr('stroke', 'black')
                        .attr('stroke-width', '2px')
                        .attr('opacity', 0.7);
            </script>
        </body>
    </html>