Skip to content

Commit f84818d

Browse files
authored
Merge pull request #3389 from Vizzuality/feature/refactor-components-loss
Feature/refactor components loss
2 parents 3dec17a + 344c884 commit f84818d

File tree

6 files changed

+257
-1
lines changed

6 files changed

+257
-1
lines changed

app/javascript/components/widget/widget-manifest.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import * as gladBiodiversity from './widgetz/alerts/glad-biodiversity';
1515
import * as gladRanked from './widgetz/alerts/glad-ranked';
1616
// Other
1717
import * as rankedPlantations from './widgetz/extent/ranked-plantations';
18+
// Loss
19+
import * as treeLossLocated from './widgetz/loss/tree-loss-located';
1820

1921
export {
2022
treeCover,
@@ -29,5 +31,6 @@ export {
2931
treeCoverRanked,
3032
gladBiodiversity,
3133
gladRanked,
32-
rankedPlantations
34+
rankedPlantations,
35+
treeLossLocated
3336
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { getLocations, getLocationsLoss } from 'services/forest-data';
2+
import groupBy from 'lodash/groupBy';
3+
import axios from 'axios';
4+
5+
export const getData = ({ params, dispatch, setWidgetData, widget }) => {
6+
axios
7+
.all([getLocations({ ...params }), getLocationsLoss({ ...params })])
8+
.then(
9+
axios.spread((getLocationsResponse, getLocationsLossResponse) => {
10+
const extentData = getLocationsResponse.data.data;
11+
const extentMappedData = {};
12+
if (extentData && extentData.length) {
13+
extentMappedData.regions = extentData.map(d => ({
14+
id: d.region,
15+
extent: d.extent || 0,
16+
percentage: d.extent ? d.extent / d.total * 100 : 0
17+
}));
18+
}
19+
const lossData = getLocationsLossResponse.data.data;
20+
const lossMappedData = {};
21+
if (lossData && lossData.length) {
22+
const lossByRegion = groupBy(lossData, 'region');
23+
lossMappedData.regions = Object.keys(lossByRegion).map(d => {
24+
const regionLoss = lossByRegion[d];
25+
return {
26+
id: parseInt(d, 10),
27+
loss: regionLoss
28+
};
29+
});
30+
}
31+
dispatch(
32+
setWidgetData({
33+
data: {
34+
loss: lossMappedData.regions,
35+
extent: extentMappedData.regions
36+
},
37+
widget
38+
})
39+
);
40+
})
41+
)
42+
.catch(error => {
43+
dispatch(setWidgetData({ widget, error: true }));
44+
console.info(error);
45+
});
46+
};
47+
48+
export default {
49+
getData
50+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
export const initialState = {
2+
title: 'Where did tree cover loss occur',
3+
config: {
4+
size: 'small',
5+
indicators: [
6+
'gadm28',
7+
'ifl_2013',
8+
'mining',
9+
'wdpa',
10+
'plantations',
11+
'landmark',
12+
'primary_forest',
13+
'ifl_2013',
14+
'ifl_2013__wdpa',
15+
'ifl_2013__mining',
16+
'ifl_2013__landmark',
17+
'primary_forest',
18+
'primary_forest__mining',
19+
'primary_forest__wdpa',
20+
'primary_forest__landmark',
21+
'plantations__mining',
22+
'plantations__wdpa',
23+
'plantations__landmark'
24+
],
25+
units: ['ha', '%'],
26+
categories: ['summary', 'forest-change'],
27+
admins: ['country', 'region'],
28+
selectors: [
29+
'indicators',
30+
'thresholds',
31+
'units',
32+
'startYears',
33+
'endYears',
34+
'extentYears'
35+
],
36+
locationCheck: true,
37+
type: 'loss',
38+
layers: ['loss'],
39+
metaKey: 'widget_tree_cover_loss_location',
40+
sortOrder: {
41+
summary: 4,
42+
forestChange: 2
43+
},
44+
sentences: {
45+
initial:
46+
'In {location}, the top {percentileLength} regions were responsible for {topLoss} of all tree cover loss between {startYear} and {endYear}. {region} had the largest tree cover loss at {value} compared to an average of {average}.',
47+
withIndicator:
48+
'For {indicator} in {location}, the top {percentileLength} regions were responsible for {topLoss} of all tree cover loss between {startYear} and {endYear}. {region} had the largest tree cover loss at {value} compared to an average of {average}.',
49+
initialPercent:
50+
'In {location}, the top {percentileLength} regions were responsible for {topLoss} of all tree cover loss between {startYear} and {endYear}. {region} had the largest relative tree cover loss at {value} compared to an average of {average}.',
51+
withIndicatorPercent:
52+
'For {indicator} in {location}, the top {percentileLength} regions were responsible for {topLoss} of all tree cover loss between {startYear} and {endYear}. {region} had the largest relative tree cover loss at {value} compared to an average of {average}.'
53+
}
54+
},
55+
settings: {
56+
indicator: 'gadm28',
57+
threshold: 30,
58+
extentYear: 2000,
59+
unit: 'ha',
60+
pageSize: 5,
61+
page: 0,
62+
startYear: 2001,
63+
endYear: 2016,
64+
layers: ['loss']
65+
},
66+
enabled: true
67+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { createSelector } from 'reselect';
2+
import isEmpty from 'lodash/isEmpty';
3+
import uniqBy from 'lodash/uniqBy';
4+
import sumBy from 'lodash/sumBy';
5+
import { sortByKey } from 'utils/data';
6+
import { format } from 'd3-format';
7+
8+
// get list data
9+
const getLoss = state => state.data.loss || null;
10+
const getExtent = state => state.data.extent || null;
11+
const getSettings = state => state.settings || null;
12+
const getOptions = state => state.options || null;
13+
const getActiveIndicator = state => state.activeIndicator;
14+
const getLocation = state => state.location || null;
15+
const getLocationsMeta = state =>
16+
(!state.region ? state.regions : state.subRegions) || null;
17+
const getLocationNames = state => state.locationNames || null;
18+
const getColors = state => state.colors || null;
19+
const getSentences = state => state.config && state.config.sentences;
20+
21+
export const mapData = createSelector(
22+
[getLoss, getExtent, getSettings, getLocation, getLocationsMeta],
23+
(data, extent, settings, location, meta) => {
24+
if (!data || isEmpty(data) || !meta || isEmpty(meta)) return null;
25+
const { startYear, endYear } = settings;
26+
const mappedData = data.map(d => {
27+
const region = meta.find(l => d.id === l.value);
28+
const loss =
29+
sumBy(
30+
d.loss.filter(l => l.year >= startYear && l.year <= endYear),
31+
'area_loss'
32+
) || 0;
33+
const locationExtent = extent.filter(l => l.id === d.id);
34+
const percentage = loss / locationExtent[0].extent * 100;
35+
return {
36+
label: (region && region.label) || '',
37+
loss,
38+
percentage,
39+
value: settings.unit === 'ha' ? loss : percentage,
40+
path: `/country/${location.country}/${
41+
location.region ? `${location.region}/` : ''
42+
}${d.id}`
43+
};
44+
});
45+
46+
return mappedData;
47+
}
48+
);
49+
50+
export const parseData = createSelector(
51+
[mapData, getColors],
52+
(data, colors) => {
53+
if (!data) return null;
54+
const sortedData = sortByKey(uniqBy(data, 'label'), 'value', true);
55+
56+
return sortedData.map(o => ({
57+
...o,
58+
color: colors.main
59+
}));
60+
}
61+
);
62+
63+
export const getSentence = createSelector(
64+
[
65+
mapData,
66+
getSettings,
67+
getOptions,
68+
getLocation,
69+
getActiveIndicator,
70+
getLocationNames,
71+
getSentences
72+
],
73+
74+
(data, settings, options, location, indicator, locationNames, sentences) => {
75+
if (!data || !options || !indicator || !locationNames) return '';
76+
const {
77+
initial,
78+
withIndicator,
79+
initialPercent,
80+
withIndicatorPercent
81+
} = sentences;
82+
const { startYear, endYear } = settings;
83+
const totalLoss = sumBy(data, 'loss');
84+
const currentLocation =
85+
locationNames && locationNames.current && locationNames.current.label;
86+
const topRegion = data.length && data[0];
87+
const avgLossPercentage = sumBy(data, 'percentage') / data.length;
88+
const avgLoss = sumBy(data, 'loss') / data.length;
89+
let percentileLoss = 0;
90+
let percentileLength = 0;
91+
92+
while (
93+
(percentileLength < data.length && percentileLoss / totalLoss < 0.5) ||
94+
(percentileLength < 10 && data.length > 10)
95+
) {
96+
percentileLoss += data[percentileLength].loss;
97+
percentileLength += 1;
98+
}
99+
const topLoss = percentileLoss / totalLoss * 100;
100+
let sentence =
101+
indicator.value === 'gadm28' ? initialPercent : withIndicatorPercent;
102+
if (settings.unit !== '%') {
103+
sentence = indicator.value === 'gadm28' ? initial : withIndicator;
104+
}
105+
106+
const params = {
107+
indicator: indicator.value,
108+
location: currentLocation,
109+
startYear,
110+
endYear,
111+
topLoss: `${format('.0f')(topLoss)}%`,
112+
percentileLength,
113+
region: percentileLength > 1 ? topRegion.label : 'This region',
114+
value:
115+
topRegion.percentage > 1 && settings.unit === '%'
116+
? `${format('.0f')(topRegion.percentage)}%`
117+
: `${format('.3s')(topRegion.loss)}ha`,
118+
average:
119+
topRegion.percentage > 1 && settings.unit === '%'
120+
? `${format('.0f')(avgLossPercentage)}%`
121+
: `${format('.3s')(avgLoss)}ha`
122+
};
123+
124+
return {
125+
sentence,
126+
params
127+
};
128+
}
129+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Component from 'components/widget/components/widget-numbered-list';
2+
import { getData } from './actions';
3+
import { parseData, getSentence } from './selectors';
4+
import { initialState } from './initial-state';
5+
6+
export { getData, parseData, getSentence, Component, initialState };

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"install": "0.10.1",
103103
"js-yaml": "3.9.1",
104104
"lodash": "4.17.4",
105+
"lodash-math": "0.0.7",
105106
"lory.js": "2.3.3",
106107
"moment": "2.19.3",
107108
"numeral": "2.0.6",

0 commit comments

Comments
 (0)