ari gold
ari gold

Reputation: 2094

d3 join on object key within array

My data looks like this:

[
   {
      name: "Joel Spolsky",
      values: [
                {
                   timestamp: 1380432214730,
                   value: 55
                },
                {
                   timestamp: 1380432215730,
                   value: 32
                },   
                {
                   timestamp: 1380432216730,
                   value: 2
                },   
                {
                   timestamp: 1380432217730,
                   value: 37
                },

                // etc
              ]     
    },
   {
      name: "Soul Jalopy",
      values: [
                {
                   timestamp: 1380432214730,
                   value: 35
                },
                {
                   timestamp: 1380432215730,
                   value: 72
                },   
                {
                   timestamp: 1380432216730,
                   value: 23
                },   
                {
                   timestamp: 1380432217730,
                   value: 3
                },

                // etc
              ]     
    },

    // and so on
]

I pass this data into d3.layout.stack so a y and y0 get added. I then draw this stacked layout.

When the data changes, I join the new data to the old.

I can join the groups on name like this:

var nameGroups = this.chartBody.selectAll(".nameGroup")
        .data(this.layers, function (d) {
                return d.name;
             });

But I'm having trouble joining the rectangles (or "bars") on timestamp. The best I can do (so far) is join them on the values key:

var rects = nameGroups.selectAll("rect")
        .data(function (d) {
                return d.values;
              });

How do I join this "inner data" on the timestamp key?

I've tried including the array index:

var rects = nameGroups.selectAll("rect")
        .data(function (d, i) {
                return d.values[i].timestamp;
              });

But that doesn't work because (I think) the timestamp is matched per array index. That is, the join isn't looking at all timestamp values for a match, just the one at that index.

UPDATE

Here is my complete update function:

updateChart: function (data) {
    var that = this,
        histogramContainer = d3.select(".histogram-container"),
        histogramContainerWidth = parseInt(histogramContainer.style('width'), 10),
        histogramContainerHeight = parseInt(histogramContainer.style('height'), 10),
        width = histogramContainerWidth,
        height = histogramContainerHeight,
        nameGroups, rects;

    /*
     FWIW, here's my stack function created within my
     init function:

     this.stack = d3.layout.stack()
         .values(function (d) { return d.values; })
         .x(function (dd) { return dd.timestamp; })
         .y(function (dd) { return dd.value; });

     */

    // save the new data
    this.layers = this.stack(data);

    // join the new data to the old via the "name" key
    nameGroups = this.chartBody.selectAll(".nameGroup")
        .data(this.layers, function (d, i) {
            return d.name;
        });

    // UPDATE
    nameGroups.transition()
        .duration(750);

    // ENTER
    nameGroups.enter().append("svg:g")
        .attr("class", "nameGroup")
        .style("fill", function(d,i) {
            //console.log("entering a namegroup: ", d.name);
            var color = (that.colors[d.name]) ?
                    that.colors[d.name].value :
                    Moonshadow.helpers.rw5(d.name);
            return "#" + color;
        });

    // EXIT
    nameGroups.exit()
        .transition()
        .duration(750)
        .style("fill-opacity", 1e-6)
        .remove();

    rects = nameGroups.selectAll("rect")
        .data(function (d) {

            // I think that this is where the change needs to happen

            return d.values;

        });

    // UPDATE
    rects.transition()
        .duration(750)
        .attr("x", function (d) {
            return that.xScale(d.timestamp);
        })
        .attr("y", function(d) {
            return -that.yScale(d.y0) - that.yScale(d.y);
        })
        .attr("width", this.barWidth)
        .attr("height", function(d) {
            return +that.yScale(d.y);
        });

    // ENTER 

    rects.enter().append("svg:rect")
        .attr("class", "stackedBar")
        .attr("x", function (d) {
            return that.xScale(d.timestamp); })
        .attr("y", function (d) {
            return -that.yScale(d.y0) - that.yScale(d.y); })
        .attr("width", this.barWidth)
        .attr("height",function (d) {
            return +that.yScale(d.y); })
        .style("fill-opacity", 1e-6)
        .transition()
        .duration(1250)
        .style("fill-opacity", 1);

    // EXIT

    rects.exit()
        .transition()
        .duration(750)
        .style("fill-opacity", 1e-6)
        .transition()
        .duration(750)
        .remove();
}

Upvotes: 2

Views: 1546

Answers (1)

Lars Kotthoff
Lars Kotthoff

Reputation: 109232

You're not actually passing a key function in your code. The key function is the optional second argument to .data() (see the documentation). So in your case, the code should be

.data(function(d) { return d.values; },
      function(d) { return d.timestamp; })

Here the first function tells D3 how to extract the values from the upper level of the nesting and the second how, for each item in the array extracted in the first argument, get the key.

Upvotes: 1

Related Questions