app/assets/javascripts/map/views/LegendView.js
/**
* Legend module.
*
* @return singleton instance of the legend class (extends Widget).
*/
define([
'underscore',
'handlebars',
'map/presenters/LegendPresenter',
'text!map/templates/legend/legend.handlebars',
'text!map/templates/legend/biomass_loss.handlebars',
'text!map/templates/legend/biomass.handlebars',
'text!map/templates/legend/idnPrimary.handlebars',
'text!map/templates/legend/intact2013.handlebars',
'text!map/templates/legend/grump.handlebars',
'text!map/templates/legend/stories.handlebars',
'text!map/templates/legend/concesiones_forestales.handlebars',
'text!map/templates/legend/concesiones_forestalesType.handlebars',
'text!map/templates/legend/hondurasForest.handlebars',
'text!map/templates/legend/colombiaForestChange.handlebars',
'text!map/templates/legend/dam_hotspots.handlebars',
'text!map/templates/legend/us_land_cover.handlebars',
'text!map/templates/legend/bra_biomes.handlebars',
'text!map/templates/legend/idn_peat.handlebars',
'text!map/templates/legend/total_carbon.handlebars',
'text!map/templates/legend/biomass_carbon.handlebars',
'text!map/templates/legend/hsdw.handlebars',
'text!map/templates/legend/plantations_by_type.handlebars',
'text!map/templates/legend/plantations_by_species.handlebars',
'text!map/templates/legend/peatland_drainage.handlebars',
'text!map/templates/legend/colombiaForestChange.handlebars',
'text!map/templates/legend/us_land_cover.handlebars',
'text!map/templates/legend/bra_biomes.handlebars',
'text!map/templates/legend/gtm_forest_change.handlebars',
'text!map/templates/legend/gtm_forest_cover.handlebars',
'text!map/templates/legend/gtm_forest_density.handlebars',
'text!map/templates/legend/khm_eco_land_conc.handlebars',
'text!map/templates/legend/usa_forest_ownership.handlebars',
'text!map/templates/legend/mysPA.handlebars',
'text!map/templates/legend/per_mining.handlebars',
'text!map/templates/legend/raisg_mining.handlebars',
'text!map/templates/legend/mangrove_biomass.handlebars',
'text!map/templates/legend/carbon_gain.handlebars'
], function(
_,
Handlebars,
Presenter,
tpl,
biomass_lossTpl,
biomassTpl,
idnPrimaryTpl,
intact2013Tpl,
grumpTpl,
storiesTpl,
concesionesTpl,
concesionesTypeTpl,
hondurasForestTPL,
colombiaForestChangeTPL,
dam_hotspotsTPL,
us_land_coverTPL,
bra_biomesTPL,
idn_peatTPL,
total_carbon,
biomass_carbonTPL,
hsdwTPL,
gfwPlantationByTypeTpl,
gfwPlantationBySpeciesTpl,
peatland_drainageTpl,
colombiaForestChangeTpl,
us_land_coverTpl,
bra_biomesTpl,
gtm_forest_changeTpl,
gtm_forest_coverTpl,
gtm_forest_densityTpl,
khm_eco_land_concTpl,
usa_forest_ownershipTpl,
mysPATpl,
per_miningTpl,
raisg_miningTpl,
mangrove_biomassTpl,
carbon_gainTpl
) {
'use strict';
var carbonGainConfig = {
ranges: {
tco: {
min: 0,
max: 440
}
},
units: [
{
name: 't CO2 ha<sup>-1</sup>',
value: 'tco'
}
],
selectedUnit: 'tco'
};
var LegendModel = Backbone.Model.extend({
defaults: {
hidden: true,
categories_status: []
}
});
var LegendView = Backbone.View.extend({
el: '#module-legend',
template: Handlebars.compile(tpl),
defaults: {
layersConfig: {
biomass_loss: {
ranges: {
tco: {
min: 0,
max: 900,
}
},
units: [
{
name: 't CO2 ha<sup>-1</sup>',
value: 'tco'
}
],
selectedUnit: 'tco'
},
// Carbon Gain
total_sg: carbonGainConfig,
total_2028: carbonGainConfig,
total_2038: carbonGainConfig,
total_2048: carbonGainConfig,
ysg_msg: carbonGainConfig,
pastures: carbonGainConfig,
crops_1: carbonGainConfig,
carbon_stocks: {
ranges: {
biomass: {
min: 0,
max: 500
},
carbon: {
min: 0,
max: 560
}
},
units: [
{
name: 'Mg biomass ha<sup>-1</sup>',
value: 'biomass'
},
{
name: 't ha<sup>-1</sup>',
value: 'carbon'
}
],
selectedUnit: 'carbon'
}
}
},
options: {
hidden: true
},
events: {
'click .category-name': '_toogleCategory',
'click .layer-sublayer': '_toggleLayer',
'click .canopy-button': '_showCanopy',
'click .layer-close': '_removeLayer',
'click .close': 'toogleLegend',
'click #title-dialog-legend': 'toogleEmbedLegend',
'click .toggle-title': 'toggleLegendOptions',
'change input': 'updateRange',
'click .js-units-selector': '_changeUnit'
},
/**
* Optional layers detail templates.
*/
detailsTemplates: {
biomass_loss: Handlebars.compile(biomass_lossTpl),
carbon_stocks: Handlebars.compile(biomassTpl),
idn_primary: Handlebars.compile(idnPrimaryTpl),
ifl_2013_deg: Handlebars.compile(intact2013Tpl),
grump2000: Handlebars.compile(grumpTpl),
user_stories: Handlebars.compile(storiesTpl),
concesiones_forestales: Handlebars.compile(concesionesTpl),
concesiones_forestalesNS: Handlebars.compile(concesionesTypeTpl),
WMSLayer: Handlebars.compile(hondurasForestTPL),
dam_hotspots: Handlebars.compile(dam_hotspotsTPL),
idn_peat_lands: Handlebars.compile(idn_peatTPL),
total_carbon: Handlebars.compile(total_carbon),
biomass_carbon: Handlebars.compile(biomass_carbonTPL),
hwsd: Handlebars.compile(hsdwTPL),
plantations_by_type: Handlebars.compile(gfwPlantationByTypeTpl),
plantations_by_species: Handlebars.compile(gfwPlantationBySpeciesTpl),
peatland_drainage: Handlebars.compile(peatland_drainageTpl),
idn_peatland_drainage: Handlebars.compile(peatland_drainageTpl),
colombia_forest_change: Handlebars.compile(colombiaForestChangeTPL),
us_land_cover: Handlebars.compile(us_land_coverTPL),
us_land_cover_change: Handlebars.compile(us_land_coverTPL),
bra_biomes: Handlebars.compile(bra_biomesTPL),
bra_plantations_type: Handlebars.compile(gfwPlantationByTypeTpl),
per_plantations_type: Handlebars.compile(gfwPlantationByTypeTpl),
lbr_plantations_type: Handlebars.compile(gfwPlantationByTypeTpl),
col_plantations_type: Handlebars.compile(gfwPlantationByTypeTpl),
khm_plantations_type: Handlebars.compile(gfwPlantationByTypeTpl),
idn_plantations_type: Handlebars.compile(gfwPlantationByTypeTpl),
mys_plantations_type: Handlebars.compile(gfwPlantationByTypeTpl),
bra_plantations_species: Handlebars.compile(gfwPlantationBySpeciesTpl),
per_plantations_species: Handlebars.compile(gfwPlantationBySpeciesTpl),
lbr_plantations_species: Handlebars.compile(gfwPlantationBySpeciesTpl),
col_plantations_species: Handlebars.compile(gfwPlantationBySpeciesTpl),
khm_plantations_species: Handlebars.compile(gfwPlantationBySpeciesTpl),
idn_plantations_species: Handlebars.compile(gfwPlantationBySpeciesTpl),
mys_plantations_species: Handlebars.compile(gfwPlantationBySpeciesTpl),
gtm_forest_change1: Handlebars.compile(gtm_forest_changeTpl),
gtm_forest_change2: Handlebars.compile(gtm_forest_changeTpl),
gtm_forest_cover: Handlebars.compile(gtm_forest_coverTpl),
gtm_forest_density: Handlebars.compile(gtm_forest_densityTpl),
khm_eco_land_conc: Handlebars.compile(khm_eco_land_concTpl),
usa_forest_ownership: Handlebars.compile(usa_forest_ownershipTpl),
mys_protected_areas: Handlebars.compile(mysPATpl),
bra_mining: Handlebars.compile(raisg_miningTpl),
per_mining: Handlebars.compile(per_miningTpl),
global_mangroves_biomass: Handlebars.compile(mangrove_biomassTpl),
total_sg: Handlebars.compile(carbon_gainTpl),
total_2028: Handlebars.compile(carbon_gainTpl),
total_2038: Handlebars.compile(carbon_gainTpl),
total_2048: Handlebars.compile(carbon_gainTpl),
ysg_msg: Handlebars.compile(carbon_gainTpl),
pastures: Handlebars.compile(carbon_gainTpl),
crops_1: Handlebars.compile(carbon_gainTpl)
},
initialize: function() {
_.bindAll(this, 'update');
this.presenter = new Presenter(this);
this.model = new LegendModel();
this.embed = $('body').hasClass('is-embed-action');
this.layersConfig = this.defaults.layersConfig;
this.$el.removeClass('hide');
this.setListeners();
},
setListeners: function() {
this.model.on('change:hidden', this.toogleModule, this);
},
toogleModule: function() {
this.$el.toggleClass('hide', this.model.get('hidden'));
},
toogleEmbedLegend: function(e) {
e && e.preventDefault();
var active = this.$titleDialog.hasClass('active');
this.$titleDialog.toggleClass('active', !active);
this.$categories.toggleClass('active', !active);
this.$buttonLegendBox.toggleClass('active', !active);
},
openGFW: function() {
if (this.embed && !!this.$linkLegendBox) {
var href = window.location.href.replace('/embed', '');
this.$linkLegendBox.attr('href', href);
}
},
/**
*
* @param {array} categories layers ordered by category
* @param {object} options legend options
*/
_renderLegend: function(categories, options, geographic) {
var iso = null;
var layersGlobal = [];
var layersIso = [];
var categoriesGlobal = [];
var categoriesIso = [];
var layers = _.flatten(categories);
var layersLength = layers.length;
// Append details template to layer.
_.each(
layers,
function(layer) {
layer.source = layer.slug === 'nothing' ? null : layer.slug;
if (this.layersConfig[layer.slug]) {
var layerData = this._getLayerData(layer, options);
layer.detailsTpl = this.detailsTemplates[layer.slug]({
threshold: options.threshold || 30,
layerTitle: layer.title,
minvalue: layerData.min,
maxvalue: layerData.max,
units: layerData.units,
unit: layerData.unit
});
} else if (this.detailsTemplates[layer.slug]) {
layer.detailsTpl = this.detailsTemplates[layer.slug]({
threshold: options.threshold || 30,
layerTitle: layer.title
});
}
if (layer.iso) {
var countries = amplify.store('countries');
iso = layer.iso;
layersIso.push(layer);
layer.category_status = layer.category_slug + '-iso';
} else {
layersGlobal.push(layer);
layer.category_status = layer.category_slug + '-global';
}
layer.geographic = geographic ? 'checked' : '';
},
this
);
categoriesGlobal = this.statusCategories(
_.groupBy(layersGlobal, function(layer) {
return layer.category_slug;
})
);
categoriesIso = this.statusCategories(
_.groupBy(layersIso, function(layer) {
return layer.category_slug;
})
);
if (iso) {
var country = _.find(
amplify.store('countries'),
_.bind(function(country) {
return country.iso === iso;
}, this)
);
}
var name = country ? country.name : iso;
var html = this.template({
categories: jQuery.isEmptyObject(categoriesGlobal)
? false
: categoriesGlobal,
categoriesIso: categoriesIso,
layersLength: layersLength,
iso: iso,
name: name
});
this.render(html);
},
statusCategories: function(array) {
// Search for layer 'nothing'
var categories_status = this.model.get('categories_status');
_.each(
array,
function(category) {
for (var i = 0; i < category.length; i++) {
// Mantain categories closed in rendering
categories_status.indexOf(category[i]['category_status']) != -1
? (category['closed'] = true)
: (category['closed'] = false);
// Get layer's length of each category
category['layers_length'] = i + 1;
}
},
this
);
return array;
},
toogleLegend: function(bool) {
var to = bool && bool.currentTarget ? false : bool;
this.$el.toggleClass('active', to);
this.presenter.toggleOverlay(to);
},
/**
* Toggle selected sublayers on the legend widget.
*
* @param {object} layers The layers object
*/
toggleSelected: function(layers) {
_.each(
this.$el.find('.layer-sublayer'),
function(div) {
var $div = $(div);
var $toggle = $div.find('.onoffswitch');
var layer = layers[$div.data('sublayer')];
if (layer) {
$toggle.addClass('checked');
$toggle.css('background', layer.category_color);
} else {
$toggle.removeClass('checked').css('background', '');
}
},
this
);
},
render: function(html) {
this.$el.html(html);
this.$titleDialog = $('#title-dialog-legend');
this.$categories = this.$el.find('.categories');
this.$buttonLegendBox = $('#button-box-embed-legend');
this.$linkLegendBox = $('#link-embed-legend');
},
/**
* Set widget from layers object.
*
* @param {array} layers
*/
update: function(categories, options, geographic) {
if (categories.length === 0) {
this.model.set('hidden', true);
} else {
this.model.set({ hidden: false, boxClosed: false });
this._renderLegend(categories, options, geographic);
}
//Experiment
this.presenter.initExperiment('source');
},
/**
* Handles a toggle layer change UI event by dispatching
* to LegendPresenter.
*
* @param {event} event Click event
*/
_toggleLayer: function(event) {
var layerSlug = $(event.currentTarget).data('sublayer');
this.presenter.toggleLayer(layerSlug);
},
_toogleCategory: function(e) {
if ($(window).width() > window.gfw.config.GFW_MOBILE) {
// Save category status in an array
var categories_status = this.model.get('categories_status');
var slug = $(e.currentTarget).data('category_slug');
var index = categories_status.indexOf(slug);
index != -1
? categories_status.splice(index, 1)
: categories_status.push(slug);
this.model.set('categories_status', categories_status);
$(e.currentTarget)
.parent()
.toggleClass('closed');
$(e.currentTarget)
.parent()
.children('.layers')
.toggleClass('closed');
}
},
_removeLayer: function(e) {
e && e.preventDefault();
var layerSlug = $(e.currentTarget).data('slug');
this.presenter.toggleLayer(layerSlug);
},
_showCanopy: function(e) {
if (!!e.target.parentNode.classList.contains('minavgmax'))
return this._getUncertainty(e);
if (!!e.target.parentNode.classList.contains('range')) return;
e && e.preventDefault();
this.presenter.showCanopy();
},
_getUncertainty: function(e) {
this.presenter.changeUncertainty(e.target.dataset);
},
_setUncertaintyOptionUI: function(type) {
var $opt = this.$el.find('[data-quantity="' + type + '"]');
$opt
.addClass('current')
.siblings('.current')
.removeClass('current');
},
_getLayerData: function(layer, opts) {
var unitsList = [];
var selectedUnit =
!!opts.rangearray && opts.rangearray[layer.slug]
? opts.rangearray[layer.slug].unit
: this.layersConfig[layer.slug].selectedUnit;
var defaultRange = this.layersConfig[layer.slug].ranges[selectedUnit];
var units = this.layersConfig[layer.slug].units;
var currentUnit = _.findWhere(units, { value: selectedUnit });
var values = {};
values.min =
!!opts.rangearray && opts.rangearray[layer.slug]
? opts.rangearray[layer.slug].minrange
: false || (!!defaultRange ? defaultRange.min : null);
values.max =
!!opts.rangearray && opts.rangearray[layer.slug]
? opts.rangearray[layer.slug].maxrange
: false || (!!defaultRange ? defaultRange.max : null);
var unit =
!!opts.rangearray && opts.rangearray[layer.slug]
? opts.rangearray[layer.slug].unit
: false || (!!currentUnit ? currentUnit.value : null);
var units =
this.layersConfig[layer.slug] && this.layersConfig[layer.slug].units
? this.layersConfig[layer.slug].units
: null;
this.layersConfig[layer.slug].selectedUnit = selectedUnit;
if (opts && opts.rangearray) {
values = this._formatValues(layer.slug, values, selectedUnit);
}
if (units) {
_.map(units, function(unit) {
unit.selected = unit.value === selectedUnit;
});
}
return {
min: values.min,
max: values.max,
unit: currentUnit.name,
units: units
};
},
toggleLegendOptions: function(e) {
if (e.target.tagName === 'SPAN') e = e.target.parentNode;
else e = e.target;
$(e)
.find('span')
.toggleClass('active');
$(e)
.siblings('.toggle-legend-option')
.toggle('250');
},
/**
* Updates the range when the user changes the values
*
* @param {object} event
*/
updateRange: function(ev) {
var parent = $(ev.target).parents('.layer-details');
var slug = parent.data('layer-group');
var values = this._getCurrentRangeFromLayer(slug);
this._setRangeLabels(slug, values);
this._updateRangeBar(slug, values);
this._updateLayerRange(slug);
},
/**
* Returns the current values and validates the limits
*
* @param {String} layer id name
* @return {Object} values and unit
*/
_getCurrentRangeFromLayer: function(slug) {
var parent = this.el.querySelector('[data-layer-group="' + slug + '"]');
var $minEl = parent.querySelector('.js-min');
var $maxEl = parent.querySelector('.js-max');
var $unitEl = parent.querySelector('.js-unit');
var min = $minEl.value * 1;
var max = $maxEl.value * 1;
var unit = $unitEl.value;
var selectedUnit = this.layersConfig[slug].selectedUnit;
var layerRange = this.layersConfig[slug].ranges[selectedUnit];
var units = this.layersConfig[slug].units;
var currentUnit = _.findWhere(units, { value: selectedUnit });
if (min < 0 || min > layerRange.max) {
min = layerRange.min;
$minEl.value = layerRange.min;
}
if (max < 0 || max > layerRange.max || max < min) {
max = layerRange.max;
$maxEl.value = layerRange.max;
}
return {
min: min,
max: max,
unit: unit ? unit : currentUnit.value
};
},
/**
* Sets the new values in the labels
*
* @param {String} layer id name
* @param {Object} min and max values
*/
_setRangeLabels: function(slug, values) {
var $parent = this.el.querySelector('[data-layer-group="' + slug + '"]');
var $minLabel = $parent.querySelector('.js-min-label');
var $maxLabel = $parent.querySelector('.js-max-label');
var $unitLabel = $parent.querySelector('.js-unit');
var $selectorLabel = $parent.querySelector('.js-units-selector');
var units = this.layersConfig[slug].units;
var unitName = _.findWhere(units, { value: values.unit });
$minLabel.innerHTML = values.min;
$maxLabel.innerHTML = values.max;
if (unitName) {
$($unitLabel).html(unitName.name);
if ($selectorLabel) {
$($selectorLabel).html(unitName.name);
}
}
},
/**
* Updates the range bar handles
*
* @param {String} layer id name
* @param {Object} min and max values
*/
_updateRangeBar: function(slug, range) {
var parent = this.el.querySelector('[data-layer-group="' + slug + '"]');
var rangeBars = parent.querySelectorAll('.range-bar');
var layerRange = this.layersConfig[slug].ranges[range.unit];
if (range.min == layerRange.min && range.max == layerRange.max) {
rangeBars[0].classList.remove('-visible');
rangeBars[1].classList.remove('-visible');
}
rangeBars[0].style.left = (range.min * 100) / layerRange.max + '%';
rangeBars[0].classList.add('-visible');
rangeBars[1].style.left = (range.max * 100) / layerRange.max + '%';
rangeBars[1].classList.add('-visible');
},
/**
* Extra values formatting for some layers
*
* @param {String} layer id name
* @param {Object} min and max values
* @param {String} unit
* @param {Boolean} store in the url
*/
_formatValues: function(slug, values, unit, toStore) {
if (slug === 'carbon_stocks') {
if (unit === 'carbon') {
if (toStore) {
values.min = values.min * 2;
values.max = values.max * 2;
} else {
values.min = values.min / 2;
values.max = values.max / 2;
}
}
}
return values;
},
/**
* When the user changes the unit it updates
* the values and range
*
* @param {Object} event
*/
_changeUnit: function(ev) {
var slug = ev.currentTarget.dataset.layer;
var $parent = this.el.querySelector('[data-layer-group="' + slug + '"]');
var $unitsList = $parent.querySelector('.js-units-list');
var $options = $unitsList.querySelectorAll('.js-item');
var current = $unitsList.value;
var selectedUnit = this.layersConfig[slug].selectedUnit;
var value;
for (var x = 0; x < $options.length; x++) {
var item = $options[x];
if (item.value !== current) {
$unitsList.value = item.value;
value = item.value;
}
}
// Special case for this layer
if (slug === 'carbon_stocks') {
this._setInputsValues(slug, selectedUnit);
}
this.layersConfig[slug].selectedUnit = value;
var currentParams = this._getCurrentRangeFromLayer(slug);
this._setRangeLabels(slug, currentParams);
this._updateLayerRange(slug);
},
/**
* Updates the inputs with the new values
* some formatting might be done for some layers
*
* @param {String} layer id name
* @param {String} unit
*/
_setInputsValues: function(slug, selectedUnit) {
var parent = this.el.querySelector('[data-layer-group="' + slug + '"]');
var $minEl = parent.querySelector('.js-min');
var $maxEl = parent.querySelector('.js-max');
var min = $minEl.value;
var max = $maxEl.value;
// Special case for this layer
if (slug === 'carbon_stocks') {
if (selectedUnit === 'biomass') {
min = min / 2;
max = max / 2;
} else if (selectedUnit === 'carbon') {
min = min * 2;
max = max * 2;
}
}
$minEl.value = min;
$maxEl.value = max;
},
/**
* Creates a new range array
* with the selected data
*
* @param {String} layer id name
* @param {Object} range values
* @return {Object} new range
*/
_createRangeArray: function(slug, range) {
var layerRange = this.layersConfig[slug].ranges[range.unit];
var rangeArray = {};
rangeArray[slug] = {
minrange: range.min,
maxrange: range.max,
TOTALMIN: layerRange.min,
TOTALMAX: layerRange.max,
unit: range.unit
};
return rangeArray;
},
/**
* Updates the new data in the url
* with the selected data and also
* updates the layer
*
* @param {String} layer id name
*/
_updateLayerRange: function(slug) {
var values = this._getCurrentRangeFromLayer(slug);
values = this._formatValues(slug, values, values.unit, true);
var rangeArray = this._createRangeArray(slug, values);
this.presenter._updateRangeArray(rangeArray, slug);
this.presenter.setNewRange([values.min, values.max], slug);
}
});
return LegendView;
});