Skip to content

Commit 2a9c9b2

Browse files
authored
Merge pull request #3677 from Vizzuality/feature/whrc-biomass
Feature/whrc biomass widget (simple sentence)
2 parents e9899f0 + 91aef67 commit 2a9c9b2

File tree

17 files changed

+309
-20
lines changed

17 files changed

+309
-20
lines changed

app/javascript/components/numbered-list/numbered-list-component.jsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ class NumberedList extends PureComponent {
3838
<div className="item-name">{item.label}</div>
3939
</div>
4040
<div className="item-value">
41-
{formatNumber({ num: item.value, unit })}
41+
{formatNumber({
42+
num: item.value,
43+
unit: item.unit || unit
44+
})}
4245
</div>
4346
</div>
4447
);

app/javascript/components/widgets/components/widget/component.jsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class Widget extends PureComponent {
3838
{loading && <Loader className="widget-loader" />}
3939
{!loading &&
4040
!error &&
41-
!hasData && (
41+
!hasData &&
42+
Component && (
4243
<NoContent message={`No data in selection for ${locationName}`} />
4344
)}
4445
{!loading &&
@@ -56,7 +57,8 @@ class Widget extends PureComponent {
5657
/>
5758
)}
5859
{!error &&
59-
hasData && (
60+
hasData &&
61+
Component && (
6062
<Component
6163
widget={widget}
6264
data={data}

app/javascript/components/widgets/components/widget/components/widget-header/components/widget-settings/widget-settings-component.jsx

+29-12
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ class WidgetSettings extends PureComponent {
1616
});
1717
}
1818

19-
getUnit = (units, widget, settings, onSettingsChange) => {
20-
if (units.length <= 1) return null;
21-
if (units.length === 2) {
19+
getUnitVariable = (items, widget, settings, onSettingsChange, type) => {
20+
if (items.length <= 1) return null;
21+
if (items.length === 2) {
2222
return (
2323
<Switch
2424
theme="theme-switch-light"
25-
label="UNIT"
26-
value={settings.unit}
27-
options={units}
25+
label={type === 'unit' ? 'UNIT' : 'VARIABLE'}
26+
value={settings[type]}
27+
options={items}
2828
onChange={option =>
29-
onSettingsChange({ value: { unit: option }, widget })
29+
onSettingsChange({ value: { [type]: option }, widget })
3030
}
3131
/>
3232
);
@@ -35,11 +35,11 @@ class WidgetSettings extends PureComponent {
3535
return (
3636
<Dropdown
3737
theme="theme-select-light"
38-
label="UNIT"
39-
value={settings.unit}
40-
options={units}
38+
label={type === 'unit' ? 'UNIT' : 'VARIABLE'}
39+
value={settings[type]}
40+
options={items}
4141
onChange={option =>
42-
onSettingsChange({ value: { unit: option.value }, widget })
42+
onSettingsChange({ value: { [type]: option.value }, widget })
4343
}
4444
/>
4545
);
@@ -118,6 +118,7 @@ class WidgetSettings extends PureComponent {
118118
} = this.props;
119119
const {
120120
units,
121+
variables,
121122
forestTypes,
122123
landCategories,
123124
periods,
@@ -133,6 +134,7 @@ class WidgetSettings extends PureComponent {
133134
} = this.props.options;
134135
const hasExtraOptions =
135136
units ||
137+
variables ||
136138
periods ||
137139
years ||
138140
startYears ||
@@ -284,7 +286,22 @@ class WidgetSettings extends PureComponent {
284286
settings,
285287
onSettingsChange
286288
)}
287-
{units && this.getUnit(units, widget, settings, onSettingsChange)}
289+
{units &&
290+
this.getUnitVariable(
291+
units,
292+
widget,
293+
settings,
294+
onSettingsChange,
295+
'unit'
296+
)}
297+
{variables &&
298+
this.getUnitVariable(
299+
variables,
300+
widget,
301+
settings,
302+
onSettingsChange,
303+
'variable'
304+
)}
288305
{periods && (
289306
<Dropdown
290307
theme="theme-select-light"

app/javascript/components/widgets/manifest.js

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import * as treeCoverLocated from './widgets/land-cover/tree-cover-located';
2727
// Climate
2828
import * as emissions from './widgets/climate/emissions';
2929
import * as emissionsDeforestation from './widgets/climate/emissions-deforestation';
30+
import * as woodyBiomass from './widgets/climate/whrc-biomass/';
3031
import * as emissionsPlantations from './widgets/climate/emissions-plantations';
3132
import * as futureCarbonGains from './widgets/climate/future-carbon-gains';
3233

@@ -65,6 +66,7 @@ export default {
6566
// climate
6667
emissions,
6768
emissionsDeforestation,
69+
woodyBiomass,
6870
emissionsPlantations,
6971
futureCarbonGains,
7072
// biodiversity

app/javascript/components/widgets/options.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import forestTypes from 'data/forest-types.json';
22
import landCategories from 'data/land-categories.json';
33
import thresholds from 'data/thresholds.json';
44
import units from 'data/units.json';
5+
import variables from 'data/variables.json';
56
import periods from 'data/periods.json';
67
import extentYears from 'data/extent-years.json';
78
import tscDriverGroups from 'data/tsc-loss-groups.json';
@@ -19,5 +20,6 @@ export default {
1920
tscDriverGroups,
2021
types,
2122
weeks,
22-
datasets
23+
datasets,
24+
variables
2325
};

app/javascript/components/widgets/selectors.js

+19
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,24 @@ export const getChildLocationData = createSelector(
113113
}
114114
);
115115

116+
export const getLocationDict = createSelector(
117+
[getLocationData, selectLocation],
118+
(locationData, location) => {
119+
let values;
120+
if (location.adm2) values = locationData.adm2;
121+
else if (location.adm1) values = locationData.adm1;
122+
else values = locationData.adm0;
123+
124+
return values.reduce(
125+
(dict, next) => ({
126+
...dict,
127+
[next.value]: next.label
128+
}),
129+
{}
130+
);
131+
}
132+
);
133+
116134
export const getLocationObject = createSelector(
117135
[getActiveLocationData, selectLocation],
118136
(adms, location) => {
@@ -298,6 +316,7 @@ export const getWidgetsProps = createStructuredSelector({
298316
locationData: getActiveLocationData,
299317
locationObject: getLocationObject,
300318
locationName: getLocationName,
319+
locationDict: getLocationDict,
301320
childLocationData: getChildLocationData,
302321
noWidgetsMessage: getNoWidgetsMessage,
303322
isTropical: isTropicalLocation

app/javascript/components/widgets/widgets/climate/emissions-deforestation/config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default {
1111
units: ['co2LossByYear', 'cLossByYear']
1212
},
1313
analysis: true,
14-
layers: ['b32a2f15-25e8-4ecc-98e0-68782ab1c0fe'],
14+
// layers: ['b32a2f15-25e8-4ecc-98e0-68782ab1c0fe'],
1515
metaKey: 'widget_carbon_emissions_tree_cover_loss',
1616
dataType: 'loss',
1717
colors: 'climate',

app/javascript/components/widgets/widgets/climate/emissions-plantations/config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default {
1313
units: ['co2LossByYear', 'cLossByYear']
1414
},
1515
colors: 'climate',
16-
layers: ['b32a2f15-25e8-4ecc-98e0-68782ab1c0fe'],
16+
// layers: ['b32a2f15-25e8-4ecc-98e0-68782ab1c0fe'],
1717
metaKey: 'tree_biomass_loss',
1818
sortOrder: {},
1919
sentences: {

app/javascript/components/widgets/widgets/climate/future-carbon-gains/config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default {
1111
options: {
1212
units: ['co2Gain', 'cGain']
1313
},
14-
layers: ['fffa76d3-5008-48b7-afeb-2c7054548f2e'],
14+
// layers: ['fffa76d3-5008-48b7-afeb-2c7054548f2e'],
1515
metaKey: 'potential_tree_biomass_gain',
1616
sortOrder: {
1717
summary: 1,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { getBiomassRanking } from 'services/climate';
2+
3+
export default ({ params }) =>
4+
getBiomassRanking({ ...params }).then(res => res.data && res.data.rows);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export default {
2+
widget: 'whrc-biomass',
3+
title: 'Biomass density in {location}',
4+
categories: ['climate'],
5+
types: ['global', 'country'],
6+
admins: ['global', 'adm0', 'adm1', 'adm2'],
7+
options: {
8+
thresholds: true,
9+
variables: ['totalbiomass', 'biomassdensity']
10+
},
11+
colors: 'climate',
12+
dataType: 'loss',
13+
metaKey: 'aboveground_biomass',
14+
// layers: ['f10bded4-94e2-40b6-8602-ae5bdfc07c08'],
15+
sortOrder: {
16+
summary: 0,
17+
forestChange: 0
18+
},
19+
sentences: {
20+
initial:
21+
'In 2000, {location} had a biomass density of {biomassDensity}, and a total biomass of {totalBiomass}.',
22+
totalbiomass:
23+
'Around {value} of the world’s {label} is contained in the top 5 countries.',
24+
biomassdensity:
25+
'The average {label} of the world’s top 5 countries is {value}.'
26+
}
27+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Component from 'components/widgets/components/widget-numbered-list';
2+
import getData from './actions';
3+
import getProps from './selectors';
4+
import config from './config';
5+
import settings from './settings';
6+
7+
export { Component, getData, getProps, config, settings };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { createSelector, createStructuredSelector } from 'reselect';
2+
import isEmpty from 'lodash/isEmpty';
3+
import findIndex from 'lodash/findIndex';
4+
import { formatNumber } from 'utils/format';
5+
import { sortByKey } from 'utils/data';
6+
7+
// get list data
8+
const getData = state => state.data && state.data;
9+
const getLocationName = state => state.locationName || null;
10+
const getLocation = state => state.allLocation || null;
11+
const getLocationDict = state => state.locationDict || null;
12+
const getLocationObject = state => state.locationObject || null;
13+
const getSentences = state => state.config && state.config.sentences;
14+
const getColors = state => state.colors || null;
15+
const getSettings = state => state.settings || null;
16+
17+
const getSortedData = createSelector(
18+
[getData, getSettings],
19+
(data, settings) => {
20+
if (isEmpty(data)) return null;
21+
return sortByKey(data, settings.variable).reverse();
22+
}
23+
);
24+
25+
export const parseData = createSelector(
26+
[
27+
getSortedData,
28+
getColors,
29+
getLocation,
30+
getLocationDict,
31+
getLocationObject,
32+
getSettings
33+
],
34+
(data, colors, location, locationsDict, locationObj, settings) => {
35+
if (isEmpty(data)) return null;
36+
37+
let dataTrimmed = data.map((d, i) => ({
38+
...d,
39+
rank: i + 1
40+
}));
41+
42+
let key;
43+
if (data[0].admin_2) key = 'admin_2';
44+
else if (data[0].admin_1) key = 'admin_1';
45+
else key = 'iso';
46+
47+
if (location.payload.adm0) {
48+
const locationIndex = locationObj
49+
? findIndex(data, d => d[key] === locationObj.value)
50+
: -1;
51+
if (locationIndex === -1) return null;
52+
53+
let trimStart = locationIndex - 2;
54+
let trimEnd = locationIndex + 3;
55+
if (locationIndex < 2) {
56+
trimStart = 0;
57+
trimEnd = 5;
58+
}
59+
if (locationIndex > data.length - 3) {
60+
trimStart = data.length - 5;
61+
trimEnd = data.length;
62+
}
63+
dataTrimmed = dataTrimmed.slice(trimStart, trimEnd);
64+
}
65+
const { query, type } = location;
66+
67+
return dataTrimmed.map((d, i) => ({
68+
...d,
69+
label: locationsDict[d[key]],
70+
color: colors.density,
71+
key: `${d.iso}-${i}`,
72+
path: {
73+
type,
74+
payload: { type: 'country', adm0: d.iso },
75+
query
76+
},
77+
value: d[settings.variable],
78+
unit: settings.variable === 'totalbiomass' ? 't' : 't/ha'
79+
}));
80+
}
81+
);
82+
83+
export const parseSentence = createSelector(
84+
[getData, getLocationName, getSentences, getLocationDict, getSettings],
85+
(data, location, sentences, locationsDict, settings) => {
86+
if (!sentences || isEmpty(data)) return null;
87+
88+
if (location === 'global') {
89+
const sorted = sortByKey(data, [settings.variable]).reverse();
90+
91+
let biomTop5 = 0;
92+
let densTop5 = 0;
93+
const biomTotal = sorted.reduce((acc, next, i) => {
94+
if (i < 5) {
95+
biomTop5 += next.totalbiomass;
96+
densTop5 += next.biomassdensity;
97+
}
98+
return acc + next.totalbiomass;
99+
}, 0);
100+
101+
const percent = biomTop5 / biomTotal * 100;
102+
const avgBiomDensity = densTop5 / 5;
103+
104+
const value =
105+
settings.variable === 'totalbiomass'
106+
? formatNumber({ num: percent, unit: '%' })
107+
: formatNumber({ num: avgBiomDensity, unit: 't/ha' });
108+
109+
const labels = {
110+
biomassdensity: 'biomass density',
111+
totalbiomass: 'total biomass'
112+
};
113+
114+
return {
115+
sentence: sentences[settings.variable],
116+
params: {
117+
label: labels[settings.variable],
118+
value
119+
}
120+
};
121+
}
122+
const iso = Object.keys(locationsDict).find(
123+
key => locationsDict[key] === location
124+
);
125+
const region = data.find(item => {
126+
if (item.admin_2) return String(item.admin_2) === iso;
127+
else if (item.admin_1) return String(item.admin_1) === iso;
128+
return item.iso === iso;
129+
});
130+
if (!region) return null;
131+
132+
const { biomassdensity, totalbiomass } = region;
133+
return {
134+
sentence: sentences.initial,
135+
params: {
136+
location,
137+
biomassDensity: formatNumber({ num: biomassdensity, unit: 't/ha' }),
138+
totalBiomass: formatNumber({ num: totalbiomass, unit: 't' })
139+
}
140+
};
141+
}
142+
);
143+
144+
export default createStructuredSelector({
145+
data: parseData,
146+
sentence: parseSentence
147+
});

0 commit comments

Comments
 (0)