@@ -64,6 +64,8 @@ import {
6464 COLOR_DARK_GREY ,
6565} from '#utils/constants' ;
6666import BaseMap from '#components/domain/BaseMap' ;
67+ import Message from '#components/Message' ;
68+ import Tooltip from '#components/Tooltip' ;
6769
6870import Filters , { type FilterValue } from './Filters' ;
6971import i18n from './i18n.json' ;
@@ -78,9 +80,91 @@ const defaultFilterValue: FilterValue = {
7880 includeCopingCapacity : false ,
7981} ;
8082
81- type BaseProps = {
82- className ?: string ;
83- bbox : LngLatBoundsLike | undefined ;
83+ interface TooltipContentProps {
84+ selectedRiskMetric : RiskMetric ,
85+ valueListByHazard : {
86+ value : number ;
87+ riskCategory : number ;
88+ hazard_type : HazardType ;
89+ hazard_type_display : string ;
90+ } [ ] ;
91+ }
92+
93+ function TooltipContent ( props : TooltipContentProps ) {
94+ const {
95+ selectedRiskMetric,
96+ valueListByHazard,
97+ } = props ;
98+
99+ const strings = useTranslation ( i18n ) ;
100+ const riskCategoryToLabelMap : Record < number , string > = useMemo (
101+ ( ) => ( {
102+ [ CATEGORY_RISK_VERY_LOW ] : strings . riskCategoryVeryLow ,
103+ [ CATEGORY_RISK_LOW ] : strings . riskCategoryLow ,
104+ [ CATEGORY_RISK_MEDIUM ] : strings . riskCategoryMedium ,
105+ [ CATEGORY_RISK_HIGH ] : strings . riskCategoryHigh ,
106+ [ CATEGORY_RISK_VERY_HIGH ] : strings . riskCategoryVeryHigh ,
107+ } ) ,
108+ [
109+ strings . riskCategoryVeryLow ,
110+ strings . riskCategoryLow ,
111+ strings . riskCategoryMedium ,
112+ strings . riskCategoryHigh ,
113+ strings . riskCategoryVeryHigh ,
114+ ] ,
115+ ) ;
116+
117+ const riskMetricLabelMap : Record < RiskMetric , string > = {
118+ riskScore : strings . riskScoreOptionLabel ,
119+ displacement : strings . peopleAtRiskOptionLabel ,
120+ exposure : strings . peopleExposedOptionLabel ,
121+ } ;
122+
123+ return valueListByHazard . map (
124+ ( {
125+ hazard_type_display,
126+ hazard_type,
127+ riskCategory,
128+ value,
129+ } ) => (
130+ < Container
131+ key = { hazard_type }
132+ heading = { hazard_type_display }
133+ headingLevel = { 5 }
134+ spacing = "condensed"
135+ icons = { (
136+ < div className = { styles . tooltipHazardIndicator } >
137+ < div
138+ className = { styles . color }
139+ // eslint-disable-next-line max-len
140+ style = { { backgroundColor : hazardTypeToColorMap [ hazard_type ] } }
141+ />
142+ </ div >
143+ ) }
144+ >
145+ < TextOutput
146+ label = { strings . riskScoreOptionLabel }
147+ strongValue
148+ value = { riskCategoryToLabelMap [ riskCategory ] }
149+ />
150+ { selectedRiskMetric !== 'riskScore' && (
151+ < TextOutput
152+ label = { riskMetricLabelMap [ selectedRiskMetric ] }
153+ strongValue
154+ value = { formatNumber ( Math . ceil ( value ) , { maximumFractionDigits : 0 } ) }
155+ />
156+ ) }
157+ </ Container >
158+ ) ,
159+ ) ;
160+ }
161+
162+ const RISK_LOW_COLOR = '#c7d3e0' ;
163+ const RISK_HIGH_COLOR = '#f5333f' ;
164+
165+ interface ClickedPoint {
166+ feature : GeoJSON . Feature < GeoJSON . Point , GeoJsonProps > ;
167+ lngLat : mapboxgl . LngLatLike ;
84168}
85169
86170interface GeoJsonProps {
@@ -96,6 +180,11 @@ interface GeoJsonProps {
96180 region_id : number ;
97181}
98182
183+ type BaseProps = {
184+ className ?: string ;
185+ bbox : LngLatBoundsLike | undefined ;
186+ }
187+
99188type Props = BaseProps & ( {
100189 variant : 'global' ;
101190 regionId ?: never ;
@@ -104,14 +193,6 @@ type Props = BaseProps & ({
104193 regionId : number ;
105194} ) ;
106195
107- interface ClickedPoint {
108- feature : GeoJSON . Feature < GeoJSON . Point , GeoJsonProps > ;
109- lngLat : mapboxgl . LngLatLike ;
110- }
111-
112- const RISK_LOW_COLOR = '#c7d3e0' ;
113- const RISK_HIGH_COLOR = '#f5333f' ;
114-
115196function RiskSeasonalMap ( props : Props ) {
116197 const {
117198 className,
@@ -164,8 +245,10 @@ function RiskSeasonalMap(props: Props) {
164245 apiType : 'risk' ,
165246 url : '/api/v1/risk-score/' ,
166247 query : variant === 'region'
167- ? { region : regionId }
168- : undefined ,
248+ ? {
249+ region : regionId ,
250+ limit : 9999 ,
251+ } : { limit : 9999 } ,
169252 } ) ;
170253
171254 // NOTE: We get single element as array in response
@@ -600,7 +683,7 @@ function RiskSeasonalMap(props: Props) {
600683 ] ;
601684
602685 if ( isDefined ( riskCategory ) && isDefined ( populationFactor ) ) {
603- riskCategory *= populationFactor ;
686+ riskCategory = Math . ceil ( riskCategory * populationFactor ) ;
604687 }
605688 }
606689
@@ -610,7 +693,7 @@ function RiskSeasonalMap(props: Props) {
610693 ] ;
611694
612695 if ( isDefined ( riskCategory ) && isDefined ( lccFactor ) ) {
613- riskCategory *= lccFactor ;
696+ riskCategory = Math . ceil ( riskCategory * lccFactor ) ;
614697 }
615698 }
616699
@@ -634,6 +717,9 @@ function RiskSeasonalMap(props: Props) {
634717 const maxRiskCategory = maxSafe (
635718 valueListByHazard . map ( ( { riskCategory } ) => riskCategory ) ,
636719 ) ;
720+ const riskCategorySum = sumSafe (
721+ valueListByHazard . map ( ( { riskCategory } ) => riskCategory ) ,
722+ ) ;
637723
638724 if (
639725 isNotDefined ( maxValue )
@@ -658,6 +744,7 @@ function RiskSeasonalMap(props: Props) {
658744 totalValue,
659745 maxValue,
660746 riskCategory : maxRiskCategory ,
747+ riskCategorySum,
661748 valueListByHazard : normalizedValueListByHazard ,
662749 country_details : firstItem . country_details ,
663750 } ;
@@ -675,7 +762,7 @@ function RiskSeasonalMap(props: Props) {
675762 normalizedValue : item . totalValue / maxValue ,
676763 maxValue,
677764 } ) ,
678- ) . sort ( ( a , b ) => compareNumber ( a . totalValue , b . totalValue , - 1 ) ) ;
765+ ) . sort ( ( a , b ) => compareNumber ( a . riskCategorySum , b . riskCategorySum , - 1 ) ) ;
679766 }
680767
681768 if ( filters . riskMetric === 'displacement' ) {
@@ -697,39 +784,6 @@ function RiskSeasonalMap(props: Props) {
697784 ) ;
698785
699786 return transformedData ;
700-
701- /*
702- return transformedData?.map(
703- (item) => {
704- let newNormalizedValue = item.normalizedValue;
705- let newTotalValue = item.totalValue;
706-
707- if (filters.normalizeByPopulation
708- && isDefined(maxPopulation)
709- && maxPopulation > 0
710- ) {
711- const population = mappings?.population[item.iso3] ?? 0;
712- newNormalizedValue *= (population / maxPopulation);
713- newTotalValue *= (population / maxPopulation);
714- }
715-
716- if (filters.includeCopingCapacity
717- && isDefined(maxLackOfCopingCapacity)
718- && maxLackOfCopingCapacity > 0
719- ) {
720- const lcc = mappings?.lcc[item.iso3] ?? 0;
721- newNormalizedValue *= (lcc / maxLackOfCopingCapacity);
722- newTotalValue *= (lcc / maxLackOfCopingCapacity);
723- }
724-
725- return {
726- ...item,
727- totalValue: newTotalValue,
728- normalizedValue: newNormalizedValue,
729- };
730- },
731- ).sort((a, b) => compareNumber(a.totalValue, b.totalValue, -1));
732- */
733787 }
734788
735789 return undefined ;
@@ -762,7 +816,7 @@ function RiskSeasonalMap(props: Props) {
762816 item . country_details . iso3 . toUpperCase ( ) ,
763817 [
764818 'interpolate' ,
765- [ 'exponential' , 1 ] ,
819+ [ 'linear' ] ,
766820 [ 'number' , item . riskCategory ] ,
767821 CATEGORY_RISK_VERY_LOW ,
768822 COLOR_LIGHT_BLUE ,
@@ -785,23 +839,6 @@ function RiskSeasonalMap(props: Props) {
785839 [ filteredData ] ,
786840 ) ;
787841
788- const riskCategoryToLabelMap : Record < number , string > = useMemo (
789- ( ) => ( {
790- [ CATEGORY_RISK_VERY_LOW ] : strings . riskCategoryVeryLow ,
791- [ CATEGORY_RISK_LOW ] : strings . riskCategoryLow ,
792- [ CATEGORY_RISK_MEDIUM ] : strings . riskCategoryMedium ,
793- [ CATEGORY_RISK_HIGH ] : strings . riskCategoryHigh ,
794- [ CATEGORY_RISK_VERY_HIGH ] : strings . riskCategoryVeryHigh ,
795- } ) ,
796- [
797- strings . riskCategoryVeryLow ,
798- strings . riskCategoryLow ,
799- strings . riskCategoryMedium ,
800- strings . riskCategoryHigh ,
801- strings . riskCategoryVeryHigh ,
802- ] ,
803- ) ;
804-
805842 const handleCountryClick = useCallback (
806843 ( feature : mapboxgl . MapboxGeoJSONFeature , lngLat : mapboxgl . LngLat ) => {
807844 setClickedPointProperties ( {
@@ -822,8 +859,8 @@ function RiskSeasonalMap(props: Props) {
822859
823860 const riskPopupValue = useMemo ( ( ) => (
824861 filteredData ?. find (
825- ( filter ) => filter . iso3
826- === clickedPointProperties ?. feature . properties . iso3 . toLowerCase ( ) ,
862+ ( filter ) => filter . iso3 === clickedPointProperties
863+ ?. feature . properties . iso3 . toLowerCase ( ) ,
827864 )
828865 ) , [ filteredData , clickedPointProperties ] ) ;
829866
@@ -932,20 +969,10 @@ function RiskSeasonalMap(props: Props) {
932969 contentViewType = "vertical"
933970 childrenContainerClassName = { styles . popupContent }
934971 >
935- { riskPopupValue . valueListByHazard . map ( ( hazard ) => (
936- < Container
937- key = { hazard . hazard_type }
938- heading = { hazard . hazard_type_display }
939- headingLevel = { 5 }
940- className = { styles . popupAppeal }
941- childrenContainerClassName = { styles . popupAppealDetail }
942- >
943- < TextOutput
944- value = { riskCategoryToLabelMap [ hazard . riskCategory ] }
945- label = { strings . riskPopupLabel }
946- />
947- </ Container >
948- ) ) }
972+ < TooltipContent
973+ selectedRiskMetric = { filters . riskMetric }
974+ valueListByHazard = { riskPopupValue . valueListByHazard }
975+ />
949976 </ MapPopup >
950977 ) }
951978 </ BaseMap >
@@ -959,6 +986,12 @@ function RiskSeasonalMap(props: Props) {
959986 contentViewType = "vertical"
960987 >
961988 { dataPending && < BlockLoading /> }
989+ { ! dataPending && ( isNotDefined ( filteredData ) || filteredData ?. length === 0 ) && (
990+ < Message
991+ // FIXME: add translations
992+ title = "Data not available for selected filters"
993+ />
994+ ) }
962995 { /* FIXME: use List */ }
963996 { ! dataPending && filteredData ?. map (
964997 ( dataItem ) => {
@@ -984,13 +1017,11 @@ function RiskSeasonalMap(props: Props) {
9841017 < div className = { styles . track } >
9851018 { dataItem . valueListByHazard . map (
9861019 ( {
987- normalizedValue,
988- value,
9891020 hazard_type,
990- hazard_type_display ,
1021+ riskCategory ,
9911022 } ) => {
9921023 // eslint-disable-next-line max-len
993- const percentage = 100 * normalizedValue * dataItem . normalizedValue ;
1024+ const percentage = ( 100 * riskCategory ) / ( 5 * filters . hazardTypes . length ) ;
9941025
9951026 if ( percentage < 1 ) {
9961027 return null ;
@@ -999,7 +1030,6 @@ function RiskSeasonalMap(props: Props) {
9991030 return (
10001031 < div
10011032 className = { styles . bar }
1002- title = { `${ hazard_type_display } : ${ filters . riskMetric === 'riskScore' ? riskCategoryToLabelMap [ value ] : formatNumber ( value ) } ` }
10031033 key = { hazard_type }
10041034 style = { {
10051035 width : `${ percentage } %` ,
@@ -1012,6 +1042,16 @@ function RiskSeasonalMap(props: Props) {
10121042 } ,
10131043 ) }
10141044 </ div >
1045+ < Tooltip
1046+ title = { dataItem . country_details . name }
1047+ className = { styles . barChartTooltip }
1048+ description = { (
1049+ < TooltipContent
1050+ selectedRiskMetric = { filters . riskMetric }
1051+ valueListByHazard = { dataItem . valueListByHazard }
1052+ />
1053+ ) }
1054+ />
10151055 </ div >
10161056 ) ;
10171057 } ,
0 commit comments