drawing on an externally loaded svg graphic in D3 - animation

I have loaded an external graphic from an svg file and I want to experiment drawing on it but cannot figure out how. my simple d3 code is here:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
</head>
<body>
<script type="text/javascript">
d3.xml("brussels.svg", "image/svg+xml", function(xml) {
document.body.appendChild(xml.documentElement);
});
svg.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 50)
.attr("cy", 50)
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", "white");});
</script>
</body>
</html>
I am sure it is something simple but I am not sure how to create the actual circle.
Thanks!

The function:
d3.xml("brussels.svg", "image/svg+xml", function(xml) {
document.body.appendChild(xml.documentElement);
});
executes asynchronously. Hence, the code following it is executed before the callback is executed. The second problem is that you need to define the svg variable before you can operate on it.
Something like the following should work:
d3.xml("brussels.svg", "image/svg+xml", function(xml) {
document.body.appendChild(xml.documentElement);
var svg = d3.select('svg');
svg.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 50)
.attr("cy", 50)
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", "white");});
});

Related

Why can't I get a d3.js map to render using topojson?

I'm having some difficulty getting d3 to render a geoAlbersUsa projection from topoJson data. I'm showing a blank screen, but no errors returned. The geoJson data seems to be coming through fine, but it's not rendering the path for some reason. Any help would be greatly appreciated!
Here's the relevant code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://unpkg.com/topojson-client#3"></script>
</head>
<body>
<script>
const width = 1000;
const height = 600;
const projection = d3.geoAlbersUsa()
.translate([width / 2, height / 2])
.scale(800);
const path = d3.geoPath(projection);
const svg = d3.select("body")
.append("svg")
.attr("height", height)
.attr("width", width)
.style("display", "block")
.style("margin", "auto");
d3.json("https://d3js.org/us-10m.v1.json").then(data => {
svg.selectAll(".states")
.data(topojson.feature(data, data.objects.states).features)
.attr("d", path)
.style("fill", "none")
.attr("class", "states")
.style("stroke", "black")
.style("stroke-width", "2px")
});
</script>
</body>
</html>
You need to join with an element:
.data(topojson.feature(data, data.objects.states).features)
.join("path") // here
.attr("d", path)
.style("fill", "none")
.attr("class", "states")
.style("stroke", "black")
.style("stroke-width", "2px")

Hide and show svg element with transition

How can I hide and show an svg element using transition?
I try this code:
<div id="bubble"></div>
<div id="buttonHide"><button>Hide</button></div>
<div id="buttonShow"><button>Show</button></div>
d3.select("#bubble")
.append("svg")
.append("g")
.append("circle")
.attr("class", "bubble")
.attr("transform", "translate(100, 100)")
.attr("r", 50)
.attr("fill", "black");
d3.select("#buttonHide").on("click", function() {
d3.select(".bubble").transition().attr('visibility', 'hidden').duration(1000);
});
d3.select("#buttonShow").on("click", function() {
d3.select(".bubble").transition().attr('visibility', 'visible').duration(1000);
});
d3.select("#bubble")
.append("svg")
.append("g")
.append("circle")
.attr("class", "bubble")
.attr("transform", "translate(100, 100)")
.attr("r", 50)
.attr("fill", "black");
d3.select("#buttonHide").on("click", function() {
d3.select(".bubble").transition().duration(1000).attr('visibility', 'hidden');
});
d3.select("#buttonShow").on("click", function() {
d3.select(".bubble").transition().duration(1000).attr('visibility', 'visible');
});
<html lang='en'>
<head>
<meta charset='utf-8'>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
</head>
<body>
<div id="bubble"></div>
<div id="buttonHide"><button>Hide</button></div>
<div id="buttonShow"><button>Show</button></div>
</body>
</html>
But transition doesn't work.
You could play with the opacity style instead of switching the visibility attribute:
d3.select("#bubble")
.append("svg")
.append("g")
.append("circle")
.attr("class", "bubble")
.attr("transform", "translate(100, 100)")
.attr("r", 50)
.attr("fill", "black")
.style("opacity", 1);
d3.select("#buttonHide").on("click", function() {
d3.select(".bubble").transition().duration(1000).style("opacity", 0);
});
d3.select("#buttonShow").on("click", function() {
d3.select(".bubble").transition().duration(1000).style("opacity", 1);
});
<html lang='en'>
<head>
<meta charset='utf-8'>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
</head>
<body>
<div id="bubble"></div>
<div id="buttonHide"><button>Hide</button></div>
<div id="buttonShow"><button>Show</button></div>
</body>
</html>
This way, the transition will be applied on the opacity, which will transition from 0 to 1 (unhide) or from 1 to 0 (hide).
To set the opacity:
d3.select(".bubble").transition().duration(1000).style("opacity", 1);
The transition is in fact expecting a change which can be interpolated between two values. This is the case for the opacity (all the way between 0 and 1), whereas the visibility attribute is a simple switch (on/off).

Text tag not displaying properly in D3 SVG

I have the following code to generate a simple graph in D3...
var width = 800, height = 800;
// force layout setup
var force = d3.layout.force()
.charge(-200).linkDistance(30).size([width, height]);
// setup svg div
var svg = d3.select("#graph").append("svg")
.attr("width", "100%").attr("height", "100%")
.attr("pointer-events", "all");
// load graph (nodes,links) json from /graph endpoint
d3.json("/graph", function(error, graph) {
if (error) return;
force.nodes(graph.nodes).links(graph.links).start();
// render relationships as lines
var link = svg.selectAll(".link")
.data(graph.links).enter()
.append("line")
.attr("class", "link")
.attr("stroke", "black");
// render nodes as circles, css-class from label
var node = svg.selectAll(".node")
.data(graph.nodes).enter()
.append("circle")
.attr("r", 10)
.call(force.drag);
// html title attribute for title node-attribute
node.append("title")
.text(function (d) { return d.title; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "black");
// force feed algo ticks for coordinate computation
force.on("tick", function() {
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; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
Everything looks great except I cannot see the title. If I look at the DOM I see the following....
<circle r="10" cx="384.5368115283669" cy="390.4516626058579"><title font-family="sans-serif" font-size="20px" fill="black">My NAme</title></circle>
However no matter what I try I cannot seem to see the title. What am I missing here?
This is nothing to blame on d3. As per the SVG 1.1 spec the title is a description string which will not be rendered when the graphic gets rendered. Most browsers, however, will display the title as a tooltip when the mouse pointer is over the element.
I have set up an updated snippet based on your code. Placing the mouse over the red circle will bring up the tooltip "My Name".
<svg width="200" height="200">
<circle r="10" cx="38" cy="39" fill="red">
<title>My Name</title>
</circle>
</svg>
To add text to your svg which will get rendered, use the <text> element instead.
<svg width="200" height="200">
<text x="50" y="100">My Text</text>
</svg>

Draw a line between points

Another trivial question. I am trying to draw a line between points, here, starting from lineData[0] to lineData[1], and so on. I am getting a very funny looking area rather than a line! Can you please help me.
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title> Icon </title>
<script type="text/javascript" src="lib/d3/d3.v3.js"></script>
</head>
<body>
<p id="drawing">
<script>
// data is not same as here, just to explain the requirement created it.
var lineData = [{"x": 55, "y": 65},
{"x": 63, "y": 57},
{"x": 157, "y": 57},
{"x": 165, "y": 65}];
var svg = d3.select("#drawing")
.append("svg")
.attr("height", 200)
.attr("width", 200)
.attr("transform", "translate(20, 20)");
var lineFunction = d3.svg.line()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
})
.interpolate("linear");
svg.append("path")
.attr("d", lineFunction(lineData))
.style("stroke-width", 0.5)
.style("stroke", "rgb(6,120,155)")
.on("mouseover", function () {
d3.select(this)
.style("stroke", "orange");
})
.on("mouseout", function () {
d3.select(this)
.style("stroke", "rgb(6,120,155)");
});
</script>
</body>
</html>
Your issue is that you're drawing a <path>, and you haven't set the path's fill. By default it's black, so you're drawing an object instead. Try removing the fill after appending your <path>:
svg.append("path")
.attr("d", lineFunction(lineData))
.style("stroke-width", 0.5)
.style("stroke", "rgb(6,120,155)")
.style("fill", "none") // <------ add this line
and you get this:

Create a Graduated Symbol Map using D3

I'm trying to create a graduated symbol map and am struggling to find a way to make this happen. I can create pie charts and I can create a symbol map, but how to place pie charts at specific coordinates on a map?
I've successfully placed proportional symbols at the proper coordinates, but I can't figure out how to replace the symbols with pie charts. Every attempt leaves me with an empty map.
I've tried to merge Mike Bostock's Pie Multiples example with his Symbol Map example but have instead only managed to expose my lack of understanding of d3's data and event functions.
Index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Graduated Symbol Map</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://d3js.org/topojson.v1.min.js"></script>
<script type="text/javascript" src="http://d3js.org/queue.v1.min.js"></script>
<style type="text/css">
body {
text-align: center;
}
</style>
</head>
<body>
<script type="text/javascript">
var width = 400,
height = 500;
var radius = d3.scale.sqrt()
.domain([0, 5e5])
.range([0, 40]);
// Define map projection
var projection = d3.geo.transverseMercator()
.rotate([72.57, -44.20])
.translate([175,190])
.scale([12000]);
// Define path generator
var path = d3.geo.path()
.projection(projection);
// Create SVG Element
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
queue()
.defer(d3.json, "vermont.json")
.defer(d3.json, "fed.json")
.await(ready)
function ready(error, vt, centroid) {
svg.append("path")
.attr("class", "towns")
.datum(topojson.feature(vt, vt.objects.vt_towns))
.attr("d", path)
.style("stroke", "#ddd")
.style("stroke-width", "1px")
.style("fill", "#ccc");
svg.append("path")
.datum(topojson.feature(vt, vt.objects.lake))
.attr("d", path)
.style("stroke", "#89b6ef")
.style("stroke-width", "1px")
.style("fill", "#b6d2f5");
svg.selectAll(".symbol")
.data(centroid.features.sort(function(a,b) {
return b.properties.dollars - a.properties.dollars; }))
.enter().append("path")
.attr("class", "symbol")
.attr("d", path.pointRadius(function(d) {
return radius(d.properties.dollars); })
)
.style("fill", "#509e2f")
.style("stroke", "#ddd")
.style("fill-opacity", 0.7);
}
</script>
</body>
</html>
fed.json (there are 14 points, all with the same format)
'dollars' are the total dollars spent by the four organizations, the size of the pie chart should relate to this value.
{
"type": "Feature",
"id": "53",
"geometry": {
"type": "Point",
"coordinates": [-73.1349605, 43.0278745]
},
"properties": {
"name": "Bennington County",
"dollars": 79730,
"unit": "county",
"ECP": 49608,
"LIP": 3451,
"NAP": 0,
"SURE": 26671
}
},
vermont.json
Large file, map is not the issue.
References I've used
http://mbostock.github.io/protovis/ex/symbol.html
http://bl.ocks.org/mbostock/1305111
http://bl.ocks.org/mbostock/4342045
Here's my solution, using #LarsKotthoff's answer from this question to solve the projection issue.
I've scaled the pie charts in a rather hackish way.
index.html
Below is just the ready function. Everything else has remained unchanged.
function ready(error, vt, centroid) {
svg.append("path")
.attr("class", "towns")
.datum(topojson.feature(vt, vt.objects.vt_towns))
.attr("d", path)
.style("stroke", "#ddd")
.style("stroke-width", "1px")
.style("fill", "#ccc");
svg.append("path")
.datum(topojson.feature(vt, vt.objects.lake))
.attr("d", path)
.style("stroke", "#89b6ef")
.style("stroke-width", "1px")
.style("fill", "#b6d2f5");
var pieArray = [],
pieMeta = [];
function pieData() {
for (var i=0; i<centroid.features.length; i++) {
pieArray.push([
parseInt(centroid.features[i].properties.ECP),
parseInt(centroid.features[i].properties.LIP),
parseInt(centroid.features[i].properties.NAP),
parseInt(centroid.features[i].properties.SURE)
]);
pieMeta.push([
projection(centroid.features[i].geometry.coordinates),
radius(parseInt(centroid.features[i].properties.dollars))
]);
}
return [pieArray, pieMeta];
};
var svgSvg = d3.select("body").select("svg").selectAll("g")
.data(pieData()[0])
.enter().append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.style("opacity", 0.8)
.attr("property", function (d,i) {
return pieData()[1][i][1];
})
.attr("transform", function (d,i) {
var coordinates = pieData()[1][i][0];
return ("translate(" + (coordinates[0]) + "," +
(coordinates[1]) + ")");
});
svgSvg.selectAll("path")
.data(d3.layout.pie())
.enter().append("svg:path")
.attr("d", d3.svg.arc()
.innerRadius(0)
.outerRadius(function (d) {
var chartList = d3.select(this.parentNode).attr("property");
return chartList;
}))
.style("fill", function(d, i) { return color[i]; });
}

Resources