Skip to content

Commit

Permalink
Merge pull request #17 from Vizzuality/SKY30-48-fe-implement-the-prop…
Browse files Browse the repository at this point in the history
…ortion-of-habitat-within-protected-and-conserved-areas-widget

[SKY30-48] Proportion of habitat widget
  • Loading branch information
SARodrigues authored Oct 23, 2023
2 parents 6661cf9 + d9064cb commit 94665f4
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 1 deletion.
68 changes: 68 additions & 0 deletions frontend/src/components/charts/horizontal-bar-chart/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useMemo } from 'react';

import { cn } from '@/lib/utils';

const DEFAULT_BAR_COLOR = '#1E1E1E';
const DEFAULT_MAX_PERCENTAGE = 55;
const PROTECTION_TARGET = 30;

type HorizontalBarChartProps = {
className: string;
data: {
barColor: string;
title: string;
totalArea: number;
protectedArea: number;
};
};

const HorizontalBarChart: React.FC<HorizontalBarChartProps> = ({ className, data }) => {
const { title, barColor, totalArea, protectedArea } = data;

const targetPositionPercentage = useMemo(() => {
return (PROTECTION_TARGET * 100) / DEFAULT_MAX_PERCENTAGE;
}, []);

const protectedAreaPercentage = useMemo(() => {
return ((protectedArea * 100) / totalArea).toFixed(1);
}, [totalArea, protectedArea]);

const barFillPercentage = useMemo(() => {
return Math.round((protectedArea * DEFAULT_MAX_PERCENTAGE) / totalArea);
}, [protectedArea, totalArea]);

return (
<div className={cn(className)}>
<div className="flex justify-end text-3xl font-bold">{protectedAreaPercentage}%</div>
<div className="flex justify-between text-xs">
<span>{title} (i)</span>
<span>
of {totalArea} km<sup>2</sup>
</span>
</div>
<div className="relative my-2 flex h-3">
<span className="absolute top-1/2 h-px w-full border-b border-dashed border-black"></span>
<span
className="absolute top-0 bottom-0 left-0"
style={{ backgroundColor: barColor || DEFAULT_BAR_COLOR, width: `${barFillPercentage}%` }}
></span>
<span
className="absolute top-0 bottom-0 border-r-2 border-orange"
style={{
width: `${targetPositionPercentage}%`,
}}
>
<span className="absolute right-0 top-5 whitespace-nowrap text-xs text-orange">
30% target
</span>
</span>
</div>
<div className="flex justify-between text-xs">
<span>0%</span>
<span>{DEFAULT_MAX_PERCENTAGE}%</span>
</div>
</div>
);
};

export default HorizontalBarChart;
20 changes: 20 additions & 0 deletions frontend/src/components/widget/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { PropsWithChildren } from 'react';

type WidgetProps = {
title: string;
lastUpdated: string;
};

const Widget: React.FC<PropsWithChildren<WidgetProps>> = ({ title, lastUpdated, children }) => {
return (
<div>
<div>
<h2 className="font-sans text-xl font-bold">{title}</h2>
<span className="text-xs">Data last updated: {lastUpdated}</span>
</div>
<div>{children}</div>
</div>
);
};

export default Widget;
8 changes: 8 additions & 0 deletions frontend/src/constants/habitat-chart-colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const HABITAT_CHART_COLORS = {
'warm-water corals': '#AD6CFF',
'cold-water corals': '#04D8F4',
mangroves: '#FD8E28',
seagrasses: '#02B07C',
saltmarshes: '#717B00',
seamounts: '#9B5400',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useMemo } from 'react';

import HorizontalBarChart from '@/components/charts/horizontal-bar-chart';
import Widget from '@/components/widget';
import { HABITAT_CHART_COLORS } from '@/constants/habitat-chart-colors';
import { useGetHabitatStats } from '@/types/generated/habitat-stat';
import type { Location } from '@/types/generated/strapi.schemas';

type HabitatWidgetProps = {
location: Location;
};

const HabitatWidget: React.FC<HabitatWidgetProps> = ({ location }) => {
const lastUpdated = 'October 2023';

const { data: habitatStatsResponse } = useGetHabitatStats({
populate: '*',
filters: {
location: {
code: location.code,
},
},
});

const habitatStatsData = habitatStatsResponse?.data;

const widgetChartData = useMemo(() => {
if (!habitatStatsData) return [];

const parsedData = habitatStatsData.map((entry) => {
const stats = entry?.attributes;
const habitat = stats?.habitat?.data.attributes;

return {
title: habitat.name,
slug: habitat.slug,
barColor: HABITAT_CHART_COLORS[habitat.slug],
totalArea: stats.totalArea,
protectedArea: stats.protectedArea,
};
});

return parsedData.reverse();
}, [habitatStatsData]);

// If there is no data for the widget, do not display it.
if (!widgetChartData.length) return null;

return (
<Widget
title="Proportion of Habitat within Protected and Conserved Areas"
lastUpdated={lastUpdated}
>
{widgetChartData.map((chartData) => (
<HorizontalBarChart key={chartData.slug} className="py-2" data={chartData} />
))}
</Widget>
);
};

export default HabitatWidget;
15 changes: 14 additions & 1 deletion frontend/src/containers/data-tool/sidebar/widgets/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { useAtomValue } from 'jotai';

import { locationAtom } from '@/store/location';

import HabitatWidget from './habitat';

const DataToolWidgets: React.FC = () => {
return <div>Widgets</div>;
const location = useAtomValue(locationAtom);

return (
<div className="flex flex-col font-mono">
<HabitatWidget location={location} />
{/* <HabitatWidget location={location} /> */}
</div>
);
};

export default DataToolWidgets;

0 comments on commit 94665f4

Please sign in to comment.