d3.js path dom element - javascript

I am creating a streamgraph, having used the example code from http://bl.ocks.org/mbostock/4060954 as a template.
I draw the streamgraph:
var svg = d3.select("#graph_area").append("svg")
.attr("width", width)
.attr("height", height)
....
svg.selectAll("path")
.data(layers)
.enter().append("path")
.attr("d", function (d) {return area(d.layer);})
Now it seems that I am not allowed to choose a different name from "path" for the DOM element here. If I do, then streamgraph no longer plots. Why is that?
Then I want to add a legend with bullets, but they don't plot. The elements show up in my web inspector (firebug), but their graphical representation is just not there. I figured it might be a similar problem with the DOM element name, but I don't actually know. Here is the code for my bullets:
//draw all the legend bullets and effects
svg.selectAll("bullets")
.data(layers)
.enter().append("bullets")
.attr("cx", 200)
.attr("cy", function(d, i) { return 20 + i*10; })
.style("fill", function(d) { return d.color; })
.attr("r", 5)
I briefly looked at API's for paths here and here, but I didn't find my answers there.

The element names you can use are defined in the SVG specification. This is a specific set (e.g. path, circle) -- using anything else will work in terms of appending the element to the DOM, but the browser won't know how to interpret and render it.
It's the same way in HTML -- there's a specific set of defined element names that browsers know how to render.

Related

D3 updating DOM, but nothing is showing

I am building a force-directed graph, and as far as I can tell, I have written the code correctly. The DOM looks exactly right after this code runs. And yet, nothing is displayed.
var type_node_list = typeNodes(data);
shuffle(type_node_list);
initializePosition(type_node_list);
var links = typeLinks(type_node_list);
var svg = d3.select('#root');
var link = svg
.append('g')
.attr('id', 'links')
.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('class', 'link');
var type_nodes = svg
.append('g')
.attr('id', 'nodes')
.selectAll('.node')
.data(type_node_list)
.enter()
.append(createTypeNode);
function updateTypeNodeLocations() {
link
.attr("x1", function(d){return d.source.x;})
.attr("y1", function(d){return d.source.y;})
.attr("x2", function(d){return d.target.x;})
.attr("y2", function(d){return d.target.y;});
type_nodes
.attr('x', nodeX)
.attr('y', nodeY);
}
updateTypeNodeLocations();
/*
var position_the_types = d3.forceSimulation()
.nodes( type_node_list )
.force("charge",d3.forceManyBody().strength(-10))
;
position_the_types.on('tick',updateTypeNodeLocations());
*/
The simulation portion is commented out because I'm trying to get the first part working. When I uncomment it, it only calls the 'tick' event once, even though the processing is clearly not complete. And there is nothing in the JavaScript console to explain it.
See http://jsfiddle.net/jarrowwx/gof5knaj/36/ for the full code.
I had things working this morning, and something changed and now nothing I do seems to work. I checked the D3 github, and the last commit appears to have been 11 days ago, so it's not likely caused by a change to the library.
Has anybody experienced something like this before? Any pointers on what I'm doing wrong? Have I uncovered a D3 bug?
The problem lie in my function createTypeNode.
I created the image element via: document.createElement('image'). That doesn't work. To create an image via JavaScript, one must use Namespaces.
See: Programmatically creating an SVG image element with javascript

Colour links of a d3 network graph according to a data variable

I would like to colour different links of a network graph according to a value within a data vector (where each element of the vector corresponds to a particular link). I have tried something of the form:
var data = [1,1,1,2];
var link = svg.selectAll(".link")
.data(force.links())
.enter()
.append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)")
.on("mouseover", linkmouseover)
.on("mouseout", linkmouseout);
link.data(force.links()).enter().append(".link")
.style("stroke", function(d,i){
return ( (data[i]==2) ?
"red" : "black" )
});
However, this does not seem to work. If it's important I also have an svg styling:
.link {
stroke: #ff0000;
opacity: 0.6;
stroke-width: 1.5px;
}
Does anyone know why this isn't working? I have the Plunker version here.
The way you are setting up your links looks quite odd. Especially the following line doesn't seem to make any sense:
link.data(force.links()).enter().append(".link")
The statement directly preceding this line successfully inserts the lines for the links handling the enter selection. On the suspicious line you are then binding the same data again to the links' selection. Because you did not specify any key function to access your data this will compute a join based on the data's index which will, because you handled this same data before, yield an empty enter selection. Therefore, the method setting style("stroke",...) will never execute the callback. Furthermore, the call to .append(".link") is wrong, because .link is not a valid SVG element which could be appended using this method.
You somehow seem to have messed up this part of your code by, maybe, some copy&paste action. If you get rid of the above mentioned line and delete the directly preceding semicolon your code will work as expected:
var link = svg.selectAll(".link")
.data(force.links())
.enter()
.append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)")
.on("mouseover", linkmouseover)
.on("mouseout", linkmouseout)
.style("stroke", function(d,i){
return ( (data[i]==2) ? "red" : "black" );
});
Have a look at the updated code for a working example.

CSS-Sprites + D3.js

I'm trying to append HTML elements within a <circle> and then use CSS-Sprite to fill them, but so far I can't see them!
Here's what im doing:
//Create the node in order to use the Force Layout
var node = svg.selectAll(".container")
.data(data)
.append("circle")
.attr("class", "dot")
.attr("r", radius)
.attr("cx", function(d) { return x(d[xVar]); }) //xAxis position
.attr("cy", function(d) { return y(d[yVar]); }) //yAxis position
.append("xhtml:i")
.attr('class', function(d) {
return 'sprite '+ d['css-code'];
});
And my css:
.sprite{
position: absolute;
background:url(sprite.png) no-repeat;
}
.ad{background-position:0 -704px;}
.ae{background-position:0 -736px;}
.etc
They are being generated and I can see them on my browser inspector with the correct css atributes but they just won't appear.
How do I use CSS-Sprites with D3's Force Layout?
You're inserting the <i> elements directly within the SVG <circle> elements, which is not legal SVG markup. There are at least a couple of alternatives that you could use.
Make the nodes <g> elements which would contain both the <circle> elements and a different element for the sprite.
Insert the HTML sprites directly in the HTML container for the graph and absolutely position them to correspond with the node positions.

Display icons/image instead of circle/path on map made with d3.js

I can successfully display some points on a openlayers basemap using d3.js however I want to actually display icons (at the moment maki png icons) instead of an svg point.
is it possible to load a png/jpg/svg image to a map using d3.js?
So far I can load the icon directly onto the svg but all the markers locate on the same spot so I think I'm having a problem converting the coordinates properly.
var feature = svg.selectAll("path")
.data(amenities.features)
.enter()
.append("svg:image")
.attr("xlink:href", "maki/renders/post-18#2x.png")
.attr("x", function(d, i) {return amenities.features[i].geometry.coordinates[0]})
.attr("y", function(d, i) {return amenities.features[i].geometry.coordinates[1]})
.attr("width", "20")
.attr("height", "20")
.attr("class", "amenity");
Previously I have been able to create an svg with image background inside it using a 'pattern' to show the image so that is also a possibility but I couldn't translate the code to use it with the geographic aspect of d3.
I know I'm writing the icons at the moment to the 'svg' div, so they don't transform correctly when I zoom in and out. I'm aiming to write the images to the 'g' div, as in have code like:
var feature = g.selectAll("path")
but when I use this line, the elements appear on the document but the icons don't actually render on the map.
Thanks in advance.
There a few issues here. First, I'm not sure you fully grasp how d3 selections works, as indicated by the fact that you are binding amenities.features to your selection and then accessing it for the x and y attributes via an index. See How Selections Work for more details on this. In addition, you need to translate the geographic coordinates of the features to screen coordinates by passing them through your geographic projection function. This should get you close:
// more projections: https://github.com/d3/d3-geo-projection/
var projection = d3.geoAlbers();
var amenities = svg.selectAll('.amenities')
.data(amenities.features);
amenities.enter().append('image');
amenities
.attr("class", "amenities")
.attr("xlink:href", "maki/renders/post-18#2x.png")
// The data is already bound so use it instead of the index. Als,
// you need to translate geo coordinates to screen coordinates by
// passing them through your projection function.
.attr("x", function(d,i) {return projection(d.geometry.coordinates)[0];})
.attr("y", function(d,i) {return projection(d.geometry.coordinates)[1];})
.attr("width", "20")
.attr("height", "20")
I don't think I appropriately used groups but I think the key is having the transform then translate thing in there.
So my example is http://bl.ocks.org/mpmckenna8/b87df1c44243aa1575cb.
But because I didn't use groups properly I don't know that the icons would handle zooming like you seem to want. In my example I just append the images to the circles I've created.
.attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr('opacity',.3)
.attr('fill', '#fad959')

what is the point of calling selectAll when there are no existing nodes yet on D3.js

when i do this :
var link = svg.selectAll('.link')
.data(links)
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal)
There is no node with the .link class. So selectAll returns en empty selection. But i've found that, when you call this for the first time, you can selectAll('whaterverYouWant')
That is because D3 doesn't matter about what you select, as you provide the tag name and the classes later .append('path'), .attr(class ...).
And, if you want to select elements that already exist, i read in the doc that .enter returns a placeholder selection. But if it returns a selection of placeholders (anonymous tags with .link class ?), there is no point to append a path to a path.
When i call .append, it does what i want, i.e. append a path to svg. But i don't understand the logic behind that. (I'm glad it works though, because d3 is powerful)
So, ok i selectAll('anything') and append what i want, regardless of what i selected. But if i try this:
d3.select('#savestring-debug')
.selectAll('div')
.data(debugobjs)
.enter().append('span')
.attr('style', function(d) { return 'background:#'+d.color })
.text(function(d) { return d.aff });
This would create placeholders for divs, but i append spans. Actually spans are created but i'm still looking for my divs ;)
So, what is the principle behind selectAll >> data >> enter >> append ?
thanks
The principle behind selectAll > data > enter > append is explained pretty well by
Mike Bostock here: http://bost.ocks.org/mike/join/ where he explains the concept of the data-join. I can't speak with any authority on the right way to use selectAll, but the way I use it is to select all of the elements I am going to be modifying-appending-removing within the part of the SVG that I need to modify.
So if I'm working with "rects" in a certain area, I'll do something like this:
var svg = d3.select('#graphID')
.append("svg")
.attr("width", 300)
.attr("height", 500);
var graphGroup = self.svg.append("g");
//...Inside a render function
//just want all the "rect" elements in graphGroup
var rects = graphGroup.selectAll("rect")
.data(dataset);
//depending on dataset new rects will need to be appendend
rects.enter()
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 0)
.attr("height", 0)
//all rects are transitioned to new co-ordinates
rects.transition().duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d){
return yScale(d);
})
//rects that have no data associated with them are removed
rects.exit()
.transition()
.duration(500)
.attr("x", -xScale.rangeBand())
.remove();
With the idea that I could have other rects in the SVG that do not belong to graphGroup. I just selectAll the rects in a certain area and work on them when needed.
This is a great question and a slightly odd property of D3. If you look carefully how anything is done in D3 you'll notice that everything is added by appending to what is previously created. So the logic behind having the svg.selectAll('whatever class of stuff you're going to add') is that you are kinda making a placeholder for where whatever you are about append to go. It's like the svg is a wall and you're hanging hooks on the upper ridge for you to THEN hang your paintings from. If you don't have the selectAll, I just tried this, you will still append whatever you were gonna make to the page, but it won't be appended to the svg.
The data-->enter-->append is basically saying for each element in the larger data file that you are passing into the data function, make a new element, and append this element to my selection with such and such properties (set when you use the .attr).

Categories

Resources