Skip to content

Commit

Permalink
Real implementation map widget
Browse files Browse the repository at this point in the history
  • Loading branch information
atrincas authored and Andrés González committed Nov 14, 2024
1 parent 0fa72ce commit a21fc25
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export class PostgresSurveyAnswerRepository
}

private async addMapDataToWidget(widget: BaseWidgetWithData): Promise<void> {
const mapSql = `SELECT country_code as country, COUNT(survey_id)::integer AS "count"
const mapSql = `SELECT country_code as country, COUNT(survey_id)::integer AS "value"
FROM ${this.answersTable}
GROUP BY country_code, question, answer
HAVING question = $1 AND answer = 'Yes' ORDER BY country_code`;
Expand Down
42 changes: 1 addition & 41 deletions client/src/containers/widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,47 +258,7 @@ export default function Widget({
/>
<Map
// TODO: Remove hardcoded data when api response is available
data={{
NLD: 1,
BEL: 2,
GBR: 2,
LVA: 2,
BGR: 3,
SWE: 4,
NOR: 2,
FIN: 1,
GRC: 1,
EST: 1,
UKR: 1,
DNK: 2,
POL: 2,
MD: 2,
ROU: 2,
FRA: 5,
IRL: 2,
ISL: 1,
ITA: 3,
CHE: 2,
ESP: 2,
PRT: 1,
DEU: 3,
RUS: 2,
BLR: 1,
SVN: 1,
SVK: 2,
CSK: 3,
CZE: 3,
LTU: 3,
AUT: 4,
HRC: 3,
BIH: 2,
MNE: 1,
HRV: 3,
ALB: 3,
SRB: 4,
HUN: 2,
MDA: 3,
}}
data={data.map}
/>
</Card>
);
Expand Down
35 changes: 15 additions & 20 deletions client/src/containers/widget/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,26 @@ import { FC } from "react";

import { ComposableMap, Geographies, Geography } from "react-simple-maps";

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

import { MapData } from "@/types";
import { WidgetMapData } from "@shared/dto/widgets/base-widget-data.interface";

const FILL_MAP = {
1: "fill-map-1",
2: "fill-map-2",
3: "fill-map-3",
4: "fill-map-4",
5: "fill-map-5",
} as const;
import { cn } from "@/lib/utils";

const BG_MAP = {
1: "bg-map-1",
2: "bg-map-2",
3: "bg-map-3",
4: "bg-map-4",
5: "bg-map-5",
} as const;
import NoData from "@/containers/no-data";
import {
BG_MAP,
FILL_MAP,
transformMapData,
} from "@/containers/widget/map/map.utils";

interface MapProps {
data: MapData;
data?: WidgetMapData;
}

const Map: FC<MapProps> = ({ data }) => {
if (!data || data?.length === 0) return <NoData />;

const map = transformMapData(data);

return (
<div className="relative h-full">
<ComposableMap
Expand All @@ -52,8 +47,8 @@ const Map: FC<MapProps> = ({ data }) => {
strokeWidth="1.0"
className={cn(
"fill-map- focus:outline-none",
data[geo.properties.ADM0_A3]
? FILL_MAP[data[geo.properties.ADM0_A3]]
map[geo.properties.ADM0_A3]
? FILL_MAP[map[geo.properties.ADM0_A3]]
: "fill-background",
)}
/>
Expand Down
43 changes: 43 additions & 0 deletions client/src/containers/widget/map/map.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { WidgetMapData } from "@shared/dto/widgets/base-widget-data.interface";

type MapScale = 1 | 2 | 3 | 4 | 5;

interface MapData {
[key: string]: MapScale;
}

const FILL_MAP = {
1: "fill-map-1",
2: "fill-map-2",
3: "fill-map-3",
4: "fill-map-4",
5: "fill-map-5",
} as const;

const BG_MAP = {
1: "bg-map-1",
2: "bg-map-2",
3: "bg-map-3",
4: "bg-map-4",
5: "bg-map-5",
} as const;

const getScaleFromPercentage = (percentage: number): MapScale => {
if (percentage <= 20) return 1;
if (percentage <= 40) return 2;
if (percentage <= 60) return 3;
if (percentage <= 80) return 4;
return 5;
};

const transformMapData = (data: WidgetMapData): MapData => {
return data.reduce(
(acc, { country, value }) => ({
...acc,
[country]: getScaleFromPercentage(value),
}),
{} as MapData,
);
};

export { FILL_MAP, BG_MAP, getScaleFromPercentage, transformMapData };
8 changes: 4 additions & 4 deletions client/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type Scale = 1 | 2 | 3 | 4 | 5;
// export type MapScale = 1 | 2 | 3 | 4 | 5;

export interface MapData {
[key: string]: Scale;
}
// export interface MapData {
// [key: string]: Scale;
// }
80 changes: 80 additions & 0 deletions client/tests/widget/map.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
getScaleFromPercentage,
transformMapData,
} from "@/containers/widget/map/map.utils";
import { describe, it, expect } from "vitest";

describe("getScaleFromPercentage", () => {
it("should return 1 for percentages <= 20", () => {
expect(getScaleFromPercentage(0)).toBe(1);
expect(getScaleFromPercentage(10)).toBe(1);
expect(getScaleFromPercentage(20)).toBe(1);
});

it("should return 2 for percentages between 21 and 40", () => {
expect(getScaleFromPercentage(21)).toBe(2);
expect(getScaleFromPercentage(30)).toBe(2);
expect(getScaleFromPercentage(40)).toBe(2);
});

it("should return 3 for percentages between 41 and 60", () => {
expect(getScaleFromPercentage(41)).toBe(3);
expect(getScaleFromPercentage(50)).toBe(3);
expect(getScaleFromPercentage(60)).toBe(3);
});

it("should return 4 for percentages between 61 and 80", () => {
expect(getScaleFromPercentage(61)).toBe(4);
expect(getScaleFromPercentage(70)).toBe(4);
expect(getScaleFromPercentage(80)).toBe(4);
});

it("should return 5 for percentages > 80", () => {
expect(getScaleFromPercentage(81)).toBe(5);
expect(getScaleFromPercentage(90)).toBe(5);
expect(getScaleFromPercentage(100)).toBe(5);
});
});

describe("transformMapData", () => {
it("should transform empty array to empty object", () => {
expect(transformMapData([])).toEqual({});
});

it("should transform single country data correctly", () => {
const input = [{ country: "USA", value: 75 }];
expect(transformMapData(input)).toEqual({ USA: 4 });
});

it("should transform multiple countries data correctly", () => {
const input = [
{ country: "BEL", value: 85 },
{ country: "NED", value: 45 },
{ country: "ESP", value: 15 },
];
expect(transformMapData(input)).toEqual({
BEL: 5,
NED: 3,
ESP: 1,
});
});

it("should handle boundary values correctly", () => {
const input = [
{ country: "A", value: 0 },
{ country: "B", value: 20 },
{ country: "C", value: 40 },
{ country: "D", value: 60 },
{ country: "E", value: 80 },
{ country: "F", value: 100 },
];
expect(transformMapData(input)).toEqual({
A: 1,
B: 1,
C: 2,
D: 3,
E: 4,
F: 5,
});
});
});

0 comments on commit a21fc25

Please sign in to comment.