/*! * dc.leaflet 0.4.0 * http://dc-js.github.io/dc.leaflet.js/ * Copyright 2014-2015 Boyan Yurukov and the dc.leaflet Developers * https://github.com/dc-js/dc.leaflet.js/blob/master/AUTHORS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function() { function _dc_leaflet(dc) { 'use strict'; var dc_leaflet = { version: '0.4.0' }; dc_leaflet.leafletBase = function(_chart) { _chart = dc.marginMixin(dc.baseChart(_chart)); _chart.margins({left:0, top:0, right:0, bottom:0}); var _map; var _mapOptions=false; var _defaultCenter=false; var _defaultZoom=false; var _cachedHandlers = {}; var _createLeaflet = function(root) { // append sub-div if not there, to allow client to put stuff (reset link etc.) // in main div. might also use relative positioning here, for now assume // appending will put in right position var child_div = root.selectAll('div.dc-leaflet'); child_div = child_div.data([0]).enter() .append('div').attr('class', 'dc-leaflet') .style('width', _chart.effectiveWidth() + "px") .style('height', _chart.effectiveHeight() + "px") .merge(child_div); return L.map(child_div.node(),_mapOptions); }; var _tiles=function(map) { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); }; _chart.createLeaflet = function(_) { if(!arguments.length) { return _createLeaflet; } _createLeaflet = _; return _chart; }; _chart._doRender = function() { if(! _chart.map()){ _map = _createLeaflet(_chart.root()); for(var ev in _cachedHandlers) _map.on(ev, _cachedHandlers[ev]); if (_defaultCenter && _defaultZoom) { _map.setView(_chart.toLocArray(_defaultCenter), _defaultZoom); } _chart.tiles()(_map); _chart._postRender(); } else console.warn("WARNING: Leaflet map already rendered."); return _chart._doRedraw(); }; _chart._doRedraw = function() { return _chart; }; _chart._postRender = function() { return _chart; }; _chart.mapOptions = function(_) { if (!arguments.length) { return _mapOptions; } _mapOptions = _; return _chart; }; _chart.center = function(_) { if (!arguments.length) { return _defaultCenter; } _defaultCenter = _; return _chart; }; _chart.zoom = function(_) { if (!arguments.length) { return _defaultZoom; } _defaultZoom = _; return _chart; }; _chart.tiles = function(_) { if (!arguments.length) { return _tiles; } _tiles = _; return _chart; }; _chart.map = function() { return _map; }; _chart.toLocArray = function(value) { if (typeof value === "string") { // expects '11.111,1.111' value = value.split(","); } // else expects [11.111,1.111] return value; }; // combine Leaflet events into d3 & dc events dc.override(_chart, 'on', function(event, callback) { var leaflet_events = ['zoomend', 'moveend']; if(leaflet_events.indexOf(event) >= 0) { if(_map) { _map.on(event, callback); } else { _cachedHandlers[event] = callback; } return this; } else return _chart._on(event, callback); }); return _chart; }; //Legend code adapted from http://leafletjs.com/examples/choropleth.html dc_leaflet.legend = function() { var _parent, _legend = {}; var _leafletLegend = null; var _position = 'bottomleft'; _legend.parent = function (parent) { if(!arguments.length) return _parent; _parent = parent; return this; }; function _LegendClass() { return L.Control.extend({ options: {position: _position}, onAdd: function (map) { this._div = L.DomUtil.create('div', 'info legend'); map.on('moveend',this._update,this); this._update(); return this._div; }, _update: function () { if (!_parent.colorDomain) console.warn('legend not supported for this dc.leaflet chart type, ignoring'); else { var minValue = _parent.colorDomain()[0], maxValue = _parent.colorDomain()[1], palette = _parent.colors().range(), colorLength = _parent.colors().range().length, delta = (maxValue - minValue)/colorLength, i; // define grades for legend colours // based on equation in dc.js colorCalculator (before version based on colorMixin) var grades = []; grades[0] = Math.round(minValue); for (i= 1; i < colorLength; i++) { grades[i] = Math.round((0.5 + (i - 1)) * delta + minValue); } var div = L.DomUtil.create('div', 'info legend'); // loop through our density intervals and generate a label with a colored // square for each interval this._div.innerHTML = ""; //reset so that legend is not plotted multiple times for (i = 0; i < grades.length; i++) { this._div.innerHTML += ' ' + grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '
' : '+'); } } } }); } _legend.LegendClass = function(LegendClass) { if(!arguments.length) return _LegendClass; _LegendClass = LegendClass; return _legend; }; _legend.render = function () { // unfortunately the dc.js legend has no concept of redraw, it's always render if(!_leafletLegend) { // fetch the legend class creator, invoke it var Legend = _legend.LegendClass()(); // and constuct that class _leafletLegend = new Legend(); _leafletLegend.addTo(_parent.map()); } return _legend.redraw(); }; _legend.redraw = function () { _leafletLegend._update(); return _legend; }; _legend.leafletLegend = function () { return _leafletLegend; }; _legend.position = function (position) { if(!arguments.length) return _position; _position = position; return _legend; }; return _legend; }; dc_leaflet.markerChart = function(parent, chartGroup) { var _chart = dc_leaflet.leafletBase({}); var _renderPopup = true; var _cluster = false; // requires leaflet.markerCluster var _clusterOptions=false; var _rebuildMarkers = false; var _brushOn = true; var _filterByArea = false; var _filter; var _innerFilter=false; var _zooming=false; var _layerGroup = false; var _markerList = []; var _currentGroups=false; _chart.renderTitle(true); var _location = function(d) { return _chart.keyAccessor()(d); }; var _marker = function(d, map) { var marker = new L.Marker(_chart.toLocArray(_chart.locationAccessor()(d)), { title: _chart.renderTitle() ? _chart.title()(d) : '', alt: _chart.renderTitle() ? _chart.title()(d) : '', icon: _icon(d, map), clickable: _chart.renderPopup() || (_chart.brushOn() && !_filterByArea), draggable: false }); return marker; }; var _icon = function(d, map) { return new L.Icon.Default(); }; var _popup = function(d, marker) { return _chart.title()(d); }; _chart._postRender = function() { if (_chart.brushOn()) { if (_filterByArea) { _chart.filterHandler(doFilterByArea); } _chart.map().on('zoomend moveend', zoomFilter, this ); if (!_filterByArea) _chart.map().on('click', zoomFilter, this ); _chart.map().on('zoomstart', zoomStart, this); } if (_cluster) { _layerGroup = new L.MarkerClusterGroup(_clusterOptions?_clusterOptions:null); } else { _layerGroup = new L.LayerGroup(); } _chart.map().addLayer(_layerGroup); }; _chart._doRedraw = function() { var groups = _chart._computeOrderedGroups(_chart.data()).filter(function (d) { return _chart.valueAccessor()(d) !== 0; }); if (_currentGroups && _currentGroups.toString() === groups.toString()) { return; } _currentGroups=groups; if (_rebuildMarkers) { _markerList=[]; } _layerGroup.clearLayers(); var addList=[]; groups.forEach(function(v, i) { var key = _chart.keyAccessor()(v); var marker = null; if (!_rebuildMarkers && key in _markerList) { marker = _markerList[key]; } else { marker = createmarker(v, key); } if (!_chart.cluster()) { _layerGroup.addLayer(marker); } else { addList.push(marker); } }); if (_chart.cluster() && addList.length > 0) { _layerGroup.addLayers(addList); } }; _chart.locationAccessor = function(_) { if (!arguments.length) { return _location; } _location= _; return _chart; }; _chart.marker = function(_) { if (!arguments.length) { return _marker; } _marker= _; return _chart; }; _chart.icon = function(_) { if (!arguments.length) { return _icon; } _icon= _; return _chart; }; _chart.popup = function(_) { if (!arguments.length) { return _popup; } _popup= _; return _chart; }; _chart.renderPopup = function(_) { if (!arguments.length) { return _renderPopup; } _renderPopup = _; return _chart; }; _chart.cluster = function(_) { if (!arguments.length) { return _cluster; } _cluster = _; return _chart; }; _chart.clusterOptions = function(_) { if (!arguments.length) { return _clusterOptions; } _clusterOptions = _; return _chart; }; _chart.rebuildMarkers = function(_) { if (!arguments.length) { return _rebuildMarkers; } _rebuildMarkers = _; return _chart; }; _chart.brushOn = function(_) { if (!arguments.length) { return _brushOn; } _brushOn = _; return _chart; }; _chart.filterByArea = function(_) { if (!arguments.length) { return _filterByArea; } _filterByArea = _; return _chart; }; _chart.markerGroup = function() { return _layerGroup; }; var createmarker = function(v, k) { var marker = _marker(v, _chart.map()); marker.key = k; if (_chart.renderPopup()) { marker.bindPopup(_chart.popup()(v, marker)); } if (_chart.brushOn() && !_filterByArea) { marker.on("click", selectFilter); } _markerList[k]=marker; return marker; }; var zoomStart = function(e) { _zooming=true; }; var zoomFilter = function(e) { if (e.type === "moveend" && (_zooming || e.hard)) { return; } _zooming=false; if (_filterByArea) { var filter; if (_chart.map().getCenter().equals(_chart.center()) && _chart.map().getZoom() === _chart.zoom()) { filter = null; } else { filter = _chart.map().getBounds(); } dc.events.trigger(function () { _chart.filter(null); if (filter) { _innerFilter=true; _chart.filter(filter); _innerFilter=false; } dc.redrawAll(_chart.chartGroup()); }); } else if (_chart.filter() && (e.type === "click" || (_markerList.indexOf(_chart.filter()) !== -1 && !_chart.map().getBounds().contains(_markerList[_chart.filter()].getLatLng())))) { dc.events.trigger(function () { _chart.filter(null); if (_renderPopup) { _chart.map().closePopup(); } dc.redrawAll(_chart.chartGroup()); }); } }; var doFilterByArea = function(dimension, filters) { _chart.dimension().filter(null); if (filters && filters.length>0) { _chart.dimension().filterFunction(function(d) { if (!(d in _markerList)) { return false; } var locO = _markerList[d].getLatLng(); return locO && filters[0].contains(locO); }); if (!_innerFilter && _chart.map().getBounds().toString !== filters[0].toString()) { _chart.map().fitBounds(filters[0]); } } }; var selectFilter = function(e) { if (!e.target) return; var filter = e.target.key; dc.events.trigger(function () { _chart.filter(filter); dc.redrawAll(_chart.chartGroup()); }); }; return _chart.anchor(parent, chartGroup); }; dc_leaflet.choroplethChart = function(parent, chartGroup) { var _chart = dc.colorChart(dc_leaflet.leafletBase({})); var _geojsonLayer = false; var _dataMap = []; var _geojson = false; var _renderPopup = true; var _brushOn = true; var _featureOptions = { 'fillColor':'black', 'color':'gray', 'opacity':0.4, 'fillOpacity':0.6, 'weight':1 }; var _featureKey = function(feature) { return feature.key; }; var _featureStyle = function(feature) { var options = _chart.featureOptions(); if (options instanceof Function) { options=options(feature); } options = JSON.parse(JSON.stringify(options)); var v = _dataMap[_chart.featureKeyAccessor()(feature)]; if (v && v.d) { options.fillColor=_chart.getColor(v.d, v.i); if (_chart.filters().indexOf(v.d.key) !== -1) { options.opacity=0.8; options.fillOpacity=1; } } return options; }; var _popup = function(d, feature) { return _chart.title()(d); }; _chart._postRender = function() { _geojsonLayer=L.geoJson(_chart.geojson(), { style: _chart.featureStyle(), onEachFeature: processFeatures }); _chart.map().addLayer(_geojsonLayer); }; dc.override(_chart, '_doRedraw', function() { _geojsonLayer.clearLayers(); _dataMap=[]; _chart._computeOrderedGroups(_chart.data()).forEach(function (d, i) { _dataMap[_chart.keyAccessor()(d)] = {'d':d, 'i':i}; }); _geojsonLayer.addData(_chart.geojson()); return _chart.__doRedraw(); }); _chart.geojson = function(_) { if (!arguments.length) { return _geojson; } _geojson = _; return _chart; }; _chart.featureOptions = function(_) { if (!arguments.length) { return _featureOptions; } _featureOptions = _; return _chart; }; _chart.featureKeyAccessor = function(_) { if (!arguments.length) { return _featureKey; } _featureKey= _; return _chart; }; _chart.featureStyle = function(_) { if (!arguments.length) { return _featureStyle; } _featureStyle= _; return _chart; }; _chart.popup = function(_) { if (!arguments.length) { return _popup; } _popup= _; return _chart; }; _chart.renderPopup = function(_) { if (!arguments.length) { return _renderPopup; } _renderPopup = _; return _chart; }; _chart.brushOn = function(_) { if (!arguments.length) { return _brushOn; } _brushOn = _; return _chart; }; var processFeatures = function (feature, layer) { var v = _dataMap[_chart.featureKeyAccessor()(feature)]; if (v && v.d) { layer.key=v.d.key; if (_chart.renderPopup()) layer.bindPopup(_chart.popup()(v.d, feature)); if (_chart.brushOn()) layer.on("click", selectFilter); } }; var selectFilter = function(e) { if (!e.target) { return; } var filter = e.target.key; dc.events.trigger(function () { _chart.filter(filter); dc.redrawAll(_chart.chartGroup()); }); }; return _chart.anchor(parent, chartGroup); }; dc_leaflet.bubbleChart = function (parent, chartGroup) { "use strict"; /* #################################### * Private variables -- default values. * #################################### */ var _chart = dc_leaflet.leafletBase({}); var _selectedColor = 'blue'; var _unselectedColor = 'gray'; var _layerGroup = false; var _location = function (d) { return _chart.keyAccessor()(d); }; var _r = d3.scaleLinear().domain([0, 100]); var _brushOn = true; var _marker = function (d, map) { var loc = _chart.locationAccessor()(d); var locArray = _chart.toLocArray(loc); var latlng = L.latLng(+locArray[0], +locArray[1]); var circle = L.circleMarker(latlng); circle.setRadius(_chart.r()(_chart.valueAccessor()(d))); circle.on("mouseover", function (e) { // TODO - Tooltips! //console.log(_chart.title()(d)); }); var key = _chart.keyAccessor()(d); var isSelected = (-1 !== _chart.filters().indexOf(key)); circle.options.color = isSelected ? _chart.selectedColor() : _chart.unselectedColor(); return circle; }; /* ######################## * Private helper functions * ######################## */ /* ################ * Public interface * ################ */ /** #### .r([bubbleRadiusScale]) Get or set bubble radius scale. By default bubble chart uses ```d3.scaleLinear().domain([0, 100])``` as its r scale . **/ _chart.r = function (_) { if (!arguments.length) return _r; _r = _; return _chart; }; _chart.brushOn = function (_) { if (!arguments.length) { return _brushOn; } _brushOn = _; return _chart; }; _chart.locationAccessor = function (_) { if (!arguments.length) { return _location; } _location = _; return _chart; }; /** #### .selectedColor([color]) Get or set the color of a selected (filter) bubble. */ _chart.selectedColor = function (_) { if (!arguments.length) { return _selectedColor; } _selectedColor = _; return _chart; }; /** #### .unselectedColor([color]) Get or set the color of a bubble which is not currently in the filter. */ _chart.unselectedColor = function (_) { if (!arguments.length) { return _unselectedColor; } _unselectedColor = _; return _chart; }; var createmarker = function (v, k) { var marker = _chart.marker()(v, _chart.map()); marker.key = k; if (_chart.brushOn()) { marker.on("click", selectFilter); } return marker; }; _chart.marker = function (_) { if (!arguments.length) { return _marker; } _marker = _; return _chart; }; /* Render and redraw overrides */ _chart._postRender = function () { if (_chart.brushOn()) { _chart.map().on('click', function (e) { _chart.filter(null); _chart.redrawGroup(); }); } _chart.map().on('boxzoomend', boxzoomFilter, this); _layerGroup = new L.LayerGroup(); _chart.map().addLayer(_layerGroup); }; _chart._doRedraw = function () { var groups = _chart._computeOrderedGroups(_chart.data()).filter(function (d) { return _chart.valueAccessor()(d) !== 0; }); _layerGroup.clearLayers(); groups.forEach(function (v, i) { var key = _chart.keyAccessor()(v); var marker = null; marker = createmarker(v, key); _layerGroup.addLayer(marker); }); }; /* Callback functions */ function boxzoomFilter(e) { var filters = []; _layerGroup.eachLayer(function (layer) { var latLng = layer.getLatLng(); if (e.boxZoomBounds.contains(latLng)) { filters.push(layer.key); } }); dc.events.trigger(function (e) { _chart.replaceFilter([filters]); _chart.redrawGroup(); }); } var selectFilter = function (e) { L.DomEvent.stopPropagation(e); var filter = e.target.key; if (e.originalEvent.ctrlKey || e.originalEvent.metaKey) { // If ctrl/cmd key modifier was pressed on click, toggle the target _chart.filter(filter); } else { // If ctrl key wasn't pressed, clear selection and add target _chart.replaceFilter([[filter]]); } _chart.redrawGroup(); }; return _chart.anchor(parent, chartGroup); }; dc_leaflet.d3 = d3; dc_leaflet.crossfilter = crossfilter; dc_leaflet.dc = dc; return dc_leaflet; } if (typeof define === 'function' && define.amd) { define(["dc"], _dc_leaflet); } else if (typeof module == "object" && module.exports) { var _dc = require('dc'); module.exports = _dc_leaflet(_dc); } else { this.dc_leaflet = _dc_leaflet(dc); } } )(); //# sourceMappingURL=dc.leaflet.js.map