> ## Documentation Index
> Fetch the complete documentation index at: https://hyperframes-fix-nested-composition-media-inpoint.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# US Bubble Map

> Animated US bubble map with proportional city markers, value callouts, and connection lines — composable with us-map

export const InstallCommand = ({command, item}) => {
  const [copied, setCopied] = React.useState(false);
  const [tuned, setTuned] = React.useState("");
  React.useEffect(() => {
    if (!item) return;
    const read = () => {
      try {
        const raw = new URLSearchParams(window.location.search).get(`vars-${item}`);
        if (!raw) return setTuned("");
        const parsed = JSON.parse(raw);
        if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return setTuned("");
        if (Object.keys(parsed).length === 0) return setTuned("");
        setTuned(` --vars '${JSON.stringify(parsed)}'`);
      } catch {
        setTuned("");
      }
    };
    read();
    window.addEventListener("hf-vars-changed", read);
    window.addEventListener("popstate", read);
    return () => {
      window.removeEventListener("hf-vars-changed", read);
      window.removeEventListener("popstate", read);
    };
  }, [item]);
  const fullCommand = `${command}${tuned}`;
  const copy = async () => {
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(fullCommand);
      } else {
        const previous = document.activeElement;
        const scratch = document.createElement("textarea");
        scratch.value = fullCommand;
        scratch.setAttribute("readonly", "");
        scratch.style.position = "fixed";
        scratch.style.opacity = "0";
        document.body.appendChild(scratch);
        scratch.select();
        document.execCommand("copy");
        document.body.removeChild(scratch);
        previous?.focus?.();
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {}
  };
  return <div className="hf-install-command not-prose my-4 flex items-stretch overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900">
      <code className="flex-1 overflow-x-auto whitespace-nowrap border-r border-zinc-200 px-4 py-3 font-mono text-sm text-zinc-800 dark:border-zinc-800 dark:text-zinc-100">
        {fullCommand}
      </code>
      <button type="button" onClick={copy} data-copied={copied ? "true" : "false"} aria-label={`Copy ${command} to the clipboard`} className="hf-install-copy">
        <svg className="hf-install-copy-clipboard" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" />
          <path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" />
        </svg>
        <svg className="hf-install-copy-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M2.75 9.5L6.5 13.25L15.25 4.5" />
        </svg>
      </button>
      <span className="hf-install-copy-status" role="status" aria-live="polite">
        {copied ? "Copied" : ""}
      </span>
    </div>;
};

<iframe className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800" title="us-map-bubble preview" loading="lazy" srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@0.7/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/us-map-bubble.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/us-map-bubble.png");document.body.appendChild(p)});<\/script></body></html>`} />

## Install

<InstallCommand command="npx hyperframes add us-map-bubble" item="us-map-bubble" />

That writes one file: `compositions/us-map-bubble.html`.

## Source

<Accordion title={`us-map-bubble.html`}>
  ```html theme={null}
  <!doctype html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=1920, height=1080" />
      <title>US Bubble Map</title>
      <link rel="preconnect" href="https://fonts.googleapis.com" />
      <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
      <link
        href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=block"
        rel="stylesheet"
      />
      <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/topojson-client@3.1.0/dist/topojson-client.min.js"></script>
      <style>
        * {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
        }
        html,
        body {
          margin: 0;
          width: 1920px;
          height: 1080px;
          overflow: hidden;
          background-color: #0f172a;
        }
      </style>
    </head>
    <body>
      <div
        id="us-map-bubble"
        data-composition-id="us-map-bubble"
        data-width="1920"
        data-height="1080"
        data-start="0"
        data-duration="12"
      >
        <div class="map-container">
          <div class="header">
            <h1 class="headline">Top 20 US Cities by Population</h1>
            <p class="subtitle">Proportional bubble markers by metropolitan population</p>
          </div>

          <svg class="map-svg" viewBox="0 0 1400 820" preserveAspectRatio="xMidYMid meet">
            <g class="states-group"></g>
            <g class="connections-group"></g>
            <g class="bubbles-group"></g>
            <g class="callouts-group"></g>
          </svg>

          <div class="source">Source: U.S. Census Bureau, 2024</div>
        </div>

        <style>
          #us-map-bubble {
            width: 1920px;
            height: 1080px;
            background: linear-gradient(145deg, #0f172a 0%, #1e293b 100%);
            font-family: "Inter", sans-serif;
            color: #e2e8f0;
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
          }

          #us-map-bubble .map-container {
            width: 1700px;
            height: 950px;
            display: flex;
            flex-direction: column;
            align-items: center;
            position: relative;
          }

          #us-map-bubble .header {
            text-align: center;
            margin-bottom: 20px;
          }

          #us-map-bubble .headline {
            font-size: 52px;
            font-weight: 700;
            letter-spacing: -0.02em;
            color: #f8fafc;
            clip-path: inset(0 100% 0 0);
          }

          #us-map-bubble .subtitle {
            font-size: 20px;
            font-weight: 300;
            color: #94a3b8;
            margin-top: 8px;
            opacity: 0;
          }

          #us-map-bubble .map-svg {
            width: 1500px;
            height: 750px;
            overflow: visible;
          }

          #us-map-bubble .state-path {
            fill: #1e293b;
            stroke: #334155;
            stroke-width: 0.5;
            opacity: 0;
          }

          #us-map-bubble .city-bubble {
            fill: rgba(59, 130, 246, 0.6);
            stroke: #ffffff;
            stroke-width: 1;
            transform-origin: center center;
            transform: scale(0);
          }

          #us-map-bubble .city-pulse {
            fill: none;
            stroke: rgba(59, 130, 246, 0.4);
            stroke-width: 2;
            opacity: 0;
          }

          #us-map-bubble .callout-group {
            opacity: 0;
          }

          #us-map-bubble .callout-line {
            stroke: rgba(148, 163, 184, 0.5);
            stroke-width: 1;
            fill: none;
          }

          #us-map-bubble .callout-name {
            font-size: 13px;
            font-weight: 600;
            fill: #f8fafc;
          }

          #us-map-bubble .callout-value {
            font-size: 11px;
            font-weight: 400;
            fill: #94a3b8;
          }

          #us-map-bubble .connection-line {
            stroke: rgba(59, 130, 246, 0.3);
            stroke-width: 1;
            fill: none;
            stroke-dasharray: 6 4;
          }

          #us-map-bubble .source {
            position: absolute;
            bottom: 0;
            right: 40px;
            font-size: 12px;
            color: #475569;
            opacity: 0;
          }
        </style>

        <script>
          (function () {
            var US_TOPO_URL = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json";

            var cities = [
              { name: "New York", lat: 40.7128, lon: -74.006, pop: 8300000 },
              { name: "Los Angeles", lat: 34.0522, lon: -118.2437, pop: 3900000 },
              { name: "Chicago", lat: 41.8781, lon: -87.6298, pop: 2700000 },
              { name: "Houston", lat: 29.7604, lon: -95.3698, pop: 2300000 },
              { name: "Phoenix", lat: 33.4484, lon: -112.074, pop: 1600000 },
              { name: "Philadelphia", lat: 39.9526, lon: -75.1652, pop: 1600000 },
              { name: "San Antonio", lat: 29.4241, lon: -98.4936, pop: 1400000 },
              { name: "San Diego", lat: 32.7157, lon: -117.1611, pop: 1400000 },
              { name: "Dallas", lat: 32.7767, lon: -96.797, pop: 1300000 },
              { name: "Jacksonville", lat: 30.3322, lon: -81.6557, pop: 950000 },
              { name: "Austin", lat: 30.2672, lon: -97.7431, pop: 960000 },
              { name: "San Jose", lat: 37.3382, lon: -121.8863, pop: 1000000 },
              { name: "Fort Worth", lat: 32.7555, lon: -97.3308, pop: 930000 },
              { name: "Columbus", lat: 39.9612, lon: -82.9988, pop: 910000 },
              { name: "Charlotte", lat: 35.2271, lon: -80.8431, pop: 870000 },
              { name: "Indianapolis", lat: 39.7684, lon: -86.1581, pop: 880000 },
              { name: "San Francisco", lat: 37.7749, lon: -122.4194, pop: 870000 },
              { name: "Seattle", lat: 47.6062, lon: -122.3321, pop: 740000 },
              { name: "Denver", lat: 39.7392, lon: -104.9903, pop: 710000 },
              { name: "Nashville", lat: 36.1627, lon: -86.7816, pop: 690000 },
            ];

            var radiusScale = d3.scaleSqrt().domain([500000, 8500000]).range([6, 45]);

            var svg = document.querySelector("#us-map-bubble .map-svg");
            var statesGroup = svg.querySelector(".states-group");
            var bubblesGroup = svg.querySelector(".bubbles-group");
            var calloutsGroup = svg.querySelector(".callouts-group");
            var connectionsGroup = svg.querySelector(".connections-group");
            var tl = gsap.timeline({ paused: true });

            function formatPop(n) {
              if (n >= 1000000) {
                return (n / 1000000).toFixed(1) + "M";
              }
              return (n / 1000).toFixed(0) + "K";
            }

            fetch(US_TOPO_URL)
              .then(function (r) {
                return r.json();
              })
              .then(function (us) {
                var states = topojson.feature(us, us.objects.states).features;
                var projection = d3.geoAlbersUsa().fitSize([1380, 800], {
                  type: "FeatureCollection",
                  features: states,
                });
                var path = d3.geoPath().projection(projection);

                states.forEach(function (feature) {
                  var d = path(feature);
                  if (!d) return;

                  var el = document.createElementNS("http://www.w3.org/2000/svg", "path");
                  el.setAttribute("d", d);
                  el.setAttribute("class", "state-path");
                  statesGroup.appendChild(el);
                });

                var projectedCities = [];
                cities.forEach(function (city) {
                  var coords = projection([city.lon, city.lat]);
                  if (!coords) return;
                  projectedCities.push({
                    name: city.name,
                    pop: city.pop,
                    x: coords[0],
                    y: coords[1],
                    r: radiusScale(city.pop),
                  });
                });

                projectedCities.sort(function (a, b) {
                  return b.pop - a.pop;
                });

                var nycCoords = null;
                projectedCities.forEach(function (city) {
                  if (city.name === "New York") {
                    nycCoords = { x: city.x, y: city.y };
                  }
                });

                var connectionTargets = ["Los Angeles", "Chicago", "Houston", "Phoenix"];
                connectionTargets.forEach(function (targetName) {
                  if (!nycCoords) return;
                  var target = null;
                  projectedCities.forEach(function (c) {
                    if (c.name === targetName) target = c;
                  });
                  if (!target) return;

                  var midX = (nycCoords.x + target.x) / 2;
                  var midY = Math.min(nycCoords.y, target.y) - 60;

                  var pathD =
                    "M " +
                    nycCoords.x +
                    " " +
                    nycCoords.y +
                    " Q " +
                    midX +
                    " " +
                    midY +
                    " " +
                    target.x +
                    " " +
                    target.y;

                  var line = document.createElementNS("http://www.w3.org/2000/svg", "path");
                  line.setAttribute("d", pathD);
                  line.setAttribute("class", "connection-line");
                  connectionsGroup.appendChild(line);

                  var length = line.getTotalLength();
                  line.style.strokeDasharray = length;
                  line.style.strokeDashoffset = length;
                });

                projectedCities.forEach(function (city) {
                  var circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
                  circle.setAttribute("cx", city.x);
                  circle.setAttribute("cy", city.y);
                  circle.setAttribute("r", city.r);
                  circle.setAttribute("class", "city-bubble");
                  circle.setAttribute("data-city", city.name);
                  bubblesGroup.appendChild(circle);
                });

                var pulseCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
                if (nycCoords) {
                  pulseCircle.setAttribute("cx", nycCoords.x);
                  pulseCircle.setAttribute("cy", nycCoords.y);
                  pulseCircle.setAttribute("r", radiusScale(8300000));
                  pulseCircle.setAttribute("class", "city-pulse");
                  bubblesGroup.appendChild(pulseCircle);
                }

                var top5 = projectedCities.slice(0, 5);
                top5.forEach(function (city, i) {
                  var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
                  g.setAttribute("class", "callout-group");

                  var offsetX = city.r + 12;
                  var offsetY = -8;

                  if (city.name === "Los Angeles" || city.name === "San Francisco") {
                    offsetX = -(city.r + 80);
                  }

                  var calloutLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
                  calloutLine.setAttribute("x1", city.x);
                  calloutLine.setAttribute("y1", city.y);
                  calloutLine.setAttribute("x2", city.x + offsetX);
                  calloutLine.setAttribute("y2", city.y + offsetY);
                  calloutLine.setAttribute("class", "callout-line");
                  g.appendChild(calloutLine);

                  var nameText = document.createElementNS("http://www.w3.org/2000/svg", "text");
                  nameText.setAttribute("x", city.x + offsetX + (offsetX > 0 ? 6 : -6));
                  nameText.setAttribute("y", city.y + offsetY - 2);
                  nameText.setAttribute("class", "callout-name");
                  if (offsetX < 0) {
                    nameText.setAttribute("text-anchor", "end");
                  }
                  nameText.textContent = city.name;
                  g.appendChild(nameText);

                  var valueText = document.createElementNS("http://www.w3.org/2000/svg", "text");
                  valueText.setAttribute("x", city.x + offsetX + (offsetX > 0 ? 6 : -6));
                  valueText.setAttribute("y", city.y + offsetY + 14);
                  valueText.setAttribute("class", "callout-value");
                  if (offsetX < 0) {
                    valueText.setAttribute("text-anchor", "end");
                  }
                  valueText.textContent = formatPop(city.pop) + " residents";
                  g.appendChild(valueText);

                  calloutsGroup.appendChild(g);
                });

                buildTimeline(projectedCities, pulseCircle);
              });

            function buildTimeline(projectedCities, pulseCircle) {
              var statePaths = statesGroup.querySelectorAll(".state-path");
              var bubbles = bubblesGroup.querySelectorAll(".city-bubble");
              var callouts = calloutsGroup.querySelectorAll(".callout-group");
              var connections = connectionsGroup.querySelectorAll(".connection-line");

              tl.to(
                "#us-map-bubble .headline",
                {
                  clipPath: "inset(0 0% 0 0)",
                  duration: 1.0,
                  ease: "power2.inOut",
                },
                0,
              );

              tl.to(
                "#us-map-bubble .subtitle",
                {
                  opacity: 1,
                  duration: 0.6,
                  ease: "power2.out",
                },
                0.4,
              );

              tl.to(
                statePaths,
                {
                  opacity: 0.8,
                  duration: 0.6,
                  ease: "power2.out",
                },
                0.8,
              );

              tl.to(
                bubbles,
                {
                  scale: 1,
                  duration: 0.5,
                  stagger: {
                    each: 0.08,
                    from: "start",
                  },
                  ease: "back.out(1.7)",
                },
                1.5,
              );

              tl.set(callouts, { x: 20 }, 3.9);

              tl.to(
                callouts,
                {
                  opacity: 1,
                  x: 0,
                  duration: 0.6,
                  stagger: 0.3,
                  ease: "power2.out",
                },
                4.0,
              );

              connections.forEach(function (line) {
                tl.to(
                  line,
                  {
                    strokeDashoffset: 0,
                    duration: 1.2,
                    ease: "power2.inOut",
                  },
                  6.0,
                );
              });

              if (pulseCircle) {
                tl.to(
                  pulseCircle,
                  {
                    opacity: 1,
                    duration: 0.01,
                  },
                  8.0,
                );

                tl.fromTo(
                  pulseCircle,
                  {
                    attr: { r: radiusScale(8300000) },
                    opacity: 0.6,
                  },
                  {
                    attr: { r: radiusScale(8300000) + 30 },
                    opacity: 0,
                    duration: 1.5,
                    repeat: 2,
                    ease: "power2.out",
                  },
                  8.0,
                );
              }

              tl.to(
                "#us-map-bubble .source",
                {
                  opacity: 1,
                  duration: 0.5,
                  ease: "power2.out",
                },
                5.5,
              );

              window.__timelines = window.__timelines || {};
              window.__timelines["us-map-bubble"] = tl;
            }
          })();
        </script>
      </div>
    </body>
  </html>
  ```
</Accordion>

## Usage

After installing, add the block to your host composition:

```html theme={null}
<div data-composition-id="us-map-bubble" data-composition-src="compositions/us-map-bubble.html" data-start="0" data-duration="12" data-track-index="1" data-width="1920" data-height="1080"></div>
```

Tagged `data` `map` `geography` `usa` `bubble` `cities`.

## Related topics

* [Browse the complete Catalog](/catalog)
* [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
* [Build a richer composition](/go-further)
