Skip to content

Instantly share code, notes, and snippets.

@AbelVM
Last active October 19, 2016 15:13
Show Gist options
  • Save AbelVM/c4cf55f6816467cb45fa to your computer and use it in GitHub Desktop.
Save AbelVM/c4cf55f6816467cb45fa to your computer and use it in GitHub Desktop.
CartoCSS pure version of Stacking Chips by Chris Whong
<!--
Based on Chris Whong work
http://blog.cartodb.com/stacking-chips-a-map-hack
It uses a simplified query for the layer, just to retrieve the "floor number":
WITH
m AS (SELECT count(*) n, array_agg(cartodb_id) id_list, the_geom_webmercator, ST_Y(the_geom_webmercator) y FROM chriswhong.cpnv GROUP BY the_geom_webmercator ORDER BY y DESC) ,
f AS (SELECT n, generate_series(1, array_length(id_list,1)) p, unnest(id_list) cartodb_id, the_geom_webmercator FROM m)
SELECT
t.the_geom_webmercator,
f.cartodb_id,
t.offense,
f.p,
to_char(t.occurrence_date,'DD Mon YYYY') date,
t.occurrence_hour
FROM f, chriswhong.cpnv t
WHERE f.cartodb_id = t.cartodb_id
and a new CSS statement that adds the displacement to get the visual effect of stacking chips:
[p>1] {
marker-transform: translate(0, -2*([p]-1));
}
This way, there is no need to query the DB at each zoom change or re-render the layer.
-->
<!DOCTYPE html>
<html lang="en">
<head profile="http://www.w3.org/2005/10/profile">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="./favicon.ico">
<title>NYC Felonies Map</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<link rel="stylesheet" href="http://libs.cartocdn.com/cartodb.js/v3/3.15/themes/css/cartodb.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="icon" type="image/png" href="./favicon.png">
</head>
<style>
html,
body,
#container {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
padding-top: 50px;
}
#map {
width: auto;
height: 100%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.55);
}
#sidebar {
width: 300px;
height: 100%;
max-width: 100%;
float: right;
-webkit-transition: all 0.25s ease-out;
-moz-transition: all 0.25s ease-out;
transition: all 0.25s ease-out;
}
.navbar .navbar-brand {
font-weight: bold;
font-size: 25px;
color: #FFFFFF;
}
.navbar {
background-image: linear-gradient(to bottom, #2b6988 0%, #1f4b61 100%);
}
.navbar-inverse .navbar-nav>li>a {
color: #fff;
}
</style>
<style id="layerCSS">
Map{
buffer-size:2048px;
}
#cpnv {
marker-fill-opacity: 1;
marker-line-color: #424141;
marker-line-width: 0.6;
marker-line-opacity: 1;
marker-placement: point;
marker-type: ellipse;
marker-width: 10;
marker-height: 8;
marker-allow-overlap: true;
[p>1] {
marker-transform: translate(0, -2*([p]-1));
}
[offense="BURGLARY"] {
marker-fill: #A6CEE3;
}
[offense="FELONY ASSAULT"] {
marker-fill: #1F78B4;
}
[offense="GRAND LARCENY"] {
marker-fill: #B2DF8A;
}
[offense="GRAND LARCENY OF MOTOR VEHICLE"] {
marker-fill: #33A02C;
}
[offense="MURDER"] {
marker-fill: #FB9A99;
}
[offense="RAPE"] {
marker-fill: #E31A1C;
}
[offense="ROBBERY"] {
marker-fill: #FDBF6F;
}
}
</style>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<div class="navbar-icon-container">
<a href="#" class="navbar-icon pull-right visible-xs" id="nav-btn"><i class="fa fa-bars fa-lg white"></i></a>
<a href="#" class="navbar-icon pull-right visible-xs" id="sidebar-toggle-btn"><i class="fa fa-search fa-lg white"></i></a>
</div>
<a class="navbar-brand" href="#">NYC Felonies Map</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
</ul>
</div>
</div>
</div>
<div id="container">
<div id="sidebar">
<div class="col-md-12">
<h3>About</h3>
<p>This map shows NYPD Crime Data for the Seven Major Felonies. The data have "Compstat Dates" from January 2015 thru September 2015. (Compstat date is the date the incident was reported to the NYPD and subsequently entered into the Department's record management system). One anomoly is that rape incidents are geo-coded as occurring at the police station house within the precinct of occurrence. There are others so read the notes carefully before interpreting what you see. <a href="https://data.cityofnewyork.us/api/views/hyij-8hr7/files/c0fd6ac0-20ee-4cd8-ad94-b30d96a0d158?download=true&filename=NYPDIncidentLevelDataFootnotes.pdf">Read the full data footnotes here.</a></p>
<p>Since the point location of each incident has been modified to the midpoint of the street segment it occurred on, mapping simple point markers would have resulted in many overlapping points on segments with multiple incidents. The technique used to render this map "stacks" the markers for a given segment, allowing you to see multiple incidents. Hover over a marker to see the occurrance date and type of felony. </p>
<p>Data was pulled from the <a href="https://data.cityofnewyork.us/Public-Safety/NYPD-7-Major-Felony-Incidents/hyij-8hr7#column-menu">NYC Open Data Portal</a> on 30 December 2015.</p>
<p>Map by <a href="https://twitter.com/chris_whong">@chris_whong</a>. Code on <a href="https://github.com/chriswhong/nycfelonies">Github</a>. Powered by <a href="https://cartodb.com">CartoDB</a></p>
<p><a href="https://team.cartodb.com/u/chriswhong/tables/cpnv/public">Download this data as an SHP, geoJSON, KML, or CSV</a></p>
</div>
</div>
<div id="map">
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="http://libs.cartocdn.com/cartodb.js/v3/3.15/cartodb.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script>
var mainLayer,
query = "WITH m AS (SELECT count(*) n, array_agg(cartodb_id) id_list, the_geom_webmercator, ST_Y(the_geom_webmercator) y FROM chriswhong.cpnv GROUP BY the_geom_webmercator ORDER BY y DESC) , f AS (SELECT n, generate_series(1, array_length(id_list,1)) p, unnest(id_list) cartodb_id, the_geom_webmercator FROM m) SELECT t.the_geom_webmercator, f.cartodb_id, t.offense, f.p, to_char(t.occurrence_date::date,'DD Mon YYYY') date, t.occurrence_hour FROM f, chriswhong.cpnv t WHERE f.cartodb_id = t.cartodb_id";
var map = new L.Map('map', {
center: [40.705075, -73.990431],
zoom: 14
});
L.tileLayer('http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
}).addTo(map);
var layerUrl = 'https://team.cartodb.com/u/chriswhong/api/v2/viz/66093444-af68-11e5-a53a-0e674067d321/viz.json';
cartodb.createLayer(map, layerUrl)
.addTo(map)
.on('done', function (layer) {
mainLayer = layer.getSubLayer(0);
mainLayer.setSQL(query);
mainLayer.setCartoCSS($('#layerCSS').html());
layer
.on('featureOver', function (e, latlng, pos, data) {
$('#map').css('cursor', 'pointer');
})
.on('featureOut', function (e, latlng, pos, data) {
$('#map').css('cursor', '-webkit-grab');
})
}).on('error', function () {
//log the error
});
</script>
</body>
</html>
@AbelVM
Copy link
Author

AbelVM commented Oct 19, 2016

The MIT License (MIT)

Copyright (c) 2016 Abel Vázquez Montoro

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment