D3v7 grouping by data bundle not by type

Viewed 34

Does the grouping in D3v7 changed? The graph below groups elements by all ribbons, all arcs and all labels. Means the data for one group is splitted into three elements. How can I group them to bundle each needed element into one group.

So instead of all ribbons, all arcs and all labels.. it should be:

  • group0
    thisRibbony
    thisArc
    thisLabel

  • group1
    thisRibbony
    thisArc
    thisLabel

  • group2
    thisRibbony
    thisArc
    thisLabel

How it is:

enter image description here

What I wish for:

Each entry got an own g grouping element which contains the bundled data.

enter image description here

        ////////////////////////////////////////////////////////////
        ////////////////////// Set-Up Data /////////////////////////
        ////////////////////////////////////////////////////////////
        
        
        let groups = [
            { name: "A", color: "blue" },
            { name: "B", color: "red" },
            { name: "C", color: "green" },
            { name: "D", color: "grey" },
        ]

        let paths = [
            { from: groups.name = "A", to: groups.name = "A", strength: 0 },
            { from: groups.name = "A", to: groups.name = "B", strength: 5 },
            { from: groups.name = "A", to: groups.name = "C", strength: 5 },
            { from: groups.name = "A", to: groups.name = "D", strength: 5 },
            { from: groups.name = "B", to: groups.name = "A", strength: 5 },
            { from: groups.name = "B", to: groups.name = "B", strength: 0 },
            { from: groups.name = "B", to: groups.name = "C", strength: 5 },
            { from: groups.name = "B", to: groups.name = "D", strength: 5 },
            { from: groups.name = "C", to: groups.name = "A", strength: 5 },
            { from: groups.name = "C", to: groups.name = "B", strength: 5 },
            { from: groups.name = "C", to: groups.name = "C", strength: 0 },
            { from: groups.name = "C", to: groups.name = "D", strength: 5 },
            { from: groups.name = "D", to: groups.name = "D", strength: 0 },
            { from: groups.name = "D", to: groups.name = "A", strength: 5 },
            { from: groups.name = "D", to: groups.name = "B", strength: 5 },
            { from: groups.name = "D", to: groups.name = "C", strength: 5 },
        ]

        let matrix = []

        function getMatrix(paths, groups) {
            matrix = []

            var mapPaths = paths.map(item => {
                const container = {}
                    container.from = groups.findIndex(ele => ele.name == item.from)
                    container.to = groups.findIndex(ele => ele.name == item.to)
                    container.strength = item.strength
                return container
            })
            
            mapPaths.forEach(function (item) {
                // initialize sub-arra if not yet exists
                if (!matrix[item.to]) {
                    matrix[item.to] = []
                }

                matrix[item.to][item.from] = item.strength
            })

            return matrix
        }

        ////////////////////////////////////////////////////////////
        ///////////////////// Set-Up Visualization /////////////////
        ////////////////////////////////////////////////////////////

        const vw = window.innerWidth 
        const vh = window.innerHeight
        const innerRadius = Math.min(vw, vh) * 0.3;
        const outerRadius = innerRadius * 1.1;
        const duration = 1000;


        const svg = d3.select("#chart").append("svg")
            .attr("width", vw)
            .attr("height", vh)
            .attr("overflow", "unset")

        const wrapper = svg.append("g")
            .attr("id", "wrapper")
            .attr("transform", "translate(" + vw / 2 + "," + vh / 2 + ")")

        const ribbonsG = wrapper.append("g").attr("id", "ribbons")
        const arcsG = wrapper.append("g").attr("id", "arcs")
        const labelsG = wrapper.append("g").attr("id", "labels")

        var chordGenerator = d3.chord()
            .padAngle(0.10)
            .sortSubgroups(d3.ascending)
            .sortChords(d3.descending)

        var arc = d3.arc()
            .innerRadius(innerRadius * 1.01)
            .outerRadius(outerRadius)

        var ribbon = d3.ribbon()
            .radius(innerRadius);
    
        window.update = update;

        update(paths, groups)

        function update(thisPaths, thisGroups) {
            const chords = chordGenerator(getMatrix(thisPaths, thisGroups))
          
            // ribbons
            const ribbonsUpdate = ribbonsG
                .selectAll("path.ribbon")
                .data(chords)

            const ribbonsEnter = ribbonsUpdate
                .enter()
                .append("path")

            ribbonsUpdate
                .merge(ribbonsEnter)
                .transition(duration)
                .attr("opacity", 0)
                .attr("class", "ribbon")    
                .transition()
                .duration(duration)
                .attr("d", ribbon)
                .style("fill", function (d) { return thisGroups[d.target.index].color; })
                .attr('opacity', 1)

            ribbonsUpdate
                .exit()
                .transition()
                .duration(duration)
                .attr("opacity", 0)
                .remove();
            
            // arcs
            const arcsUpdate = arcsG
                .selectAll("path.arc")
                .data(chords.groups)
          
            const arcsEnter = arcsUpdate
                .enter()
                .append("path")
       
            arcsUpdate
                .merge(arcsEnter)
                .transition(duration)
                .attr("opacity", 0)
                .attr("class", "arc")
                .transition()
                .duration(duration)
                .attr("d", arc)
                .style("fill", function (d) { return thisGroups[d.index].color; })
                .attr('opacity', 1)
          
            arcsUpdate
                .exit()
                .transition()
                .duration(duration)
                .attr("opacity", 0)
                .remove();

             // adding labels
            const labelsUpdate = labelsG
                .selectAll("text.titles")
                .data(chords.groups)

            const labelsEnter = labelsUpdate
                .enter()
                .append("text")

            labelsUpdate
                .merge(labelsEnter)
                .attr("class", "titles")
                .attr("opacity", 0)
                .transition()
                .duration(duration)
                .each(function(d){ d.angle = (d.startAngle + d.endAngle) / 2; })
                .attr("dy", ".35em")
                .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
                .attr("transform", function(d) {
                    return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                        + "translate(" + (outerRadius + 10) + ")"
                        + (d.angle > Math.PI ? "rotate(180)" : "");
                })
                .text(function(d,i){ return thisGroups[i].name; })        
                .attr("opacity", 1)
                
            labelsUpdate
                .exit()
                .remove()
        }
    body {
        font-size: 16px;
        font-family: 'Oswald', sans-serif;
        background-color: #ECF0F3;
        cursor: default;
        overflow: hidden;
    }
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <title>D3v7</title>

    <!-- D3.js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js" charset="utf-8"></script>
    <!-- fontawesome stylesheet https://fontawesome.com/ -->
    <script src="https://kit.fontawesome.com/98a5e27706.js" crossorigin="anonymous"></script>
</head>

<body>
    <div id="chart"></div>
 
</body>

</html>

1 Answers

The issue was, that there were 3 distinct groupings for arcs, ribbons and labels. To group the pieces of one section logically together one can create a single grouping for each section of the chart and then append an arc and a label to each section.

Afterwards one can use select to make changes to the path and text element inside the g.group.

The ribbons are not a owned by any section, so they can not be easily assigned and should stay separately.

Please have a look at https://bost.ocks.org/mike/selection/ to get a deeper understanding of selections and how to manipulate them.

Here is a working solution of the expected behavior:

                ////////////////////////////////////////////////////////////
    ////////////////////// Set-Up Data /////////////////////////
    ////////////////////////////////////////////////////////////
    
    
    let groups = [
        { name: "A", color: "blue" },
        { name: "B", color: "red" },
        { name: "C", color: "green" },
        { name: "D", color: "grey" },
    ]

    let paths = [
        { from: groups.name = "A", to: groups.name = "A", strength: 0 },
        { from: groups.name = "A", to: groups.name = "B", strength: 5 },
        { from: groups.name = "A", to: groups.name = "C", strength: 5 },
        { from: groups.name = "A", to: groups.name = "D", strength: 5 },
        { from: groups.name = "B", to: groups.name = "A", strength: 5 },
        { from: groups.name = "B", to: groups.name = "B", strength: 0 },
        { from: groups.name = "B", to: groups.name = "C", strength: 5 },
        { from: groups.name = "B", to: groups.name = "D", strength: 5 },
        { from: groups.name = "C", to: groups.name = "A", strength: 5 },
        { from: groups.name = "C", to: groups.name = "B", strength: 5 },
        { from: groups.name = "C", to: groups.name = "C", strength: 0 },
        { from: groups.name = "C", to: groups.name = "D", strength: 5 },
        { from: groups.name = "D", to: groups.name = "D", strength: 0 },
        { from: groups.name = "D", to: groups.name = "A", strength: 5 },
        { from: groups.name = "D", to: groups.name = "B", strength: 5 },
        { from: groups.name = "D", to: groups.name = "C", strength: 5 },
    ]

    let matrix = []

    function getMatrix(paths, groups) {
        matrix = []

        var mapPaths = paths.map(item => {
            const container = {}
                container.from = groups.findIndex(ele => ele.name == item.from)
                container.to = groups.findIndex(ele => ele.name == item.to)
                container.strength = item.strength
            return container
        })
        
        mapPaths.forEach(function (item) {
            // initialize sub-arra if not yet exists
            if (!matrix[item.to]) {
                matrix[item.to] = []
            }

            matrix[item.to][item.from] = item.strength
        })

        return matrix
    }

    ////////////////////////////////////////////////////////////
    ///////////////////// Set-Up Visualization /////////////////
    ////////////////////////////////////////////////////////////

    const vw = window.innerWidth 
    const vh = window.innerHeight
    const innerRadius = Math.min(vw, vh) * 0.3;
    const outerRadius = innerRadius * 1.1;
    const duration = 1000;


    const svg = d3.select("#chart").append("svg")
        .attr("width", vw)
        .attr("height", vh)
        .attr("overflow", "unset")

    const wrapper = svg.append("g")
        .attr("id", "wrapper")
        .attr("transform", "translate(" + vw / 2 + "," + vh / 2 + ")")

    const ribbonsG = wrapper.append("g").attr("id", "ribbons")
    const groupsG = wrapper.append("g").attr("id", "groups")

    var chordGenerator = d3.chord()
        .padAngle(0.10)
        .sortSubgroups(d3.ascending)
        .sortChords(d3.descending)

    var arc = d3.arc()
        .innerRadius(innerRadius * 1.01)
        .outerRadius(outerRadius)

    var ribbon = d3.ribbon()
        .radius(innerRadius);


    update(paths, groups)

    function update(thisPaths, thisGroups) {
        const chords = chordGenerator(getMatrix(thisPaths, thisGroups))

        // ribbons
        const ribbonsUpdate = ribbonsG
            .selectAll("path.ribbon")
            .data(chords)

        const ribbonsEnter = ribbonsUpdate
            .enter()
            .append("path")

        ribbonsUpdate
            .merge(ribbonsEnter)
            .transition(duration)
            .attr("opacity", 0)
            .attr("class", "ribbon")    
            .transition()
            .duration(duration)
            .attr("d", ribbon)
            .style("fill", function (d) { return thisGroups[d.target.index].color; })
            .attr('opacity', 1)

        ribbonsUpdate
            .exit()
            .transition()
            .duration(duration)
            .attr("opacity", 0)
            .remove();
            
            
        let itemsUpdate = groupsG.selectAll("g.group")
            .data(chords.groups);
          
        const itemsEnter = itemsUpdate.enter().append("g").attr("class", "group");
        
        itemsEnter.append('path')
            .attr("class", "arc");
        itemsEnter.append('text')
            .attr("class", "title");
            
        itemsUpdate = itemsUpdate.merge(itemsEnter);
        
        itemsUpdate.select("path.arc")
            .transition(duration)
          .attr("opacity", 0)
          .transition()
          .duration(duration)
          .attr("d", arc)
          .style("fill", function (d) { return thisGroups[d.index].color; })
          .attr('opacity', 1)
          
        itemsUpdate.exit()
            .select("path.arc")
            .transition()
            .duration(duration)
            .attr("opacity", 0)
            .remove();

        itemsUpdate.select("text.title")
            .attr("opacity", 0)
            .transition()
            .duration(duration)
            .each(function(d){ d.angle = (d.startAngle + d.endAngle) / 2; })
            .attr("dy", ".35em")
            .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
            .attr("transform", function(d) {
                return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                    + "translate(" + (outerRadius + 10) + ")"
                    + (d.angle > Math.PI ? "rotate(180)" : "");
            })
            .text(function(d,i){ return thisGroups[i].name; })        
            .attr("opacity", 1)
            
        itemsUpdate.exit()
            .select("text.title")
            .remove()
    }
    body {
        font-size: 16px;
        font-family: 'Oswald', sans-serif;
        background-color: #ECF0F3;
        cursor: default;
        overflow: hidden;
    }
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <title>D3v7</title>

    <!-- D3.js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js" charset="utf-8"></script>
    <!-- fontawesome stylesheet https://fontawesome.com/ -->
    <script src="https://kit.fontawesome.com/98a5e27706.js" crossorigin="anonymous"></script>
</head>

<body>
    <div id="chart"></div>
 
</body>

</html>

Related