Reputation: 83
I am 2 days into d3.js and I have build this draw structure used in tennis tournaments.
And the data that I start with is below -
var data = [
[["Andy","Marin"],["David","Kei"],["Novak","Stani"],["Roger","Gael"],["Phili","John"],["Milos","Rafa"],["Tomas","Grig"],["Jo-Wi","Gill"]],
[["Marin","Kei"],["Stani","Roger"],["John","Rafa"],["Tomas","Jo-Wi"]],
[["Marin","Stani"],["John","Jo-Wi"]],
[["Stani","John"]]
];
I don't know how but I arrived at the following code to achieve this (I used a lot of mbostock's code) -
var playerOffsetX = 20;
rectY= 20;
rectGapY = 10;
matchGapY = 30;
rectX = 100;
matchY = 2 * rectY + rectGapY + matchGapY;
totalY = 8 * matchY;
var svg = d3.selectAll("svg");
var level = svg.selectAll(".level")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(" + (i * 200) + "," + Math.pow(2,i)*matchY/2 + ")"; })
.attr("class", "level");
var match = level.selectAll(".match")
.data(function(d) { return d ; })
.enter().append("g")
.attr("transform", function(d, i, j) { return "translate(0," + (Math.pow(2,j)*i*matchY) + ")"; })
.attr("class", "match");
var player = match.selectAll(".player")
.data(function(d) { return d; })
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate("+ (playerOffsetX) + "," + (i == 0 ? 0 : (rectY+rectGapY)) + ")"; })
.attr("class", "player");
player.append("rect")
.attr("width", rectX)
.attr("height", rectY)
.attr("class", "player");
player.append("text")
.attr("x", 3)
.attr("y", rectY / 2)
.attr("dy", ".35em")
.text(function(d) { return d; });
</script>
Now here is my problem. I want to restructure by data as this -
var data = [{ "level": 1, "matches": [{"seq": 1, "seeds": [1,16]}, {"seq": 2, "seeds": [8,9]}, {"seq": 3, "seeds": [4,13]}, {"seq": 4, "seeds": [5,12]},
{"seq": 5, "seeds": [2,15]}, {"seq": 6, "seeds": [7,10]}, {"seq": 7, "seeds": [3,14]}, {"seq": 8, "seeds": [6,11]}]},
{ "level": 2, "matches": [{"seq": 9, "seeds": [0,0]}, {"seq": 10, "seeds": [0,0]}, {"seq": 11, "seeds": [0,0]}, {"seq": 12, "seeds": [0,0]}]},
{ "level": 3, "matches": [{"seq": 13, "seeds": [0,0]}, "seq": 14, "seeds": [0,0]}]},
{ "level": 4, "matches": [{"seq": 15, "seeds": [0,0]}]}];
... and I cannot figure out what I should change in my code.
Of course, I have another problem. You can see that I do not have links connecting the winners across the levels. I need them. I considered cluster layout but I guess I will have to restructure by data to suit. My case is quite reverse of a cluster case. (Mukul)
Upvotes: 0
Views: 156
Reputation: 25157
The structure you want it to become should work just as well as the original one, since it's still essentially a 3-level array as before, but since you're now wrapping things in Objects, you have to adjust the way the sub-arrays are accessed when rendered. I've marked those 3 places like <-- THIS
:
var level = svg.selectAll(".level")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(" + (i * 200) + "," + Math.pow(2,i)*matchY/2 + ")"; })
.attr("class", "level");
var match = level.selectAll(".match")
.data(function(d) { return d.matches ; })// <-- THIS
.enter().append("g")
.attr("transform", function(d, i, j) { return "translate(0," + (Math.pow(2,j)*i*matchY) + ")"; })
.attr("class", "match");
var player = match.selectAll(".player")
.data(function(d) { return d.seeds; })// <-- THIS
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate("+ (playerOffsetX) + "," + (i == 0 ? 0 : (rectY+rectGapY)) + ")"; })
.attr("class", "player");
player.append("rect")
.attr("width", rectX)
.attr("height", rectY)
.attr("class", "player");
player.append("text")
.attr("x", 3)
.attr("y", rectY / 2)
.attr("dy", ".35em")
.text(function(d) { return getPlayerByIdOrSomethingLikeThat(d); });// <-- THIS
Unless I'm overlooking something, that'll work.
Regarding the 2nd question, I think the force layout (not cluster) is what you'll want look at and learn from — not because the force layout is moving nodes around (which you're not) but because most force layout examples have nodes AND links (which you do too). In that case, you want to express the links as an Array of Objects that map source
to target
:
[
{ source: {...}, target: {...} },
{ source: {...}, target: {...} },
{ source: {...}, target: {...} },
...
]
because then you can do the whole d3.select(...).selectAll(...).data(...).enter().append('line')
business as usual, to append the lines that connect them. I.e. that would be convenient since it is a 1 to 1 mapping between source-target pairs to SVG <line>
s). So one task you have is to figure out how to extract those links out of your data structure. But before that, you have to figure out what source
and target
actually are, and I'm not entirely sure what would work best. It can't just be a player object, since in your case it's the same player in both places. So the source
and target
have to encapsulate level
as well, and anyway, you need level
in order to determine the x-positions of each end of the lines. And seq
is a 3rd thing you need in source
and target
, since that's the way to determine the lines' y-positions. My recommendation is to hand-code two such links into a links
array, then figure out how to render them as lines. Once you arrive at a way that works, figure out how to build links
programmatically, from data
. Hope that helps.
Upvotes: 1