Reputation: 149
I'm just getting started with D3.js and it's great, but a steep learning curve. I've got the following code.
var local = d3.local();
var data2 = [
{
id: 1,
name: "reg1",
movements: [
{
id: 1,
text: "mov1",
start: new Date(2017, 1, 1, 17, 0, 0, 0),
end: new Date(2017, 1, 1, 17, 30, 0, 0)
},
{
id: 2,
text: "mov2",
start: new Date(2017, 1, 1, 16, 0, 0, 0),
end: new Date(2017, 1, 1, 16, 30, 0, 0)
},
{
id: 3,
text: "mov3",
start: new Date(2017, 1, 1, 15, 0, 0, 0),
end: new Date(2017, 1, 1, 15, 30, 0, 0)
}
]
},
{
id: 2,
name: "reg2",
movements: [
{
id: 5,
text: "mov4",
start: new Date(2017, 1, 1, 15, 0, 0, 0),
end: new Date(2017, 1, 1, 15, 45, 0, 0)
},
{
id: 6,
text: "mov5",
start: new Date(2017, 1, 1, 18, 0, 0, 0),
end: new Date(2017, 1, 1, 18, 45, 0, 0)
},
{
id: 7,
text: "mov6",
start: new Date(2017, 1, 1, 22, 0, 0, 0),
end: new Date(2017, 1, 1, 23, 45, 0, 0)
}
]
}
];
var svg = d3
.select("body")
.append("svg")
.attr("width", 1500)
.attr("height", 500);
svg
.append("g")
.selectAll("g")
.data(data2)
.enter()
.append("text")
.text(function(d, i, j) {
return d.name;
})
.attr("x", function(d, i, j) {
return 40;
})
.attr("y", function(d, i, j) {
return i* 20 + 40;
})
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.append("g") //removing
.selectAll("text") // these
.data(function(d, i, j) {
local.set(this, i);
return d.movements;
}) //lines
.enter() //text displays normally
.append("text")
.text(function(d, i, j) {
return d.start.getHours();
})
.attr("x", function(d, i, j) {
return i * 300 + 40;
})
.attr("y", function(d, i, j) {
return local.get(this) * 20 + 40;
})
.attr("font-family", "sans-serif")
.attr("font-size", "20px");
What I don't understand is why the hours aren't being shown correctly. This is what's getting generated
<svg width="1500" height="500">
<g>
<text x="40" y="40" font-family="sans-serif" font-size="20px">reg1
<g>
<text x="40" y="40" font-family="sans-serif" font-size="20px">17</text>
<text x="340" y="40" font-family="sans-serif" font-size="20px">16</text>
<text x="640" y="40" font-family="sans-serif" font-size="20px">15</text>
</g>
</text>
<text x="40" y="60" font-family="sans-serif" font-size="20px">reg2
<g>
<text x="40" y="60" font-family="sans-serif" font-size="20px">15</text>
<text x="340" y="60" font-family="sans-serif" font-size="20px">18</text>
<text x="640" y="60" font-family="sans-serif" font-size="20px">22</text>
</g>
</text>
</g>
</svg>
Obviously I want the </text>
to come before the <g>
tag but I can't see what I've done wrong. Any ideas?
Upvotes: 1
Views: 247
Reputation: 38151
The problem is that you are appending a <g>
element to a <text>
element. Appending element A to element B places element A as a child in element B. This is why the text close tag comes where it does.
Why does your code do this?
You need to keep track of what object is returned by each method when chaining, especially when selecting, entering, appending multiple times in a row.
The following code block uses your code, but I've stripped out all the .attr
and .text
methods as these methods just return the same selection they modify. Remember that each line (other than the first) invokes a method of the object returned by the line above it:
svg.append("g") // returns the new <g> in a selection
.selectAll("g") // returns a null selection with the 1st <g> as the parent element
.data(data2)
.enter() // returns a selection of elements to create in the 1st <g> based on the data
.append("text") // returns a selection of text elements in the 1st <g>
.append("g") // returns a selection of <g> elements appended to the <text> elements
.selectAll("text")// returns a null selection of <text> elements in those <g> elements in the <text> elements
.data(function(d, i, j) { })
.enter() // returns a selection of elements to create in those <g> elements in the <text> elements in the 1st <g>
.append("text") // ....
You can see that you have a selection of text elements to which you append child g tags (and to those more text).
Instead, break the chain up. Append parent g elements right away to separate your different datums and store these in a selection. Then append text and child g elements to this selection based on the bound data:
var data2 = [
{
id: 1,
name: "reg1",
movements: [
{
id: 1,
text: "mov1",
start: new Date(2017, 1, 1, 17, 0, 0, 0),
end: new Date(2017, 1, 1, 17, 30, 0, 0)
},
{
id: 2,
text: "mov2",
start: new Date(2017, 1, 1, 16, 0, 0, 0),
end: new Date(2017, 1, 1, 16, 30, 0, 0)
},
{
id: 3,
text: "mov3",
start: new Date(2017, 1, 1, 15, 0, 0, 0),
end: new Date(2017, 1, 1, 15, 30, 0, 0)
}
]
},
{
id: 2,
name: "reg2",
movements: [
{
id: 5,
text: "mov4",
start: new Date(2017, 1, 1, 15, 0, 0, 0),
end: new Date(2017, 1, 1, 15, 45, 0, 0)
},
{
id: 6,
text: "mov5",
start: new Date(2017, 1, 1, 18, 0, 0, 0),
end: new Date(2017, 1, 1, 18, 45, 0, 0)
},
{
id: 7,
text: "mov6",
start: new Date(2017, 1, 1, 22, 0, 0, 0),
end: new Date(2017, 1, 1, 23, 45, 0, 0)
}
]
}
];
var svg = d3
.select("body")
.append("svg")
.attr("width", 1500)
.attr("height", 500);
// a group for each data array item
var g = svg.selectAll("g")
.data(data2)
.enter()
.append("g")
.attr("transform", function(d,i) {
return "translate("+[40,i*20+40]+ ")"
})
// some text for each group array item
g.append("text")
.text(function(d) {
return d.name;
})
.attr("font-family", "sans-serif")
.attr("font-size", "20px");
// a sub group with text for each item in the nested data array
g.append("g")
.selectAll("text")
.data(function(d) {
return d.movements;
})
.enter()
.append("text")
.text(function(d) {
return d.start.getHours();
})
.attr("x", function(d, i, j) {
return i * 150 + 60;
})
.attr("font-family", "sans-serif")
.attr("font-size", "20px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Upvotes: 3