d3 line and points on map from csv data - csv

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 }

Related

Legend doesn't appear, but no errors in the code (?)

I want to create a legend with my 3 classes (a, b, and c), but the legend doesn't appear on my localhost webmap. I couldn't find any errors. This is the assignment I am working on: https://github.com/NieneB/webmapping_for_beginners_v2/wiki/D3-step-3
I have tried to move the codes of the legend to another place, but this doesn't seem to work. I have checked the code if there were any ;)} etc. missing.
And these are some of my codes:
<h1>Bigfoot Field Researchers Organizations</h1>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
//Width and height
var w = 1000;
var h = 800;
//Create SVG
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// create a new SVG group element
var layerWorld = svg.append('g');
//Load in GeoJSON data
var data = d3.json("world_simple.json", function(error, data){
if (error) console.log(error);
return data
});
// create a new SVG group element
var layerYeti = svg.append('g');
//Load in GeoJSON data
var yetiData = d3.json("All_BFRO_Reports_points.json", function (error, data) {
if (error) console.log(error);
return data
});
Promise.all([data, yetiData]).then(function (values){
console.log(values[1])
console.log(data)
//Bind data and create one path per GeoJSON feature
layerWorld.selectAll("path")
.data(values[0].features)
.enter()
.append("path")
.attr("class", "countries")
.attr("d", path)
.style("fill", function(d){
return color(d.properties.pop_est)})
.style("stroke", "#5a5959")
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);
layerYeti.selectAll("circle")
.data(values[1].features)
.enter()
.append("circle")
.attr("cx", function(d) {
//[0] returns the first coordinate (x) of the projected value
return projection(d.geometry.coordinates)[0];})
.attr("cy", function(d) {
//[1] returns the second coordinate (y) of the projected value
return projection(d.geometry.coordinates)[1];})
.attr("r", 2)
.style("fill", function(d){
if (d.properties.styleUrl == "#a") {return "red"}
else if (d.properties.styleUrl == "#b") {return "blue"}
else { return "yellow"}
})
.style("opacity", 0.75);
//Create Legend
var legend = d3.select("body")
.append("svg")
.attr("class", "legend")
.attr("width", 200)
.attr("height", 300);
})
var unique_values = d3.map(data.features, function(d){return d.properties.styleUrl;}).keys();
console.log(unique_values);

d3 multiple line graphs

I have two problems I am struggling to understand or solve in my following d3 code (showing simplest version of the problem). Background is that I am trying to plot lines from a csv file (entire contents pasted at the end and full code that runs also below). The first 2 lines (to illustrate the format of csv) are:
date,cust,prod,units
2012-04-01,team1,A,34
Basically there are customers (team1, team2, team3) that purchase units of products (A, B,C,D). I am trying to draw line graphs filtered by product (A, B, C, D). This works individually. However since the units and date ranges vary for each product (A, B, C, D) I am first trying to fix my range to be maximum date range and max units sold. When I do this, the axis look correct but the graph does not get drawn. If I use just the filtered data the graphs works fine. What am I missing? I don't see any JS errors in the console. My next goal is to add and remove the graphs (via checkboxes) without redrawing anything, whats the easiest way to achieve that?
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
#line1 {
fill: none;
stroke: steelblue;
stroke-width: 1px;
}
</style>
<div id="customer">
<input class="custCB" type="checkbox" id="D_CB" name="cust" value="D" enabled onclick="showGraph('D')"> D<br>
<input class="custCB" type="checkbox" id="C_cb" name="cust" value="C" enabled onclick="showGraph('C')"> C<br>
<input class="custCB" type="checkbox" id="B_cb" name="cust" value="B" enabled onclick="showGraph('B')"> B<br>
<input class="custCB" type="checkbox" id="A_cb" name="cust" value="A" enabled onclick="showGraph('A')"> A<br>
</div>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
showGraph("D");
function showGraph(prod_name) {
var mydomain = 5000;
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().domain([mydomain, 0]).range([height, 0]);
//console.log(y);
// define the 1st line
var valueline = d3.line()
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.units);
});
d3.select("svg").remove();
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id", "parentgroup")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Get the data
d3.csv("test2.csv", function (error, tdata) {
if (error) throw error;
data = tdata.filter(e=> e.prod===prod_name);
//_data = data.filter(e=> e.cust==="team2");
//console.log("__data_", _data);
// format the data
console.log("data:", data);
data.forEach(function (d) {
d.date = parseTime(d.date);
d.units = parseInt(d.units);
});
tdata.forEach(function (d) {
d.date = parseTime(d.date);
d.units = parseInt(d.units);
});
x.domain(d3.extent(tdata, function (d) {
console.log(d.date);
return d.date;
}));
//console.log("data", data);
var m = d3.max(tdata, function (d) {
console.log(d.units);
var m = parseInt(d.units);
return m;
});
console.log("Max:", m);
y.domain([0, m]);
console.log("tdata:", tdata);
//console.log("data:", data);
svg.append("path")
.data([data])
.attr("id", "line1")
.attr("d", valueline);
//console.log("DATA", data);
svg.selectAll(".point")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("cx", function (d) {
return x(d.date);
})
.attr("cy", function (d) {
return y(d.units);
})
.attr("r", 4)
.on("mouseover", function (d) {
console.log(d.units)
});
svg.append("g")
.attr("id", "xaxis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.attr("id", "yaxis")
.call(d3.axisLeft(y));
});
}
</script>
</body>
test2.csv
date,cust,prod,units
2012-04-01,team1,A,34
2012-04-02,team1,B,45
2012-04-03,team2,D,67
2012-04-04,team1,A,78
2012-04-05,team3,C,89
2012-04-06,team2,D,99
2012-04-07,team2,A,101
2012-04-08,team3,A,122
2012-04-09,team1,C,134
2012-04-10,team1,C,160
2012-04-11,team2,C,180
2012-04-12,team2,D,210
2012-04-13,team3,D,223
2012-04-14,team1,D,229
2012-04-15,team1,D,241
2012-04-16,team2,D,258
2012-04-17,team2,C,350
2012-04-18,team3,D,305
2012-04-19,team3,B,335
2012-04-20,team2,B,375
2012-04-21,team3,D,345
2012-04-22,team1,A,534
2012-04-23,team1,C,578
2012-04-24,team2,A,590
2012-04-25,team1,B,601
2012-04-26,team3,B,387
2012-04-27,team2,C,613
2012-04-28,team2,D,645
2012-04-29,team3,D,410
2012-04-30,team1,A,612
2012-05-01,team2,A,670
2012-05-02,team3,A,657
2012-05-03,team1,A,690
2012-05-04,team3,A,709
2012-05-05,team2,C,690
2012-05-06,team3,B,740
2012-05-07,team1,A,1000
This snippet is problematic:
data.forEach(function (d) {
d.date = parseTime(d.date);
d.units = parseInt(d.units);
});
tdata.forEach(function (d) {
d.date = parseTime(d.date);
d.units = parseInt(d.units);
});
tdata and data are arrays holding references to the same objects. So the second forEach is then acting on the same objects and the parseTime fails. Just do:
tdata.forEach(function(d) {
d.date = parseTime(d.date);
d.units = parseInt(d.units);
});
var data = tdata.filter(e => e.prod === prod_name);
Here's your code all cleaned up.

Displaying different colors on the map or displaying color on mouse over event

I have made a map in d3 for usa. Now I want to apply different color as soon as i move my mouse on the province area or from first have many colors to the map.
My code is :
<script type="text/javascript">
var w = 1560;
var h = 900;
var color = d3.scale.category10();
var proj = d3.geo.mercator();
var path = d3.geo.path().projection(proj);
var t = proj.translate(); // the projection's default translation
var s = proj.scale() // the projection's default scale
var map = d3.select("#vis").append("svg:svg")
.attr("width", w)
.attr("height", h)
.call(d3.behavior.zoom().on("zoom", redraw));
var axes = map.append("svg:g").attr("id", "axes");
var xAxis = axes.append("svg:line")
.attr("x1", t[0])
.attr("y1", 0)
.attr("x2", t[0])
.attr("y2", h);
var yAxis = axes.append("svg:line")
.attr("x1", 0)
.attr("y1", t[1])
.attr("x2", w)
.attr("y2", t[1]);
var uk = map.append("svg:g").attr("id", "uk");
d3.json("tryusa.json", function (json) {
uk.selectAll("path")
.data(json.features).on("mouseover", function () { d3.select(this).style("fill", "aqua"); })
.enter().append("svg:path")
.attr("d", path)
.on("click", click)
.on("click", function () { window.open("Default3.aspx") })
.append("svg:title")
.text(function (d) { return d.properties.name; })
});
svg.selectAll(".subunit")
.data(topojson.object(uk, uk.objects.subunits).geometries)
.enter().append("path")
.attr("class", function (d) { return "subunit " + d.id; })
;
function redraw() {
var tx = t[0] * d3.event.scale + d3.event.translate[0];
var ty = t[1] * d3.event.scale + d3.event.translate[1];
proj.translate([tx, ty]);
// now we determine the projection's new scale, but there's a problem:
// the map doesn't 'zoom onto the mouse point'
proj.scale(s * d3.event.scale);
// redraw the map
uk.selectAll("path").attr("d", path);
// redraw the x axis
xAxis.attr("x1", tx).attr("x2", tx);
// redraw the y axis
yAxis.attr("y1", ty).attr("y2", ty);
}
</script>
I tried few things like mouse over event, but I am unable to get the result.
I also tried using the inbuilt function for automatically displaying different colors.
But also I am unable to get that.
I am new to d3
Would you please help me.
Color can be placed on mouse over event or from first. Anything would work.
You can just use an argument on the function you use to fill (and for everything else):
.data(json.features)
.on("mouseover", function (d, i) { d3.select(this).style("fill", d.color); })
With color being one of the properties of each field of the json.features array. In this function, the argument d represents the data (here the state), and i represents the index of the state in the json.features array.
An example implementation can be seen for the color of the nodes in this d3 example: http://bl.ocks.org/mbostock/4062045

Drawing voronoi diagram from a csv file with d3.js

I want to draw voronoi diagram using http://mbostock.github.com/d3/ex/voronoi.html from a set of points in a csv file.
I have modified the code:
var w = 1200,
h = 800;
d3.text("ReMeRi_4_2_2_0.csv", function(datasetText) {
var vertices = d3.csv.parseRows(datasetText);
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("class", "BuPu");
svg.selectAll("path")
.data(d3.geom.voronoi(vertices))
.enter().append("path")
.attr("class", function(d, i) { return i ? "q" + (i % 9) + "-9" : null; })
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
svg.selectAll("circle")
.data(vertices.slice(1))
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + d + ")"; })
.attr("r", 2)
text1 = svg.append("svg:text")
.text("control")
.attr("x", 150)
.attr("y", 200)
.style("stroke", "orange")
.style("stroke-width", 0)
.style("font-size", "80%")
.style("fill", "orange");
});
The points are drawn correctly but the polygons of the tesselation are not.
I have tried to add header row and the csv.parse() function without success. At the beginning I was thinking the solution was to iterate over the array to parse to float, but I couldn't do it. If that is the reason why the points are drawn correctly anyway?.
The csv file looks like this:
0.0,0.0
116.78032769067718,0.0
193.02842412648215,78.92418723196411
323.01058809711515,54.15210221124609
378.8576448450217,202.5192012545716
...
I think it is, as you suggest, a problem with the numbers getting parsed as Strings rather than Numbers. Even if that's not what's breaking it, it'd be good to fix. This is one way of doing it (might be a more idiomatic way to do it, dunno):
var vertices = d3.csv.parseRows(
datasetText,
function(pt) { return [parseFloat(pt[0]), parseFloat(pt[1])]; })
);
That might fix your problem.

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.