brevitylabs
brevitylabs

Reputation: 83

Need d3.js logic to work on JSON data

I am 2 days into d3.js and I have build this draw structure used in tennis tournaments.

enter image description here

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

Answers (1)

meetamit
meetamit

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

Related Questions