title: 旋转地球
description: A 3D interactive globe visualization with customizable arcs, points, and animation options inspired from Github.
---

```vue
<template>
  <ClientOnly>
    <div class="flex w-full flex-col items-center justify-center">
      <GithubGlobe
        :globe-config="globeConfig"
        :data="sampleArcs"
        class="h-[32rem]"
      />
    </div>
  </ClientOnly>
</template>

<script setup lang="ts">
const globeConfig = {
  pointSize: 1,
  globeColor: "#0b43bd",
  showAtmosphere: true,
  atmosphereColor: "#FFFFFF",
  atmosphereAltitude: 0.1,
  emissive: "#062056",
  emissiveIntensity: 0.1,
  shininess: 0.9,
  polygonColor: "rgba(255,255,255,1)",
  ambientLight: "#38bdf8",
  directionalLeftLight: "#ffffff",
  directionalTopLight: "#ffffff",
  pointLight: "#ffffff",
  arcTime: 1000,
  arcLength: 1,
  rings: 1,
  maxRings: 10,
  initialPosition: { lat: 22.3193, lng: 114.1694 },
  autoRotate: true,
  autoRotateSpeed: 0.5,
};

const colors = [
  "#eae547",
  "#9347ea",
  "#d4ea47",
  "#ddea47",
  "#47ea70",
  "#eab447",
  "#eaa647",
  "#c747ea",
  "#52ea47",
  "#4754ea",
];
const sampleArcs = [
  {
    order: 1,
    startLat: -19.885592,
    startLng: -43.951191,
    endLat: -22.9068,
    endLng: -43.1729,
    arcAlt: 0.1,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 1,
    startLat: 28.6139,
    startLng: 77.209,
    endLat: 3.139,
    endLng: 101.6869,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 1,
    startLat: -19.885592,
    startLng: -43.951191,
    endLat: -1.303396,
    endLng: 36.852443,
    arcAlt: 0.5,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 2,
    startLat: 1.3521,
    startLng: 103.8198,
    endLat: 35.6762,
    endLng: 139.6503,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 2,
    startLat: 51.5072,
    startLng: -0.1276,
    endLat: 3.139,
    endLng: 101.6869,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 2,
    startLat: -15.785493,
    startLng: -47.909029,
    endLat: 36.162809,
    endLng: -115.119411,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 3,
    startLat: -33.8688,
    startLng: 151.2093,
    endLat: 22.3193,
    endLng: 114.1694,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 3,
    startLat: 21.3099,
    startLng: -157.8581,
    endLat: 40.7128,
    endLng: -74.006,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 3,
    startLat: -6.2088,
    startLng: 106.8456,
    endLat: 51.5072,
    endLng: -0.1276,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 4,
    startLat: 11.986597,
    startLng: 8.571831,
    endLat: -15.595412,
    endLng: -56.05918,
    arcAlt: 0.5,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 4,
    startLat: -34.6037,
    startLng: -58.3816,
    endLat: 22.3193,
    endLng: 114.1694,
    arcAlt: 0.7,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 4,
    startLat: 51.5072,
    startLng: -0.1276,
    endLat: 48.8566,
    endLng: -2.3522,
    arcAlt: 0.1,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 5,
    startLat: 14.5995,
    startLng: 120.9842,
    endLat: 51.5072,
    endLng: -0.1276,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 5,
    startLat: 1.3521,
    startLng: 103.8198,
    endLat: -33.8688,
    endLng: 151.2093,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 5,
    startLat: 34.0522,
    startLng: -118.2437,
    endLat: 48.8566,
    endLng: -2.3522,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 6,
    startLat: -15.432563,
    startLng: 28.315853,
    endLat: 1.094136,
    endLng: -63.34546,
    arcAlt: 0.7,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 6,
    startLat: 37.5665,
    startLng: 126.978,
    endLat: 35.6762,
    endLng: 139.6503,
    arcAlt: 0.1,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 6,
    startLat: 22.3193,
    startLng: 114.1694,
    endLat: 51.5072,
    endLng: -0.1276,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 7,
    startLat: -19.885592,
    startLng: -43.951191,
    endLat: -15.595412,
    endLng: -56.05918,
    arcAlt: 0.1,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 7,
    startLat: 48.8566,
    startLng: -2.3522,
    endLat: 52.52,
    endLng: 13.405,
    arcAlt: 0.1,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 7,
    startLat: 52.52,
    startLng: 13.405,
    endLat: 34.0522,
    endLng: -118.2437,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 8,
    startLat: -8.833221,
    startLng: 13.264837,
    endLat: -33.936138,
    endLng: 18.436529,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 8,
    startLat: 49.2827,
    startLng: -123.1207,
    endLat: 52.3676,
    endLng: 4.9041,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 8,
    startLat: 1.3521,
    startLng: 103.8198,
    endLat: 40.7128,
    endLng: -74.006,
    arcAlt: 0.5,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 9,
    startLat: 51.5072,
    startLng: -0.1276,
    endLat: 34.0522,
    endLng: -118.2437,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 9,
    startLat: 22.3193,
    startLng: 114.1694,
    endLat: -22.9068,
    endLng: -43.1729,
    arcAlt: 0.7,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 9,
    startLat: 1.3521,
    startLng: 103.8198,
    endLat: -34.6037,
    endLng: -58.3816,
    arcAlt: 0.5,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 10,
    startLat: -22.9068,
    startLng: -43.1729,
    endLat: 28.6139,
    endLng: 77.209,
    arcAlt: 0.7,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 10,
    startLat: 34.0522,
    startLng: -118.2437,
    endLat: 31.2304,
    endLng: 121.4737,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 10,
    startLat: -6.2088,
    startLng: 106.8456,
    endLat: 52.3676,
    endLng: 4.9041,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 11,
    startLat: 41.9028,
    startLng: 12.4964,
    endLat: 34.0522,
    endLng: -118.2437,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 11,
    startLat: -6.2088,
    startLng: 106.8456,
    endLat: 31.2304,
    endLng: 121.4737,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 11,
    startLat: 22.3193,
    startLng: 114.1694,
    endLat: 1.3521,
    endLng: 103.8198,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 12,
    startLat: 34.0522,
    startLng: -118.2437,
    endLat: 37.7749,
    endLng: -122.4194,
    arcAlt: 0.1,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 12,
    startLat: 35.6762,
    startLng: 139.6503,
    endLat: 22.3193,
    endLng: 114.1694,
    arcAlt: 0.2,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 12,
    startLat: 22.3193,
    startLng: 114.1694,
    endLat: 34.0522,
    endLng: -118.2437,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 13,
    startLat: 52.52,
    startLng: 13.405,
    endLat: 22.3193,
    endLng: 114.1694,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 13,
    startLat: 11.986597,
    startLng: 8.571831,
    endLat: 35.6762,
    endLng: 139.6503,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 13,
    startLat: -22.9068,
    startLng: -43.1729,
    endLat: -34.6037,
    endLng: -58.3816,
    arcAlt: 0.1,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
  {
    order: 14,
    startLat: -33.936138,
    startLng: 18.436529,
    endLat: 21.395643,
    endLng: 39.883798,
    arcAlt: 0.3,
    color: colors[Math.floor(Math.random() * (colors.length - 1))],
  },
];
</script>

```

::alert{type="info"}
**注意:** This component uses Three.js & requires `three`, `three-globe` & `postprocessing` npm package as a dependency.
::

::alert{type="warning"}
**For Nuxt users**:
Use `<ClientOnly>` tag to wrap this component to avoid `window is not defined` error.
::

## 通过 CLI 安装

```vue
<InstallationCli component-id="github-globe" />
```

## 手动安装

::steps{level=4}

#### Install the dependencies

::code-group

```bash [npm]
npm install three postprocessing three-globe
npm install -D @types/three
```

```bash [pnpm]
pnpm install three postprocessing three-globe
pnpm install -D @types/three
```

```bash [bun]
bun add three postprocessing three-globe
bun add -d @types/three
```

```bash [yarn]
yarn add three postprocessing three-globe
yarn add --dev @types/three
```

::

复制并粘贴以下代码：

```vue
<template>
  <canvas
    ref="githubGlobeRef"
    :class="cn('w-96 h-96', props.class)"
  ></canvas>
</template>

<script lang="ts" setup>
// Download globe json file from https://geojson-maps.kyd.au/ and save in the same folder
/* eslint-disable @typescript-eslint/no-explicit-any */

import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import ThreeGlobe from "three-globe";
import {
  AmbientLight,
  Color,
  DirectionalLight,
  PerspectiveCamera,
  PointLight,
  Scene,
  WebGLRenderer,
} from "three";
import contries from "./globe.json";
import { cn } from "@/lib/utils";
import { ref, onMounted, onBeforeUnmount, watch } from "vue";

type Position = {
  order: number;
  startLat: number;
  startLng: number;
  endLat: number;
  endLng: number;
  arcAlt: number;
  color: string;
};

interface GlobeData {
  size: number | undefined;
  order: number;
  color: (t: number) => string;
  lat: number;
  lng: number;
}

interface GlobeConfig {
  pointSize?: number;
  globeColor?: string;
  showAtmosphere?: boolean;
  atmosphereColor?: string;
  atmosphereAltitude?: number;
  emissive?: string;
  emissiveIntensity?: number;
  shininess?: number;
  polygonColor?: string;
  ambientLight?: string;
  directionalLeftLight?: string;
  directionalTopLight?: string;
  pointLight?: string;
  arcTime?: number;
  arcLength?: number;
  rings?: number;
  maxRings?: number;
  initialPosition?: {
    lat: number;
    lng: number;
  };
  autoRotate?: boolean;
  autoRotateSpeed?: number;
}

interface Props {
  globeConfig?: GlobeConfig;
  data?: Position[];
  class?: string;
}

const props = withDefaults(defineProps<Props>(), {
  globeConfig: () => {
    return {};
  },
  data: () => [],
});

const defaultGlobeConfig: GlobeConfig = {
  pointSize: 1,
  atmosphereColor: "#ffffff",
  showAtmosphere: true,
  atmosphereAltitude: 0.1,
  polygonColor: "rgba(255,255,255,0.7)",
  globeColor: "#1d072e",
  emissive: "#000000",
  emissiveIntensity: 0.1,
  shininess: 0.9,
  arcTime: 2000,
  arcLength: 0.9,
  rings: 1,
  maxRings: 3,
  ...props.globeConfig,
};

const githubGlobeRef = ref<HTMLCanvasElement>();
const globeData = ref<GlobeData[]>();

let numberOfRings: number[] = [];

let renderer: WebGLRenderer;
let scene: Scene;
let camera: PerspectiveCamera;
let controls: OrbitControls;

let globe: ThreeGlobe;

onMounted(() => {
  setupScene();
  initGlobe();
  startAnimation();
  animate();

  onWindowResize();

  window.addEventListener("resize", onWindowResize, false);

  watch(globeData, () => {
    if (!globe || !globeData.value) return;

    numberOfRings = genRandomNumbers(0, props.data.length, Math.floor((props.data.length * 4) / 5));

    globe.ringsData(globeData.value.filter((d, i) => numberOfRings.includes(i)));
  });
});

function setupScene() {
  if (!githubGlobeRef.value) {
    throw new Error("Canvas not initialized");
  }

  const width = githubGlobeRef.value.clientWidth;
  const height = githubGlobeRef.value.clientHeight;

  renderer = new WebGLRenderer({ canvas: githubGlobeRef.value, antialias: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setSize(width, height);
  renderer.autoClear = false;

  scene = new Scene();

  camera = new PerspectiveCamera();
  camera.aspect = width / height;
  camera.position.setX(0);
  camera.position.setY(0);
  camera.position.setZ(400);

  const ambientLight = new AmbientLight(defaultGlobeConfig.ambientLight || "#ffffff", 0.6);
  scene.add(ambientLight);

  const dLight1 = new DirectionalLight(defaultGlobeConfig.directionalLeftLight || "#ffffff", 1);
  dLight1.position.set(-400, 100, 400);
  camera.add(dLight1);

  const dLight2 = new DirectionalLight(defaultGlobeConfig.directionalTopLight || "#ffffff", 1);
  dLight2.position.set(-200, 500, 200);
  camera.add(dLight2);

  const pLight = new PointLight(defaultGlobeConfig.pointLight || "#ffffff", 0.8);
  pLight.position.set(-200, 500, 200);
  camera.add(pLight);

  camera.updateProjectionMatrix();
  scene.add(camera);

  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableZoom = false;
  controls.enablePan = false;
  controls.enableDamping = true;
  controls.dampingFactor = 0.01;
  controls.minDistance = 200;
  controls.maxDistance = 500;
  controls.rotateSpeed = defaultGlobeConfig.autoRotateSpeed || 0.8;
  controls.zoomSpeed = 1;
  controls.autoRotate = defaultGlobeConfig.autoRotate || false;

  controls.minPolarAngle = Math.PI / 3.5;
  controls.maxPolarAngle = Math.PI - Math.PI / 3;
}

function initGlobe() {
  buildData();

  globe = new ThreeGlobe({
    waitForGlobeReady: true,
    animateIn: true,
  })
    .hexPolygonsData(contries.features)
    .hexPolygonResolution(3)
    .hexPolygonMargin(0.7)
    .showAtmosphere(defaultGlobeConfig.showAtmosphere!)
    .atmosphereColor(defaultGlobeConfig.atmosphereColor!)
    .atmosphereAltitude(defaultGlobeConfig.atmosphereAltitude!)
    .hexPolygonColor((e) => defaultGlobeConfig.polygonColor!);

  globe.rotateY(-Math.PI * (5 / 9));
  globe.rotateZ(-Math.PI / 6);

  const globeMaterial = globe.globeMaterial() as unknown as {
    color: Color;
    emissive: Color;
    emissiveIntensity: number;
    shininess: number;
  };

  globeMaterial.color = new Color(defaultGlobeConfig.globeColor!);
  globeMaterial.emissive = new Color(defaultGlobeConfig.emissive!);
  globeMaterial.emissiveIntensity = defaultGlobeConfig.emissiveIntensity || 0.1;
  globeMaterial.shininess = defaultGlobeConfig.shininess || 0.9;

  scene.add(globe);
}

function onWindowResize() {
  if (!githubGlobeRef.value) {
    return;
  }

  const width = githubGlobeRef.value.clientWidth;
  const height = githubGlobeRef.value.clientHeight;

  camera.aspect = width / height;
  camera.updateProjectionMatrix();

  renderer.setSize(width, height);
}
function startAnimation() {
  if (!globe || !globeData.value!) return;
  globe
    .arcsData(props.data)
    .arcStartLat((d: any) => d.startLat * 1)
    .arcStartLng((d: any) => d.startLng * 1)
    .arcEndLat((d: any) => d.endLat * 1)
    .arcEndLng((d: any) => d.endLng * 1)
    .arcColor((e: any) => e.color)
    .arcAltitude((e: any) => e.arcAlt * 1)
    .arcStroke((e: any) => [0.32, 0.28, 0.3][Math.round(Math.random() * 4)])
    .arcDashLength(defaultGlobeConfig.arcLength!)
    .arcDashInitialGap((e: any) => e.order * 1)
    .arcDashGap(15)
    .arcDashAnimateTime(defaultGlobeConfig.arcTime!)

    .pointsData(props.data)
    .pointColor((e: any) => e.color)
    .pointsMerge(true)
    .pointAltitude(0.0)
    .pointRadius(2)

    .ringsData([])
    .ringColor((e: any) => (t: any) => e.color(t))
    .ringMaxRadius(defaultGlobeConfig.maxRings!)
    .ringPropagationSpeed(3)
    .ringRepeatPeriod(
      (defaultGlobeConfig.arcTime! * defaultGlobeConfig.arcLength!) / defaultGlobeConfig.rings!,
    );
}

function animate() {
  globe.rotation.y += 0.01; // Rotate globe

  renderer.render(scene, camera);

  requestAnimationFrame(animate);
}

function buildData() {
  const arcs = props.data;
  let points = [];
  for (let i = 0; i < arcs.length; i++) {
    const arc = arcs[i];
    const rgb = hexToRgb(arc.color) as { r: number; g: number; b: number };
    points.push({
      size: props.globeConfig.pointSize,
      order: arc.order,
      color: (t: number) => `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${1 - t})`,
      lat: arc.startLat,
      lng: arc.startLng,
    });
    points.push({
      size: props.globeConfig.pointSize,
      order: arc.order,
      color: (t: number) => `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${1 - t})`,
      lat: arc.endLat,
      lng: arc.endLng,
    });
  }

  // remove duplicates for same lat and lng
  const filteredPoints = points.filter(
    (v, i, a) =>
      a.findIndex((v2) =>
        ["lat", "lng"].every((k) => v2[k as "lat" | "lng"] === v[k as "lat" | "lng"]),
      ) === i,
  );

  globeData.value = filteredPoints;
}

function hexToRgb(color: string) {
  let hex = color.replace(/^#/, "");

  // If the hex code is 3 characters, expand it to 6 characters
  if (hex.length === 3) {
    hex = hex
      .split("")
      .map((char) => char + char)
      .join("");
  }

  // Parse the r, g, b values from the hex string
  const bigint = parseInt(hex, 16);
  const r = (bigint >> 16) & 255; // Extract the red component
  const g = (bigint >> 8) & 255; // Extract the green component
  const b = bigint & 255; // Extract the blue component

  // Return the RGB values as a string separated by spaces
  return {
    r,
    g,
    b,
  };
}

function genRandomNumbers(min: number, max: number, count: number) {
  const arr = [];
  while (arr.length < count) {
    const r = Math.floor(Math.random() * (max - min)) + min;
    if (arr.indexOf(r) === -1) arr.push(r);
  }

  return arr;
}
</script>

```

#### Download GeoJSON file

Download a GeoJSON file containing the globe's geographical data from [GeoJSON Maps](https://geojson-maps.kyd.au/) by customizing the continents and detail level. Save the downloaded file as `globe.json` in the same folder as your component.

::

## API

| Prop 名称     | 类型         | 默认值 | 描述                                                                                                |
| ------------- | ------------ | ------ | --------------------------------------------------------------------------------------------------- |
| `globeConfig` | `object`     | `{}`   | Configuration options for the globe, including colors, atmosphere, rotation speed, and lighting.    |
| `data`        | `Position[]` | `[]`   | Array of positions representing arcs and points on the globe, with latitude, longitude, color, etc. |
| `class`       | `string`     | `""`   | Additional CSS classes for custom styling.                                                          |

### `globeConfig` Properties

| Property             | 类型      | 默认值                  | 描述                                                     |
| -------------------- | --------- | ----------------------- | -------------------------------------------------------- |
| `pointSize`          | `number`  | `1`                     | Size of individual points on the globe.                  |
| `globeColor`         | `string`  | `"#1d072e"`             | Color of the globe surface.                              |
| `showAtmosphere`     | `boolean` | `true`                  | Whether to display atmosphere around the globe.          |
| `atmosphereColor`    | `string`  | `"#ffffff"`             | Color of the atmosphere around the globe.                |
| `atmosphereAltitude` | `number`  | `0.1`                   | Altitude of the atmosphere layer.                        |
| `emissive`           | `string`  | `"#000000"`             | Emissive color for the globe material.                   |
| `emissiveIntensity`  | `number`  | `0.1`                   | Intensity of the emissive color.                         |
| `shininess`          | `number`  | `0.9`                   | Shininess of the globe material.                         |
| `polygonColor`       | `string`  | `rgba(255,255,255,0.7)` | Color of polygon boundaries on the globe.                |
| `arcTime`            | `number`  | `2000`                  | Duration for arcs animation.                             |
| `arcLength`          | `number`  | `0.9`                   | Length of arcs on the globe.                             |
| `rings`              | `number`  | `1`                     | Number of rings to display per point.                    |
| `maxRings`           | `number`  | `3`                     | Maximum rings around each point.                         |
| `initialPosition`    | `object`  | `{ lat: 0, lng: 0 }`    | Initial latitude and longitude for the globe's position. |
| `autoRotate`         | `boolean` | `false`                 | Automatically rotate the globe.                          |
| `autoRotateSpeed`    | `number`  | `0.8`                   | Speed of auto-rotation when enabled.                     |

### `data` Structure (Position)

| Field      | 类型     | 描述                                            |
| ---------- | -------- | ----------------------------------------------- |
| `order`    | `number` | Order of the point or arc for sequencing.       |
| `startLat` | `number` | Starting latitude for an arc.                   |
| `startLng` | `number` | Starting longitude for an arc.                  |
| `endLat`   | `number` | Ending latitude for an arc.                     |
| `endLng`   | `number` | Ending longitude for an arc.                    |
| `arcAlt`   | `number` | Altitude of the arc (determines arc height).    |
| `color`    | `string` | Color of the arc or point in hex or RGB format. |

## 功能特性

- **3D Interactive Globe**: A Three.js-based globe with interactive controls and orbit support.
- **Customizable Globe & Atmosphere**: Allows configuration of globe color, atmosphere visibility, and colors.
- **Arcs & Points Visualization**: Display arcs and points on the globe, simulating connections and locations.
- **Auto-Rotate and Zoom**: Supports auto-rotation, zoom, and customizable controls for a dynamic experience.
- **Responsive Design**: Adapts to container size and maintains performance with WebGL rendering.

## 感谢

- Built with Three.js and Three Globe libraries, designed for global data visualizations and dynamic effects.
- Ported from [Aceternity UI](https://ui.aceternity.com/components/github-globe).