\r\n\r\n(function(_) {\r\n var ns, sharedNs;\r\n\r\n ns = window.nspace(\"ccc.viz.map.models\");\r\n sharedNs = window.nspace(\"ccc.viz.shared.models\");\r\n\r\n /**\r\n * Data response model is responsible for querying the server-side for data,\r\n * storing the response, and other views listen for the response to change\r\n * in order to be updated when new data is retrieved\r\n */\r\n ns.DataResponseModel = sharedNs.DataResponseModel.extend({\r\n getDataResponseOnChange: function() {\r\n // Don't bother requesting new data if the only change was to the selectedFips.\r\n if (\r\n _.size(this.selectorsModel.changed) === 1 &&\r\n this.selectorsModel.changed.hasOwnProperty(\"selectedFips\")\r\n ) {\r\n return;\r\n }\r\n\r\n this.getDataResponse();\r\n },\r\n\r\n getFilterString: function() {\r\n var params, filterStringNvc, filterString;\r\n\r\n params = window.ccc.viz.util.UrlParams;\r\n\r\n filterStringNvc = new window.velir.collections.NameValueCollection();\r\n filterStringNvc.add(\r\n params.indicator,\r\n this.selectorsModel.get(\"indicatorId\")\r\n );\r\n filterStringNvc.add(\r\n params.timeFrame,\r\n this.selectorsModel.get(\"timeFrameId\")\r\n );\r\n filterStringNvc.add(\r\n params.locationType,\r\n this.selectorsModel.get(\"locationTypeId\")\r\n );\r\n filterStringNvc.add(\r\n params.dataFormat,\r\n this.selectorsModel.get(\"dataFormatId\")\r\n );\r\n filterStringNvc.add(\r\n params.characteristic,\r\n this.selectorsModel.get(\"characteristicId\")\r\n );\r\n\r\n filterString = filterStringNvc.toQueryString();\r\n\r\n return filterString;\r\n },\r\n\r\n getDataParams: function(filterString) {\r\n return {\r\n filterString: filterString,\r\n numBreaks: window.ccc.viz.map.config.MapConfig.getNumBreaks()\r\n };\r\n },\r\n\r\n hasData: function() {\r\n return this.get(\"response\").hasData;\r\n }\r\n });\r\n})(_);\r\n","(function($, _) {\r\n var ns = nspace(\"ccc.viz.map.util.ChartRenderer\");\r\n\r\n ns.renderSparkline = function(\r\n chartContainer,\r\n xAxisCategories,\r\n dataSeries,\r\n displayValues,\r\n onAnimationCompleteCallback\r\n ) {\r\n return new Highcharts.Chart({\r\n chart: {\r\n renderTo: chartContainer,\r\n margin: [0, 0, 20, 0],\r\n backgroundColor: \"rgba(0, 0, 0, 0)\",\r\n borderWidth: 0\r\n },\r\n title: {\r\n text: null\r\n },\r\n credits: {\r\n enabled: false\r\n },\r\n xAxis: {\r\n maxPadding: 0.06,\r\n minPadding: 0.06,\r\n categories: xAxisCategories,\r\n endOnTick: true,\r\n lineWidth: 0,\r\n tickWidth: 0,\r\n labels: {\r\n enabled: true,\r\n align: \"center\",\r\n useHTML: true,\r\n formatter: function() {\r\n // Only show the first and last labels\r\n if (\r\n this.value === xAxisCategories[0] ||\r\n this.value === xAxisCategories[xAxisCategories.length - 1]\r\n ) {\r\n // We need to manually tweak the label positioning\r\n var style = \"\";\r\n if (this.value === xAxisCategories[0]) {\r\n style += \" left: 10px;\";\r\n }\n else {\r\n style += \" right: 10px;\";\r\n }\r\n\r\n return (\r\n \"\" +\r\n this.value +\r\n \"
\"\r\n );\r\n }\r\n return null;\r\n }\r\n }\r\n },\r\n yAxis: {\r\n maxPadding: 0.12,\r\n minPadding: 0.12,\r\n labels: {\r\n enabled: false\r\n },\r\n gridLineWidth: 0,\r\n title: null\r\n },\r\n legend: {\r\n enabled: false\r\n },\r\n tooltip: {\r\n enabled: true,\r\n useHTML: true,\r\n borderWidth: 0,\r\n shadow: false,\r\n formatter: function() {\r\n return (\r\n \"\" +\r\n this.x +\r\n \": \" +\r\n displayValues[this.x] +\r\n \"
\"\r\n );\r\n },\r\n style: {\r\n padding: 0\r\n },\r\n positioner: function(labelWidth, labelHeight, point) {\r\n return { x: point.plotX - labelWidth / 2, y: point.plotY - 50 };\r\n }\r\n },\r\n plotOptions: {\r\n series: {\r\n lineWidth: 2,\r\n shadow: false,\r\n states: {\r\n hover: {\r\n lineWidth: 2\r\n }\r\n },\r\n marker: {\r\n radius: 3,\r\n states: {\r\n hover: {\r\n radius: 4\r\n }\r\n }\r\n }\r\n }\r\n },\r\n series: [\r\n {\r\n color: \"#FE3D34\",\r\n data: dataSeries,\r\n animation: {\r\n complete: function() {\r\n if (onAnimationCompleteCallback) {\r\n onAnimationCompleteCallback();\r\n }\r\n }\r\n }\r\n }\r\n ]\r\n });\r\n };\r\n})(jQuery, _);\r\n","(function(google) {\r\n if (!google) {\r\n throw \"Google Maps API must be loaded before loading extensions\";\r\n }\r\n\r\n // From here: https://github.com/tparkin/Google-Maps-Point-in-Polygon/blob/master/maps.google.polygon.containsLatLng.js\r\n\r\n if (!google.maps.Polygon.prototype.getBounds) {\r\n google.maps.Polygon.prototype.getBounds = function(latLng) {\r\n //If this polygon has a bounding box assigned to it already\r\n if (this.boundingBox) {\r\n return this.boundingBox;\r\n }\r\n\r\n var bounds = new google.maps.LatLngBounds();\r\n var paths = this.getPaths();\r\n var path;\r\n\r\n for (var p = 0; p < paths.getLength(); p++) {\r\n path = paths.getAt(p);\r\n for (var i = 0; i < path.getLength(); i++) {\r\n bounds.extend(path.getAt(i));\r\n }\r\n }\r\n\r\n return bounds;\r\n };\r\n }\r\n\r\n if (!google.maps.Polygon.prototype.contains) {\r\n google.maps.Polygon.prototype.contains = function(latLng) {\r\n // Exclude points outside of bounds as there is no way they are in the poly\r\n var bounds = this.getBounds();\r\n\r\n if (bounds != null && !bounds.contains(latLng)) {\r\n return false;\r\n }\r\n\r\n // Raycast point in polygon method\r\n var inPoly = false;\r\n\r\n var numPaths = this.getPaths().getLength();\r\n for (var p = 0; p < numPaths; p++) {\r\n var path = this.getPaths().getAt(p);\r\n var numPoints = path.getLength();\r\n var j = numPoints - 1;\r\n\r\n for (var i = 0; i < numPoints; i++) {\r\n var vertex1 = path.getAt(i);\r\n var vertex2 = path.getAt(j);\r\n\r\n if (\r\n (vertex1.lng() < latLng.lng() && vertex2.lng() >= latLng.lng()) ||\r\n (vertex2.lng() < latLng.lng() && vertex1.lng() >= latLng.lng())\r\n ) {\r\n if (\r\n vertex1.lat() +\r\n ((latLng.lng() - vertex1.lng()) /\r\n (vertex2.lng() - vertex1.lng())) *\r\n (vertex2.lat() - vertex1.lat()) <\r\n latLng.lat()\r\n ) {\r\n inPoly = !inPoly;\r\n }\r\n }\r\n\r\n j = i;\r\n }\r\n }\r\n\r\n return inPoly;\r\n };\r\n }\r\n})(google);\r\n","/// \r\n\r\n(function($, Proj4js, google) {\r\n var ns = nspace(\"ccc.viz.map.util\");\r\n\r\n var wgs84Coords = new Proj4js.Proj(\"WGS84\");\r\n var epsg3857Coords = new Proj4js.Proj(\"GOOGLE\");\r\n\r\n // These regexes and the WKT polygon parsing code is roughly based off code found in OpenLayers\r\n // https://github.com/openlayers/openlayers/blob/master/lib/OpenLayers/Format/WKT.js\r\n var regExes = {\r\n typeStr: /^\\s*(\\w+)\\s*\\(\\s*(.*)\\s*\\)\\s*$/,\r\n parenComma: /\\)\\s*,\\s*\\(/,\r\n doubleParenComma: /\\)\\s*\\)\\s*,\\s*\\(\\s*\\(/,\r\n trimParens: /^\\s*\\(?(.*?)\\)?\\s*$/\r\n };\r\n\r\n function getPolygonLatLngs(coords) {\r\n // Expects polygon text that looks like this:\r\n // -12211621.104157 4438929.90942386, ... , -12212798.9101653 4438929.90942386\r\n\r\n // split into coord pairs\r\n var pairs = coords.split(\", \");\r\n var latLngs = [];\r\n for (var i = 0; i < pairs.length; i++) {\r\n // create a new LatLng from each pair\r\n var parts = pairs[i].split(\" \");\r\n var lat = parseFloat(parts[1]);\r\n var lng = parseFloat(parts[0]);\r\n\r\n latLngs.push(new google.maps.LatLng(lat, lng));\r\n }\r\n\r\n return latLngs;\r\n }\r\n\r\n function parsePolygon(text) {\r\n var paths = [];\r\n var rings = $.trim(text).split(regExes.parenComma);\r\n for (var i = 0, len = rings.length; i < len; ++i) {\r\n var ring = rings[i].replace(regExes.trimParens, \"$1\");\r\n paths.push(getPolygonLatLngs(ring));\r\n }\r\n\r\n return paths;\r\n }\r\n\r\n function parseMultiPolygon(text) {\r\n var polygon;\r\n var polygons = $.trim(text).split(regExes.doubleParenComma);\r\n var paths = [];\r\n for (var i = 0, len = polygons.length; i < len; ++i) {\r\n polygon = polygons[i].replace(regExes.trimParens, \"$1\");\r\n var polyPaths = parsePolygon(polygon);\r\n for (var j = 0; j < polyPaths.length; j++) {\r\n paths.push(polyPaths[j]);\r\n }\r\n }\r\n return paths;\r\n }\r\n\r\n ns.MapUtil = {\r\n /**\r\n * Translates latitude and longitude into screen pixel coordinates relative to the map container\r\n */\r\n fromPositionToRelativePixels: function(gmap, position) {\r\n var scale = Math.pow(2, gmap.getZoom());\r\n var worldXY = new google.maps.LatLng(\r\n gmap\r\n .getBounds()\r\n .getNorthEast()\r\n .lat(),\r\n gmap\r\n .getBounds()\r\n .getSouthWest()\r\n .lng()\r\n );\r\n var worldEdge = gmap.getProjection().fromLatLngToPoint(worldXY);\r\n var worldPosition = gmap.getProjection().fromLatLngToPoint(position);\r\n\r\n return {\r\n x: Math.floor((worldPosition.x - worldEdge.x) * scale),\r\n y: Math.floor((worldPosition.y - worldEdge.y) * scale)\r\n };\r\n },\r\n\r\n /**\r\n * Converts a latitude/longitude in EPSG 3857 (web mercator meters) to wgs84 (degrees)\r\n */\r\n epsg3857ToWgs84: function(lat, lng) {\r\n var p = new Proj4js.Point(lng, lat);\r\n Proj4js.transform(epsg3857Coords, wgs84Coords, p);\r\n return new google.maps.LatLng(p.y, p.x);\r\n },\r\n\r\n /**\r\n * Converts a google Point in wgs84 (degrees) to EPSG 3857 (web mercator meters)\r\n */\r\n wgs84ToEpsg3857: function(googlePoint) {\r\n var p = new Proj4js.Point(googlePoint.lng(), googlePoint.lat());\r\n Proj4js.transform(wgs84Coords, epsg3857Coords, p);\r\n return p;\r\n },\r\n\r\n /**\r\n * Converts a polygon in WKT format (Well Known Text) into a google maps polygon\r\n */\r\n wktToGooglePolygon: function(wktPolygon) {\r\n // WKT Polygon examples available here: http://en.wikipedia.org/wiki/Well-known_text\r\n\r\n var paths = [];\r\n\r\n wktPolygon = wktPolygon.replace(/[\\n\\r]/g, \" \");\r\n var matches = regExes.typeStr.exec(wktPolygon);\r\n if (matches) {\r\n var type = matches[1].toLowerCase();\r\n var text = matches[2];\r\n if (type === \"multipolygon\") {\r\n paths = parseMultiPolygon(text);\r\n }\n else if (type === \"polygon\") {\r\n paths = parsePolygon(text);\r\n }\n else {\r\n throw \"Unrecognized feature type: \" + type;\r\n }\r\n }\r\n\r\n // create a polygon from the LatLngs\r\n var polygon = new google.maps.Polygon({\r\n paths: paths,\r\n fillColor: \"#eee\",\r\n fillOpacity: 0.9,\r\n strokeColor: \"#e2ecec\",\r\n strokeWeight: 1\r\n });\r\n\r\n return polygon;\r\n }\r\n };\r\n})(jQuery, Proj4js, google);\r\n","/// \r\n/// \r\n/// \r\n/// \r\n/// \r\n/// \r\n\r\n(function($, _, velir, google) {\r\n var ns = window.nspace(\"ccc.viz.map.views\");\r\n\r\n var pushNycClick = function(response, selectedFips) {\r\n var location = \"New York City\";\r\n if (\r\n selectedFips &&\r\n response &&\r\n response.locationsByFips &&\r\n response.locationsByFips[selectedFips]\r\n ) {\r\n location = response.locationsByFips[selectedFips].name || \"New York City\";\r\n }\r\n\r\n // Tracks if user clicks on the map on the \"Explore Data\" page\r\n dataLayer.push({\r\n event: \"Explore Map Clicks\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Explore Map|\" + location,\r\n eventLabel: window.location.pathname\r\n });\r\n };\r\n\r\n ns.GoogleMapView = Backbone.View.extend({\r\n el: \"#gmap-container\",\r\n\r\n initialize: function(options) {\r\n this.initialRenderPromise = $.Deferred();\r\n\r\n var self = this;\r\n\r\n _.bindAll(\r\n this,\r\n \"render\",\r\n \"findAndDrawPolygon\",\r\n \"preLoadPolygons\",\r\n \"selectRegion\",\r\n \"drawSelectedRegion\"\r\n );\r\n\r\n this.dispatcher = options.dispatcher;\r\n this.polygonManager = ccc.viz.map.layers.PolygonManager;\r\n this.currentMouseover = {};\r\n this.dataResponseModel = options.dataResponseModel;\r\n this.initializeMap();\r\n\r\n // When the user's selections change, rerender the map\r\n this.model.on(\"change\", this.render);\r\n\r\n // When the location type ID changes, pre load the new polygons\r\n this.model.on(\"change:locationTypeId\", this.preLoadPolygons);\r\n\r\n // When the selectedFips changes, redraw the highlighted polygon\r\n this.model.on(\"change:selectedFips\", this.drawSelectedRegion);\r\n\r\n // Prevent mouseover popups from getting \"stuck\" when quickly mousing off the map\r\n this.$el.on(\"mouseleave\", function() {\r\n // Remove any previously drawn polygon\r\n if (self.currentMouseover && self.currentMouseover.polygon) {\r\n self.currentMouseover.polygon.setMap(null);\r\n self.currentMouseover = null;\r\n }\r\n self.dispatcher.trigger(\"mouseoff-region\");\r\n });\r\n\r\n // The above change event isn't emitted upon initialization (only on subsequent changes)\r\n // so manually trigger initial draw\r\n this.drawSelectedRegion();\r\n },\r\n\r\n getInitialRenderPromise: function() {\r\n return this.initialRenderPromise;\r\n },\r\n\r\n initializeMap: function() {\r\n var self = this;\r\n\r\n // GMap styles\r\n var styles = [\r\n {\r\n featureType: \"water\",\r\n elementType: \"geometry\",\r\n stylers: [{ color: \"#8d8d8d\" }, { lightness: 17 }]\r\n },\r\n {\r\n featureType: \"landscape\",\r\n elementType: \"geometry\",\r\n stylers: [{ color: \"#f5f5f5\" }, { lightness: 20 }]\r\n },\r\n {\r\n featureType: \"road.highway\",\r\n elementType: \"geometry.fill\",\r\n stylers: [{ color: \"#ffffff\" }, { lightness: 17 }]\r\n },\r\n {\r\n featureType: \"road.highway\",\r\n elementType: \"geometry.stroke\",\r\n stylers: [{ color: \"#ffffff\" }, { lightness: 29 }, { weight: 0.2 }]\r\n },\r\n {\r\n featureType: \"road.arterial\",\r\n elementType: \"geometry\",\r\n stylers: [{ color: \"#ffffff\" }, { lightness: 18 }]\r\n },\r\n {\r\n featureType: \"road.local\",\r\n elementType: \"geometry\",\r\n stylers: [{ color: \"#ffffff\" }, { lightness: 16 }]\r\n },\r\n {\r\n featureType: \"poi\",\r\n elementType: \"geometry\",\r\n stylers: [{ color: \"#f5f5f5\" }, { lightness: 21 }]\r\n },\r\n {\r\n featureType: \"poi.park\",\r\n elementType: \"geometry\",\r\n stylers: [{ color: \"#dedede\" }, { lightness: 21 }]\r\n },\r\n {\r\n elementType: \"labels.text.stroke\",\r\n stylers: [\r\n { visibility: \"on\" },\r\n { color: \"#ffffff\" },\r\n { lightness: 16 }\r\n ]\r\n },\r\n {\r\n elementType: \"labels.text.fill\",\r\n stylers: [{ saturation: 36 }, { color: \"#333333\" }, { lightness: 40 }]\r\n },\r\n { elementType: \"labels.icon\", stylers: [{ visibility: \"off\" }] },\r\n {\r\n featureType: \"transit\",\r\n elementType: \"geometry\",\r\n stylers: [{ color: \"#f2f2f2\" }, { lightness: 19 }]\r\n },\r\n {\r\n featureType: \"administrative\",\r\n elementType: \"geometry.fill\",\r\n stylers: [{ color: \"#fefefe\" }, { lightness: 20 }]\r\n },\r\n {\r\n featureType: \"administrative\",\r\n elementType: \"geometry.stroke\",\r\n stylers: [{ color: \"#fefefe\" }, { lightness: 17 }, { weight: 1.2 }]\r\n }\r\n ];\r\n\r\n var config = ccc.viz.map.config.MapConfig;\r\n\r\n var mapOptions = {\r\n // Show NYC upon load\r\n center: new google.maps.LatLng(40.72332, -74.0039),\r\n zoom: 10,\r\n\r\n // Roadmap\r\n mapTypeId: google.maps.MapTypeId.ROADMAP,\r\n\r\n // Map Controls\r\n panControl: false,\r\n mapTypeControl: false,\r\n scaleControl: false,\r\n streetViewControl: false,\r\n overviewMapControl: false,\r\n zoomControlOptions: {\r\n position: google.maps.ControlPosition.RIGHT_CENTER\r\n },\r\n scrollwheel: false,\r\n\r\n // Min/max zoom\r\n minZoom: config.minZoom,\r\n maxZoom: config.maxZoom,\r\n\r\n // So stylee!\r\n styles: styles\r\n };\r\n\r\n // Notify other components that we're about to load the map\r\n this.gmapLoading = $.Deferred();\r\n this.dispatcher.trigger(\"gmap-busy\");\r\n this.dispatcher.trigger(\"add-task\", {\r\n name: \"gmap-loading\",\r\n text: \"Loading base map...\"\r\n });\r\n\r\n // Load the map\r\n this.gmap = new google.maps.Map(this.$el[0], mapOptions);\r\n\r\n\t\t\tthis.labelEngine = new CCCLabelEngine(this.gmap);\r\n\r\n // Listen for the map to finish loading and emit an event. Map MUST finish loading before a choropleth overlay can be added,\r\n // otherwise no map projection will be set.\r\n google.maps.event.addListenerOnce(this.gmap, \"idle\", function() {\r\n self.gmapLoading.resolve();\r\n self.dispatcher.trigger(\"gmap-idle\");\r\n self.dispatcher.trigger(\"end-task\", { name: \"gmap-loading\" });\r\n\r\n\r\n\t\t\t\t// Google Maps seems to like to refresh the map markers when panning the map\r\n\t\t\t\t// This causes the MarkerWithLabels' class list to get reset to the initial provided values\r\n\t\t\t\t// There doesn't seem to be a good way to prevent this and keep the markers displayed while panning\r\n\t\t\t\t// For now, just re-render the markers on idle (finished panning/zooming/toggling full screen)\r\n\t\t\t\tself.renderLabels();\r\n\t\t\t\tgoogle.maps.event.addListener(self.gmap, 'idle', () => self.renderLabels());\r\n });\r\n\r\n // Listen for the mouse to move over the map and draw polygons as necessary\r\n google.maps.event.addListener(\r\n this.gmap,\r\n \"mousemove\",\r\n self.findAndDrawPolygon\r\n );\r\n\r\n // Listen for clicks on the map and select the clicked region\r\n google.maps.event.addListener(this.gmap, \"click\", self.selectRegion);\r\n },\r\n\r\n\t\trenderLabels: function(takeScreenshot) {\t\t\t\r\n\t\t\tthis.labelEngine.renderLabels(this.overlayMarkers, this.addLoadingLabelsTask(), this.updateLabelsTaskDone(takeScreenshot));\r\n\t\t},\r\n\r\n\t\taddLoadingLabelsTask: function() {\r\n\t\t\tvar self = this;\r\n\t\t\treturn () => {\r\n\t\t\t\tself.dispatcher.trigger(\"add-task\", { name: \"choropleth-labels-loading\", text: \"Loading labels...\" });\r\n\t\t\t};\r\n\t\t},\r\n\r\n\t\tupdateLabelsTaskDone: function(takeScreenshot) {\r\n\t\t\tvar self = this;\t\t\t\r\n\t\t\treturn () => {\r\n self.dispatcher.trigger(\"end-all-tasks\", { name: \"choropleth-labels-loading\" });\r\n \r\n if (takeScreenshot && window.triggerScreenshot instanceof Function) {\r\n window.triggerScreenshot();\r\n }\r\n\t\t\t};\r\n\t\t},\r\n\r\n preLoadPolygons: function() {\r\n this.polygonManager.loadPolygons(this.model.get(\"locationTypeId\"));\r\n },\r\n\r\n render: function() {\r\n var self = this;\r\n\r\n // The above \"change:locationTypeId\" event isn't emitted upon initialization (only on subsequent changes)\r\n // so manually trigger polygon preloading\r\n this.preLoadPolygons();\r\n\r\n $.when(this.gmapLoading).done(function() {\r\n // Don't bother re-rendering if the only change was to the selectedFips. That's handled elsewhere.\r\n if (\r\n _.size(self.model.changed) === 1 &&\r\n self.model.changed.hasOwnProperty(\"selectedFips\")\r\n ) {\r\n return;\r\n }\r\n\r\n // Assemble a filter string from the model\r\n var parms = ccc.viz.util.UrlParams;\r\n\r\n var filterString = new velir.collections.NameValueCollection();\r\n filterString.add(parms.indicator, self.model.get(\"indicatorId\"));\r\n filterString.add(parms.timeFrame, self.model.get(\"timeFrameId\"));\r\n filterString.add(parms.locationType, self.model.get(\"locationTypeId\"));\r\n filterString.add(parms.dataFormat, self.model.get(\"dataFormatId\"));\r\n filterString.add(\r\n parms.characteristic,\r\n self.model.get(\"characteristicId\")\r\n );\r\n\r\n self.updateChoropleth(filterString.toQueryString());\r\n });\r\n },\r\n\r\n\t\toverlayMarkers: null,\r\n\t\tremoveOverlayMarkers: function () {\r\n\t\t\t// Remove old markers\r\n\t\t\tif (this.overlayMarkers) {\r\n\t\t\t\tfor (var index in this.overlayMarkers) {\r\n\t\t\t\t\tvar marker = this.overlayMarkers[index];\r\n\t\t\t\t\tif (marker && marker.setMap) {\r\n\t\t\t\t\t\tmarker.setMap(null);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tsetLabelTimeout: function(cb) {\r\n\t\t\t// Google maps renders map markers (like our labels) asynchronously\r\n\t\t\t// There's no event to tell when it's done - \"idle\" doesn't work\r\n\t\t\t// So we just have to wait, based on how many markers there are\r\n\t\t\tvar delayPerMarker = 13; // 10 works fine, but include a buffer\r\n\t\t\tvar delayLength = this.overlayMarkers.length * delayPerMarker;\r\n\t\t\tconsole.debug('delayLength', delayLength);\r\n\t\t\tsetTimeout(cb, delayLength);\r\n\t\t},\r\n\r\n\t\tgetOverlayLabels: function () {\r\n\t\t\tconsole.debug('getOverlayLabels start');\r\n\t\t\tlet self = this;\r\n\t\t\tself.removeOverlayMarkers();\r\n\r\n\t\t\tconst zipcodesOverlayTypeId = '4';\r\n\t\t\tvar labelClasslist = \"overlay-label overlay-label--hidden\"; // Hide labels until we're ready, once LabelGun prunes the overlapping ones\r\n\t\t\tvar overlayTypeIds = self.model.get(\"overlays\");\r\n\t\t\tif (overlayTypeIds === zipcodesOverlayTypeId ||\r\n\t\t\t\t(overlayTypeIds instanceof Array && overlayTypeIds.indexOf(zipcodesOverlayTypeId) >= 0)) {\r\n\t\t\t\tlabelClasslist += \" overlay-label--small\"; // Use smaller font for zipcodes\r\n\t\t\t}\r\n\r\n\t\t\t// global\r\n\t\t\tvar hardcodedPositions = hardcodedPositionsByOverlayTypeAndFips;\r\n\t\t\t$.get('/api/MapData/GetWktOverlayPolygons?overlays=' + self.model.get(\"overlays\")).done(function successCallback(response) {\r\n\t\t\t\t// Remove references to the marker objects as late as possible\r\n\t\t\t\t// Doing this too soon results in orphaned markers after zooming\r\n\t\t\t\t// This is because Google's setMap() is asynchronous\r\n\t\t\t\t// See removeOverlayMarkers()\r\n\t\t\t\tself.overlayMarkers = [];\r\n\r\n\t\t\t\t// Parse out the WKT text into google API Polygons\r\n\t\t\t\tfor (var key in response) {\r\n\t\t\t\t\tvar tokens = key.split('|');\r\n\t\t\t\t\tvar overlayType = tokens[0];\r\n\t\t\t\t\tvar fips = tokens[1];\r\n\t\t\t\t\tvar labelContent = tokens[2];\r\n\r\n\t\t\t\t\tvar wktPolygon = response[key];\r\n\r\n\t\t\t\t\tvar polygon = ccc.viz.map.util.MapUtil.wktToGooglePolygon(wktPolygon);\r\n\r\n\t\t\t\t\t// Add label marker for the polygon\r\n\t\t\t\t\tvar noIcon = {\r\n\t\t\t\t\t\t// Workaround to have a label with no icon\r\n\t\t\t\t\t\tpath: google.maps.SymbolPath.CIRCLE,\r\n\t\t\t\t\t\tscale: 0\r\n\t\t\t\t\t};\t\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t// account for label text itself\r\n\t\t\t\t\tvar labelXOffset = 5 + fips.length * 4;\r\n\t\t\t\t\tvar labelYOffset = 15;\r\n\t\t\t\t\tvar labelAnchor = new google.maps.Point(labelXOffset, labelYOffset);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// hardcoded positions\r\n\t\t\t\t\tvar positions;\r\n\t\t\t\t\tif (hardcodedPositions && hardcodedPositions[overlayType]) {\r\n\t\t\t\t\t\tpositions = hardcodedPositions[overlayType][fips];\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (!positions || !positions.length) {\r\n\t\t\t\t\t\t// Just need one label - position over geometric center\r\n\t\t\t\t\t\tvar center = polygon.getBounds().getCenter();\r\n\t\t\t\t\t\tself.overlayMarkers.push(new MarkerWithLabel({\r\n\t\t\t\t\t\t\tmap: self.gmap,\r\n\t\t\t\t\t\t\tposition: center,\r\n\t\t\t\t\t\t\ticon: noIcon,\r\n\t\t\t\t\t\t\tlabelContent: labelContent,\r\n\t\t\t\t\t\t\tlabelAnchor: labelAnchor,\r\n\t\t\t\t\t\t\tlabelClass: labelClasslist\r\n\t\t\t\t\t\t}));\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tpositions.forEach(position => {\r\n\t\t\t\t\t\t\t// Hardcoded positions, and possibly multiple labels for gerrymandered/multipolygon locations\r\n\t\t\t\t\t\t\tself.overlayMarkers.push(new MarkerWithLabel({\r\n\t\t\t\t\t\t\t\tmap: self.gmap,\r\n\t\t\t\t\t\t\t\tposition: new google.maps.LatLng(position[0], position[1]),\r\n\t\t\t\t\t\t\t\ticon: noIcon,\r\n\t\t\t\t\t\t\t\tlabelContent: labelContent,\r\n\t\t\t\t\t\t\t\tlabelAnchor: labelAnchor,\r\n\t\t\t\t\t\t\t\tlabelClass: labelClasslist\r\n\t\t\t\t\t\t\t}));\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t} // foreach key in response\r\n\r\n\t\t\t\tself.renderLabels(true);\r\n\r\n\t\t\t\tconsole.debug('getOverlayLabels end');\r\n\t\t\t}) // $.get().done()\r\n\t\t\t.fail(function errorCallback(err) {\r\n\t\t\t\tconsole.error('Failed to get overlay label data. ', err);\r\n\t\t\t}); // $.get()\r\n\t\t}, // getOverlayLabels()\r\n\r\n updateChoropleth: function(filterString) {\r\n var self = this;\r\n\r\n // Assume the choropleth layer is the bottom-most overlay. Remove the old layer\r\n if (this.gmap.overlayMapTypes.length > 0) {\r\n this.gmap.overlayMapTypes.removeAt(0);\r\n }\r\n\r\n // Add a new layer\r\n this.choroplethOverlay = new ccc.viz.map.layers.ChoroplethMapType(\r\n this.gmap.getProjection(),\r\n filterString,\r\n self.model.get(\"overlays\")\r\n );\r\n this.gmap.overlayMapTypes.push(this.choroplethOverlay);\r\n\r\n\t\t\tself.getOverlayLabels();\r\n\r\n // Emit an event whenever the layer is busy\r\n $(this.choroplethOverlay).bind(\"busy\", function() {\r\n self.dispatcher.trigger(\"add-task\", {\r\n name: \"choropleth-overlay-busy\",\r\n text: \"Updating data overlay...\"\r\n });\r\n });\r\n\r\n // Emit an event when the layer finishes loading\r\n $(this.choroplethOverlay).bind(\"idle\", function() {\r\n self.dispatcher.trigger(\"end-all-tasks\", {\r\n name: \"choropleth-overlay-busy\"\r\n });\r\n self.initialRenderPromise.resolve();\r\n });\r\n },\r\n\r\n findAndDrawPolygon: function(mouseEvent) {\r\n var self = this;\r\n\r\n var locTypeId = self.model.get(\"locationTypeId\");\r\n this.polygonManager.findPolygon(locTypeId, mouseEvent.latLng, function(\r\n polygonInfo\r\n ) {\r\n function removePrevious() {\r\n // Remove any previously drawn polygon\r\n if (self.currentMouseover && self.currentMouseover.polygon) {\r\n self.currentMouseover.polygon.setMap(null);\r\n self.currentMouseover = null;\r\n }\r\n }\r\n\r\n // Not over a polygon currently?\r\n if (polygonInfo == null) {\r\n removePrevious();\r\n self.dispatcher.trigger(\"mouseoff-region\");\r\n return;\r\n }\r\n\r\n // Over a polygon? Is it still the same one?\r\n if (\r\n self.currentMouseover &&\r\n polygonInfo.fips == self.currentMouseover.fips\r\n ) {\r\n // Nothing to do\r\n return;\r\n }\r\n\r\n // Over a polygon, but NOT the same one? Remove the previous and draw the new!\r\n removePrevious();\r\n polygonInfo.polygon.setOptions(\r\n ccc.viz.map.config.MapConfig.mouseoverPolygonStyle\r\n );\r\n polygonInfo.polygon.setMap(self.gmap);\r\n\r\n // Events don't propagate\r\n google.maps.event.addListener(\r\n polygonInfo.polygon,\r\n \"click\",\r\n self.selectRegion\r\n );\r\n self.currentMouseover = polygonInfo;\r\n self.dispatcher.trigger(\"mouseover-region\", {\r\n latLng: mouseEvent.latLng,\r\n gmap: self.gmap,\r\n fips: polygonInfo.fips\r\n });\r\n });\r\n },\r\n\r\n selectRegion: function(mouseEvent) {\r\n var self = this;\r\n\r\n // Figure out where the user clicked and highlight/dehighlight regions as necessary\r\n var locTypeId = self.model.get(\"locationTypeId\");\r\n this.polygonManager.findPolygon(locTypeId, mouseEvent.latLng, function(\r\n polygonInfo\r\n ) {\r\n // Update the model\r\n var selectedFips = polygonInfo == null ? null : polygonInfo.fips;\r\n\r\n // If we clicked on NYC data, don't show anything else, since NYC data is already showing.\r\n if (+selectedFips === ccc.viz.map.config.MapConfig.nycFips) {\r\n $(\".legend-column\")\r\n .stop(true, true)\r\n .hide()\r\n .fadeIn();\r\n return;\r\n }\r\n\r\n self.model.set(\"selectedFips\", selectedFips);\r\n\r\n var response = self.dataResponseModel.get(\"response\");\r\n self.debounceExploreMapClicks(response, selectedFips);\r\n });\r\n },\r\n\r\n debounceExploreMapClicks: _.debounce(pushNycClick, 100),\r\n\r\n drawSelectedRegion: function() {\r\n var self = this;\r\n\r\n function removePrevious() {\r\n // Remove the polygon for the previously selected region, if any\r\n if (self.selectedPolygonInfo) {\r\n self.selectedPolygonInfo.innerPolygon.setMap(null);\r\n self.selectedPolygonInfo.outerPolygon.setMap(null);\r\n self.selectedPolygonInfo = null;\r\n }\r\n }\r\n\r\n // Anything selected?\r\n // NOTE: \"a\" is the default value in the URL for when a parameter is absent. That _shouldn't_\r\n // NOTE: won't collide with any actual FIPS values. Probably.\r\n var fips = this.model.get(\"selectedFips\");\r\n if (fips == null || fips == \"a\") {\r\n removePrevious();\r\n return;\r\n }\r\n\r\n // We have something selected, but was it already selected?\r\n if (self.selectedPolygonInfo && self.selectedPolygonInfo.fips === fips) {\r\n // Nothing to do\r\n return;\r\n }\r\n\r\n // New selection. Find the polygon for the selected fips and draw it\r\n var locTypeId = self.model.get(\"locationTypeId\");\r\n this.polygonManager.getPolygonByFips(locTypeId, fips, function(\r\n polygonInfo\r\n ) {\r\n if (!polygonInfo) {\r\n throw \"Could not find polygon for FIPS: \" + fips;\r\n }\r\n\r\n // PolygonManager returns the same polygon instance every time, so clone the polygon\r\n // so that it's a different instance than the one drawn for mouseovers\r\n polygonInfo.innerPolygon = self.polygonManager.clonePolygon(\r\n polygonInfo.polygon\r\n );\r\n polygonInfo.outerPolygon = self.polygonManager.clonePolygon(\r\n polygonInfo.polygon\r\n );\r\n\r\n // Draw it\r\n removePrevious();\r\n polygonInfo.innerPolygon.setOptions(\r\n ccc.viz.map.config.MapConfig.selectedPolygonInnerStyle\r\n );\r\n polygonInfo.innerPolygon.setMap(self.gmap);\r\n polygonInfo.outerPolygon.setOptions(\r\n ccc.viz.map.config.MapConfig.selectedPolygonOuterStyle\r\n );\r\n polygonInfo.outerPolygon.setMap(self.gmap);\r\n self.selectedPolygonInfo = polygonInfo;\r\n });\r\n }\r\n });\r\n})(jQuery, _, velir, google);\r\n","(function($, _) {\r\n var ns = window.nspace(\"ccc.viz.map.views\");\r\n\r\n ns.LegendView = Backbone.View.extend({\r\n\t\tel: \".legend-container\",\r\n name: \"Map Legend\",\r\n templateSelector: \"#map-legend-template\",\r\n\r\n initialize: function() {\r\n this.initialRenderPromise = $.Deferred();\r\n\r\n var self = this;\r\n _.bindAll(this, \"render\");\r\n\r\n this.model.on(\"change\", this.render);\r\n\r\n // Hide the legend text when a data update begins\r\n this.model.on(\"busy\", function() {\r\n self.$el.hide();\r\n });\r\n\r\n // Show/hide the legend when the \"+/-\" button is clicked\r\n this.$el.on(\"click\", \".icon-map-overlay-control\", function(e) {\r\n e.preventDefault();\r\n\r\n\t\t\t\tvar $content = $(\".map-overlay-content\", $(e.target).parents('.map-overlay-block'));\r\n if (self.isClosed) {\r\n // Show it\r\n\t\t\t\t\t$content.parent().removeClass(\"closed\");\r\n $content.slideDown();\r\n self.isClosed = false;\r\n }\r\n else {\r\n // Hide it\r\n $content.slideUp(400, function() {\r\n\t\t\t\t\t\t$content.parent().addClass(\"closed\");\r\n });\r\n self.isClosed = true;\r\n }\r\n\t\t\t});\r\n\r\n\t\t\t// Boundary overlay radios\r\n\t\t\t$('.boundary-radio-hidden, .boundary-radio-hidden::before, .boundary-overlay-list label').click(function (e) {\r\n\t\t\t\t$('.boundary-radio-hidden').each(function (_index, radio) {\r\n\t\t\t\t\t$(radio).prop('checked', null);\r\n\t\t\t\t});\r\n\r\n\t\t\t\tvar $hiddenRadio = $('.boundary-radio-hidden', $(e.target).parents('li'));\r\n\t\t\t\t$hiddenRadio.prop('checked', true);\r\n\t\t\t\tvar radioVal = $hiddenRadio.val();\r\n\t\t\t\tvar overlayVals = radioVal\r\n\t\t\t\t\t? [radioVal]\r\n\t\t\t\t\t: null;\r\n\t\t\t\tself.model.selectorsModel.set('overlays', overlayVals);\r\n\t\t\t\r\n\t\t\t\te.preventDefault();\r\n\t\t\t\te.stopPropagation();\r\n });\r\n },\r\n\r\n getInitialRenderPromise: function() {\r\n return this.initialRenderPromise;\r\n },\r\n\r\n render: function() {\r\n var self = this;\r\n\r\n // Verify this selector's container is present\r\n if (this.$el.length === 0) {\r\n throw \"Could not find the \" + this.name + \" container: \" + this.el;\r\n }\r\n\r\n // Get the underscore template\r\n if (!this.template) {\r\n var $tmpl = $(this.templateSelector);\r\n if ($tmpl.length === 0) {\r\n throw \"Could not find the \" +\r\n this.name +\r\n \" template: \" +\r\n this.templateSelector;\r\n }\r\n\r\n // Pre-compile and cache the template\r\n this.template = _.template($tmpl.html());\r\n }\r\n\t\t\t\r\n\t\t\tvar overlay = self.model.selectorsModel.get('overlays');\r\n\t\t\tif (overlay) {\r\n\t\t\t\t// Update boundary overlay legend so it reflects the model\r\n\t\t\t\tvar overlayId = Array.isArray(overlay)\r\n\t\t\t\t\t? overlay[0]\r\n\t\t\t\t\t: overlay.toString().split(',')[0]; \r\n\t\t\t\t$('.boundary-radio-hidden[value=\"' + overlayId + '\"]')\r\n\t\t\t\t\t.prop('checked', true);\r\n\t\t\t}\r\n // Pull the data summary from the global selector instance.\r\n var summary = ccc.viz.map.views.instances.SelectorsView.getDataSummaryByPropertyNames(\r\n [\"indicatorId\"]\r\n );\r\n var response = this.model.get(\"response\");\r\n if (_.str.isBlank(summary)) {\r\n summary = response.indicatorGroupName;\r\n }\r\n\r\n var largerValuesAre = response.largerValuesAre;\r\n\r\n var themeColors = ccc.viz.map.config.MapConfig.themeColors.slice();\r\n\r\n if (largerValuesAre == \"worse\" || largerValuesAre == \"neutral\") {\r\n themeColors = themeColors.reverse();\r\n }\r\n\r\n // Re-render the control\r\n var legendInfo = _.extend(\r\n {\r\n title: summary,\r\n themeColors: themeColors,\r\n largerValuesAre: largerValuesAre\r\n },\r\n response\r\n );\r\n\r\n // Allow the container to resize to fit content once again\r\n this.$el.css(\"height\", \"auto\");\r\n this.$el.css(\"width\", \"auto\");\r\n\r\n // Insert the new HTML\r\n\t\t\t$('.map-legend-container').remove();\r\n\t\t\tthis.$el.prepend(this.template(legendInfo));\r\n\r\n // Show it\r\n if (this.isClosed) {\r\n $(\".map-overlay-content\", self.el).hide();\r\n }\r\n this.$el.fadeIn(600, function() {\r\n self.initialRenderPromise.resolve();\r\n });\r\n }\r\n });\r\n})(jQuery, _);\r\n","(function($, _) {\r\n var ns = window.nspace(\"ccc.viz.map.views\");\r\n\r\n /**\r\n * Figures out which legend range this data point falls into\r\n */\r\n function findThemeColor(dataPoint, ranges, largerValuesAre) {\r\n for (var i = 0; i < ranges.length; i++) {\r\n var range = ranges[i];\r\n var min = parseFloat(range.minValue);\r\n var max = parseFloat(range.maxValue);\r\n var dp = parseFloat(dataPoint);\r\n\r\n // Assumes non-continuous legend breaks\r\n if (min <= dp && max >= dp) {\r\n break;\r\n }\r\n }\r\n\r\n if (i >= ranges.length) {\r\n i = ranges.length - 1;\r\n }\r\n\r\n var themeColors = ccc.viz.map.config.MapConfig.themeColors.slice();\r\n\r\n if (largerValuesAre == \"worse\" || largerValuesAre == \"neutral\") {\r\n return themeColors.reverse()[i].color;\r\n }\r\n else {\r\n return themeColors[i].color;\r\n }\r\n }\r\n\r\n /**\r\n * Displays a popup with a data point when mouse is hovering over a location\r\n */\r\n ns.MapPopupView = Backbone.View.extend({\r\n el: \"#map-popup-container\",\r\n templateSelector: \"#map-popup-template\",\r\n name: \"Map Popup\",\r\n\r\n initialize: function(options) {\r\n _.bindAll(this, \"showPopup\", \"hidePopup\", \"loadTemplate\");\r\n\r\n this.selectorsModel = options.selectorsModel;\r\n\r\n this.dispatcher = options.dispatcher;\r\n this.dispatcher.on(\"mouseoff-region\", this.hidePopup);\r\n this.dispatcher.on(\"mouseover-region\", this.showPopup);\r\n\r\n this.offset = {\r\n x: 0,\r\n y: 30\r\n };\r\n },\r\n\r\n loadTemplate: function() {\r\n // Get the underscore template\r\n if (!this.template) {\r\n var $tmpl = $(this.templateSelector);\r\n if ($tmpl.length === 0) {\r\n throw \"Could not find the \" +\r\n this.name +\r\n \" template: \" +\r\n this.templateSelector;\r\n }\r\n\r\n // Pre-compile and cache the template\r\n this.template = _.template($tmpl.html());\r\n }\r\n },\r\n\r\n /**\r\n * Hides the map popup\r\n */\r\n hidePopup: function(args) {\r\n this.$el.hide();\r\n },\r\n\r\n /**\r\n * Displays the map popup\r\n */\r\n showPopup: function(args) {\r\n this.loadTemplate();\r\n\r\n var self = this;\r\n\r\n // Get the data to display\r\n var fips = args.fips;\r\n $.when(this.model.getDataResponse()).done(function() {\r\n var response = self.model.get(\"response\");\r\n var tfId = self.selectorsModel.get(\"timeFrameId\");\r\n var allYearsData = response.dataByFips[fips];\r\n var data = !allYearsData\r\n ? null\r\n : _.find(allYearsData, function(yearData) {\r\n return yearData.timeFrameId === tfId;\r\n });\r\n\r\n var location = response.locationsByFips[fips];\r\n\r\n var largerValuesAre = response.largerValuesAre;\r\n\r\n // Defaults\r\n var templateData = {\r\n name: \"Location\",\r\n text: \"No Data\",\r\n swatchColor: \"#fff\",\r\n rank: \"\"\r\n };\r\n\r\n // Fill out the actual values\r\n if (location) {\r\n templateData.name = location.name;\r\n\r\n\t\t if (location.isUninhabited) {\r\n\t\t\ttemplateData.text = \"Area is uninhabited\";\r\n\t\t\ttemplateData.swatchColor = \"gray\";\r\n\t\t }\r\n }\r\n\r\n if (data) {\r\n templateData.text = data.displayValue;\r\n templateData.swatchColor = findThemeColor(\r\n data.value,\r\n response.ranges,\r\n largerValuesAre\r\n );\r\n templateData.rank = _.str.isBlank(data.rank)\r\n ? \"\"\r\n : \"Ranks \" + data.rank;\r\n }\r\n\r\n // Rerender the popup contents\r\n var html = self.template(templateData);\r\n self.$el.html(html);\r\n\r\n // Reposition the popup\r\n var pixels = ccc.viz.map.util.MapUtil.fromPositionToRelativePixels(\r\n args.gmap,\r\n args.latLng\r\n );\r\n\r\n // Move left/right/up/down based on the configured offset\r\n pixels.x += self.offset.x;\r\n pixels.y += self.offset.y;\r\n\r\n // Move west by half the width of the popup in order to center it under cursor\r\n pixels.x -= self.$el.outerWidth() / 2;\r\n\r\n // Set the position\r\n self.$el.css({\r\n left: pixels.x,\r\n top: pixels.y\r\n });\r\n\r\n // Quick fade in\r\n self.$el.stop(true, true).fadeIn(200);\r\n });\r\n }\r\n });\r\n})(jQuery, _);\r\n","/// \r\n/// \r\n\r\n(function($, _) {\r\n var ns = window.nspace(\"ccc.viz.map.views\");\r\n\r\n /**\r\n * Displays the \"NYC summary\" view on the right hand side of the map that\r\n * shows data for New York City for the current selections, if available\r\n */\r\n ns.NYCSummaryView = ns.SelectedRegionSummaryView.extend({\r\n el: \"#nyc-summary-container\",\r\n name: \"NYCSummaryView\",\r\n sparklineContainerId: \"nyc-chart-container\",\r\n\r\n getCurrentFips: function() {\r\n return ccc.viz.util.Fips.NYC;\r\n },\r\n\r\n render: function() {\r\n // Don't bother rerendering if the only thing that changed was the selected region\r\n if (\r\n _.size(this.model.changed) === 1 &&\r\n this.model.changed.hasOwnProperty(\"selectedFips\")\r\n ) {\r\n return;\r\n }\r\n\r\n // Call the base implementation\r\n ns.NYCSummaryView.__super__.render.call(this);\r\n },\r\n\r\n overrideDisplayValues: function(displayVals) {\r\n var response = this.dataResponseModel.get(\"response\");\r\n var tfId = this.model.get(\"timeFrameId\");\r\n\r\n displayVals.rankSubtext = \"In \" + response.timeFramesById[tfId].name;\r\n }\r\n });\r\n})(jQuery, _);\r\n","/// \r\n/// \r\n\r\n(function($, _) {\r\n var ns = window.nspace(\"ccc.viz.map.views\");\r\n\r\n /**\r\n * Displays the \"NYC table\" view on the right hand side of the map that\r\n * shows data for New York City for the current selections, if available\r\n */\r\n ns.NYCTableView = ns.SelectedRegionTableView.extend({\r\n el: \"#nyc-table-container\",\r\n name: \"NYCTableView\",\r\n\r\n getCurrentFips: function() {\r\n return ccc.viz.util.Fips.NYC;\r\n },\r\n\r\n render: function() {\r\n // Don't bother rerendering if the only thing that changed was the selected region\r\n if (\r\n _.size(this.model.changed) === 1 &&\r\n this.model.changed.hasOwnProperty(\"selectedFips\")\r\n ) {\r\n return;\r\n }\r\n\r\n // Call the base implementation\r\n ns.NYCTableView.__super__.render.call(this);\r\n }\r\n });\r\n})(jQuery, _);\r\n","(function($, _) {\r\n var ns = window.nspace(\"ccc.viz.map.views\");\r\n\r\n ns.OverlayView = Backbone.View.extend({\r\n el: \"#overlay\",\r\n name: \"No Data Overlay\",\r\n\r\n initialize: function() {\r\n var self = this;\r\n _.bindAll(this, \"render\");\r\n\r\n this.model.on(\"change\", this.render);\r\n\r\n // Hide the legend text when a data update begins\r\n this.model.on(\"busy\", function() {\r\n self.$el.hide();\r\n });\r\n },\r\n\r\n render: function() {\r\n this.$el.toggle(!this.model.hasData());\r\n }\r\n });\r\n})(jQuery, _);\r\n","/// \r\n/// \r\n\r\n(function($, _) {\r\n var ns, selectorNs, sharedNs;\r\n\r\n ns = window.nspace(\"ccc.viz.map.views\");\r\n selectorNs = window.nspace(\"ccc.viz.selectors\");\r\n sharedNs = window.nspace(\"ccc.viz.shared\");\r\n\r\n ns.SelectorsView = sharedNs.views.SelectorsView.extend({\r\n initializeView: function() {\r\n // Expose this (presumably one-and-only) instance globally so that other components\r\n // can call \"public\" methods and get selector information, such as for building data summaries.\r\n // TODO: What's a better way to architect this?\r\n ns.instances = { SelectorsView: this };\r\n\r\n this.model.on(\"change\", this.updateDataSummary);\r\n },\r\n\r\n initializeSelectors: function() {\r\n this.allSelectors = [\r\n // Breakdown\r\n (this.breakdownSelectorView = new selectorNs.BreakdownSelectorView({\r\n model: this.model\r\n })),\r\n // Location Type\r\n (this.locationTypeSelectorView = new selectorNs.LocationTypeSelectorView(\r\n {\r\n model: this.model\r\n }\r\n )),\r\n // Data Type\r\n (this.dataTypeSelectorView = new selectorNs.DataTypeSelectorView({\r\n model: this.model,\r\n getSelectorInfoFunc: this.getSelectorInfoOrThrow\r\n })),\r\n // TimeFrame\r\n (this.timeFrameSelectorView = new selectorNs.TimeFrameSelectorView({\r\n model: this.model,\r\n getSelectorInfoFunc: this.getSelectorInfoOrThrow\r\n }))\r\n ];\r\n },\r\n\r\n getIndicatorSpecificSelectors: function() {\r\n return [\r\n this.locationTypeSelectorView,\r\n this.dataTypeSelectorView,\r\n this.timeFrameSelectorView\r\n ];\r\n },\r\n\r\n updateDataSummary: function() {\r\n $(\"#data-summary\").text(this.getDataSummary());\r\n }\r\n });\r\n})(jQuery, _);\r\n","(function($, _) {\r\n var ns = window.nspace(\"ccc.viz.map.views\");\r\n\r\n /**\r\n * The \"select a region from the map\" prompt that appears when no location is selected\r\n */\r\n ns.SelectRegionPromptView = Backbone.View.extend({\r\n el: \"#select-region-prompt-container\",\r\n name: \"Select a Region prompt\",\r\n\r\n initialize: function() {\r\n _.bindAll(this, \"render\");\r\n\r\n this.initialRenderPromise = $.Deferred();\r\n\r\n this.model.on(\"change:selectedFips change:locationTypeId\", this.render);\r\n // Toggle the prompt the first time we come to the page.\r\n this.model.once(\"change\", this.render);\r\n this.render();\r\n },\r\n\r\n getInitialRenderPromise: function() {\r\n return this.initialRenderPromise;\r\n },\r\n\r\n render: function() {\r\n var self = this;\r\n\r\n // Verify this selector's container is present\r\n if (this.$el.length === 0) {\r\n throw \"Could not find the \" + this.name + \" container: \" + this.el;\r\n }\r\n\r\n if (\r\n +this.model.get(\"locationTypeId\") ===\r\n ccc.viz.map.config.MapConfig.nycLocationId\r\n ) {\r\n self.$el.hide();\r\n self.initialRenderPromise.resolve();\r\n return;\r\n }\r\n\r\n var fips = this.model.get(\"selectedFips\");\r\n if (!fips || fips === \"a\") {\r\n self.$el.hide().fadeIn(function() {\r\n self.initialRenderPromise.resolve();\r\n });\r\n }\n else {\r\n self.$el.hide();\r\n self.initialRenderPromise.resolve();\r\n }\r\n }\r\n });\r\n})(jQuery, _);\r\n"]}