Home > Mobile >  D3v7 grouping by data bundle not by type
D3v7 grouping by data bundle not by type

Time:09-23

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>

CodePudding user response:

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