Vizzuality/gfw-climate

View on GitHub
app/assets/javascripts/insights/views/InsightsGladAlertsView.js

Summary

Maintainability
F
2 wks
Test Coverage
define([
  'backbone',
  'mps',
  'handlebars',
  'd3',
  'underscore',
  '_string',
  'insights/views/InsightsGladAlertsChartView',
  'views/ShareView',
  'views/SourceModalView',
  'helpers/NumbersHelper',
  'text!insights/templates/insights-glad-alerts.handlebars',
  'text!insights/templates/insights-glad-alerts-legend.handlebars',
], function(Backbone, mps, Handlebars, d3, _, _string, InsightsGladAlertsChart,
  ShareView, SourceModalView, NumbersHelper, tpl, tplLegend) {

  'use strict';

  var GFW_URL = window.gfw.config.GFW_URL;
  var API = window.gfw.config.GFW_API_HOST_V2;
  var ENDPOINT_CONFIG = '/query?sql=SELECT * FROM '+ window.gfw.config.GLAD_INSIGHT_CONFIG_ID;
  var ENDPOINT_DATA = '/query/'+ window.gfw.config.GLAD_INSIGHT_ID + '?sql=SELECT sum(alerts) AS alerts, sum(cumulative_emissions) AS cumulative_emissions, sum(above_ground_carbon_loss) AS above_ground_carbon_loss, sum(percent_to_emissions_target) AS percent_to_emissions_target, sum(percent_to_deforestation_target) AS percent_to_deforestation_target, sum(loss_ha) AS loss, sum(cumulative_deforestation) AS cumulative_deforestation, year as year, country_iso, week FROM data WHERE %s AND year IN (\'%s\') AND week <= 53 GROUP BY week, country_iso ORDER BY week ASC';

  var WEEKS_YEAR = 53;

  var InsightsGladAlerts = Backbone.View.extend({

    events: {
      'click .js-selector': '_changeVisualizations',
      'click .js-share': '_openShare',
      'click .js-year-nav': '_changeYear',
      'change .js-country-selector': '_changeDataByCountry',
      'change .js-year-selector': '_changeDataByYear'
    },

    el: '#insights',

    template: Handlebars.compile(tpl),

    templateLegend: Handlebars.compile(tplLegend),

    defaults: {
      selectedClassEl: '-selected',
      filter: 'carbon_emissions',
      country: '',
      year: 2017,
      weekStep: 1,
      desforestationFilter: 'deforestation',
      loadingClassEl: 'is-loading',
      mainVisSwitchEl: 'main-vis-switch',
      loadedClassEl: 'loaded',
      countryLabelClassEl: 'js-country-label',
      countrySelectorClassEl: 'js-country-selector',
      legendSelectoClassEl: 'js-legend',
      noDataClassEl: 'no-data',
      imageURI: window.gfw.config.GFW_DATA_S3 + 'climate/glad_maps'
    },

    initialize: function(params) {
      this.defaults = _.extend(this.defaults, params);
      this.legends = [];
      this.chartConfig = [];
      this.locations = [];
      this.images = {};
      this.currentStep = this.defaults.weekStep;
      this.currentCountry = this.defaults.country;
      this.currentYear = this.defaults.year;
      this.filter = this.defaults.filter;
      this.imageURI = this.defaults.imageURI;

      // Get the visualization's configuration
      $.when(this._getConfig())
        .done(this._initVisualization.bind(this));
    },

    _getConfig: function() {
      return $.ajax({
        url: API + ENDPOINT_CONFIG,
        type: 'GET'
      });
    },

    _initVisualization: function(config) {
      this.config = this._parseConfig(config);
      this.render();
      this._setListeners();

      // Info window
      new SourceModalView();
    },

    _parseConfig: function(config) {
      var data = config && config.data &&
      config.data[0] ? config.data[0] : {};

      if (data) {
        this.chartConfig = typeof (data.vizsetup) === 'string' ? JSON.parse(data.vizsetup) : data.vizsetup;
        this.locations = typeof (data.locations) === 'string' ? JSON.parse(data.locations) : data.locations;
      }

      if (!this.currentCountry) {
        this.currentCountry = this.chartConfig.defaultSelection;
        mps.publish('Router/goInsight', [this.currentCountry]);
      }
    },

    render: function() {
      this.$el.html(this.template({
        locations: _.sortBy(this.locations, 'name')
      }));
      this.$el.removeClass(this.defaults.loadingClassEl);
      this.$el.addClass(this.defaults.loadedClassEl);

      // Set default selection from the config
      var defaultCountry = _.findWhere(this.locations, {
        iso: this.currentCountry
      });

      if (defaultCountry) {
        this._setCurrentCountry(defaultCountry.name, defaultCountry.iso);
      }

      this._renderMainChart();
    },

    _setListeners: function() {
      Backbone.Events.on('insights:glad:update', this._updateLegends.bind(this));
    },

    _renderMainChart: function() {
      var $el = this.el.querySelector('#visMain');
      var $vis = $el.querySelector('.visualization');
      var $chartEl = $el.querySelector('.chart');
      var url = this._getDataQuery();

      this._clearVisualization();

      $chartEl.innerHTML = '';
      $vis.classList.add(this.defaults.loadingClassEl);

      $.ajax({
        url: url,
        type: 'GET',
        success: function(res) {
          var data = res.data;

          if (data.length) {
            $el.classList.remove(this.defaults.noDataClassEl);
            this._createVisualization(data);
          } else {
            $el.classList.add(this.defaults.noDataClassEl);
            this._renderNoDataPlaceHolder();
          }

          $vis.classList.remove(this.defaults.loadingClassEl);
        }.bind(this)
      });
    },

    _getDataQuery: function() {
      var iso = this.currentCountry;
      var year = this.currentYear;
      var filter = ' country_iso =\''+ iso +'\'';
      var locationData = _.findWhere(this.locations, {
        iso: iso
      });

      if (locationData && locationData.groupBy) {
        filter = ' state_iso IN(\''+locationData.groupBy.join('\', \'') + '\')';
      }

      return API + _.str.sprintf(ENDPOINT_DATA, filter, year);
    },

    _createVisualization: function(data) {
      var el = this.el.querySelector('#visMain');
      var chartEl = el.querySelector('.chart');
      var legendEl = el.querySelector('.legend');

      this._setMaxWeek(data);

      this.visMain = new InsightsGladAlertsChart({
        el: chartEl,
        params: {
          data: this._parseData(data),
          filter: this.filter,
          currentStep: this.currentStep,
          iso: this.currentCountry,
          year: this.currentYear,
          desforestationFilter: this.defaults.desforestationFilter,
        }
      });

      el.classList.remove(this.defaults.loadingClassEl);
      this._createLegend(legendEl, data, 'main');
    },

    _renderNoDataPlaceHolder: function() {
      var el = this.el.querySelector('#visMain');
      var chartEl = el.querySelector('.chart');

      chartEl.innerHTML = 'There\'s no data available for this selection';
    },

    _parseData: function(data) {
      return _.map(data, function(d) {
        var locationData = _.findWhere(this.locations, {
          iso: this.currentCountry
        });

        if (locationData) {
          d.carbon_average = locationData.targets['carbon_emissions'].average;
          d.carbon_target = locationData.targets['carbon_emissions'].target;
          d.deforestation_average = locationData.targets['deforestation'].average;
          d.deforestation_target = locationData.targets['deforestation'].target;
        }

        if (d.iso === this.currentCountry) {
          d.selected = true;
        }

        return d;
      }.bind(this));
    },

    _setMaxWeek: function(data) {
      var lastValue = data && data[data.length - 1] ?
        data[data.length - 1] : [];

      if (lastValue) {
        this.currentStep = (lastValue.week * 1);
      }
    },

    _getIso: function() {
      var country = this.currentCountry;
      var re = /(\d+)/g;
      var subst = '-$1';

      country = country.replace(re, subst);
      return country;
    },

    _createLegend: function(el, data, category) {
      this.legends.push({
        element: el,
        data: data,
        category: category,
        filter: this.filter
      });

      this._renderLegend(el, data, this.filter);
    },

    _renderLegend: function(el, data, filter) {
      var current = _.filter(data, function(d) {
        return (d.week * 1) === this.currentStep;
      }.bind(this))[0];

      if (current) {
        var target = Math.round(current.carbon_target * 1);
        var emissions = Math.round(current.cumulative_emissions * 1);
        var deforestation = Math.round(current.cumulative_deforestation * 1);
        var target_deforestation = Math.round(current.deforestation_target * 1);
        var alerts = Math.round(current.alerts);
        var annual_budget = Math.round(((emissions / target) * 100));
        var annual_budget_deforestation = Math.round(((deforestation / target_deforestation) * 100));
        var co2Equivalency = Math.round(((current.above_ground_carbon_loss * 1) * 10000000) / 4.7);

        var date = moment.utc().year(this.currentYear);
        var weeksInYear = date.weeksInYear();
        var currentWeek = weeksInYear > WEEKS_YEAR ? (this.currentStep + 1) : this.currentStep;
        var dateWithWeek = date.week(currentWeek);

        if (weeksInYear > WEEKS_YEAR) {
          dateWithWeek.subtract(1, 'days');
        } else {
          dateWithWeek.add(1, 'days');
        }

        var begin = dateWithWeek.clone().format('YYYY-MM-DD');
        var end = dateWithWeek.clone().add(6, 'days').format('YYYY-MM-DD');

        el.innerHTML = this.templateLegend({
          isDesforestation: filter === this.defaults.desforestationFilter,
          emissions: NumbersHelper.addNumberDecimals(emissions),
          deforestation: NumbersHelper.addNumberDecimals(deforestation),
          annual_budget:  NumbersHelper.addNumberDecimals(annual_budget),
          annual_budget_deforestation: NumbersHelper.addNumberDecimals(annual_budget_deforestation),
          alerts: NumbersHelper.addNumberDecimals(alerts),
          co2Equivalency: NumbersHelper.addNumberDecimals(co2Equivalency),
          begin: begin,
          end: end,
          iso: this._getIso(),
          week: this.currentStep,
          url: GFW_URL
        });

        this.legendImage = el.querySelector('.image');
        this.showImage();
      }
    },

    _updateLegends: function(state) {
      if (this.currentStep !== state.step) {
        this.currentStep = state.step;
        this.maxData = state.maxData;
        this.legends.forEach(function(legend) {
          this._renderLegend(legend.element, legend.data, legend.filter);
        }.bind(this));
      }
    },

    _changeVisualizations: function(ev) {
      var current = ev.currentTarget;
      var filter = current.dataset.filter;

      this._changeMainVis(filter);
    },

    _changeMainVis: function(filter) {
      this._toggleFilter(this.defaults.mainVisSwitchEl, filter);

      this.visMain.updateByFilter(filter);

      var legend = _.findWhere(this.legends, {
        category: 'main'
      });
      legend.filter = filter;

      this._renderLegend(legend.element, legend.data, legend.filter);
    },

    _clearVisualization: function() {
      if (this.visMain) {
        this.visMain.remove();
        this.legends = [];
        this.images = {}

        var legend = this.el.querySelector('.' + this.defaults.legendSelectoClassEl);
        legend.innerHTML = '';
      }
    },

    _changeDataByCountry: function(ev) {
      var current = ev.currentTarget;
      var iso = current.value;
      var selected = current.querySelector('[data-iso="'+ iso +'"]');

      mps.publish('Router/goInsight', [iso]);
      this._setCurrentCountry(selected.text, iso);
      this._renderMainChart();
    },

    _setCurrentCountry: function(text, iso) {
      var label = this.el.querySelector('.' + this.defaults.countryLabelClassEl);
      var selector = this.el.querySelector('.' + this.defaults.countrySelectorClassEl);
      label.innerHTML = text;
      selector.value = iso;

      this.currentCountry = iso;
    },

    _changeDataByYear: function(ev) {
      var current = ev.currentTarget;
      this.currentYear = parseInt(current.value, 10);

      this._renderMainChart();
    },

    _changeYear: function(ev) {
      var action = ev.currentTarget.dataset.action;
      var $parent = ev.currentTarget.parentNode;
      var $selector = $parent.querySelector('.js-year-selector');
      var options = $selector.options;
      var current = $selector.selectedIndex;
      var max = $selector.length - 1;
      var newIndex = current;
      var $add = $parent.querySelector('.-js-year-nav-right');
      var $sub = $parent.querySelector('.-js-year-nav-left');


      if (action === 'add') {
        newIndex++;
      } else if (action === 'sub') {
        newIndex--;
      }

      if (newIndex < 0) {
        newIndex = max;
      } else if (newIndex > max) {
        newIndex = 0;
      }

      if (newIndex < max) {
        $add.classList.remove('-disabled');
      } else {
        $add.classList.add('-disabled');
      }
      if (newIndex === 0) {
        $sub.classList.add('-disabled');
      } else {
        $sub.classList.remove('-disabled');
      }

      $selector.selectedIndex = newIndex;
      this.currentYear = parseInt(options[newIndex].text, 10);

      this._renderMainChart();
    },

    _toggleFilter: function(element, filter) {
      var parent = this.el.querySelector('.' + element);
      var selected = parent.querySelector('.' + this.defaults.selectedClassEl);
      var newSelection = parent.querySelector('[data-filter="'+ filter +'"]');

      selected.classList.remove(this.defaults.selectedClassEl);
      newSelection.classList.add(this.defaults.selectedClassEl);
      this.filter = filter;
    },

    _openShare: function(event) {
      var shareView = new ShareView().share(event);
      $('body').append(shareView.el);
    },

    /**
     * Shows the image of the current step
     */
    showImage: function() {
      var maxData = this.maxData || this.currentStep;

      if (!this.images[this.currentStep] &&
        (this.currentStep <= maxData)) {
        var image = new Image();
        var currentStep = this.currentStep;

        image.onload = this._onImageLoad(currentStep, image);
        image.onerror = this._onImageError(currentStep);
        image.src = this.imageURI + '/' + this.currentCountry.toUpperCase() +
          '_' + this.currentYear + '_' +
          NumbersHelper.padNumberToTwo(this.currentStep) + '.png';
      } else {
        var img = this.images[this.currentStep];

        if (img && img.image) {
          this._renderLegendImage(img.image);
        } else {
          this._renderLegendImage(false);
        }
      }
    },

    /**
     * On image error
     * @param  {Number} current step
     */
    _onImageError: function(currentStep) {
      return (function(step) {
        return function(e) {
          this.images[step] = {
            loaded: false,
            image: null
          };

          if (step === this.currentStep) {
            this._renderLegendImage(false);
          }
        }.bind(this);
      }.bind(this))(currentStep);
    },

    /**
     * Updates the tooltip of the current step
     * @param  {Number} current step
     * @param  {Object} image object
     */
    _onImageLoad: function(currentStep, image) {
      return (function(step, image) {
        return function(e) {
          this.images[step] = {
            loaded: true,
            image: image
          };

          if (step === this.currentStep) {
            this._renderLegendImage(image);
          }
        }.bind(this);
      }.bind(this))(currentStep, image);
    },

    /**
     * Renders the image or the error message
     * @param  {Object} image object
     */
    _renderLegendImage: function(image) {
      this.legendImage.innerHTML = '';
      this.legendImage.classList.remove('-no-data');

      if (image) {
        this.legendImage.appendChild(image);
      } else {
        this.legendImage.innerHTML = 'Not available';
        this.legendImage.classList.add('-no-data');
      }
    },
  });

  return InsightsGladAlerts;

});