How to put labels on the edges in the Dendrogram example? - javascript

Given a tree diagram like the Dendrogram example (source), how would one put labels on the edges? The JavaScript code to draw the edges looks like the next lines:
var link = vis.selectAll("path.link")
.data(cluster.links(nodes))
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal);

Mike Bostock, the author of D3, very graciously helped with the following solution. Define a style for g.link; I just copied the style for g.node. Then I replaced the "var link =...." code with the following. The x and y functions place the label in the center of the path.
var linkg = vis.selectAll("g.link")
.data(cluster.links(nodes))
.enter().append("g")
.attr("class", "link");
linkg.append("path")
.attr("class", "link")
.attr("d", diagonal);
linkg.append("text")
.attr("x", function(d) { return (d.source.y + d.target.y) / 2; })
.attr("y", function(d) { return (d.source.x + d.target.x) / 2; })
.attr("text-anchor", "middle")
.text(function(d) {
return "edgeLabel";
});
The text function should ideally provide a label specifically for each edge. I populated an object with the names of my edges while preparing my data, so my text function looks like this:
.text(function(d) {
var key = d.source.name + ":" + d.target.name;
return edgeNames[key];
});

Related

I can not move my nodes, the force diagram does not apply in 3.js

In a previous question a user helped me with a problem that consisted in not knowing how to put images where the circles are. this time my problem is that I can not drag the nodes. This gif illustrates my problem (first I show how the nodes should move normally, then I illustrate my problem.).
Why does this happen?
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke", function(d) {
console.log(d);
return colorLink(d.group);
})
.attr("marker-end", "url(#arrow)");
var node = g.selectAll("g.node")
.data(graph.nodes)
//.filter(function(d){ return d.type=="circle"; })
var NodeCircleEnter= node.enter().append("g")
.attr("class","node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
NodeCircleEnter
.append("circle")
.attr("r", 20)
.attr("fill", function(d) {
return colorNode(d.group);
})
.style("stroke", function(d) {
return colorNode(d.group);
})
// Append images
var images = NodeCircleEnter.append("svg:image")
.attr("xlink:href", function(d) {console.log(d); return d.image;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
This is my full code:
https://plnkr.co/edit/9uH13N3or3G9VTgQlMm9?p=preview
Your event handlers need to be applied to the elements themselves rather than the canvas as a whole.
In this code (only existing in your Plunker project, but not in your question):
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node);
Your variable node is the drawing. You actually want a collection of elements to affect which you get from functions like .enter(). Your variable NodeCircleEnter contains that collection so the particular line should be:
drag_handler(NodeCircleEnter);
This still leaves an issue where dragging on the labels themselves doesn't work. This is because the labels aren't children of the elements you set the handlers on.

Interference between two graphs using 'paths'

I'm building a dashboard with a couple of d3 visualizations, which looks like:
The way I built this is by making a few div's on the page, and call the .js script inside these divs. This worked perfectly fine so far. Unfortunately I run into a problem when calling my .js file with a line graph on the right top div on my page. When I call the graph.js file, this happens:
I'm not entirely sure what's happening, but I think both the visualizations are using "path" elements, and therefore they interfere with eachother. This is the main code for the map of Europe:
//Load in GeoJSON data
d3.json("ne_50m_admin_0_countries_simplified.json", function(json) {
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.filter(function(d) { return d.properties.continent == 'Europe'}) //we only want Europe to be shown ánd loaded.
.append("path")
.attr("class", "border")
.attr("d", path)
.attr("stroke", "black")
.attr("fill", "#ADD8E6")
.attr("countryName", function(d){return d.properties.sovereignt})
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html(d.properties.sovereignt + ', ' + getCountryTempByYear(dataset, d.properties.sovereignt, document.getElementById("selectYear").value))
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.on("click", function(d){
//add/delete country to/from selected countries
updateSelectedCountries(d.properties.sovereignt);
//draw the temperature visualizations again
redrawTemp(getCountryTempByYear(dataset, selectedCountries[0], document.getElementById("selectYear").value), getCountryTempByYear(dataset, selectedCountries[1], document.getElementById("selectYear").value));
//update header from the webpage
d3.select("#header").text("Climate Change: 1840-2013. Currently viewing: " + selectedCountries + ", " + document.getElementById("selectYear").value + '.');
console.log(selectedCountries);
});
});
And this is the code for the line graph:
// set the dimensions and margins of the grap
var array = [[1850, 11.1], [1851, 11.7], [1852, 12.2], [1853, 11.1], [1854, 11.7], [1855, 12.2], [1856, 13.4]]
var array2 = [[1850, 14.1], [1851, 17.7], [1852, 22.2], [1853, 13.1], [1854, 24.7], [1855, 19.2], [1856, 13.4]]
// set the ranges
var x = d3.scaleLinear().range([0, 200]);
var y = d3.scaleLinear().range([200, 0]);
// define the line
var valueline = d3.line()
.x(function(d, i) { return x(array[i][0]); })
.y(function(d, i) { return y(array[i][1]); })
.curve(d3.curveMonotoneX); //smooth line
var valueline2 = d3.line()
.x(function(d, i) { return x(array2[i][0]); })
.y(function(d, i) { return y(array2[i][1]); })
.curve(d3.curveMonotoneX); //smooth line
var svg = d3.select("#div2")
.append("svg")
.attr("width", 200)
.attr("height", 200)
.attr("id", "graph");
x.domain([1850, 1856]);
y.domain([10, 25]);
array.forEach(function(data, i){
svg.append("path")
.data([array])
.attr("class", "line")
.attr("d", valueline);
})
array2.forEach(function(data, i){
svg.append("path")
.data([array2])
.attr("class", "line")
.attr("d", valueline2);
})
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + 200 + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
As I said, I'm not entirely sure it's because of the graphs both using the "path" element, it could be something different too. Now what I'm wondering is: what is interfering with eachother? And how can I prevent this?
I can imagine something like a filter needs to be applied, but I'm not sure how to apply this to the right "path" elements.
Any help is highly appreciated!
When you create a new chart you should create a new variable to associate with it.
In your case, this might be happening due to the reuse of the variable var svg =....
Create one variable for each chart should be enough.

How do I get both mouseover events for overlapping svg elements in d3?

I have some trouble with painting two maps on top of each other : one for the counties of Germany, one for the states. I want for both that the mouseover event triggers. Furthermore in the mouseover function this shall give me the path.
If I code the following, only the mouseoverSubunit() will trigger:
var unitData, subunitData; //json files
var height, width; //integers
var projection = d3.geoAlbers();
var path = d3.geoPath().projection(projection);
var svg = d3.select("#container").append("svg").attr("width", width).attr("height", height);
var origin = svg.append("g");
origin.selectAll(".unit")
.data(unitData)
.enter().append("path")
.attr("class", function(d) { return "unit " + d.name; })
.attr("d", path)
.on("mouseover", mouseoverUnit);
origin.selectAll(".subunit")
.data(subunitData)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.name; })
.attr("d", path)
.on("mouseover", mouseoverSubunit);
mouseoverUnit(){console.log(this)}
mouseoverSubunit(){console.log(this)}
Okay, I succeeded to invoke both functions by changing the DOM but here I will get the output < g class="map"> for this in mouseoverUnit() and not the paths.
origin.append("g").attr("class","map").selectAll(".unit")
.data(unitData)
.enter().append("path")
.attr("class", function(d) { return "unit " + d.name; })
.attr("d", path);
origin.append("g").attr("class","map").selectAll(".subunit")
.data(subunitData)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.name; })
.attr("d", path)
.on("mouseover", mouseoverSubunit);
d3.selectAll(".map").on("mouseover", mouseoverUnit);
Any help? Thanks!

Displaying cities on an Australian map using topojson and d3js

I'm trying to make a map exactly like this example ( http://bost.ocks.org/mike/map/ ) except focused on Australia and New Zealand.
I've followed the instructions but the dots for the places don't render on my map.
This is how I'm generating my data:
ogr2ogr -f GeoJSON -where "adm0_a3 IN ('AUS', 'NZL')" subunits.json ne_10m_admin_0_map_subunits.shp
ogr2ogr -f GeoJSON -where "(iso_a2 = 'AU' OR iso_a2 = 'NZ') AND SCALERANK < 8" places.json ne_10m_populated_places.shp
topojson --id-property su_a3 -p name=NAME -p name -o aus.json subunits.json places.json
Here is the code I've got so far: http://bashsolutions.com.au/australia.html
The map shows up but the dots for the cities are not displaying. What am I doing wrong?
EDIT: So this isn't very clear just with the big long error so here's the actual code:
<script>
var width = 960,
height = 1160;
//var subunits = topojson.object(aus, aus.objects.subunitsAUS);
var projection = d3.geo.mercator()
//.center([0,0])
.center([180,-40])
.scale(400)
//.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection)
.pointRadius(2);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("aus.json", function(error, aus) {
svg.selectAll(".subunit")
.data(topojson.object(aus, aus.objects.subunits).geometries)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", path);
svg.append("path")
.datum(topojson.mesh(aus, aus.objects.subunits, function(a,b) { return a !== b; }))
.attr("d", path)
.attr("class", "subunit-boundary");
svg.append("path")
.datum(topojson.mesh(aus, aus.objects.subunits, function(a,b) { return a == b; }))
.attr("d", path)
.attr("class", "subunit-boundary External");
/* This is the failing bit */
svg.append("path")
.datum(topojson.object(aus, aus.objects.places))
.attr("class", "place")
.attr("d", path);
/* End of failing bit */
/*
svg.selectAll(".place-label")
.data(topojson.object(aus, aus.objects.places).geometries)
.enter().append("text")
.attr("class", "place-label")
.attr("transform", function(d) { return "translate(" + projection(d.coordinates) + ")"; })
.attr("x", function(d) { return d.coordinates[0] > -1 ? 6 : -6; })
.attr("dy", ".35em")
.style("text-anchor", function(d) { return d.coordinates[0] > -1 ? "start" : "end"; })
.text(function(d) { return d.properties.name; });
*/
});
When you plot the outline you need to remove the TKL (Tokelau) data points.
svg.append("path")
.datum(topojson.mesh(aus, aus.objects.subunits, function(a, b) {
return a === b && a.id !=="TKL" }))
.attr("d", path)
.attr("class", "subunit-boundary External");
I'm still researching why this creates the error, but adding that condition to the mesh function filter seems to fix things.
I found away around this issue that solves the problem but it still doesn't explain why it was failing in the first place.
Here is the fix: http://bashsolutions.com.au/australia2.html
This chunk of code replaces the failing bit above:
svg.selectAll(".place")
.data(topojson.object(aus, aus.objects.places).geometries)
.enter().append("circle")
.attr("d", path)
.attr("transform", function(d) { return "translate(" + projection(d.coordinates) + ")"; })
.attr("r", 2)
.attr("class", "place");
So this gets around it by doing something similar to the labels bit (which is commented out above) but drawing a circle instead of text.
But I'm still not sure what was wrong with the above bit considering it's the same as Mike Bostock's example (apart from the data).

Used Arc.centroid to plot circles - but text do no go along with it why?

I've used arc.Centroid to try to plot my circles on the arcs with labels. However, the labels do not stay with it?
force.on("tick", function() {
text.attr("x", function(d) { return d.x + 6; })
.attr("y", function(d) { return d.y + 4; });
node.attr("transform", function(d,i) {
return "translate(" + arc[i].centroid(d) + ")"; })
});
I have attempted to put centroid & arc[i] instead of the x & y. How can I put my circles with text? http://jsfiddle.net/xwZjN/20/
Also say if I were to have more json data, would I be able to restrict the plots only going into each section e.g. each section being a category?
Any help would be great. I think the solution may be similar to this - http://jsfiddle.net/nrabinowitz/GQDUS/
It seems that the force layout is not the right choice for your application. Try to group your symbol and text in a g element and place them at the calculated coordinates. See updated fiddle without force layout: http://jsfiddle.net/xwZjN/26/
var node = svg.selectAll("g.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d,i) {
return "translate(" + arc[i].centroid() + ")";
});
node.append("path")
.attr("d", d3.svg.symbol().type(function(d) { return d.type; }))
// change (0,0) for exact symbol placement
.attr("transform", "translate(0,0)")
.style("fill", "blue" );
node.append("text")
.text(function(d) { return d.Name; })
// shift text in nice position
.attr("x", 10)
.attr("y", 5);

Categories

Resources