diff --git a/components/charts/composed-chart/component.jsx b/components/charts/composed-chart/component.jsx index c95d23bfae..24d96b5989 100644 --- a/components/charts/composed-chart/component.jsx +++ b/components/charts/composed-chart/component.jsx @@ -46,7 +46,7 @@ const XAxisTickWithoutGap = ({ x, y, payload }) => { ); - /* + /* Work around to show number 100 in the end of X axis in the chart since the Data API sends 0 to 90 percent in the tree cover density widget 0 stands to 0%-9% as 90 stands to 90%-99% diff --git a/components/widget/component.jsx b/components/widget/component.jsx index 25e5324ad8..7cb4cf2fe4 100644 --- a/components/widget/component.jsx +++ b/components/widget/component.jsx @@ -13,6 +13,7 @@ class Widget extends PureComponent { title: PropTypes.string.isRequired, type: PropTypes.string, active: PropTypes.bool, + analysis: PropTypes.bool, downloadDisabled: PropTypes.bool, filterSelected: PropTypes.bool, maxSize: PropTypes.number, @@ -85,6 +86,7 @@ class Widget extends PureComponent { colors, type, active, + analysis, downloadDisabled, filterSelected, maxSize, @@ -218,6 +220,7 @@ class Widget extends PureComponent { large={large} autoHeight={autoHeight} embed={embed} + analysis={analysis} location={location} locationName={locationLabelFull} active={active} diff --git a/components/widget/components/widget-chart-legend/component.jsx b/components/widget/components/widget-chart-legend/component.jsx new file mode 100644 index 0000000000..707670fff6 --- /dev/null +++ b/components/widget/components/widget-chart-legend/component.jsx @@ -0,0 +1,78 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +import cx from 'classnames'; + +class WidgetChartLegend extends PureComponent { + render() { + const { className, vertical = false, data = {} } = this.props; + const { columns = [] } = data; + + const anyColumnsHaveTitle = !!columns.find((column) => column.title); + + return ( +
+ {columns.map(({ title, items }, columnIdx) => { + return ( +
+ {title && ( + + {title} + + )} + +
+ ); + })} +
+ ); + } +} + +WidgetChartLegend.propTypes = { + className: PropTypes.string, + vertical: PropTypes.bool, + data: { + columns: { + title: PropTypes.string, + items: PropTypes.arrayOf( + PropTypes.shape({ + color: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + }) + ), + }, + }, +}; + +export default WidgetChartLegend; diff --git a/components/widget/components/widget-chart-legend/index.js b/components/widget/components/widget-chart-legend/index.js new file mode 100644 index 0000000000..f1d269317e --- /dev/null +++ b/components/widget/components/widget-chart-legend/index.js @@ -0,0 +1,3 @@ +import Component from './component'; + +export default Component; diff --git a/components/widget/components/widget-chart-legend/styles.scss b/components/widget/components/widget-chart-legend/styles.scss new file mode 100644 index 0000000000..20556f2722 --- /dev/null +++ b/components/widget/components/widget-chart-legend/styles.scss @@ -0,0 +1,81 @@ +@import '~styles/settings.scss'; + +.c-widget-chart-legend { + display: flex; + margin: 20px 0 0; + width: 100%; + gap: 60px; + + &__column { + &-title { + display: block; + font-weight: 500; + font-size: rem(13px); + margin: 0 0 rem(8px) 0; + } + + &-items { + display: flex; + flex-direction: column; + gap: 5px; + + &.padded { + padding-top: rem(2px); + } + } + + &-item { + font-size: rem(12px); + display: flex; + + &--circle, + &--dashline { + display: inline-block; + width: rem(12px); + min-width: rem(12px); + min-height: rem(12px); + height: rem(12px); + margin-right: rem(7px); + } + + &--circle { + border-radius: 100%; + align-self: flex-start; + margin-top: 1px; + } + + &--dashline { + border-radius: 0; + border: none; + border-top: 2px dotted; + align-self: center; + transform: translateY(4px); + } + + p { + color: $slate; + font-size: rem(12px); + line-height: 1.4; + } + } + } + + &.vertical { + flex-direction: column; + gap: 5px; + + .c-widget-chart-legend { + &__column { + &-title { + margin-top: rem(8px); + } + + &-items { + &.padded { + padding-top: 0; + } + } + } + } + } +} diff --git a/components/widget/components/widget-composed-chart/component.jsx b/components/widget/components/widget-composed-chart/component.jsx index f4cc1106f9..19f7ee1f68 100644 --- a/components/widget/components/widget-composed-chart/component.jsx +++ b/components/widget/components/widget-composed-chart/component.jsx @@ -5,9 +5,11 @@ import debounce from 'lodash/debounce'; import ComposedChart from 'components/charts/composed-chart'; import Brush from 'components/charts/brush-chart'; import Legend from 'components/charts/components/chart-legend'; +import ChartLegend from '../widget-chart-legend'; class WidgetComposedChart extends Component { static propTypes = { + analysis: PropTypes.bool, originalData: PropTypes.array, data: PropTypes.array, config: PropTypes.object, @@ -63,6 +65,7 @@ class WidgetComposedChart extends Component { render() { const { + analysis, originalData, data, config, @@ -72,7 +75,7 @@ class WidgetComposedChart extends Component { barBackground, toggleSettingsMenu, } = this.props; - const { brush, legend } = config; + const { brush, legend, chartLegend } = config; const showLegendSettingsBtn = settingsConfig && settingsConfig.some((conf) => conf.key === 'compareYear'); @@ -107,6 +110,8 @@ class WidgetComposedChart extends Component { onBrushEnd={this.handleBrushEnd} /> )} + + {chartLegend && } ); } diff --git a/components/widgets/climate/emissions-deforestation-drivers/selectors.js b/components/widgets/climate/emissions-deforestation-drivers/selectors.js index c6269a223a..8253efb859 100644 --- a/components/widgets/climate/emissions-deforestation-drivers/selectors.js +++ b/components/widgets/climate/emissions-deforestation-drivers/selectors.js @@ -161,6 +161,25 @@ export const parseConfig = createSelector( }) .reverse() ); + + // Example on how to add columns & titles to the Chart Legend + // See: https://gfw.atlassian.net/browse/FLAG-1145 + // const chartLegend = { + // columns: [ + // { + // items: ['Wildfire', 'Forestry', 'Shifting agriculture']?.map( + // (name) => ({ label: name, color: categoryColors[name] }) + // ), + // }, + // { + // title: 'Drivers of permanent deforestation', + // items: ['Commodity driven deforestation', 'Urbanization']?.map( + // (name) => ({ label: name, color: categoryColors[name] }) + // ), + // }, + // ], + // }; + const insertIndex = findIndex(tooltip, { key: 'class_Urbanization' }); if (insertIndex > -1) { tooltip.splice(insertIndex, 0, { @@ -181,6 +200,7 @@ export const parseConfig = createSelector( formatNumber({ num: value, specialSpecifier: '.2s', spaceUnit: true }), unit: 'tCO2e', tooltip, + // chartLegend, }; } ); diff --git a/components/widgets/component.jsx b/components/widgets/component.jsx index 0113cdc2bf..16230e657c 100644 --- a/components/widgets/component.jsx +++ b/components/widgets/component.jsx @@ -31,6 +31,7 @@ class Widgets extends PureComponent { setMapSettings: PropTypes.func.isRequired, handleClickWidget: PropTypes.func.isRequired, embed: PropTypes.bool, + analysis: PropTypes.bool, dashboard: PropTypes.bool, groupBySubcategory: PropTypes.bool, modalClosing: PropTypes.bool, @@ -60,6 +61,7 @@ class Widgets extends PureComponent { groupBySubcategory = false, embed, dashboard, + analysis, simple, modalClosing, noDataMessage, @@ -112,6 +114,7 @@ class Widgets extends PureComponent { authenticated={authenticated} active={activeWidget && activeWidget.widget === w.widget} embed={embed} + analysis={analysis} dashboard={dashboard} simple={simple} location={location} diff --git a/components/widgets/forest-change/tree-loss-primary/selectors.js b/components/widgets/forest-change/tree-loss-primary/selectors.js index 47f86bd964..60effc90ae 100644 --- a/components/widgets/forest-change/tree-loss-primary/selectors.js +++ b/components/widgets/forest-change/tree-loss-primary/selectors.js @@ -151,6 +151,23 @@ const parseConfig = createSelector([getColors], (colors) => ({ color: colors.primaryForestLoss, }, ], + chartLegend: { + columns: [ + { + items: [ + { + label: 'Area of tree cover loss within 2001 primary forest extent', + color: colors.primaryForestLoss, + }, + { + label: 'Percent of primary forest area in 2001 remaining', + color: colors.primaryForestExtent, + dashline: true, + }, + ], + }, + ], + }, })); export const parseTitle = createSelector( diff --git a/pages/_app.js b/pages/_app.js index 603aba4db8..5354de7a79 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -276,6 +276,7 @@ import '../components/widget/styles.scss'; import '../components/widgets/styles.scss'; import '../components/widget/components/widget-alert/styles.scss'; import '../components/widget/components/widget-body/styles.scss'; +import '../components/widget/components/widget-chart-legend/styles.scss'; import '../components/widget/components/widget-chart-and-list/styles.scss'; import '../components/widget/components/widget-chart-list/styles.scss'; import '../components/widget/components/widget-footer/styles.scss';