4

I'm a newbie to D3 and trying to solve a not so easy problem (at least to me!) Here's my sample dataset.

var data = [
    {"x":1000, "y": {"10":100, "10.2":200, "10.3":300, "10.4":600}},
    {"x":1001, "y": {"9.2":60, "9.6":400, "10.1":20, "10.8":320, "11":20, "8.8":722}},
    {"x":1002, "y": {"9.8":80, "9.9":600, "9.2":40}},
    {"x":1003, "y": {"10.1":60, "10.3":400, "10.8":360, "9.6":88}},
    {"x":1004, "y": {"9.8":20, "10.4":450}}
];

How can I bind this using d3 to create rectangles with the attributes x= value of x, y= keys of the y object, width= values of the y object and height= any given constant?

Essentially, this will have a variable number of rectangles for each x based on the number of elements in the y object.

1 Answer 1

3

This is, indeed, a little bit complicated, mainly because your y property contains just one object, instead of an array of objects.

My solution here uses d3.entries, which, according to the API:

Returns an array containing the property keys and values of the specified object (an associative array). Each entry is an object with a key and value attribute, such as {key: "foo", value: 42}. The order of the returned array is undefined. (emphasis mine)

It's important having an array, so we can bind the data (since the data() method accepts only three things: an array, a function or nothing).

So, in this solution, I'm using the outer objects to append groups...

var groups = svg.selectAll(null)
  .data(data)
  .enter()
  .append("g");

... and the y object of each outer object to append the rectangles...

var rects = groups.selectAll(null)
  .data(function(d) {
    return d3.entries(d.y)
  })
  .enter()
  .append("rect");

... and using d3.entries() to convert the object into an array of objects, which can be used with data().

Here is a demo (I changed your actual values, so we can better see the rectangles):

var data = [{
  "x": 100,
  "y": {
    "10": 100,
    "20": 200,
    "26": 300,
    "35": 400
  }
}, {
  "x": 20,
  "y": {
    "50": 60,
    "59.6": 400,
    "70.1": 20,
    "80.8": 320,
    "88": 20,
    "98.8": 722
  }
}, {
  "x": 30,
  "y": {
    "109.8": 80,
    "119.9": 600,
    "139.2": 40
  }
}, {
  "x": 10,
  "y": {
    "150.1": 60,
    "170.3": 400,
    "190.8": 360,
    "209.6": 88
  }
}, {
  "x": 50,
  "y": {
    "259.8": 20,
    "280.4": 450
  }
}];

var colours = d3.scaleOrdinal(d3.schemeCategory10);

var constant = 5;

var svg = d3.select("svg");

var groups = svg.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .style("fill", function(d, i) {
    return colours(i)
  })

var rects = groups.selectAll(null)
  .data(function(d) {
    return d3.entries(d.y)
  })
  .enter()
  .append("rect");

rects.attr("y", function(d) {
    return +d.key
  })
  .attr("width", function(d) {
    return d.value
  })
  .attr("height", constant)
  .attr("x", function(d) {
    return d3.select(this.parentNode).datum().x;
  })
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="300"></svg>

Pay attention to how I get the y and width values and, specially, the x value (accessing the parent's datum).

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

4 Comments

Thanks a lot for your quick reply! While I try to understand the solution, I just want to mention that the values of y are an ideal solution from a data pull perspective, not a required one. If there can be a cleaner solution with some re-arranging of y values like to return an array of keys and values, I'd be okay to change the data pull to return such a result or change it in the JS layer itself. Thanks again!
I'm not following your comment... My answer is returning an array of keys and values.
What I meant is, I'd be OK to change the y object to an array, if it would make it any easier/clean in d3. E.g for the last record it would look like this: [{"k":9.8,"v":20}, {"k":10.4, "v":450}]. But I guess it is really not needed, as your solution works great! Thank you
That is precisely what my code is doing. It is changing the object into an array.

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.