diff --git a/static/js/colors.js b/static/js/colors.js index f3c5e0a..41e0fa5 100644 --- a/static/js/colors.js +++ b/static/js/colors.js @@ -9,9 +9,9 @@ var colorUtils = { hexToRGB: function toRGB (color) { var r = color.substr(1, 2) , g = color.substr(3, 2) - , b = color.substr(5, 2); + , b = color.substr(5, 2) - return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)]; + return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)] }, /** @@ -20,18 +20,18 @@ var colorUtils = { * @return {String} The mixed color as hex string */ mix: function mixColors (colors) { - var rgb = colors.map(colorUtils.hexToRGB); + var rgb = colors.map(colorUtils.hexToRGB) - var result = [ 0, 0, 0 ]; + var result = [ 0, 0, 0 ] for (var i = 0, l = rgb.length; i < l; i++) { - result[0] += rgb[i][0] / l; - result[1] += rgb[i][1] / l; - result[2] += rgb[i][2] / l; + result[0] += rgb[i][0] / l + result[1] += rgb[i][1] / l + result[2] += rgb[i][2] / l } - return '#' + result[0].toString(16) + result[1].toString(16) + result[2].toString(16); + return '#' + result[0].toString(16) + result[1].toString(16) + result[2].toString(16) } } -export default colorUtils; +export default colorUtils diff --git a/static/js/filter.js b/static/js/filter.js index 640bea1..fc0fb6c 100644 --- a/static/js/filter.js +++ b/static/js/filter.js @@ -4,15 +4,15 @@ export default { * Returns only the incidents which fall into the given categories * @param {Array[Object]} data The incidents to filter * @param {Array[String]} categories - * @return {Arreay[Obect]} + * @return {Array[Obect]} */ categories: function (data, categories) { - return response.filter(function (incident) { + return data.filter(function (incident) { for (var i = 0, l = incident.categories.length; i < l; i++) if (categories.indexOf(incident.categories[i]) !== -1) - return true; + return true - return false; + return false }); } diff --git a/static/js/main.js b/static/js/main.js index 7f104b9..a3ec3ee 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1,92 +1,26 @@ 'use strict'; -import $ from 'jquery'; -import filter from './filter'; -import colorUtils from './colors'; +import Visualization from './visualization' // http://www.colourlovers.com/palette/1811244/1001_Stories -var colors = [ '#F8B195', '#F67280', '#C06C84', '#6C5B7B', '#355C7D' ]; +var colors = [ '#F8B195', '#F67280', '#C06C84', '#6C5B7B', '#355C7D' ] // set up background map -var map = L.map('map').setView([52.50, 13.40], 11); -var layer = new L.StamenTileLayer('toner-lite'); -map.addLayer(layer); +var map = L.map('map').setView([52.50, 13.40], 11) +var layer = new L.StamenTileLayer('toner-lite') +map.addLayer(layer) // restrict viewable area -map.setMaxBounds(map.getBounds()); -map.options.minZoom = map.getZoom(); +map.setMaxBounds(map.getBounds()) +map.options.minZoom = map.getZoom() // get response from server and draw the map -var response; +var visualization $.getJSON('/articles/') .fail(console.error.bind(console)) - .then(function (data) { - console.log('Got data successfully!'); - response = data; + .then(function (response) { + console.log('Got data successfully!') + visualization = new Visualization(map, response, colors) - displayMarkers(response); + visualization.displayMarkers() + visualization.setupCategoryFilter('.category-filter') }); - -// event handling / user interaction -var $categoryList = $('.category-filter'); - -function getActiveCategories ($li) { - var activeCategories = []; - $categoryList.children().each(function () { - var $li = $(this); - if ($li.hasClass('active')) - activeCategories.push($li[0].classList[0]) - }); - - return activeCategories; -} - -$categoryList.on('click', 'a', function (e) { - $(this).parent().toggleClass('active'); - - var categories = getActiveCategories(); - var incidents = filter.categories(reponse, categories); - displayMarkers(incidents); - - e.preventDefault(); - e.stopPropagation(); - return false; -}) - -// logic for drawing follows - -var markers = []; -function createMarker (data) { - var options = { color: pickColor(data) }; - var marker = L.circleMarker([data.lat, data.lng], options).addTo(map); - return marker; -} - -function pickColor (incident) { - var categories = ['racism', 'antisemitism', 'sexism', 'homophobia'].map(function (category, index) { - if (incident.categories.indexOf(category) !== -1) - return index; - }).filter(function (value) { - return value != null - }); - - var categoryColors = colors.filter(function (color, index) { - return categories.indexOf(index) !== -1; - }); - - return incident.categories.length ? colorUtils.mix(categoryColors) : colors[4]; -} - -/** - * Clear the map and render new markers - * @param {Array[Object]} incidents The incidents to be shown - */ -function displayMarkers (incidents) { - markers.forEach(function (marker) { - map.removeLayer(marker); - }); - markers = []; - - incidents.forEach(function (incident) { - markers.push(createMarker(incident).bindPopup(incident.description)); - }); -} diff --git a/static/js/visualization.js b/static/js/visualization.js new file mode 100644 index 0000000..c908e24 --- /dev/null +++ b/static/js/visualization.js @@ -0,0 +1,112 @@ +'use strict'; +import $ from 'jquery' +import filter from './filter' +import colorUtils from './colors' + +class Visualization { + + /** + * Creates a new visualization + * @param {L.Map} map The Leaflet map to add the visualization to + * @param {Arrray[Object]} data The complete list of incidents to visualize + * @param {Array[String]} colors The colors to be used in HEX format + * @constructor + */ + constructor (map, data, colors) { + this.map = map + this.data = data + this.colors = colors + + this._markers = [] + this._$categoryList = null + } + + /** + * Clear the map and render new markers + * @param {Array[Object]} incidents The incidents to be shown + */ + displayMarkers (incidents) { + if (incidents == null) + incidents = this.data + + this._markers.forEach(marker => { this.map.removeLayer(marker) }) + this._markers = [] + + incidents.forEach(incident => { + this._markers.push( + this._createMarker(incident).bindPopup(incident.description) + ) + }) + } + + /** + * Gets all currently active categories + * @return {Array[string]} + */ + getActiveCategories () { + var activeCategories = [] + this._$categoryList.children().each(function () { + var $li = $(this) + if ($li.hasClass('active')) + activeCategories.push($li[0].classList[0]) + }) + + return activeCategories + } + + /** + * Sets up the category filter + * @param {jQuery.Selector} selector The container holding the LIs + * representing the different categories + */ + setupCategoryFilter (selector) { + this._$categoryList = $(selector) + + this._$categoryList.on('click', 'a', e => { + $(e.target).parent().toggleClass('active') + + var categories = this.getActiveCategories() + var incidents = filter.categories(this.data, categories) + this.displayMarkers(incidents) + + e.preventDefault() + e.stopPropagation() + return false + }) + } + + /** + * Creates the correct marker for a single incident and adds it to the map + * @param {Object} data A single incident + * @return {L.CircleMarker} + */ + _createMarker (data) { + var options = { color: this._pickColor(data) } + var marker = L.circleMarker([data.lat, data.lng], options).addTo(this.map) + return marker + } + + /** + * Picks a color for a given incident based on its categories + * @param {Object} incident A single incident + * @return {String} A color as HEX string + */ + _pickColor (incident) { + var categories = ['racism', 'antisemitism', 'sexism', 'homophobia'] + .map(function (category, index) { + if (incident.categories.indexOf(category) !== -1) + return index + }).filter(function (value) { + return value != null + }) + + var categoryColors = this.colors.filter(function (color, index) { + return categories.indexOf(index) !== -1 + }) + + return incident.categories.length ? colorUtils.mix(categoryColors) : this.colors[4] + } + +} + +export default Visualization