Skip to content

Commit

Permalink
Merge pull request #4873 from wri/feat/tree-cover-gain-FLAG-1018
Browse files Browse the repository at this point in the history
[FLAG-1018][EPIC] Tree cover gain 5y baseline year
  • Loading branch information
willian-viana authored Dec 19, 2024
2 parents 789ac26 + 577092e commit 58d67d5
Show file tree
Hide file tree
Showing 20 changed files with 334 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const mapStateToProps = (
startDate,
endDate,
trimEndDate,
step,
matchLegend,
dynamicTimeline,
...props
}
Expand All @@ -28,7 +30,8 @@ const mapStateToProps = (
dynamicTimeline,
};
return {
marks: getMarks({ dates, dynamicTimeline }),
marks: getMarks({ dates, step, matchLegend, dynamicTimeline }),
step,
...props,
};
};
Expand Down
13 changes: 11 additions & 2 deletions components/map/components/legend/components/timeline/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import moment from 'moment';
import range from 'lodash/range';

const getDates = (state) => state.dates;
export const getMarks = createSelector(getDates, (dates) => {
const getSliderStep = (state) => state.step;
const getMatchLegend = (state) => state.matchLegend;

const getTicksStep = (numOfYears, sliderStep, matchLegend) => {
if (matchLegend && numOfYears && sliderStep) return numOfYears / sliderStep;
return (numOfYears > 6 ? 6 : numOfYears);
}

export const getMarks = createSelector(getDates, getSliderStep, getMatchLegend, (dates, sliderStep, matchLegend) => {
if (!dates) return null;
const { minDate, maxDate, dynamicTimeline = false } = dates;
const numOfYears = moment(maxDate).diff(minDate, 'years');
Expand All @@ -12,11 +20,12 @@ export const getMarks = createSelector(getDates, (dates) => {
if (!numOfYears || maxDays <= 365) return null;

const marks = {};
const ticksStep = getTicksStep(numOfYears, sliderStep, matchLegend);

let ticks = range(
0,
maxDays + 1,
maxDays / (numOfYears > 6 ? 6 : numOfYears)
maxDays / ticksStep
);

if (dynamicTimeline) {
Expand Down
69 changes: 67 additions & 2 deletions components/slider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export class Slider extends PureComponent {
railStyle: PropTypes.shape({}),
dotStyle: PropTypes.shape({}),
pushable: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
disableStartHandle: PropTypes.bool,
disableEndHandle: PropTypes.bool,
playing: PropTypes.bool,
onChange: PropTypes.func,
};

static defaultProps = {
Expand All @@ -44,14 +48,39 @@ export class Slider extends PureComponent {
railStyle: { backgroundColor: '#d9d9d9' },
dotStyle: { visibility: 'hidden', border: '0px' },
pushable: true,
disableStartHandle: false,
disableEndHandle: false,
playing: false,
onChange: () => {},
};

renderHandle = (props) => {
const { formatValue, showTooltip } = this.props;
const {
formatValue,
showTooltip,
playing,
disableStartHandle,
disableEndHandle,
} = this.props;
const { value, dragging, index, ...restProps } = props;
const formattedValue = formatValue ? formatValue(value) : value;
const tooltipVisible = showTooltip ? showTooltip(index) : false;

// Start handle
if (disableStartHandle && props?.index === 0) {
return null;
}

// End handle
if (disableEndHandle && props?.index === 2) {
return null;
}

// Vertical line that indicates the current position, when playing
if (!playing && props?.index === 1) {
return null;
}

return (
<Tooltip
key={index}
Expand All @@ -68,8 +97,43 @@ export class Slider extends PureComponent {
);
};

handleOnChange = (newSliderPositions) => {
const {
value: sliderPositions,
disableStartHandle,
disableEndHandle,
onChange,
} = this.props;

// Both handles are disabled, no possible changes can be made.
if (disableStartHandle && disableEndHandle) {
return null;
}

// Start handle disabled. We allow trim and end value, but keep the start value the same.
if (disableStartHandle) {
onChange([
sliderPositions[0],
newSliderPositions[1],
newSliderPositions[2],
]);
return null;
}

// End handle disabled. We allow the start value, but keep the same trim and end value.
if (disableEndHandle) {
onChange([newSliderPositions[0], sliderPositions[1], sliderPositions[2]]);
return null;
}

// Full functionality, pass the new values along.
onChange(newSliderPositions);
return null;
};

render() {
const { customClass, range, handleStyle, value, ...rest } = this.props;
const { customClass, range, handleStyle, value, onChange, ...rest } =
this.props;

const Component = range ? Range : RCSlider;
const handleNum = Array.isArray(value) ? value.length : 1;
Expand Down Expand Up @@ -98,6 +162,7 @@ export class Slider extends PureComponent {
handle={this.renderHandle}
handleStyle={handleStyles}
value={value}
onChange={this.handleOnChange}
{...rest}
/>
</div>
Expand Down
11 changes: 10 additions & 1 deletion components/timestep/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ class Timestep extends PureComponent {
handleOnChange: PropTypes.func,
handleOnAfterChange: PropTypes.func,
handleOnPlay: PropTypes.func,
disableStartHandle: PropTypes.bool,
disableEndHandle: PropTypes.bool,
};

static defaultProps = {
customClass: null,
range: true,
pushable: 0,
canPlay: false,
canPlay: true,

trim: null,

Expand Down Expand Up @@ -75,6 +77,8 @@ class Timestep extends PureComponent {
handleOnChange: null,
handleOnAfterChange: null,
handleOnPlay: null,
disableStartHandle: false,
disableEndHandle: false,
};

constructor(props) {
Expand Down Expand Up @@ -462,6 +466,8 @@ class Timestep extends PureComponent {
range,
pushable,
PlayButton,
disableStartHandle,
disableEndHandle,
} = this.props;

const { playing } = this.state;
Expand All @@ -488,6 +494,9 @@ class Timestep extends PureComponent {
pushable={pushable}
onChange={this.handleOnChange}
onAfterChange={this.handleOnAfterChange}
disableStartHandle={disableStartHandle}
disableEndHandle={disableEndHandle}
playing={playing}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,29 @@ class WidgetSettings extends PureComponent {
/>
</div>
);

case 'baseline-select':
return (
options &&
!!options.length && (
<Dropdown
className={cx('widget-settings-selector', type)}
theme={cx('theme-select-light', {
'theme-dropdown-button': type === 'mini-select',
})}
label={label}
value={startValue}
options={options}
onChange={(change) =>
propagateChange({ [startKey]: change && change.value })}
disabled={loading}
clearable={clearable}
infoAction={metaKey ? () => handleShowInfo(metaKey) : null}
optionsAction={handleShowInfo}
optionsActionKey="metaKey"
noSelectedValue={placeholder}
/>
)
);
case 'compare-select':
return (
<div className={cx('widget-settings-selector', type)}>
Expand All @@ -120,7 +142,7 @@ class WidgetSettings extends PureComponent {
const loadingDatepicker = !startValue || !minDate || !maxDate;

return (
<div className={cx("widget-settings-selector", type)}>
<div className={cx('widget-settings-selector', type)}>
<div className="datepicker-selector">
<div>
<span className="label">From</span>
Expand All @@ -130,7 +152,7 @@ class WidgetSettings extends PureComponent {
selected={new Date(startValue)}
onChange={(change) =>
propagateChange({
[startKey]: moment(change).format("YYYY-MM-DD"),
[startKey]: moment(change).format('YYYY-MM-DD'),
})}
minDate={new Date(minDate)}
maxDate={new Date(maxDate)}
Expand All @@ -146,7 +168,7 @@ class WidgetSettings extends PureComponent {
selected={new Date(endValue)}
onChange={(change) =>
propagateChange({
[endKey]: moment(change).format("YYYY-MM-DD"),
[endKey]: moment(change).format('YYYY-MM-DD'),
})}
minDate={new Date(minDate)}
maxDate={new Date(maxDate)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {

import getWidgetProps from './selectors';

const MIN_YEAR = 2000;

export default {
widget: 'treeCoverGainOutsidePlantations',
title: {
Expand Down Expand Up @@ -41,8 +43,16 @@ export default {
clearable: true,
blacklist: ['wdpa'],
},
{
key: 'baselineYear',
label: 'Baseline Year',
type: 'baseline-select',
startKey: 'startYear',
placeholder: MIN_YEAR,
clearable: true,
},
],
refetchKeys: ['forestType', 'landCategory'],
refetchKeys: ['forestType', 'landCategory', 'startYear'],
chartType: 'pieChart',
colors: 'gainWithinOutsidePlantations',
metaKey: 'widget_tree_cover_gain_outside_plantations',
Expand All @@ -68,21 +78,25 @@ export default {
},
sentences: {
globalInitial:
'Globally between 2000 and 2020, {gainPercent} of tree cover gain occurred outside of plantations.',
'Globally between {baselineYear} and 2020, {gainPercent} of tree cover gain occurred outside of plantations.',
globalWithIndicator:
'Globally between 2000 and 2020, {gainPercent} of tree cover gain within {indicator} occurred outside of plantations.',
'Globally between {baselineYear} and 2020, {gainPercent} of tree cover gain within {indicator} occurred outside of plantations.',
regionInitial:
'In {location} between 2000 and 2020, {gainPercent} of tree cover gain occurred outside of plantations.',
'In {location} between {baselineYear} and 2020, {gainPercent} of tree cover gain occurred outside of plantations.',
regionWithIndicator:
'In {location} between 2000 and 2020, {gainPercent} of tree cover gain within {indicator} occurred outside of plantations.',
'In {location} between {baselineYear} and 2020, {gainPercent} of tree cover gain within {indicator} occurred outside of plantations. ',
},
settings: {
threshold: 0,
startYear: MIN_YEAR,
endYear: 2020, // reference to display the correct data on the map
},
getData: (params) => {
return getTreeCoverGainByPlantationType(params).then((response) => {
const { data } = (response && response.data) || {};

if (data?.length === 0) return null;

const totalArea = data.reduce(
(prev, curr) => prev + curr?.gain_area_ha,
0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@ export const parseSentence = createSelector(
}
})();

const { baselineYear: dateFromDashboard, startYear: dateFromMapLayer } =
settings;

const params = {
location: locationName,
indicator: indicator && indicator.label,
startYear: settings.startYear,
endYear: settings.endYear,
baselineYear: dateFromMapLayer || dateFromDashboard || 2000,
gainPercent: formatNumber({
num: (100 * data?.areaOutsidePlantations) / data?.totalArea,
unit: '%',
Expand Down
18 changes: 16 additions & 2 deletions components/widgets/forest-change/tree-cover-gain-simple/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {

import getWidgetProps from './selectors';

const MIN_YEAR = 2000;

export default {
widget: 'treeCoverGainSimple',
title: 'Tree cover gain in {location}',
Expand All @@ -24,7 +26,7 @@ export default {
metaKey: 'umd_tree_cover_gain_from_height',
dataType: 'gain',
pendingKeys: ['threshold'],
refetchKeys: ['threshold'],
refetchKeys: ['threshold', 'startYear'],
datasets: [
{
dataset: POLITICAL_BOUNDARIES_DATASET,
Expand All @@ -45,11 +47,23 @@ export default {
settings: {
threshold: 0,
extentYear: 2000,
startYear: MIN_YEAR,
endYear: 2020, // reference to display the correct data on the map
},
chartType: 'listLegend',
colors: 'gain',
sentence:
'From 2000 to 2020, {location} gained {gain} of tree cover equal to {gainPercent} is its total extent.',
'From {baselineYear} to 2020, {location} gained {gain} of tree cover equal to {gainPercent} is its total extent in that time period.',
settingsConfig: [
{
key: 'baselineYear',
label: 'Baseline Year',
type: 'baseline-select',
startKey: 'startYear',
placeholder: MIN_YEAR,
clearable: true,
},
],
getData: (params) => {
if (shouldQueryPrecomputedTables(params)) {
return getGain(params).then((response) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ const getExtent = (state) => state.data && state.data.extent;
const getSentence = (state) => state.sentence;
const getLocationName = (state) => state.locationLabel;
const getColors = (state) => state.colors;
const getSettings = (state) => state.settings;

export const parseSentence = createSelector(
[getGain, getExtent, getSentence, getLocationName],
(gain, extent, sentence, location) => {
[getGain, getExtent, getSentence, getLocationName, getSettings],
(gain, extent, sentence, location, settings) => {
if (!gain && !extent) return null;
const gainPerc = (gain && extent && (gain / extent) * 100) || 0;
const { baselineYear: dateFromDashboard, startYear: dateFromMapLayer } =
settings;

const params = {
gain: formatNumber({ num: gain, unit: 'ha', spaceUnit: true }),
gainPercent: formatNumber({ num: gainPerc, unit: '%' }),
location,
baselineYear: dateFromMapLayer || dateFromDashboard || 2000,
};

return {
Expand Down
Loading

0 comments on commit 58d67d5

Please sign in to comment.