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]; });
}
Related
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")
I am trying to put together an interactive bar chart in cshtml.
The good news is it works on every browser except for Firefox.
That being said I'd very much like to know why it is failing on Firefox when it even works on Internet Explorer.. I mean come on, the internet doesn't even work on Internet Explorer.
I have added in what I believe to be the relevant patch of code here:
function buildVisualization(dataSet) {
var barWidth = (chartWidth / dataSet.Items.length - 1) - 1;
var bars = svg.selectAll("rect")
.data(dataSet.Items);
// Build bars for each item
// Example "rect" element: <rect x="200" y="400" width="300" height="100" style="" class="" />
bars.enter()
.append("rect")
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) } )
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate)})
.attr("width", function (item) { return barWidth})
.attr("height", function (item) { return yScale(item.Rate)})
.attr("fill", "teal");
bars.exit().remove();
bars.transition()
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked))} )
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate)})
.attr("width", function (item) { return barWidth})
.attr("height", function (item) { return yScale(item.Rate)})
.attr("fill", "teal");
}
That being said I can provide any information required if requested.
I should point out that when run the chart itself is put into the right place however the bars (an important bit of a bar chart) are all pushed off to the left and stacked on top of each other though they do change height when different options are selected so it seems to be something wrong with the positioning rather than with how they are created. Any advice would be quite welcome.
Entire Snippet:
#{
ViewBag.Title = "Bar Chart";
var choices = new List<SelectListItem>
(){
new SelectListItem(){Text= "C#", Value="c#", Selected=true },
new SelectListItem(){Text= ".Net", Value=".net" },
new SelectListItem(){Text= "ASP.Net", Value="asp.net" },
new SelectListItem(){Text= "ASP.Net MVC", Value="asp.net-mvc" },
new SelectListItem(){Text= "C", Value="c" },
new SelectListItem(){Text= "C++", Value="c++" },
new SelectListItem(){Text= "JavaScript", Value="javascript" },
new SelectListItem(){Text= "Objective C", Value="objective-c" },
new SelectListItem(){Text= "PHP", Value="php" },
new SelectListItem(){Text= "Ruby", Value="ruby" },
new SelectListItem(){Text= "Python", Value="python" }
};
}
<style type="text/css">
svg g.axis {
font-size: .75em;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
svg g.axis text.label {
font-size: 2em;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
svg g.axis path,
svg g.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
</style>
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-md-8">
<p>This demo takes tag information from data.stackexchange.com and projects it below.</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
#Html.Label("TagChoice", "Tag")
#Html.DropDownList("TagChoice", choices)
</div>
</div>
<div class="row">
<div id="chartContainer">
</div>
</div>
#Scripts.Render("~/bundles/d3")
#section Scripts{
<script type="text/javascript">
$(document).ready(function () {
$("#TagChoice").on("change", function () {
var tag = $(this).val();
var url = "/api/tags?tag=";
url += encodeURIComponent(tag);
$.getJSON(url, function (data) {
buildVisualization(data);
});
});
$("#TagChoice").change();
});
// Overall dimensions of the SVG
var height = 400;
var width = 900;
// Padding...
var leftPadding = 75;
var bottomPadding = 50;
// Actual space for the bars
var chartWidth = width - leftPadding;
var chartHeight = height - bottomPadding;
//Building the scale for the heights
var yScale = d3.scale
.linear()
.range([0, chartHeight])
.domain([0, 21000]);
var yAxisScale = d3.scale
.linear()
.range([chartHeight, 0])
.domain([0, 21000]);
//Building the scale for the bar locations
var xScale = d3.time.scale()
.domain([new Date("5-1-2008"), new Date("2-1-2014")])
.range([leftPadding, width - 10]);
//Building a Y axis
var yAxis = d3.svg.axis()
.scale(yAxisScale)
.orient("left");
// Building an X Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(d3.time.format("%m/%d/%Y"));
// Build the overall SVG container
var svg = d3.select("#chartContainer")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "chart");
// Adding the Axes
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + leftPadding + ",0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("dy", "-55px")
.attr("dx", "-50px")
.attr("class", "label")
.style("text-anchor", "end")
.text("Number of Questions Asked");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + chartHeight + ")")
.call(xAxis)
.append("text")
.attr("dy", "40px")
.attr("dx", "475px")
.attr("class", "label")
.style("text-anchor", "end")
.text("Month Asked");
function buildVisualization(dataSet) {
var barWidth = (chartWidth / dataSet.Items.length - 1) - 1;
var bars = svg.selectAll("rect")
.data(dataSet.Items);
// Build bars for each item
// Example "rect" element: <rect x="200" y="400" width="300" height="100" style="" class="" />
bars.enter()
.append("rect")
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) })
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate) })
.attr("width", function (item) { return barWidth })
.attr("height", function (item) { return yScale(item.Rate) })
.attr("fill", "teal");
bars.exit().remove();
bars.transition()
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) })
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate) })
.attr("width", function (item) { return barWidth })
.attr("height", function (item) { return yScale(item.Rate) })
.attr("fill", "teal");
}
</script>
}
Perhaps the reason of your problem is that in Chrome
>> new Date("5-1-2008")
Thu May 01 2008 ...
while in Firefox:
>> new Date("5-1-2008")
Invalid Date
(this is relevant to lines, where you construct xScale)
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>
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:
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");});
});