I'm creating a force graph with nodes representing political candidates as circles. Each circle's radius corresponds to the candidate's approval rating (a percentage). I have data over three years. It's coming from a .CSV file. I'm working in d3.js, and I'm still very new.
What I'm trying to do is dynamically update each node object such that the radius reflects the currently-selected year. Loading the graph works fine: the first year (2013) works. But, when I switch to 2012, the DOM elements don't change: they retain their 2013 values. Here are the relevant snips of code:
After opening the CSV call:
data.forEach(function(d) {
var node = {
id: d.id,
name: d.candidate,
value: (parseInt(d["y"+year])),
radius: (parseInt(d["y"+year], 10))*3,
party: d.cand_party,
sex: d.gender
};
nodes.push(node);
});
And here's the update I'd like to do:
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = Math.max(year0, year - 1); break;
case 39: year = Math.min(year1, year + 1); break;
}
update ();
});
function update() {
title.text(year);
circles.transition(1500)
.attr("r", function(d) { return d.radius; });
};
The update() function is working, in that my title is switching to 2012, as desired. However, when I check each node's DOM info, I see that radius and value retain their 2013 values (the circles aren't changing size).
Any advice appreciated, thanks.
Posted too soon! In line with what Lars commented, I found this worked:
data.forEach(function(d) {
var node = {
id: d.id,
name: d.candidate,
value: (parseInt(d["y"+year])),
radius2014: (parseInt(d.y2014, 10))*3,
radius2013: (parseInt(d.y2013, 10))*3,
radius2012: (parseInt(d.y2012, 10))*3,
party: d.cand_party,
sex: d.gender
};
nodes.push(node);
});
And then calling things like so:
function update() {
title.text(year);
circles.transition(1500)
.attr("r", function(d) { return d["radius"+year]; });
};
Related
I am new to dc.js and facing issues in deciding dimensions and groups. I have data like this
this.data = [
{Type:'Type1', Day:1, Count: 20},
{Type:'Type2', Day:1, Count: 10},
{Type:'Type1', Day:2, Count: 30},
{Type:'Type2', Day:2, Count: 10}
]
I have to show a composite chart of two linecharts one for type Type1 and other for Type2. My x-axis will be Day. So one of my dimensions will be Day
var ndx = crossfilter(this.data);
var dayDim = ndx.dimension(function(d) { return d.Day; })
How the grouping will be done? If I do it on Count, the total count of a particular Day shows up which I don't want.
Your question isn't entirely clear, but it sounds like you want to group by both Type and Day
One way to do it is to use composite keys:
var typeDayDimension = ndx.dimension(function(d) {return [d.Type, d.Day]; }),
typeDayGroup = typeDayDimension.group().reduceSum(function(d) { return d.Count; });
Then you could use the series chart to generate two line charts inside a composite chart.
var chart = dc.seriesChart("#test");
chart
.width(768)
.height(480)
.chart(function(c) { return dc.lineChart(c); })
// ...
.dimension(typeDayDimension)
.group(typeDayGroup)
.seriesAccessor(function(d) {return d.key[0];})
.keyAccessor(function(d) {return +d.key[1];}) // convert to number
// ...
See the series chart example for more details.
Although what Gordon suggested is working perfectly fine, if you want to achieve the same result using composite chart then you can use group.reduce(add, remove, initial) method.
function reduceAdd(p, v) {
if (v.Type === "Type1") {
p.docCount += v.Count;
}
return p;
}
function reduceRemove(p, v) {
if (v.Type === "Type1") {
p.docCount -= v.Count;
}
return p;
}
function reduceInitial() {
return { docCount: 0 };
}
Here's an example: http://jsfiddle.net/curtisp/7frw79q6
Quoting Gordon:
Series chart is just a composite chart with the automatic splitting of the data and generation of the child charts.
How can I set different diameters to graph nodes, depend on their 'grade' (by grade I mean root or children)?
For example, I have one source node and I want to set the diameter to a value. Its children will have another value.
This is what I've tried by now:
Here I build the links array:
reply.forEach(function (targetNode) {
links.push({
source: sourceNode, // the source is a string
target: targetNode // the target is an array of strings
});
});
And here I tried to give different diameters:
.attr("r", function (d) {
links.forEach(function (link) {
if (d === link.source) {
return 15;
} else return 6;
})
})
The result was a graph only with links, all the nodes disappeared.
Any ideas how can I resolve this?
I fixed it. If it will help anyone, I leave the code here:
function setDiameter() {
links.forEach(function (link) {
svg.selectAll("circle")
.attr("r", function (d) {
if (d === link.source) {
return 15;
} else return 6;
})
});
}
I'm learning about nesting and have been looking at phoebe bright's explanations, where she writes:
var nested_data = d3.nest()
.key(function(d) { return d.status; })
.entries(csv_data);
gets this:
[
{
"key": "Complete",
"values": [
{
"id": "T-024",
"name": "Organisation list in directory",
"priority": "MUST",
},
{
When I try to do the same, in my console, if I can recreate it, looks like this:
Object
key: "1847"
values: Array [2]
0: Object
production: "A Mirror for Witches"
1: Object
production: "Sadlers Wells"
When I try to display the "Values" as text, all I get is [Object, object] in my html, where what I want is the production names.
How do I do this? I have tried nesting production as a key also, but this doesn't seem to work, and have also tried returning the index when returning the values, but can't get that to work either.
Any help I will really appreciate, thanks.
Here is my code
data.csv
year,production,company
1847,A Mirror for Witches
1847,Sadlers Wells
d3.csv("data.csv", function(csv_data){
var nested_data = d3.nest()
.key(function(d) { return d.year; })
.entries(csv_data)
console.log(nested_data);
var selection =
d3.select("body").selectAll("div")
.data(nested_data)
.enter()
selection.append("div")
.classed('classed', true)
.text(function(d){
return d.key;
});
d3.selectAll(".classed").append("div")
.text(function(d){
return d.values;
});
});
Here's a working plunk: http://plnkr.co/edit/0QuH8P9ujMdl0vWuQkrQ?p=preview
I added a few more lines of data to show it working properly.
The thing to do here is to add a second selection (I've called it production_selection) and bind data based off the first selection (year_selection): You use nested selections to show nested data.
First selection (show a div for each year, or key, in your nested data):
var year_selection = d3.select("#chart").selectAll("div")
.data(nested_data)
.enter().append("div")
...
Second selection (show all productions, or values, under that key):
var production_selection = year_selection.selectAll(".classed")
.data(function(d) { return d.values; })
.enter().append("div")
...
For the second selection, you just define the accessor function (d.values)
I am working on a d3 sample http://bost.ocks.org/mike/nations/:
I am trying to add title for circles with with the name as well as checkin details.
following is the modified code for the display year function (rest of the code almost no change,,,):
// Updates the display to show the specified year.
function displayYear(year) {
dot.data(interpolateData(year), key)
.call(position)
.sort(order);
dot.append("title").text(function(d) { return d.name});
dot.text(function(d) { return d.name + "~"+ d.checkins + d.Checkintimes; });
label.text(Math.round(year));
}
// Interpolates the dataset for the given (fractional) year.
function interpolateData(year) {
return nations.map(function(d) {
return {
name: d.name,
region: d.region,
checkins: interpolateValues(d.checkins, year),
teamsize: interpolateValues(d.teamsize, year),
Checkintimes: interpolateValues(d.Checkintimes, year)
};
});
}
However the same is not appearing as title in the circles. I just want to append the checkin detail with the circle.
My json file contains the following:
[
{
"name":"Search&Navigator",
"region":"IPScience",
"checkins":[[2000,100],[2001,200],[2002,300],[2003,275],[2004,222],[2005,280],[2006,281],[2007,400],[2008,55],[2009,300]],
"teamsize":[[2000,10],[2001,7],[2002,7],[2003,12],[2004,5],[2005,3],[2006,10],[2007,12],[2008,12],[2009,10]],
"Checkintimes":[[2000,40],[2001,50],[2002,60],[2003,50],[2004,40],[2005,30],[2006,30],[2007,35],[2008,30],[2009,30]]
}
]
Your variable dot doesn't contain a reference to the title element. Simply change the function that appends it to do what you want:
dot.append("title")
.text(function(d) { return d.name + "~"+ d.checkins + d.Checkintimes; });
I have the following complex data structure:
[
Object {id: 15, targets: Array[2]},
Object {id: 26, targets: Array[2]},
Object {id: 39, targets: Array[2]}
]
'targets' is an array of objects. Each of them has this shape:
Object {idTarget: "target1", events: Array[315]}
Object {idTarget: "target2", events: Array[310]}
'events' is an array with the real values to plot.
So, each element has this shape:
Object {timestamp: 1373241642, value: 1801.335}
Now, with this structured dataset, I would like to create an svg group 'g' for each external object (I am referring to 15, 26 and 39) and inside each group I want to create two lines, one for each target, using the values in 'events'.
Having a flat dataset it's easy to proceed in the drawing following the pattern: select + data + enter + append, but I am having trouble with this complex dataset.
For example I don't even know how to assign a key function to start.
svg.selectAll('.element')
.data(data, function(d){ return d.id + ?})
I would like to have this kind of keys '15target1', '15target2', '26target1', '26target2' and so on.
Do you recommend to simplify the dataset giving up the possibility of having neat groups or there is a workaround here that lets me easily draw what I want?
Thanks.
You want nested selections for this. Your code would look something like this.
var gs = d3.selectAll("g").data(data, function(d) { return d.id; });
gs.enter().append("g");
var line = d3.svg.line().x(function(d) { return xscale(d.timestamp); })
.y(function(d) { return yscale(d.value); });
gs.selectAll("path")
.data(function(d) { return d.targets; }, function(d) { return d.idTarget; })
.enter().append("path")
.attr("d", function(d) { return line(d.events); });