diff --git a/frontend/src/js/map.js b/frontend/src/js/map.js index e64d6346..1e850d23 100644 --- a/frontend/src/js/map.js +++ b/frontend/src/js/map.js @@ -1,7 +1,7 @@ 'use strict'; if (!window.console) console = {log: function() {}}; - + var updateVoronoi; // When the DOM is loaded, check for params and add listeners: $(document).ready(function(){ var lhStatus, peerStatus, branchStatus; @@ -14,6 +14,8 @@ if (!window.console) console = {log: function() {}}; setMapHeight(); }); + updateVoronoi = initVoronoi(); + // When minority changes, redraw the circles with appropriate styles $('#category-selector').on('change', function(e) { var val = $('#category-selector').val(); @@ -119,9 +121,13 @@ if (!window.console) console = {log: function() {}}; toggleBranches(true); } }); - + map.on('moveend', _.debounce(init, 500) ); + map.on('movestart', function(){ + d3.selectAll(".densityDot").remove(); + }); + // When the page loads, update the print link, and update it whenever the hash changes updatePrintLink(); updateCensusLink(); @@ -132,10 +138,9 @@ if (!window.console) console = {log: function() {}}; updatePrintLink(); updateCensusLink(); }); - + //Let the application do its thing init(); - }); // Go get the tract centroids and supporting data, THEN build a data object (uses jQuery Deferreds) @@ -154,8 +159,9 @@ if (!window.console) console = {log: function() {}}; createTractDataObj(); // Redraw the circles using the created tract object AND the layer bubble type - redrawCircles(dataStore.tracts, layerInfo.type ); - + //redrawCircles(dataStore.tracts, layerInfo.type ); + + updateVoronoi(rawGeo); // Unblock the user interface (remove gradient) $.unblockUI(); isUIBlocked = false; @@ -420,6 +426,119 @@ if (!window.console) console = {log: function() {}}; /* END GET DATA SECTION */ + /* + * Voronoi drawing + */ + + function randomPoint(rBounds){ + var point = new Array(2); + var west = rBounds.getNorth(); + var north = rBounds.getEast(); + var east = rBounds.getSouth(); + var south = rBounds.getWest(); + point[0] = west + Math.random()*(east - west); + point[1] = north + Math.random()*(south - north); + return point; + } + + function makeDots(polygons,features){ + var points = []; + for(var i=0; i10) break; + pt = randomPoint(reverseBounds); + } + points.push(pt); + } + } + return points; + } + + //https://github.com/substack/point-in-polygon + function pointInPoly(point, vs) { + var x = point[0], y = point[1]; + + var inside = false; + for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) { + var xi = vs[i][0], yi = vs[i][1]; + var xj = vs[j][0], yj = vs[j][1]; + + var intersect = ((yi > y) != (yj > y)) + && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + + return inside; + } + + function initVoronoi(){ + map._initPathRoot(); + var svg = d3.select("#map").select("svg"), + g = svg.append("g").attr("class", "leaflet-zoom-hide"), + voronoi = d3.geom.voronoi(); + + return function(collection){ + var positions = []; + var features = collection.features; + var size = map.getSize(); + + features.forEach(function(d) { + var latlng = new L.LatLng(d.properties.centlat, d.properties.centlon); + var point = map.latLngToLayerPoint(latlng); + positions.push([ + point.x, + point.y + ]); + }); + + + var polygons = voronoi(positions); + + window.poly = polygons; + window.feat = features; + var points = makeDots(polygons, features); + + /* + * Draw voronoi diagrams + * + svg.selectAll(".voronoi").remove(); + polygons.forEach(function(v) { v.cell = v; }); + svg.selectAll("path") + .data(polygons) + .enter() + .append("svg:path") + .attr("class", "voronoi") + .attr({ + "d": function(d) { + if(!d) return null; + return "M" + d.cell.join("L") + "Z"; + }, + stroke:"black", + fill:"none" + }); + */ + + g.selectAll("circle") + .data(points) + .enter() + .append("circle") + .attr("class", "densityDot") + .attr({ + "cx":function(d, i) { return d[0]; }, + "cy":function(d, i) { return d[1]; }, + "r":1, + fill:"#444" + }); + } + } + /* + * End Voronoi drawing + */ /* ---- DRAW CIRCLES AND MARKERS ---- @@ -772,7 +891,7 @@ if (!window.console) console = {log: function() {}}; } addParam( 'category', $('#category-selector option:selected').val() ); - updateCircles( layerType ); + //updateCircles( layerType ); } // Gets non-hash URL parameters @@ -838,4 +957,4 @@ if (!window.console) console = {log: function() {}}; /* END UTILITY FUNCTIONS - */ \ No newline at end of file + */ diff --git a/mapusaurus/mapping/static/mapping/css/vendor.css b/mapusaurus/mapping/static/mapping/css/vendor.css index 54a0cce1..a1902a4e 100644 --- a/mapusaurus/mapping/static/mapping/css/vendor.css +++ b/mapusaurus/mapping/static/mapping/css/vendor.css @@ -1,176 +1,73 @@ -/* Rrose layout */ - -.leaflet-rrose { - position: absolute; - text-align: center; -} - -.leaflet-rrose-content-wrapper { - padding: 1px; - text-align: left; -} - -.leaflet-rrose-content { - margin: 8px 10px; -} - -.leaflet-rrose-tip-container { - margin: 0 auto; - width: 140px; - height: 20px; - position: relative; - overflow: hidden; -} - -.leaflet-rrose-tip-container-sse, .leaflet-rrose-tip-container-ese, -.leaflet-rrose-tip-container-nne, .leaflet-rrose-tip-container-ene, -.leaflet-rrose-tip-container-e { - margin-left: 0px; -} - -.leaflet-rrose-tip-container-ssw, .leaflet-rrose-tip-container-wsw, -.leaflet-rrose-tip-container-nnw, .leaflet-rrose-tip-container-wnw, -.leaflet-rrose-tip-container-w { - margin-right: 0px; -} - -.leaflet-rrose-tip { - width: 15px; - height: 15px; - padding: 1px; - - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); -} - -.leaflet-rrose-tip-n { - margin: -8px auto 0; +.leaflet-control-minimap { + border:solid rgba(255, 255, 255, 1.0) 4px; + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 3px; + background: #f8f8f9; + transition: all .2s; } -.leaflet-rrose-tip-s { - margin: 11px auto 0; +.leaflet-control-minimap a { + background-color: rgba(255, 255, 255, 1.0); + background-repeat: no-repeat; + z-index: 99999; + transition: all .2s; } -.leaflet-rrose-tip-sse, .leaflet-rrose-tip-ese { - margin: 11px 11px 11px -8px; overflow: hidden; +.leaflet-control-minimap a.minimized-bottomright { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); + border-radius: 0px; } -.leaflet-rrose-tip-ssw, .leaflet-rrose-tip-wsw { - margin: 11px 11px 11px 32px; overflow: hidden; +.leaflet-control-minimap a.minimized-topleft { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + border-radius: 0px; } -.leaflet-rrose-tip-nne, .leaflet-rrose-tip-ene, .leaflet-rrose-tip-e { - margin: -8px 11px 11px -8px; overflow: hidden; +.leaflet-control-minimap a.minimized-bottomleft { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); + border-radius: 0px; } -.leaflet-rrose-tip-nnw, .leaflet-rrose-tip-wnw, .leaflet-rrose-tip-w { - margin: -8px 11px 11px 32px; overflow: hidden; +.leaflet-control-minimap a.minimized-topright { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + border-radius: 0px; } -a.leaflet-rrose-close-button { +.leaflet-control-minimap-toggle-display{ + background-image: url("images/toggle.svg"); + background-size: cover; position: absolute; - top: 0; - right: 0; - padding: 4px 5px 0 0; - text-align: center; - width: 18px; - height: 14px; - font: 16px/14px Tahoma, Verdana, sans-serif; - color: #c3c3c3; - text-decoration: none; - font-weight: bold; -} - -a.leaflet-rrose-close-button:hover { - color: #999; -} - -.leaflet-rrose-content p { - margin: 18px 0; -} - -.leaflet-rrose-scrolled { - overflow: auto; - border-bottom: 1px solid #ddd; - border-top: 1px solid #ddd; -} - -/* Visual appearance */ - -.leaflet-rrose-content-wrapper, .leaflet-rrose-tip { - background: white; - - box-shadow: 0 3px 10px #888; - -moz-box-shadow: 0 3px 10px #888; - -webkit-box-shadow: 0 3px 14px #999; -} - -.leaflet-rrose-content-wrapper { - -moz-border-radius: 20px; - -webkit-border-radius: 20px; - border-radius: 20px; -} - -.leaflet-rrose-content-wrapper-sse, .leaflet-rrose-content-wrapper-ese { - -moz-border-radius: 0px 20px 20px 20px; - -webkit-border-radius: 0px 20px 20px 20px; - border-radius: 0px 20px 20px 20px; -} - -.leaflet-rrose-content-wrapper-ssw, .leaflet-rrose-content-wrapper-wsw { - -moz-border-radius: 20px 0px 20px 20px; - -webkit-border-radius: 20px 0px 20px 20px; - border-radius: 20px 0px 20px 20px; -} - -.leaflet-rrose-content-wrapper-nnw, .leaflet-rrose-content-wrapper-wnw, .leaflet-rrose-content-wrapper-w { - -moz-border-radius: 20px 20px 0px 20px; - -webkit-border-radius: 20px 20px 0px 20px; - border-radius: 20px 20px 0px 20px; -} - -.leaflet-rrose-content-wrapper-nne, .leaflet-rrose-content-wrapper-ene, .leaflet-rrose-content-wrapper-e { - -moz-border-radius: 20px 20px 20px 0px; - -webkit-border-radius: 20px 20px 20px 0px; - border-radius: 20px 20px 20px 0px; -} - -.leaflet-rrose-content { - font: 12px/1.4 "Helvetica Neue", Arial, Helvetica, sans-serif; -} - -.leaflet-control-minimap { - border:solid rgba(255, 255, 255, 1.0) 4px; - box-shadow: 0 1px 5px rgba(0,0,0,0.65); - border-radius: 3px; - background: #f8f8f9; - transition: all .2s; + border-radius: 3px 0px 0px 0px; } -.leaflet-control-minimap a { - background-color: rgba(255, 255, 255, 1.0); - background-repeat: no-repeat; - z-index: 99999; - transition: all .2s; - border-radius: 3px 0px 0px 0px; +.leaflet-control-minimap-toggle-display-bottomright { + bottom: 0; + right: 0; } -.leaflet-control-minimap a.minimized { +.leaflet-control-minimap-toggle-display-topleft{ + top: 0; + left: 0; -webkit-transform: rotate(180deg); transform: rotate(180deg); - border-radius: 0px; } -.leaflet-control-minimap-toggle-display { - background-image: url("images/toggle.png"); - height: 19px; - width: 19px; - position: absolute; +.leaflet-control-minimap-toggle-display-bottomleft{ bottom: 0; + left: 0; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} + +.leaflet-control-minimap-toggle-display-topright{ + top: 0; right: 0; + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } /* Old IE */ diff --git a/mapusaurus/mapping/static/mapping/js/map-vendor.min.js b/mapusaurus/mapping/static/mapping/js/map-vendor.min.js index f794d6e8..8de876f6 100644 --- a/mapusaurus/mapping/static/mapping/js/map-vendor.min.js +++ b/mapusaurus/mapping/static/mapping/js/map-vendor.min.js @@ -997,6 +997,8 @@ function addParam(paramName, values) { autoToggleDisplay: !1, width: 150, height: 150, + collapsedWidth: 19, + collapsedHeight: 19, aimingRectOptions: { color: "#ff7800", weight: 1, @@ -1052,7 +1054,8 @@ function addParam(paramName, values) { this._miniMap.removeLayer(this._layer), this._layer = layer, this._miniMap.addLayer(this._layer); }, _addToggleButton: function() { - this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton("", this.hideText, "leaflet-control-minimap-toggle-display", this._container, this._toggleDisplayButtonClicked, this) : void 0; + this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton("", this.hideText, "leaflet-control-minimap-toggle-display leaflet-control-minimap-toggle-display-" + this.options.position, this._container, this._toggleDisplayButtonClicked, this) : void 0, + this._toggleDisplayButton.style.width = this.options.collapsedWidth + "px", this._toggleDisplayButton.style.height = this.options.collapsedHeight + "px"; }, _createButton: function(html, title, className, container, fn, context) { var link = L.DomUtil.create("a", className, container); @@ -1069,13 +1072,13 @@ function addParam(paramName, values) { minimize != this._minimized && (this._minimized ? this._restore() : this._minimize()); }, _minimize: function() { - this.options.toggleDisplay ? (this._container.style.width = "19px", this._container.style.height = "19px", - this._toggleDisplayButton.className += " minimized") : this._container.style.display = "none", + this.options.toggleDisplay ? (this._container.style.width = this.options.collapsedWidth + "px", + this._container.style.height = this.options.collapsedHeight + "px", this._toggleDisplayButton.className += " minimized-" + this.options.position) : this._container.style.display = "none", this._minimized = !0; }, _restore: function() { this.options.toggleDisplay ? (this._container.style.width = this.options.width + "px", - this._container.style.height = this.options.height + "px", this._toggleDisplayButton.className = this._toggleDisplayButton.className.replace(/(?:^|\s)minimized(?!\S)/g, "")) : this._container.style.display = "block", + this._container.style.height = this.options.height + "px", this._toggleDisplayButton.className = this._toggleDisplayButton.className.replace("minimized-" + this.options.position, "")) : this._container.style.display = "block", this._minimized = !1; }, _onMainMapMoved: function() { @@ -1121,6 +1124,6 @@ function addParam(paramName, values) { miniMapControl: !1 }), L.Map.addInitHook(function() { this.options.miniMapControl && (this.miniMapControl = new L.Control.MiniMap().addTo(this)); -}), L.control.minimap = function(options) { - return new L.Control.MiniMap(options); +}), L.control.minimap = function(layer, options) { + return new L.Control.MiniMap(layer, options); }; \ No newline at end of file diff --git a/mapusaurus/mapping/static/mapping/js/map.min.js b/mapusaurus/mapping/static/mapping/js/map.min.js index 2c2a3b56..9defe9b8 100644 --- a/mapusaurus/mapping/static/mapping/js/map.min.js +++ b/mapusaurus/mapping/static/mapping/js/map.min.js @@ -2,8 +2,11 @@ function init() { blockStuff(), $.when(getTractsInBounds(getBoundParams()), getTractData(getBoundParams(), getActionTaken($("#action-taken-selector option:selected").val()))).done(function(data1, data2) { - var hashInfo = getHashParams(), layerInfo = getLayerType(hashInfo.category.values); - rawGeo = data1[0], rawData = data2[0], createTractDataObj(), redrawCircles(dataStore.tracts, layerInfo.type), + { + var hashInfo = getHashParams(); + getLayerType(hashInfo.category.values); + } + rawGeo = data1[0], rawData = data2[0], createTractDataObj(), updateVoronoi(rawGeo), $.unblockUI(), isUIBlocked = !1; }); } @@ -132,6 +135,56 @@ function drawBranches() { }); } +function randomPoint(rBounds) { + var point = new Array(2), west = rBounds.getNorth(), north = rBounds.getEast(), east = rBounds.getSouth(), south = rBounds.getWest(); + return point[0] = west + Math.random() * (east - west), point[1] = north + Math.random() * (south - north), + point; +} + +function makeDots(polygons, features) { + for (var points = [], i = 0; i < polygons.length; i++) for (var poly = polygons[i], reverseBounds = L.latLngBounds(poly), j = 0, len = features[i].properties.volume; len > j; j++) { + for (var pt = randomPoint(reverseBounds), bail = 0; !(pointInPoly(pt, poly) || ++bail > 10); ) pt = randomPoint(reverseBounds); + points.push(pt); + } + return points; +} + +function pointInPoly(point, vs) { + for (var x = point[0], y = point[1], inside = !1, i = 0, j = vs.length - 1; i < vs.length; j = i++) { + var xi = vs[i][0], yi = vs[i][1], xj = vs[j][0], yj = vs[j][1], intersect = yi > y != yj > y && (xj - xi) * (y - yi) / (yj - yi) + xi > x; + intersect && (inside = !inside); + } + return inside; +} + +function initVoronoi() { + map._initPathRoot(); + var svg = d3.select("#map").select("svg"), g = svg.append("g").attr("class", "leaflet-zoom-hide"), voronoi = d3.geom.voronoi(); + return function(collection) { + { + var positions = [], features = collection.features; + map.getSize(); + } + features.forEach(function(d) { + var latlng = new L.LatLng(d.properties.centlat, d.properties.centlon), point = map.latLngToLayerPoint(latlng); + positions.push([ point.x, point.y ]); + }); + var polygons = voronoi(positions); + window.poly = polygons, window.feat = features; + var points = makeDots(polygons, features); + g.selectAll("circle").data(points).enter().append("circle").attr("class", "densityDot").attr({ + cx: function(d) { + return d[0]; + }, + cy: function(d) { + return d[1]; + }, + r: 1, + fill: "#444" + }); + }; +} + function redrawCircles(geoData, layerType) { $("#bubbles_loading").show(), layers.Centroids.clearLayers(), _.each(geoData, function(geo) { drawCircle(geo, layerType); @@ -305,7 +358,7 @@ function layerUpdate(layer) { layerEval = getLayerType(layer), mbLayer = layerEval.layer, layerType = layerEval.type, map.addLayer(mbLayer), mbLayer.bringToFront(), layers.Water.bringToFront(), layers.Boundaries.bringToFront(), layers.MSALabels.bringToFront(), map.hasLayer(layers.CountyLabels) && layers.CountyLabels.bringToFront(), - addParam("category", $("#category-selector option:selected").val()), updateCircles(layerType); + addParam("category", $("#category-selector option:selected").val()); } function urlParam(field) { @@ -359,11 +412,15 @@ function getUniques(arr) { window.console || (console = { log: function() {} -}), $(document).ready(function() { +}); + +var updateVoronoi; + +$(document).ready(function() { var lhStatus, peerStatus, branchStatus; $(".tabs").show(), $(window).resize(function() { setMapHeight(); - }), $("#category-selector").on("change", function() { + }), updateVoronoi = initVoronoi(), $("#category-selector").on("change", function() { var val = $("#category-selector").val(); layerUpdate(val); }), "undefined" != typeof loadParams.category ? ($("#category-selector").val(loadParams.category.values), @@ -398,8 +455,10 @@ window.console || (console = { } }), map.on("moveend", function() { $("#branchSelect").prop("checked") && toggleBranches(!0); - }), map.on("moveend", _.debounce(init, 500)), updatePrintLink(), updateCensusLink(), - layerUpdate($("#category-selector").val()), $(window).on("hashchange", function() { + }), map.on("moveend", _.debounce(init, 500)), map.on("movestart", function() { + d3.selectAll(".densityDot").remove(); + }), updatePrintLink(), updateCensusLink(), layerUpdate($("#category-selector").val()), + $(window).on("hashchange", function() { updatePrintLink(), updateCensusLink(); }), init(); }); diff --git a/mapusaurus/mapping/templates/map.html b/mapusaurus/mapping/templates/map.html index 62f4f7ec..420acfc1 100644 --- a/mapusaurus/mapping/templates/map.html +++ b/mapusaurus/mapping/templates/map.html @@ -13,6 +13,7 @@ {% block head_scripts %} + @@ -97,7 +98,6 @@ Sequential3: L.mapbox.tileLayer('cfpb.FL_TRACT_seq_pct_minorv1_US'), Sequential10: L.mapbox.tileLayer('cfpb.FL_TRACT_seq-10_minorv1_US'), Plurality: L.mapbox.tileLayer('cfpb.FL_TRACT_minor_plurality_US'), - Centroids: L.layerGroup([]), Branches: L.layerGroup([]) }; @@ -112,8 +112,7 @@ 'Water': layers.Water, 'Boundaries': layers.Boundaries, 'MSA Labels': layers.MSALabels, - 'County Labels': layers.CountyLabels, - 'LAR Circles': layers.Centroids + 'County Labels': layers.CountyLabels }; // Setup the minimap @@ -131,8 +130,6 @@ layers.MSALabels.addTo(map); layers.Sequential3.addTo(map); - // Create a layer for our circles - layers.Centroids.addTo(map); // Create a layer for Branch Locations layers.Branches.addTo(map);