I have structure with data and shapes definition:
var data = [
{
"id": "first",
"shapes": [
{
"shape": "polygon",
"points": [["8","64"],["8","356"],["98","356"],["98","64"]]
}
]
},
{
"id": "second",
"shapes": [
{
"shape": "ellipse",
"cx": "63", "cy": "306", "rx": "27","ry": "18"
}, {
"shape": "polygon",
"points": [["174","262"],["171","252"],["167","262"]]
}
]
}
]; // in the data may be stored any SVG shape
I would like to create SVG:
<svg width="218" height="400">
<g transform="translate(0,400) scale(1,-1)">
<g>
<polygon points="8,64 8,356 98,356 98,64"/>
</g>
<g>
<ellipse cx="63" cy="306" rx="27" ry="18"/>
<polygon points="174,262 171,252 167,262"/>
</g>
</g>
</svg>
For each data
element I'm appending <g>
:
var groups = svg.selectAll("g").data(data, function (d) {
return d.id;
});
groups.enter().append("g");
Now I'm binding data for group shapes:
var shapes = groups.selectAll(".shape").data(function (d) {
return d.shapes;
}, function(d,i){return [d.shape,i].join('-');});
So far it was as expected. Now I want to for each entering DOM node dispatch drawing function with proper shape but apparently shapes.enter().each()
is not working in this context (not defined). I suppose it works rather on each DOM node than on each data to be bound. And this is working:
shapes.enter().append("g").each(function(draw, i) {
var shape = draw.shape;
d3.select(this).call(drawer[shape]);
});
But painful side-effect is that SVG has two levels of <g>
:
<svg width="218" height="400">
<g transform="translate(0,400) scale(1,-1)">
<g>
<g>
<polygon points="8,64 8,356 98,356 98,64"/>
</g>
</g>
...
</svg>
How to get rid of that? How to build data based shapes correctly?
Two ways to add basic shapes based on data provided by AmeliaBR are ok but slightly outside d3.js API. After long consideration I've decided to add answer my own question.
Inspiration for searching other solution was comment of Lars Kotthoff under question suggesting using paths instead of primitive SVG shapes. In this approach instead of adding second level of
<g>
there should be added<path>
:Original
drawer
object generating basic shapes would return value for attributed
of<path>
.Working example you can check at http://fiddle.jshell.net/daKkJ/4/
Tricky question. Shame that you can't call each on an enter() selection.
Neither can you use a(See comments)function(d){}
in an append statement.But I got it working, using a Javascript
forEach()
call on the data array itself. It calls your function with the array entry, index, and array itself as parameters, and you can specify athis
context -- I just passed in the desired parent element as a selection.The fabulous fiddle: http://fiddle.jshell.net/994XM/1/
(simpler than your case, but should be able to adapt easily)
It's a bit confusing since the D3 code inside the
forEach
is all per element, with thed
andi
values already available for you, so you don't need any internal functions in your D3 method calls. But once you figure that out, it all works.I found this question through Google, and the jsfiddles linked to above have ceased to work. You now have to call document.createElementNS, giving it the SVG namespace, otherwise the shapes don't show up.
I created an updated fiddle. I also cleaned out a lot of the unnecessary bits, so it should be easier to see what's going on.