d3 join on object key within array - d3.js

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();
}

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.

Related

D3.JS : recover cell value when click in a row

I have made a table from a csv with d3.js. When I click in a cell I want to get the value of the last cell in its row .
I tried this :
var container = d3.select("body")
.append("table")
.attr("id","myTable");
//header
var headers=container.append("thead").append("tr")
.selectAll("th")
.data(titles)
.enter().append("th")
.text(function(d) {
return d;
});
var rows=container.append("tbody")
.selectAll("tr").data(dataCSV)
.enter().append("tr")
.on('click',function(d){
if(d3.select(this).classed("highlight")){
d3.selectAll("tr").classed("highlight", false);
}else{
d3.selectAll("tr").classed("highlight", false);
d3.select(this).classed("highlight", true);
}
});
rows.selectAll("td")
.data(function (d) {
return titles.map(function (k) {
return { 'value': d[k], 'name': k};
});
}).enter()
.append('td')
.attr("style","width:"+widthCellule+"%")
.attr('data-th', function (d) {
return d.name;
})
.text(function (d) {
return d.value;
})
/* Here is my mistake , I d'ont know */
.on('click',function(d,i,j){
column=i;
last_column=titles.length;
alert(d3.select('id','myTable').rows[j].cells[last_column].innerHTML);
});

d3 .data() function not working as expected multiple data sources

I have a chart that works off two data sources and essentially the first [updateSetupData(data)] builds the elements the chart needs and the second [update(data)] does not append elements it only updates the html formed by the other function. I use an id as a key to keep things in sync.
function updateSetupData(data) {
var countsByParent = d3.nest()
.key(function (d) { return d.parent + '_tbl'; })
.key(function (d) { return d.SkillGroup + '_grp'; })
//.key(node => node.AgtName)
//.rollup(function(leaves) { return leaves.length;})
.entries(data);
var treeRoot = {
key: "root",
parent: null,
value: "100",
values: countsByParent };
var root = d3.hierarchy(treeRoot, function (d) { return d.values; })
.sum(function (d) { return d.value; });
// .sort(function(a, b) { return b.value - a.value; });
var nodes = pack(root);
//console.log(nodes);
var node = canvas.selectAll(".node")
.data(pack(root).descendants())
.enter().append("g")
.attr("class", function (d) {
return d.data.key == null ? "node " + d.data.AgtName + " agent " :
"node " + d.data.key;
})
.attr("id", function (d) { return d.data.AgtName + "a_" + d.data.AgtId +
"_s" + d.data.skillId + "_g" + d.data.groupId })
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y
+ ")"; })
.attr("fill", "steelblue")
.on("mouseover", function (d) {
highlight(d.label ? d.label : d.data.AgtName);
}).on("mouseout", function (d) { highlight(null); });
function highlight(agtName) {
if (agtName == null) d3.selectAll(".node").classed("active", false);
else d3.selectAll(".node." + agtName).classed("active", true);
}
node.append("circle")
.attr("r", function (d) { return d.r; })
// .attr("fill", "steelblue")
.attr("opacity", 0.25)
.attr("stroke", "#ADADAD")
.attr("stroke-width", "2");
node
.append("svg:title").text(function (d) { return d.data.AgtName; });
var arc = arcGenerator
.outerRadius(function (d, i) { return d.r; })
.startAngle(0)
.endAngle(180);
node.append('defs')
.append('path')
.attr("id", function (d, i) { return "s" + i; })
.attr("d", arc);
//.attr("d", function (d, i) { return getPathData(d.r); } );
node.append("text")
.attr("transform", "rotate(90)")
.attr("text-anchor", function (d) { return d.data.key == null ? "start"
: d.data.key.split("_") [1] === "tbl" ? "end" : "start"; })
.append("textPath")
.attr("startOffset", '50%')
.attr("xlink:href", function (d, i) { return '#s' + i; })
.attr("fill", function (d) { return d.data.key == null ? "none" :
d.data.key.split("_") [1] === "tbl" ? "blue" : "black"; })
.text(function (d) {
return d.data.key == null ? "" :
d.data.key == "root" ? "" : d.data.key.split("_")[0];
});
});
The second function is where I am having the issue. Even though I call .data() and have the new [different] data only used to overlay live calls on the static chart; The classed function just after the .data(data, key) works fine; the (d) there has the new data.
for the var text variable (d) in the data functions is from the other function, so the data to set the text with is wrong.
function update(data) {
var agent = canvas.selectAll(".node.agent")
//sets all elements to false for the class before the update
.classed("newCall", false)
.data(data, function (d) {
// the key is either an element id or an id from the data
var myId = d.id ? d.id : this.id;
// console.log("data key: " + d.id + " element id: " + this.id + "
new: " + d.newCall);
return myId;
}).classed("newCall", function (d) {
var f = d.newCall ? d.newCall : false;
//console.log(this.id + " " + f )
return f;
})
var text = agent.selectAll(".newCall text")
.attr("transform", null)
.attr("startOffset", null)
.attr("xlink:href", null)
.attr("fill", function (d) { return "black"; })
.attr("dx", function (d) { return -4;})
.attr("dy", function (d) { return 4; })
.text(function (d) {
console.log(d);
return "3";
});
Is there something I need to do with the text var to get the right data? I was thinking that because I call .data on the agents var that the text var in the would be OK since it appears that when I class the elements the data is there.
Including the whole fixed function. First the data and classes are cleared then the new data is added. The text elements for agent.newCall are updated when the function runs.
function update(data) {
var agent = canvas.selectAll(".node.agent");
// clear call data
agent.select("text").text("");
// sets all elements to false for the class before the update
agent.classed("newCall", false)
.data(data, function (d) {
// the key is either an element id or an id from the data
var myId = d.id ? d.id : this.id;
// console.log("data key: " + d.id + " element id: " + this.id + " new: " + d.newCall);
return myId;
}).classed("newCall", function (d) {
var f = d.newCall ? d.newCall : false;
//console.log(this.id + " " + f )
return f;
})
agent
.select(".newCall text")
.attr("transform", null)
.style("text-anchor", "middle")
.attr("dy", function (d) { return 4; })
.attr("fill", "black")
.style("font-size", function (d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 24) + "px"; })
.text(function (d) {
var txt = d.Calls ? d.Calls.length > 0 ? d.Calls.length : "" : "";
return txt;
});
agent.enter().append("g")
.classed("newCall", function (d) {
return d.newCall ? d.newCall : false;
});
agent.exit().classed("newCall", function (d) {
// console.log(d);
return false;
});
};

How to update line chart points when the source data changes

I've created a multi-line chart from the following source data.
[
{
"key":"108",
"title":"Series 1",
"items":[
{
"key":"54048872e9c2021fd8231051",
"value":1.0
},
{
"key":"540488a1e9c2021fd823107b",
"value":2.0
}
]
},
{
"key":"15",
"title":"Series 2",
"items":[
{
"key":"54048872e9c2021fd8231051",
"value":1.0
},
{
"key":"540488a1e9c2021fd823107b",
"value":4.0
}
]
}
]
We render a line for each Series and circles for each data "point"
var series = svg.selectAll('.series')
.data(this.data)
.enter()
.append('g')
.attr('class', 'series');
series.append('path')
.attr('class', 'line');
// add points to chart
var points = series.append('g')
.attr('class', 'point')
.selectAll('circle')
.data(function (d) {
return d.items;
})
.enter()
.append('circle')
.attr('r', 5);
Then when we first render the chart or when the window resizes we actually set the coordinates of the line and circles:
this.svg.selectAll('.line')
.attr('d', function (d) {
return chart.line(d.items);
});
this.svg.selectAll('.point circle')
.attr('cx', function (d) { return chart.xScale(d.key); })
.attr('cy', function (d) { return chart.yScale(d.value); });
Here, chart.line corresponds to a d3 line generator previously created.
When the source data changes I can update the line by setting its data:
this.svg.selectAll('.line')
.data(this.data);
But I can't figure out how to do the same thing with the data points (circles).
I've tried the following but the data on the individual point is not actually updated. Here the first console log (after selecting the .point elements) returns the correct data but the cx attribute function is returning the old data.
var series = this.svg.selectAll('.series')
.data(this.data)
.selectAll('.point')
.data(function (d) {
console.log(d.items);
return d.items;
})
.selectAll('circle')
.attr('class', 'updated')
.attr('cx', function (d) { console.log(d); return chart.xScale(d.key); })
.attr('cy', function (d) { return chart.yScale(d.value); });

Simple Colission Example With g groups?

Simple collision example, using g groups?
Hi all, i test this examples:
1- http://bl.ocks.org/mbostock/3231298 - this example use a invisible circle for generate the force.
2- http://bl.ocks.org/mbostock/1747543 - this example use a central circle for force layout
This examples always use a circles.
My idea is use pie charts with collision force but i can't find a simple example of this.
I use this construction:
chart_vars_object.svg = d3.select(".canvas_svg").append("svg")
.attr({
"width": general_vars_object.width,
"height": general_vars_object.height
})
var nodes = chart_vars_object.svg.selectAll(".pie")
.data(chart_vars_object.json_init_data)
.enter()
.append("g")
.attr("class", function(d) {
return "pie pie_id_" + d.hash_chart_id
})
.attr("width", chart_vars_object.general_radius * 2)
.attr("height", chart_vars_object.general_radius * 2)
.attr("hash_link", function(d) {
return d.hash_link
})
.call(chart_vars_object.force.drag);
chart_vars_object.force.on("tick", function(e) {
chart_vars_object.svg.selectAll(".pie")
.attr("transform", function(d, i) {
return "translate(" + Math.round(d.x) + "," + Math.round(d.y) + ")";
});
});
//CREATE ARC GROUP's
var g = nodes.selectAll(".arc")
.data(function(d) {
return chart_vars_object.pie(d.hash_taggeds);
})
.enter().append("g")
.attr("class", "arc_group")
.attr("hash_type", function(d, i) {
if (i == 0 ) {
return "article";
}
if (i == 1 ) {
return "image";
}
if (i == 2 ) {
return "video";
}
})
.on("click", function() {
d3.select(this).select('.arc')
alert('Abro el Has Tag');
})
.on("mouseover", function() {
d3.select(this).select('.arc')
.classed("active", true )
})
.on("mouseout", function() {
d3.select(this).select('.arc')
.classed("active", false)
});
//CREATE PATHS WITH ARC
chart_vars_object.path = g.append("path")
.attr("class","arc")
.style({'cursor': 'pointer', 'opacity': '0.9'})
chart_vars_object.path.transition()
.duration(20)
.attr("fill", function(d) {
return chart_vars_object.color(d.data.hash_taggeds_name);
})
.attr("d", chart_vars_object.arc)
.each(function(d) {
this._current = d;
});
Any suggest?

D3 update pie data with zero values

I am updating a pie chart with a dynamic JSON feed. My update function is below
function updateChart(data) {
arcs.data(pie(data)); // recompute angles, rebind data
arcs.transition()
.ease("elastic")
.duration(1250)
.attrTween("d", arcTween)
sliceLabel.data(pie(data));
sliceLabel.transition()
.ease("elastic").duration(1250)
.attr("transform", function (d) {
return "translate(" + arc2.centroid(d) + ")";
})
.style("fill-opacity", function (d) {
return d.value == 0 ? 1e-6 : 1;
});
}
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return arc(i(t));
};
When the JSON has 0 values for all objects, the arcs and labels disappear. Exactly what I want to happen.
The problem is when I pass a new JSON after one that was full of zeros, the labels come back and tween etc but the arcs never redraw.
Any suggestions on correcting my update function so that the arcs redraw correctly after they their d values have been pushed to zero?
-- edit --
Lars suggested below that I use the .enter() exactly in the same way as I did when I created the graph. I tried doing this but the results did not change. See new update function below.
this.updatePie = function updateChart(data) {
arcs.data(pie(data))
.enter()
.append("svg:path")
.attr("stroke", "white")
.attr("stroke-width", 0.5)
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc)
.each(function (d) {
this._current = d
})
arcs.transition()
.ease("elastic")
.duration(1250)
.attrTween("d", arcTween)
sliceLabel.data(pie(data));
sliceLabel.transition()
.ease("elastic").duration(1250)
.attr("transform", function (d) {
return "translate(" + arc2.centroid(d) + ")";
})
.style("fill-opacity", function (d) {
return d.value == 0 ? 1e-6 : 1;
});
}
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return arc(i(t));
};
}
You've actually hit a bug in D3 there -- if everything is zero, the pie layout returns angles of NaN, which cause errors when drawing the paths. As a workaround, you can check whether everything is zero and handle this case separately. I've modified your change function as follows.
if(data.filter(function(d) { return d.totalCrimes > 0; }).length > 0) {
path = svg.selectAll("path").data(pie(data));
path.enter().append("path")
.attr("fill", function(d, i) { return color(d.data.crimeType); })
.attr("d", arc)
.each(function(d) { this._current = d; });
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
} else {
path.remove();
}
Complete jsbin here.

Resources