The dataset can be downloaded at:
https://data.sfgov.org/City-Infrastructure/Tree-Maintenance-2015/jzte-ntie
Requests marked as transfered, canceled, duplicated, or invalid were removed. Requests without valid GPS coordinates, district, or neighborhood information were also removed. And, some basic preprocessing has been done to make certain columns more readable.
xxxxxxxxxx
<meta charset="utf-8">
<style>
body {
font: 20px sans-serif;
color: slategrey;
}
h1 {
}
h5{
font-size: .5em;
font-weight: 200;
}
h3 {
}
div#caption{
position: absolute;
top: 20px;
left: 900px;
width: 400px;
height: 600px;
}
p {
padding: 10px;
}
svg {
position: relative;
top: 40px;
left: 40px;
}
a:link {
text-decoration: none;
}
.land.active {
fill:cornsilk;
}
.axis path,
.axis line {
stroke: #000;
shape-rendering: crispEdges;
}
.axis {
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: .5px;
}
path.land {
fill:rgba(0,0,0,0);
stroke:slategrey;
stroke-width:1px;
}
circle.active {
r:5;
}
circle {
stroke:none;
}
/*rect.pane {
cursor: move;
fill: none;
pointer-events: all;
}
form {
position: absolute;
top: 20px;
right: 30px;
}*/
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="util.js"></script>
<svg></svg>
<div id="caption">
<h1>San Francisco Tree Maintenance<br> Requests April 2015</h1>
<h3>Case Data from <a href="https://data.sfgov.org/City-Infrastructure/Tree-Maintenance-2015/jzte-ntie" target="_blank">SF OpenData<a> 311 (non-emergency) <h3>
<h5> This map informs me that the most tree maintenance cases come from a very central part of the city. After exploring more with interactivity, I have come to the conclusion that this "hotspot" is at the intersection of Market and Mission, which happens to also be at the intersection of four supervisor districts. This "hot" area is one of the more violent sectors in the city and you can tell this from the 311 Tree Maintenance data of April 2015.</h5>
</div>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
// initialize our groups in the right order
var projection = d3.geo.conicEqualArea();
var path = d3.geo.path()
.projection(projection);
projection.center([-122.419416, 37.77489]);
projection.parallels([37.692514, 37.840699]);
// rotate to view we usually see of sf
// zoom in significantly
//projection.translate([-100,-100])
projection.rotate([122,0]);
projection.translate([-309700,-238650]);
projection.scale(250000);
//projection.translate([400,400])
// funky way to get (x, y) position of projection center
var center = projection(projection.center());
// projection.translate([0,0]);
var centered;
var svg = d3.select("body").select("svg").attr("width",width).attr("height",height);
// .attr("width",width).attr("height",height);
var g = svg.append("g")
.attr("id","plot");
var map = g.append("g").attr("id", "map");
var symbols = g.append("g").attr("id", "symbols");
var tooltip = g.append("text").attr("id", "tooltip");
var details = d3.select("body").append("svg").attr("id","dod").attr("width",400).attr("transform",util.translate(200,0))
.append("foreignObject").attr("id", "details");
var legend = svg.append("g").attr("id","legend");
legend.append("circle").attr("r",10).attr("fill","red");
// legend.append("text").attr({
legend.append("text").attr({
"text-anchor": "end",
"dx": 245,
"dy": 6
}).text(" = Damaged by Vandalism");
legend.append("text").attr({
"text-anchor": "end",
"dx": 223,
"dy": 40
}).text(" = Other type of request");
legend.append("text").attr({
"text-anchor": "end",
"dx": 790,
"dy": -10,
"font-size": ".5em"
}).text("(click district to zoom)");
// });
legend.append("circle").attr("r",10).attr("fill","grey")
.attr("transform",util.translate(0,35));
legend.attr("transform", util.translate(30,30));
tooltip.attr({
"x": center[0],
"y": center[1],
"text-anchor": "end",
"dx": -5,
"dy": -5
}).style({
"visibility": "hidden"
})
.text("N/A");
details.attr({
// "x": center[0],
// "y": center[1] + 300,
"width": 400
}).style({
"visibility": "hidden"
})
.append("xhtml:body")
.html("<p>N/A</p>");
d3.csv("Tree_Maintenance_2015.csv", function(error, data) {
if (error) throw error;
console.log(data);
d3.json("Supervisor Districts as of April 2012.geojson", function(json) {
console.log(json);
drawMap(json);
// util.resize(svg, g, 3);
});
draw(data);
});
function drawMap(json) {
var featureCollection = json.features;
var bounds = d3.geo.bounds(json);
console.log(featureCollection);
map.selectAll("path")
.data(featureCollection)
.enter().append("path")
.attr("class", "land")
.attr("d", path)
.on("click", clicked)
.on("mouseover", function(d) {
var me = d3.select(this);
me.classed({"active": true});
tooltip.text(util.toTitleCase(d.properties["supdist"]));
tooltip.style({"visibility": "visible"});
// move selected path to front
this.parentNode.appendChild(this);
})
.on("mousemove", function(d) {
// get coordinates according to "plot" group
var coords = d3.mouse(g.node());
tooltip.attr({"x": coords[0], "y": coords[1]});
})
.on("mouseout", function(d) {
var me = d3.select(this);
me.classed({"active": false});
tooltip.style({"visibility": "hidden"});
});
}
function draw(data) {
symbols
.selectAll("circle")
.data(data)
.enter().append("circle").attr("class",function(d){
return d["Supervisor District"]
}).style({"visibility": "visible"})
.attr("cx", function(d) {
return projection(pointCoverter(d.Point))[0]})
.attr("cy", function(d) { return projection(pointCoverter(d.Point))[1]})
.attr("r", 1.5)
.attr("opacity",.3)
.attr("fill", function(d) {
if (d["Request Details"] === "Damaged vandalism"){
return "red"
} else {
return "grey"
}
})
.on("mouseover", function(d) {
var me = d3.select(this);
me.classed({"active": true});
me.attr("opacity",1);
details.html("<p><b>" + "Source of Request: </b>" + d["Source"] + "<br/>" +
"<b>Details: </b>" +d["Request Details"] + "<br/>" +
"<b>Neighborhood: </b>" + d.Neighborhood + "<br/>" +
"<b>Opened: </b>" + d.Opened +"<br/> <b> Current Status: </b>" + d.Status + "</p>");
details.style({"visibility": "visible"});
})
.on("mouseout", function(d) {
var me = d3.select(this);
me.classed({"active": false});
me.attr("opacity",.3);
details.style({"visibility": "hidden"});
});
}
var pointCoverter = function(points){
var LatLng = points.replace("(", "").replace(")", "").split(", ");
var Lat = parseFloat(LatLng[0]);
var Lng = parseFloat(LatLng[1]);
return [Lng,Lat];
}
//https://bl.ocks.org/mbostock/2206590 clicked from Mbostock block
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 2.5;
centered = d;
} else {
x = center[0] - 55;
y = center[1] - 28;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) {
tooltip.style({"visibility": "hidden"});
symbols.selectAll("circle").attr("r",2);
// symbols.selectAll("circle")
// .classed(d.properties.supervisor, function(d){
// d3.selectAll(d).style({"visibility":"hidden"});
// })
// symbols.selectAll( + d.properties.supervisor);
tooltip.style("font-size","7px");
return d === centered; });
g.selectAll("path")
.classed("active", !centered && function(d) {
tooltip.style({"visibility": "hidden"});
symbols.selectAll("circle").attr("r",1.5);
tooltip.style("font-size","20px");
return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")");
}
</script>
</body>
<!-- <form>
(INTERPOLATION TYPE)</br>
<input type="radio" name="inter" value="Step-After (interpolation)" id="show-voronoi">Step-After
</br>
<input type="radio" name="inter" id="linear" value="Linear">Linear
</br>
<input type="radio" name="inter" id="normal" value="interpots">Basis
</label>
<form> -->
</html>
https://d3js.org/d3.v3.min.js
https://d3js.org/topojson.v1.min.js