3

I'm trying to create a fixed number of elements using D3, taking this value from a json file. This means that, if the given json data is n, I want to print n elements into my svg.

This is my code:

    // Defining the size of the svg element
    var w = 1000;
    var h = 50;

    // Defining the dataset
    d3.json("people.json", function(dataset) {

    // Iterating through the json
    for(var i=0; i<dataset.length; i++) {

                var svg = d3.select("body").append("svg")
                    .attr("width", w)
                    .attr("height", h);

            // Iterating through the value
            for(var j=0; j<dataset[i].age; j++) {

                svg.selectAll("rect")
                        .data(dataset)
                        .enter()
                        .append("rect")
                        .attr("width", 20)
                        .attr("height", 20)
                        .attr("x", function(d, j) { return j * 55 })
            }

        }

    });

This is my json example file (totally random values for ages):

  [{
    "name": "Larry",
    "last": "Page",
    "country": "USA",
    "city": "Mountain View",
    "age": 32
  }, {
    "name": "Sergey",
    "last": "Bean",
    "country": "USA",
    "city": "Mountain View",
    "age": 37
  }, {
    "name": "Bill",
    "last": "Gates",
    "country": "USA",
    "city": "Seattle",
    "age": 60
  }, {
    "name": "Mark",
    "last": "Zuckemberg",
    "country": "USA",
    "city": "Palo Alto",
    "age": 35
  }, {
    "name": "Sergio",
    "last": "Marchionne",
    "country": "Italy",
    "city": "Milan",
    "age": 65
  }
]

My expected result should be something like that ( [-] --> svg rectangle)

  1. Larry Page: [-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]

  2. Sergey Bean: [-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]

and so on...

Can you help me to understand what I'm doing wrong?

Thanks!

11
  • 1
    Just remove .data(dataset) from rects drawing. That's misplaced there. Commented Aug 23, 2016 at 10:31
  • Thanks for your answer. However removing it has as consequence this error: get_data_from_json.html?_ijt=4057q09d7k7p7tej2g0mf5objt:56 Uncaught TypeError: svg.selectAll(...).enter is not a function(anonymous function) @ get_data_from_json.html? Commented Aug 23, 2016 at 10:33
  • 2
    Let me give you an advice, for free: every time you think about using a fior loop in a D3 visualisation, ask yourself: "is this necessary?". In 98.7℅ of the cases it's not necessary (source: FakeData Inc.) Commented Aug 23, 2016 at 10:34
  • 1
    selectAll is misplaced too, like enter(). You only need append("rect") and set its attributes. Commented Aug 23, 2016 at 10:35
  • 1
    The name of this is "pictogram". It's a very basic task in d3 creating what you want without a single for loop. Right now I'm on my mobile, terrible to write, but I'm sure that someone will soon show you how to do it. Commented Aug 23, 2016 at 10:57

2 Answers 2

2

The pure D3 approach would require no for-loops at all (like mentioned by Gerardo Furtado in his comment) and will be something along the following lines:

d3.select("body").selectAll("svg")
  .data(dataset)              // Bind the entire dataset
  .enter().append("svg")      // Append one svg per object in dataset
  .selectAll("rect")
    .data(function(d) {       // Inherit data from svg by mapping of objects
      // Create an array of number of required rects
      return d3.range(d.age).map(function(d) { return d*15; }); 
    })
    .enter().append("rect")   // Append rects per svg
      .attr("x", function(d) {
        return d;             // Position was calculated in above mapping in data()
      });

This is all you need to draw the graph you are after (attributes left aside). For a full example setting all values have a look at the following example:

var dataset =   [{
    "name": "Larry",
    "last": "Page",
    "country": "USA",
    "city": "Mountain View",
    "age": 32
  }, {
    "name": "Sergey",
    "last": "Bean",
    "country": "USA",
    "city": "Mountain View",
    "age": 37
  }, {
    "name": "Bill",
    "last": "Gates",
    "country": "USA",
    "city": "Seattle",
    "age": 60
  }, {
    "name": "Mark",
    "last": "Zuckemberg",
    "country": "USA",
    "city": "Palo Alto",
    "age": 35
  }, {
    "name": "Sergio",
    "last": "Marchionne",
    "country": "Italy",
    "city": "Milan",
    "age": 65
  }
];

// Defining the size of the svg element
var w = 1000;
var h = 50;

// Defining the dataset
//d3.json("people.json", function(dataset) {

  d3.select("body")
    .selectAll("svg")
    .data(dataset)              // Bind the entire dataset
    .enter().append("svg")      // Append one svg per object in dataset
      .attr("width", w)
      .attr("height", h)
    .selectAll("rect")
      .data(function(d) {       // Inherit data from svg by mapping of objects
        // Create an array of number of required rects
        return d3.range(d.age).map(function(d) { return d*15; }); 
      })
      .enter().append("rect")   // Append rects per svg
        .attr("width", 10)
        .attr("height",10)
        .attr("x", function(d) {
          return d;             // Position was calculated in above mapping in data()
        });


//});
<script src="https://d3js.org/d3.v4.js"></script>

Sign up to request clarification or add additional context in comments.

Comments

1

You could do it like this:

var dataset = [{
  "name": "Larry",
  "last": "Page",
  "country": "USA",
  "city": "Mountain View",
  "age": 32
}, {
  "name": "Sergey",
  "last": "Bean",
  "country": "USA",
  "city": "Mountain View",
  "age": 37
}, {
  "name": "Bill",
  "last": "Gates",
  "country": "USA",
  "city": "Seattle",
  "age": 60
}, {
  "name": "Mark",
  "last": "Zuckemberg",
  "country": "USA",
  "city": "Palo Alto",
  "age": 35
}, {
  "name": "Sergio",
  "last": "Marchionne",
  "country": "Italy",
  "city": "Milan",
  "age": 65
}];

// Defining the size of the svg element
var w = 1000;
var h = 500;
var itemH = 10;
var itemW = 3;
var padding = 2;


// Append svg element
var svg = d3.select("body").append("svg")
  .attr("width", w)
  .attr("height", h);

// Append groups for each in dataset
var group = svg.selectAll("g")
  .data(dataset)
  .enter()
  .append('g');

// Each group
group.each(function(d, i) {

  //console.log(d);
  var age = d.age;

  var self = d3.select(this);

  // Transform the group in Y
  self.attr('transform', function() {
    return 'translate(0,' + (i * (itemH + padding)) + ')';
  });

  // Append rect forEach from 0 - age
  d3.range(age).forEach(function(i) {
    //console.log(i);

    self.append('rect')
      .attr('width', itemW)
      .attr('height', itemH)
      .attr('x', function() {
        return i * (itemW + padding);
      });
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>


<body></body>

2 Comments

Nitpicking, but the D3 way of doing loops is functional style, ie d3.range(age).each(function(d, i){ ... })
@Duopixel Did you mean forEach? Nice, i've added it to my answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.