> ## 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.

# Minimal Line Graph

> One or two SVG series draw on with dots popping and value labels riding the draw front; dim baseline axis, x-labels, color-dot legend

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="mk-line-graph 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/mk-line-graph.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/mk-line-graph.png");document.body.appendChild(p)});<\/script></body></html>`} />

## Install

<InstallCommand command="npx hyperframes add mk-line-graph" item="mk-line-graph" />

That writes one file: `compositions/mk-line-graph.html`.

## Add it to your video

It runs for 7 seconds at 1920×1080. Paste this into your composition:

```html index.html theme={null}
<div
  data-composition-id="mk-line-graph"
  data-composition-src="compositions/mk-line-graph.html"
  data-start="0"
  data-duration="7"
  data-track-index="1"
  data-width="1920"
  data-height="1080"
></div>
```

Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.

## Source

<Accordion title={`mk-line-graph.html`}>
  ```html theme={null}
  <!doctype html>
  <html lang="en">
    <head>
      <meta charset="utf-8" />
      <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
      <style>
        *,
        *::before,
        *::after {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
        }
        body {
          background: transparent;
          overflow: hidden;
        }
        #mk-lg-root {
          /* ---- mk tokens: override in the host to re-skin the family ---- */
          --mk-font: "Inter", -apple-system, sans-serif;
          --mk-ink: #1d1d1f;
          --mk-ink-dim: #6e6e73;
          --mk-accent: #0071e3;
          --mk-paper: #ffffff;
          --mk-blob-2: #45d6c8;
          --mk-axis: rgba(29, 29, 31, 0.22);

          position: relative;
          width: 1920px;
          height: 1080px;
          overflow: hidden;
          background: transparent; /* overlays footage or mk-background */
          font-family: var(--mk-font);
        }
        #mk-lg-svg {
          position: absolute;
          inset: 0;
        }
        .mk-lg-line {
          fill: none;
          stroke-width: 5;
          stroke-linecap: round;
          stroke-linejoin: round;
        }
        .mk-lg-dot {
          stroke-width: 4;
          fill: var(--mk-paper);
        }
        .mk-lg-axis {
          stroke: var(--mk-axis);
          stroke-width: 2;
        }
        .mk-lg-value {
          position: absolute;
          font-weight: 600;
          font-size: 30px;
          letter-spacing: -0.01em;
          color: var(--mk-ink);
          font-variant-numeric: tabular-nums;
          /* Horizontal centring only. The label's vertical offset is baked into
             `top` instead of a CSS translate, because the entrance tween animates
             `y` and GSAP rewrites this transform, dropping any vertical shift. */
          transform: translate(-50%, 0);
          line-height: 38px;
          white-space: nowrap;
        }
        .mk-lg-xlabel {
          position: absolute;
          font-weight: 400;
          font-size: 26px;
          color: var(--mk-ink-dim);
          transform: translateX(-50%);
          white-space: nowrap;
        }
        .mk-lg-legend {
          position: absolute;
          display: flex;
          gap: 40px;
        }
        .mk-lg-legend-item {
          display: flex;
          align-items: center;
          gap: 12px;
          font-weight: 500;
          font-size: 30px;
          color: var(--mk-ink-dim);
        }
        .mk-lg-legend-dot {
          width: 14px;
          height: 14px;
          border-radius: 50%;
        }
      </style>
    </head>
    <body>
      <div
        id="mk-lg-root"
        data-composition-id="mk-line-graph"
        data-start="0"
        data-duration="7"
        data-width="1920"
        data-height="1080"
      >
        <svg id="mk-lg-svg" viewBox="0 0 1920 1080"></svg>
        <div id="mk-lg-labels"></div>
        <div
          id="mk-lg-drv"
          class="clip"
          data-start="0"
          data-duration="7"
          data-track-index="0"
          style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
        ></div>
      </div>
      <script>
        (function () {
          window.__timelines = window.__timelines || {};

          /* ---- published parameters (inspector-style CONFIG) ----
             A minimal presentation line graph: 1–2 series draw on, dots pop, value labels
             ride their points, axes dimmer than labels (clear hierarchy). */
          var CONFIG = {
            // no scheme param — scheme-agnostic; re-skin via the --mk-* tokens
            series: [
              { name: "Renders", color: null /* accent */, values: [12, 26, 22, 38, 44, 58] },
              { name: "Projects", color: null /* blob-2 */, values: [8, 14, 18, 16, 28, 36] },
            ],
            xLabels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
            showValues: true,
            area: { x: 160, y: 240, w: 980, h: 500 }, // plot rectangle
            animationIn: true,
            animationOut: true,
          };

          var DUR = 7;
          var NS = "http://www.w3.org/2000/svg";
          var root = document.getElementById("mk-lg-root");
          var svg = document.getElementById("mk-lg-svg");
          var labels = document.getElementById("mk-lg-labels");
          var A = CONFIG.area;

          var accent = getComputedStyle(root).getPropertyValue("--mk-accent").trim();
          var blob2 = getComputedStyle(root).getPropertyValue("--mk-blob-2").trim();
          if (CONFIG.series[1] && !CONFIG.series[1].color) CONFIG.series[1].color = blob2;

          /* scale: 0..max(all values)*1.15 into the plot rect */
          var max = 0;
          CONFIG.series.forEach(function (s) {
            s.values.forEach(function (v) {
              if (v > max) max = v;
            });
          });
          max *= 1.15;
          var n = CONFIG.series[0].values.length;
          function px(i) {
            return A.x + (i / (n - 1)) * A.w;
          }
          function py(v) {
            return A.y + A.h - (v / max) * A.h;
          }

          /* baseline axis */
          var axis = document.createElementNS(NS, "line");
          axis.setAttribute("class", "mk-lg-axis");
          axis.setAttribute("x1", A.x - 20);
          axis.setAttribute("y1", A.y + A.h);
          axis.setAttribute("x2", A.x + A.w + 20);
          axis.setAttribute("y2", A.y + A.h);
          axis.id = "mk-lg-axis";
          svg.appendChild(axis);

          /* series paths, dots, value labels */
          var paths = [],
            dots = [],
            vals = [];
          CONFIG.series.forEach(function (s, si) {
            var color = s.color || accent;
            var d = s.values
              .map(function (v, i) {
                return (i === 0 ? "M" : "L") + px(i) + " " + py(v);
              })
              .join(" ");
            var path = document.createElementNS(NS, "path");
            path.setAttribute("class", "mk-lg-line");
            path.setAttribute("d", d);
            path.setAttribute("stroke", color);
            path.id = "mk-lg-path-" + si;
            svg.appendChild(path);
            paths.push(path);

            s.values.forEach(function (v, i) {
              var dot = document.createElementNS(NS, "circle");
              dot.setAttribute("class", "mk-lg-dot");
              dot.setAttribute("cx", px(i));
              dot.setAttribute("cy", py(v));
              dot.setAttribute("r", 9);
              dot.setAttribute("stroke", color);
              dot.id = "mk-lg-dot-" + si + "-" + i;
              svg.appendChild(dot);
              dots.push({ el: dot, si: si, i: i });

              if (CONFIG.showValues) {
                var val = document.createElement("div");
                /* Series after the first label downward. With every series
                   labelling upward the numbers collide wherever the lines run
                   close together. GAP clears the dot radius plus line stroke. */
                var below = si > 0;
                var GAP = 22;
                val.className = "mk-lg-value";
                val.id = "mk-lg-val-" + si + "-" + i;
                val.textContent = v;
                val.style.left = px(i) + "px";
                val.style.top = py(v) + (below ? GAP : -(GAP + 38)) + "px";
                labels.appendChild(val);
                vals.push({ el: val, si: si, i: i });
              }
            });
          });

          /* x labels + legend */
          CONFIG.xLabels.slice(0, n).forEach(function (t, i) {
            var xl = document.createElement("div");
            xl.className = "mk-lg-xlabel";
            xl.id = "mk-lg-xl-" + i;
            xl.textContent = t;
            xl.style.left = px(i) + "px";
            xl.style.top = A.y + A.h + 24 + "px";
            labels.appendChild(xl);
          });
          var legend = document.createElement("div");
          legend.className = "mk-lg-legend";
          legend.id = "mk-lg-legend";
          legend.style.left = A.x + "px";
          legend.style.top = A.y + A.h + 90 + "px";
          CONFIG.series.forEach(function (s) {
            var item = document.createElement("div");
            item.className = "mk-lg-legend-item";
            var dot = document.createElement("span");
            dot.className = "mk-lg-legend-dot";
            dot.style.background = s.color || accent;
            item.appendChild(dot);
            item.appendChild(document.createTextNode(s.name));
            legend.appendChild(item);
          });
          labels.appendChild(legend);

          var tl = gsap.timeline({ paused: true });
          var DRAW = 1.3;

          /* entrance: axis + x labels settle first */
          gsap.set([axis, legend], { opacity: 0 });
          tl.to(axis, { opacity: 1, duration: 0.5, ease: "power2.out" }, 0.2);
          CONFIG.xLabels.slice(0, n).forEach(function (t, i) {
            var xl = document.getElementById("mk-lg-xl-" + i);
            gsap.set(xl, { opacity: 0, y: 10 });
            tl.to(xl, { opacity: 1, y: 0, duration: 0.4, ease: "power2.out" }, 0.25 + i * 0.05);
          });

          /* draw each series, dots + values riding the draw front */
          CONFIG.series.forEach(function (s, si) {
            var t0 = (CONFIG.animationIn ? 0.5 : 0) + si * 0.35;
            var path = paths[si];
            var len = path.getTotalLength();
            gsap.set(path, { strokeDasharray: len, strokeDashoffset: len });
            tl.to(path, { strokeDashoffset: 0, duration: DRAW, ease: "power2.inOut" }, t0);

            s.values.forEach(function (v, i) {
              var dot = document.getElementById("mk-lg-dot-" + si + "-" + i);
              var td = t0 + (i / (n - 1)) * DRAW * 0.92;
              gsap.set(dot, { scale: 0, transformOrigin: "50% 50%" });
              tl.to(dot, { scale: 1, duration: 0.35, ease: "back.out(1.2)" }, td);
              if (CONFIG.showValues) {
                var val = document.getElementById("mk-lg-val-" + si + "-" + i);
                gsap.set(val, { opacity: 0, y: 8 });
                tl.to(val, { opacity: 1, y: 0, duration: 0.35, ease: "power2.out" }, td + 0.06);
              }
            });
          });

          tl.to(legend, { opacity: 1, duration: 0.5, ease: "power2.out" }, 2.3);

          /* exit + hard kill */
          if (CONFIG.animationOut) {
            tl.to([svg, labels], { opacity: 0, y: -20, duration: 0.4, ease: "power2.in" }, DUR - 0.5);
          }
          tl.set(root, { visibility: "hidden" }, DUR - 0.02);

          window.__timelines["mk-line-graph"] = tl;
        })();
      </script>
    </body>
  </html>
  ```
</Accordion>

Tagged `data-viz` `chart` `graph` `minimal`.

## Related topics

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