Skip to content

Commit

Permalink
feat(global): tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
hamster1963 committed Nov 25, 2024
1 parent 32df026 commit f043f5e
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 64 deletions.
73 changes: 13 additions & 60 deletions app/(main)/ClientComponents/Global.tsx
Original file line number Diff line number Diff line change
@@ -1,91 +1,44 @@
import { countryCodeMapping } from "@/lib/geo";
import { GetNezhaData } from "@/lib/serverFetch";
import { geoEqualEarth, geoPath } from "d3-geo";

import { geoJsonString } from "../../../lib/geo-json-string";
import GlobalInfo from "./GlobalInfo";

interface GlobalProps {
countries?: string[];
}
import { InteractiveMap } from "./InteractiveMap";

export default async function ServerGlobal() {
const nezhaServerList = await GetNezhaData();

const countrytList: string[] = [];
const serverCounts: { [key: string]: number } = {};

nezhaServerList.result.forEach((server) => {
if (server.host.CountryCode) {
server.host.CountryCode = server.host.CountryCode.toUpperCase();
if (!countrytList.includes(server.host.CountryCode)) {
countrytList.push(server.host.CountryCode);
const countryCode = server.host.CountryCode.toUpperCase();
if (!countrytList.includes(countryCode)) {
countrytList.push(countryCode);
}
serverCounts[countryCode] = (serverCounts[countryCode] || 0) + 1;
}
});

return <Global countries={countrytList} />;
}

export async function Global({ countries = [] }: GlobalProps) {
const width = 900;
const height = 500;

const projection = geoEqualEarth()
.scale(180)
.translate([width / 2, height / 2])
.rotate([0, 0]); // 调整旋转以优化显示效果

const path = geoPath().projection(projection);

const geoJson = JSON.parse(geoJsonString);
const countries_alpha3 = countries
.map((code) => countryCodeMapping[code])
.filter((code) => code !== undefined);

const filteredFeatures = geoJson.features.filter(
(feature: any) => feature.properties.iso_a3 !== "",
);

return (
<section className="flex flex-col gap-4 mt-[3.2px]">
<GlobalInfo countries={countries} />
<GlobalInfo countries={countrytList} />
<div className="w-full overflow-x-auto">
<svg
<InteractiveMap
countries={countrytList}
serverCounts={serverCounts}
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
xmlns="http://www.w3.org/2000/svg"
className="w-full h-auto"
>
<defs>
<pattern
id="dots"
width="2"
height="2"
patternUnits="userSpaceOnUse"
>
<circle cx="1" cy="1" r="0.5" fill="currentColor" />
</pattern>
</defs>
<g>
{/* @ts-ignore */}
{filteredFeatures.map((feature, index) => {
const isHighlighted = countries_alpha3.includes(
feature.properties.iso_a3,
);
return (
<path
key={index}
d={path(feature) || ""}
className={
isHighlighted
? "fill-orange-500 stroke-orange-500 dark:stroke-amber-900 dark:fill-amber-900"
: " fill-neutral-200/50 dark:fill-neutral-800 stroke-neutral-300/40 dark:stroke-neutral-700 stroke-[0.5]"
}
/>
);
})}
</g>
</svg>
filteredFeatures={filteredFeatures}
/>
</div>
</section>
);
Expand Down
108 changes: 108 additions & 0 deletions app/(main)/ClientComponents/InteractiveMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"use client";

import { countryCodeMapping } from "@/lib/geo";
import { geoEqualEarth, geoPath } from "d3-geo";
import { useTranslations } from "next-intl";
import { useState } from "react";

interface InteractiveMapProps {
countries: string[];
serverCounts: { [key: string]: number };
width: number;
height: number;
filteredFeatures: any[];
}

export function InteractiveMap({
countries,
serverCounts,
width,
height,
filteredFeatures,
}: InteractiveMapProps) {
const t = useTranslations("Global");

const [tooltipData, setTooltipData] = useState<{
centroid: [number, number];
country: string;
count: number;
} | null>(null);

const countries_alpha3 = countries
.map((code) => countryCodeMapping[code])
.filter((code) => code !== undefined);

const projection = geoEqualEarth()
.scale(180)
.translate([width / 2, height / 2]);

const path = geoPath().projection(projection);

return (
<div className="relative w-full aspect-[2/1]">
<svg
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
xmlns="http://www.w3.org/2000/svg"
className="w-full h-auto"
>
<defs>
<pattern id="dots" width="2" height="2" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="0.5" fill="currentColor" />
</pattern>
</defs>
<g>
{filteredFeatures.map((feature, index) => {
const isHighlighted = countries_alpha3.includes(
feature.properties.iso_a3,
);
const countryCode = Object.entries(countryCodeMapping).find(
([_, alpha3]) => alpha3 === feature.properties.iso_a3,
)?.[0];
const serverCount = countryCode
? serverCounts[countryCode] || 0
: 0;

return (
<path
key={index}
d={path(feature) || ""}
className={
isHighlighted
? "fill-orange-500 hover:fill-orange-400 stroke-orange-500 dark:stroke-amber-900 dark:fill-amber-900 dark:hover:fill-amber-800 transition-all cursor-pointer"
: "fill-neutral-200/50 dark:fill-neutral-800 stroke-neutral-300/40 dark:stroke-neutral-700 stroke-[0.5]"
}
onMouseEnter={() => {
if (isHighlighted && path.centroid(feature)) {
setTooltipData({
centroid: path.centroid(feature),
country: feature.properties.name,
count: serverCount,
});
}
}}
onMouseLeave={() => setTooltipData(null)}
/>
);
})}
</g>
</svg>
{tooltipData && (
<div
className="absolute pointer-events-none bg-white dark:bg-neutral-800 px-2 py-1 rounded shadow-lg text-sm"
style={{
left: tooltipData.centroid[0],
top: tooltipData.centroid[1],
transform: "translate(-50%, -50%)",
}}
>
<p className="font-medium">{tooltipData.country}</p>
<p className="text-neutral-600 dark:text-neutral-400">
{tooltipData.count} {t("Servers")}
</p>
</div>
)}
</div>
);
}
3 changes: 2 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
"Global": {
"Loading": "Loading...",
"Distributions": "Servers are distributed in",
"Regions": "Regions"
"Regions": "Regions",
"Servers": "servers"
},
"NotFoundPage": {
"h1_490-590_404NotFound": "404 Not Found",
Expand Down
3 changes: 2 additions & 1 deletion messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
"Global": {
"Loading": "Loading...",
"Distributions": "サーバーは",
"Regions": "つの地域に分散されています"
"Regions": "つの地域に分散されています",
"Servers": "サーバー"
},
"NotFoundPage": {
"h1_490-590_404NotFound": "404 見つかりませんでした",
Expand Down
3 changes: 2 additions & 1 deletion messages/zh-t.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
"Global": {
"Loading": "載入中...",
"Distributions": "伺服器分佈在",
"Regions": "個地區"
"Regions": "個地區",
"Servers": "個伺服器"
},
"NotFoundPage": {
"h1_490-590_404NotFound": "404 未找到",
Expand Down
3 changes: 2 additions & 1 deletion messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
"Global": {
"Loading": "加载中...",
"Distributions": "服务器分布在",
"Regions": "个地区"
"Regions": "个地区",
"Servers": "个服务器"
},
"NotFoundPage": {
"h1_490-590_404NotFound": "404 未找到",
Expand Down

0 comments on commit f043f5e

Please sign in to comment.