Last active
August 29, 2015 14:10
-
-
Save dcasciotti/637cfaccc66c0fbe2c2f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#map { | |
width:960px; | |
height:500px; | |
} | |
#legend { | |
position: absolute; | |
top: 3px; | |
left: 780px; | |
margin: 10px; | |
padding: 5px; | |
border-radius: 5px; | |
z-index: 100; | |
font-size: 1em; | |
font-family: sans-serif; | |
width: 165px; | |
background: rgba(255,255,255,0.6); | |
} | |
.legendheading { | |
position: relative; | |
height: 25px; | |
padding: 5px 2px 0px 2px; | |
font-size: larger; | |
font-weight: bold; | |
} | |
.legenditem { | |
padding: 2px; | |
margin-bottom: 2px; | |
} | |
/*Marker clusters*/ | |
.marker-cluster-pie g.arc{ | |
fill-opacity: 0.5; | |
} | |
.marker-cluster-pie-label { | |
font-size: 14px; | |
font-weight: bold; | |
font-family: sans-serif; | |
} | |
/*Markers*/ | |
.marker { | |
width: 18px; | |
height: 18px; | |
border-width: 2px; | |
border-radius:10px; | |
margin-top: -10px; | |
margin-left: -10px; | |
border-style: solid; | |
fill: #CCC; | |
stroke: #444; | |
background: #CCC; | |
border-color: #444; | |
} | |
.marker div{ | |
text-align: center; | |
font-size: 14px; | |
font-weight: bold; | |
font-family: sans-serif; | |
} | |
/*marker categories*/ | |
.category-BURGLARY{ | |
fill: #F88; | |
stroke: #800; | |
background: #F88; | |
border-color: #800; | |
} | |
.category-ROBBERY{ | |
fill: #0C9; | |
stroke: #B60; | |
background: #0C9; | |
border-color: #B60; | |
} | |
.category-LARCENY{ | |
fill: #FF3; | |
stroke: #D80; | |
background: #FF3; | |
border-color: #D80; | |
} | |
.category-AGGRAVATED_ASSAULT{ | |
fill: #0CF; | |
stroke: #D80; | |
background: #0CF; | |
border-color: #D80; | |
} | |
.category-MV_LARCENY{ | |
fill: #F0F; | |
stroke: #D80; | |
background: #F0F; | |
border-color: #D80; | |
} | |
/*marker icons*/ | |
.icon-AGGRAVATED_ASSAULT{ | |
background-image: url('http://webmappingevents.com/wp-content/uploads/2014/12/police.RMS_.violence.assaultAggravated12.png'); | |
background-repeat: no-repeat; | |
background-position: 0px 1px; | |
} | |
.icon-BURGLARY{ | |
background-image:url('http://webmappingevents.com/wp-content/uploads/2014/12/police.RMS_.property.burglaryResidential252.png'); | |
background-repeat: no-repeat; | |
background-position: 1px -2px; | |
} | |
.icon-LARCENY{ | |
background-image: url('http://webmappingevents.com/wp-content/uploads/2014/12/police.RMS_.property.theftOther251.png'); | |
background-repeat: no-repeat; | |
background-position: 1px -2px; | |
} | |
.icon-ROBBERY{ | |
background-image: url('http://webmappingevents.com/wp-content/uploads/2014/12/police.RMS_.property.robberyOther252.png'); | |
background-repeat: no-repeat; | |
background-position: 1px -2px; | |
} | |
.icon-MV_LARCENY{ | |
background-image: url('http://webmappingevents.com/wp-content/uploads/2014/12/police.RMS_.property.vehicleTheft252.png'); | |
background-repeat: no-repeat; | |
background-position: 1px -2px; | |
} | |
/*Popup*/ | |
.map-popup span.heading { | |
display: block; | |
font-size: 1.2em; | |
font-weight: bold; | |
} | |
.map-popup span.attribute { | |
display: block; | |
} | |
.map-popup span.label { | |
font-weight: bold; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<title>ClusterPies</title> | |
<link rel="stylesheet" type="text/css" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" /> | |
<link rel="stylesheet" type="text/css" href="https:////cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/0.4.0/MarkerCluster.Default.css" /> | |
<link rel="stylesheet" type="text/css" href="clusterpies_ucr.css" /> | |
</head> | |
<body> | |
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js" charset="utf-8"></script> | |
<script src="http://leaflet.github.io/Leaflet.markercluster/dist/leaflet.markercluster.js"></script> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<div id="container"> | |
<div id="map" /> | |
</div> | |
<script> | |
"use strict" | |
var geojson, | |
metadata, | |
geojsonPath = 'points_rand.json', | |
categoryField = 'UCR_NAME', //This is the fieldname for marker category (used in the pie and legend) | |
iconField = 'UCR_NAME', //This is the fieldame for marker icon | |
popupFields = ['UCR_NAME','OCCUR_BEGI','REPORT_DAT'], //Popup will display these fields | |
tileServer = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', | |
tileAttribution = 'Map data: <a href="http://openstreetmap.org">OSM</a>', | |
rmax = 30, //Maximum radius for cluster pies | |
markerclusters = L.markerClusterGroup({ | |
maxClusterRadius: 2*rmax, | |
iconCreateFunction: defineClusterIcon //this is where the magic happens | |
}), | |
map = L.map('map').setView([42.56, -73.78], 8); | |
//Add basemap | |
L.tileLayer(tileServer, {attribution: tileAttribution, maxZoom: 15}).addTo(map); | |
//and the empty markercluster layer | |
map.addLayer(markerclusters); | |
//Ready to go, load the geojson | |
d3.json(geojsonPath, function(error, data) { | |
if (!error) { | |
geojson = data; | |
metadata = data.properties; | |
var markers = L.geoJson(geojson, { | |
pointToLayer: defineFeature, | |
onEachFeature: defineFeaturePopup | |
}); | |
markerclusters.addLayer(markers); | |
map.fitBounds(markers.getBounds()); | |
map.attributionControl.addAttribution(metadata.attribution); | |
renderLegend(); | |
} else { | |
console.log('Could not load data...'); | |
} | |
}); | |
function defineFeature(feature, latlng) { | |
var categoryVal = feature.properties[categoryField], | |
iconVal = feature.properties[iconField]; | |
var myClass = 'marker category-'+categoryVal+' icon-'+iconVal; | |
var myIcon = L.divIcon({ | |
className: myClass, | |
iconSize:null | |
}); | |
return L.marker(latlng, {icon: myIcon}); | |
} | |
function defineFeaturePopup(feature, layer) { | |
var props = feature.properties, | |
fields = metadata.fields, | |
popupContent = ''; | |
popupFields.map( function(key) { | |
if (props[key]) { | |
var val = props[key], | |
label = fields[key].name; | |
if (fields[key].lookup) { | |
val = fields[key].lookup[val]; | |
} | |
popupContent += '<span class="attribute"><span class="label">'+label+':</span> '+val+'</span>'; | |
} | |
}); | |
popupContent = '<div class="map-popup">'+popupContent+'</div>'; | |
layer.bindPopup(popupContent,{offset: L.point(1,-2)}); | |
} | |
function defineClusterIcon(cluster) { | |
var children = cluster.getAllChildMarkers(), | |
n = children.length, //Get number of markers in cluster | |
strokeWidth = 1, //Set clusterpie stroke width | |
r = rmax-2*strokeWidth-(n<10?12:n<100?8:n<1000?4:0), //Calculate clusterpie radius... | |
iconDim = (r+strokeWidth)*2, //...and divIcon dimensions (leaflet really want to know the size) | |
data = d3.nest() //Build a dataset for the pie chart | |
.key(function(d) { return d.feature.properties[categoryField]; }) | |
.entries(children, d3.map), | |
//bake some svg markup | |
html = bakeThePie({data: data, | |
valueFunc: function(d){return d.values.length;}, | |
strokeWidth: 1, | |
outerRadius: r, | |
innerRadius: r-10, | |
pieClass: 'cluster-pie', | |
pieLabel: n, | |
pieLabelClass: 'marker-cluster-pie-label', | |
pathClassFunc: function(d){return "category-"+d.data.key;}, | |
pathTitleFunc: function(d){return metadata.fields[categoryField].lookup[d.data.key]+' ('+d.data.values.length+' accident'+(d.data.values.length!=1?'s':'')+')';} | |
}), | |
//Create a new divIcon and assign the svg markup to the html property | |
myIcon = new L.DivIcon({ | |
html: html, | |
className: 'marker-cluster', | |
iconSize: new L.Point(iconDim, iconDim) | |
}); | |
return myIcon; | |
} | |
/*function that generates a svg markup for the pie chart*/ | |
function bakeThePie(options) { | |
/*data and valueFunc are required*/ | |
if (!options.data || !options.valueFunc) { | |
return ''; | |
} | |
var data = options.data, | |
valueFunc = options.valueFunc, | |
r = options.outerRadius?options.outerRadius:28, //Default outer radius = 28px | |
rInner = options.innerRadius?options.innerRadius:r-10, //Default inner radius = r-10 | |
strokeWidth = options.strokeWidth?options.strokeWidth:1, //Default stroke is 1 | |
pathClassFunc = options.pathClassFunc?options.pathClassFunc:function(){return '';}, //Class for each path | |
pathTitleFunc = options.pathTitleFunc?options.pathTitleFunc:function(){return '';}, //Title for each path | |
pieClass = options.pieClass?options.pieClass:'marker-cluster-pie', //Class for the whole pie | |
pieLabel = options.pieLabel?options.pieLabel:d3.sum(data,valueFunc), //Label for the whole pie | |
pieLabelClass = options.pieLabelClass?options.pieLabelClass:'marker-cluster-pie-label',//Class for the pie label | |
origo = (r+strokeWidth), //Center coordinate | |
w = origo*2, //width and height of the svg element | |
h = w, | |
donut = d3.layout.pie(), | |
arc = d3.svg.arc().innerRadius(rInner).outerRadius(r); | |
//Create an svg element | |
var svg = document.createElementNS(d3.ns.prefix.svg, 'svg'); | |
//Create the pie chart | |
var vis = d3.select(svg) | |
.data([data]) | |
.attr('class', pieClass) | |
.attr('width', w) | |
.attr('height', h); | |
var arcs = vis.selectAll('g.arc') | |
.data(donut.value(valueFunc)) | |
.enter().append('svg:g') | |
.attr('class', 'arc') | |
.attr('transform', 'translate(' + origo + ',' + origo + ')'); | |
arcs.append('svg:path') | |
.attr('class', pathClassFunc) | |
.attr('stroke-width', strokeWidth) | |
.attr('d', arc) | |
.append('svg:title') | |
.text(pathTitleFunc); | |
vis.append('text') | |
.attr('x',origo) | |
.attr('y',origo) | |
.attr('class', pieLabelClass) | |
.attr('text-anchor', 'middle') | |
//.attr('dominant-baseline', 'central') | |
/*IE doesn't seem to support dominant-baseline, but setting dy to .3em does the trick*/ | |
.attr('dy','.3em') | |
.text(pieLabel); | |
//Return the svg-markup rather than the actual element | |
return serializeXmlNode(svg); | |
} | |
/*Function for generating a legend with the same categories as in the clusterPie*/ | |
function renderLegend() { | |
var data = d3.entries(metadata.fields[categoryField].lookup), | |
legenddiv = d3.select('body').append('div') | |
.attr('id','legend'); | |
var heading = legenddiv.append('div') | |
.classed('legendheading', true) | |
.text(metadata.fields[categoryField].name); | |
var legenditems = legenddiv.selectAll('.legenditem') | |
.data(data); | |
legenditems | |
.enter() | |
.append('div') | |
.attr('class',function(d){return 'category-'+d.key;}) | |
.classed({'legenditem': true}) | |
.text(function(d){return d.value;}); | |
} | |
/*Helper function*/ | |
function serializeXmlNode(xmlNode) { | |
if (typeof window.XMLSerializer != "undefined") { | |
return (new window.XMLSerializer()).serializeToString(xmlNode); | |
} else if (typeof xmlNode.xml != "undefined") { | |
return xmlNode.xml; | |
} | |
return ""; | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment