javascriptd3.jschord-diagram

colorize D3 chord paths based on data


I migrated this https://bl.ocks.org/nbremer/d2720fdaab1123df73f4806360a09c9e D3 Chord layout to D3v7. My current problem is, I want to utilize the color attribute from var objects to fill the individual paths. Those are currently grey.

I managed to get it done for the outer circle with

.style("fill", function (d) {
      return objects[d.index].color
])

I thought I could use the same line of code to display the respected color for the paths but I receive the error that objects[d.index].color is undefined, which confuse me. I assume I did not understood the path procedure fully, which makes me believe I do everything correct.. pathetic.

I appreciate any help.

 // Setup

        var objects = [
            { id: 0, name: "Black Widow", color: "#301E1E" },
            { id: 1, name: "Captian America", color: "#083E77" },
            { id: 2, name: "Hawkeye", color: "#342350" },
            { id: 3, name: "The Hulk", color: "##567235" },
            { id: 4, name: "Iron Man", color: "#8B161C" },
            { id: 5, name: "Thor", color: "#DF7C00" }
        ]

        var flows = [
            { from: 0, to: 0, quantity: 0 },
            { from: 0, to: 1, quantity: 4 },
            { from: 0, to: 2, quantity: 3 },
            { from: 0, to: 3, quantity: 2 },
            { from: 0, to: 4, quantity: 5 },
            { from: 0, to: 5, quantity: 2 },
            { from: 1, to: 0, quantity: 4 },
            { from: 1, to: 1, quantity: 0 },
            { from: 1, to: 2, quantity: 3 },
            { from: 1, to: 3, quantity: 2 },
            { from: 1, to: 4, quantity: 4 },
            { from: 1, to: 5, quantity: 3 },
            { from: 2, to: 0, quantity: 3 },
            { from: 2, to: 1, quantity: 3 },
            { from: 2, to: 2, quantity: 0 },
            { from: 2, to: 3, quantity: 2 },
            { from: 2, to: 4, quantity: 3 },
            { from: 2, to: 5, quantity: 3 },
            { from: 3, to: 0, quantity: 2 },
            { from: 3, to: 1, quantity: 2 },
            { from: 3, to: 2, quantity: 2 },
            { from: 3, to: 3, quantity: 0 },
            { from: 3, to: 4, quantity: 3 },
            { from: 3, to: 5, quantity: 3 },
            { from: 4, to: 0, quantity: 5 },
            { from: 4, to: 1, quantity: 4 },
            { from: 4, to: 2, quantity: 3 },
            { from: 4, to: 3, quantity: 3 },
            { from: 4, to: 4, quantity: 0 },
            { from: 4, to: 5, quantity: 2 },
            { from: 5, to: 0, quantity: 2 },
            { from: 5, to: 1, quantity: 3 },
            { from: 5, to: 2, quantity: 3 },
            { from: 5, to: 3, quantity: 3 },
            { from: 5, to: 4, quantity: 2 },
            { from: 5, to: 5, quantity: 0 },
        ]

        var matrix = [];
        
        // Map flows data to valid matrix
        flows.forEach(function (flow) {
            //initialize sub-array if not yet exists
            if (!matrix[flow.to]) {
                matrix[flow.to] = [];
            }
            
            matrix[flow.to][flow.from] = flow.quantity;
        })   

        const width = window.innerWidth
        const height = window.innerHeight

        const svg = d3.select("body")
            .append("svg")
                .attr("width", width)
                .attr("height", height)
            .append("g")
                .attr("transform", "translate(" + width/2 + "," + height/2 + ")")

        // Übergibt die Daten-Matrix zu d3.chord()
        const root = d3.chord()
            .padAngle(0.05)
            .sortSubgroups(d3.descending)(matrix)

        // Fügt die Gruppen für den inneren Kreis hinzu
        svg
            .datum(root)
            .append("g")
            .selectAll("g")
            .data(d => d.groups)
            .join("g")
            .append("path")
                .style("fill", "grey")
                .style("stroke", "black")
                .attr("d", d3.arc()
                    .innerRadius(width/2 - 210)
                    .outerRadius(width/2 - 200)
                )
            .style("fill", function (d) {
                return objects[d.index].color
            })
            

        // Fügt Verlinkungen zwischen den Gruppen hinzu
        svg
            .datum(root)
            .append("g")
            .selectAll("path")
            .data(d => d)
            .join("path")
                .attr("d", d3.ribbon()
                    .radius(width/2 - 220)
                )
                .style("fill", function (d) {
                    return objects[d.index].color
                })
                //.style("fill", "grey")
                .style("stroke", "black")
    body {
        font-size: 12px;
        font-family: 'Lato', sans-serif;
        text-align: center;
        fill: #2B2B2B;
        cursor: default;
        overflow: hidden;
    }

    @media (min-width: 600px) {
        #chart {
            font-size: 16px;
        }
    }
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <title>Step 1 - Collaborations between MCU Avengers</title>

    <!-- D3.js -->
    <script src="https://d3js.org/d3.v7.js"></script>
    
</head>



<body>
    
</body>

</html>


Solution

  • TL;DR:

    Lines 92 through 94:

    .style("fill", function (d) {
        return objects[d.index].color
    })
    

    ... become:

    .style('fill', ({ index }) => objects.find(({ id }) => id === index).color)
    

    And lines 107 through 109:

    .style("fill", function (d) {
        return objects[d.index].color
    })
    

    ... become:

    .style('fill', ({ source: { index } }) => objects.find(({ id }) => id === index).color)
    

    Explanation

    You have two problems:

    1. Your items d in the second method actually contain both "source" and "target", for instance:
    {
      "source": {
        "index": 0,
        "startAngle": 0.3399537106352038,
        "endAngle": 0.6119166791433668,
        "value": 4
      },
       "target": {
       "index": 1,
       "startAngle": 1.1378518740326522,
       "endAngle": 1.4098148425408152,
       "value": 4
      }
    }
    

    ... and you therefore need to dig into either "source" or "target" before having access to an "index" attribute.

    1. your objects Array contains references to items that are apparently ordered such that their "id" attribute corresponds to their index in the objects Array, but I presume it to be either an unreliable coincidence or an inadvertent oversight.

    In any case, you seem to be disregarding the index property entirely, while I believe you meant to use it to identify each item. I suggest you use Array#find here!

    Updated Code Snippet

    // Setup
    
            var objects = [
                { id: 0, name: "Black Widow", color: "#301E1E" },
                { id: 1, name: "Captian America", color: "#083E77" },
                { id: 2, name: "Hawkeye", color: "#342350" },
                { id: 3, name: "The Hulk", color: "##567235" },
                { id: 4, name: "Iron Man", color: "#8B161C" },
                { id: 5, name: "Thor", color: "#DF7C00" }
            ]
    
            var flows = [
                { from: 0, to: 0, quantity: 0 },
                { from: 0, to: 1, quantity: 4 },
                { from: 0, to: 2, quantity: 3 },
                { from: 0, to: 3, quantity: 2 },
                { from: 0, to: 4, quantity: 5 },
                { from: 0, to: 5, quantity: 2 },
                { from: 1, to: 0, quantity: 4 },
                { from: 1, to: 1, quantity: 0 },
                { from: 1, to: 2, quantity: 3 },
                { from: 1, to: 3, quantity: 2 },
                { from: 1, to: 4, quantity: 4 },
                { from: 1, to: 5, quantity: 3 },
                { from: 2, to: 0, quantity: 3 },
                { from: 2, to: 1, quantity: 3 },
                { from: 2, to: 2, quantity: 0 },
                { from: 2, to: 3, quantity: 2 },
                { from: 2, to: 4, quantity: 3 },
                { from: 2, to: 5, quantity: 3 },
                { from: 3, to: 0, quantity: 2 },
                { from: 3, to: 1, quantity: 2 },
                { from: 3, to: 2, quantity: 2 },
                { from: 3, to: 3, quantity: 0 },
                { from: 3, to: 4, quantity: 3 },
                { from: 3, to: 5, quantity: 3 },
                { from: 4, to: 0, quantity: 5 },
                { from: 4, to: 1, quantity: 4 },
                { from: 4, to: 2, quantity: 3 },
                { from: 4, to: 3, quantity: 3 },
                { from: 4, to: 4, quantity: 0 },
                { from: 4, to: 5, quantity: 2 },
                { from: 5, to: 0, quantity: 2 },
                { from: 5, to: 1, quantity: 3 },
                { from: 5, to: 2, quantity: 3 },
                { from: 5, to: 3, quantity: 3 },
                { from: 5, to: 4, quantity: 2 },
                { from: 5, to: 5, quantity: 0 },
            ]
    
            var matrix = [];
            
            // Map flows data to valid matrix
            flows.forEach(function (flow) {
                //initialize sub-array if not yet exists
                if (!matrix[flow.to]) {
                    matrix[flow.to] = [];
                }
                
                matrix[flow.to][flow.from] = flow.quantity;
            })   
    
            const width = window.innerWidth
            const height = window.innerHeight
    
            const svg = d3.select("body")
                .append("svg")
                    .attr("width", width)
                    .attr("height", height)
                .append("g")
                    .attr("transform", "translate(" + width/2 + "," + height/2 + ")")
                    
            // Übergibt die Daten-Matrix zu d3.chord()
            const root = d3.chord()
                .padAngle(0.05)
                .sortSubgroups(d3.descending)(matrix)
                
            // Fügt die Gruppen für den inneren Kreis hinzu
            svg
                .datum(root)
                .append("g")
                .selectAll("g")
                .data(d => d.groups)
                .join("g")
                .append("path")
                    .style("fill", "grey")
                    .style("stroke", "black")
                    .attr("d", d3.arc()
                        .innerRadius(width/2 - 210)
                        .outerRadius(width/2 - 200)
                    )
                .style("fill", function ({ index }) {
                    return objects.find(({ id }) => id === index).color;
                })
                
    
            // Fügt Verlinkungen zwischen den Gruppen hinzu
            svg
                .datum(root)
                .append("g")
                .selectAll("path")
                .data(d => d)
                .join("path")
                    .attr("d", d3.ribbon()
                        .radius(width/2 - 220)
                    )
                    .style("fill", function ({ source: { index } }) {
                        return objects.find(({ id }) => id === index).color;
                    })
                    //.style("fill", "grey")
                    .style("stroke", "black")
    body {
            font-size: 12px;
            font-family: 'Lato', sans-serif;
            text-align: center;
            fill: #2B2B2B;
            cursor: default;
            overflow: hidden;
        }
    
        @media (min-width: 600px) {
            #chart {
                font-size: 16px;
            }
        }
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
        <title>Step 1 - Collaborations between MCU Avengers</title>
    
        <!-- D3.js -->
        <script src="https://d3js.org/d3.v7.js"></script>
        
    </head>
    
    
    
    <body>
        
    </body>
    
    </html>