d3js animated pie chart using json - json

I am trying to create an animated pie chart using a JSON file to store the data.
My JSON file is like this:
{"data":[
{"ap": [
{"floorratio": [
{"floor":"Basement", "ratio": 0.20},
{"floor":"Ground", "ratio": 0.20},
{"floor":"First Floor", "ratio": 0.15},
{"floor":"Second Floor", "ratio": 0.20},
{"floor":"Third Floor", "ratio": 0.25}
]}
]},
{"ap": [
{"floorratio": [
{"floor":"Basement", "ratio": 0.10},
{"floor":"Ground", "ratio": 0.30},
{"floor":"First Floor", "ratio": 0.10},
{"floor":"Second Floor", "ratio": 0.15},
{"floor":"Third Floor", "ratio": 0.35}
]}
]}
]}
and my HTML code:
<!doctype html>
<html>
<head>
<title>Pie Chart Test</title>
<script src="d3.min.js"></script>
</head>
<style>
body {
font: 10px sans-serif;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<label><input type="radio" name="dataset" value="0" checked> 1</label>
<label><input type="radio" name="dataset" value="1"> 2</label>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.ratio;});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var nodeSelected;
d3.json("APCategories.json", function (error, data) {
//initialise data
floorRatio = data.data[0].ap[0].floorratio;
var g = svg.selectAll(".arc")
.data(pie(floorRatio))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) {
return color(d.data.floor);
})
.each(function(d) {this._current = d;});
g.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) {
return d.data.floor;
});
//showing initial pie chart code ends here
function changePieChart() {
nodeSelected = this.value;
newFloorRatio = data.data[nodeSelected].ap[0].floorratio;
g = g.data(pie(newFloorRatio));
g.transition().duration(750).attrTween("d", arcTween);
}
function radioSelectedChange() {
nodeSelected = this.value;
changePieChart();
}
d3.selectAll("input")
.on("change", changePieChart);
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
});
</script>
</body>
</html>
So at the moment I am only loading the first set of data because I am calling
floorRatio = data.data[0].ap[0].floorratio
but what I want to be able to do is to have something more along the lines of:
floorRatio = data.data[i].ap[0].floorratio
I have tried to do this in the changePieChart function, however I think the issue lies with the initialising of the data and I am not quite sure of the correct way to do it with JSON files.
Note: My json file is only a small example of a much larger version.
I want to create one pie chart that updates/changes but am unsure how to with JSON data.
What is the correct way of loading all of the data and then utilising it?

You will have to write a function like forEach to browse through each element inside json data. Look at this plnkr link. d will have the object i.e in this example (data.data) and i will hold the index of each object.
d3.json("json/APCategories.json", function (error, data) {
//initialise data
//floorRatio = data.data[0].ap[0].floorratio;
(data.data).forEach(function(d,i) {
console.log(d.ap[i].floorratio);
// d.data = +d.data;
floorRatio = d.ap[i].floorratio;
console.log(floorRatio);
});
EDITED :
I believe you want to draw multiple pie charts for on number of "ap" objects see this link for working demo.
This link will show svg object which will update when selection changes.

Related

d3 line and points on map from csv data

I am trying to plot individual data points and also a line path that runs between these points like this example D3 stop and restart transition along path to allow click through to geo data coordinates that I have been helped with before.
Now I want to use my actual data rather than a test set of coordinates but am running into trouble. I have tried both a geoJson file and also csv for my data. I am using a csv file with lon and lat for the points and was hoping to make the line from that same set of data, ie use one set of data for the points and line.
I can’t get my line to show up in the correct place - it is in top right corner but should be on / through points. I think this is something to do with projection but I am having trouble parsing the data correctly to get a line string as required. I have tried to use the sample here https://bl.ocks.org/alandunning/cfb7dcd7951826b9eacd54f0647f48d3 - but get empty objects??
My question is how to use the csv lon lat with a d3 svg line generator.
This is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Working version 3</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-geo.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<style type="text/css">
circle {
fill: steelblue;
stroke: pink;
stroke-width: 3px;
}
.line{
fill: none;
stroke: red;
stroke-width: 6;
}
</style>
</head>
<body>
<script>
var w = 960,
h = 500;
var projection = d3.geoMercator()
.translate([w/2, h/2])
.scale([w * 0.16]);
var path = d3.geoPath()
.projection(projection);
var duration = 10000;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
/*
var line = d3.line()
.x(function (d) {return projection([d.lon]);})
.y(function (d) {return projection([d.lat]);})
.curve(d3.curveBasis);
var line = d3.line()
.x(function(d){return projection(d[0].lon);})
.y(function(d){return projection(d[0].lat);})
.curve(d3.curveBasis);
/*ok line shows up but in wrong place
var line = d3.line()
.x(function(d) { return (d.lon); })
.y(function(d) { return (d.lat); })
.curve(d3.curveBasis);
*/
var line = d3.line()
.x(function(d) { return (d.lon); })
.y(function(d) { return (d.lat); })
.curve(d3.curveBasis);
//original
/*
var line = d3.line()
.x(function(d){return projection(d)[0];})
.y(function(d){return projection(d)[1];})
.curve(d3.curveBasis);
*/
//
//bring in data
d3.queue()
.defer(d3.json, "data/oceans.json")
.defer(d3.csv, "data/speckCities.csv")
.await(ready);
function ready (error, oceans, data){
if (error) throw error;
//console.log(data[0]);
//console.log(data[0].lon);
//map
svg.selectAll("path")
.data(oceans.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", "#A8B2C3");
var linepath = svg.append("path")
.datum(data)
.attr("d", line)
.attr('class', 'line');
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5)
.style("fill", "yellow")
.style("stroke", "gray")
.style("stroke-width", 0.25)
.style("opacity", 0.75)
.append("title") //Simple tooltip
.text(function(d) {
return d.name ;
});
//
//
/*svg.selectAll(".point")
.data(coordinates)
.enter()
.append("circle")
.attr("r", 7)
.attr("transform", function(d) { return "translate(" + projection(d) + ")"; });
var circle = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + projection(d) + ")");
/*
var pauseValues = {
lastT: 0,
currentT: 0
};
function transition() {
circle.transition()
.duration(duration - (duration * pauseValues.lastT))
.attrTween("transform", translateAlong(linepath.node()))
.on("end", function(){
pauseValues = {
lastT: 0,
currentT: 0
};
transition()
});
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
t += pauseValues.lastT;
var p = path.getPointAtLength(t * l);
pauseValues.currentT = t;
return "translate(" + p.x + "," + p.y + ")";
};
};
}
d3.select('button').on('click',function(d,i){
var self = d3.select(this);
if (self.text() == "Pause"){
self.text('Play');
circle.transition()
.duration(0);
setTimeout(function(){
pauseValues.lastT = pauseValues.currentT;
}, 100);
}else{
self.text('Pause');
transition();
}
});
*/
}
</script>
</body>
</html>
You are not projecting your line:
var linepath = svg.append("path")
.datum(data)
.attr("d", line)
.attr('class', 'line');
In this case your longitude/latitude pairs in your geojson are converted to straight pixel coordinates:
var line = d3.line()
.x(function(d) { return (d.lon); })
.y(function(d) { return (d.lat); })
.curve(d3.curveBasis);
As svg coordinates start at [0,0] in the top left, and your points appear to be around 10 degrees east or so (positive longitude), and 50 degrees north or so (positive latitude), your first point in the line appears 10 pixels from the left and 50 pixels from the top. Also, because svg y values increase as one moves down, but latitude values increase as one moves north (typcally up on a map), your line also appears inverted on the y axis compared to your points.
You could set your line function to use a projection to set the x and y points:
var line = d3.line()
.x(function(d) { return projection([d.lon,d.lat])[0] ; })
.y(function(d) { return projection([d.lon,d.lat])[1]; })
.curve(d3.curveBasis);
You need both latitude and longitude to project a point, so the projection function takes both, and returns both x and y, hence the [0] and [1], this is why your commented out sections don't work
But this is unnecessary, you can pass geojson straight to the path (the same as you do for the world background), that is if your data is available in geojson (though it is not hard to make a geojson on the fly):
var linepath = svg.append("path")
.datum(data) // in geojson form
.attr("d", path) // use your path
.attr('class', 'line');
This is more accurate than a line - the segments between lines in a d3.line are straight or follow a predefined curve, in Cartesian coordinate space. A d3.geoPath follows great circle distance, so the segments between points follow the shortest path on the planet, a more accurate representation, thought at times perhaps, less stylistic.
To create the geojson on the fly, assuming your data looks like: [{lon:number,lat:number},{lon:number,lat:number}] you could use something like:
var points = data.map(function(d) { return [d.lon,d.lat] })
var geojson = { "type": "LineString", "coordinates": points }

Piechart not being rendered when values supplied through d3.json

I am trying to read the values as specified in a JSON and based on those values creating a Piechart in d3.json. But on supplying values manually, the piechart is fully functional! The JSON file is present in the same directory as the .html file. Moreover, the var dataset is being populated with the desired values, which is verified by logging the dataset in the console. And I am not getting any error in the Chrome Browser.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sentiments Score</title>
<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript">
var dataset = [
{ "label": 'Neutral'},
{ "label": 'Positive'},
{ "label": 'Negative' }
];
d3.json("senti_analysis.json", function(data){
for (var i =0; i<dataset.length;i++)
{
if (dataset[i].label == 'Neutral')
{
dataset[i].count = +data.key.senti.neu;
}
else if (dataset[i].label == 'Positive') {
dataset[i].count = +data.key.senti.pos;
}
else
{
dataset[i].count = +data.key.senti.neg;
}
}
});
/*
var dataset = [{ label: 'Neutral', count: 0.45 },
{ label: 'Positive', count: 0.45 },
{ label: 'Negative', count: 0.10 }
];*/
console.log(dataset);
var w = 360;
var h = 360;
var r = Math.min(w, h) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var svg = d3.select('#chart')
.append('svg')
.attr('width', w)
.attr('height', h)
.append('g')
.attr('transform', 'translate(' + (w / 2) + ',' + (h / 2) + ')');
var arc = d3.arc()
.innerRadius(0)
.outerRadius(r);
var pie = d3.pie()
.value(function(d) { return d.count; })
.sort(null);
var path = svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.label);
});
</script>
</body>
The json file I am trying to read is as below:
{
"key": {
"senti": {
"neg": 0.10,
"neu": 0.45,
"pos": 0.45,
"compound": 0.784
},
"post": "I am excited to do Sentiment Analysis. I live in Tempe
and I love learning!"
}
}

d3.tree get min & max value

I want to get the min and max value of the "size" attributes(the json structure for tree includes three attributes: name, size and children)
But I cannot get the right value. Though in the json file, the max value is 100, the min value is 5, the result I get is 94 and 100. And the most strange thing is that when I change the maximum size "100" to "99", the max and min value changes to 99 and 11. What is the problem?
here is the html file:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 120, bottom: 20, left: 120}, width = 1500 - margin.right - margin.left, height = 500 - margin.top - margin.bottom;
var i = 0, duration = 750, root;
var tree = d3.layout.tree() .size([height, width]) .value(function(d) { return d.size;});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("./parse_tree.json", function(error, parse_tree) {
root = parse_tree;
root.x0 = height / 2;
root.y0 = 0;
update(root);
});
function update(source1) {
var originalConsole = console;
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
var t_max = d3.max(nodes, function(d) { return d.size;})
var t_min = d3.min(nodes, function(d) { return d.size;});
originalConsole.log(t_max);
originalConsole.log(t_min);
}
</script>
Here is the json file:
{
"name":"para",
"size":"11",
"children":[
{
"name":"top",
"size":"32",
"children":[
{"name":"S",
"size":"13",
"children": [
{"name":"NP",
"size":"5",
"children":[
{"name":"PRP",
"size":"89",
"children":[{"name":"You","size":"88"}]
}
]
},
{"name":"VP",
"size":"89",
"children": [
{"name":"VBP",
"size":"15",
"children":[{"name":"are", "size":"38"}]
},
{"name":"NP",
"size":"83",
"children": [
{"name":"DT",
"size":"29",
"children":[{"name":"a", "size":"53"}]
},
{"name":"NN",
"size":"50",
"children":[{"name":"boy", "size":"99"}]
}
]
}
]
},
{"name":".",
"size":"94",
"children":[{"name":".", "size": "67"}]
}
]
}
]
}
]
}
The size in the JSON object is a string, thus things turn out "not quite like expected".
So, to fix it, you have to change type to int, which is "luckily" pretty direct in JS:
var t_max = d3.max(nodes, function(d) {
return +d.size;
})
var t_min = d3.min(nodes, function(d) {
return +d.size;
});
... or somewhere else in the script.
Oh, and by the way, try this:
console.log(d3.extent(nodes, function(d) { return +d.size;})); // [5, 99]

d3js json pie chart

I am trying to create a piechart for some data I have in a JSON file, however I keep getting an error and I am not sure why.
My JSON file is like this:
{"data":[
{"ap": [
{"floorratio": [
{"floor":"Basement", "ratio": 0.20},
{"floor":"Ground", "ratio": 0.20},
{"floor":"First Floor", "ratio": 0.15},
{"floor":"Second Floor", "ratio": 0.20},
{"floor":"Third Floor", "ratio": 0.25}
]}
]},
{"ap": [
{"floorratio": [
{"floor":"Basement", "ratio": 0.10},
{"floor":"Ground", "ratio": 0.30},
{"floor":"First Floor", "ratio": 0.10},
{"floor":"Second Floor", "ratio": 0.15},
{"floor":"Third Floor", "ratio": 0.35}
]}
]}
]}
and my HTML code is:
<!doctype html>
<html>
<head>
<title>Pie Chart Test</title>
<script src="d3.min.js"></script>
</head>
<style>
body {
font: 10px sans-serif;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.data[0].ap[0].floorratio[0].ratio;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.json("APCategories.json", function (data) {
var g = svg.selectAll(".arc")
.data(pie(function (d) { return d.data[0].ap[0].floorratio[0]}))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) {
return color[d.data[0].ap[0].floorratio[0].floor];
});
g.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) {
return d.data[0].ap[0].floorratio[0].floor;
});
});
</script>
</body>
</html>
I keep getting the error when I inspect the element that i.map is not a function. Am I reading the data into the code incorrectly? Any help would be appreciated.
edit: I am currently only trying to load the first ap, the json file will be much bigger and I will be creating a pie chart that changes so that it represents each ap.floorratio.
So for now, only the data from the first floorratio array needs to go into the pie chart.
Here's a working version.
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
// defines wedge size
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.ratio; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.json("APCategories.json", function(error, data) {
node = data.data[0].ap[0].floorratio; // <------ here
var g = svg.selectAll(".arc")
.data(pie(node))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.floor); });
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) { return d.data.floor; });
});
The [0] after floorratio wasn't necessary.
http://bl.ocks.org/steveharoz/0638d230c133da1de385

d3 Node Labeling

I've been using the sample code from this d3 project to learn how to display d3 graphs and I can't seem to get text to show up in the middle of the circles (similar to this example and this example). I've looked at other examples and have tried adding
node.append("title").text("Node Name To Display")
and
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em").text("Node Name To Display")
right after node is defined but the only results I see is "Node Name To Display" is showing up when I hover over each node. It's not showing up as text inside the circle. Do I have to write my own svg text object and determine the coordinates of that it needs to be placed at based on the coordinates of radius of the circle? From the other two examples, it would seem like d3 already takes cares of this somehow. I just don't know the right attribute to call/set.
There are lots of examples showing how to add labels to graph and tree visualizations, but I'd probably start with this one as the simplest:
http://bl.ocks.org/950642
You haven’t posted a link to your code, but I'm guessing that node refers to a selection of SVG circle elements. You can’t add text elements to circle elements because circle elements are not containers; adding a text element to a circle will be ignored.
Typically you use a G element to group a circle element (or an image element, as above) and a text element for each node. The resulting structure looks like this:
<g class="node" transform="translate(130,492)">
<circle r="4.5"/>
<text dx="12" dy=".35em">Gavroche</text>
</g>
Use a data-join to create the G elements for each node, and then use selection.append to add a circle and a text element for each. Something like this:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
One downside of this approach is that you may want the labels to be drawn on top of the circles. Since SVG does not yet support z-index, elements are drawn in document order; so, the above approach causes a label to be drawn above its circle, but it may be drawn under other circles. You can fix this by using two data-joins and creating separate groups for circles and labels, like so:
<g class="nodes">
<circle transform="translate(130,492)" r="4.5"/>
<circle transform="translate(110,249)" r="4.5"/>
…
</g>
<g class="labels">
<text transform="translate(130,492)" dx="12" dy=".35em">Gavroche</text>
<text transform="translate(110,249)" dx="12" dy=".35em">Valjean</text>
…
</g>
And the corresponding JavaScript:
var circle = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 4.5)
.call(force.drag);
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
This technique is used in the Mobile Patent Suits example (with an additional text element used to create a white shadow).
I found this guide very useful in trying to accomplish something similar :
https://www.dashingd3js.com/svg-text-element
Based on above link this code will generate circle labels :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes": [
{name: "1", "group": 1, x: 100, y: 90, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 50, r: 15, connected : "1"},
{name: "3", "group": 2, x: 200, y: 130, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 2000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.nodes)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function (d, i) {
return d.x
})
.attr("y1", function (d) {
return d.y
})
.attr("x2", function (d) {
return findAttribute(d.connected).x
})
.attr("y2", function (d) {
return findAttribute(d.connected).y
})
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function (d, i) {
return d.r
})
.attr("cx", function (d, i) {
return d.x
})
.attr("cy", function (d, i) {
return d.y
});
var text = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text( function (d) { return d.name })
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
});
function findAttribute(name) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
if (graph.nodes[i].name === name)
return graph.nodes[i]; // Return as soon as the object is found
}
return null; // The object was not found
}
</script>
</body>
</html>
If you want to grow the nodes to fit large labels, you can use the getBBox property of an SVG text node after you've drawn it. Here's how I did it, for a list of nodes with fixed coordinates, and two possible shapes:
nodes.forEach(function(v) {
var nd;
var cx = v.coord[0];
var cy = v.coord[1];
switch (v.shape) {
case "circle":
nd = svg.append("circle");
break;
case "rectangle":
nd = svg.append("rect");
break;
}
var w = 10;
var h = 10;
if (v.label != "") {
var lText = svg.append("text");
lText.attr("x", cx)
.attr("y", cy + 5)
.attr("class", "labelText")
.text(v.label);
var bbox = lText.node().getBBox();
w = Math.max(w,bbox.width);
h = Math.max(h,bbox.height);
}
var pad = 4;
switch (v.shape) {
case "circle":
nd.attr("cx", cx)
.attr("cy", cy)
.attr("r", Math.sqrt(w*w + h*h)/2 + pad);
break;
case "rectangle":
nd.attr("x", cx - w/2 - pad)
.attr("y", cy - h/2 - pad)
.attr("width", w + 2*pad)
.attr("height", h + 2*pad);
break;
}
});
Note that the shape is added, the text is added, then the shape is positioned, in order to get the text to show on top.