Search code examples
javascriptd3.js

Break D3 objects into rows


I have a D3 script that creates a circle for every point in my data and then places the circles next to each other. How can I place the circles into a new row after, say, five circles (or after the graphic hits a specific width)? The image below shows what I am trying to accomplish. Code and JS fiddle also included.

Thanks so much for any help that you can provide.

[![enter image description here][1]][1]

Js Fiddle: https://jsfiddle.net/amarton/Ly1go0sp/1/

    {
      "category": "apple",
      "step": 1,
      "description": "Lorem ipsum dolor sit amet"
    },
    {
      "category": "banana",
      "step": 2,
      "description": "Lorem ipsum dolor sit amet"
    },
    {
      "category": "grape",
      "step": 3,
      "description": "Lorem ipsum dolor sit amet"
    },
    {
      "category": "apple",
      "step": 3,
      "description": "Lorem ipsum dolor sit amet"
    },
    {
      "category": "banana",
      "step": 2,
      "description": "Lorem ipsum dolor sit amet"
    },
    {
      "category": "grape",
      "step": 1,
      "description": "Lorem ipsum dolor sit amet"
    }
   ]

async function draw(){
    //Get data
   const data = jsonData;

//apend the svg container to the div
var svgContainer = d3.select("#myChart").append("svg")
.attr("viewBox", `0 0 500 500`)


//append the circles to the svg 
var circles = svgContainer.selectAll("circle")
    //this calls the data
    .data(data)
    .enter()
    .append("circle")

//draw the circles    
var circleAttributes = circles
    //increment the x posiiton
    .attr("cx", function(e, counter){return 50  * (counter) + 50})
    .attr("cy", 30)
    .attr("r", 20 )

}
  
draw()```


  [1]: https://i.sstatic.net/nouHP.png

Solution

  • Check if cx exceeds the viewBox edge. You can then increase cy to place the circle in different rows:

    // the width and height of the view box
    var viewBoxEdge = 500;
    // the x/y offset between the center of two circles
    var circleOffset = 50;
    
    //apend the svg container to the div
    var svgContainer = d3.select("#myChart").append("svg")
    .attr("viewBox", `0 0 ${viewBoxEdge} 500`)
    
    // ...
    
    //draw the circles    
    var circleAttributes = circles
        //increment the x posiiton
        .attr("cx", function(e, counter){
            // this will 'return' cx to the horizontal start of the row when exceeding width - think of this as a 'column index'
            var cx = (50  * counter) % (viewBoxEdge - circleOffset) + circleOffset;
            return cx;
          
          })
        .attr("cy", function(e, counter) {
            // this will move the circle to the next row when exceeding the width - think of this as a 'row index'
            var cy = Math.floor(50 * counter / (viewBoxEdge - circleOffset)) * circleOffset + circleOffset;
            return cy;
        
        })
        .attr("r", 20 )
    

    var jsonData = [
        {
          "category": "apple",
          "step": 1,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "banana",
          "step": 2,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "grape",
          "step": 3,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "apple",
          "step": 3,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "banana",
          "step": 2,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "grape",
          "step": 1,
          "description": "Lorem ipsum dolor sit amet"
        },{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}
       ]
    
    async function draw(){
        //Get data
       const data = jsonData;
    
    // the width and height of the view box
    var viewBoxEdge = 500;
    // the x/y offset between the center of two circles
    var circleOffset = 50;
    
    //apend the svg container to the div
    var svgContainer = d3.select("#myChart").append("svg")
    .attr("viewBox", `0 0 ${viewBoxEdge} 500`)
    
    
    //append the circles to the svg 
    var circles = svgContainer.selectAll("circle")
        //this calls the data
        .data(data)
        .enter()
        .append("circle")
    
    //draw the circles    
    var circleAttributes = circles
        // increment the x posiiton
        .attr("cx", function(e, counter){
            // this will 'return' cx to the horizontal start of the row when exceeding width
            var cx = (50  * counter) % (viewBoxEdge - circleOffset) + circleOffset;
            return cx;
          
          })
        .attr("cy", function(e, counter) {
            // this will move the circle to the next row when exceeding the width
            var cy = Math.floor(50 * counter / (viewBoxEdge - circleOffset)) * circleOffset + circleOffset;
            return cy;
        
        })
        .attr("r", 20 )
    
    }
    
    draw();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <div id="myChart"></div>

    Use HTML Elements

    d3 is not limited to SVG manipulation. You can manipulate the entire element tree of html.

    If the visualization you are trying to build can be done in html only, do it in html!

    You could use display: flex to layout the circles over multiple rows. This has two advantages:

    1. You could define responsive css to handle different viewports. Doing this in css is easier than writing your own d3 'layout engine'.
    2. The browser changes the layout when the viewport changes (resize window)

    var jsonData = [
        {
          "category": "apple",
          "step": 1,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "banana",
          "step": 2,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "grape",
          "step": 3,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "apple",
          "step": 3,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "banana",
          "step": 2,
          "description": "Lorem ipsum dolor sit amet"
        },
        {
          "category": "grape",
          "step": 1,
          "description": "Lorem ipsum dolor sit amet"
        },{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}
       ]
       
       var circlesContainer = d3.select('.circles');
       
       circlesContainer = circlesContainer
           .selectAll('.circle')
           .data(jsonData)
           .enter()
           .append('div')
           .attr('class', 'circle')
       
    .circles {
      display: flex;
      width: 100%;
      flex-direction: row;
      flex-wrap: wrap;
    }
    
    .circles .circle {
      display: block;
      height: 40px;
      width: 40px;
      border-radius: 20px;
      background-color: black;
      margin: 3px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <div class="circles"></div>