Skip to content

Commit

Permalink
Merge pull request #4910 from wri/feat/grouped-legend-FLAG-1226
Browse files Browse the repository at this point in the history
[FLAG-1226] Support grouped legend in doughnut charts
  • Loading branch information
willian-viana authored Jan 14, 2025
2 parents e56b581 + d9b26f1 commit 6b6c12b
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 46 deletions.
70 changes: 68 additions & 2 deletions components/charts/components/pie-chart-legend/component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import PropTypes from 'prop-types';
import { formatNumber } from 'utils/format';
import cx from 'classnames';

import groupBy from 'lodash/groupBy';

class PieChartLegend extends PureComponent {
render() {
const { data, chartSettings = {}, config, className, simple } = this.props;
const { size, legend } = chartSettings;
const {
data,
chartSettings = {},
config,
className,
simple,
onMap,
} = this.props;
const { size, legend, groupedLegends } = chartSettings;

const sizeClass = (() => {
if (size) return size;
Expand All @@ -24,6 +33,61 @@ class PieChartLegend extends PureComponent {
})}`?.length > 9
);

if (groupedLegends) {
const groupedItems = groupBy(data, 'category');

return (
<div
className={cx(
{
'c-pie-chart-legend': true,
'c-pie-chart-legend-grouped': groupedLegends,
'top-margin-20': groupedLegends && onMap,
},
className
)}
>
{Object.entries(groupedItems).map(([category, categoryItems]) => (
<div className="legend-group">
<h2 className="legend-group-title">{category}</h2>
<ul className={cx('legend-group-list', simple, sizeClass)}>
{categoryItems.map((item, index) => {
const value = `${formatNumber({
num: item[config.key],
unit: item.unit ? item.unit : config.unit,
spaceUnit: item.unit !== '%' && config.unit !== 'countsK',
})}`;

return (
<li className="legend-item" key={index.toString()}>
<div className="legend-title">
<span style={{ backgroundColor: item.color }}>{}</span>
<p>
{item.label}
{sizeClass === 'x-small' && `- ${value}`}
</p>
</div>
{sizeClass !== 'x-small' && (
<div
className={cx({
'legend-value': true,
'legend-value--small': shouldDisplaySmallerValues,
})}
style={{ color: item.color }}
>
{value}
</div>
)}
</li>
);
})}
</ul>
</div>
))}
</div>
);
}

return (
<div
className={cx('c-pie-chart-legend', className)}
Expand Down Expand Up @@ -69,12 +133,14 @@ class PieChartLegend extends PureComponent {
}

PieChartLegend.propTypes = {
onMap: PropTypes.bool,
data: PropTypes.array,
config: PropTypes.object,
chartSettings: PropTypes.shape({
size: PropTypes.oneOf(['small', 'x-small']),
legend: PropTypes.shape({ style: PropTypes.object }),
chart: PropTypes.shape({ style: PropTypes.object }),
groupedLegends: PropTypes.bool,
}),
simple: PropTypes.bool,
className: PropTypes.string,
Expand Down
42 changes: 34 additions & 8 deletions components/charts/components/pie-chart-legend/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,30 @@
display: inline-block;
width: 100%;

&-grouped {
flex-direction: column;
}

.legend-group {
display: flex;
flex-direction: column;

.legend-group-title {
margin-bottom: 0.9375rem;
font-weight: 500;
font-size: 0.875rem;
}

.legend-group-list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-bottom: 1.25rem;
}
}

.legend-title {
font-size: rem(12px);
font-size: 0.75rem;
line-height: 1;
color: $slate;
position: relative;
Expand All @@ -14,19 +36,19 @@

span {
display: inline-block;
width: rem(12px);
min-width: rem(12px);
min-height: rem(12px);
height: rem(12px);
margin-right: rem(7px);
width: 0.75rem;
min-width: 0.75rem;
min-height: 0.75rem;
height: 0.75rem;
margin-right: 0.4375rem;
border-radius: 100%;
align-self: flex-start;
margin-top: 1px;
margin-top: 0.0625rem;
}

p {
color: $slate;
font-size: rem(12px);
font-size: 0.75rem;
line-height: 1.4;
}
}
Expand Down Expand Up @@ -72,3 +94,7 @@
}
}
}

.top-margin-20 {
margin-top: 1.25rem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { formatNumber } from 'utils/format';

import cx from 'classnames';

import PieChart from 'components/charts/pie-chart';
import PieChartLegend from 'components/charts/components/pie-chart-legend';
import Button from 'components/ui/button';
Expand All @@ -21,6 +23,9 @@ class WidgetPieChart extends PureComponent {
} = this.props;
const { pathname } = location;
const { chartHeight } = settings;
const { groupedLegends } = chartSettings;

const onMap = pathname.indexOf('map') !== -1;

const showSettingsBtn =
settingsBtnConfig &&
Expand All @@ -47,7 +52,12 @@ class WidgetPieChart extends PureComponent {
{settingsBtnConfig.text}
</Button>
)}
<div className="pie-and-legend">
<div
className={cx({
'pie-and-legend': true,
'pie-and-legend-grouped': groupedLegends && onMap,
})}
>
<PieChartLegend
className="cover-legend"
data={legendData || data}
Expand All @@ -57,6 +67,7 @@ class WidgetPieChart extends PureComponent {
key: 'value',
...settings,
}}
onMap={onMap}
simple={simple}
chartSettings={chartSettings}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
flex-direction: row;
justify-content: space-between;
gap: 4%;

&-grouped {
flex-direction: column-reverse;
}
}

.cover-legend {
Expand Down
29 changes: 2 additions & 27 deletions components/widgets/forest-change/_tree-loss-drivers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,6 @@ export default {
},
types: ['global', 'country'],
admins: ['global', 'adm0'],
alerts: {
default: [
{
id: 'tree-loss-drivers-alert-1',
text: `The methods behind this data have changed over time. Be cautious comparing old and new, data especially before/after 2015. [Read more here](https://www.globalforestwatch.org/blog/data-and-research/tree-cover-loss-satellite-data-trend-analysis/).`,
icon: 'warning',
visible: ['global', 'country', 'geostore', 'aoi', 'wdpa', 'use'],
},
],
indonesia: [
{
id: 'tree-loss-drivers-indonesia-alert-1',
text: `Indonesia’s rates of deforestation have slowed significantly in recent years (2016-2021), largely due to reductions in commodity-driven expansion. Much of the primary forest loss from commodity-driven deforestation in Indonesia according to the GFW data actually took place in areas legally classified as secondary forests, not primary forests. Please note that ground verification is recommended before any hard conclusions are drawn about the type of forest affected, or cause of loss, in specific patches of loss on the GFW map.`,
icon: 'warning',
visible: ['global', 'country', 'geostore', 'aoi', 'wdpa', 'use'],
areaWhitelist: ['IDN'],
},
],
},
categories: ['summary', 'forest-change'],
subcategories: ['forest-loss'],
large: true,
Expand Down Expand Up @@ -100,27 +81,21 @@ export default {
return {
...((dashboard || embed) && {
size: 'small',
legend: {
style: {
display: 'flex',
justifyContent: 'center',
paddingLeft: '8%',
},
},
chart: {
style: {
display: 'flex',
height: 'auto',
alignItems: 'center',
paddingRight: '14%',
},
},
}),
groupedLegends: true,
};
},
getData: (params) =>
getTreeCoverLossByDriverType(params).then((response) => {
const { data } = (response && response.data) || {};

return data;
}),
getDataURL: (params) => [
Expand Down
25 changes: 17 additions & 8 deletions components/widgets/forest-change/_tree-loss-drivers/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,26 @@ const getColors = (state) => state.colors;
const getSettings = (state) => state.settings;
const getTitle = (state) => state.title;
const getSentences = (state) => state.sentences;
const getCaution = (state) => state.caution;
const getLocationLabel = (state) => state.locationLabel;
const getAdm0 = (state) => state.adm0;
const getSortedCategories = () =>
tscLossCategories.sort((a, b) => (a.position > b.position ? 1 : -1));

const groupedLegends = {
'commodity driven deforestation': 'Drivers of temporary deforestation',
forestry: 'Drivers of temporary deforestation',
'forest management': 'Drivers of temporary deforestation',
'shifting cultivation': 'Drivers of temporary deforestation',
'shifting agriculture': 'Drivers of temporary deforestation',
wildfire: 'Drivers of temporary deforestation',
'other natural disasters': 'Drivers of temporary deforestation',
'hard commodities': 'Drivers of permanent deforestation',
'Drivers of permanent deforestation agriculture':
'Drivers of permanent deforestation',
'settlements and infrastructure': 'Drivers of permanent deforestation',
urbanization: 'Drivers of permanent deforestation',
unknown: 'Drivers of permanent deforestation',
};

export const getPermanentCategories = createSelector(
[getSortedCategories],
(sortedCategories) =>
Expand Down Expand Up @@ -47,6 +61,7 @@ export const parseData = createSelector(
return {
label: driver_type,
value: loss_area_ha,
category: groupedLegends[driver_type.toLowerCase()],
color: categoryColors[driver_type],
percentage: (loss_area_ha * 100) / totalLoss,
};
Expand Down Expand Up @@ -112,14 +127,8 @@ export const parseSentence = createSelector(
}
);

export const parseCaution = createSelector(
[getCaution, getAdm0],
(caution, adm0) => (adm0 === 'IDN' ? caution.indonesia : caution.default)
);

export default createStructuredSelector({
data: parseData,
title: parseTitle,
sentence: parseSentence,
caution: parseCaution,
});

0 comments on commit 6b6c12b

Please sign in to comment.