Skip to content

Commit

Permalink
Feature/f 154 legend color hovering (#126)
Browse files Browse the repository at this point in the history
* feat: add hovering effect on legend colors

* feat: update tooltip comp

* feat: update legend data with hovering values

* fix: replace ternarys with clsx
  • Loading branch information
ahmedfarouk2000 authored Dec 13, 2024
1 parent 646177f commit 404edba
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 49 deletions.
48 changes: 37 additions & 11 deletions src/components/Legend/GradientLegend.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import clsx from 'clsx';

import { ColorsData } from '@/domain/props/ColorsData';
import GradientLegendProps from '@/domain/props/GradientLegendProps';

export default function GradientLegend({ colors, startLabel, endLabel, hasNotAnalyzedPoint }: GradientLegendProps) {
const gradients: string = colors
.map((color: string, index: number) => {
const percentage = (index / (colors.length - 1)) * 100;
return `hsl(var(--nextui-${color})) ${percentage}%`;
import { Tooltip } from '../Tooltip/Tooltip';

export default function GradientLegend({ colorsData, startLabel, endLabel, hasNotAnalyzedPoint }: GradientLegendProps) {
const gradients: string = colorsData
.map((colorData: ColorsData, index: number) => {
const percentage = (index / (colorsData.length - 1)) * 100;
return `hsl(var(--nextui-${colorData.color})) ${percentage}%`;
})
.join(', ');

const segmentWidth: number = 100 / colorsData.length;

return (
<div className="relative flex flex-col items-end w-full md:w-96 px-4 py-3">
{hasNotAnalyzedPoint && (
Expand All @@ -20,12 +27,31 @@ export default function GradientLegend({ colors, startLabel, endLabel, hasNotAna
</div>
)}

<div
className="flex items-center w-full h-2 rounded-full"
style={{
background: `linear-gradient(90deg, ${gradients})`,
}}
/>
<div className="relative w-full h-2 rounded-full" style={{ background: `linear-gradient(90deg, ${gradients})` }}>
{colorsData.map((colorData, index) => (
<div
key={colorData.color}
className="absolute top-0 left-0 h-full"
style={{
width: `${segmentWidth}%`,
left: `${index * segmentWidth}%`,
}}
>
<Tooltip title={colorData.title} text={colorData.value} titleStyle="text-center" textStyle="text-center">
<div
className={clsx('hover:bg-opacity-25 hover:bg-black group flex-1 h-full cursor-pointer', {
'rounded-l-full': index === 0,
'rounded-r-full': index === colorsData.length - 1,
})}
style={{
flexBasis: `${segmentWidth}%`,
}}
/>
</Tooltip>
</div>
))}
</div>

<div className="flex justify-between w-full mt-2 text-xs font-medium">
<span>{startLabel}</span>
<span>{endLabel}</span>
Expand Down
11 changes: 7 additions & 4 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Tooltip as NextUITooltip } from '@nextui-org/tooltip';
import clsx from 'clsx';

import TooltipProps from '@/domain/props/TooltipProps';

Expand All @@ -11,9 +12,11 @@ import TooltipProps from '@/domain/props/TooltipProps';
* @param text textual content of the tooltip
* @param delay delay with which tooltip appears on hover in milliseconds; default is 0
* @param warning selected if the tooltip should be highlighted (optional)
* @param titleStyle tailwind classes to style the title (optional)
* @param textStyle tailwind classes to style the text (optional)
* @constructor
*/
export function Tooltip({ children, title, text, delay, warning }: TooltipProps) {
export function Tooltip({ children, title, text, delay, warning, titleStyle, textStyle }: TooltipProps) {
const OFFSET: number = 10;
const RADIUS = 'sm';
const SHADOW = 'md';
Expand All @@ -23,11 +26,11 @@ export function Tooltip({ children, title, text, delay, warning }: TooltipProps)

const tooltipContent = title ? (
<div>
<h3 className="text-small font-bold mb-1"> {title} </h3>
<p className="text-small font-normal"> {text} </p>
<h3 className={clsx('text-small font-bold mb-1', titleStyle)}> {title} </h3>
<p className={clsx('text-small font-normal', textStyle)}> {text} </p>
</div>
) : (
<p className="text-small font-normal"> {text} </p>
<p className={`text-small font-normal ${textStyle}`}> {text} </p>
);

return (
Expand Down
76 changes: 46 additions & 30 deletions src/domain/constant/legend/mapLegendData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,15 @@ export function mapLegendData(
case GlobalInsight.FOOD:
legendData.push({
title: 'Prevalence of insufficient food consumption',
colors: ['fcsGradient1', 'fcsGradient2', 'fcsGradient3', 'fcsGradient4', 'fcsGradient5', 'fcsGradient6'],
colorsData: [
{ color: 'fcsGradient1', title: 'Very Low', value: '0-5%' },
{ color: 'fcsGradient2', title: 'Low', value: '5-10%' },
{ color: 'fcsGradient3', title: 'Moderately Low', value: '10-20%' },
{ color: 'fcsGradient4', title: 'Moderately High', value: '20-30%' },
{ color: 'fcsGradient5', title: 'High', value: '30-40%' },
{ color: 'fcsGradient6', title: 'Very High', value: 'Above 40%' },
],

startLabel: '0%',
endLabel: 'above 40%',
popoverInfo: (
Expand Down Expand Up @@ -158,17 +166,18 @@ export function mapLegendData(
case GlobalInsight.RAINFALL:
legendData.push({
title: 'Rainfall',
colors: [
'vegetationGradient1',
'vegetationGradient2',
'vegetationGradient3',
'vegetationGradient4',
'vegetationGradient5',
'rainfallGradient6',
'rainfallGradient7',
'rainfallGradient8',
'rainfallGradient9',
colorsData: [
{ color: 'vegetationGradient1', value: '<40%' },
{ color: 'vegetationGradient2', value: '40-60%' },
{ color: 'vegetationGradient3', value: '60-80%' },
{ color: 'vegetationGradient4', value: '80-90%' },
{ color: 'vegetationGradient5', value: '90-110%' },
{ color: 'rainfallGradient6', value: '110-120%' },
{ color: 'rainfallGradient7', value: '120-140%' },
{ color: 'rainfallGradient8', value: '140-180%' },
{ color: 'rainfallGradient9', value: '>180%' },
],

startLabel: '<40%',
endLabel: '>180%',
popoverInfo: (
Expand Down Expand Up @@ -196,17 +205,18 @@ export function mapLegendData(
case GlobalInsight.VEGETATION:
legendData.push({
title: 'Vegetation',
colors: [
'vegetationGradient1',
'vegetationGradient2',
'vegetationGradient3',
'vegetationGradient4',
'vegetationGradient5',
'vegetationGradient6',
'vegetationGradient7',
'vegetationGradient8',
'vegetationGradient9',
colorsData: [
{ color: 'vegetationGradient1', value: '<50%' },
{ color: 'vegetationGradient2', value: '50-70%' },
{ color: 'vegetationGradient3', value: '70-80%' },
{ color: 'vegetationGradient4', value: '80-90%' },
{ color: 'vegetationGradient5', value: '90-110%' },
{ color: 'vegetationGradient6', value: '110-120%' },
{ color: 'vegetationGradient7', value: '120-130%' },
{ color: 'vegetationGradient8', value: '130-150%' },
{ color: 'vegetationGradient9', value: '>150%' },
],

startLabel: '<50%',
endLabel: '>150%',
popoverInfo: (
Expand Down Expand Up @@ -236,14 +246,14 @@ export function mapLegendData(
legendData.push({
title: 'Number of people in IPC/CH Phase 3 or above (millions)',
hasNotAnalyzedPoint: true,
colors: [
'ipcGradient1',
'ipcGradient2',
'ipcGradient3',
'ipcGradient4',
'ipcGradient5',
'ipcGradient6',
'ipcGradient7',
colorsData: [
{ color: 'ipcGradient1', value: '0-0.099' },
{ color: 'ipcGradient2', value: '0.1-0.49' },
{ color: 'ipcGradient3', value: '0.5-0.99' },
{ color: 'ipcGradient4', value: '1.0-2.99' },
{ color: 'ipcGradient5', value: '3.0-4.99' },
{ color: 'ipcGradient6', value: '5.0-9.99' },
{ color: 'ipcGradient7', value: '>10' },
],
startLabel: '0',
endLabel: '>10',
Expand Down Expand Up @@ -356,7 +366,13 @@ export function mapLegendData(
} else {
legendData.push({
title: 'Risk of Inadequate Micronutrient Intake',
colors: ['ipcGradient1', 'ipcGradient2', 'ipcGradient3', 'ipcGradient4', 'ipcGradient5'],
colorsData: [
{ color: 'ipcGradient1', title: 'Lowest', value: '0-19%' },
{ color: 'ipcGradient2', title: 'Low', value: '20-39%' },
{ color: 'ipcGradient3', title: 'Moderate', value: '40-59%' },
{ color: 'ipcGradient4', title: 'High', value: '60-79%' },
{ color: 'ipcGradient5', title: 'Highest', value: '80-100%' },
],
startLabel: '0%',
endLabel: '100%',
popoverInfo: (
Expand Down
5 changes: 5 additions & 0 deletions src/domain/props/ColorsData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ColorsData {
color: string;
title?: string;
value: string;
}
4 changes: 3 additions & 1 deletion src/domain/props/GradientLegendContainerItem.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ReactNode } from 'react';

import { ColorsData } from './ColorsData';

export interface GradientLegendContainerItem {
colors: string[];
colorsData: ColorsData[];
title: string;
startLabel: string;
endLabel: string;
Expand Down
4 changes: 3 additions & 1 deletion src/domain/props/GradientLegendProps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ColorsData } from './ColorsData';

export default interface GradientLegendProps {
colors: string[];
colorsData: ColorsData[];
startLabel: string;
endLabel: string;
hasNotAnalyzedPoint?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions src/domain/props/TooltipProps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ export default interface TooltipProps {
text: string;
delay?: number;
warning?: boolean;
titleStyle?: string;
textStyle?: string;
}
4 changes: 2 additions & 2 deletions src/operations/legends/LegendOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export class LegendOperations {
return false;
}
return (
Array.isArray((value as GradientLegendContainerItem).colors) &&
(value as GradientLegendContainerItem).colors.every((color) => typeof color === 'string')
Array.isArray((value as GradientLegendContainerItem).colorsData) &&
(value as GradientLegendContainerItem).colorsData.every((colorsData) => typeof colorsData.color === 'string')
);
}
}

0 comments on commit 404edba

Please sign in to comment.